autotel 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +1946 -0
- package/dist/chunk-2LNRY4QK.js +273 -0
- package/dist/chunk-2LNRY4QK.js.map +1 -0
- package/dist/chunk-3HENGDW2.js +587 -0
- package/dist/chunk-3HENGDW2.js.map +1 -0
- package/dist/chunk-4OAT42CA.cjs +73 -0
- package/dist/chunk-4OAT42CA.cjs.map +1 -0
- package/dist/chunk-5GWX5LFW.js +70 -0
- package/dist/chunk-5GWX5LFW.js.map +1 -0
- package/dist/chunk-5R2M36QB.js +195 -0
- package/dist/chunk-5R2M36QB.js.map +1 -0
- package/dist/chunk-5ZN622AO.js +73 -0
- package/dist/chunk-5ZN622AO.js.map +1 -0
- package/dist/chunk-77MSMAUQ.cjs +498 -0
- package/dist/chunk-77MSMAUQ.cjs.map +1 -0
- package/dist/chunk-ABPEQ6RK.cjs +596 -0
- package/dist/chunk-ABPEQ6RK.cjs.map +1 -0
- package/dist/chunk-BWYGJKRB.js +95 -0
- package/dist/chunk-BWYGJKRB.js.map +1 -0
- package/dist/chunk-BZHG5IZ4.js +73 -0
- package/dist/chunk-BZHG5IZ4.js.map +1 -0
- package/dist/chunk-G7VZBCD6.cjs +35 -0
- package/dist/chunk-G7VZBCD6.cjs.map +1 -0
- package/dist/chunk-GVLK7YUU.cjs +30 -0
- package/dist/chunk-GVLK7YUU.cjs.map +1 -0
- package/dist/chunk-HCCXC7XG.js +205 -0
- package/dist/chunk-HCCXC7XG.js.map +1 -0
- package/dist/chunk-HE6T6FIX.cjs +203 -0
- package/dist/chunk-HE6T6FIX.cjs.map +1 -0
- package/dist/chunk-KIXWPOCO.cjs +100 -0
- package/dist/chunk-KIXWPOCO.cjs.map +1 -0
- package/dist/chunk-KVGNW3FC.js +87 -0
- package/dist/chunk-KVGNW3FC.js.map +1 -0
- package/dist/chunk-LITNXTTT.js +3 -0
- package/dist/chunk-LITNXTTT.js.map +1 -0
- package/dist/chunk-M4ANN7RL.js +114 -0
- package/dist/chunk-M4ANN7RL.js.map +1 -0
- package/dist/chunk-NC52UBR2.cjs +32 -0
- package/dist/chunk-NC52UBR2.cjs.map +1 -0
- package/dist/chunk-NHCNRQD3.cjs +212 -0
- package/dist/chunk-NHCNRQD3.cjs.map +1 -0
- package/dist/chunk-NZ72VDNY.cjs +4 -0
- package/dist/chunk-NZ72VDNY.cjs.map +1 -0
- package/dist/chunk-P6JUDYNO.js +57 -0
- package/dist/chunk-P6JUDYNO.js.map +1 -0
- package/dist/chunk-RJYY7BWX.js +1349 -0
- package/dist/chunk-RJYY7BWX.js.map +1 -0
- package/dist/chunk-TRI4V5BF.cjs +126 -0
- package/dist/chunk-TRI4V5BF.cjs.map +1 -0
- package/dist/chunk-UL33I6IS.js +139 -0
- package/dist/chunk-UL33I6IS.js.map +1 -0
- package/dist/chunk-URRW6M2C.cjs +61 -0
- package/dist/chunk-URRW6M2C.cjs.map +1 -0
- package/dist/chunk-UY3UYPBZ.cjs +77 -0
- package/dist/chunk-UY3UYPBZ.cjs.map +1 -0
- package/dist/chunk-W3253FGB.cjs +277 -0
- package/dist/chunk-W3253FGB.cjs.map +1 -0
- package/dist/chunk-W7LHZVQF.js +26 -0
- package/dist/chunk-W7LHZVQF.js.map +1 -0
- package/dist/chunk-WBWNM6LB.cjs +1360 -0
- package/dist/chunk-WBWNM6LB.cjs.map +1 -0
- package/dist/chunk-WFJ7L2RV.js +494 -0
- package/dist/chunk-WFJ7L2RV.js.map +1 -0
- package/dist/chunk-X4RMFFMR.js +28 -0
- package/dist/chunk-X4RMFFMR.js.map +1 -0
- package/dist/chunk-Y4Y2S7BM.cjs +92 -0
- package/dist/chunk-Y4Y2S7BM.cjs.map +1 -0
- package/dist/chunk-YLPNXZFI.cjs +143 -0
- package/dist/chunk-YLPNXZFI.cjs.map +1 -0
- package/dist/chunk-YTXEZ4SD.cjs +77 -0
- package/dist/chunk-YTXEZ4SD.cjs.map +1 -0
- package/dist/chunk-Z6ZWNWWR.js +30 -0
- package/dist/chunk-Z6ZWNWWR.js.map +1 -0
- package/dist/config.cjs +26 -0
- package/dist/config.cjs.map +1 -0
- package/dist/config.d.cts +75 -0
- package/dist/config.d.ts +75 -0
- package/dist/config.js +5 -0
- package/dist/config.js.map +1 -0
- package/dist/db.cjs +233 -0
- package/dist/db.cjs.map +1 -0
- package/dist/db.d.cts +123 -0
- package/dist/db.d.ts +123 -0
- package/dist/db.js +228 -0
- package/dist/db.js.map +1 -0
- package/dist/decorators.cjs +67 -0
- package/dist/decorators.cjs.map +1 -0
- package/dist/decorators.d.cts +91 -0
- package/dist/decorators.d.ts +91 -0
- package/dist/decorators.js +65 -0
- package/dist/decorators.js.map +1 -0
- package/dist/event-subscriber.cjs +6 -0
- package/dist/event-subscriber.cjs.map +1 -0
- package/dist/event-subscriber.d.cts +116 -0
- package/dist/event-subscriber.d.ts +116 -0
- package/dist/event-subscriber.js +3 -0
- package/dist/event-subscriber.js.map +1 -0
- package/dist/event-testing.cjs +21 -0
- package/dist/event-testing.cjs.map +1 -0
- package/dist/event-testing.d.cts +110 -0
- package/dist/event-testing.d.ts +110 -0
- package/dist/event-testing.js +4 -0
- package/dist/event-testing.js.map +1 -0
- package/dist/event.cjs +30 -0
- package/dist/event.cjs.map +1 -0
- package/dist/event.d.cts +282 -0
- package/dist/event.d.ts +282 -0
- package/dist/event.js +13 -0
- package/dist/event.js.map +1 -0
- package/dist/exporters.cjs +17 -0
- package/dist/exporters.cjs.map +1 -0
- package/dist/exporters.d.cts +1 -0
- package/dist/exporters.d.ts +1 -0
- package/dist/exporters.js +4 -0
- package/dist/exporters.js.map +1 -0
- package/dist/functional.cjs +46 -0
- package/dist/functional.cjs.map +1 -0
- package/dist/functional.d.cts +478 -0
- package/dist/functional.d.ts +478 -0
- package/dist/functional.js +13 -0
- package/dist/functional.js.map +1 -0
- package/dist/http.cjs +189 -0
- package/dist/http.cjs.map +1 -0
- package/dist/http.d.cts +169 -0
- package/dist/http.d.ts +169 -0
- package/dist/http.js +184 -0
- package/dist/http.js.map +1 -0
- package/dist/index.cjs +333 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +758 -0
- package/dist/index.d.ts +758 -0
- package/dist/index.js +143 -0
- package/dist/index.js.map +1 -0
- package/dist/instrumentation.cjs +182 -0
- package/dist/instrumentation.cjs.map +1 -0
- package/dist/instrumentation.d.cts +49 -0
- package/dist/instrumentation.d.ts +49 -0
- package/dist/instrumentation.js +179 -0
- package/dist/instrumentation.js.map +1 -0
- package/dist/logger.cjs +19 -0
- package/dist/logger.cjs.map +1 -0
- package/dist/logger.d.cts +146 -0
- package/dist/logger.d.ts +146 -0
- package/dist/logger.js +6 -0
- package/dist/logger.js.map +1 -0
- package/dist/metric-helpers.cjs +31 -0
- package/dist/metric-helpers.cjs.map +1 -0
- package/dist/metric-helpers.d.cts +13 -0
- package/dist/metric-helpers.d.ts +13 -0
- package/dist/metric-helpers.js +6 -0
- package/dist/metric-helpers.js.map +1 -0
- package/dist/metric-testing.cjs +21 -0
- package/dist/metric-testing.cjs.map +1 -0
- package/dist/metric-testing.d.cts +110 -0
- package/dist/metric-testing.d.ts +110 -0
- package/dist/metric-testing.js +4 -0
- package/dist/metric-testing.js.map +1 -0
- package/dist/metric.cjs +26 -0
- package/dist/metric.cjs.map +1 -0
- package/dist/metric.d.cts +240 -0
- package/dist/metric.d.ts +240 -0
- package/dist/metric.js +9 -0
- package/dist/metric.js.map +1 -0
- package/dist/processors.cjs +17 -0
- package/dist/processors.cjs.map +1 -0
- package/dist/processors.d.cts +1 -0
- package/dist/processors.d.ts +1 -0
- package/dist/processors.js +4 -0
- package/dist/processors.js.map +1 -0
- package/dist/sampling.cjs +40 -0
- package/dist/sampling.cjs.map +1 -0
- package/dist/sampling.d.cts +260 -0
- package/dist/sampling.d.ts +260 -0
- package/dist/sampling.js +7 -0
- package/dist/sampling.js.map +1 -0
- package/dist/semantic-helpers.cjs +35 -0
- package/dist/semantic-helpers.cjs.map +1 -0
- package/dist/semantic-helpers.d.cts +442 -0
- package/dist/semantic-helpers.d.ts +442 -0
- package/dist/semantic-helpers.js +14 -0
- package/dist/semantic-helpers.js.map +1 -0
- package/dist/tail-sampling-processor.cjs +13 -0
- package/dist/tail-sampling-processor.cjs.map +1 -0
- package/dist/tail-sampling-processor.d.cts +27 -0
- package/dist/tail-sampling-processor.d.ts +27 -0
- package/dist/tail-sampling-processor.js +4 -0
- package/dist/tail-sampling-processor.js.map +1 -0
- package/dist/testing.cjs +286 -0
- package/dist/testing.cjs.map +1 -0
- package/dist/testing.d.cts +291 -0
- package/dist/testing.d.ts +291 -0
- package/dist/testing.js +263 -0
- package/dist/testing.js.map +1 -0
- package/dist/trace-context-DRZdUvVY.d.cts +181 -0
- package/dist/trace-context-DRZdUvVY.d.ts +181 -0
- package/dist/trace-helpers.cjs +54 -0
- package/dist/trace-helpers.cjs.map +1 -0
- package/dist/trace-helpers.d.cts +524 -0
- package/dist/trace-helpers.d.ts +524 -0
- package/dist/trace-helpers.js +5 -0
- package/dist/trace-helpers.js.map +1 -0
- package/dist/tracer-provider.cjs +21 -0
- package/dist/tracer-provider.cjs.map +1 -0
- package/dist/tracer-provider.d.cts +169 -0
- package/dist/tracer-provider.d.ts +169 -0
- package/dist/tracer-provider.js +4 -0
- package/dist/tracer-provider.js.map +1 -0
- package/package.json +280 -0
- package/src/baggage-span-processor.test.ts +202 -0
- package/src/baggage-span-processor.ts +98 -0
- package/src/circuit-breaker.test.ts +341 -0
- package/src/circuit-breaker.ts +184 -0
- package/src/config.test.ts +94 -0
- package/src/config.ts +169 -0
- package/src/db.test.ts +252 -0
- package/src/db.ts +447 -0
- package/src/decorators.test.ts +203 -0
- package/src/decorators.ts +188 -0
- package/src/env-config.test.ts +246 -0
- package/src/env-config.ts +158 -0
- package/src/event-queue.test.ts +222 -0
- package/src/event-queue.ts +203 -0
- package/src/event-subscriber.ts +136 -0
- package/src/event-testing.ts +197 -0
- package/src/event.test.ts +718 -0
- package/src/event.ts +556 -0
- package/src/exporters.ts +96 -0
- package/src/functional.test.ts +1059 -0
- package/src/functional.ts +2295 -0
- package/src/http.test.ts +487 -0
- package/src/http.ts +424 -0
- package/src/index.ts +158 -0
- package/src/init.customization.test.ts +210 -0
- package/src/init.integrations.test.ts +366 -0
- package/src/init.openllmetry.test.ts +282 -0
- package/src/init.protocol.test.ts +215 -0
- package/src/init.ts +1426 -0
- package/src/instrumentation.test.ts +108 -0
- package/src/instrumentation.ts +308 -0
- package/src/logger.test.ts +117 -0
- package/src/logger.ts +246 -0
- package/src/metric-helpers.ts +47 -0
- package/src/metric-testing.ts +197 -0
- package/src/metric.ts +434 -0
- package/src/metrics.test.ts +205 -0
- package/src/operation-context.ts +93 -0
- package/src/processors.ts +106 -0
- package/src/rate-limiter.test.ts +199 -0
- package/src/rate-limiter.ts +98 -0
- package/src/sampling.test.ts +513 -0
- package/src/sampling.ts +428 -0
- package/src/semantic-helpers.test.ts +311 -0
- package/src/semantic-helpers.ts +584 -0
- package/src/shutdown.test.ts +311 -0
- package/src/shutdown.ts +222 -0
- package/src/stub.integration.test.ts +361 -0
- package/src/tail-sampling-processor.test.ts +226 -0
- package/src/tail-sampling-processor.ts +51 -0
- package/src/testing.ts +670 -0
- package/src/trace-context.ts +470 -0
- package/src/trace-helpers.new.test.ts +278 -0
- package/src/trace-helpers.test.ts +242 -0
- package/src/trace-helpers.ts +690 -0
- package/src/tracer-provider.test.ts +183 -0
- package/src/tracer-provider.ts +266 -0
- package/src/track.test.ts +153 -0
- package/src/track.ts +120 -0
- package/src/validation.test.ts +306 -0
- package/src/validation.ts +239 -0
- package/src/variable-name-inference.test.ts +178 -0
- package/src/variable-name-inference.ts +242 -0
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for isolated tracer provider support
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
6
|
+
import {
|
|
7
|
+
setAutotelTracerProvider,
|
|
8
|
+
getAutotelTracerProvider,
|
|
9
|
+
getAutotelTracer,
|
|
10
|
+
} from './tracer-provider';
|
|
11
|
+
import { trace } from '@opentelemetry/api';
|
|
12
|
+
import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node';
|
|
13
|
+
|
|
14
|
+
describe('Isolated Tracer Provider', () => {
|
|
15
|
+
let customProvider: NodeTracerProvider;
|
|
16
|
+
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
// Create a custom provider for testing
|
|
19
|
+
customProvider = new NodeTracerProvider();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
afterEach(() => {
|
|
23
|
+
// Clean up: reset to null after each test
|
|
24
|
+
setAutotelTracerProvider(null);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
describe('setAutotelTracerProvider', () => {
|
|
28
|
+
it('should set isolated provider', () => {
|
|
29
|
+
setAutotelTracerProvider(customProvider);
|
|
30
|
+
|
|
31
|
+
const provider = getAutotelTracerProvider();
|
|
32
|
+
expect(provider).toBe(customProvider);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('should clear isolated provider when set to null', () => {
|
|
36
|
+
setAutotelTracerProvider(customProvider);
|
|
37
|
+
setAutotelTracerProvider(null);
|
|
38
|
+
|
|
39
|
+
const provider = getAutotelTracerProvider();
|
|
40
|
+
// Should fall back to global provider
|
|
41
|
+
expect(provider).toBe(trace.getTracerProvider());
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should allow overwriting existing isolated provider', () => {
|
|
45
|
+
const provider1 = new NodeTracerProvider();
|
|
46
|
+
const provider2 = new NodeTracerProvider();
|
|
47
|
+
|
|
48
|
+
setAutotelTracerProvider(provider1);
|
|
49
|
+
expect(getAutotelTracerProvider()).toBe(provider1);
|
|
50
|
+
|
|
51
|
+
setAutotelTracerProvider(provider2);
|
|
52
|
+
expect(getAutotelTracerProvider()).toBe(provider2);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
describe('getAutotelTracerProvider', () => {
|
|
57
|
+
it('should return isolated provider when set', () => {
|
|
58
|
+
setAutotelTracerProvider(customProvider);
|
|
59
|
+
|
|
60
|
+
const provider = getAutotelTracerProvider();
|
|
61
|
+
expect(provider).toBe(customProvider);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('should return global provider when no isolated provider is set', () => {
|
|
65
|
+
const provider = getAutotelTracerProvider();
|
|
66
|
+
expect(provider).toBe(trace.getTracerProvider());
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('should be idempotent', () => {
|
|
70
|
+
setAutotelTracerProvider(customProvider);
|
|
71
|
+
|
|
72
|
+
const provider1 = getAutotelTracerProvider();
|
|
73
|
+
const provider2 = getAutotelTracerProvider();
|
|
74
|
+
|
|
75
|
+
expect(provider1).toBe(provider2);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
describe('getAutotelTracer', () => {
|
|
80
|
+
it('should return tracer from isolated provider when set', () => {
|
|
81
|
+
setAutotelTracerProvider(customProvider);
|
|
82
|
+
|
|
83
|
+
const tracer = getAutotelTracer('test-tracer');
|
|
84
|
+
|
|
85
|
+
// Verify tracer is from custom provider
|
|
86
|
+
// (We can't directly compare tracers, but we can verify it's not throwing)
|
|
87
|
+
expect(tracer).toBeDefined();
|
|
88
|
+
expect(typeof tracer.startSpan).toBe('function');
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('should return tracer from global provider when no isolated provider is set', () => {
|
|
92
|
+
const tracer = getAutotelTracer('test-tracer');
|
|
93
|
+
|
|
94
|
+
expect(tracer).toBeDefined();
|
|
95
|
+
expect(typeof tracer.startSpan).toBe('function');
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('should use default tracer name when not specified', () => {
|
|
99
|
+
setAutotelTracerProvider(customProvider);
|
|
100
|
+
|
|
101
|
+
const tracer = getAutotelTracer();
|
|
102
|
+
|
|
103
|
+
expect(tracer).toBeDefined();
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('should respect custom tracer name', () => {
|
|
107
|
+
setAutotelTracerProvider(customProvider);
|
|
108
|
+
|
|
109
|
+
const tracer = getAutotelTracer('custom-name', '1.0.0');
|
|
110
|
+
|
|
111
|
+
expect(tracer).toBeDefined();
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('should support version parameter', () => {
|
|
115
|
+
setAutotelTracerProvider(customProvider);
|
|
116
|
+
|
|
117
|
+
const tracer = getAutotelTracer('my-service', '2.0.0');
|
|
118
|
+
|
|
119
|
+
expect(tracer).toBeDefined();
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
describe('Integration with Autotel config', () => {
|
|
124
|
+
it('should allow isolated provider to work independently of init()', () => {
|
|
125
|
+
// Don't call init(), just set isolated provider
|
|
126
|
+
setAutotelTracerProvider(customProvider);
|
|
127
|
+
|
|
128
|
+
const tracer = getAutotelTracer('standalone');
|
|
129
|
+
expect(tracer).toBeDefined();
|
|
130
|
+
|
|
131
|
+
// Should be able to create spans
|
|
132
|
+
const span = tracer.startSpan('test-span');
|
|
133
|
+
expect(span).toBeDefined();
|
|
134
|
+
expect(typeof span.end).toBe('function');
|
|
135
|
+
span.end();
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('should persist across multiple getTracer calls', () => {
|
|
139
|
+
setAutotelTracerProvider(customProvider);
|
|
140
|
+
|
|
141
|
+
const tracer1 = getAutotelTracer('service-1');
|
|
142
|
+
const tracer2 = getAutotelTracer('service-2');
|
|
143
|
+
|
|
144
|
+
// Both should come from the same provider
|
|
145
|
+
expect(tracer1).toBeDefined();
|
|
146
|
+
expect(tracer2).toBeDefined();
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
describe('Global state isolation', () => {
|
|
151
|
+
it('should not affect global OTel provider', () => {
|
|
152
|
+
const globalProvider = trace.getTracerProvider();
|
|
153
|
+
|
|
154
|
+
setAutotelTracerProvider(customProvider);
|
|
155
|
+
|
|
156
|
+
// Global provider should remain unchanged
|
|
157
|
+
expect(trace.getTracerProvider()).toBe(globalProvider);
|
|
158
|
+
// But Autotel provider should be our custom one
|
|
159
|
+
expect(getAutotelTracerProvider()).toBe(customProvider);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('should allow both global and isolated providers to coexist', () => {
|
|
163
|
+
const _globalProvider = trace.getTracerProvider();
|
|
164
|
+
setAutotelTracerProvider(customProvider);
|
|
165
|
+
|
|
166
|
+
const globalTracer = trace.getTracer('global-tracer');
|
|
167
|
+
const isolatedTracer = getAutotelTracer('isolated-tracer');
|
|
168
|
+
|
|
169
|
+
expect(globalTracer).toBeDefined();
|
|
170
|
+
expect(isolatedTracer).toBeDefined();
|
|
171
|
+
|
|
172
|
+
// Can create spans from both
|
|
173
|
+
const globalSpan = globalTracer.startSpan('global-span');
|
|
174
|
+
const isolatedSpan = isolatedTracer.startSpan('isolated-span');
|
|
175
|
+
|
|
176
|
+
expect(globalSpan).toBeDefined();
|
|
177
|
+
expect(isolatedSpan).toBeDefined();
|
|
178
|
+
|
|
179
|
+
globalSpan.end();
|
|
180
|
+
isolatedSpan.end();
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
});
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Isolated tracer provider support for Autotel
|
|
3
|
+
*
|
|
4
|
+
* Allows Autotel to use a separate TracerProvider instance, avoiding conflicts
|
|
5
|
+
* with other OpenTelemetry instrumentation in the application.
|
|
6
|
+
*
|
|
7
|
+
* **Use Case:** Library authors who want to use Autotel without interfering
|
|
8
|
+
* with the application's global OpenTelemetry setup.
|
|
9
|
+
*
|
|
10
|
+
* **Limitation:** While this isolates span processing and export, OpenTelemetry
|
|
11
|
+
* context (trace IDs, parent spans) is still shared globally. Spans created with
|
|
12
|
+
* the isolated provider may inherit trace context from global spans.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { trace } from '@opentelemetry/api';
|
|
16
|
+
import type { TracerProvider } from '@opentelemetry/api';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Symbol for storing isolated tracer provider in global scope
|
|
20
|
+
* Using Symbol.for() ensures the same symbol across module boundaries
|
|
21
|
+
*/
|
|
22
|
+
const AUTOLEMETRY_GLOBAL_SYMBOL = Symbol.for('autotel');
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Global state for Autotel
|
|
26
|
+
*/
|
|
27
|
+
type AutotelGlobalState = {
|
|
28
|
+
isolatedTracerProvider: TracerProvider | null;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Create initial state
|
|
33
|
+
*/
|
|
34
|
+
function createState(): AutotelGlobalState {
|
|
35
|
+
return {
|
|
36
|
+
isolatedTracerProvider: null,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Extend globalThis to include our symbol
|
|
42
|
+
*/
|
|
43
|
+
interface GlobalThis {
|
|
44
|
+
[AUTOLEMETRY_GLOBAL_SYMBOL]?: AutotelGlobalState;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Get the global state, creating it if it doesn't exist
|
|
49
|
+
* Handles edge cases like missing globalThis
|
|
50
|
+
*/
|
|
51
|
+
function getGlobalState(): AutotelGlobalState {
|
|
52
|
+
const initialState = createState();
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
const g = globalThis as typeof globalThis & GlobalThis;
|
|
56
|
+
|
|
57
|
+
if (typeof g !== 'object' || g === null) {
|
|
58
|
+
console.warn(
|
|
59
|
+
'[autotel] globalThis is not available, using fallback state',
|
|
60
|
+
);
|
|
61
|
+
return initialState;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (!g[AUTOLEMETRY_GLOBAL_SYMBOL]) {
|
|
65
|
+
Object.defineProperty(g, AUTOLEMETRY_GLOBAL_SYMBOL, {
|
|
66
|
+
value: initialState,
|
|
67
|
+
writable: false, // Lock the slot (not the contents)
|
|
68
|
+
configurable: false,
|
|
69
|
+
enumerable: false,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return g[AUTOLEMETRY_GLOBAL_SYMBOL]!;
|
|
74
|
+
} catch (error) {
|
|
75
|
+
if (error instanceof Error) {
|
|
76
|
+
console.error(
|
|
77
|
+
`[autotel] Failed to access global state: ${error.message}`,
|
|
78
|
+
);
|
|
79
|
+
} else {
|
|
80
|
+
console.error(
|
|
81
|
+
`[autotel] Failed to access global state: ${String(error)}`,
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return initialState;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Sets an isolated TracerProvider for Autotel tracing operations.
|
|
91
|
+
*
|
|
92
|
+
* This allows Autotel to use its own TracerProvider instance, separate from
|
|
93
|
+
* the global OpenTelemetry TracerProvider. This is useful for avoiding conflicts
|
|
94
|
+
* with other OpenTelemetry instrumentation in the application.
|
|
95
|
+
*
|
|
96
|
+
* **Limitation: Span Context Sharing**
|
|
97
|
+
*
|
|
98
|
+
* While this function isolates span processing and export, it does NOT provide
|
|
99
|
+
* complete trace isolation. OpenTelemetry context (trace IDs, parent spans) is
|
|
100
|
+
* still shared between the global and isolated providers. This means:
|
|
101
|
+
*
|
|
102
|
+
* - Spans created with the isolated provider inherit trace IDs from global spans
|
|
103
|
+
* - Spans created with the isolated provider inherit parent relationships from global spans
|
|
104
|
+
* - This can result in spans from different providers being part of the same logical trace
|
|
105
|
+
*
|
|
106
|
+
* **Why this happens:**
|
|
107
|
+
* OpenTelemetry uses a global context propagation mechanism that operates at the
|
|
108
|
+
* JavaScript runtime level, independent of individual TracerProvider instances.
|
|
109
|
+
* The context (containing trace ID, span ID) flows through async boundaries and
|
|
110
|
+
* is inherited by all spans created within that context, regardless of which
|
|
111
|
+
* TracerProvider creates them.
|
|
112
|
+
*
|
|
113
|
+
* **When to use this:**
|
|
114
|
+
* - Library code that ships with embedded Autotel
|
|
115
|
+
* - SDKs that want observability without requiring users to set up OpenTelemetry
|
|
116
|
+
* - Applications that need separate span processing for different subsystems
|
|
117
|
+
* - Testing scenarios where you want to isolate trace collection
|
|
118
|
+
*
|
|
119
|
+
* @param provider - The TracerProvider instance to use, or null to clear the isolated provider
|
|
120
|
+
*
|
|
121
|
+
* @example Library with embedded Autotel
|
|
122
|
+
* ```typescript
|
|
123
|
+
* import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node'
|
|
124
|
+
* import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base'
|
|
125
|
+
* import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'
|
|
126
|
+
* import { setAutolem
|
|
127
|
+
|
|
128
|
+
etryTracerProvider } from 'autotel/tracer-provider'
|
|
129
|
+
*
|
|
130
|
+
* // Create provider with span processors in constructor
|
|
131
|
+
* const exporter = new OTLPTraceExporter({
|
|
132
|
+
* url: 'https://your-backend.com/v1/traces'
|
|
133
|
+
* })
|
|
134
|
+
*
|
|
135
|
+
* const provider = new NodeTracerProvider()
|
|
136
|
+
* provider.addSpanProcessor(new BatchSpanProcessor(exporter))
|
|
137
|
+
*
|
|
138
|
+
* // Set as Autotel's isolated provider (doesn't call provider.register())
|
|
139
|
+
* setAutotelTracerProvider(provider)
|
|
140
|
+
*
|
|
141
|
+
* // Now all Autotel trace() calls use this provider
|
|
142
|
+
* // But won't interfere with the application's global OpenTelemetry setup
|
|
143
|
+
* ```
|
|
144
|
+
*
|
|
145
|
+
* @example Testing with isolated provider
|
|
146
|
+
* ```typescript
|
|
147
|
+
* import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node'
|
|
148
|
+
* import { InMemorySpanExporter } from '@opentelemetry/sdk-trace-base'
|
|
149
|
+
* import { setAutotelTracerProvider } from 'autotel/tracer-provider'
|
|
150
|
+
*
|
|
151
|
+
* // Test setup
|
|
152
|
+
* const exporter = new InMemorySpanExporter()
|
|
153
|
+
* const provider = new NodeTracerProvider()
|
|
154
|
+
* provider.addSpanProcessor(new SimpleSpanProcessor(exporter))
|
|
155
|
+
*
|
|
156
|
+
* setAutotelTracerProvider(provider)
|
|
157
|
+
*
|
|
158
|
+
* // Run tests...
|
|
159
|
+
* const spans = exporter.getFinishedSpans()
|
|
160
|
+
*
|
|
161
|
+
* // Cleanup
|
|
162
|
+
* setAutotelTracerProvider(null)
|
|
163
|
+
* ```
|
|
164
|
+
*
|
|
165
|
+
* @example Multiple subsystems with different exporters
|
|
166
|
+
* ```typescript
|
|
167
|
+
* import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node'
|
|
168
|
+
* import { setAutotelTracerProvider } from 'autotel/tracer-provider'
|
|
169
|
+
*
|
|
170
|
+
* // Payment subsystem - send to payment team's backend
|
|
171
|
+
* const paymentProvider = new NodeTracerProvider()
|
|
172
|
+
* paymentProvider.addSpanProcessor(new BatchSpanProcessor(
|
|
173
|
+
* new OTLPTraceExporter({ url: 'https://payment-team-backend.com/v1/traces' })
|
|
174
|
+
* ))
|
|
175
|
+
*
|
|
176
|
+
* // In payment module initialization
|
|
177
|
+
* setAutotelTracerProvider(paymentProvider)
|
|
178
|
+
* ```
|
|
179
|
+
*
|
|
180
|
+
* @public
|
|
181
|
+
*/
|
|
182
|
+
export function setAutotelTracerProvider(
|
|
183
|
+
provider: TracerProvider | null,
|
|
184
|
+
): void {
|
|
185
|
+
getGlobalState().isolatedTracerProvider = provider;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Gets the TracerProvider for Autotel tracing operations.
|
|
190
|
+
*
|
|
191
|
+
* Returns the isolated TracerProvider if one has been set via setAutotelTracerProvider(),
|
|
192
|
+
* otherwise falls back to the global OpenTelemetry TracerProvider.
|
|
193
|
+
*
|
|
194
|
+
* This function is used internally by Autotel's trace functions. Most users
|
|
195
|
+
* will not need to call this directly.
|
|
196
|
+
*
|
|
197
|
+
* @returns The TracerProvider instance to use for Autotel tracing
|
|
198
|
+
*
|
|
199
|
+
* @example Getting the current provider
|
|
200
|
+
* ```typescript
|
|
201
|
+
* import { getAutotelTracerProvider } from 'autotel/tracer-provider'
|
|
202
|
+
*
|
|
203
|
+
* const provider = getAutotelTracerProvider()
|
|
204
|
+
* const tracer = provider.getTracer('my-service', '1.0.0')
|
|
205
|
+
* ```
|
|
206
|
+
*
|
|
207
|
+
* @example Checking if isolated provider is active
|
|
208
|
+
* ```typescript
|
|
209
|
+
* import { getAutotelTracerProvider, setAutotelTracerProvider } from 'autotel/tracer-provider'
|
|
210
|
+
* import { trace } from '@opentelemetry/api'
|
|
211
|
+
*
|
|
212
|
+
* const currentProvider = getAutotelTracerProvider()
|
|
213
|
+
* const globalProvider = trace.getTracerProvider()
|
|
214
|
+
*
|
|
215
|
+
* if (currentProvider === globalProvider) {
|
|
216
|
+
* console.log('Using global provider')
|
|
217
|
+
* } else {
|
|
218
|
+
* console.log('Using isolated provider')
|
|
219
|
+
* }
|
|
220
|
+
* ```
|
|
221
|
+
*
|
|
222
|
+
* @public
|
|
223
|
+
*/
|
|
224
|
+
export function getAutotelTracerProvider(): TracerProvider {
|
|
225
|
+
const { isolatedTracerProvider } = getGlobalState();
|
|
226
|
+
|
|
227
|
+
if (isolatedTracerProvider) return isolatedTracerProvider;
|
|
228
|
+
|
|
229
|
+
return trace.getTracerProvider();
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Gets the OpenTelemetry tracer instance for Autotel.
|
|
234
|
+
*
|
|
235
|
+
* This function returns a tracer specifically configured for Autotel
|
|
236
|
+
* with the correct tracer name and version. Used internally by all
|
|
237
|
+
* Autotel tracing functions to ensure consistent trace creation.
|
|
238
|
+
*
|
|
239
|
+
* Uses the isolated provider if set, otherwise uses the global provider.
|
|
240
|
+
*
|
|
241
|
+
* @param name - Tracer name (default: 'autotel')
|
|
242
|
+
* @param version - Optional version string
|
|
243
|
+
* @returns The Autotel OpenTelemetry tracer instance
|
|
244
|
+
*
|
|
245
|
+
* @example Basic usage
|
|
246
|
+
* ```typescript
|
|
247
|
+
* import { getAutotelTracer } from 'autotel/tracer-provider'
|
|
248
|
+
*
|
|
249
|
+
* const tracer = getAutotelTracer()
|
|
250
|
+
* const span = tracer.startSpan('my-operation')
|
|
251
|
+
* // ... use span
|
|
252
|
+
* span.end()
|
|
253
|
+
* ```
|
|
254
|
+
*
|
|
255
|
+
* @example Custom tracer name
|
|
256
|
+
* ```typescript
|
|
257
|
+
* import { getAutotelTracer } from 'autotel/tracer-provider'
|
|
258
|
+
*
|
|
259
|
+
* const tracer = getAutotelTracer('my-library', '2.1.0')
|
|
260
|
+
* ```
|
|
261
|
+
*
|
|
262
|
+
* @public
|
|
263
|
+
*/
|
|
264
|
+
export function getAutotelTracer(name = 'autotel', version?: string) {
|
|
265
|
+
return getAutotelTracerProvider().getTracer(name, version);
|
|
266
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for track() function
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
6
|
+
import { trace } from '@opentelemetry/api';
|
|
7
|
+
import { track, getEventQueue } from './track';
|
|
8
|
+
import { init, getLogger } from './init';
|
|
9
|
+
|
|
10
|
+
type TrackedEvent = {
|
|
11
|
+
name: string;
|
|
12
|
+
attributes?: Record<string, unknown>;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
// Mock adapter for testing
|
|
16
|
+
class MockAdapter {
|
|
17
|
+
public events: TrackedEvent[] = [];
|
|
18
|
+
|
|
19
|
+
async trackEvent(
|
|
20
|
+
name: string,
|
|
21
|
+
attributes?: Record<string, unknown>,
|
|
22
|
+
): Promise<void> {
|
|
23
|
+
this.events.push({ name, attributes });
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async trackFunnelStep(): Promise<void> {}
|
|
27
|
+
async trackOutcome(): Promise<void> {}
|
|
28
|
+
async trackValue(): Promise<void> {}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
describe('track() function', () => {
|
|
32
|
+
let mockAdapter: MockAdapter;
|
|
33
|
+
let loggerWarnSpy: ReturnType<typeof vi.spyOn> | null = null;
|
|
34
|
+
|
|
35
|
+
beforeEach(() => {
|
|
36
|
+
vi.resetModules();
|
|
37
|
+
mockAdapter = new MockAdapter();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
afterEach(() => {
|
|
41
|
+
if (loggerWarnSpy) {
|
|
42
|
+
loggerWarnSpy.mockRestore();
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
describe('Initialization checks', () => {
|
|
47
|
+
it('should warn in dev if track() called before init()', () => {
|
|
48
|
+
process.env.NODE_ENV = 'development';
|
|
49
|
+
|
|
50
|
+
// Spy on logger after it's initialized (it uses default silent logger initially)
|
|
51
|
+
loggerWarnSpy = vi.spyOn(getLogger(), 'warn');
|
|
52
|
+
|
|
53
|
+
track('test.event', { foo: 'bar' });
|
|
54
|
+
|
|
55
|
+
expect(loggerWarnSpy).toHaveBeenCalledWith(
|
|
56
|
+
expect.stringContaining('track() used before init()'),
|
|
57
|
+
);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('should not throw in production if track() called before init()', () => {
|
|
61
|
+
process.env.NODE_ENV = 'production';
|
|
62
|
+
|
|
63
|
+
expect(() => {
|
|
64
|
+
track('test.event', { foo: 'bar' });
|
|
65
|
+
}).not.toThrow();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('should be no-op if no adapters configured', () => {
|
|
69
|
+
init({ service: 'test-app' }); // No adapters
|
|
70
|
+
|
|
71
|
+
expect(() => {
|
|
72
|
+
track('test.event', { foo: 'bar' });
|
|
73
|
+
}).not.toThrow();
|
|
74
|
+
|
|
75
|
+
const queue = getEventQueue();
|
|
76
|
+
expect(queue).toBeNull();
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
describe('Event tracking', () => {
|
|
81
|
+
beforeEach(() => {
|
|
82
|
+
init({
|
|
83
|
+
service: 'test-app',
|
|
84
|
+
subscribers: [mockAdapter],
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('should enqueue events', () => {
|
|
89
|
+
track('user.signup', { userId: '123', plan: 'pro' });
|
|
90
|
+
|
|
91
|
+
const queue = getEventQueue();
|
|
92
|
+
expect(queue).not.toBeNull();
|
|
93
|
+
expect(queue?.size()).toBeGreaterThan(0);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('should track event name and attributes', () => {
|
|
97
|
+
track('user.signup', { userId: '123', plan: 'pro' });
|
|
98
|
+
|
|
99
|
+
// Queue batches events, so we need to flush
|
|
100
|
+
// In real usage, this would be automatic
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
describe('Trace correlation', () => {
|
|
105
|
+
beforeEach(() => {
|
|
106
|
+
init({
|
|
107
|
+
service: 'test-app',
|
|
108
|
+
subscribers: [mockAdapter],
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('should auto-attach traceId and spanId when in active span', () => {
|
|
113
|
+
const tracer = trace.getTracer('test');
|
|
114
|
+
|
|
115
|
+
tracer.startActiveSpan('test-span', (span) => {
|
|
116
|
+
track('user.signup', { userId: '123' });
|
|
117
|
+
|
|
118
|
+
// Verify event was enqueued with trace context
|
|
119
|
+
const queue = getEventQueue();
|
|
120
|
+
expect(queue).not.toBeNull();
|
|
121
|
+
|
|
122
|
+
span.end();
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('should not fail if no active span', () => {
|
|
127
|
+
expect(() => {
|
|
128
|
+
track('user.signup', { userId: '123' });
|
|
129
|
+
}).not.toThrow();
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
describe('Type safety', () => {
|
|
134
|
+
beforeEach(() => {
|
|
135
|
+
init({
|
|
136
|
+
service: 'test-app',
|
|
137
|
+
subscribers: [mockAdapter],
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('should accept typed events', () => {
|
|
142
|
+
interface Events {
|
|
143
|
+
'user.signup': { userId: string; plan: string };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Type-safe call (TypeScript would catch errors here)
|
|
147
|
+
track<Events>('user.signup', { userId: '123', plan: 'pro' });
|
|
148
|
+
|
|
149
|
+
const queue = getEventQueue();
|
|
150
|
+
expect(queue!.size()).toBeGreaterThan(0);
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
});
|