11import * as OpenTelemetry from '@opentelemetry/api' ;
2- import { BasicTracerProvider , Span as OtelSpan } from '@opentelemetry/sdk-trace-base' ;
2+ import { Span as OtelSpan } from '@opentelemetry/sdk-trace-base' ;
3+ import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node' ;
34import { Hub , makeMain } from '@sentry/core' ;
45import { addExtensionMethods , Span as SentrySpan , Transaction } from '@sentry/tracing' ;
56
@@ -13,68 +14,128 @@ beforeAll(() => {
1314
1415describe ( 'SentrySpanProcessor' , ( ) => {
1516 let hub : Hub ;
17+ let provider : NodeTracerProvider ;
18+ let spanProcessor : SentrySpanProcessor ;
19+
1620 beforeEach ( ( ) => {
1721 hub = new Hub ( ) ;
1822 makeMain ( hub ) ;
1923
20- const provider = new BasicTracerProvider ( ) ;
21- provider . addSpanProcessor ( new SentrySpanProcessor ( ) ) ;
24+ spanProcessor = new SentrySpanProcessor ( ) ;
25+ provider = new NodeTracerProvider ( ) ;
26+ provider . addSpanProcessor ( spanProcessor ) ;
2227 provider . register ( ) ;
2328 } ) ;
2429
25- describe ( 'onStart' , ( ) => {
26- it ( 'create a transaction' , ( ) => {
27- const otelSpan = OpenTelemetry . trace . getTracer ( 'default' ) . startSpan ( 'GET /users' ) as OtelSpan ;
28- const sentrySpanTransaction = hub . getScope ( ) ?. getSpan ( ) as Transaction ;
29- expect ( sentrySpanTransaction ) . toBeInstanceOf ( Transaction ) ;
30+ afterEach ( async ( ) => {
31+ await provider . forceFlush ( ) ;
32+ await provider . shutdown ( ) ;
33+ } ) ;
3034
31- // Make sure name is set
32- expect ( sentrySpanTransaction ?. name ) . toBe ( 'GET /users' ) ;
35+ function getSpanForOtelSpan ( otelSpan : OtelSpan | OpenTelemetry . Span ) {
36+ return spanProcessor . _map . get ( otelSpan . spanContext ( ) . spanId ) as SentrySpan | undefined ;
37+ }
3338
34- // Enforce we use otel timestamps
35- expect ( sentrySpanTransaction . startTimestamp ) . toEqual ( otelSpan . startTime [ 0 ] ) ;
39+ it ( 'creates a transaction' , async ( ) => {
40+ const startTime = otelNumberToHrtime ( new Date ( ) . valueOf ( ) ) ;
3641
37- // Check for otel trace context
38- expect ( sentrySpanTransaction . traceId ) . toEqual ( otelSpan . spanContext ( ) . traceId ) ;
39- expect ( sentrySpanTransaction . parentSpanId ) . toEqual ( otelSpan . parentSpanId ) ;
40- expect ( sentrySpanTransaction . spanId ) . toEqual ( otelSpan . spanContext ( ) . spanId ) ;
41- } ) ;
42+ const otelSpan = provider . getTracer ( 'default' ) . startSpan ( 'GET /users' , { startTime } ) as OtelSpan ;
43+
44+ const sentrySpanTransaction = getSpanForOtelSpan ( otelSpan ) as Transaction | undefined ;
45+ expect ( sentrySpanTransaction ) . toBeInstanceOf ( Transaction ) ;
4246
43- it . only ( 'creates a child span if there is a running transaction' , ( ) => {
44- const tracer = OpenTelemetry . trace . getTracer ( 'default' ) ;
47+ expect ( sentrySpanTransaction ?. name ) . toBe ( 'GET /users' ) ;
48+ expect ( sentrySpanTransaction ?. startTimestamp ) . toEqual ( otelSpan . startTime [ 0 ] ) ;
49+ expect ( sentrySpanTransaction ?. startTimestamp ) . toEqual ( startTime [ 0 ] ) ;
50+ expect ( sentrySpanTransaction ?. traceId ) . toEqual ( otelSpan . spanContext ( ) . traceId ) ;
51+ expect ( sentrySpanTransaction ?. parentSpanId ) . toEqual ( otelSpan . parentSpanId ) ;
52+ expect ( sentrySpanTransaction ?. spanId ) . toEqual ( otelSpan . spanContext ( ) . spanId ) ;
4553
46- tracer . startActiveSpan ( 'GET /users' , parentOtelSpan => {
47- // console.log((parentOtelSpan as any).spanContext());
48- // console.log(hub.getScope()?.getSpan()?.traceId);
49- tracer . startActiveSpan ( 'SELECT * FROM users;' , child => {
50- const childOtelSpan = child as OtelSpan ;
54+ expect ( hub . getScope ( ) ?. getSpan ( ) ) . toBeUndefined ( ) ;
5155
52- const sentrySpan = hub . getScope ( ) ?. getSpan ( ) ;
53- expect ( sentrySpan ) . toBeInstanceOf ( SentrySpan ) ;
54- // console.log(hub.getScope()?.getSpan()?.traceId);
55- // console.log(sentrySpan);
56+ const endTime = otelNumberToHrtime ( new Date ( ) . valueOf ( ) ) ;
57+ otelSpan . end ( endTime ) ;
5658
57- // Make sure name is set
58- expect ( sentrySpan ?. description ) . toBe ( 'SELECT * FROM users;' ) ;
59+ expect ( sentrySpanTransaction ?. endTimestamp ) . toBe ( endTime [ 0 ] ) ;
60+ expect ( sentrySpanTransaction ?. endTimestamp ) . toBe ( otelSpan . endTime [ 0 ] ) ;
5961
60- // Enforce we use otel timestamps
61- expect ( sentrySpan ?. startTimestamp ) . toEqual ( childOtelSpan . startTime [ 0 ] ) ;
62+ expect ( hub . getScope ( ) ?. getSpan ( ) ) . toBeUndefined ( ) ;
63+ } ) ;
6264
63- // Check for otel trace context
64- expect ( sentrySpan ?. spanId ) . toEqual ( childOtelSpan . spanContext ( ) . spanId ) ;
65+ it ( 'creates a child span if there is a running transaction' , ( ) => {
66+ const tracer = provider . getTracer ( 'default' ) ;
6567
66- childOtelSpan . end ( ) ;
67- } ) ;
68+ tracer . startActiveSpan ( 'GET /users' , parentOtelSpan => {
69+ tracer . startActiveSpan ( 'SELECT * FROM users;' , child => {
70+ const childOtelSpan = child as OtelSpan ;
6871
69- parentOtelSpan . end ( ) ;
72+ const sentrySpanTransaction = getSpanForOtelSpan ( parentOtelSpan ) as Transaction | undefined ;
73+ expect ( sentrySpanTransaction ) . toBeInstanceOf ( Transaction ) ;
74+
75+ const sentrySpan = getSpanForOtelSpan ( childOtelSpan ) ;
76+ expect ( sentrySpan ) . toBeInstanceOf ( SentrySpan ) ;
77+ expect ( sentrySpan ?. description ) . toBe ( 'SELECT * FROM users;' ) ;
78+ expect ( sentrySpan ?. startTimestamp ) . toEqual ( childOtelSpan . startTime [ 0 ] ) ;
79+ expect ( sentrySpan ?. spanId ) . toEqual ( childOtelSpan . spanContext ( ) . spanId ) ;
80+ expect ( sentrySpan ?. parentSpanId ) . toEqual ( sentrySpanTransaction ?. spanId ) ;
81+
82+ expect ( hub . getScope ( ) ?. getSpan ( ) ) . toBeUndefined ( ) ;
83+
84+ const endTime = otelNumberToHrtime ( new Date ( ) . valueOf ( ) ) ;
85+ child . end ( endTime ) ;
86+
87+ expect ( sentrySpan ?. endTimestamp ) . toEqual ( childOtelSpan . endTime [ 0 ] ) ;
88+ expect ( sentrySpan ?. endTimestamp ) . toEqual ( endTime [ 0 ] ) ;
7089 } ) ;
90+
91+ parentOtelSpan . end ( ) ;
7192 } ) ;
7293 } ) ;
7394
74- // it('Creates a transaction if there is no running ', () => {
75- // const otelSpan = OpenTelemetry.trace.getTracer('default').startSpan('GET /users') as OtelSpan;
76- // processor.onStart(otelSpan, OpenTelemetry.context.active());
95+ it ( 'allows to create multiple child spans on same level' , ( ) => {
96+ const tracer = provider . getTracer ( 'default' ) ;
7797
78- // const sentrySpanTransaction = hub.getScope()?.getSpan() as Transaction;
79- // });
98+ tracer . startActiveSpan ( 'GET /users' , parentOtelSpan => {
99+ const sentrySpanTransaction = getSpanForOtelSpan ( parentOtelSpan ) as Transaction | undefined ;
100+
101+ expect ( sentrySpanTransaction ) . toBeInstanceOf ( SentrySpan ) ;
102+ expect ( sentrySpanTransaction ?. name ) . toBe ( 'GET /users' ) ;
103+
104+ // Create some parallel, independent spans
105+ const span1 = tracer . startSpan ( 'SELECT * FROM users;' ) as OtelSpan ;
106+ const span2 = tracer . startSpan ( 'SELECT * FROM companies;' ) as OtelSpan ;
107+ const span3 = tracer . startSpan ( 'SELECT * FROM locations;' ) as OtelSpan ;
108+
109+ const sentrySpan1 = getSpanForOtelSpan ( span1 ) ;
110+ const sentrySpan2 = getSpanForOtelSpan ( span2 ) ;
111+ const sentrySpan3 = getSpanForOtelSpan ( span3 ) ;
112+
113+ expect ( sentrySpan1 ?. parentSpanId ) . toEqual ( sentrySpanTransaction ?. spanId ) ;
114+ expect ( sentrySpan2 ?. parentSpanId ) . toEqual ( sentrySpanTransaction ?. spanId ) ;
115+ expect ( sentrySpan3 ?. parentSpanId ) . toEqual ( sentrySpanTransaction ?. spanId ) ;
116+
117+ expect ( sentrySpan1 ?. description ) . toEqual ( 'SELECT * FROM users;' ) ;
118+ expect ( sentrySpan2 ?. description ) . toEqual ( 'SELECT * FROM companies;' ) ;
119+ expect ( sentrySpan3 ?. description ) . toEqual ( 'SELECT * FROM locations;' ) ;
120+
121+ span1 . end ( ) ;
122+ span2 . end ( ) ;
123+ span3 . end ( ) ;
124+
125+ parentOtelSpan . end ( ) ;
126+ } ) ;
127+ } ) ;
80128} ) ;
129+
130+ // OTEL expects a custom date format
131+ const NANOSECOND_DIGITS = 9 ;
132+ const SECOND_TO_NANOSECONDS = Math . pow ( 10 , NANOSECOND_DIGITS ) ;
133+
134+ function otelNumberToHrtime ( epochMillis : number ) : OpenTelemetry . HrTime {
135+ const epochSeconds = epochMillis / 1000 ;
136+ // Decimals only.
137+ const seconds = Math . trunc ( epochSeconds ) ;
138+ // Round sub-nanosecond accuracy to nanosecond.
139+ const nanos = Number ( ( epochSeconds - seconds ) . toFixed ( NANOSECOND_DIGITS ) ) * SECOND_TO_NANOSECONDS ;
140+ return [ seconds , nanos ] ;
141+ }
0 commit comments