autotel-edge 3.0.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 (49) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +333 -0
  3. package/dist/chunk-F32WSLNX.js +309 -0
  4. package/dist/chunk-F32WSLNX.js.map +1 -0
  5. package/dist/events.d.ts +86 -0
  6. package/dist/events.js +157 -0
  7. package/dist/events.js.map +1 -0
  8. package/dist/index.d.ts +326 -0
  9. package/dist/index.js +921 -0
  10. package/dist/index.js.map +1 -0
  11. package/dist/logger.d.ts +89 -0
  12. package/dist/logger.js +81 -0
  13. package/dist/logger.js.map +1 -0
  14. package/dist/sampling.d.ts +166 -0
  15. package/dist/sampling.js +108 -0
  16. package/dist/sampling.js.map +1 -0
  17. package/dist/testing.d.ts +2 -0
  18. package/dist/testing.js +3 -0
  19. package/dist/testing.js.map +1 -0
  20. package/dist/types-Dj85cPUj.d.ts +182 -0
  21. package/package.json +88 -0
  22. package/src/api/logger.test.ts +367 -0
  23. package/src/api/logger.ts +197 -0
  24. package/src/compose.ts +243 -0
  25. package/src/core/buffer.ts +16 -0
  26. package/src/core/config.test.ts +388 -0
  27. package/src/core/config.ts +167 -0
  28. package/src/core/context.ts +224 -0
  29. package/src/core/exporter.ts +99 -0
  30. package/src/core/provider.ts +45 -0
  31. package/src/core/span.ts +222 -0
  32. package/src/core/spanprocessor.test.ts +521 -0
  33. package/src/core/spanprocessor.ts +232 -0
  34. package/src/core/trace-context.ts +66 -0
  35. package/src/core/tracer.test.ts +123 -0
  36. package/src/core/tracer.ts +216 -0
  37. package/src/events/index.test.ts +242 -0
  38. package/src/events/index.ts +338 -0
  39. package/src/events.ts +6 -0
  40. package/src/functional.test.ts +702 -0
  41. package/src/functional.ts +846 -0
  42. package/src/index.ts +81 -0
  43. package/src/logger.ts +13 -0
  44. package/src/sampling/index.test.ts +297 -0
  45. package/src/sampling/index.ts +276 -0
  46. package/src/sampling.ts +6 -0
  47. package/src/testing/index.ts +9 -0
  48. package/src/testing.ts +6 -0
  49. package/src/types.ts +267 -0
