autotel 4.0.0 → 4.2.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/README.md +26 -1
- package/dist/auto.cjs +2 -2
- package/dist/auto.js +1 -1
- package/dist/correlation-id.cjs +1 -1
- package/dist/correlation-id.js +1 -1
- package/dist/decorators.cjs +1 -1
- package/dist/decorators.js +1 -1
- package/dist/{event-Dlqr4ZNL.cjs → event-BhHREDJk.cjs} +3 -3
- package/dist/{event-Dlqr4ZNL.cjs.map → event-BhHREDJk.cjs.map} +1 -1
- package/dist/{event-_58ryBjh.js → event-ByBTV9M2.js} +3 -3
- package/dist/{event-_58ryBjh.js.map → event-ByBTV9M2.js.map} +1 -1
- package/dist/event.cjs +1 -1
- package/dist/event.js +1 -1
- package/dist/{functional-BGkT8J-h.js → functional-DtI0u4vx.js} +19 -19
- package/dist/functional-DtI0u4vx.js.map +1 -0
- package/dist/{functional-C4CzoVrX.cjs → functional-zpzNLhky.cjs} +4 -4
- package/dist/{functional-C4CzoVrX.cjs.map → functional-zpzNLhky.cjs.map} +1 -1
- package/dist/functional.cjs +1 -1
- package/dist/functional.js +1 -1
- package/dist/http.cjs +1 -1
- package/dist/http.js +1 -1
- package/dist/index.cjs +5 -5
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +5 -5
- package/dist/{init-DJQOdVlN.d.ts → init-B7u-DjxM.d.ts} +57 -2
- package/dist/init-B7u-DjxM.d.ts.map +1 -0
- package/dist/{init-DvapOXCc.cjs → init-BX7AmFRl.cjs} +40 -21
- package/dist/init-BX7AmFRl.cjs.map +1 -0
- package/dist/{init-Ch6t7MNI.js → init-D-jnNMix.js} +39 -20
- package/dist/init-D-jnNMix.js.map +1 -0
- package/dist/{init-CNp-ee80.d.cts → init-DSrRmVnz.d.cts} +57 -2
- package/dist/init-DSrRmVnz.d.cts.map +1 -0
- package/dist/instrumentation.cjs +1 -1
- package/dist/instrumentation.js +1 -1
- package/dist/logger-D3Ej3DII.js +446 -0
- package/dist/logger-D3Ej3DII.js.map +1 -0
- package/dist/logger-thMPLpOG.cjs +487 -0
- package/dist/logger-thMPLpOG.cjs.map +1 -0
- package/dist/logger.cjs +8 -236
- package/dist/logger.js +2 -204
- package/dist/messaging.cjs +1 -1
- package/dist/messaging.js +1 -1
- package/dist/semantic-helpers.cjs +1 -1
- package/dist/semantic-helpers.js +1 -1
- package/dist/{track-3HY4NGV-.cjs → track-D59FfpL0.cjs} +2 -2
- package/dist/{track-3HY4NGV-.cjs.map → track-D59FfpL0.cjs.map} +1 -1
- package/dist/{track-nsKVy-pj.js → track-wc0HafS_.js} +6 -6
- package/dist/track-wc0HafS_.js.map +1 -0
- package/dist/webhook.cjs +1 -1
- package/dist/webhook.js +1 -1
- package/dist/workflow-distributed.cjs +1 -1
- package/dist/workflow-distributed.js +1 -1
- package/dist/workflow.cjs +1 -1
- package/dist/workflow.js +1 -1
- package/dist/{yaml-config-B3dQ82GR.cjs → yaml-config-Ck2uB0Dp.cjs} +2 -1
- package/dist/yaml-config-Ck2uB0Dp.cjs.map +1 -0
- package/dist/yaml-config.cjs +1 -1
- package/dist/yaml-config.d.cts +7 -1
- package/dist/yaml-config.d.cts.map +1 -1
- package/dist/yaml-config.d.ts +7 -1
- package/dist/yaml-config.d.ts.map +1 -1
- package/dist/yaml-config.js +1 -0
- package/dist/yaml-config.js.map +1 -1
- package/package.json +1 -2
- package/skills/autotel-core/SKILL.md +2 -0
- package/skills/autotel-instrumentation/SKILL.md +25 -0
- package/skills/debug-missing-spans/SKILL.md +3 -1
- package/skills/migrate-to-autotel/SKILL.md +24 -23
- package/skills/review-otel-patterns/SKILL.md +5 -4
- package/dist/functional-BGkT8J-h.js.map +0 -1
- package/dist/init-CNp-ee80.d.cts.map +0 -1
- package/dist/init-Ch6t7MNI.js.map +0 -1
- package/dist/init-DJQOdVlN.d.ts.map +0 -1
- package/dist/init-DvapOXCc.cjs.map +0 -1
- package/dist/logger.cjs.map +0 -1
- package/dist/logger.js.map +0 -1
- package/dist/track-nsKVy-pj.js.map +0 -1
- package/dist/yaml-config-B3dQ82GR.cjs.map +0 -1
- package/src/attribute-redacting-processor.test.ts +0 -763
- package/src/attribute-redacting-processor.ts +0 -621
- package/src/attributes/attachers.ts +0 -161
- package/src/attributes/builders.ts +0 -529
- package/src/attributes/domains.ts +0 -42
- package/src/attributes/index.ts +0 -81
- package/src/attributes/registry.ts +0 -323
- package/src/attributes/types.ts +0 -211
- package/src/attributes/utils.ts +0 -64
- package/src/attributes/validators.ts +0 -266
- package/src/attributes.test.ts +0 -292
- package/src/auto.ts +0 -67
- package/src/autotel-logger.test.ts +0 -548
- package/src/autotel-logger.ts +0 -364
- package/src/baggage-span-processor.test.ts +0 -202
- package/src/baggage-span-processor.ts +0 -100
- package/src/business-baggage.test.ts +0 -500
- package/src/business-baggage.ts +0 -669
- package/src/circuit-breaker.test.ts +0 -341
- package/src/circuit-breaker.ts +0 -184
- package/src/config.test.ts +0 -94
- package/src/config.ts +0 -172
- package/src/correlated-events.test.ts +0 -151
- package/src/correlated-events.ts +0 -47
- package/src/correlation-id.test.ts +0 -163
- package/src/correlation-id.ts +0 -206
- package/src/db.test.ts +0 -252
- package/src/db.ts +0 -447
- package/src/decorators.test.ts +0 -153
- package/src/decorators.ts +0 -188
- package/src/define-event.test.ts +0 -41
- package/src/define-event.ts +0 -58
- package/src/devtools.ts +0 -60
- package/src/drain-pipeline.test.ts +0 -68
- package/src/drain-pipeline.ts +0 -199
- package/src/drain-toolkit.test.ts +0 -113
- package/src/drain-toolkit.ts +0 -129
- package/src/enricher-toolkit.test.ts +0 -67
- package/src/enricher-toolkit.ts +0 -79
- package/src/enrichers.test.ts +0 -150
- package/src/enrichers.ts +0 -145
- package/src/env-config.test.ts +0 -323
- package/src/env-config.ts +0 -309
- package/src/error-catalog.test.ts +0 -133
- package/src/error-catalog.ts +0 -262
- package/src/event-queue.test.ts +0 -864
- package/src/event-queue.ts +0 -699
- package/src/event-subscriber.ts +0 -262
- package/src/event-testing.ts +0 -197
- package/src/event.test.ts +0 -1104
- package/src/event.ts +0 -988
- package/src/events-config.ts +0 -235
- package/src/exporters.ts +0 -165
- package/src/filtering-span-processor.test.ts +0 -281
- package/src/filtering-span-processor.ts +0 -111
- package/src/flatten-attributes.test.ts +0 -76
- package/src/flatten-attributes.ts +0 -80
- package/src/functional.strict-types.typecheck.ts +0 -53
- package/src/functional.test.ts +0 -1464
- package/src/functional.ts +0 -2539
- package/src/functional.types.test.ts +0 -135
- package/src/hook.mjs +0 -15
- package/src/http.test.ts +0 -485
- package/src/http.ts +0 -424
- package/src/index.ts +0 -433
- package/src/init-auto-redactor.test.ts +0 -53
- package/src/init-redactor.test.ts +0 -8
- package/src/init.customization.test.ts +0 -594
- package/src/init.integrations.test.ts +0 -399
- package/src/init.openllmetry.test.ts +0 -194
- package/src/init.protocol.test.ts +0 -215
- package/src/init.ts +0 -2312
- package/src/instrumentation.test.ts +0 -108
- package/src/instrumentation.ts +0 -319
- package/src/logger.test.ts +0 -125
- package/src/logger.ts +0 -341
- package/src/messaging-adapters.test.ts +0 -595
- package/src/messaging-adapters.ts +0 -583
- package/src/messaging-testing.test.ts +0 -573
- package/src/messaging-testing.ts +0 -935
- package/src/messaging.test.ts +0 -1646
- package/src/messaging.ts +0 -2245
- package/src/metric-helpers.ts +0 -47
- package/src/metric-testing.ts +0 -197
- package/src/metric.ts +0 -446
- package/src/metrics.test.ts +0 -241
- package/src/node-require.ts +0 -123
- package/src/operation-context.ts +0 -93
- package/src/parse-error.test.ts +0 -73
- package/src/parse-error.ts +0 -112
- package/src/posthog-logs.test.ts +0 -115
- package/src/posthog-logs.ts +0 -77
- package/src/pretty-console-exporter.test.ts +0 -545
- package/src/pretty-console-exporter.ts +0 -413
- package/src/pretty-log-formatter.test.ts +0 -123
- package/src/pretty-log-formatter.ts +0 -210
- package/src/processors/canonical-log-line-processor.test.ts +0 -523
- package/src/processors/canonical-log-line-processor.ts +0 -396
- package/src/processors.ts +0 -152
- package/src/rate-limiter.test.ts +0 -199
- package/src/rate-limiter.ts +0 -98
- package/src/redact-values.test.ts +0 -90
- package/src/redact-values.ts +0 -34
- package/src/register.ts +0 -37
- package/src/request-logger.test.ts +0 -545
- package/src/request-logger.ts +0 -342
- package/src/sampling.test.ts +0 -1060
- package/src/sampling.ts +0 -737
- package/src/security-schema.test.ts +0 -45
- package/src/security-schema.ts +0 -107
- package/src/semantic-conventions.ts +0 -15
- package/src/semantic-helpers.test.ts +0 -226
- package/src/semantic-helpers.ts +0 -438
- package/src/shutdown.test.ts +0 -364
- package/src/shutdown.ts +0 -246
- package/src/span-name-normalizer.test.ts +0 -377
- package/src/span-name-normalizer.ts +0 -213
- package/src/stable-hash.ts +0 -27
- package/src/structured-error.test.ts +0 -191
- package/src/structured-error.ts +0 -157
- package/src/stub.integration.test.ts +0 -361
- package/src/tail-sampling-processor.test.ts +0 -230
- package/src/tail-sampling-processor.ts +0 -55
- package/src/test-span-collector.test.ts +0 -234
- package/src/test-span-collector.ts +0 -150
- package/src/testing.ts +0 -705
- package/src/trace-context.test.ts +0 -73
- package/src/trace-context.ts +0 -567
- package/src/trace-helpers.new.test.ts +0 -278
- package/src/trace-helpers.test.ts +0 -290
- package/src/trace-helpers.ts +0 -710
- package/src/trace-hybrid.test.ts +0 -42
- package/src/trace-hybrid.ts +0 -37
- package/src/tracer-provider.test.ts +0 -183
- package/src/tracer-provider.ts +0 -266
- package/src/track.test.ts +0 -154
- package/src/track.ts +0 -216
- package/src/validate.test.ts +0 -287
- package/src/validate.ts +0 -307
- package/src/validation-attributes.ts +0 -43
- package/src/validation.test.ts +0 -330
- package/src/validation.ts +0 -246
- package/src/variable-name-inference.test.ts +0 -178
- package/src/variable-name-inference.ts +0 -242
- package/src/webhook.test.ts +0 -649
- package/src/webhook.ts +0 -637
- package/src/workflow-distributed.test.ts +0 -786
- package/src/workflow-distributed.ts +0 -916
- package/src/workflow.async-safety.integration.test.ts +0 -345
- package/src/workflow.test.ts +0 -647
- package/src/workflow.ts +0 -810
- package/src/yaml-config.test.ts +0 -337
- package/src/yaml-config.ts +0 -342
package/src/event.test.ts
DELETED
|
@@ -1,1104 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
|
|
2
|
-
import { Event, getEvents, resetEvents } from './event';
|
|
3
|
-
import { type Logger } from './logger';
|
|
4
|
-
import { init } from './init';
|
|
5
|
-
import { shutdown } from './shutdown';
|
|
6
|
-
import { trace } from './functional';
|
|
7
|
-
import { context, propagation } from '@opentelemetry/api';
|
|
8
|
-
|
|
9
|
-
describe('Events', () => {
|
|
10
|
-
let mockLogger: Logger;
|
|
11
|
-
|
|
12
|
-
beforeEach(() => {
|
|
13
|
-
resetEvents();
|
|
14
|
-
mockLogger = {
|
|
15
|
-
info: vi.fn(),
|
|
16
|
-
warn: vi.fn(),
|
|
17
|
-
error: vi.fn(),
|
|
18
|
-
debug: vi.fn(),
|
|
19
|
-
};
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
describe('trackEvent', () => {
|
|
23
|
-
it('should track business events', () => {
|
|
24
|
-
const event = new Event('test-service', { logger: mockLogger });
|
|
25
|
-
|
|
26
|
-
event.trackEvent('application.submitted', {
|
|
27
|
-
jobId: '123',
|
|
28
|
-
userId: '456',
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
// Pino-native: (extra, message)
|
|
32
|
-
expect(mockLogger.info).toHaveBeenCalledWith(
|
|
33
|
-
{
|
|
34
|
-
event: 'application.submitted',
|
|
35
|
-
attributes: { service: 'test-service', jobId: '123', userId: '456' },
|
|
36
|
-
},
|
|
37
|
-
'Event tracked',
|
|
38
|
-
);
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it('should track events without attributes', () => {
|
|
42
|
-
const event = new Event('test-service', { logger: mockLogger });
|
|
43
|
-
|
|
44
|
-
event.trackEvent('user.login');
|
|
45
|
-
|
|
46
|
-
// Pino-native: (extra, message)
|
|
47
|
-
expect(mockLogger.info).toHaveBeenCalledWith(
|
|
48
|
-
{
|
|
49
|
-
event: 'user.login',
|
|
50
|
-
attributes: { service: 'test-service' },
|
|
51
|
-
},
|
|
52
|
-
'Event tracked',
|
|
53
|
-
);
|
|
54
|
-
});
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
describe('trackFunnelStep', () => {
|
|
58
|
-
it('should track funnel progression', () => {
|
|
59
|
-
const event = new Event('test-service', { logger: mockLogger });
|
|
60
|
-
|
|
61
|
-
event.trackFunnelStep('checkout', 'started', { cartValue: 99.99 });
|
|
62
|
-
event.trackFunnelStep('checkout', 'completed', { cartValue: 99.99 });
|
|
63
|
-
|
|
64
|
-
expect(mockLogger.info).toHaveBeenCalledTimes(2);
|
|
65
|
-
// Pino-native: (extra, message)
|
|
66
|
-
expect(mockLogger.info).toHaveBeenCalledWith(
|
|
67
|
-
{
|
|
68
|
-
funnel: 'checkout',
|
|
69
|
-
status: 'started',
|
|
70
|
-
attributes: { service: 'test-service', cartValue: 99.99 },
|
|
71
|
-
},
|
|
72
|
-
'Funnel step tracked',
|
|
73
|
-
);
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
it('should track funnel abandonment', () => {
|
|
77
|
-
const event = new Event('test-service', { logger: mockLogger });
|
|
78
|
-
|
|
79
|
-
event.trackFunnelStep('checkout', 'abandoned', { reason: 'timeout' });
|
|
80
|
-
|
|
81
|
-
// Pino-native: (extra, message)
|
|
82
|
-
expect(mockLogger.info).toHaveBeenCalledWith(
|
|
83
|
-
{
|
|
84
|
-
funnel: 'checkout',
|
|
85
|
-
status: 'abandoned',
|
|
86
|
-
attributes: { service: 'test-service', reason: 'timeout' },
|
|
87
|
-
},
|
|
88
|
-
'Funnel step tracked',
|
|
89
|
-
);
|
|
90
|
-
});
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
describe('trackOutcome', () => {
|
|
94
|
-
it('should track successful outcomes', () => {
|
|
95
|
-
const event = new Event('test-service', { logger: mockLogger });
|
|
96
|
-
|
|
97
|
-
event.trackOutcome('email.delivery', 'success', {
|
|
98
|
-
recipientType: 'school',
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
// Pino-native: (extra, message)
|
|
102
|
-
expect(mockLogger.info).toHaveBeenCalledWith(
|
|
103
|
-
{
|
|
104
|
-
operation: 'email.delivery',
|
|
105
|
-
status: 'success',
|
|
106
|
-
attributes: { service: 'test-service', recipientType: 'school' },
|
|
107
|
-
},
|
|
108
|
-
'Outcome tracked',
|
|
109
|
-
);
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
it('should track failed outcomes', () => {
|
|
113
|
-
const event = new Event('test-service', { logger: mockLogger });
|
|
114
|
-
|
|
115
|
-
event.trackOutcome('email.delivery', 'failure', {
|
|
116
|
-
error: 'invalid_email',
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
// Pino-native: (extra, message)
|
|
120
|
-
expect(mockLogger.info).toHaveBeenCalledWith(
|
|
121
|
-
{
|
|
122
|
-
operation: 'email.delivery',
|
|
123
|
-
status: 'failure',
|
|
124
|
-
attributes: { service: 'test-service', error: 'invalid_email' },
|
|
125
|
-
},
|
|
126
|
-
'Outcome tracked',
|
|
127
|
-
);
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
it('should track partial outcomes', () => {
|
|
131
|
-
const event = new Event('test-service', { logger: mockLogger });
|
|
132
|
-
|
|
133
|
-
event.trackOutcome('batch.process', 'partial', {
|
|
134
|
-
successCount: 8,
|
|
135
|
-
failureCount: 2,
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
// Pino-native: (extra, message)
|
|
139
|
-
expect(mockLogger.info).toHaveBeenCalledWith(
|
|
140
|
-
{
|
|
141
|
-
operation: 'batch.process',
|
|
142
|
-
status: 'partial',
|
|
143
|
-
attributes: {
|
|
144
|
-
service: 'test-service',
|
|
145
|
-
successCount: 8,
|
|
146
|
-
failureCount: 2,
|
|
147
|
-
},
|
|
148
|
-
},
|
|
149
|
-
'Outcome tracked',
|
|
150
|
-
);
|
|
151
|
-
});
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
describe('trackValue', () => {
|
|
155
|
-
it('should track revenue metrics', () => {
|
|
156
|
-
const event = new Event('test-service', { logger: mockLogger });
|
|
157
|
-
|
|
158
|
-
event.trackValue('order.revenue', 149.99, {
|
|
159
|
-
currency: 'USD',
|
|
160
|
-
productCategory: 'electronics',
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
// Pino-native: (extra, message)
|
|
164
|
-
expect(mockLogger.debug).toHaveBeenCalledWith(
|
|
165
|
-
{
|
|
166
|
-
metric: 'order.revenue',
|
|
167
|
-
value: 149.99,
|
|
168
|
-
attributes: {
|
|
169
|
-
service: 'test-service',
|
|
170
|
-
metric: 'order.revenue',
|
|
171
|
-
currency: 'USD',
|
|
172
|
-
productCategory: 'electronics',
|
|
173
|
-
},
|
|
174
|
-
},
|
|
175
|
-
'Value tracked',
|
|
176
|
-
);
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
it('should track processing time', () => {
|
|
180
|
-
const event = new Event('test-service', { logger: mockLogger });
|
|
181
|
-
|
|
182
|
-
event.trackValue('application.processing_time', 2500, {
|
|
183
|
-
unit: 'ms',
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
// Pino-native: (extra, message)
|
|
187
|
-
expect(mockLogger.debug).toHaveBeenCalledWith(
|
|
188
|
-
{
|
|
189
|
-
metric: 'application.processing_time',
|
|
190
|
-
value: 2500,
|
|
191
|
-
attributes: {
|
|
192
|
-
service: 'test-service',
|
|
193
|
-
metric: 'application.processing_time',
|
|
194
|
-
unit: 'ms',
|
|
195
|
-
},
|
|
196
|
-
},
|
|
197
|
-
'Value tracked',
|
|
198
|
-
);
|
|
199
|
-
});
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
describe('getEvents', () => {
|
|
203
|
-
it('should return singleton instance', () => {
|
|
204
|
-
const events1 = getEvents('test-service');
|
|
205
|
-
const events2 = getEvents('test-service');
|
|
206
|
-
|
|
207
|
-
expect(events1).toBe(events2);
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
it('should return different instances for different services', () => {
|
|
211
|
-
const events1 = getEvents('service-1');
|
|
212
|
-
const events2 = getEvents('service-2');
|
|
213
|
-
|
|
214
|
-
expect(events1).not.toBe(events2);
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
it('should reset instances', () => {
|
|
218
|
-
const events1 = getEvents('test-service');
|
|
219
|
-
resetEvents();
|
|
220
|
-
const events2 = getEvents('test-service');
|
|
221
|
-
|
|
222
|
-
expect(events1).not.toBe(events2);
|
|
223
|
-
});
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
describe('real-world usage example', () => {
|
|
227
|
-
it('should track job application flow', () => {
|
|
228
|
-
const event = new Event('job-application', {
|
|
229
|
-
logger: mockLogger,
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
// User starts application
|
|
233
|
-
event.trackFunnelStep('application', 'started', { jobId: '123' });
|
|
234
|
-
|
|
235
|
-
// User submits application
|
|
236
|
-
event.trackEvent('application.submitted', {
|
|
237
|
-
jobId: '123',
|
|
238
|
-
userId: '456',
|
|
239
|
-
});
|
|
240
|
-
event.trackFunnelStep('application', 'completed', { jobId: '123' });
|
|
241
|
-
|
|
242
|
-
// Email sent successfully
|
|
243
|
-
event.trackOutcome('email.sent', 'success', {
|
|
244
|
-
recipientType: 'school',
|
|
245
|
-
jobId: '123',
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
expect(mockLogger.info).toHaveBeenCalledTimes(4);
|
|
249
|
-
});
|
|
250
|
-
|
|
251
|
-
it('should track email delivery failures', () => {
|
|
252
|
-
const event = new Event('email-service', { logger: mockLogger });
|
|
253
|
-
|
|
254
|
-
// Failed email delivery
|
|
255
|
-
event.trackOutcome('email.delivery', 'failure', {
|
|
256
|
-
error: 'invalid_email',
|
|
257
|
-
recipientEmail: 'redacted',
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
// Track event for alerting
|
|
261
|
-
event.trackEvent('email.bounce', {
|
|
262
|
-
bounceType: 'permanent',
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
expect(mockLogger.info).toHaveBeenCalledTimes(2);
|
|
266
|
-
});
|
|
267
|
-
});
|
|
268
|
-
|
|
269
|
-
describe('automatic telemetry context enrichment', () => {
|
|
270
|
-
beforeEach(() => {
|
|
271
|
-
resetEvents();
|
|
272
|
-
});
|
|
273
|
-
|
|
274
|
-
afterEach(async () => {
|
|
275
|
-
await shutdown();
|
|
276
|
-
});
|
|
277
|
-
|
|
278
|
-
// Test without config first (before any init() is called)
|
|
279
|
-
it('should still work without config (graceful degradation)', () => {
|
|
280
|
-
// Don't initialize - no config available
|
|
281
|
-
const event = new Event('test-service', { logger: mockLogger });
|
|
282
|
-
|
|
283
|
-
event.trackEvent('user.login');
|
|
284
|
-
|
|
285
|
-
// Pino-native: (extra, message)
|
|
286
|
-
expect(mockLogger.info).toHaveBeenCalledWith(
|
|
287
|
-
expect.objectContaining({
|
|
288
|
-
attributes: {
|
|
289
|
-
service: 'test-service',
|
|
290
|
-
// No version/environment - gracefully omitted
|
|
291
|
-
},
|
|
292
|
-
}),
|
|
293
|
-
'Event tracked',
|
|
294
|
-
);
|
|
295
|
-
});
|
|
296
|
-
|
|
297
|
-
it('should auto-capture resource attributes (service.version, deployment.environment)', () => {
|
|
298
|
-
// Initialize with config
|
|
299
|
-
init({
|
|
300
|
-
service: 'test-service',
|
|
301
|
-
version: '2.1.0',
|
|
302
|
-
environment: 'production',
|
|
303
|
-
});
|
|
304
|
-
|
|
305
|
-
const event = new Event('test-service', { logger: mockLogger });
|
|
306
|
-
|
|
307
|
-
event.trackEvent('user.signup', { userId: '123' });
|
|
308
|
-
|
|
309
|
-
// Pino-native: (extra, message)
|
|
310
|
-
expect(mockLogger.info).toHaveBeenCalledWith(
|
|
311
|
-
expect.objectContaining({
|
|
312
|
-
attributes: expect.objectContaining({
|
|
313
|
-
service: 'test-service',
|
|
314
|
-
'service.version': '2.1.0',
|
|
315
|
-
'deployment.environment': 'production',
|
|
316
|
-
userId: '123',
|
|
317
|
-
}),
|
|
318
|
-
}),
|
|
319
|
-
'Event tracked',
|
|
320
|
-
);
|
|
321
|
-
});
|
|
322
|
-
|
|
323
|
-
it('should auto-capture trace context (traceId, spanId, correlationId) when inside a trace', async () => {
|
|
324
|
-
init({ service: 'test-service' });
|
|
325
|
-
|
|
326
|
-
const event = new Event('test-service', { logger: mockLogger });
|
|
327
|
-
|
|
328
|
-
const tracedOperation = trace('test.operation', async () => {
|
|
329
|
-
event.trackEvent('operation.started', { step: 1 });
|
|
330
|
-
});
|
|
331
|
-
|
|
332
|
-
await tracedOperation();
|
|
333
|
-
|
|
334
|
-
// Pino-native: first arg is the extra object
|
|
335
|
-
const capturedCall = (mockLogger.info as ReturnType<typeof vi.fn>).mock
|
|
336
|
-
.calls[0];
|
|
337
|
-
const attributes = capturedCall[0].attributes;
|
|
338
|
-
|
|
339
|
-
expect(attributes).toHaveProperty('traceId');
|
|
340
|
-
expect(attributes).toHaveProperty('spanId');
|
|
341
|
-
expect(attributes).toHaveProperty('correlationId');
|
|
342
|
-
expect(typeof attributes.traceId).toBe('string');
|
|
343
|
-
expect(typeof attributes.spanId).toBe('string');
|
|
344
|
-
expect(typeof attributes.correlationId).toBe('string');
|
|
345
|
-
// Correlation ID should be first 16 chars of traceId
|
|
346
|
-
expect(attributes.correlationId).toBe(attributes.traceId.slice(0, 16));
|
|
347
|
-
});
|
|
348
|
-
|
|
349
|
-
it('should enrich trackFunnelStep with telemetry context', async () => {
|
|
350
|
-
init({
|
|
351
|
-
service: 'test-service',
|
|
352
|
-
version: '1.5.0',
|
|
353
|
-
environment: 'staging',
|
|
354
|
-
});
|
|
355
|
-
|
|
356
|
-
const event = new Event('test-service', { logger: mockLogger });
|
|
357
|
-
|
|
358
|
-
const tracedOperation = trace('checkout.flow', async () => {
|
|
359
|
-
event.trackFunnelStep('checkout', 'started', { cartValue: 99.99 });
|
|
360
|
-
});
|
|
361
|
-
|
|
362
|
-
await tracedOperation();
|
|
363
|
-
|
|
364
|
-
// Pino-native: (extra, message)
|
|
365
|
-
expect(mockLogger.info).toHaveBeenCalledWith(
|
|
366
|
-
expect.objectContaining({
|
|
367
|
-
attributes: expect.objectContaining({
|
|
368
|
-
service: 'test-service',
|
|
369
|
-
'service.version': '1.5.0',
|
|
370
|
-
'deployment.environment': 'staging',
|
|
371
|
-
cartValue: 99.99,
|
|
372
|
-
traceId: expect.any(String),
|
|
373
|
-
spanId: expect.any(String),
|
|
374
|
-
correlationId: expect.any(String),
|
|
375
|
-
}),
|
|
376
|
-
}),
|
|
377
|
-
'Funnel step tracked',
|
|
378
|
-
);
|
|
379
|
-
});
|
|
380
|
-
|
|
381
|
-
it('should enrich trackOutcome with telemetry context', async () => {
|
|
382
|
-
init({
|
|
383
|
-
service: 'test-service',
|
|
384
|
-
version: '3.0.0',
|
|
385
|
-
environment: 'development',
|
|
386
|
-
});
|
|
387
|
-
|
|
388
|
-
const event = new Event('test-service', { logger: mockLogger });
|
|
389
|
-
|
|
390
|
-
const tracedOperation = trace('email.send', async () => {
|
|
391
|
-
event.trackOutcome('email.delivery', 'success', {
|
|
392
|
-
recipientType: 'user',
|
|
393
|
-
});
|
|
394
|
-
});
|
|
395
|
-
|
|
396
|
-
await tracedOperation();
|
|
397
|
-
|
|
398
|
-
// Pino-native: (extra, message)
|
|
399
|
-
expect(mockLogger.info).toHaveBeenCalledWith(
|
|
400
|
-
expect.objectContaining({
|
|
401
|
-
attributes: expect.objectContaining({
|
|
402
|
-
service: 'test-service',
|
|
403
|
-
'service.version': '3.0.0',
|
|
404
|
-
'deployment.environment': 'development',
|
|
405
|
-
recipientType: 'user',
|
|
406
|
-
traceId: expect.any(String),
|
|
407
|
-
spanId: expect.any(String),
|
|
408
|
-
correlationId: expect.any(String),
|
|
409
|
-
}),
|
|
410
|
-
}),
|
|
411
|
-
'Outcome tracked',
|
|
412
|
-
);
|
|
413
|
-
});
|
|
414
|
-
|
|
415
|
-
it('should enrich trackValue with telemetry context', async () => {
|
|
416
|
-
init({
|
|
417
|
-
service: 'test-service',
|
|
418
|
-
version: '4.2.1',
|
|
419
|
-
environment: 'production',
|
|
420
|
-
});
|
|
421
|
-
|
|
422
|
-
const event = new Event('test-service', { logger: mockLogger });
|
|
423
|
-
|
|
424
|
-
const tracedOperation = trace('order.process', async () => {
|
|
425
|
-
event.trackValue('order.revenue', 149.99, { currency: 'USD' });
|
|
426
|
-
});
|
|
427
|
-
|
|
428
|
-
await tracedOperation();
|
|
429
|
-
|
|
430
|
-
// Pino-native: (extra, message)
|
|
431
|
-
expect(mockLogger.debug).toHaveBeenCalledWith(
|
|
432
|
-
expect.objectContaining({
|
|
433
|
-
attributes: expect.objectContaining({
|
|
434
|
-
service: 'test-service',
|
|
435
|
-
metric: 'order.revenue',
|
|
436
|
-
'service.version': '4.2.1',
|
|
437
|
-
'deployment.environment': 'production',
|
|
438
|
-
currency: 'USD',
|
|
439
|
-
traceId: expect.any(String),
|
|
440
|
-
spanId: expect.any(String),
|
|
441
|
-
correlationId: expect.any(String),
|
|
442
|
-
}),
|
|
443
|
-
}),
|
|
444
|
-
'Value tracked',
|
|
445
|
-
);
|
|
446
|
-
});
|
|
447
|
-
|
|
448
|
-
it('should still work outside a trace (no trace context)', () => {
|
|
449
|
-
init({
|
|
450
|
-
service: 'test-service',
|
|
451
|
-
version: '1.0.0',
|
|
452
|
-
environment: 'test',
|
|
453
|
-
});
|
|
454
|
-
|
|
455
|
-
const event = new Event('test-service', { logger: mockLogger });
|
|
456
|
-
|
|
457
|
-
// Call outside a trace
|
|
458
|
-
event.trackEvent('background.job.completed', { jobId: 'job-123' });
|
|
459
|
-
|
|
460
|
-
// Pino-native: (extra, message)
|
|
461
|
-
expect(mockLogger.info).toHaveBeenCalledWith(
|
|
462
|
-
expect.objectContaining({
|
|
463
|
-
attributes: {
|
|
464
|
-
service: 'test-service',
|
|
465
|
-
'service.version': '1.0.0',
|
|
466
|
-
'deployment.environment': 'test',
|
|
467
|
-
jobId: 'job-123',
|
|
468
|
-
// No traceId/spanId/correlationId - gracefully omitted
|
|
469
|
-
},
|
|
470
|
-
}),
|
|
471
|
-
'Event tracked',
|
|
472
|
-
);
|
|
473
|
-
});
|
|
474
|
-
});
|
|
475
|
-
|
|
476
|
-
describe('automatic operation context enrichment', () => {
|
|
477
|
-
beforeEach(() => {
|
|
478
|
-
resetEvents();
|
|
479
|
-
});
|
|
480
|
-
|
|
481
|
-
afterEach(async () => {
|
|
482
|
-
await shutdown();
|
|
483
|
-
});
|
|
484
|
-
|
|
485
|
-
it('should auto-capture operation.name when inside trace() with string name', async () => {
|
|
486
|
-
init({ service: 'test-service' });
|
|
487
|
-
|
|
488
|
-
const event = new Event('test-service', { logger: mockLogger });
|
|
489
|
-
|
|
490
|
-
const operation = trace('user.create', async () => {
|
|
491
|
-
event.trackEvent('user.created', { userId: '123' });
|
|
492
|
-
});
|
|
493
|
-
|
|
494
|
-
await operation();
|
|
495
|
-
|
|
496
|
-
// Pino-native: (extra, message)
|
|
497
|
-
expect(mockLogger.info).toHaveBeenCalledWith(
|
|
498
|
-
expect.objectContaining({
|
|
499
|
-
attributes: expect.objectContaining({
|
|
500
|
-
'operation.name': 'user.create',
|
|
501
|
-
userId: '123',
|
|
502
|
-
}),
|
|
503
|
-
}),
|
|
504
|
-
'Event tracked',
|
|
505
|
-
);
|
|
506
|
-
});
|
|
507
|
-
|
|
508
|
-
it('should auto-capture operation.name when inside trace() with named function', async () => {
|
|
509
|
-
init({ service: 'test-service' });
|
|
510
|
-
|
|
511
|
-
const event = new Event('test-service', { logger: mockLogger });
|
|
512
|
-
|
|
513
|
-
const createUser = trace(async function createUser() {
|
|
514
|
-
event.trackEvent('user.created', { userId: '456' });
|
|
515
|
-
});
|
|
516
|
-
|
|
517
|
-
await createUser();
|
|
518
|
-
|
|
519
|
-
// Pino-native: (extra, message)
|
|
520
|
-
expect(mockLogger.info).toHaveBeenCalledWith(
|
|
521
|
-
expect.objectContaining({
|
|
522
|
-
attributes: expect.objectContaining({
|
|
523
|
-
// Function name might be inferred with slight variations (e.g., 'createUser2')
|
|
524
|
-
// The important thing is that operation.name is auto-captured
|
|
525
|
-
'operation.name': expect.stringMatching(/createUser/),
|
|
526
|
-
userId: '456',
|
|
527
|
-
}),
|
|
528
|
-
}),
|
|
529
|
-
'Event tracked',
|
|
530
|
-
);
|
|
531
|
-
});
|
|
532
|
-
|
|
533
|
-
it('should reliably infer function names in both factory and non-factory patterns', async () => {
|
|
534
|
-
init({ service: 'test-service' });
|
|
535
|
-
|
|
536
|
-
const event = new Event('test-service', { logger: mockLogger });
|
|
537
|
-
|
|
538
|
-
// Test 1: Named function declaration (non-factory pattern)
|
|
539
|
-
// Should infer name from function declaration
|
|
540
|
-
const updateUser = trace(async function updateUser(userId: string) {
|
|
541
|
-
event.trackEvent('user.updated', { userId });
|
|
542
|
-
});
|
|
543
|
-
await updateUser('user-123');
|
|
544
|
-
|
|
545
|
-
// Test 2: Named function with factory pattern (ctx parameter)
|
|
546
|
-
// Explicit name should take precedence
|
|
547
|
-
const deleteUser = trace(
|
|
548
|
-
'user.delete',
|
|
549
|
-
(ctx) =>
|
|
550
|
-
async function deleteUser(userId: string) {
|
|
551
|
-
ctx.setAttribute('user.id', userId);
|
|
552
|
-
event.trackEvent('user.deleted', { userId });
|
|
553
|
-
},
|
|
554
|
-
);
|
|
555
|
-
await deleteUser('user-456');
|
|
556
|
-
|
|
557
|
-
// Test 3: Named function in factory pattern (should infer inner function name)
|
|
558
|
-
const createOrder = trace(
|
|
559
|
-
(ctx) =>
|
|
560
|
-
async function createOrder(orderId: string) {
|
|
561
|
-
ctx.setAttribute('order.id', orderId);
|
|
562
|
-
event.trackEvent('order.created', { orderId });
|
|
563
|
-
},
|
|
564
|
-
);
|
|
565
|
-
await createOrder('order-789');
|
|
566
|
-
|
|
567
|
-
// Verify all operations captured correct names
|
|
568
|
-
// Pino-native: first arg is the extra object
|
|
569
|
-
const calls = (mockLogger.info as ReturnType<typeof vi.fn>).mock.calls;
|
|
570
|
-
|
|
571
|
-
// First call: updateUser - should infer from named function declaration
|
|
572
|
-
expect(calls[0][0].attributes['operation.name']).toMatch(/updateUser/);
|
|
573
|
-
|
|
574
|
-
// Second call: user.delete - explicit name takes precedence
|
|
575
|
-
expect(calls[1][0].attributes['operation.name']).toBe('user.delete');
|
|
576
|
-
|
|
577
|
-
// Third call: createOrder - should infer from inner named function in factory pattern
|
|
578
|
-
expect(calls[2][0].attributes['operation.name']).toMatch(/createOrder/);
|
|
579
|
-
});
|
|
580
|
-
|
|
581
|
-
it('should auto-capture operation.name in nested spans', async () => {
|
|
582
|
-
init({ service: 'test-service' });
|
|
583
|
-
|
|
584
|
-
const event = new Event('test-service', { logger: mockLogger });
|
|
585
|
-
const { span } = await import('./functional');
|
|
586
|
-
|
|
587
|
-
const operation = trace('order.process', async () => {
|
|
588
|
-
span({ name: 'order.validate' }, () => {
|
|
589
|
-
// Should capture the innermost operation name
|
|
590
|
-
event.trackEvent('order.validated', { orderId: 'ord_123' });
|
|
591
|
-
});
|
|
592
|
-
});
|
|
593
|
-
|
|
594
|
-
await operation();
|
|
595
|
-
|
|
596
|
-
// Pino-native: (extra, message)
|
|
597
|
-
expect(mockLogger.info).toHaveBeenCalledWith(
|
|
598
|
-
expect.objectContaining({
|
|
599
|
-
attributes: expect.objectContaining({
|
|
600
|
-
'operation.name': 'order.validate',
|
|
601
|
-
orderId: 'ord_123',
|
|
602
|
-
}),
|
|
603
|
-
}),
|
|
604
|
-
'Event tracked',
|
|
605
|
-
);
|
|
606
|
-
});
|
|
607
|
-
|
|
608
|
-
it('should auto-capture operation.name in trackFunnelStep', async () => {
|
|
609
|
-
init({ service: 'test-service' });
|
|
610
|
-
|
|
611
|
-
const event = new Event('test-service', { logger: mockLogger });
|
|
612
|
-
|
|
613
|
-
const checkout = trace('checkout.flow', async () => {
|
|
614
|
-
event.trackFunnelStep('checkout', 'started', {
|
|
615
|
-
cartValue: 99.99,
|
|
616
|
-
});
|
|
617
|
-
});
|
|
618
|
-
|
|
619
|
-
await checkout();
|
|
620
|
-
|
|
621
|
-
// Pino-native: (extra, message)
|
|
622
|
-
expect(mockLogger.info).toHaveBeenCalledWith(
|
|
623
|
-
expect.objectContaining({
|
|
624
|
-
attributes: expect.objectContaining({
|
|
625
|
-
'operation.name': 'checkout.flow',
|
|
626
|
-
cartValue: 99.99,
|
|
627
|
-
}),
|
|
628
|
-
}),
|
|
629
|
-
'Funnel step tracked',
|
|
630
|
-
);
|
|
631
|
-
});
|
|
632
|
-
|
|
633
|
-
it('should auto-capture operation.name in trackOutcome', async () => {
|
|
634
|
-
init({ service: 'test-service' });
|
|
635
|
-
|
|
636
|
-
const event = new Event('test-service', { logger: mockLogger });
|
|
637
|
-
|
|
638
|
-
const sendEmail = trace('email.send', async () => {
|
|
639
|
-
event.trackOutcome('email.delivery', 'success', {
|
|
640
|
-
recipientType: 'user',
|
|
641
|
-
});
|
|
642
|
-
});
|
|
643
|
-
|
|
644
|
-
await sendEmail();
|
|
645
|
-
|
|
646
|
-
// Pino-native: (extra, message)
|
|
647
|
-
expect(mockLogger.info).toHaveBeenCalledWith(
|
|
648
|
-
expect.objectContaining({
|
|
649
|
-
attributes: expect.objectContaining({
|
|
650
|
-
'operation.name': 'email.send',
|
|
651
|
-
recipientType: 'user',
|
|
652
|
-
}),
|
|
653
|
-
}),
|
|
654
|
-
'Outcome tracked',
|
|
655
|
-
);
|
|
656
|
-
});
|
|
657
|
-
|
|
658
|
-
it('should auto-capture operation.name in trackValue', async () => {
|
|
659
|
-
init({ service: 'test-service' });
|
|
660
|
-
|
|
661
|
-
const event = new Event('test-service', { logger: mockLogger });
|
|
662
|
-
|
|
663
|
-
const processOrder = trace('order.process', async () => {
|
|
664
|
-
event.trackValue('order.revenue', 149.99, { currency: 'USD' });
|
|
665
|
-
});
|
|
666
|
-
|
|
667
|
-
await processOrder();
|
|
668
|
-
|
|
669
|
-
// Pino-native: (extra, message)
|
|
670
|
-
expect(mockLogger.debug).toHaveBeenCalledWith(
|
|
671
|
-
expect.objectContaining({
|
|
672
|
-
attributes: expect.objectContaining({
|
|
673
|
-
'operation.name': 'order.process',
|
|
674
|
-
currency: 'USD',
|
|
675
|
-
}),
|
|
676
|
-
}),
|
|
677
|
-
'Value tracked',
|
|
678
|
-
);
|
|
679
|
-
});
|
|
680
|
-
|
|
681
|
-
it('should handle missing operation.name gracefully (outside trace)', () => {
|
|
682
|
-
init({ service: 'test-service' });
|
|
683
|
-
|
|
684
|
-
const event = new Event('test-service', { logger: mockLogger });
|
|
685
|
-
|
|
686
|
-
// Call outside any trace
|
|
687
|
-
event.trackEvent('background.job', { jobId: 'job-123' });
|
|
688
|
-
|
|
689
|
-
// Pino-native: (extra, message)
|
|
690
|
-
expect(mockLogger.info).toHaveBeenCalledWith(
|
|
691
|
-
expect.objectContaining({
|
|
692
|
-
attributes: {
|
|
693
|
-
service: 'test-service',
|
|
694
|
-
'service.version': undefined,
|
|
695
|
-
'deployment.environment': undefined,
|
|
696
|
-
jobId: 'job-123',
|
|
697
|
-
// No operation.name - gracefully omitted
|
|
698
|
-
},
|
|
699
|
-
}),
|
|
700
|
-
'Event tracked',
|
|
701
|
-
);
|
|
702
|
-
});
|
|
703
|
-
|
|
704
|
-
it('should capture parent operation.name when not in nested span', async () => {
|
|
705
|
-
init({ service: 'test-service' });
|
|
706
|
-
|
|
707
|
-
const event = new Event('test-service', { logger: mockLogger });
|
|
708
|
-
|
|
709
|
-
const parentOperation = trace('parent.operation', async () => {
|
|
710
|
-
// Track event in parent context (not in a nested span)
|
|
711
|
-
event.trackEvent('parent.event', { step: 1 });
|
|
712
|
-
|
|
713
|
-
// Then create a nested span
|
|
714
|
-
const { span } = await import('./functional');
|
|
715
|
-
span({ name: 'child.operation' }, () => {
|
|
716
|
-
event.trackEvent('child.event', { step: 2 });
|
|
717
|
-
});
|
|
718
|
-
});
|
|
719
|
-
|
|
720
|
-
await parentOperation();
|
|
721
|
-
|
|
722
|
-
// Check parent event has parent operation name
|
|
723
|
-
// Pino-native: (extra, message)
|
|
724
|
-
expect(mockLogger.info).toHaveBeenNthCalledWith(
|
|
725
|
-
1,
|
|
726
|
-
expect.objectContaining({
|
|
727
|
-
attributes: expect.objectContaining({
|
|
728
|
-
'operation.name': 'parent.operation',
|
|
729
|
-
step: 1,
|
|
730
|
-
}),
|
|
731
|
-
}),
|
|
732
|
-
'Event tracked',
|
|
733
|
-
);
|
|
734
|
-
|
|
735
|
-
// Check child event has child operation name
|
|
736
|
-
// Pino-native: (extra, message)
|
|
737
|
-
expect(mockLogger.info).toHaveBeenNthCalledWith(
|
|
738
|
-
2,
|
|
739
|
-
expect.objectContaining({
|
|
740
|
-
attributes: expect.objectContaining({
|
|
741
|
-
'operation.name': 'child.operation',
|
|
742
|
-
step: 2,
|
|
743
|
-
}),
|
|
744
|
-
}),
|
|
745
|
-
'Event tracked',
|
|
746
|
-
);
|
|
747
|
-
});
|
|
748
|
-
|
|
749
|
-
it('should work with trace() factory pattern', async () => {
|
|
750
|
-
init({ service: 'test-service' });
|
|
751
|
-
|
|
752
|
-
const event = new Event('test-service', { logger: mockLogger });
|
|
753
|
-
|
|
754
|
-
const operation = trace('factory.operation', (ctx) => async () => {
|
|
755
|
-
ctx.setAttribute('custom', 'attribute');
|
|
756
|
-
event.trackEvent('factory.event', { data: 'test' });
|
|
757
|
-
});
|
|
758
|
-
|
|
759
|
-
await operation();
|
|
760
|
-
|
|
761
|
-
// Pino-native: (extra, message)
|
|
762
|
-
expect(mockLogger.info).toHaveBeenCalledWith(
|
|
763
|
-
expect.objectContaining({
|
|
764
|
-
attributes: expect.objectContaining({
|
|
765
|
-
'operation.name': 'factory.operation',
|
|
766
|
-
data: 'test',
|
|
767
|
-
}),
|
|
768
|
-
}),
|
|
769
|
-
'Event tracked',
|
|
770
|
-
);
|
|
771
|
-
});
|
|
772
|
-
});
|
|
773
|
-
|
|
774
|
-
describe('autotel trace context', () => {
|
|
775
|
-
beforeEach(() => {
|
|
776
|
-
resetEvents();
|
|
777
|
-
});
|
|
778
|
-
|
|
779
|
-
afterEach(async () => {
|
|
780
|
-
await shutdown();
|
|
781
|
-
});
|
|
782
|
-
|
|
783
|
-
it('should always include correlation_id even without includeTraceContext', async () => {
|
|
784
|
-
init({
|
|
785
|
-
service: 'test-service',
|
|
786
|
-
// events.includeTraceContext is false by default
|
|
787
|
-
});
|
|
788
|
-
|
|
789
|
-
const mockSubscriber = {
|
|
790
|
-
name: 'MockSubscriber',
|
|
791
|
-
trackEvent: vi.fn().mockResolvedValue(undefined),
|
|
792
|
-
trackFunnelStep: vi.fn().mockResolvedValue(undefined),
|
|
793
|
-
trackOutcome: vi.fn().mockResolvedValue(undefined),
|
|
794
|
-
trackValue: vi.fn().mockResolvedValue(undefined),
|
|
795
|
-
};
|
|
796
|
-
|
|
797
|
-
const event = new Event('test-service', {
|
|
798
|
-
subscribers: [mockSubscriber],
|
|
799
|
-
});
|
|
800
|
-
|
|
801
|
-
event.trackEvent('test.event', { userId: '123' });
|
|
802
|
-
|
|
803
|
-
// Wait for async subscriber notification
|
|
804
|
-
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
805
|
-
|
|
806
|
-
expect(mockSubscriber.trackEvent).toHaveBeenCalledWith(
|
|
807
|
-
'test.event',
|
|
808
|
-
expect.any(Object),
|
|
809
|
-
expect.objectContaining({
|
|
810
|
-
autotel: expect.objectContaining({
|
|
811
|
-
correlation_id: expect.any(String),
|
|
812
|
-
}),
|
|
813
|
-
}),
|
|
814
|
-
);
|
|
815
|
-
|
|
816
|
-
// Correlation ID should be 16 hex chars
|
|
817
|
-
const call = mockSubscriber.trackEvent.mock.calls[0];
|
|
818
|
-
const autotelContext = call[2].autotel;
|
|
819
|
-
expect(autotelContext.correlation_id).toHaveLength(16);
|
|
820
|
-
expect(/^[0-9a-f]{16}$/.test(autotelContext.correlation_id)).toBe(true);
|
|
821
|
-
});
|
|
822
|
-
|
|
823
|
-
it('should include full trace context when includeTraceContext is enabled', async () => {
|
|
824
|
-
init({
|
|
825
|
-
service: 'test-service',
|
|
826
|
-
events: {
|
|
827
|
-
includeTraceContext: true,
|
|
828
|
-
},
|
|
829
|
-
});
|
|
830
|
-
|
|
831
|
-
const mockSubscriber = {
|
|
832
|
-
name: 'MockSubscriber',
|
|
833
|
-
trackEvent: vi.fn().mockResolvedValue(undefined),
|
|
834
|
-
trackFunnelStep: vi.fn().mockResolvedValue(undefined),
|
|
835
|
-
trackOutcome: vi.fn().mockResolvedValue(undefined),
|
|
836
|
-
trackValue: vi.fn().mockResolvedValue(undefined),
|
|
837
|
-
};
|
|
838
|
-
|
|
839
|
-
const event = new Event('test-service', {
|
|
840
|
-
subscribers: [mockSubscriber],
|
|
841
|
-
});
|
|
842
|
-
|
|
843
|
-
// Track event inside a trace
|
|
844
|
-
const tracedOperation = trace('test.operation', async () => {
|
|
845
|
-
event.trackEvent('traced.event', { data: 'test' });
|
|
846
|
-
});
|
|
847
|
-
|
|
848
|
-
await tracedOperation();
|
|
849
|
-
|
|
850
|
-
// Wait for async subscriber notification
|
|
851
|
-
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
852
|
-
|
|
853
|
-
expect(mockSubscriber.trackEvent).toHaveBeenCalledWith(
|
|
854
|
-
'traced.event',
|
|
855
|
-
expect.any(Object),
|
|
856
|
-
expect.objectContaining({
|
|
857
|
-
autotel: expect.objectContaining({
|
|
858
|
-
trace_id: expect.any(String),
|
|
859
|
-
span_id: expect.any(String),
|
|
860
|
-
trace_flags: expect.any(String),
|
|
861
|
-
correlation_id: expect.any(String),
|
|
862
|
-
}),
|
|
863
|
-
}),
|
|
864
|
-
);
|
|
865
|
-
|
|
866
|
-
// Verify trace_id is 32 hex chars
|
|
867
|
-
const call = mockSubscriber.trackEvent.mock.calls[0];
|
|
868
|
-
const autotelContext = call[2].autotel;
|
|
869
|
-
expect(autotelContext.trace_id).toHaveLength(32);
|
|
870
|
-
expect(/^[0-9a-f]{32}$/.test(autotelContext.trace_id)).toBe(true);
|
|
871
|
-
|
|
872
|
-
// Verify span_id is 16 hex chars
|
|
873
|
-
expect(autotelContext.span_id).toHaveLength(16);
|
|
874
|
-
expect(/^[0-9a-f]{16}$/.test(autotelContext.span_id)).toBe(true);
|
|
875
|
-
|
|
876
|
-
// Verify trace_flags is 2 hex chars
|
|
877
|
-
expect(autotelContext.trace_flags).toHaveLength(2);
|
|
878
|
-
});
|
|
879
|
-
|
|
880
|
-
it('should call traceUrl function when configured', async () => {
|
|
881
|
-
const traceUrlFn = vi
|
|
882
|
-
.fn()
|
|
883
|
-
.mockReturnValue('https://traces.example.com/trace/abc123');
|
|
884
|
-
|
|
885
|
-
init({
|
|
886
|
-
service: 'test-service',
|
|
887
|
-
events: {
|
|
888
|
-
includeTraceContext: true,
|
|
889
|
-
traceUrl: traceUrlFn,
|
|
890
|
-
},
|
|
891
|
-
});
|
|
892
|
-
|
|
893
|
-
const mockSubscriber = {
|
|
894
|
-
name: 'MockSubscriber',
|
|
895
|
-
trackEvent: vi.fn().mockResolvedValue(undefined),
|
|
896
|
-
trackFunnelStep: vi.fn().mockResolvedValue(undefined),
|
|
897
|
-
trackOutcome: vi.fn().mockResolvedValue(undefined),
|
|
898
|
-
trackValue: vi.fn().mockResolvedValue(undefined),
|
|
899
|
-
};
|
|
900
|
-
|
|
901
|
-
const event = new Event('test-service', {
|
|
902
|
-
subscribers: [mockSubscriber],
|
|
903
|
-
});
|
|
904
|
-
|
|
905
|
-
// Track event inside a trace
|
|
906
|
-
const tracedOperation = trace('test.operation', async () => {
|
|
907
|
-
event.trackEvent('traced.event', {});
|
|
908
|
-
});
|
|
909
|
-
|
|
910
|
-
await tracedOperation();
|
|
911
|
-
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
912
|
-
|
|
913
|
-
expect(traceUrlFn).toHaveBeenCalledWith(
|
|
914
|
-
expect.objectContaining({
|
|
915
|
-
traceId: expect.any(String),
|
|
916
|
-
spanId: expect.any(String),
|
|
917
|
-
correlationId: expect.any(String),
|
|
918
|
-
serviceName: 'test-service',
|
|
919
|
-
}),
|
|
920
|
-
);
|
|
921
|
-
|
|
922
|
-
expect(mockSubscriber.trackEvent).toHaveBeenCalledWith(
|
|
923
|
-
'traced.event',
|
|
924
|
-
expect.any(Object),
|
|
925
|
-
expect.objectContaining({
|
|
926
|
-
autotel: expect.objectContaining({
|
|
927
|
-
trace_url: 'https://traces.example.com/trace/abc123',
|
|
928
|
-
}),
|
|
929
|
-
}),
|
|
930
|
-
);
|
|
931
|
-
});
|
|
932
|
-
|
|
933
|
-
it('should include autotel context in trackFunnelStep', async () => {
|
|
934
|
-
init({
|
|
935
|
-
service: 'test-service',
|
|
936
|
-
events: {
|
|
937
|
-
includeTraceContext: true,
|
|
938
|
-
},
|
|
939
|
-
});
|
|
940
|
-
|
|
941
|
-
const mockSubscriber = {
|
|
942
|
-
name: 'MockSubscriber',
|
|
943
|
-
trackEvent: vi.fn().mockResolvedValue(undefined),
|
|
944
|
-
trackFunnelStep: vi.fn().mockResolvedValue(undefined),
|
|
945
|
-
trackOutcome: vi.fn().mockResolvedValue(undefined),
|
|
946
|
-
trackValue: vi.fn().mockResolvedValue(undefined),
|
|
947
|
-
};
|
|
948
|
-
|
|
949
|
-
const event = new Event('test-service', {
|
|
950
|
-
subscribers: [mockSubscriber],
|
|
951
|
-
});
|
|
952
|
-
|
|
953
|
-
const tracedOperation = trace('checkout.flow', async () => {
|
|
954
|
-
event.trackFunnelStep('checkout', 'started', { cartValue: 99.99 });
|
|
955
|
-
});
|
|
956
|
-
|
|
957
|
-
await tracedOperation();
|
|
958
|
-
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
959
|
-
|
|
960
|
-
expect(mockSubscriber.trackFunnelStep).toHaveBeenCalledWith(
|
|
961
|
-
'checkout',
|
|
962
|
-
'started',
|
|
963
|
-
expect.any(Object),
|
|
964
|
-
expect.objectContaining({
|
|
965
|
-
autotel: expect.objectContaining({
|
|
966
|
-
trace_id: expect.any(String),
|
|
967
|
-
correlation_id: expect.any(String),
|
|
968
|
-
}),
|
|
969
|
-
}),
|
|
970
|
-
);
|
|
971
|
-
});
|
|
972
|
-
|
|
973
|
-
it('should include autotel context in trackOutcome', async () => {
|
|
974
|
-
init({
|
|
975
|
-
service: 'test-service',
|
|
976
|
-
events: {
|
|
977
|
-
includeTraceContext: true,
|
|
978
|
-
},
|
|
979
|
-
});
|
|
980
|
-
|
|
981
|
-
const mockSubscriber = {
|
|
982
|
-
name: 'MockSubscriber',
|
|
983
|
-
trackEvent: vi.fn().mockResolvedValue(undefined),
|
|
984
|
-
trackFunnelStep: vi.fn().mockResolvedValue(undefined),
|
|
985
|
-
trackOutcome: vi.fn().mockResolvedValue(undefined),
|
|
986
|
-
trackValue: vi.fn().mockResolvedValue(undefined),
|
|
987
|
-
};
|
|
988
|
-
|
|
989
|
-
const event = new Event('test-service', {
|
|
990
|
-
subscribers: [mockSubscriber],
|
|
991
|
-
});
|
|
992
|
-
|
|
993
|
-
const tracedOperation = trace('payment.process', async () => {
|
|
994
|
-
event.trackOutcome('payment', 'success', { amount: 99.99 });
|
|
995
|
-
});
|
|
996
|
-
|
|
997
|
-
await tracedOperation();
|
|
998
|
-
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
999
|
-
|
|
1000
|
-
expect(mockSubscriber.trackOutcome).toHaveBeenCalledWith(
|
|
1001
|
-
'payment',
|
|
1002
|
-
'success',
|
|
1003
|
-
expect.any(Object),
|
|
1004
|
-
expect.objectContaining({
|
|
1005
|
-
autotel: expect.objectContaining({
|
|
1006
|
-
trace_id: expect.any(String),
|
|
1007
|
-
correlation_id: expect.any(String),
|
|
1008
|
-
}),
|
|
1009
|
-
}),
|
|
1010
|
-
);
|
|
1011
|
-
});
|
|
1012
|
-
|
|
1013
|
-
it('should include autotel context in trackValue', async () => {
|
|
1014
|
-
init({
|
|
1015
|
-
service: 'test-service',
|
|
1016
|
-
events: {
|
|
1017
|
-
includeTraceContext: true,
|
|
1018
|
-
},
|
|
1019
|
-
});
|
|
1020
|
-
|
|
1021
|
-
const mockSubscriber = {
|
|
1022
|
-
name: 'MockSubscriber',
|
|
1023
|
-
trackEvent: vi.fn().mockResolvedValue(undefined),
|
|
1024
|
-
trackFunnelStep: vi.fn().mockResolvedValue(undefined),
|
|
1025
|
-
trackOutcome: vi.fn().mockResolvedValue(undefined),
|
|
1026
|
-
trackValue: vi.fn().mockResolvedValue(undefined),
|
|
1027
|
-
};
|
|
1028
|
-
|
|
1029
|
-
const event = new Event('test-service', {
|
|
1030
|
-
subscribers: [mockSubscriber],
|
|
1031
|
-
});
|
|
1032
|
-
|
|
1033
|
-
const tracedOperation = trace('order.process', async () => {
|
|
1034
|
-
event.trackValue('revenue', 149.99, { currency: 'USD' });
|
|
1035
|
-
});
|
|
1036
|
-
|
|
1037
|
-
await tracedOperation();
|
|
1038
|
-
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
1039
|
-
|
|
1040
|
-
expect(mockSubscriber.trackValue).toHaveBeenCalledWith(
|
|
1041
|
-
'revenue',
|
|
1042
|
-
149.99,
|
|
1043
|
-
expect.any(Object),
|
|
1044
|
-
expect.objectContaining({
|
|
1045
|
-
autotel: expect.objectContaining({
|
|
1046
|
-
trace_id: expect.any(String),
|
|
1047
|
-
correlation_id: expect.any(String),
|
|
1048
|
-
}),
|
|
1049
|
-
}),
|
|
1050
|
-
);
|
|
1051
|
-
});
|
|
1052
|
-
});
|
|
1053
|
-
|
|
1054
|
-
describe('baggage enrichment', () => {
|
|
1055
|
-
afterEach(async () => {
|
|
1056
|
-
await shutdown();
|
|
1057
|
-
});
|
|
1058
|
-
|
|
1059
|
-
it('should apply maxBytes after hashing baggage values', async () => {
|
|
1060
|
-
init({
|
|
1061
|
-
service: 'test-service',
|
|
1062
|
-
events: {
|
|
1063
|
-
enrichFromBaggage: {
|
|
1064
|
-
allow: ['user.id'],
|
|
1065
|
-
maxBytes: 16,
|
|
1066
|
-
transform: {
|
|
1067
|
-
'user.id': 'hash',
|
|
1068
|
-
},
|
|
1069
|
-
},
|
|
1070
|
-
},
|
|
1071
|
-
});
|
|
1072
|
-
|
|
1073
|
-
const mockSubscriber = {
|
|
1074
|
-
name: 'MockSubscriber',
|
|
1075
|
-
trackEvent: vi.fn().mockResolvedValue(undefined),
|
|
1076
|
-
trackFunnelStep: vi.fn().mockResolvedValue(undefined),
|
|
1077
|
-
trackOutcome: vi.fn().mockResolvedValue(undefined),
|
|
1078
|
-
trackValue: vi.fn().mockResolvedValue(undefined),
|
|
1079
|
-
};
|
|
1080
|
-
|
|
1081
|
-
const event = new Event('test-service', {
|
|
1082
|
-
subscribers: [mockSubscriber],
|
|
1083
|
-
});
|
|
1084
|
-
|
|
1085
|
-
const largeValue = 'x'.repeat(2048);
|
|
1086
|
-
const baggage = propagation
|
|
1087
|
-
.createBaggage()
|
|
1088
|
-
.setEntry('user.id', { value: largeValue });
|
|
1089
|
-
const ctx = propagation.setBaggage(context.active(), baggage);
|
|
1090
|
-
|
|
1091
|
-
context.with(ctx, () => {
|
|
1092
|
-
event.trackEvent('test.event', { foo: 'bar' });
|
|
1093
|
-
});
|
|
1094
|
-
|
|
1095
|
-
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
1096
|
-
|
|
1097
|
-
const call = mockSubscriber.trackEvent.mock.calls[0];
|
|
1098
|
-
const attributes = call[1] as Record<string, unknown>;
|
|
1099
|
-
|
|
1100
|
-
expect(attributes['user.id']).toBeDefined();
|
|
1101
|
-
expect(String(attributes['user.id'])).toHaveLength(8);
|
|
1102
|
-
});
|
|
1103
|
-
});
|
|
1104
|
-
});
|