autotel 4.1.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.
Files changed (154) hide show
  1. package/package.json +1 -2
  2. package/src/attribute-redacting-processor.test.ts +0 -763
  3. package/src/attribute-redacting-processor.ts +0 -621
  4. package/src/attributes/attachers.ts +0 -161
  5. package/src/attributes/builders.ts +0 -529
  6. package/src/attributes/domains.ts +0 -42
  7. package/src/attributes/index.ts +0 -81
  8. package/src/attributes/registry.ts +0 -323
  9. package/src/attributes/types.ts +0 -211
  10. package/src/attributes/utils.ts +0 -64
  11. package/src/attributes/validators.ts +0 -266
  12. package/src/attributes.test.ts +0 -292
  13. package/src/auto.ts +0 -67
  14. package/src/autotel-logger.test.ts +0 -548
  15. package/src/autotel-logger.ts +0 -364
  16. package/src/baggage-span-processor.test.ts +0 -202
  17. package/src/baggage-span-processor.ts +0 -100
  18. package/src/business-baggage.test.ts +0 -500
  19. package/src/business-baggage.ts +0 -669
  20. package/src/circuit-breaker.test.ts +0 -341
  21. package/src/circuit-breaker.ts +0 -184
  22. package/src/config.test.ts +0 -94
  23. package/src/config.ts +0 -172
  24. package/src/correlated-events.test.ts +0 -151
  25. package/src/correlated-events.ts +0 -47
  26. package/src/correlation-id.test.ts +0 -163
  27. package/src/correlation-id.ts +0 -206
  28. package/src/db.test.ts +0 -252
  29. package/src/db.ts +0 -447
  30. package/src/decorators.test.ts +0 -153
  31. package/src/decorators.ts +0 -188
  32. package/src/define-event.test.ts +0 -41
  33. package/src/define-event.ts +0 -58
  34. package/src/devtools.ts +0 -60
  35. package/src/drain-pipeline.test.ts +0 -68
  36. package/src/drain-pipeline.ts +0 -199
  37. package/src/drain-toolkit.test.ts +0 -113
  38. package/src/drain-toolkit.ts +0 -129
  39. package/src/enricher-toolkit.test.ts +0 -67
  40. package/src/enricher-toolkit.ts +0 -79
  41. package/src/enrichers.test.ts +0 -150
  42. package/src/enrichers.ts +0 -145
  43. package/src/env-config.test.ts +0 -323
  44. package/src/env-config.ts +0 -309
  45. package/src/error-catalog.test.ts +0 -133
  46. package/src/error-catalog.ts +0 -262
  47. package/src/event-queue.test.ts +0 -864
  48. package/src/event-queue.ts +0 -699
  49. package/src/event-subscriber.ts +0 -262
  50. package/src/event-testing.ts +0 -197
  51. package/src/event.test.ts +0 -1104
  52. package/src/event.ts +0 -988
  53. package/src/events-config.ts +0 -235
  54. package/src/exporters.ts +0 -165
  55. package/src/filtering-span-processor.test.ts +0 -281
  56. package/src/filtering-span-processor.ts +0 -111
  57. package/src/flatten-attributes.test.ts +0 -76
  58. package/src/flatten-attributes.ts +0 -80
  59. package/src/functional.strict-types.typecheck.ts +0 -53
  60. package/src/functional.test.ts +0 -1464
  61. package/src/functional.ts +0 -2539
  62. package/src/functional.types.test.ts +0 -135
  63. package/src/hook.mjs +0 -15
  64. package/src/http.test.ts +0 -485
  65. package/src/http.ts +0 -424
  66. package/src/index.ts +0 -433
  67. package/src/init-auto-redactor.test.ts +0 -53
  68. package/src/init-redactor.test.ts +0 -8
  69. package/src/init.customization.test.ts +0 -665
  70. package/src/init.integrations.test.ts +0 -399
  71. package/src/init.openllmetry.test.ts +0 -194
  72. package/src/init.protocol.test.ts +0 -215
  73. package/src/init.ts +0 -2439
  74. package/src/instrumentation.test.ts +0 -108
  75. package/src/instrumentation.ts +0 -319
  76. package/src/logger.test.ts +0 -125
  77. package/src/logger.ts +0 -341
  78. package/src/messaging-adapters.test.ts +0 -595
  79. package/src/messaging-adapters.ts +0 -583
  80. package/src/messaging-testing.test.ts +0 -573
  81. package/src/messaging-testing.ts +0 -935
  82. package/src/messaging.test.ts +0 -1646
  83. package/src/messaging.ts +0 -2245
  84. package/src/metric-helpers.ts +0 -47
  85. package/src/metric-testing.ts +0 -197
  86. package/src/metric.ts +0 -446
  87. package/src/metrics.test.ts +0 -241
  88. package/src/node-require.ts +0 -123
  89. package/src/operation-context.ts +0 -93
  90. package/src/parse-error.test.ts +0 -73
  91. package/src/parse-error.ts +0 -112
  92. package/src/posthog-logs.test.ts +0 -115
  93. package/src/posthog-logs.ts +0 -77
  94. package/src/pretty-console-exporter.test.ts +0 -545
  95. package/src/pretty-console-exporter.ts +0 -413
  96. package/src/pretty-log-formatter.test.ts +0 -123
  97. package/src/pretty-log-formatter.ts +0 -210
  98. package/src/processors/canonical-log-line-processor.test.ts +0 -523
  99. package/src/processors/canonical-log-line-processor.ts +0 -396
  100. package/src/processors.ts +0 -152
  101. package/src/rate-limiter.test.ts +0 -199
  102. package/src/rate-limiter.ts +0 -98
  103. package/src/redact-values.test.ts +0 -90
  104. package/src/redact-values.ts +0 -34
  105. package/src/register.ts +0 -37
  106. package/src/request-logger.test.ts +0 -545
  107. package/src/request-logger.ts +0 -342
  108. package/src/sampling.test.ts +0 -1060
  109. package/src/sampling.ts +0 -737
  110. package/src/security-schema.test.ts +0 -45
  111. package/src/security-schema.ts +0 -107
  112. package/src/semantic-conventions.ts +0 -15
  113. package/src/semantic-helpers.test.ts +0 -226
  114. package/src/semantic-helpers.ts +0 -438
  115. package/src/shutdown.test.ts +0 -364
  116. package/src/shutdown.ts +0 -246
  117. package/src/span-name-normalizer.test.ts +0 -377
  118. package/src/span-name-normalizer.ts +0 -213
  119. package/src/stable-hash.ts +0 -27
  120. package/src/structured-error.test.ts +0 -191
  121. package/src/structured-error.ts +0 -157
  122. package/src/stub.integration.test.ts +0 -361
  123. package/src/tail-sampling-processor.test.ts +0 -230
  124. package/src/tail-sampling-processor.ts +0 -55
  125. package/src/test-span-collector.test.ts +0 -234
  126. package/src/test-span-collector.ts +0 -150
  127. package/src/testing.ts +0 -705
  128. package/src/trace-context.test.ts +0 -73
  129. package/src/trace-context.ts +0 -567
  130. package/src/trace-helpers.new.test.ts +0 -278
  131. package/src/trace-helpers.test.ts +0 -290
  132. package/src/trace-helpers.ts +0 -710
  133. package/src/trace-hybrid.test.ts +0 -42
  134. package/src/trace-hybrid.ts +0 -37
  135. package/src/tracer-provider.test.ts +0 -183
  136. package/src/tracer-provider.ts +0 -266
  137. package/src/track.test.ts +0 -154
  138. package/src/track.ts +0 -216
  139. package/src/validate.test.ts +0 -287
  140. package/src/validate.ts +0 -307
  141. package/src/validation-attributes.ts +0 -43
  142. package/src/validation.test.ts +0 -330
  143. package/src/validation.ts +0 -246
  144. package/src/variable-name-inference.test.ts +0 -178
  145. package/src/variable-name-inference.ts +0 -242
  146. package/src/webhook.test.ts +0 -649
  147. package/src/webhook.ts +0 -637
  148. package/src/workflow-distributed.test.ts +0 -786
  149. package/src/workflow-distributed.ts +0 -916
  150. package/src/workflow.async-safety.integration.test.ts +0 -345
  151. package/src/workflow.test.ts +0 -647
  152. package/src/workflow.ts +0 -810
  153. package/src/yaml-config.test.ts +0 -373
  154. package/src/yaml-config.ts +0 -351
