mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
# Backport This will backport the following commits from `main` to `8.17`: - [[APM][OTel] Add url.full fallback (#215397)](https://github.com/elastic/kibana/pull/215397) <!--- Backport version: 9.6.6 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sorenlouv/backport) <!--BACKPORT [{"author":{"name":"jennypavlova","email":"dzheni.pavlova@elastic.co"},"sourceCommit":{"committedDate":"2025-04-01T09:50:28Z","message":"[APM][OTel] Add url.full fallback (#215397)\n\n## Summary\n\nThis PR fixes the missing URL in the transaction summary \n\n## Testing [_UPDATED_]\n- [SOLVED ✅ ⬇️ ] ~~This is tricky to test ( I am trying to create a\nserverless instance from this PR and it should make it easier)~~\n- Testing on serverless (the env linked in the PR) \n - EDOT service (I run locally in Docker and connect to the env): \n<img width=\"1904\" alt=\"image\"\nsrc=\"https://github.com/user-attachments/assets/c3a7ab56-5b8f-42a5-8033-55ccbb915b40\"\n/>\n\n - Other generated service (from the env):\n\n\n \n- In the meantime \n - using synthtrace: Case to run/expectation\n- `node scripts/synthtrace otel_edot_simple_trace.ts` / The trace\nsummary should be visible\n \n\n\n\n- `node scripts/synthtrace simple_trace.ts` / The trace summary should\nstill be visible (using `url.full` in this case)\n \n\n","sha":"a987209d3fc6c10153181781bdf1c8c829ceba04","branchLabelMapping":{"^v9.1.0$":"main","^v8.19.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:fix","backport:prev-minor","backport:prev-major","ci:build-serverless-image","ci:project-deploy-observability","Team:obs-ux-infra_services","ci:project-redeploy","v9.1.0"],"title":"[APM][OTel] Add url.full fallback","number":215397,"url":"https://github.com/elastic/kibana/pull/215397","mergeCommit":{"message":"[APM][OTel] Add url.full fallback (#215397)\n\n## Summary\n\nThis PR fixes the missing URL in the transaction summary \n\n## Testing [_UPDATED_]\n- [SOLVED ✅ ⬇️ ] ~~This is tricky to test ( I am trying to create a\nserverless instance from this PR and it should make it easier)~~\n- Testing on serverless (the env linked in the PR) \n - EDOT service (I run locally in Docker and connect to the env): \n<img width=\"1904\" alt=\"image\"\nsrc=\"https://github.com/user-attachments/assets/c3a7ab56-5b8f-42a5-8033-55ccbb915b40\"\n/>\n\n - Other generated service (from the env):\n\n\n \n- In the meantime \n - using synthtrace: Case to run/expectation\n- `node scripts/synthtrace otel_edot_simple_trace.ts` / The trace\nsummary should be visible\n \n\n\n\n- `node scripts/synthtrace simple_trace.ts` / The trace summary should\nstill be visible (using `url.full` in this case)\n \n\n","sha":"a987209d3fc6c10153181781bdf1c8c829ceba04"}},"sourceBranch":"main","suggestedTargetBranches":[],"targetPullRequestStates":[{"branch":"main","label":"v9.1.0","branchLabelMappingKey":"^v9.1.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/215397","number":215397,"mergeCommit":{"message":"[APM][OTel] Add url.full fallback (#215397)\n\n## Summary\n\nThis PR fixes the missing URL in the transaction summary \n\n## Testing [_UPDATED_]\n- [SOLVED ✅ ⬇️ ] ~~This is tricky to test ( I am trying to create a\nserverless instance from this PR and it should make it easier)~~\n- Testing on serverless (the env linked in the PR) \n - EDOT service (I run locally in Docker and connect to the env): \n<img width=\"1904\" alt=\"image\"\nsrc=\"https://github.com/user-attachments/assets/c3a7ab56-5b8f-42a5-8033-55ccbb915b40\"\n/>\n\n - Other generated service (from the env):\n\n\n \n- In the meantime \n - using synthtrace: Case to run/expectation\n- `node scripts/synthtrace otel_edot_simple_trace.ts` / The trace\nsummary should be visible\n \n\n\n\n- `node scripts/synthtrace simple_trace.ts` / The trace summary should\nstill be visible (using `url.full` in this case)\n \n\n","sha":"a987209d3fc6c10153181781bdf1c8c829ceba04"}}]}] BACKPORT-->
This commit is contained in:
parent
1100183e7b
commit
3af7a2e42a
14 changed files with 218 additions and 6 deletions
|
@ -191,13 +191,19 @@ export const METRIC_OTEL_JVM_SYSTEM_CPU_PERCENT = 'process.runtime.jvm.system.cp
|
|||
export const METRIC_OTEL_JVM_GC_DURATION = 'process.runtime.jvm.gc.duration';
|
||||
export const VALUE_OTEL_JVM_PROCESS_MEMORY_HEAP = 'heap';
|
||||
export const VALUE_OTEL_JVM_PROCESS_MEMORY_NON_HEAP = 'non_heap';
|
||||
|
||||
// OpenTelemetry semconv fields for AgentName https://opentelemetry.io/docs/specs/semconv/resource/#telemetry-sdk
|
||||
export const TELEMETRY_SDK_NAME = 'telemetry.sdk.name';
|
||||
export const TELEMETRY_SDK_LANGUAGE = 'telemetry.sdk.language';
|
||||
export const TELEMETRY_SDK_VERSION = 'telemetry.sdk.version';
|
||||
|
||||
// OpenTelemetry span links
|
||||
// OpenTelemetry semconv fields for HTTP server https://opentelemetry.io/docs/specs/semconv/http/http-spans/#http-server-semantic-conventions
|
||||
export const URL_PATH = 'url.path';
|
||||
export const URL_SCHEME = 'url.scheme';
|
||||
export const SERVER_ADDRESS = 'server.address';
|
||||
export const SERVER_PORT = 'server.port';
|
||||
|
||||
// OpenTelemetry span links
|
||||
export const LINKS_SPAN_ID = 'links.span_id';
|
||||
export const LINKS_TRACE_ID = 'links.trace_id';
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ import {
|
|||
Url,
|
||||
User,
|
||||
} from './fields';
|
||||
import { Server } from './fields/server';
|
||||
|
||||
export interface Processor {
|
||||
name: 'error';
|
||||
|
@ -72,5 +73,6 @@ export interface ErrorRaw extends APMBaseDoc {
|
|||
process?: Process;
|
||||
service: Service;
|
||||
url?: Url;
|
||||
server?: Server;
|
||||
user?: User;
|
||||
}
|
||||
|
|
13
packages/kbn-apm-types/src/es_schemas/raw/fields/server.ts
Normal file
13
packages/kbn-apm-types/src/es_schemas/raw/fields/server.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the "Elastic License
|
||||
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
export interface Server {
|
||||
address?: string;
|
||||
port?: string;
|
||||
}
|
|
@ -11,4 +11,6 @@ export interface Url {
|
|||
domain?: string;
|
||||
full?: string;
|
||||
original?: string;
|
||||
scheme?: string;
|
||||
path?: string;
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
import { APMBaseDoc } from './apm_base_doc';
|
||||
import { EventOutcome } from './fields/event_outcome';
|
||||
import { Http } from './fields/http';
|
||||
import { Server } from './fields/server';
|
||||
import { SpanLink } from './fields/span_links';
|
||||
import { Stackframe } from './fields/stackframe';
|
||||
import { TimestampUs } from './fields/timestamp_us';
|
||||
|
@ -78,4 +79,5 @@ export interface SpanRaw extends APMBaseDoc {
|
|||
};
|
||||
http?: Http;
|
||||
url?: Url;
|
||||
server?: Server;
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ import { User } from './fields/user';
|
|||
import { UserAgent } from './fields/user_agent';
|
||||
import { Faas } from './fields/faas';
|
||||
import { SpanLink } from './fields/span_links';
|
||||
import { Server } from './fields/server';
|
||||
|
||||
interface Processor {
|
||||
name: 'transaction';
|
||||
|
@ -66,6 +67,7 @@ export interface TransactionRaw extends APMBaseDoc {
|
|||
ecs?: { version?: string };
|
||||
host?: Host;
|
||||
http?: Http;
|
||||
server?: Server;
|
||||
kubernetes?: Kubernetes;
|
||||
process?: Process;
|
||||
service: Service;
|
||||
|
|
|
@ -256,6 +256,10 @@ exports[`Error PROCESSOR_EVENT 1`] = `"error"`;
|
|||
|
||||
exports[`Error PROCESSOR_NAME 1`] = `"error"`;
|
||||
|
||||
exports[`Error SERVER_ADDRESS 1`] = `undefined`;
|
||||
|
||||
exports[`Error SERVER_PORT 1`] = `undefined`;
|
||||
|
||||
exports[`Error SERVICE 1`] = `
|
||||
Object {
|
||||
"language": Object {
|
||||
|
@ -372,6 +376,10 @@ exports[`Error TRANSACTION_TYPE 1`] = `"request"`;
|
|||
|
||||
exports[`Error URL_FULL 1`] = `undefined`;
|
||||
|
||||
exports[`Error URL_PATH 1`] = `undefined`;
|
||||
|
||||
exports[`Error URL_SCHEME 1`] = `undefined`;
|
||||
|
||||
exports[`Error USER_AGENT_NAME 1`] = `undefined`;
|
||||
|
||||
exports[`Error USER_AGENT_ORIGINAL 1`] = `undefined`;
|
||||
|
@ -627,6 +635,10 @@ exports[`Span PROCESSOR_EVENT 1`] = `"span"`;
|
|||
|
||||
exports[`Span PROCESSOR_NAME 1`] = `"transaction"`;
|
||||
|
||||
exports[`Span SERVER_ADDRESS 1`] = `undefined`;
|
||||
|
||||
exports[`Span SERVER_PORT 1`] = `undefined`;
|
||||
|
||||
exports[`Span SERVICE 1`] = `
|
||||
Object {
|
||||
"name": "service name",
|
||||
|
@ -739,6 +751,10 @@ exports[`Span TRANSACTION_TYPE 1`] = `undefined`;
|
|||
|
||||
exports[`Span URL_FULL 1`] = `undefined`;
|
||||
|
||||
exports[`Span URL_PATH 1`] = `undefined`;
|
||||
|
||||
exports[`Span URL_SCHEME 1`] = `undefined`;
|
||||
|
||||
exports[`Span USER_AGENT_NAME 1`] = `undefined`;
|
||||
|
||||
exports[`Span USER_AGENT_ORIGINAL 1`] = `undefined`;
|
||||
|
@ -1008,6 +1024,10 @@ exports[`Transaction PROCESSOR_EVENT 1`] = `"transaction"`;
|
|||
|
||||
exports[`Transaction PROCESSOR_NAME 1`] = `"transaction"`;
|
||||
|
||||
exports[`Transaction SERVER_ADDRESS 1`] = `undefined`;
|
||||
|
||||
exports[`Transaction SERVER_PORT 1`] = `undefined`;
|
||||
|
||||
exports[`Transaction SERVICE 1`] = `
|
||||
Object {
|
||||
"language": Object {
|
||||
|
@ -1124,6 +1144,10 @@ exports[`Transaction TRANSACTION_TYPE 1`] = `"transaction type"`;
|
|||
|
||||
exports[`Transaction URL_FULL 1`] = `"http://www.elastic.co"`;
|
||||
|
||||
exports[`Transaction URL_PATH 1`] = `undefined`;
|
||||
|
||||
exports[`Transaction URL_SCHEME 1`] = `undefined`;
|
||||
|
||||
exports[`Transaction USER_AGENT_NAME 1`] = `"Other"`;
|
||||
|
||||
exports[`Transaction USER_AGENT_ORIGINAL 1`] = `"test original"`;
|
||||
|
|
|
@ -63,7 +63,7 @@ export function WaterfallWithSummary<TSample extends {}>({
|
|||
waterfallFetchStatus === FETCH_STATUS.LOADING ||
|
||||
traceSamplesFetchStatus === FETCH_STATUS.LOADING;
|
||||
// When traceId is not present, call to waterfallFetchResult will not be initiated
|
||||
const isSucceded =
|
||||
const isSucceeded =
|
||||
(waterfallFetchStatus === FETCH_STATUS.SUCCESS ||
|
||||
waterfallFetchStatus === FETCH_STATUS.NOT_INITIATED) &&
|
||||
traceSamplesFetchStatus === FETCH_STATUS.SUCCESS;
|
||||
|
@ -90,7 +90,7 @@ export function WaterfallWithSummary<TSample extends {}>({
|
|||
|
||||
const { entryTransaction } = waterfallFetchResult;
|
||||
|
||||
if (!entryTransaction && traceSamples?.length === 0 && isSucceded) {
|
||||
if (!entryTransaction && traceSamples?.length === 0 && isSucceeded) {
|
||||
return (
|
||||
<EuiEmptyPrompt
|
||||
title={
|
||||
|
|
|
@ -24,7 +24,7 @@ const Url = euiStyled('span')`
|
|||
interface HttpInfoProps {
|
||||
method?: string;
|
||||
status?: number;
|
||||
url: string;
|
||||
url?: string;
|
||||
}
|
||||
|
||||
const Span = euiStyled('span')`
|
||||
|
|
|
@ -15,6 +15,7 @@ import { HttpInfoSummaryItem } from './http_info_summary_item';
|
|||
import { TransactionResultSummaryItem } from './transaction_result_summary_item';
|
||||
import { UserAgentSummaryItem } from './user_agent_summary_item';
|
||||
import { ColdStartBadge } from '../../app/transaction_details/waterfall_with_summary/waterfall_container/waterfall/badge/cold_start_badge';
|
||||
import { buildUrl } from '../../../utils/build_url';
|
||||
|
||||
interface Props {
|
||||
transaction: Transaction;
|
||||
|
@ -25,12 +26,13 @@ interface Props {
|
|||
|
||||
function getTransactionResultSummaryItem(transaction: Transaction) {
|
||||
const result = transaction.transaction.result;
|
||||
const url = transaction.url?.full || transaction.transaction?.page?.url;
|
||||
const urlFull = transaction.url?.full || transaction.transaction?.page?.url;
|
||||
|
||||
const url = urlFull ?? buildUrl(transaction);
|
||||
|
||||
if (url) {
|
||||
const method = transaction.http?.request?.method;
|
||||
const status = transaction.http?.response?.status_code;
|
||||
|
||||
return <HttpInfoSummaryItem method={method} status={status} url={url} />;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,115 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { buildUrl } from './build_url';
|
||||
import type { Transaction } from '../../typings/es_schemas/ui/transaction';
|
||||
|
||||
describe('buildUrl', () => {
|
||||
it('should return a full URL when all fields are provided', () => {
|
||||
const item = {
|
||||
url: {
|
||||
scheme: 'ftp',
|
||||
path: '/some/path',
|
||||
},
|
||||
server: {
|
||||
address: 'example.com',
|
||||
port: 443,
|
||||
},
|
||||
};
|
||||
const result = buildUrl(item as unknown as Transaction);
|
||||
expect(result).toBe('ftp://example.com:443/some/path');
|
||||
});
|
||||
|
||||
it('should return a URL without a port if the port is not provided', () => {
|
||||
const item = {
|
||||
url: {
|
||||
scheme: 'http',
|
||||
path: '/another/path',
|
||||
},
|
||||
server: {
|
||||
address: 'example.org',
|
||||
},
|
||||
};
|
||||
const result = buildUrl(item as Transaction);
|
||||
expect(result).toBe('http://example.org/another/path');
|
||||
});
|
||||
|
||||
it('should return a URL without a path if the path is not provided', () => {
|
||||
const item = {
|
||||
url: {
|
||||
scheme: 'https',
|
||||
},
|
||||
server: {
|
||||
address: 'example.net',
|
||||
port: 8443,
|
||||
},
|
||||
};
|
||||
const result = buildUrl(item as unknown as Transaction);
|
||||
expect(result).toBe('https://example.net:8443/');
|
||||
});
|
||||
|
||||
it('should return undefined if the scheme is missing', () => {
|
||||
const item = {
|
||||
url: {
|
||||
path: '/missing/scheme',
|
||||
},
|
||||
server: {
|
||||
address: 'example.com',
|
||||
port: 8080,
|
||||
},
|
||||
};
|
||||
const result = buildUrl(item as unknown as Transaction);
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return undefined if the server address is missing', () => {
|
||||
const item = {
|
||||
url: {
|
||||
scheme: 'https',
|
||||
path: '/missing/address',
|
||||
},
|
||||
server: {
|
||||
port: 8080,
|
||||
},
|
||||
};
|
||||
const result = buildUrl(item as unknown as Transaction);
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return undefined and log an error if the port is invalid', () => {
|
||||
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
|
||||
const item = {
|
||||
url: {
|
||||
scheme: 'https',
|
||||
path: '/invalid/port',
|
||||
},
|
||||
server: {
|
||||
address: 'example.com',
|
||||
port: 'invalid-port',
|
||||
},
|
||||
};
|
||||
|
||||
const result = buildUrl(item as unknown as Transaction);
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
||||
'Failed to build URL',
|
||||
expect.objectContaining({
|
||||
message: 'Invalid base URL: https://example.com:invalid-port',
|
||||
})
|
||||
);
|
||||
|
||||
consoleErrorSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('should handle an empty object gracefully', () => {
|
||||
const item = {};
|
||||
const result = buildUrl(item as Transaction);
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import type { Transaction } from '../../typings/es_schemas/ui/transaction';
|
||||
import type { Span } from '../../typings/es_schemas/ui/span';
|
||||
import type { APMError } from '../../typings/es_schemas/ui/apm_error';
|
||||
|
||||
export const buildUrl = (item: Transaction | Span | APMError) => {
|
||||
// URL fields from Otel
|
||||
const urlScheme = item?.url?.scheme;
|
||||
const urlPath = item?.url?.path;
|
||||
const serverAddress = item?.server?.address;
|
||||
const serverPort = item?.server?.port;
|
||||
|
||||
const hasURLFromFields = urlScheme && serverAddress;
|
||||
|
||||
const urlServerPort = serverPort ? `:${serverPort}` : '';
|
||||
|
||||
try {
|
||||
const url = hasURLFromFields
|
||||
? new URL(urlPath ?? '', `${urlScheme}://${serverAddress}${urlServerPort}`).toString()
|
||||
: undefined;
|
||||
|
||||
return url;
|
||||
} catch (e) {
|
||||
console.error('Failed to build URL', e);
|
||||
return undefined;
|
||||
}
|
||||
};
|
|
@ -34,6 +34,10 @@ Object {
|
|||
"http.response.status_code",
|
||||
"http.request.method",
|
||||
"user_agent.name",
|
||||
"url.path",
|
||||
"url.scheme",
|
||||
"server.address",
|
||||
"server.port",
|
||||
"user_agent.version",
|
||||
],
|
||||
"query": Object {
|
||||
|
|
|
@ -30,6 +30,10 @@ import {
|
|||
HTTP_RESPONSE_STATUS_CODE,
|
||||
TRANSACTION_PAGE_URL,
|
||||
USER_AGENT_NAME,
|
||||
URL_PATH,
|
||||
URL_SCHEME,
|
||||
SERVER_ADDRESS,
|
||||
SERVER_PORT,
|
||||
USER_AGENT_VERSION,
|
||||
} from '../../../../common/es_fields/apm';
|
||||
import { asMutableArray } from '../../../../common/utils/as_mutable_array';
|
||||
|
@ -72,6 +76,10 @@ export async function getTransaction({
|
|||
HTTP_RESPONSE_STATUS_CODE,
|
||||
HTTP_REQUEST_METHOD,
|
||||
USER_AGENT_NAME,
|
||||
URL_PATH,
|
||||
URL_SCHEME,
|
||||
SERVER_ADDRESS,
|
||||
SERVER_PORT,
|
||||
USER_AGENT_VERSION,
|
||||
] as const);
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue