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/sampling.ts
ADDED
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sampling Strategies
|
|
3
|
+
*
|
|
4
|
+
* Provides intelligent sampling beyond simple random rates.
|
|
5
|
+
* Helps reduce telemetry costs while capturing critical data.
|
|
6
|
+
*
|
|
7
|
+
* Key strategies:
|
|
8
|
+
* - Always trace errors and slow requests (critical for debugging)
|
|
9
|
+
* - Sample by user ID for consistent request tracing
|
|
10
|
+
* - Adaptive sampling based on load
|
|
11
|
+
* - Sample by feature flags for A/B testing correlation
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* import { AlwaysOnErrorSampler, UserIdSampler } from './sampling'
|
|
16
|
+
*
|
|
17
|
+
* @Instrumented({
|
|
18
|
+
* serviceName: 'user',
|
|
19
|
+
* sampler: new AlwaysOnErrorSampler(0.1) // 10% baseline, 100% on errors
|
|
20
|
+
* })
|
|
21
|
+
* class UserService { }
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { type Logger } from './logger';
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Sampler interface - return true to trace, false to skip
|
|
29
|
+
*/
|
|
30
|
+
export interface Sampler {
|
|
31
|
+
/**
|
|
32
|
+
* Decide whether to trace this operation
|
|
33
|
+
*
|
|
34
|
+
* @param context - Sampling context
|
|
35
|
+
* @returns true to trace, false to skip
|
|
36
|
+
*/
|
|
37
|
+
shouldSample(context: SamplingContext): boolean;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Whether this sampler needs tail sampling (post-execution decision)
|
|
41
|
+
* If true, spans are always created and shouldKeepTrace() is called after execution
|
|
42
|
+
*
|
|
43
|
+
* @returns true if this sampler needs to evaluate after operation completes
|
|
44
|
+
*/
|
|
45
|
+
needsTailSampling?(): boolean;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Re-evaluate sampling decision after operation completes (tail sampling)
|
|
49
|
+
* Only called if needsTailSampling() returns true
|
|
50
|
+
*
|
|
51
|
+
* @param context - Sampling context
|
|
52
|
+
* @param result - Operation result
|
|
53
|
+
* @returns true if this trace should be kept, false to drop it
|
|
54
|
+
*/
|
|
55
|
+
shouldKeepTrace?(context: SamplingContext, result: OperationResult): boolean;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Context information for sampling decisions
|
|
60
|
+
*/
|
|
61
|
+
export interface SamplingContext {
|
|
62
|
+
/** Operation name */
|
|
63
|
+
operationName: string;
|
|
64
|
+
/** Method arguments (for extracting user IDs, etc.) */
|
|
65
|
+
args: unknown[];
|
|
66
|
+
/** Optional metadata (e.g., feature flags, request headers) */
|
|
67
|
+
metadata?: Record<string, unknown>;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Result of a trace operation (for post-execution sampling)
|
|
72
|
+
*/
|
|
73
|
+
export interface OperationResult {
|
|
74
|
+
/** Whether the operation succeeded */
|
|
75
|
+
success: boolean;
|
|
76
|
+
/** Duration in milliseconds */
|
|
77
|
+
duration: number;
|
|
78
|
+
/** Error if operation failed */
|
|
79
|
+
error?: Error;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Simple random sampler
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* ```typescript
|
|
87
|
+
* new RandomSampler(0.1) // Sample 10% of requests
|
|
88
|
+
* ```
|
|
89
|
+
*/
|
|
90
|
+
export class RandomSampler implements Sampler {
|
|
91
|
+
constructor(private readonly sampleRate: number) {
|
|
92
|
+
if (sampleRate < 0 || sampleRate > 1) {
|
|
93
|
+
throw new Error('Sample rate must be between 0 and 1');
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
98
|
+
shouldSample(_context: SamplingContext): boolean {
|
|
99
|
+
return Math.random() < this.sampleRate;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Always sample (100% tracing)
|
|
105
|
+
*/
|
|
106
|
+
export class AlwaysSampler implements Sampler {
|
|
107
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
108
|
+
shouldSample(_context: SamplingContext): boolean {
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Never sample (0% tracing)
|
|
115
|
+
*/
|
|
116
|
+
export class NeverSampler implements Sampler {
|
|
117
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
118
|
+
shouldSample(_context: SamplingContext): boolean {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Adaptive sampler that always traces errors and slow requests
|
|
125
|
+
*
|
|
126
|
+
* This is the recommended sampler for production use.
|
|
127
|
+
* It ensures you never miss critical issues while keeping costs down.
|
|
128
|
+
*
|
|
129
|
+
* Strategy:
|
|
130
|
+
* - Always trace errors (critical for debugging)
|
|
131
|
+
* - Always trace slow requests (performance issues)
|
|
132
|
+
* - Use baseline sample rate for successful fast requests
|
|
133
|
+
*
|
|
134
|
+
* **IMPORTANT - Tail Sampling Requirement:**
|
|
135
|
+
* This sampler uses tail sampling (makes decisions AFTER execution).
|
|
136
|
+
* You MUST use TailSamplingSpanProcessor for it to work correctly:
|
|
137
|
+
*
|
|
138
|
+
* - If using initInstrumentation(): TailSamplingSpanProcessor is auto-configured
|
|
139
|
+
* - If using custom TracerProvider: You MUST manually register TailSamplingSpanProcessor
|
|
140
|
+
*
|
|
141
|
+
* Without TailSamplingSpanProcessor, ALL spans are exported (defeating the cost savings).
|
|
142
|
+
*
|
|
143
|
+
* @see TailSamplingSpanProcessor
|
|
144
|
+
* @see README.md "Tail Sampling with Custom Providers" section
|
|
145
|
+
*
|
|
146
|
+
* @example
|
|
147
|
+
* ```typescript
|
|
148
|
+
* new AdaptiveSampler({
|
|
149
|
+
* baselineSampleRate: 0.1, // 10% of normal requests
|
|
150
|
+
* slowThresholdMs: 1000, // Requests > 1s are "slow"
|
|
151
|
+
* alwaysSampleErrors: true, // Always trace errors
|
|
152
|
+
* alwaysSampleSlow: true // Always trace slow requests
|
|
153
|
+
* })
|
|
154
|
+
* ```
|
|
155
|
+
*/
|
|
156
|
+
export class AdaptiveSampler implements Sampler {
|
|
157
|
+
private baselineSampleRate: number;
|
|
158
|
+
private slowThresholdMs: number;
|
|
159
|
+
private alwaysSampleErrors: boolean;
|
|
160
|
+
private alwaysSampleSlow: boolean;
|
|
161
|
+
private logger?: Logger;
|
|
162
|
+
|
|
163
|
+
// Track whether we should sample this request
|
|
164
|
+
private readonly samplingDecisions = new WeakMap<unknown[], boolean>();
|
|
165
|
+
// Track operation results to enable post-execution decision
|
|
166
|
+
private readonly operationResults = new WeakMap<unknown[], OperationResult>();
|
|
167
|
+
|
|
168
|
+
constructor(
|
|
169
|
+
options: {
|
|
170
|
+
baselineSampleRate?: number;
|
|
171
|
+
slowThresholdMs?: number;
|
|
172
|
+
alwaysSampleErrors?: boolean;
|
|
173
|
+
alwaysSampleSlow?: boolean;
|
|
174
|
+
logger?: Logger;
|
|
175
|
+
} = {},
|
|
176
|
+
) {
|
|
177
|
+
this.baselineSampleRate = options.baselineSampleRate ?? 0.1;
|
|
178
|
+
this.slowThresholdMs = options.slowThresholdMs ?? 1000;
|
|
179
|
+
this.alwaysSampleErrors = options.alwaysSampleErrors ?? true;
|
|
180
|
+
this.alwaysSampleSlow = options.alwaysSampleSlow ?? true;
|
|
181
|
+
this.logger = options.logger;
|
|
182
|
+
|
|
183
|
+
if (this.baselineSampleRate < 0 || this.baselineSampleRate > 1) {
|
|
184
|
+
throw new Error('Baseline sample rate must be between 0 and 1');
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
needsTailSampling(): boolean {
|
|
189
|
+
// AdaptiveSampler ALWAYS needs tail sampling to implement error/slow capture
|
|
190
|
+
return true;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
shouldSample(context: SamplingContext): boolean {
|
|
194
|
+
// For tail sampling, we optimistically create spans for all requests
|
|
195
|
+
// The real decision happens in shouldKeepTrace() after execution
|
|
196
|
+
// We still store the baseline decision for shouldKeepTrace() to use
|
|
197
|
+
const baselineDecision = Math.random() < this.baselineSampleRate;
|
|
198
|
+
this.samplingDecisions.set(context.args, baselineDecision);
|
|
199
|
+
|
|
200
|
+
// Always return true to create the span (tail sampling will decide if we keep it)
|
|
201
|
+
return true;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Re-evaluate sampling decision after operation completes
|
|
206
|
+
*
|
|
207
|
+
* This allows us to always capture errors and slow requests,
|
|
208
|
+
* even if they weren't initially sampled.
|
|
209
|
+
*
|
|
210
|
+
* @param context - Sampling context
|
|
211
|
+
* @param result - Operation result
|
|
212
|
+
* @returns true if this operation should be kept (not discarded)
|
|
213
|
+
*/
|
|
214
|
+
shouldKeepTrace(context: SamplingContext, result: OperationResult): boolean {
|
|
215
|
+
const baselineDecision = this.samplingDecisions.get(context.args) ?? false;
|
|
216
|
+
|
|
217
|
+
// Always keep errors
|
|
218
|
+
if (this.alwaysSampleErrors && !result.success) {
|
|
219
|
+
if (!baselineDecision) {
|
|
220
|
+
this.logger?.debug('Adaptive sampling: Keeping error trace', {
|
|
221
|
+
operation: context.operationName,
|
|
222
|
+
error: result.error?.message,
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
return true;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Always keep slow requests
|
|
229
|
+
if (this.alwaysSampleSlow && result.duration >= this.slowThresholdMs) {
|
|
230
|
+
if (!baselineDecision) {
|
|
231
|
+
this.logger?.debug('Adaptive sampling: Keeping slow trace', {
|
|
232
|
+
operation: context.operationName,
|
|
233
|
+
duration: result.duration,
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
return true;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Otherwise, use baseline decision
|
|
240
|
+
return baselineDecision;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* User-based sampler for consistent tracing
|
|
246
|
+
*
|
|
247
|
+
* Always samples requests from specific user IDs.
|
|
248
|
+
* Useful for debugging specific user issues or monitoring VIP users.
|
|
249
|
+
*
|
|
250
|
+
* @example
|
|
251
|
+
* ```typescript
|
|
252
|
+
* new UserIdSampler({
|
|
253
|
+
* baselineSampleRate: 0.01, // 1% of normal users
|
|
254
|
+
* alwaysSampleUsers: ['vip_123'], // Always trace VIP users
|
|
255
|
+
* extractUserId: (args) => args[0]?.userId // Extract user ID from first arg
|
|
256
|
+
* })
|
|
257
|
+
* ```
|
|
258
|
+
*/
|
|
259
|
+
export class UserIdSampler implements Sampler {
|
|
260
|
+
private baselineSampleRate: number;
|
|
261
|
+
private alwaysSampleUsers: Set<string>;
|
|
262
|
+
private extractUserId: (args: unknown[]) => string | undefined;
|
|
263
|
+
private logger?: Logger;
|
|
264
|
+
|
|
265
|
+
constructor(options: {
|
|
266
|
+
baselineSampleRate?: number;
|
|
267
|
+
alwaysSampleUsers?: string[];
|
|
268
|
+
extractUserId: (args: unknown[]) => string | undefined;
|
|
269
|
+
logger?: Logger;
|
|
270
|
+
}) {
|
|
271
|
+
this.baselineSampleRate = options.baselineSampleRate ?? 0.1;
|
|
272
|
+
this.alwaysSampleUsers = new Set(options.alwaysSampleUsers || []);
|
|
273
|
+
this.extractUserId = options.extractUserId;
|
|
274
|
+
this.logger = options.logger;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
shouldSample(context: SamplingContext): boolean {
|
|
278
|
+
const userId = this.extractUserId(context.args);
|
|
279
|
+
|
|
280
|
+
// Always sample specific users
|
|
281
|
+
if (userId && this.alwaysSampleUsers.has(userId)) {
|
|
282
|
+
this.logger?.debug('Sampling user request', {
|
|
283
|
+
operation: context.operationName,
|
|
284
|
+
userId,
|
|
285
|
+
});
|
|
286
|
+
return true;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// For consistent per-user sampling, hash the user ID
|
|
290
|
+
if (userId) {
|
|
291
|
+
const hash = this.hashString(userId);
|
|
292
|
+
return hash < this.baselineSampleRate;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Fallback to random sampling if no user ID
|
|
296
|
+
return Math.random() < this.baselineSampleRate;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Add user IDs to always-sample list
|
|
301
|
+
*/
|
|
302
|
+
addAlwaysSampleUsers(...userIds: string[]): void {
|
|
303
|
+
for (const userId of userIds) {
|
|
304
|
+
this.alwaysSampleUsers.add(userId);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Remove user IDs from always-sample list
|
|
310
|
+
*/
|
|
311
|
+
removeAlwaysSampleUsers(...userIds: string[]): void {
|
|
312
|
+
for (const userId of userIds) {
|
|
313
|
+
this.alwaysSampleUsers.delete(userId);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Simple hash function for consistent user sampling
|
|
319
|
+
*/
|
|
320
|
+
private hashString(str: string): number {
|
|
321
|
+
let hash = 0;
|
|
322
|
+
for (let i = 0; i < str.length; i++) {
|
|
323
|
+
const char = str.codePointAt(i) ?? 0;
|
|
324
|
+
hash = (hash << 5) - hash + char;
|
|
325
|
+
hash = hash & hash; // Convert to 32-bit integer
|
|
326
|
+
}
|
|
327
|
+
return Math.abs(hash) / 2_147_483_647; // Normalize to 0-1
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Composite sampler that combines multiple samplers
|
|
333
|
+
*
|
|
334
|
+
* Samples if ANY of the child samplers returns true.
|
|
335
|
+
*
|
|
336
|
+
* @example
|
|
337
|
+
* ```typescript
|
|
338
|
+
* new CompositeSampler([
|
|
339
|
+
* new UserIdSampler({ extractUserId: (args) => args[0]?.userId }),
|
|
340
|
+
* new AdaptiveSampler({ baselineSampleRate: 0.1 })
|
|
341
|
+
* ])
|
|
342
|
+
* ```
|
|
343
|
+
*/
|
|
344
|
+
export class CompositeSampler implements Sampler {
|
|
345
|
+
constructor(private readonly samplers: Sampler[]) {
|
|
346
|
+
if (samplers.length === 0) {
|
|
347
|
+
throw new Error('CompositeSampler requires at least one child sampler');
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
shouldSample(context: SamplingContext): boolean {
|
|
352
|
+
return this.samplers.some((sampler) => sampler.shouldSample(context));
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Feature flag sampler
|
|
358
|
+
*
|
|
359
|
+
* Always samples requests with specific feature flags enabled.
|
|
360
|
+
* Perfect for correlating A/B test experiments with metrics.
|
|
361
|
+
*
|
|
362
|
+
* @example
|
|
363
|
+
* ```typescript
|
|
364
|
+
* new FeatureFlagSampler({
|
|
365
|
+
* baselineSampleRate: 0.01,
|
|
366
|
+
* alwaysSampleFlags: ['new_checkout', 'experimental_ui'],
|
|
367
|
+
* extractFlags: (args, metadata) => metadata?.featureFlags
|
|
368
|
+
* })
|
|
369
|
+
* ```
|
|
370
|
+
*/
|
|
371
|
+
export class FeatureFlagSampler implements Sampler {
|
|
372
|
+
private baselineSampleRate: number;
|
|
373
|
+
private alwaysSampleFlags: Set<string>;
|
|
374
|
+
private extractFlags: (
|
|
375
|
+
args: unknown[],
|
|
376
|
+
metadata?: Record<string, unknown>,
|
|
377
|
+
) => string[] | undefined;
|
|
378
|
+
private logger?: Logger;
|
|
379
|
+
|
|
380
|
+
constructor(options: {
|
|
381
|
+
baselineSampleRate?: number;
|
|
382
|
+
alwaysSampleFlags?: string[];
|
|
383
|
+
extractFlags: (
|
|
384
|
+
args: unknown[],
|
|
385
|
+
metadata?: Record<string, unknown>,
|
|
386
|
+
) => string[] | undefined;
|
|
387
|
+
logger?: Logger;
|
|
388
|
+
}) {
|
|
389
|
+
this.baselineSampleRate = options.baselineSampleRate ?? 0.1;
|
|
390
|
+
this.alwaysSampleFlags = new Set(options.alwaysSampleFlags || []);
|
|
391
|
+
this.extractFlags = options.extractFlags;
|
|
392
|
+
this.logger = options.logger;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
shouldSample(context: SamplingContext): boolean {
|
|
396
|
+
const flags = this.extractFlags(context.args, context.metadata);
|
|
397
|
+
|
|
398
|
+
// Always sample if any monitored flag is enabled
|
|
399
|
+
if (flags && flags.some((flag) => this.alwaysSampleFlags.has(flag))) {
|
|
400
|
+
this.logger?.debug('Sampling feature flag request', {
|
|
401
|
+
operation: context.operationName,
|
|
402
|
+
flags,
|
|
403
|
+
});
|
|
404
|
+
return true;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Fallback to random sampling
|
|
408
|
+
return Math.random() < this.baselineSampleRate;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Add feature flags to always-sample list
|
|
413
|
+
*/
|
|
414
|
+
addAlwaysSampleFlags(...flags: string[]): void {
|
|
415
|
+
for (const flag of flags) {
|
|
416
|
+
this.alwaysSampleFlags.add(flag);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Remove feature flags from always-sample list
|
|
422
|
+
*/
|
|
423
|
+
removeAlwaysSampleFlags(...flags: string[]): void {
|
|
424
|
+
for (const flag of flags) {
|
|
425
|
+
this.alwaysSampleFlags.delete(flag);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|