package/src/sampling.ts DELETED
@@ -1,737 +0,0 @@
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 { Link, Attributes } from '@opentelemetry/api';
26
- import { TraceFlags } from '@opentelemetry/api';
27
- import { type Logger } from './logger';
28
-
29
- /**
30
- * Tail sampling attribute keys (autotel-internal, not OTel semconv)
31
- */
32
- export const AUTOTEL_SAMPLING_TAIL_KEEP = 'autotel.sampling.tail.keep';
33
- export const AUTOTEL_SAMPLING_TAIL_EVALUATED =
34
- 'autotel.sampling.tail.evaluated';
35
-
36
- /**
37
- * Sampler interface - return true to trace, false to skip
38
- */
39
- export interface Sampler {
40
- /**
41
- * Decide whether to trace this operation
42
- *
43
- * @param context - Sampling context
44
- * @returns true to trace, false to skip
45
- */
46
- shouldSample(context: SamplingContext): boolean;
47
-
48
- /**
49
- * Whether this sampler needs tail sampling (post-execution decision)
50
- * If true, spans are always created and shouldKeepTrace() is called after execution
51
- *
52
- * @returns true if this sampler needs to evaluate after operation completes
53
- */
54
- needsTailSampling?(): boolean;
55
-
56
- /**
57
- * Re-evaluate sampling decision after operation completes (tail sampling)
58
- * Only called if needsTailSampling() returns true
59
- *
60
- * @param context - Sampling context
61
- * @param result - Operation result
62
- * @returns true if this trace should be kept, false to drop it
63
- */
64
- shouldKeepTrace?(context: SamplingContext, result: OperationResult): boolean;
65
- }
66
-
67
- /**
68
- * Context information for sampling decisions
69
- */
70
- export interface SamplingContext {
71
- /** Operation name */
72
- operationName: string;
73
- /** Method arguments (for extracting user IDs, etc.) */
74
- args: unknown[];
75
- /** Optional metadata (e.g., feature flags, request headers) */
76
- metadata?: Record<string, unknown>;
77
- /** Optional span links for links-based sampling */
78
- links?: Link[];
79
- }
80
-
81
- /**
82
- * Result of a trace operation (for post-execution sampling)
83
- */
84
- export interface OperationResult {
85
- /** Whether the operation succeeded */
86
- success: boolean;
87
- /** Duration in milliseconds */
88
- duration: number;
89
- /** Error if operation failed */
90
- error?: Error;
91
- }
92
-
93
- /**
94
- * Simple random sampler
95
- *
96
- * @example
97
- * ```typescript
98
- * new RandomSampler(0.1) // Sample 10% of requests
99
- * ```
100
- */
101
- export class RandomSampler implements Sampler {
102
- constructor(private readonly sampleRate: number) {
103
- if (sampleRate < 0 || sampleRate > 1) {
104
- throw new Error('Sample rate must be between 0 and 1');
105
- }
106
- }
107
-
108
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
109
- shouldSample(_context: SamplingContext): boolean {
110
- return Math.random() < this.sampleRate;
111
- }
112
- }
113
-
114
- /**
115
- * Always sample (100% tracing)
116
- */
117
- export class AlwaysSampler implements Sampler {
118
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
119
- shouldSample(_context: SamplingContext): boolean {
120
- return true;
121
- }
122
- }
123
-
124
- /**
125
- * Never sample (0% tracing)
126
- */
127
- export class NeverSampler implements Sampler {
128
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
129
- shouldSample(_context: SamplingContext): boolean {
130
- return false;
131
- }
132
- }
133
-
134
- /**
135
- * Adaptive sampler that always traces errors and slow requests
136
- *
137
- * This is the recommended sampler for production use.
138
- * It ensures you never miss critical issues while keeping costs down.
139
- *
140
- * Strategy:
141
- * - Always trace errors (critical for debugging)
142
- * - Always trace slow requests (performance issues)
143
- * - Use baseline sample rate for successful fast requests
144
- *
145
- * **IMPORTANT - Tail Sampling Requirement:**
146
- * This sampler uses tail sampling (makes decisions AFTER execution).
147
- * You MUST use TailSamplingSpanProcessor for it to work correctly:
148
- *
149
- * - If using initInstrumentation(): TailSamplingSpanProcessor is auto-configured
150
- * - If using custom TracerProvider: You MUST manually register TailSamplingSpanProcessor
151
- *
152
- * Without TailSamplingSpanProcessor, ALL spans are exported (defeating the cost savings).
153
- *
154
- * @see TailSamplingSpanProcessor
155
- * @see README.md "Tail Sampling with Custom Providers" section
156
- *
157
- * @example
158
- * ```typescript
159
- * new AdaptiveSampler({
160
- * baselineSampleRate: 0.1, // 10% of normal requests
161
- * slowThresholdMs: 1000, // Requests > 1s are "slow"
162
- * alwaysSampleErrors: true, // Always trace errors
163
- * alwaysSampleSlow: true // Always trace slow requests
164
- * })
165
- * ```
166
- */
167
- export class AdaptiveSampler implements Sampler {
168
- private baselineSampleRate: number;
169
- private slowThresholdMs: number;
170
- private alwaysSampleErrors: boolean;
171
- private alwaysSampleSlow: boolean;
172
- private linksBased: boolean;
173
- private linksRate: number;
174
- private logger?: Logger;
175
-
176
- // Track whether we should sample this request
177
- private readonly samplingDecisions = new WeakMap<unknown[], boolean>();
178
- // Track operation results to enable post-execution decision
179
- private readonly operationResults = new WeakMap<unknown[], OperationResult>();
180
-
181
- constructor(
182
- options: {
183
- baselineSampleRate?: number;
184
- slowThresholdMs?: number;
185
- alwaysSampleErrors?: boolean;
186
- alwaysSampleSlow?: boolean;
187
- /** Enable links-based sampling for event-driven architectures */
188
- linksBased?: boolean;
189
- /** Sampling rate for spans linked to sampled spans (0.0-1.0) */
190
- linksRate?: number;
191
- logger?: Logger;
192
- } = {},
193
- ) {
194
- this.baselineSampleRate = options.baselineSampleRate ?? 0.1;
195
- this.slowThresholdMs = options.slowThresholdMs ?? 1000;
196
- this.alwaysSampleErrors = options.alwaysSampleErrors ?? true;
197
- this.alwaysSampleSlow = options.alwaysSampleSlow ?? true;
198
- this.linksBased = options.linksBased ?? false;
199
- this.linksRate = options.linksRate ?? 1;
200
- this.logger = options.logger;
201
-
202
- if (this.baselineSampleRate < 0 || this.baselineSampleRate > 1) {
203
- throw new Error('Baseline sample rate must be between 0 and 1');
204
- }
205
- if (this.linksRate < 0 || this.linksRate > 1) {
206
- throw new Error('Links rate must be between 0 and 1');
207
- }
208
- }
209
-
210
- needsTailSampling(): boolean {
211
- // AdaptiveSampler ALWAYS needs tail sampling to implement error/slow capture
212
- return true;
213
- }
214
-
215
- shouldSample(context: SamplingContext): boolean {
216
- // For tail sampling, we optimistically create spans for all requests
217
- // The real decision happens in shouldKeepTrace() after execution
218
- // We still store the baseline decision for shouldKeepTrace() to use
219
- const baselineDecision = Math.random() < this.baselineSampleRate;
220
- this.samplingDecisions.set(context.args, baselineDecision);
221
-
222
- // Always return true to create the span (tail sampling will decide if we keep it)
223
- return true;
224
- }
225
-
226
- /**
227
- * Check if any links point to sampled spans.
228
- *
229
- * A span is considered linked to a sampled span if any of its links
230
- * have trace_flags with the sampled bit set (0x01).
231
- *
232
- * @param links - Array of span links to check
233
- * @returns true if any linked span is sampled, false otherwise
234
- */
235
- hasSampledLink(links: Link[]): boolean {
236
- if (!links || links.length === 0) {
237
- return false;
238
- }
239
- return links.some(
240
- (link) =>
241
- link.context && (link.context.traceFlags & TraceFlags.SAMPLED) !== 0,
242
- );
243
- }
244
-
245
- /**
246
- * Re-evaluate sampling decision after operation completes
247
- *
248
- * This allows us to always capture errors and slow requests,
249
- * even if they weren't initially sampled.
250
- *
251
- * @param context - Sampling context
252
- * @param result - Operation result
253
- * @returns true if this operation should be kept (not discarded)
254
- */
255
- shouldKeepTrace(context: SamplingContext, result: OperationResult): boolean {
256
- const baselineDecision = this.samplingDecisions.get(context.args) ?? false;
257
-
258
- // Always keep errors
259
- if (this.alwaysSampleErrors && !result.success) {
260
- if (!baselineDecision) {
261
- this.logger?.debug(
262
- {
263
- operation: context.operationName,
264
- error: result.error?.message,
265
- },
266
- 'Adaptive sampling: Keeping error trace',
267
- );
268
- }
269
- return true;
270
- }
271
-
272
- // Always keep slow requests
273
- if (this.alwaysSampleSlow && result.duration >= this.slowThresholdMs) {
274
- if (!baselineDecision) {
275
- this.logger?.debug(
276
- {
277
- operation: context.operationName,
278
- duration: result.duration,
279
- },
280
- 'Adaptive sampling: Keeping slow trace',
281
- );
282
- }
283
- return true;
284
- }
285
-
286
- // Check for sampled links (links-based sampling for event-driven systems)
287
- if (
288
- this.linksBased &&
289
- context.links &&
290
- this.hasSampledLink(context.links)
291
- ) {
292
- // Use linksRate to decide whether to keep the linked span
293
- const keepLinked = Math.random() < this.linksRate;
294
- if (keepLinked && !baselineDecision) {
295
- this.logger?.debug(
296
- {
297
- operation: context.operationName,
298
- linkCount: context.links.length,
299
- },
300
- 'Adaptive sampling: Keeping trace due to sampled link',
301
- );
302
- }
303
- return keepLinked;
304
- }
305
-
306
- // Otherwise, use baseline decision
307
- return baselineDecision;
308
- }
309
- }
310
-
311
- /**
312
- * User-based sampler for consistent tracing
313
- *
314
- * Always samples requests from specific user IDs.
315
- * Useful for debugging specific user issues or monitoring VIP users.
316
- *
317
- * @example
318
- * ```typescript
319
- * new UserIdSampler({
320
- * baselineSampleRate: 0.01, // 1% of normal users
321
- * alwaysSampleUsers: ['vip_123'], // Always trace VIP users
322
- * extractUserId: (args) => args[0]?.userId // Extract user ID from first arg
323
- * })
324
- * ```
325
- */
326
- export class UserIdSampler implements Sampler {
327
- private baselineSampleRate: number;
328
- private alwaysSampleUsers: Set<string>;
329
- private extractUserId: (args: unknown[]) => string | undefined;
330
- private logger?: Logger;
331
-
332
- constructor(options: {
333
- baselineSampleRate?: number;
334
- alwaysSampleUsers?: string[];
335
- extractUserId: (args: unknown[]) => string | undefined;
336
- logger?: Logger;
337
- }) {
338
- this.baselineSampleRate = options.baselineSampleRate ?? 0.1;
339
- this.alwaysSampleUsers = new Set(options.alwaysSampleUsers || []);
340
- this.extractUserId = options.extractUserId;
341
- this.logger = options.logger;
342
- }
343
-
344
- shouldSample(context: SamplingContext): boolean {
345
- const userId = this.extractUserId(context.args);
346
-
347
- // Always sample specific users
348
- if (userId && this.alwaysSampleUsers.has(userId)) {
349
- this.logger?.debug(
350
- {
351
- operation: context.operationName,
352
- userId,
353
- },
354
- 'Sampling user request',
355
- );
356
- return true;
357
- }
358
-
359
- // For consistent per-user sampling, hash the user ID
360
- if (userId) {
361
- const hash = this.hashString(userId);
362
- return hash < this.baselineSampleRate;
363
- }
364
-
365
- // Fallback to random sampling if no user ID
366
- return Math.random() < this.baselineSampleRate;
367
- }
368
-
369
- /**
370
- * Add user IDs to always-sample list
371
- */
372
- addAlwaysSampleUsers(...userIds: string[]): void {
373
- for (const userId of userIds) {
374
- this.alwaysSampleUsers.add(userId);
375
- }
376
- }
377
-
378
- /**
379
- * Remove user IDs from always-sample list
380
- */
381
- removeAlwaysSampleUsers(...userIds: string[]): void {
382
- for (const userId of userIds) {
383
- this.alwaysSampleUsers.delete(userId);
384
- }
385
- }
386
-
387
- /**
388
- * Simple hash function for consistent user sampling
389
- */
390
- private hashString(str: string): number {
391
- let hash = 0;
392
- for (let i = 0; i < str.length; i++) {
393
- const char = str.codePointAt(i) ?? 0;
394
- hash = (hash << 5) - hash + char;
395
- hash = hash & hash; // Convert to 32-bit integer
396
- }
397
- return Math.abs(hash) / 2_147_483_647; // Normalize to 0-1
398
- }
399
- }
400
-
401
- /**
402
- * Composite sampler that combines multiple samplers
403
- *
404
- * Samples if ANY of the child samplers returns true.
405
- *
406
- * @example
407
- * ```typescript
408
- * new CompositeSampler([
409
- * new UserIdSampler({ extractUserId: (args) => args[0]?.userId }),
410
- * new AdaptiveSampler({ baselineSampleRate: 0.1 })
411
- * ])
412
- * ```
413
- */
414
- export class CompositeSampler implements Sampler {
415
- constructor(private readonly samplers: Sampler[]) {
416
- if (samplers.length === 0) {
417
- throw new Error('CompositeSampler requires at least one child sampler');
418
- }
419
- }
420
-
421
- shouldSample(context: SamplingContext): boolean {
422
- return this.samplers.some((sampler) => sampler.shouldSample(context));
423
- }
424
- }
425
-
426
- /**
427
- * Feature flag sampler
428
- *
429
- * Always samples requests with specific feature flags enabled.
430
- * Perfect for correlating A/B test experiments with metrics.
431
- *
432
- * @example
433
- * ```typescript
434
- * new FeatureFlagSampler({
435
- * baselineSampleRate: 0.01,
436
- * alwaysSampleFlags: ['new_checkout', 'experimental_ui'],
437
- * extractFlags: (args, metadata) => metadata?.featureFlags
438
- * })
439
- * ```
440
- */
441
- export class FeatureFlagSampler implements Sampler {
442
- private baselineSampleRate: number;
443
- private alwaysSampleFlags: Set<string>;
444
- private extractFlags: (
445
- args: unknown[],
446
- metadata?: Record<string, unknown>,
447
- ) => string[] | undefined;
448
- private logger?: Logger;
449
-
450
- constructor(options: {
451
- baselineSampleRate?: number;
452
- alwaysSampleFlags?: string[];
453
- extractFlags: (
454
- args: unknown[],
455
- metadata?: Record<string, unknown>,
456
- ) => string[] | undefined;
457
- logger?: Logger;
458
- }) {
459
- this.baselineSampleRate = options.baselineSampleRate ?? 0.1;
460
- this.alwaysSampleFlags = new Set(options.alwaysSampleFlags || []);
461
- this.extractFlags = options.extractFlags;
462
- this.logger = options.logger;
463
- }
464
-
465
- shouldSample(context: SamplingContext): boolean {
466
- const flags = this.extractFlags(context.args, context.metadata);
467
-
468
- // Always sample if any monitored flag is enabled
469
- if (flags && flags.some((flag) => this.alwaysSampleFlags.has(flag))) {
470
- this.logger?.debug(
471
- {
472
- operation: context.operationName,
473
- flags,
474
- },
475
- 'Sampling feature flag request',
476
- );
477
- return true;
478
- }
479
-
480
- // Fallback to random sampling
481
- return Math.random() < this.baselineSampleRate;
482
- }
483
-
484
- /**
485
- * Add feature flags to always-sample list
486
- */
487
- addAlwaysSampleFlags(...flags: string[]): void {
488
- for (const flag of flags) {
489
- this.alwaysSampleFlags.add(flag);
490
- }
491
- }
492
-
493
- /**
494
- * Remove feature flags from always-sample list
495
- */
496
- removeAlwaysSampleFlags(...flags: string[]): void {
497
- for (const flag of flags) {
498
- this.alwaysSampleFlags.delete(flag);
499
- }
500
- }
501
- }
502
-
503
- // ============================================================================
504
- // Sampling Presets
505
- // ============================================================================
506
-
507
- /**
508
- * Named sampling presets for common environments.
509
- * Use with `init({ sampling: 'production' })` or directly via factories.
510
- */
511
- export type SamplingPreset =
512
- | 'development'
513
- | 'errors-only'
514
- | 'production'
515
- | 'off';
516
-
517
- /**
518
- * Sampling preset factories.
519
- *
520
- * For most users, the string shorthand on `init()` is simpler:
521
- * ```typescript
522
- * init({ service: 'my-app', sampling: 'production' })
523
- * ```
524
- *
525
- * Use factories when you need to customize:
526
- * ```typescript
527
- * init({ service: 'my-app', sampler: samplingPresets.production({ baselineSampleRate: 0.05 }) })
528
- * ```
529
- */
530
- export const samplingPresets = {
531
- /** Capture everything — best for local development and debugging */
532
- development: () => new AlwaysSampler(),
533
-
534
- /** Only bad outcomes — zero baseline, errors always kept */
535
- errorsOnly: () =>
536
- new AdaptiveSampler({
537
- baselineSampleRate: 0,
538
- alwaysSampleErrors: true,
539
- }),
540
-
541
- /**
542
- * Balanced production defaults — 10% baseline + errors + slow traces.
543
- * Pass overrides to tune (uses the same option names as AdaptiveSampler).
544
- */
545
- production: (overrides?: {
546
- baselineSampleRate?: number;
547
- slowThresholdMs?: number;
548
- alwaysSampleErrors?: boolean;
549
- alwaysSampleSlow?: boolean;
550
- }) =>
551
- new AdaptiveSampler({
552
- baselineSampleRate: 0.1,
553
- alwaysSampleErrors: true,
554
- alwaysSampleSlow: true,
555
- slowThresholdMs: 1000,
556
- ...overrides,
557
- }),
558
-
559
- /** Disable sampling entirely */
560
- off: () => new NeverSampler(),
561
- };
562
-
563
- /**
564
- * Resolve a preset string to a Sampler instance.
565
- * Used internally by `init()` when `sampling` string is provided.
566
- *
567
- * @throws Error if preset is not recognized
568
- */
569
- export function resolveSamplingPreset(preset: SamplingPreset): Sampler {
570
- switch (preset) {
571
- case 'development':
572
- return samplingPresets.development();
573
- case 'errors-only':
574
- return samplingPresets.errorsOnly();
575
- case 'production':
576
- return samplingPresets.production();
577
- case 'off':
578
- return samplingPresets.off();
579
- default:
580
- throw new Error(
581
- `Unknown sampling preset: "${preset}". Valid presets: development, errors-only, production, off`,
582
- );
583
- }
584
- }
585
-
586
- // ============================================================================
587
- // Link Helper Functions
588
- // ============================================================================
589
-
590
- /**
591
- * Create a Link from W3C trace context headers (e.g., from a message queue).
592
- *
593
- * This is useful for message consumers that need to link to the producer span.
594
- * The headers should contain at least a `traceparent` header in W3C format.
595
- *
596
- * @param headers - Dictionary containing traceparent/tracestate headers
597
- * @param attributes - Optional attributes for the link
598
- * @returns Link object if context could be extracted, null otherwise
599
- *
600
- * @example
601
- * ```typescript
602
- * // In a Kafka consumer
603
- * const headers = { traceparent: '00-abc123...-def456...-01' };
604
- * const link = createLinkFromHeaders(headers);
605
- * if (link) {
606
- * // Use with tracer.startActiveSpan options or ctx.addLink()
607
- * tracer.startActiveSpan('process.message', { links: [link] }, span => { ... });
608
- * }
609
- * ```
610
- */
611
- export function createLinkFromHeaders(
612
- headers: Record<string, string>,
613
- attributes?: Attributes,
614
- ): Link | null {
615
- // Parse W3C traceparent header directly for reliability
616
- // Format: version-traceId-spanId-traceFlags (e.g., 00-abc123...-def456...-01)
617
- const traceparent = headers.traceparent || headers['traceparent'];
618
- if (!traceparent) {
619
- return null;
620
- }
621
-
622
- const spanContext = parseTraceparent(traceparent);
623
- if (!spanContext || !isValidSpanContext(spanContext)) {
624
- return null;
625
- }
626
-
627
- return {
628
- context: spanContext,
629
- attributes: attributes ?? {},
630
- };
631
- }
632
-
633
- /**
634
- * Extract Links from a batch of messages for fan-in scenarios.
635
- *
636
- * Useful for batch processing where multiple producer spans should be linked.
637
- * This enables tracing causality in event-driven architectures where a single
638
- * consumer processes messages from multiple producers.
639
- *
640
- * @param messages - List of message objects
641
- * @param headersKey - Key in each message containing trace headers (default: 'headers')
642
- * @returns List of Link objects for all valid trace contexts
643
- *
644
- * @example
645
- * ```typescript
646
- * // Processing a batch of SQS/Kafka messages
647
- * const messages = [
648
- * { body: '...', headers: { traceparent: '...' } },
649
- * { body: '...', headers: { traceparent: '...' } },
650
- * ];
651
- * const links = extractLinksFromBatch(messages);
652
- *
653
- * tracer.startActiveSpan('process.batch', { links }, span => {
654
- * for (const msg of messages) {
655
- * processMessage(msg);
656
- * }
657
- * });
658
- * ```
659
- */
660
- export function extractLinksFromBatch(
661
- messages: Array<{ [key: string]: unknown }>,
662
- headersKey: string = 'headers',
663
- ): Link[] {
664
- const links: Link[] = [];
665
-
666
- for (const msg of messages) {
667
- const msgHeaders = msg[headersKey];
668
- if (msgHeaders && typeof msgHeaders === 'object' && msgHeaders !== null) {
669
- const link = createLinkFromHeaders(msgHeaders as Record<string, string>, {
670
- 'messaging.batch.message_index': links.length,
671
- });
672
- if (link) {
673
- links.push(link);
674
- }
675
- }
676
- }
677
-
678
- return links;
679
- }
680
-
681
- /**
682
- * Parse W3C traceparent header into SpanContext
683
- * Format: version-traceId-spanId-traceFlags (e.g., 00-abc123...-def456...-01)
684
- *
685
- * @see https://www.w3.org/TR/trace-context/#traceparent-header
686
- */
687
- function parseTraceparent(
688
- traceparent: string,
689
- ): import('@opentelemetry/api').SpanContext | null {
690
- // W3C traceparent format: version-traceId-parentId-traceFlags
691
- // Example: 00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01
692
- const TRACEPARENT_REGEX =
693
- /^([0-9a-f]{2})-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})$/i;
694
-
695
- const match = traceparent.match(TRACEPARENT_REGEX);
696
- if (!match || match.length < 5) {
697
- return null;
698
- }
699
-
700
- const version = match[1];
701
- const traceId = match[2];
702
- const spanId = match[3];
703
- const flags = match[4];
704
-
705
- // Validate all parts are present (TypeScript narrowing)
706
- if (!version || !traceId || !spanId || !flags) {
707
- return null;
708
- }
709
-
710
- // Version 00 is currently the only version, but we should be forward compatible
711
- if (version === 'ff') {
712
- // Version ff is invalid according to spec
713
- return null;
714
- }
715
-
716
- return {
717
- traceId,
718
- spanId,
719
- traceFlags: Number.parseInt(flags, 16),
720
- isRemote: true,
721
- };
722
- }
723
-
724
- /**
725
- * Check if a SpanContext is valid (has non-zero trace and span IDs)
726
- */
727
- function isValidSpanContext(
728
- spanContext: import('@opentelemetry/api').SpanContext | null,
729
- ): spanContext is import('@opentelemetry/api').SpanContext {
730
- if (!spanContext) return false;
731
- // TraceId should not be all zeros (00000000000000000000000000000000)
732
- // SpanId should not be all zeros (0000000000000000)
733
- return (
734
- spanContext.traceId !== '00000000000000000000000000000000' &&
735
- spanContext.spanId !== '0000000000000000'
736
- );
737
- }