/
spanprocessor.test.ts
141 lines (106 loc) · 5.28 KB
/
spanprocessor.test.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
import * as OpenTelemetry from '@opentelemetry/api';
import { Span as OtelSpan } from '@opentelemetry/sdk-trace-base';
import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node';
import { Hub, makeMain } from '@sentry/core';
import { addExtensionMethods, Span as SentrySpan, Transaction } from '@sentry/tracing';
import { SentrySpanProcessor } from '../src/spanprocessor';
// Integration Test of SentrySpanProcessor
beforeAll(() => {
addExtensionMethods();
});
describe('SentrySpanProcessor', () => {
let hub: Hub;
let provider: NodeTracerProvider;
let spanProcessor: SentrySpanProcessor;
beforeEach(() => {
hub = new Hub();
makeMain(hub);
spanProcessor = new SentrySpanProcessor();
provider = new NodeTracerProvider();
provider.addSpanProcessor(spanProcessor);
provider.register();
});
afterEach(async () => {
await provider.forceFlush();
await provider.shutdown();
});
function getSpanForOtelSpan(otelSpan: OtelSpan | OpenTelemetry.Span) {
return spanProcessor._map.get(otelSpan.spanContext().spanId);
}
it('creates a transaction', async () => {
const startTime = otelNumberToHrtime(new Date().valueOf());
const otelSpan = provider.getTracer('default').startSpan('GET /users', { startTime }) as OtelSpan;
const sentrySpanTransaction = getSpanForOtelSpan(otelSpan) as Transaction | undefined;
expect(sentrySpanTransaction).toBeInstanceOf(Transaction);
expect(sentrySpanTransaction?.name).toBe('GET /users');
expect(sentrySpanTransaction?.startTimestamp).toEqual(otelSpan.startTime[0]);
expect(sentrySpanTransaction?.startTimestamp).toEqual(startTime[0]);
expect(sentrySpanTransaction?.traceId).toEqual(otelSpan.spanContext().traceId);
expect(sentrySpanTransaction?.parentSpanId).toEqual(otelSpan.parentSpanId);
expect(sentrySpanTransaction?.spanId).toEqual(otelSpan.spanContext().spanId);
expect(hub.getScope()?.getSpan()).toBeUndefined();
const endTime = otelNumberToHrtime(new Date().valueOf());
otelSpan.end(endTime);
expect(sentrySpanTransaction?.endTimestamp).toBe(endTime[0]);
expect(sentrySpanTransaction?.endTimestamp).toBe(otelSpan.endTime[0]);
expect(hub.getScope()?.getSpan()).toBeUndefined();
});
it('creates a child span if there is a running transaction', () => {
const tracer = provider.getTracer('default');
tracer.startActiveSpan('GET /users', parentOtelSpan => {
tracer.startActiveSpan('SELECT * FROM users;', child => {
const childOtelSpan = child as OtelSpan;
const sentrySpanTransaction = getSpanForOtelSpan(parentOtelSpan) as Transaction | undefined;
expect(sentrySpanTransaction).toBeInstanceOf(Transaction);
const sentrySpan = getSpanForOtelSpan(childOtelSpan);
expect(sentrySpan).toBeInstanceOf(SentrySpan);
expect(sentrySpan?.description).toBe('SELECT * FROM users;');
expect(sentrySpan?.startTimestamp).toEqual(childOtelSpan.startTime[0]);
expect(sentrySpan?.spanId).toEqual(childOtelSpan.spanContext().spanId);
expect(sentrySpan?.parentSpanId).toEqual(sentrySpanTransaction?.spanId);
expect(hub.getScope()?.getSpan()).toBeUndefined();
const endTime = otelNumberToHrtime(new Date().valueOf());
child.end(endTime);
expect(sentrySpan?.endTimestamp).toEqual(childOtelSpan.endTime[0]);
expect(sentrySpan?.endTimestamp).toEqual(endTime[0]);
});
parentOtelSpan.end();
});
});
it('allows to create multiple child spans on same level', () => {
const tracer = provider.getTracer('default');
tracer.startActiveSpan('GET /users', parentOtelSpan => {
const sentrySpanTransaction = getSpanForOtelSpan(parentOtelSpan) as Transaction | undefined;
expect(sentrySpanTransaction).toBeInstanceOf(SentrySpan);
expect(sentrySpanTransaction?.name).toBe('GET /users');
// Create some parallel, independent spans
const span1 = tracer.startSpan('SELECT * FROM users;') as OtelSpan;
const span2 = tracer.startSpan('SELECT * FROM companies;') as OtelSpan;
const span3 = tracer.startSpan('SELECT * FROM locations;') as OtelSpan;
const sentrySpan1 = getSpanForOtelSpan(span1);
const sentrySpan2 = getSpanForOtelSpan(span2);
const sentrySpan3 = getSpanForOtelSpan(span3);
expect(sentrySpan1?.parentSpanId).toEqual(sentrySpanTransaction?.spanId);
expect(sentrySpan2?.parentSpanId).toEqual(sentrySpanTransaction?.spanId);
expect(sentrySpan3?.parentSpanId).toEqual(sentrySpanTransaction?.spanId);
expect(sentrySpan1?.description).toEqual('SELECT * FROM users;');
expect(sentrySpan2?.description).toEqual('SELECT * FROM companies;');
expect(sentrySpan3?.description).toEqual('SELECT * FROM locations;');
span1.end();
span2.end();
span3.end();
parentOtelSpan.end();
});
});
});
// OTEL expects a custom date format
const NANOSECOND_DIGITS = 9;
const SECOND_TO_NANOSECONDS = Math.pow(10, NANOSECOND_DIGITS);
function otelNumberToHrtime(epochMillis: number): OpenTelemetry.HrTime {
const epochSeconds = epochMillis / 1000;
// Decimals only.
const seconds = Math.trunc(epochSeconds);
// Round sub-nanosecond accuracy to nanosecond.
const nanos = Number((epochSeconds - seconds).toFixed(NANOSECOND_DIGITS)) * SECOND_TO_NANOSECONDS;
return [seconds, nanos];
}