diff --git a/public/app/features/explore/TraceView/components/model/trace-viewer.test.ts b/public/app/features/explore/TraceView/components/model/trace-viewer.test.ts new file mode 100644 index 00000000000..2895b6c0013 --- /dev/null +++ b/public/app/features/explore/TraceView/components/model/trace-viewer.test.ts @@ -0,0 +1,80 @@ +import { TraceSpan } from '../types'; + +import { findHeaderTags } from './trace-viewer'; + +describe('findHeaderTags()', () => { + it('return empty object when no spans are provided', () => { + const spans: TraceSpan[] = []; + expect(findHeaderTags(spans)).toEqual({}); + }); + + it('return header tags when spans follow the OTEL semantic convention', () => { + const spans: TraceSpan[] = [ + // @ts-ignore + { + tags: [ + { key: 'http.request.method', value: 'GET' }, + { key: 'http.response.status_code', value: '200' }, + { key: 'http.route', value: '/api/users' }, + ], + }, + ]; + expect(findHeaderTags(spans)).toEqual({ + method: [{ key: 'http.request.method', value: 'GET' }], + status: [{ key: 'http.response.status_code', value: '200' }], + url: [{ key: 'http.route', value: '/api/users' }], + }); + }); + + it('return header tags when spans follow the alternative convention', () => { + const spans: TraceSpan[] = [ + // @ts-ignore + { + tags: [ + { key: 'http.method', value: 'GET' }, + { key: 'http.status_code', value: '200' }, + { key: 'http.path', value: '/api/users' }, + ], + }, + // @ts-ignore + { + tags: [ + { key: 'http.method', value: 'POST' }, + { key: 'http.status_code', value: '404' }, + { key: 'http.path', value: '/api/posts' }, + ], + }, + ]; + expect(findHeaderTags(spans)).toEqual({ + method: [{ key: 'http.method', value: 'GET' }], + status: [{ key: 'http.status_code', value: '200' }], + url: [{ key: 'http.path', value: '/api/users' }], + }); + }); + + it('return header tags, prioritizing the spans that follow the OTEL semantinc convention', () => { + const spans: TraceSpan[] = [ + // @ts-ignore + { + tags: [ + { key: 'http.method', value: 'GET' }, + { key: 'http.status', value: '200' }, + { key: 'http.path', value: '/api/users' }, + ], + }, + // @ts-ignore + { + tags: [ + { key: 'http.request.method', value: 'POST' }, + { key: 'http.response.status_code', value: '404' }, + { key: 'http.route', value: '/api/users' }, + ], + }, + ]; + expect(findHeaderTags(spans)).toEqual({ + method: [{ key: 'http.request.method', value: 'POST' }], + status: [{ key: 'http.response.status_code', value: '404' }], + url: [{ key: 'http.route', value: '/api/users' }], + }); + }); +}); diff --git a/public/app/features/explore/TraceView/components/model/trace-viewer.ts b/public/app/features/explore/TraceView/components/model/trace-viewer.ts index 9e68aa9ea45..14a359a33b9 100644 --- a/public/app/features/explore/TraceView/components/model/trace-viewer.ts +++ b/public/app/features/explore/TraceView/components/model/trace-viewer.ts @@ -56,7 +56,34 @@ export const getTraceName = memoize(_getTraceNameImpl, (spans: TraceSpan[]) => { return spans[0].traceID; }); +// Find header tags according to either old standard (e..g, `http.method`) or the +// standard OTEL semantic convention, as per https://opentelemetry.io/docs/specs/semconv/http/http-spans +// (e.g., `http.request.method`). Spans following the OTEL semantic convention are prioritized. +// +// Note that we are ignoring these cases: +// - conventions are mixed, e.g., a span with method in `http.method` but status code in `http.response.status_code` +// - tags are not in the same span, e.g., method in spans[0] but status in spans[1] export function findHeaderTags(spans: TraceSpan[]) { + // OTEL semantic convention + for (let i = 0; i < spans.length; i++) { + const method = spans[i].tags.filter((tag) => { + return tag.key === 'http.request.method'; + }); + + const status = spans[i].tags.filter((tag) => { + return tag.key === 'http.response.status_code'; + }); + + const url = spans[i].tags.filter((tag) => { + return tag.key === 'http.route'; + }); + + if (method.length > 0 || status.length > 0 || url.length > 0) { + return { method, status, url }; + } + } + + // Non-standard convention for (let i = 0; i < spans.length; i++) { const method = spans[i].tags.filter((tag) => { return tag.key === 'http.method'; @@ -74,6 +101,7 @@ export function findHeaderTags(spans: TraceSpan[]) { return { method, status, url }; } } + return {}; }