1
1
import * 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' ;
3
4
import { Hub , makeMain } from '@sentry/core' ;
4
5
import { addExtensionMethods , Span as SentrySpan , Transaction } from '@sentry/tracing' ;
5
6
@@ -13,68 +14,128 @@ beforeAll(() => {
13
14
14
15
describe ( 'SentrySpanProcessor' , ( ) => {
15
16
let hub : Hub ;
17
+ let provider : NodeTracerProvider ;
18
+ let spanProcessor : SentrySpanProcessor ;
19
+
16
20
beforeEach ( ( ) => {
17
21
hub = new Hub ( ) ;
18
22
makeMain ( hub ) ;
19
23
20
- const provider = new BasicTracerProvider ( ) ;
21
- provider . addSpanProcessor ( new SentrySpanProcessor ( ) ) ;
24
+ spanProcessor = new SentrySpanProcessor ( ) ;
25
+ provider = new NodeTracerProvider ( ) ;
26
+ provider . addSpanProcessor ( spanProcessor ) ;
22
27
provider . register ( ) ;
23
28
} ) ;
24
29
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
+ } ) ;
30
34
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
+ }
33
38
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 ( ) ) ;
36
41
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 ) ;
42
46
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 ) ;
45
53
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 ( ) ;
51
55
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 ) ;
56
58
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 ] ) ;
59
61
60
- // Enforce we use otel timestamps
61
- expect ( sentrySpan ?. startTimestamp ) . toEqual ( childOtelSpan . startTime [ 0 ] ) ;
62
+ expect ( hub . getScope ( ) ?. getSpan ( ) ) . toBeUndefined ( ) ;
63
+ } ) ;
62
64
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' ) ;
65
67
66
- childOtelSpan . end ( ) ;
67
- } ) ;
68
+ tracer . startActiveSpan ( 'GET /users' , parentOtelSpan => {
69
+ tracer . startActiveSpan ( 'SELECT * FROM users;' , child => {
70
+ const childOtelSpan = child as OtelSpan ;
68
71
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 ] ) ;
70
89
} ) ;
90
+
91
+ parentOtelSpan . end ( ) ;
71
92
} ) ;
72
93
} ) ;
73
94
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' ) ;
77
97
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
+ } ) ;
80
128
} ) ;
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