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
package/src/event.ts
ADDED
|
@@ -0,0 +1,556 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Events API for product events platforms
|
|
3
|
+
*
|
|
4
|
+
* Track user behavior, business events, and critical actions.
|
|
5
|
+
* Sends to product events platforms (PostHog, Mixpanel, Amplitude) via subscribers.
|
|
6
|
+
* For business people who think in events/funnels.
|
|
7
|
+
*
|
|
8
|
+
* For OpenTelemetry metrics (Prometheus/Grafana), use the Metrics class instead.
|
|
9
|
+
*
|
|
10
|
+
* @example Recommended: Configure subscribers in init(), use track() function
|
|
11
|
+
* ```typescript
|
|
12
|
+
* import { init, track } from 'autotel';
|
|
13
|
+
* import { PostHogSubscriber } from 'autotel-subscribers/posthog';
|
|
14
|
+
*
|
|
15
|
+
* init({
|
|
16
|
+
* service: 'my-app',
|
|
17
|
+
* subscribers: [new PostHogSubscriber({ apiKey: 'phc_...' })]
|
|
18
|
+
* });
|
|
19
|
+
*
|
|
20
|
+
* // Track events - uses subscribers from init()
|
|
21
|
+
* track('application.submitted', { jobId: '123', userId: '456' });
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* @example Create Event instance (inherits subscribers from init)
|
|
25
|
+
* ```typescript
|
|
26
|
+
* import { Event } from 'autotel/event';
|
|
27
|
+
*
|
|
28
|
+
* // Uses subscribers configured in init()
|
|
29
|
+
* const event = new Event('job-application');
|
|
30
|
+
* event.trackEvent('application.submitted', { jobId: '123' });
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* @example Override subscribers for specific Event instance
|
|
34
|
+
* ```typescript
|
|
35
|
+
* import { Event } from 'autotel/event';
|
|
36
|
+
* import { PostHogSubscriber } from 'autotel-subscribers/posthog';
|
|
37
|
+
*
|
|
38
|
+
* // Override: use different subscribers for this instance
|
|
39
|
+
* const event = new Event('job-application', {
|
|
40
|
+
* subscribers: [new PostHogSubscriber({ apiKey: 'phc_different_project' })]
|
|
41
|
+
* });
|
|
42
|
+
*
|
|
43
|
+
* event.trackEvent('application.submitted', { jobId: '123' });
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
|
|
47
|
+
import { trace } from '@opentelemetry/api';
|
|
48
|
+
import { type Logger } from './logger';
|
|
49
|
+
import { getLogger, getValidationConfig, getConfig } from './init';
|
|
50
|
+
import {
|
|
51
|
+
type EventSubscriber,
|
|
52
|
+
type EventAttributes,
|
|
53
|
+
type FunnelStatus,
|
|
54
|
+
type OutcomeStatus,
|
|
55
|
+
} from './event-subscriber';
|
|
56
|
+
import { type EventCollector } from './event-testing';
|
|
57
|
+
import { CircuitBreaker, CircuitOpenError } from './circuit-breaker';
|
|
58
|
+
import { validateEvent } from './validation';
|
|
59
|
+
import { getOperationContext } from './operation-context';
|
|
60
|
+
|
|
61
|
+
// Re-export types for convenience
|
|
62
|
+
export type {
|
|
63
|
+
EventAttributes,
|
|
64
|
+
FunnelStatus,
|
|
65
|
+
OutcomeStatus,
|
|
66
|
+
} from './event-subscriber';
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Events class for tracking user behavior and product events
|
|
70
|
+
*
|
|
71
|
+
* Track critical indicators such as:
|
|
72
|
+
* - User events (signups, purchases, feature usage)
|
|
73
|
+
* - Conversion funnels (signup → activation → purchase)
|
|
74
|
+
* - Business outcomes (success/failure rates)
|
|
75
|
+
* - Product metrics (revenue, engagement, retention)
|
|
76
|
+
*
|
|
77
|
+
* All events are sent to events platforms via subscribers (PostHog, Mixpanel, etc.).
|
|
78
|
+
* For OpenTelemetry metrics, use the Metrics class instead.
|
|
79
|
+
*/
|
|
80
|
+
/**
|
|
81
|
+
* Events options
|
|
82
|
+
*/
|
|
83
|
+
export interface EventsOptions {
|
|
84
|
+
/** Optional logger for audit trail */
|
|
85
|
+
logger?: Logger;
|
|
86
|
+
/** Optional collector for testing (captures events in memory) */
|
|
87
|
+
collector?: EventCollector;
|
|
88
|
+
/**
|
|
89
|
+
* Optional subscribers to send events to other platforms
|
|
90
|
+
* (e.g., PostHog, Mixpanel, Amplitude)
|
|
91
|
+
*
|
|
92
|
+
* **Subscriber Resolution**:
|
|
93
|
+
* - If provided → uses these subscribers (instance override)
|
|
94
|
+
* - If not provided → falls back to subscribers from `init()` (global config)
|
|
95
|
+
* - If neither → no subscribers (events logged only)
|
|
96
|
+
*
|
|
97
|
+
* Install `autotel-subscribers` package for ready-made subscribers
|
|
98
|
+
*/
|
|
99
|
+
subscribers?: EventSubscriber[];
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export class Event {
|
|
103
|
+
private serviceName: string;
|
|
104
|
+
private logger?: Logger;
|
|
105
|
+
private collector?: EventCollector;
|
|
106
|
+
private subscribers: EventSubscriber[];
|
|
107
|
+
private hasSubscribers: boolean; // Cached for performance
|
|
108
|
+
private circuitBreakers: Map<EventSubscriber, CircuitBreaker>; // One per subscriber
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Create a new Event instance
|
|
112
|
+
*
|
|
113
|
+
* **Note**: Most users should use `init()` + `track()` instead of creating Event instances directly.
|
|
114
|
+
*
|
|
115
|
+
* **Subscriber Resolution**:
|
|
116
|
+
* - If `subscribers` provided in options → uses those (instance override)
|
|
117
|
+
* - If `subscribers` not provided → falls back to subscribers from `init()` (global config)
|
|
118
|
+
* - If neither → no subscribers (events logged only)
|
|
119
|
+
*
|
|
120
|
+
* @param serviceName - Service name for identifying events
|
|
121
|
+
* @param options - Optional configuration (logger, collector, subscribers)
|
|
122
|
+
*
|
|
123
|
+
* @example Recommended: Use track() with init()
|
|
124
|
+
* ```typescript
|
|
125
|
+
* import { init, track } from 'autotel';
|
|
126
|
+
* import { PostHogSubscriber } from 'autotel-subscribers/posthog';
|
|
127
|
+
*
|
|
128
|
+
* init({
|
|
129
|
+
* service: 'checkout',
|
|
130
|
+
* subscribers: [new PostHogSubscriber({ apiKey: 'phc_...' })]
|
|
131
|
+
* });
|
|
132
|
+
*
|
|
133
|
+
* track('purchase.completed', { amount: 99.99 });
|
|
134
|
+
* ```
|
|
135
|
+
*
|
|
136
|
+
* @example Inherit subscribers from init()
|
|
137
|
+
* ```typescript
|
|
138
|
+
* // Uses subscribers configured in init()
|
|
139
|
+
* const event = new Event('checkout');
|
|
140
|
+
* event.trackEvent('purchase.completed', { amount: 99.99 });
|
|
141
|
+
* ```
|
|
142
|
+
*
|
|
143
|
+
* @example Override subscribers for this instance
|
|
144
|
+
* ```typescript
|
|
145
|
+
* import { Event } from 'autotel/event';
|
|
146
|
+
* import { PostHogSubscriber } from 'autotel-subscribers/posthog';
|
|
147
|
+
*
|
|
148
|
+
* // Override: use different subscribers for this instance only
|
|
149
|
+
* const event = new Event('checkout', {
|
|
150
|
+
* subscribers: [new PostHogSubscriber({ apiKey: 'phc_different_project' })]
|
|
151
|
+
* });
|
|
152
|
+
* ```
|
|
153
|
+
*/
|
|
154
|
+
constructor(serviceName: string, options: EventsOptions = {}) {
|
|
155
|
+
this.serviceName = serviceName;
|
|
156
|
+
this.logger = options.logger;
|
|
157
|
+
this.collector = options.collector;
|
|
158
|
+
|
|
159
|
+
// Subscriber resolution: instance-level overrides global init() config
|
|
160
|
+
// If subscribers provided to constructor, use those
|
|
161
|
+
// Otherwise, fall back to subscribers from init()
|
|
162
|
+
this.subscribers =
|
|
163
|
+
options.subscribers === undefined
|
|
164
|
+
? getConfig()?.subscribers || []
|
|
165
|
+
: options.subscribers;
|
|
166
|
+
|
|
167
|
+
this.hasSubscribers = this.subscribers.length > 0; // Cache for hot path
|
|
168
|
+
|
|
169
|
+
// Create circuit breaker for each subscriber
|
|
170
|
+
this.circuitBreakers = new Map();
|
|
171
|
+
for (const subscriber of this.subscribers) {
|
|
172
|
+
const subscriberName = subscriber.name || 'Unknown';
|
|
173
|
+
this.circuitBreakers.set(
|
|
174
|
+
subscriber,
|
|
175
|
+
new CircuitBreaker(subscriberName, {
|
|
176
|
+
failureThreshold: 5,
|
|
177
|
+
resetTimeout: 30_000, // 30s
|
|
178
|
+
windowSize: 60_000, // 1min
|
|
179
|
+
}),
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Automatically enrich attributes with all available telemetry context
|
|
186
|
+
*
|
|
187
|
+
* Auto-captures:
|
|
188
|
+
* - Resource attributes: service.version, deployment.environment
|
|
189
|
+
* - Trace context: traceId, spanId, correlationId
|
|
190
|
+
* - Operation context: operation.name
|
|
191
|
+
*/
|
|
192
|
+
private enrichWithTelemetryContext(
|
|
193
|
+
attributes: EventAttributes = {},
|
|
194
|
+
): EventAttributes {
|
|
195
|
+
const enriched: EventAttributes = {
|
|
196
|
+
service: this.serviceName,
|
|
197
|
+
...attributes,
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
// 1. Resource attributes (service-level context)
|
|
201
|
+
const config = getConfig();
|
|
202
|
+
if (config) {
|
|
203
|
+
if (config.version) {
|
|
204
|
+
enriched['service.version'] = config.version;
|
|
205
|
+
}
|
|
206
|
+
if (config.environment) {
|
|
207
|
+
enriched['deployment.environment'] = config.environment;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// 2. Trace context (if inside a traced operation)
|
|
212
|
+
const span = trace.getActiveSpan();
|
|
213
|
+
const spanContext = span?.spanContext();
|
|
214
|
+
if (spanContext) {
|
|
215
|
+
enriched.traceId = spanContext.traceId;
|
|
216
|
+
enriched.spanId = spanContext.spanId;
|
|
217
|
+
// Add correlation ID (first 16 chars of trace ID) for easier log grouping
|
|
218
|
+
enriched.correlationId = spanContext.traceId.slice(0, 16);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// 3. Operation context (if inside a trace/span)
|
|
222
|
+
const operationContext = getOperationContext();
|
|
223
|
+
if (operationContext) {
|
|
224
|
+
enriched['operation.name'] = operationContext.name;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return enriched;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Track a business event
|
|
232
|
+
*
|
|
233
|
+
* Use this for tracking user actions, business events, product usage:
|
|
234
|
+
* - "user.signup"
|
|
235
|
+
* - "order.completed"
|
|
236
|
+
* - "feature.used"
|
|
237
|
+
*
|
|
238
|
+
* Events are sent to configured subscribers (PostHog, Mixpanel, etc.).
|
|
239
|
+
*
|
|
240
|
+
* @example
|
|
241
|
+
* ```typescript
|
|
242
|
+
* // Track user signup
|
|
243
|
+
* events.trackEvent('user.signup', {
|
|
244
|
+
* userId: '123',
|
|
245
|
+
* plan: 'pro'
|
|
246
|
+
* })
|
|
247
|
+
*
|
|
248
|
+
* // Track order
|
|
249
|
+
* events.trackEvent('order.completed', {
|
|
250
|
+
* orderId: 'ord_123',
|
|
251
|
+
* amount: 99.99
|
|
252
|
+
* })
|
|
253
|
+
* ```
|
|
254
|
+
*/
|
|
255
|
+
trackEvent(eventName: string, attributes?: EventAttributes): void {
|
|
256
|
+
// Validate and sanitize input (with custom config if provided)
|
|
257
|
+
const validationConfig = getValidationConfig();
|
|
258
|
+
const validated = validateEvent(
|
|
259
|
+
eventName,
|
|
260
|
+
attributes,
|
|
261
|
+
validationConfig || undefined,
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
// Auto-attach all available telemetry context
|
|
265
|
+
const enrichedAttributes = this.enrichWithTelemetryContext(
|
|
266
|
+
validated.attributes,
|
|
267
|
+
);
|
|
268
|
+
|
|
269
|
+
this.logger?.info('Event tracked', {
|
|
270
|
+
event: validated.eventName,
|
|
271
|
+
attributes: enrichedAttributes,
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
// Record for testing
|
|
275
|
+
this.collector?.recordEvent({
|
|
276
|
+
event: validated.eventName,
|
|
277
|
+
attributes: enrichedAttributes,
|
|
278
|
+
service: this.serviceName,
|
|
279
|
+
timestamp: Date.now(),
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
// Notify subscribers (zero overhead if no subscribers)
|
|
283
|
+
// Run in background - don't block event recording
|
|
284
|
+
if (this.hasSubscribers) {
|
|
285
|
+
void this.notifySubscribers((subscriber) =>
|
|
286
|
+
subscriber.trackEvent(validated.eventName, enrichedAttributes),
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Notify all subscribers concurrently without blocking
|
|
293
|
+
* Uses circuit breakers to protect against failing subscribers
|
|
294
|
+
* Uses Promise.allSettled to prevent subscriber errors from affecting other subscribers
|
|
295
|
+
*/
|
|
296
|
+
private async notifySubscribers(
|
|
297
|
+
fn: (subscriber: EventSubscriber) => Promise<void>,
|
|
298
|
+
): Promise<void> {
|
|
299
|
+
const promises = this.subscribers.map(async (subscriber) => {
|
|
300
|
+
const circuitBreaker = this.circuitBreakers.get(subscriber);
|
|
301
|
+
if (!circuitBreaker) return; // Should never happen
|
|
302
|
+
|
|
303
|
+
try {
|
|
304
|
+
// Execute with circuit breaker protection
|
|
305
|
+
await circuitBreaker.execute(() => fn(subscriber));
|
|
306
|
+
} catch (error) {
|
|
307
|
+
// Handle circuit open errors (expected behavior when subscriber is down)
|
|
308
|
+
if (error instanceof CircuitOpenError) {
|
|
309
|
+
// Circuit is open - subscriber is down, log at warn level for visibility (same behavior in all environments)
|
|
310
|
+
getLogger().warn(`[Events] ${error.message}`, {
|
|
311
|
+
subscriberName: subscriber.name || 'Unknown',
|
|
312
|
+
});
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Log other subscriber errors but don't throw - event failures shouldn't break business logic
|
|
317
|
+
getLogger().error(
|
|
318
|
+
`[Events] Subscriber ${subscriber.name || 'Unknown'} failed`,
|
|
319
|
+
error instanceof Error ? error : undefined,
|
|
320
|
+
{ subscriberName: subscriber.name || 'Unknown' },
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
// Wait for all subscribers (success or failure)
|
|
326
|
+
await Promise.allSettled(promises);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Track conversion funnel steps
|
|
331
|
+
*
|
|
332
|
+
* Monitor where users drop off in multi-step processes.
|
|
333
|
+
*
|
|
334
|
+
* @example
|
|
335
|
+
* ```typescript
|
|
336
|
+
* // Track signup funnel
|
|
337
|
+
* events.trackFunnelStep('signup', 'started', { userId: '123' })
|
|
338
|
+
* events.trackFunnelStep('signup', 'email_verified', { userId: '123' })
|
|
339
|
+
* events.trackFunnelStep('signup', 'completed', { userId: '123' })
|
|
340
|
+
*
|
|
341
|
+
* // Track checkout flow
|
|
342
|
+
* events.trackFunnelStep('checkout', 'started', { cartValue: 99.99 })
|
|
343
|
+
* events.trackFunnelStep('checkout', 'payment_info', { cartValue: 99.99 })
|
|
344
|
+
* events.trackFunnelStep('checkout', 'completed', { cartValue: 99.99 })
|
|
345
|
+
* ```
|
|
346
|
+
*/
|
|
347
|
+
trackFunnelStep(
|
|
348
|
+
funnelName: string,
|
|
349
|
+
status: FunnelStatus,
|
|
350
|
+
attributes?: EventAttributes,
|
|
351
|
+
): void {
|
|
352
|
+
// Auto-attach all available telemetry context
|
|
353
|
+
const enrichedAttributes = this.enrichWithTelemetryContext(attributes);
|
|
354
|
+
|
|
355
|
+
this.logger?.info('Funnel step tracked', {
|
|
356
|
+
funnel: funnelName,
|
|
357
|
+
status,
|
|
358
|
+
attributes: enrichedAttributes,
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
// Record for testing
|
|
362
|
+
this.collector?.recordFunnelStep({
|
|
363
|
+
funnel: funnelName,
|
|
364
|
+
status,
|
|
365
|
+
attributes: enrichedAttributes,
|
|
366
|
+
service: this.serviceName,
|
|
367
|
+
timestamp: Date.now(),
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
// Notify subscribers
|
|
371
|
+
if (this.hasSubscribers) {
|
|
372
|
+
void this.notifySubscribers((subscriber) =>
|
|
373
|
+
subscriber.trackFunnelStep(funnelName, status, enrichedAttributes),
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Track outcomes (success/failure/partial)
|
|
380
|
+
*
|
|
381
|
+
* Monitor success rates of critical operations.
|
|
382
|
+
*
|
|
383
|
+
* @example
|
|
384
|
+
* ```typescript
|
|
385
|
+
* // Track email delivery
|
|
386
|
+
* events.trackOutcome('email.delivery', 'success', {
|
|
387
|
+
* recipientType: 'user',
|
|
388
|
+
* emailType: 'welcome'
|
|
389
|
+
* })
|
|
390
|
+
*
|
|
391
|
+
* events.trackOutcome('email.delivery', 'failure', {
|
|
392
|
+
* recipientType: 'user',
|
|
393
|
+
* errorCode: 'invalid_email'
|
|
394
|
+
* })
|
|
395
|
+
*
|
|
396
|
+
* // Track payment processing
|
|
397
|
+
* events.trackOutcome('payment.process', 'success', { amount: 99.99 })
|
|
398
|
+
* events.trackOutcome('payment.process', 'failure', { error: 'insufficient_funds' })
|
|
399
|
+
* ```
|
|
400
|
+
*/
|
|
401
|
+
trackOutcome(
|
|
402
|
+
operationName: string,
|
|
403
|
+
status: OutcomeStatus,
|
|
404
|
+
attributes?: EventAttributes,
|
|
405
|
+
): void {
|
|
406
|
+
// Auto-attach all available telemetry context
|
|
407
|
+
const enrichedAttributes = this.enrichWithTelemetryContext(attributes);
|
|
408
|
+
|
|
409
|
+
this.logger?.info('Outcome tracked', {
|
|
410
|
+
operation: operationName,
|
|
411
|
+
status,
|
|
412
|
+
attributes: enrichedAttributes,
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
// Record for testing
|
|
416
|
+
this.collector?.recordOutcome({
|
|
417
|
+
operation: operationName,
|
|
418
|
+
status,
|
|
419
|
+
attributes: enrichedAttributes,
|
|
420
|
+
service: this.serviceName,
|
|
421
|
+
timestamp: Date.now(),
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
// Notify subscribers
|
|
425
|
+
if (this.hasSubscribers) {
|
|
426
|
+
void this.notifySubscribers((subscriber) =>
|
|
427
|
+
subscriber.trackOutcome(operationName, status, enrichedAttributes),
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Track value metrics
|
|
434
|
+
*
|
|
435
|
+
* Record numerical values like revenue, transaction amounts,
|
|
436
|
+
* item counts, processing times, engagement scores, etc.
|
|
437
|
+
*
|
|
438
|
+
* @example
|
|
439
|
+
* ```typescript
|
|
440
|
+
* // Track revenue
|
|
441
|
+
* events.trackValue('order.revenue', 149.99, {
|
|
442
|
+
* currency: 'USD',
|
|
443
|
+
* productCategory: 'electronics'
|
|
444
|
+
* })
|
|
445
|
+
*
|
|
446
|
+
* // Track items per cart
|
|
447
|
+
* events.trackValue('cart.item_count', 5, {
|
|
448
|
+
* userId: '123'
|
|
449
|
+
* })
|
|
450
|
+
*
|
|
451
|
+
* // Track processing time
|
|
452
|
+
* events.trackValue('api.response_time', 250, {
|
|
453
|
+
* unit: 'ms',
|
|
454
|
+
* endpoint: '/api/checkout'
|
|
455
|
+
* })
|
|
456
|
+
* ```
|
|
457
|
+
*/
|
|
458
|
+
trackValue(
|
|
459
|
+
metricName: string,
|
|
460
|
+
value: number,
|
|
461
|
+
attributes?: EventAttributes,
|
|
462
|
+
): void {
|
|
463
|
+
// Auto-attach all available telemetry context
|
|
464
|
+
const enrichedAttributes = this.enrichWithTelemetryContext({
|
|
465
|
+
metric: metricName,
|
|
466
|
+
...attributes,
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
this.logger?.debug('Value tracked', {
|
|
470
|
+
metric: metricName,
|
|
471
|
+
value,
|
|
472
|
+
attributes: enrichedAttributes,
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
// Record for testing
|
|
476
|
+
this.collector?.recordValue({
|
|
477
|
+
metric: metricName,
|
|
478
|
+
value,
|
|
479
|
+
attributes: enrichedAttributes,
|
|
480
|
+
service: this.serviceName,
|
|
481
|
+
timestamp: Date.now(),
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
// Notify subscribers
|
|
485
|
+
if (this.hasSubscribers) {
|
|
486
|
+
void this.notifySubscribers((subscriber) =>
|
|
487
|
+
subscriber.trackValue(metricName, value, enrichedAttributes),
|
|
488
|
+
);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* Flush all subscribers and wait for pending events
|
|
494
|
+
*
|
|
495
|
+
* Call this before shutdown to ensure all events are delivered.
|
|
496
|
+
*
|
|
497
|
+
* @example
|
|
498
|
+
* ```typescript
|
|
499
|
+
* const event =new Event('app', { subscribers: [...] });
|
|
500
|
+
*
|
|
501
|
+
* // Before shutdown
|
|
502
|
+
* await events.flush();
|
|
503
|
+
* ```
|
|
504
|
+
*/
|
|
505
|
+
async flush(): Promise<void> {
|
|
506
|
+
if (!this.hasSubscribers) return;
|
|
507
|
+
|
|
508
|
+
const shutdownPromises = this.subscribers.map(async (subscriber) => {
|
|
509
|
+
if (subscriber.shutdown) {
|
|
510
|
+
try {
|
|
511
|
+
await subscriber.shutdown();
|
|
512
|
+
} catch (error) {
|
|
513
|
+
getLogger().error(
|
|
514
|
+
`[Events] Failed to shutdown subscriber ${subscriber.name || 'Unknown'}`,
|
|
515
|
+
error instanceof Error ? error : undefined,
|
|
516
|
+
{ subscriberName: subscriber.name || 'Unknown' },
|
|
517
|
+
);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
await Promise.allSettled(shutdownPromises);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
/**
|
|
527
|
+
* Global events instances (singleton pattern)
|
|
528
|
+
*/
|
|
529
|
+
const eventsInstances = new Map<string, Event>();
|
|
530
|
+
|
|
531
|
+
/**
|
|
532
|
+
* Get or create an Events instance for a service
|
|
533
|
+
*
|
|
534
|
+
* @param serviceName - Service name for identifying events
|
|
535
|
+
* @param logger - Optional logger
|
|
536
|
+
* @returns Events instance
|
|
537
|
+
*
|
|
538
|
+
* @example
|
|
539
|
+
* ```typescript
|
|
540
|
+
* const event =getEvents('job-application')
|
|
541
|
+
* events.trackEvent('application.submitted', { jobId: '123' })
|
|
542
|
+
* ```
|
|
543
|
+
*/
|
|
544
|
+
export function getEvents(serviceName: string, logger?: Logger): Event {
|
|
545
|
+
if (!eventsInstances.has(serviceName)) {
|
|
546
|
+
eventsInstances.set(serviceName, new Event(serviceName, { logger }));
|
|
547
|
+
}
|
|
548
|
+
return eventsInstances.get(serviceName)!;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* Reset all events instances (mainly for testing)
|
|
553
|
+
*/
|
|
554
|
+
export function resetEvents(): void {
|
|
555
|
+
eventsInstances.clear();
|
|
556
|
+
}
|
package/src/exporters.ts
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenTelemetry Exporters
|
|
3
|
+
*
|
|
4
|
+
* Re-exports commonly-needed OpenTelemetry exporters for development and debugging.
|
|
5
|
+
*
|
|
6
|
+
* These exporters are already included in autotel's dependencies, so re-exporting
|
|
7
|
+
* them provides a "one install is all you need" developer experience without any
|
|
8
|
+
* bundle size impact.
|
|
9
|
+
*
|
|
10
|
+
* Use these for:
|
|
11
|
+
* - Development debugging (see spans in console)
|
|
12
|
+
* - Progressive development (verify instrumentation works)
|
|
13
|
+
* - Example applications (demonstrate tracing)
|
|
14
|
+
* - Testing (capture spans for assertions)
|
|
15
|
+
*
|
|
16
|
+
* @example Console debugging (development)
|
|
17
|
+
* ```typescript
|
|
18
|
+
* import { init } from 'autotel'
|
|
19
|
+
* import { ConsoleSpanExporter } from 'autotel/exporters'
|
|
20
|
+
*
|
|
21
|
+
* init({
|
|
22
|
+
* service: 'my-app',
|
|
23
|
+
* spanExporter: new ConsoleSpanExporter(),
|
|
24
|
+
* })
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* @example In-memory testing
|
|
28
|
+
* ```typescript
|
|
29
|
+
* import { init } from 'autotel'
|
|
30
|
+
* import { InMemorySpanExporter } from 'autotel/exporters'
|
|
31
|
+
* import { SimpleSpanProcessor } from 'autotel/processors'
|
|
32
|
+
*
|
|
33
|
+
* const exporter = new InMemorySpanExporter()
|
|
34
|
+
* init({
|
|
35
|
+
* service: 'test',
|
|
36
|
+
* spanProcessor: new SimpleSpanProcessor(exporter),
|
|
37
|
+
* })
|
|
38
|
+
*
|
|
39
|
+
* // Run code under test
|
|
40
|
+
* await myFunction()
|
|
41
|
+
*
|
|
42
|
+
* // Assert on collected spans
|
|
43
|
+
* const spans = exporter.getFinishedSpans()
|
|
44
|
+
* expect(spans).toHaveLength(1)
|
|
45
|
+
* ```
|
|
46
|
+
*
|
|
47
|
+
* Note: For high-level testing utilities with assertion helpers, see `autotel/testing`.
|
|
48
|
+
* For custom span processing, see `autotel/processors`.
|
|
49
|
+
*
|
|
50
|
+
* @module autotel/exporters
|
|
51
|
+
* @see {@link https://opentelemetry.io/docs/specs/otel/trace/sdk/#span-exporter | OTel Span Exporter Spec}
|
|
52
|
+
*/
|
|
53
|
+
|
|
54
|
+
export {
|
|
55
|
+
/**
|
|
56
|
+
* Console exporter - prints spans to stdout.
|
|
57
|
+
*
|
|
58
|
+
* Perfect for:
|
|
59
|
+
* - Local development (see what's being traced)
|
|
60
|
+
* - Example applications (demonstrate tracing)
|
|
61
|
+
* - Quick debugging (no backend setup required)
|
|
62
|
+
* - Progressive development (verify spans are created)
|
|
63
|
+
*
|
|
64
|
+
* Note: Not recommended for production use.
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* ```typescript
|
|
68
|
+
* import { ConsoleSpanExporter } from 'autotel/exporters'
|
|
69
|
+
*
|
|
70
|
+
* const exporter = new ConsoleSpanExporter()
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
73
|
+
ConsoleSpanExporter,
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* In-memory exporter - stores spans in memory.
|
|
77
|
+
*
|
|
78
|
+
* Perfect for:
|
|
79
|
+
* - Unit testing (capture and assert on spans)
|
|
80
|
+
* - Integration testing (verify trace structure)
|
|
81
|
+
* - Development (inspect spans programmatically)
|
|
82
|
+
*
|
|
83
|
+
* Note: Memory will grow unbounded - clear spans periodically or use for testing only.
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* ```typescript
|
|
87
|
+
* import { InMemorySpanExporter } from 'autotel/exporters'
|
|
88
|
+
*
|
|
89
|
+
* const exporter = new InMemorySpanExporter()
|
|
90
|
+
* // ... run code
|
|
91
|
+
* const spans = exporter.getFinishedSpans()
|
|
92
|
+
* exporter.reset() // Clear memory
|
|
93
|
+
* ```
|
|
94
|
+
*/
|
|
95
|
+
InMemorySpanExporter,
|
|
96
|
+
} from '@opentelemetry/sdk-trace-base';
|