package/src/index.ts ADDED
@@ -0,0 +1,81 @@
1
+ /**
2
+ * autotel-edge
3
+ *
4
+ * Vendor-agnostic OpenTelemetry for edge runtimes
5
+ * Foundation for Cloudflare Workers, Vercel Edge, Netlify Edge, Deno Deploy
6
+ *
7
+ * Bundle size: ~20KB minified (~8KB gzipped)
8
+ *
9
+ * @example Quick Start
10
+ * ```typescript
11
+ * import { trace, init } from 'autotel-edge'
12
+ *
13
+ * init({
14
+ * service: { name: 'my-edge-function' },
15
+ * exporter: { url: process.env.OTEL_ENDPOINT }
16
+ * })
17
+ *
18
+ * export const handler = trace(async (request: Request) => {
19
+ * return new Response('Hello World')
20
+ * })
21
+ * ```
22
+ */
23
+
24
+ // Core exports
25
+ export { SpanImpl } from './core/span';
26
+ export { WorkerTracer, withNextSpan } from './core/tracer';
27
+ export { OTLPExporter } from './core/exporter';
28
+ export { AsyncLocalStorageContextManager } from './core/context';
29
+ export { WorkerTracerProvider } from './core/provider';
30
+ export { Buffer } from './core/buffer';
31
+ export {
32
+ parseConfig,
33
+ createInitialiser,
34
+ getActiveConfig,
35
+ setConfig,
36
+ type Initialiser,
37
+ } from './core/config';
38
+
39
+ // Functional API (PRIMARY - zero-boilerplate tracing)
40
+ export {
41
+ trace,
42
+ withTracing,
43
+ instrument as instrumentFunctions,
44
+ span,
45
+ type traceOptions,
46
+ type TraceContext,
47
+ type InstrumentOptions,
48
+ } from './functional';
49
+
50
+ // Types
51
+ export type {
52
+ Trigger,
53
+ EdgeConfig,
54
+ ResolvedEdgeConfig,
55
+ ServiceConfig,
56
+ OTLPExporterConfig,
57
+ ExporterConfig,
58
+ SamplingConfig,
59
+ InstrumentationOptions,
60
+ ResolveConfigFn,
61
+ ConfigurationOption,
62
+ PostProcessorFn,
63
+ TailSampleFn,
64
+ LocalTrace,
65
+ TraceFlushableSpanProcessor,
66
+ InitialSpanInfo,
67
+ HandlerInstrumentation,
68
+ EdgeSubscriber,
69
+ ReadableSpan,
70
+ } from './types';
71
+
72
+ // Re-export OpenTelemetry APIs for convenience
73
+ export { context, propagation } from '@opentelemetry/api';
74
+
75
+ // Re-export common OpenTelemetry types
76
+ export type {
77
+ Span,
78
+ SpanContext,
79
+ Tracer,
80
+ Context,
81
+ } from '@opentelemetry/api';
package/src/logger.ts ADDED
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Zero-dependency logger for edge runtimes with dynamic log level control
3
+ * Entry point: autotel-edge/logger
4
+ */
5
+
6
+ export {
7
+ createEdgeLogger,
8
+ getEdgeTraceContext,
9
+ runWithLogLevel,
10
+ getActiveLogLevel,
11
+ type EdgeLogger,
12
+ type LogLevel,
13
+ } from './api/logger';
@@ -0,0 +1,297 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import {
3
+ createAdaptiveTailSampler,
4
+ createRandomTailSampler,
5
+ createErrorOnlyTailSampler,
6
+ createSlowOnlyTailSampler,
7
+ combineTailSamplers,
8
+ SamplingPresets,
9
+ type LocalTrace,
10
+ } from './index';
11
+ import { SpanStatusCode } from '@opentelemetry/api';
12
+ import type { ReadableSpan } from '@opentelemetry/sdk-trace-base';
13
+
14
+ // Helper to create a mock span
15
+ function createMockSpan(overrides: Partial<ReadableSpan> = {}): ReadableSpan {
16
+ const now = Date.now();
17
+ return {
18
+ name: 'test-span',
19
+ spanContext: () => ({
20
+ traceId: 'test-trace-id',
21
+ spanId: 'test-span-id',
22
+ traceFlags: 1,
23
+ }),
24
+ startTime: [Math.floor(now / 1000), (now % 1000) * 1_000_000],
25
+ endTime: [Math.floor(now / 1000), (now % 1000) * 1_000_000],
26
+ status: { code: SpanStatusCode.UNSET },
27
+ attributes: {},
28
+ links: [],
29
+ events: [],
30
+ duration: [0, 0],
31
+ ended: true,
32
+ resource: {} as any,
33
+ instrumentationLibrary: { name: 'test', version: '1.0.0' },
34
+ droppedAttributesCount: 0,
35
+ droppedEventsCount: 0,
36
+ droppedLinksCount: 0,
37
+ kind: 0,
38
+ parentSpanId: undefined,
39
+ ...overrides,
40
+ } as ReadableSpan;
41
+ }
42
+
43
+ // Helper to create a LocalTrace
44
+ function createLocalTrace(
45
+ traceId: string,
46
+ spanOverrides: Partial<ReadableSpan> = {},
47
+ ): LocalTrace {
48
+ return {
49
+ traceId,
50
+ localRootSpan: createMockSpan(spanOverrides),
51
+ spans: [createMockSpan(spanOverrides)],
52
+ };
53
+ }
54
+
55
+ describe('Sampling Strategies', () => {
56
+ describe('createAdaptiveTailSampler', () => {
57
+ it('should sample based on baseline rate for normal requests', () => {
58
+ const sampler = createAdaptiveTailSampler({ baselineSampleRate: 0.5 });
59
+
60
+ // Create multiple traces and check sampling distribution
61
+ const traces = Array.from({ length: 100 }, (_, i) =>
62
+ createLocalTrace(`trace-${i}`, { status: { code: SpanStatusCode.UNSET } }),
63
+ );
64
+
65
+ const sampled = traces.filter((trace) => sampler(trace));
66
+
67
+ // Should be roughly 50% sampled (allow some variance)
68
+ expect(sampled.length).toBeGreaterThan(30);
69
+ expect(sampled.length).toBeLessThan(70);
70
+ });
71
+
72
+ it('should always sample errors when alwaysSampleErrors is true', () => {
73
+ const sampler = createAdaptiveTailSampler({
74
+ baselineSampleRate: 0,
75
+ alwaysSampleErrors: true,
76
+ });
77
+
78
+ const errorTrace = createLocalTrace('error-trace', {
79
+ status: { code: SpanStatusCode.ERROR, message: 'Test error' },
80
+ });
81
+
82
+ expect(sampler(errorTrace)).toBe(true);
83
+ });
84
+
85
+ it('should not sample errors when alwaysSampleErrors is false', () => {
86
+ const sampler = createAdaptiveTailSampler({
87
+ baselineSampleRate: 0,
88
+ alwaysSampleErrors: false,
89
+ });
90
+
91
+ const errorTrace = createLocalTrace('error-trace', {
92
+ status: { code: SpanStatusCode.ERROR, message: 'Test error' },
93
+ });
94
+
95
+ expect(sampler(errorTrace)).toBe(false);
96
+ });
97
+
98
+ it('should always sample slow requests when alwaysSampleSlow is true', () => {
99
+ const now = Date.now();
100
+ const sampler = createAdaptiveTailSampler({
101
+ baselineSampleRate: 0,
102
+ slowThresholdMs: 1000,
103
+ alwaysSampleSlow: true,
104
+ });
105
+
106
+ const slowTrace = createLocalTrace('slow-trace', {
107
+ startTime: [Math.floor(now / 1000), (now % 1000) * 1_000_000],
108
+ endTime: [Math.floor((now + 2000) / 1000), ((now + 2000) % 1000) * 1_000_000],
109
+ });
110
+
111
+ expect(sampler(slowTrace)).toBe(true);
112
+ });
113
+
114
+ it('should not sample slow requests when alwaysSampleSlow is false', () => {
115
+ const now = Date.now();
116
+ const sampler = createAdaptiveTailSampler({
117
+ baselineSampleRate: 0,
118
+ slowThresholdMs: 1000,
119
+ alwaysSampleSlow: false,
120
+ });
121
+
122
+ const slowTrace = createLocalTrace('slow-trace', {
123
+ startTime: [Math.floor(now / 1000), (now % 1000) * 1_000_000],
124
+ endTime: [Math.floor((now + 2000) / 1000), ((now + 2000) % 1000) * 1_000_000],
125
+ });
126
+
127
+ expect(sampler(slowTrace)).toBe(false);
128
+ });
129
+
130
+ it('should maintain consistent baseline decision for same trace', () => {
131
+ const sampler = createAdaptiveTailSampler({ baselineSampleRate: 0.5 });
132
+
133
+ const trace1 = createLocalTrace('consistent-trace');
134
+ const decision1 = sampler(trace1);
135
+
136
+ // Create new trace with same ID
137
+ const trace2 = createLocalTrace('consistent-trace');
138
+ const decision2 = sampler(trace2);
139
+
140
+ expect(decision1).toBe(decision2);
141
+ });
142
+ });
143
+
144
+ describe('createRandomTailSampler', () => {
145
+ it('should sample at specified rate', () => {
146
+ const sampler = createRandomTailSampler(0.5);
147
+
148
+ const traces = Array.from({ length: 100 }, (_, i) =>
149
+ createLocalTrace(`trace-${i}`),
150
+ );
151
+
152
+ const sampled = traces.filter((trace) => sampler(trace));
153
+
154
+ // Should be roughly 50% sampled
155
+ expect(sampled.length).toBeGreaterThan(30);
156
+ expect(sampled.length).toBeLessThan(70);
157
+ });
158
+
159
+ it('should never sample when rate is 0', () => {
160
+ const sampler = createRandomTailSampler(0);
161
+
162
+ const traces = Array.from({ length: 100 }, (_, i) =>
163
+ createLocalTrace(`trace-${i}`),
164
+ );
165
+
166
+ const sampled = traces.filter((trace) => sampler(trace));
167
+
168
+ expect(sampled.length).toBe(0);
169
+ });
170
+
171
+ it('should always sample when rate is 1', () => {
172
+ const sampler = createRandomTailSampler(1);
173
+
174
+ const traces = Array.from({ length: 100 }, (_, i) =>
175
+ createLocalTrace(`trace-${i}`),
176
+ );
177
+
178
+ const sampled = traces.filter((trace) => sampler(trace));
179
+
180
+ expect(sampled.length).toBe(100);
181
+ });
182
+ });
183
+
184
+ describe('createErrorOnlyTailSampler', () => {
185
+ it('should only sample errors', () => {
186
+ const sampler = createErrorOnlyTailSampler();
187
+
188
+ const errorTrace = createLocalTrace('error-trace', {
189
+ status: { code: SpanStatusCode.ERROR },
190
+ });
191
+ const okTrace = createLocalTrace('ok-trace', {
192
+ status: { code: SpanStatusCode.OK },
193
+ });
194
+ const unsetTrace = createLocalTrace('unset-trace', {
195
+ status: { code: SpanStatusCode.UNSET },
196
+ });
197
+
198
+ expect(sampler(errorTrace)).toBe(true);
199
+ expect(sampler(okTrace)).toBe(false);
200
+ expect(sampler(unsetTrace)).toBe(false);
201
+ });
202
+ });
203
+
204
+ describe('createSlowOnlyTailSampler', () => {
205
+ it('should only sample slow requests', () => {
206
+ const now = Date.now();
207
+ const sampler = createSlowOnlyTailSampler(1000);
208
+
209
+ const fastTrace = createLocalTrace('fast-trace', {
210
+ startTime: [Math.floor(now / 1000), (now % 1000) * 1_000_000],
211
+ endTime: [Math.floor((now + 500) / 1000), ((now + 500) % 1000) * 1_000_000],
212
+ });
213
+
214
+ const slowTrace = createLocalTrace('slow-trace', {
215
+ startTime: [Math.floor(now / 1000), (now % 1000) * 1_000_000],
216
+ endTime: [Math.floor((now + 2000) / 1000), ((now + 2000) % 1000) * 1_000_000],
217
+ });
218
+
219
+ expect(sampler(fastTrace)).toBe(false);
220
+ expect(sampler(slowTrace)).toBe(true);
221
+ });
222
+ });
223
+
224
+ describe('combineTailSamplers', () => {
225
+ it('should sample if any sampler returns true (OR logic)', () => {
226
+ const errorSampler = createErrorOnlyTailSampler();
227
+ const slowSampler = createSlowOnlyTailSampler(1000);
228
+ const combined = combineTailSamplers(errorSampler, slowSampler);
229
+
230
+ const now = Date.now();
231
+
232
+ // Error but fast - should sample
233
+ const errorTrace = createLocalTrace('error-trace', {
234
+ status: { code: SpanStatusCode.ERROR },
235
+ startTime: [Math.floor(now / 1000), (now % 1000) * 1_000_000],
236
+ endTime: [Math.floor((now + 500) / 1000), ((now + 500) % 1000) * 1_000_000],
237
+ });
238
+
239
+ // OK but slow - should sample
240
+ const slowTrace = createLocalTrace('slow-trace', {
241
+ status: { code: SpanStatusCode.OK },
242
+ startTime: [Math.floor(now / 1000), (now % 1000) * 1_000_000],
243
+ endTime: [Math.floor((now + 2000) / 1000), ((now + 2000) % 1000) * 1_000_000],
244
+ });
245
+
246
+ // OK and fast - should not sample
247
+ const normalTrace = createLocalTrace('normal-trace', {
248
+ status: { code: SpanStatusCode.OK },
249
+ startTime: [Math.floor(now / 1000), (now % 1000) * 1_000_000],
250
+ endTime: [Math.floor((now + 500) / 1000), ((now + 500) % 1000) * 1_000_000],
251
+ });
252
+
253
+ expect(combined(errorTrace)).toBe(true);
254
+ expect(combined(slowTrace)).toBe(true);
255
+ expect(combined(normalTrace)).toBe(false);
256
+ });
257
+ });
258
+
259
+ describe('SamplingPresets', () => {
260
+ it('should have production preset', () => {
261
+ const sampler = SamplingPresets.production();
262
+ expect(typeof sampler).toBe('function');
263
+ });
264
+
265
+ it('should have highTraffic preset', () => {
266
+ const sampler = SamplingPresets.highTraffic();
267
+ expect(typeof sampler).toBe('function');
268
+ });
269
+
270
+ it('should have debugging preset', () => {
271
+ const sampler = SamplingPresets.debugging();
272
+ expect(typeof sampler).toBe('function');
273
+ });
274
+
275
+ it('should have development preset', () => {
276
+ const sampler = SamplingPresets.development();
277
+ expect(typeof sampler).toBe('function');
278
+ });
279
+
280
+ it('production preset should capture errors and slow requests', () => {
281
+ const sampler = SamplingPresets.production();
282
+ const now = Date.now();
283
+
284
+ const errorTrace = createLocalTrace('error-trace', {
285
+ status: { code: SpanStatusCode.ERROR },
286
+ });
287
+
288
+ const slowTrace = createLocalTrace('slow-trace', {
289
+ startTime: [Math.floor(now / 1000), (now % 1000) * 1_000_000],
290
+ endTime: [Math.floor((now + 2000) / 1000), ((now + 2000) % 1000) * 1_000_000],
291
+ });
292
+
293
+ expect(sampler(errorTrace)).toBe(true);
294
+ expect(sampler(slowTrace)).toBe(true);
295
+ });
296
+ });
297
+ });
@@ -0,0 +1,276 @@
1
+ /**
2
+ * Sampling strategies for autotel-edge
3
+ *
4
+ * Provides intelligent sampling to reduce telemetry costs while capturing critical data.
5
+ *
6
+ * Key strategies:
7
+ * - Always trace errors and slow requests (critical for debugging)
8
+ * - Adaptive sampling based on load
9
+ * - Baseline random sampling for normal traffic
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * import { createAdaptiveTailSampler } from 'autotel-edge/sampling'
14
+ *
15
+ * export default instrument(handler, {
16
+ * sampling: {
17
+ * tailSampler: createAdaptiveTailSampler({
18
+ * baselineSampleRate: 0.1, // 10% of normal requests
19
+ * slowThresholdMs: 1000, // Requests > 1s are "slow"
20
+ * })
21
+ * }
22
+ * })
23
+ * ```
24
+ */
25
+
26
+ import type { TailSampleFn, LocalTrace } from '../types';
27
+ import type { ReadableSpan } from '@opentelemetry/sdk-trace-base';
28
+
29
+ export interface AdaptiveSamplerOptions {
30
+ /**
31
+ * Baseline sample rate for normal (successful, fast) requests
32
+ * @default 0.1 (10%)
33
+ */
34
+ baselineSampleRate?: number;
35
+
36
+ /**
37
+ * Threshold in milliseconds for "slow" requests
38
+ * Requests taking longer than this will always be trace
39
+ * @default 1000ms
40
+ */
41
+ slowThresholdMs?: number;
42
+
43
+ /**
44
+ * Always trace error spans
45
+ * @default true
46
+ */
47
+ alwaysSampleErrors?: boolean;
48
+
49
+ /**
50
+ * Always trace slow spans
51
+ * @default true
52
+ */
53
+ alwaysSampleSlow?: boolean;
54
+ }
55
+
56
+ /**
57
+ * Create an adaptive tail sampler
58
+ *
59
+ * This sampler ensures you never miss critical issues while keeping costs down:
60
+ * - Always traces errors (status code = ERROR)
61
+ * - Always traces slow requests (duration >= slowThresholdMs)
62
+ * - Uses baseline sample rate for successful fast requests
63
+ *
64
+ * **Recommended for production use.**
65
+ *
66
+ * @example
67
+ * ```typescript
68
+ * const tailSampler = createAdaptiveTailSampler({
69
+ * baselineSampleRate: 0.1, // 10% of normal requests
70
+ * slowThresholdMs: 1000, // Requests > 1s are "slow"
71
+ * alwaysSampleErrors: true, // Always trace errors
72
+ * alwaysSampleSlow: true // Always trace slow requests
73
+ * })
74
+ * ```
75
+ */
76
+ export function createAdaptiveTailSampler(
77
+ options: AdaptiveSamplerOptions = {},
78
+ ): TailSampleFn {
79
+ const baselineSampleRate = options.baselineSampleRate ?? 0.1;
80
+ const slowThresholdMs = options.slowThresholdMs ?? 1000;
81
+ const alwaysSampleErrors = options.alwaysSampleErrors ?? true;
82
+ const alwaysSampleSlow = options.alwaysSampleSlow ?? true;
83
+
84
+ if (baselineSampleRate < 0 || baselineSampleRate > 1) {
85
+ throw new Error('Baseline sample rate must be between 0 and 1');
86
+ }
87
+
88
+ // Store baseline decisions using trace ID
89
+ const baselineDecisions = new Map<string, boolean>();
90
+
91
+ return (traceInfo: LocalTrace): boolean => {
92
+ const { traceId, localRootSpan } = traceInfo;
93
+
94
+ // Get or create baseline decision for this trace
95
+ if (!baselineDecisions.has(traceId)) {
96
+ baselineDecisions.set(traceId, Math.random() < baselineSampleRate);
97
+ }
98
+ const baselineDecision = baselineDecisions.get(traceId)!;
99
+
100
+ // Always keep errors (SpanStatusCode.ERROR = 2)
101
+ if (alwaysSampleErrors && localRootSpan.status.code === 2) {
102
+ return true;
103
+ }
104
+
105
+ // Always keep slow requests
106
+ if (alwaysSampleSlow) {
107
+ const duration = getDurationMs(localRootSpan);
108
+ if (duration >= slowThresholdMs) {
109
+ return true;
110
+ }
111
+ }
112
+
113
+ // Otherwise, use baseline decision
114
+ return baselineDecision;
115
+ };
116
+ }
117
+
118
+ /**
119
+ * Create a simple random tail sampler
120
+ *
121
+ * Samples a fixed percentage of all traces regardless of outcome.
122
+ *
123
+ * @example
124
+ * ```typescript
125
+ * const tailSampler = createRandomTailSampler(0.1) // 10% of all requests
126
+ * ```
127
+ */
128
+ export function createRandomTailSampler(sampleRate: number): TailSampleFn {
129
+ if (sampleRate < 0 || sampleRate > 1) {
130
+ throw new Error('Sample rate must be between 0 and 1');
131
+ }
132
+
133
+ const decisions = new Map<string, boolean>();
134
+
135
+ return (traceInfo: LocalTrace): boolean => {
136
+ const { traceId } = traceInfo;
137
+
138
+ if (!decisions.has(traceId)) {
139
+ decisions.set(traceId, Math.random() < sampleRate);
140
+ }
141
+
142
+ return decisions.get(traceId)!;
143
+ };
144
+ }
145
+
146
+ /**
147
+ * Create a tail sampler that keeps all errors
148
+ *
149
+ * Useful for debugging - captures all failures while dropping successful requests.
150
+ *
151
+ * @example
152
+ * ```typescript
153
+ * const tailSampler = createErrorOnlyTailSampler()
154
+ * ```
155
+ */
156
+ export function createErrorOnlyTailSampler(): TailSampleFn {
157
+ return (traceInfo: LocalTrace): boolean => {
158
+ // SpanStatusCode.ERROR = 2
159
+ return traceInfo.localRootSpan.status.code === 2;
160
+ };
161
+ }
162
+
163
+ /**
164
+ * Create a tail sampler that keeps slow requests
165
+ *
166
+ * Useful for performance debugging - captures slow requests while dropping fast ones.
167
+ *
168
+ * @example
169
+ * ```typescript
170
+ * const tailSampler = createSlowOnlyTailSampler(1000) // Keep requests > 1s
171
+ * ```
172
+ */
173
+ export function createSlowOnlyTailSampler(thresholdMs: number): TailSampleFn {
174
+ return (traceInfo: LocalTrace): boolean => {
175
+ const duration = getDurationMs(traceInfo.localRootSpan);
176
+ return duration >= thresholdMs;
177
+ };
178
+ }
179
+
180
+ /**
181
+ * Combine multiple tail samplers with OR logic
182
+ *
183
+ * Keeps a trace if ANY sampler returns true.
184
+ *
185
+ * @example
186
+ * ```typescript
187
+ * const tailSampler = combineTailSamplers(
188
+ * createErrorOnlyTailSampler(),
189
+ * createSlowOnlyTailSampler(1000),
190
+ * createRandomTailSampler(0.01) // 1% baseline
191
+ * )
192
+ * ```
193
+ */
194
+ export function combineTailSamplers(...samplers: TailSampleFn[]): TailSampleFn {
195
+ if (samplers.length === 0) {
196
+ throw new Error('combineTailSamplers requires at least one sampler');
197
+ }
198
+
199
+ return (traceInfo: LocalTrace): boolean => {
200
+ return samplers.some((sampler) => sampler(traceInfo));
201
+ };
202
+ }
203
+
204
+ /**
205
+ * Create a tail sampler based on custom predicate
206
+ *
207
+ * @example
208
+ * ```typescript
209
+ * // Keep traces with specific attributes
210
+ * const tailSampler = createCustomTailSampler((trace) => {
211
+ * const attrs = trace.localRootSpan.attributes
212
+ * return attrs['user.id'] === 'vip_123'
213
+ * })
214
+ * ```
215
+ */
216
+ export function createCustomTailSampler(
217
+ predicate: (traceInfo: LocalTrace) => boolean,
218
+ ): TailSampleFn {
219
+ return predicate;
220
+ }
221
+
222
+ /**
223
+ * Helper: Get span duration in milliseconds
224
+ */
225
+ function getDurationMs(span: ReadableSpan): number {
226
+ const start = span.startTime[0] * 1000 + span.startTime[1] / 1_000_000;
227
+ const end = span.endTime[0] * 1000 + span.endTime[1] / 1_000_000;
228
+ return end - start;
229
+ }
230
+
231
+ /**
232
+ * Common presets for quick setup
233
+ */
234
+ export const SamplingPresets = {
235
+ /**
236
+ * Production: 10% baseline, all errors, all slow (>1s)
237
+ * Recommended for most production workloads
238
+ */
239
+ production: (): TailSampleFn =>
240
+ createAdaptiveTailSampler({
241
+ baselineSampleRate: 0.1,
242
+ slowThresholdMs: 1000,
243
+ }),
244
+
245
+ /**
246
+ * High-traffic: 1% baseline, all errors, all slow (>2s)
247
+ * For high-volume services where cost is a concern
248
+ */
249
+ highTraffic: (): TailSampleFn =>
250
+ createAdaptiveTailSampler({
251
+ baselineSampleRate: 0.01,
252
+ slowThresholdMs: 2000,
253
+ }),
254
+
255
+ /**
256
+ * Debugging: All errors, all slow (>500ms), 50% baseline
257
+ * For active debugging sessions
258
+ */
259
+ debugging: (): TailSampleFn =>
260
+ createAdaptiveTailSampler({
261
+ baselineSampleRate: 0.5,
262
+ slowThresholdMs: 500,
263
+ }),
264
+
265
+ /**
266
+ * Development: 100% sampling
267
+ * For local development and testing
268
+ */
269
+ development: (): TailSampleFn => () => true,
270
+
271
+ /**
272
+ * Errors only: Capture all failures, drop all successes
273
+ * For error-focused monitoring
274
+ */
275
+ errorsOnly: (): TailSampleFn => createErrorOnlyTailSampler(),
276
+ };
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Sampling strategies for edge runtimes
3
+ * Entry point: autotel-edge/sampling
4
+ */
5
+
6
+ export * from './sampling/index';
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Testing utilities for autotel-edge
3
+ *
4
+ * This module provides testing helpers for edge runtime environments.
5
+ * Currently minimal - can be extended with utilities from autotel package as needed.
6
+ */
7
+
8
+ // Empty export to make this a valid module
9
+ export {};
package/src/testing.ts ADDED
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Testing utilities for edge runtimes
3
+ * Entry point: autotel-edge/testing
4
+ */
5
+
6
+ export type * from './testing/index';