Skip to content

Commit 5615968

Browse files
committed
unit tests
1 parent b1d574e commit 5615968

2 files changed

Lines changed: 157 additions & 2 deletions

File tree

packages/core/src/tracing/spans/captureSpan.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,8 @@ const SPAN_KIND_CLIENT = 2;
173173
* Backfills: `sentry.op`, `sentry.source`, and `name` (description).
174174
* Uses `safeSetSpanJSONAttributes` so explicitly set attributes are never overwritten.
175175
*/
176-
function inferSpanDataFromOtelAttributes(spanJSON: StreamedSpanJSON, spanKind?: number): void {
176+
/** Exported only for tests. */
177+
export function inferSpanDataFromOtelAttributes(spanJSON: StreamedSpanJSON, spanKind?: number): void {
177178
const attributes = spanJSON.attributes;
178179
if (!attributes) {
179180
return;

packages/core/test/lib/tracing/spans/captureSpan.test.ts

Lines changed: 155 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import {
2121
withScope,
2222
withStreamedSpan,
2323
} from '../../../../src';
24-
import { safeSetSpanJSONAttributes } from '../../../../src/tracing/spans/captureSpan';
24+
import { inferSpanDataFromOtelAttributes, safeSetSpanJSONAttributes } from '../../../../src/tracing/spans/captureSpan';
2525
import { getDefaultTestClientOptions, TestClient } from '../../../mocks/client';
2626

2727
describe('captureSpan', () => {
@@ -483,3 +483,157 @@ describe('safeSetSpanJSONAttributes', () => {
483483
expect(spanJSON.attributes).toEqual({});
484484
});
485485
});
486+
487+
describe('inferSpanDataFromOtelAttributes', () => {
488+
function makeSpanJSON(name: string, attributes: Record<string, unknown>): StreamedSpanJSON {
489+
return {
490+
name,
491+
span_id: 'abc123',
492+
trace_id: 'def456',
493+
start_timestamp: 0,
494+
end_timestamp: 1,
495+
status: 'ok',
496+
is_segment: false,
497+
attributes,
498+
};
499+
}
500+
501+
describe('http spans', () => {
502+
it('infers http.client op for CLIENT kind', () => {
503+
const spanJSON = makeSpanJSON('GET', { 'http.request.method': 'GET' });
504+
inferSpanDataFromOtelAttributes(spanJSON, 2); // SPAN_KIND_CLIENT
505+
expect(spanJSON.attributes?.['sentry.op']).toBe('http.client');
506+
});
507+
508+
it('infers http.server op for SERVER kind', () => {
509+
const spanJSON = makeSpanJSON('GET', { 'http.request.method': 'GET' });
510+
inferSpanDataFromOtelAttributes(spanJSON, 1); // SPAN_KIND_SERVER
511+
expect(spanJSON.attributes?.['sentry.op']).toBe('http.server');
512+
});
513+
514+
it('infers http op when kind is unknown', () => {
515+
const spanJSON = makeSpanJSON('GET', { 'http.request.method': 'GET' });
516+
inferSpanDataFromOtelAttributes(spanJSON);
517+
expect(spanJSON.attributes?.['sentry.op']).toBe('http');
518+
});
519+
520+
it('appends prefetch to op', () => {
521+
const spanJSON = makeSpanJSON('GET', { 'http.request.method': 'GET', 'sentry.http.prefetch': true });
522+
inferSpanDataFromOtelAttributes(spanJSON, 2);
523+
expect(spanJSON.attributes?.['sentry.op']).toBe('http.client.prefetch');
524+
});
525+
526+
it('sets name and source from http.route', () => {
527+
const spanJSON = makeSpanJSON('GET', { 'http.request.method': 'GET', 'http.route': '/users/:id' });
528+
inferSpanDataFromOtelAttributes(spanJSON, 1);
529+
expect(spanJSON.name).toBe('GET /users/:id');
530+
expect(spanJSON.attributes?.['sentry.source']).toBe('route');
531+
});
532+
533+
it('does not overwrite name when no http.route', () => {
534+
const spanJSON = makeSpanJSON('GET', { 'http.request.method': 'GET', 'url.full': 'http://example.com/api' });
535+
inferSpanDataFromOtelAttributes(spanJSON, 2);
536+
expect(spanJSON.name).toBe('GET');
537+
});
538+
539+
it('does not overwrite sentry.op if already set', () => {
540+
const spanJSON = makeSpanJSON('GET', { 'http.request.method': 'GET', 'sentry.op': 'http.client.custom' });
541+
inferSpanDataFromOtelAttributes(spanJSON, 2);
542+
expect(spanJSON.attributes?.['sentry.op']).toBe('http.client.custom');
543+
});
544+
545+
it('restores custom span name from sentry.custom_span_name', () => {
546+
const spanJSON = makeSpanJSON('overwritten-by-otel', {
547+
'http.request.method': 'GET',
548+
'sentry.custom_span_name': 'my-custom-name',
549+
'sentry.source': 'custom',
550+
'http.route': '/users/:id',
551+
});
552+
inferSpanDataFromOtelAttributes(spanJSON, 1);
553+
expect(spanJSON.name).toBe('my-custom-name');
554+
});
555+
556+
it('does not overwrite name when sentry.source is custom', () => {
557+
const spanJSON = makeSpanJSON('my-name', {
558+
'http.request.method': 'GET',
559+
'sentry.source': 'custom',
560+
'http.route': '/users/:id',
561+
});
562+
inferSpanDataFromOtelAttributes(spanJSON, 1);
563+
expect(spanJSON.name).toBe('my-name');
564+
});
565+
566+
it('supports legacy http.method attribute', () => {
567+
const spanJSON = makeSpanJSON('GET', { 'http.method': 'GET' });
568+
inferSpanDataFromOtelAttributes(spanJSON, 2);
569+
expect(spanJSON.attributes?.['sentry.op']).toBe('http.client');
570+
});
571+
});
572+
573+
describe('db spans', () => {
574+
it('infers db op', () => {
575+
const spanJSON = makeSpanJSON('redis', { 'db.system': 'redis' });
576+
inferSpanDataFromOtelAttributes(spanJSON);
577+
expect(spanJSON.attributes?.['sentry.op']).toBe('db');
578+
});
579+
580+
it('sets name from db.statement', () => {
581+
const spanJSON = makeSpanJSON('mysql', { 'db.system': 'mysql', 'db.statement': 'SELECT * FROM users' });
582+
inferSpanDataFromOtelAttributes(spanJSON);
583+
expect(spanJSON.name).toBe('SELECT * FROM users');
584+
expect(spanJSON.attributes?.['sentry.source']).toBe('task');
585+
});
586+
587+
it('skips db inference for cache spans', () => {
588+
const spanJSON = makeSpanJSON('cache-get', { 'db.system': 'redis', 'sentry.op': 'cache.get_item' });
589+
inferSpanDataFromOtelAttributes(spanJSON);
590+
expect(spanJSON.attributes?.['sentry.op']).toBe('cache.get_item');
591+
expect(spanJSON.name).toBe('cache-get');
592+
});
593+
594+
it('restores custom span name from sentry.custom_span_name', () => {
595+
const spanJSON = makeSpanJSON('overwritten', {
596+
'db.system': 'mysql',
597+
'db.statement': 'SELECT 1',
598+
'sentry.custom_span_name': 'my-db-span',
599+
'sentry.source': 'custom',
600+
});
601+
inferSpanDataFromOtelAttributes(spanJSON);
602+
expect(spanJSON.name).toBe('my-db-span');
603+
});
604+
});
605+
606+
describe('other span types', () => {
607+
it('infers rpc op', () => {
608+
const spanJSON = makeSpanJSON('grpc', { 'rpc.service': 'UserService' });
609+
inferSpanDataFromOtelAttributes(spanJSON);
610+
expect(spanJSON.attributes?.['sentry.op']).toBe('rpc');
611+
});
612+
613+
it('infers message op', () => {
614+
const spanJSON = makeSpanJSON('kafka', { 'messaging.system': 'kafka' });
615+
inferSpanDataFromOtelAttributes(spanJSON);
616+
expect(spanJSON.attributes?.['sentry.op']).toBe('message');
617+
});
618+
619+
it('infers faas op from trigger', () => {
620+
const spanJSON = makeSpanJSON('lambda', { 'faas.trigger': 'http' });
621+
inferSpanDataFromOtelAttributes(spanJSON);
622+
expect(spanJSON.attributes?.['sentry.op']).toBe('http');
623+
});
624+
});
625+
626+
it('does nothing when attributes are missing', () => {
627+
const spanJSON = makeSpanJSON('test', undefined as unknown as Record<string, unknown>);
628+
spanJSON.attributes = undefined;
629+
inferSpanDataFromOtelAttributes(spanJSON, 2);
630+
expect(spanJSON.attributes).toBeUndefined();
631+
});
632+
633+
it('does nothing for spans without recognizable attributes', () => {
634+
const spanJSON = makeSpanJSON('test', { 'custom.attr': 'value' });
635+
inferSpanDataFromOtelAttributes(spanJSON);
636+
expect(spanJSON.attributes?.['sentry.op']).toBeUndefined();
637+
expect(spanJSON.name).toBe('test');
638+
});
639+
});

0 commit comments

Comments
 (0)