autotel 2.25.2 → 2.25.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +152 -1
- package/dist/attributes.d.cts +2 -2
- package/dist/attributes.d.ts +2 -2
- package/dist/auto.cjs +6 -6
- package/dist/auto.js +4 -4
- package/dist/{chunk-QUW4I2OI.js → chunk-3ZLYWPMY.js} +3 -3
- package/dist/{chunk-QUW4I2OI.js.map → chunk-3ZLYWPMY.js.map} +1 -1
- package/dist/{chunk-BGVKKL2N.cjs → chunk-7FIGORWI.cjs} +46 -2
- package/dist/chunk-7FIGORWI.cjs.map +1 -0
- package/dist/{chunk-MNMLCLHH.cjs → chunk-AQXPGGQK.cjs} +5 -5
- package/dist/{chunk-MNMLCLHH.cjs.map → chunk-AQXPGGQK.cjs.map} +1 -1
- package/dist/{chunk-4KGC5N3J.cjs → chunk-FXKB7EPR.cjs} +13 -13
- package/dist/{chunk-4KGC5N3J.cjs.map → chunk-FXKB7EPR.cjs.map} +1 -1
- package/dist/{chunk-4SCBD22Z.js → chunk-IFKDBL65.js} +3 -3
- package/dist/{chunk-4SCBD22Z.js.map → chunk-IFKDBL65.js.map} +1 -1
- package/dist/{chunk-JVICEM6W.cjs → chunk-ITYASFHQ.cjs} +91 -19
- package/dist/chunk-ITYASFHQ.cjs.map +1 -0
- package/dist/{chunk-IKRHEUS7.js → chunk-JM63D22M.js} +10 -10
- package/dist/{chunk-IKRHEUS7.js.map → chunk-JM63D22M.js.map} +1 -1
- package/dist/{chunk-GVLK7YUU.cjs → chunk-KZEC4CHV.cjs} +6 -4
- package/dist/chunk-KZEC4CHV.cjs.map +1 -0
- package/dist/{chunk-VXLEJWLY.js → chunk-MNBAXRVG.js} +89 -17
- package/dist/chunk-MNBAXRVG.js.map +1 -0
- package/dist/{chunk-6YIDHH2S.cjs → chunk-OFPZULMQ.cjs} +32 -10
- package/dist/chunk-OFPZULMQ.cjs.map +1 -0
- package/dist/{chunk-UKUYBUFQ.cjs → chunk-OWXXS4JB.cjs} +7 -7
- package/dist/{chunk-UKUYBUFQ.cjs.map → chunk-OWXXS4JB.cjs.map} +1 -1
- package/dist/{chunk-77PLEJ54.js → chunk-PKXD2RMI.js} +4 -4
- package/dist/{chunk-77PLEJ54.js.map → chunk-PKXD2RMI.js.map} +1 -1
- package/dist/{chunk-RWOVNF3V.cjs → chunk-RMGSBMQF.cjs} +36 -36
- package/dist/chunk-RMGSBMQF.cjs.map +1 -0
- package/dist/{chunk-YTGF4L2C.js → chunk-RUD7KS4R.js} +27 -5
- package/dist/chunk-RUD7KS4R.js.map +1 -0
- package/dist/{chunk-KUSYIHW7.js → chunk-VVYSQXQL.js} +3 -3
- package/dist/{chunk-KUSYIHW7.js.map → chunk-VVYSQXQL.js.map} +1 -1
- package/dist/{chunk-XND7WBVX.js → chunk-VYA6QDNA.js} +43 -3
- package/dist/chunk-VYA6QDNA.js.map +1 -0
- package/dist/{chunk-L627YSSP.cjs → chunk-WOWTZ4EB.cjs} +9 -9
- package/dist/{chunk-L627YSSP.cjs.map → chunk-WOWTZ4EB.cjs.map} +1 -1
- package/dist/{chunk-X4RMFFMR.js → chunk-XDKK53OL.js} +6 -4
- package/dist/chunk-XDKK53OL.js.map +1 -0
- package/dist/decorators.cjs +5 -5
- package/dist/decorators.js +5 -5
- package/dist/event.cjs +8 -8
- package/dist/event.js +5 -5
- package/dist/functional.cjs +12 -12
- package/dist/functional.js +5 -5
- package/dist/index.cjs +71 -51
- package/dist/index.d.cts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +13 -13
- package/dist/{init-Q4uIQKbq.d.cts → init-CIzpC5kZ.d.cts} +9 -2
- package/dist/{init-ls4xSZe5.d.ts → init-C_PiC_Su.d.ts} +9 -2
- package/dist/instrumentation.cjs +12 -12
- package/dist/instrumentation.cjs.map +1 -1
- package/dist/instrumentation.js +4 -4
- package/dist/instrumentation.js.map +1 -1
- package/dist/messaging-adapters.d.cts +1 -1
- package/dist/messaging-adapters.d.ts +1 -1
- package/dist/messaging.cjs +9 -9
- package/dist/messaging.d.cts +1 -1
- package/dist/messaging.d.ts +1 -1
- package/dist/messaging.js +6 -6
- package/dist/metric-helpers.d.cts +1 -1
- package/dist/metric-helpers.d.ts +1 -1
- package/dist/sampling.cjs +26 -10
- package/dist/sampling.d.cts +49 -1
- package/dist/sampling.d.ts +49 -1
- package/dist/sampling.js +1 -1
- package/dist/semantic-helpers.cjs +10 -10
- package/dist/semantic-helpers.js +6 -6
- package/dist/tail-sampling-processor.cjs +6 -2
- package/dist/tail-sampling-processor.d.cts +2 -2
- package/dist/tail-sampling-processor.d.ts +2 -2
- package/dist/tail-sampling-processor.js +5 -1
- package/dist/trace-helpers.d.cts +1 -1
- package/dist/trace-helpers.d.ts +1 -1
- package/dist/{utils-D1trOLNm.d.ts → utils-Buel3cj0.d.ts} +1 -1
- package/dist/{utils-DuNJfXSH.d.cts → utils-CbUkl8r1.d.cts} +1 -1
- package/dist/webhook.cjs +6 -6
- package/dist/webhook.js +5 -5
- package/dist/workflow-distributed.cjs +7 -7
- package/dist/workflow-distributed.js +5 -5
- package/dist/workflow.cjs +10 -10
- package/dist/workflow.js +6 -6
- package/dist/yaml-config.cjs +5 -5
- package/dist/yaml-config.d.cts +5 -4
- package/dist/yaml-config.d.ts +5 -4
- package/dist/yaml-config.js +2 -2
- package/package.json +1 -1
- package/src/env-config.test.ts +77 -0
- package/src/env-config.ts +106 -0
- package/src/functional.ts +13 -7
- package/src/index.ts +6 -0
- package/src/init.customization.test.ts +61 -0
- package/src/init.integrations.test.ts +6 -1
- package/src/init.ts +23 -13
- package/src/instrumentation.ts +1 -1
- package/src/sampling.test.ts +96 -3
- package/src/sampling.ts +90 -0
- package/src/tail-sampling-processor.test.ts +26 -22
- package/src/tail-sampling-processor.ts +8 -4
- package/src/yaml-config.test.ts +71 -0
- package/src/yaml-config.ts +32 -2
- package/dist/chunk-6YIDHH2S.cjs.map +0 -1
- package/dist/chunk-BGVKKL2N.cjs.map +0 -1
- package/dist/chunk-GVLK7YUU.cjs.map +0 -1
- package/dist/chunk-JVICEM6W.cjs.map +0 -1
- package/dist/chunk-RWOVNF3V.cjs.map +0 -1
- package/dist/chunk-VXLEJWLY.js.map +0 -1
- package/dist/chunk-X4RMFFMR.js.map +0 -1
- package/dist/chunk-XND7WBVX.js.map +0 -1
- package/dist/chunk-YTGF4L2C.js.map +0 -1
|
@@ -4,6 +4,10 @@
|
|
|
4
4
|
|
|
5
5
|
import { describe, it, expect, beforeEach } from 'vitest';
|
|
6
6
|
import { TailSamplingSpanProcessor } from './tail-sampling-processor';
|
|
7
|
+
import {
|
|
8
|
+
AUTOTEL_SAMPLING_TAIL_KEEP,
|
|
9
|
+
AUTOTEL_SAMPLING_TAIL_EVALUATED,
|
|
10
|
+
} from './sampling';
|
|
7
11
|
import type {
|
|
8
12
|
SpanProcessor,
|
|
9
13
|
ReadableSpan,
|
|
@@ -86,8 +90,8 @@ describe('TailSamplingSpanProcessor', () => {
|
|
|
86
90
|
|
|
87
91
|
it('should forward spans marked to keep (sampling.tail.keep = true)', () => {
|
|
88
92
|
const span = createMockSpan({
|
|
89
|
-
|
|
90
|
-
|
|
93
|
+
[AUTOTEL_SAMPLING_TAIL_EVALUATED]: true,
|
|
94
|
+
[AUTOTEL_SAMPLING_TAIL_KEEP]: true,
|
|
91
95
|
});
|
|
92
96
|
|
|
93
97
|
tailSamplingProcessor.onEnd(span);
|
|
@@ -100,8 +104,8 @@ describe('TailSamplingSpanProcessor', () => {
|
|
|
100
104
|
describe('Span dropping', () => {
|
|
101
105
|
it('should drop spans marked to drop (sampling.tail.keep = false)', () => {
|
|
102
106
|
const span = createMockSpan({
|
|
103
|
-
|
|
104
|
-
|
|
107
|
+
[AUTOTEL_SAMPLING_TAIL_EVALUATED]: true,
|
|
108
|
+
[AUTOTEL_SAMPLING_TAIL_KEEP]: false,
|
|
105
109
|
});
|
|
106
110
|
|
|
107
111
|
tailSamplingProcessor.onEnd(span);
|
|
@@ -112,13 +116,13 @@ describe('TailSamplingSpanProcessor', () => {
|
|
|
112
116
|
|
|
113
117
|
it('should drop multiple spans marked as false', () => {
|
|
114
118
|
const span1 = createMockSpan({
|
|
115
|
-
|
|
116
|
-
|
|
119
|
+
[AUTOTEL_SAMPLING_TAIL_EVALUATED]: true,
|
|
120
|
+
[AUTOTEL_SAMPLING_TAIL_KEEP]: false,
|
|
117
121
|
});
|
|
118
122
|
|
|
119
123
|
const span2 = createMockSpan({
|
|
120
|
-
|
|
121
|
-
|
|
124
|
+
[AUTOTEL_SAMPLING_TAIL_EVALUATED]: true,
|
|
125
|
+
[AUTOTEL_SAMPLING_TAIL_KEEP]: false,
|
|
122
126
|
});
|
|
123
127
|
|
|
124
128
|
tailSamplingProcessor.onEnd(span1);
|
|
@@ -131,8 +135,8 @@ describe('TailSamplingSpanProcessor', () => {
|
|
|
131
135
|
describe('Edge cases', () => {
|
|
132
136
|
it('should forward spans when only evaluated but no keep attribute', () => {
|
|
133
137
|
const span = createMockSpan({
|
|
134
|
-
|
|
135
|
-
// Missing
|
|
138
|
+
[AUTOTEL_SAMPLING_TAIL_EVALUATED]: true,
|
|
139
|
+
// Missing AUTOTEL_SAMPLING_TAIL_KEEP
|
|
136
140
|
});
|
|
137
141
|
|
|
138
142
|
tailSamplingProcessor.onEnd(span);
|
|
@@ -143,8 +147,8 @@ describe('TailSamplingSpanProcessor', () => {
|
|
|
143
147
|
|
|
144
148
|
it('should forward spans when only keep but not evaluated', () => {
|
|
145
149
|
const span = createMockSpan({
|
|
146
|
-
// Missing
|
|
147
|
-
|
|
150
|
+
// Missing AUTOTEL_SAMPLING_TAIL_EVALUATED
|
|
151
|
+
[AUTOTEL_SAMPLING_TAIL_KEEP]: false,
|
|
148
152
|
});
|
|
149
153
|
|
|
150
154
|
tailSamplingProcessor.onEnd(span);
|
|
@@ -156,12 +160,12 @@ describe('TailSamplingSpanProcessor', () => {
|
|
|
156
160
|
it('should handle mixed spans (some kept, some dropped)', () => {
|
|
157
161
|
const keptSpan1 = createMockSpan({ foo: 'bar' });
|
|
158
162
|
const droppedSpan = createMockSpan({
|
|
159
|
-
|
|
160
|
-
|
|
163
|
+
[AUTOTEL_SAMPLING_TAIL_EVALUATED]: true,
|
|
164
|
+
[AUTOTEL_SAMPLING_TAIL_KEEP]: false,
|
|
161
165
|
});
|
|
162
166
|
const keptSpan2 = createMockSpan({
|
|
163
|
-
|
|
164
|
-
|
|
167
|
+
[AUTOTEL_SAMPLING_TAIL_EVALUATED]: true,
|
|
168
|
+
[AUTOTEL_SAMPLING_TAIL_KEEP]: true,
|
|
165
169
|
});
|
|
166
170
|
|
|
167
171
|
tailSamplingProcessor.onEnd(keptSpan1);
|
|
@@ -196,20 +200,20 @@ describe('TailSamplingSpanProcessor', () => {
|
|
|
196
200
|
// 3. Error request (keep)
|
|
197
201
|
|
|
198
202
|
const fastSpan = createMockSpan({
|
|
199
|
-
|
|
200
|
-
|
|
203
|
+
[AUTOTEL_SAMPLING_TAIL_EVALUATED]: true,
|
|
204
|
+
[AUTOTEL_SAMPLING_TAIL_KEEP]: false, // Fast, no errors
|
|
201
205
|
duration: 50,
|
|
202
206
|
});
|
|
203
207
|
|
|
204
208
|
const slowSpan = createMockSpan({
|
|
205
|
-
|
|
206
|
-
|
|
209
|
+
[AUTOTEL_SAMPLING_TAIL_EVALUATED]: true,
|
|
210
|
+
[AUTOTEL_SAMPLING_TAIL_KEEP]: true, // Slow request
|
|
207
211
|
duration: 1000,
|
|
208
212
|
});
|
|
209
213
|
|
|
210
214
|
const errorSpan = createMockSpan({
|
|
211
|
-
|
|
212
|
-
|
|
215
|
+
[AUTOTEL_SAMPLING_TAIL_EVALUATED]: true,
|
|
216
|
+
[AUTOTEL_SAMPLING_TAIL_KEEP]: true, // Had error
|
|
213
217
|
error: true,
|
|
214
218
|
});
|
|
215
219
|
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Tail Sampling Span Processor
|
|
3
3
|
*
|
|
4
|
-
* Filters spans based on the `sampling.tail.keep` attribute set during execution.
|
|
4
|
+
* Filters spans based on the `autotel.sampling.tail.keep` attribute set during execution.
|
|
5
5
|
* This enables adaptive sampling where we decide whether to keep a span AFTER
|
|
6
6
|
* the operation completes, based on criteria like errors, duration, etc.
|
|
7
7
|
*
|
|
8
8
|
* How it works:
|
|
9
9
|
* 1. Decorator creates span optimistically (head sampling returns true)
|
|
10
10
|
* 2. Operation executes and completes
|
|
11
|
-
* 3. Decorator calls shouldKeepTrace() and sets sampling.tail.keep attribute
|
|
11
|
+
* 3. Decorator calls shouldKeepTrace() and sets autotel.sampling.tail.keep attribute
|
|
12
12
|
* 4. This processor checks the attribute and drops spans marked as false
|
|
13
13
|
*/
|
|
14
14
|
|
|
@@ -18,6 +18,10 @@ import type {
|
|
|
18
18
|
} from '@opentelemetry/sdk-trace-base';
|
|
19
19
|
import type { Context } from '@opentelemetry/api';
|
|
20
20
|
import type { Span } from '@opentelemetry/sdk-trace-base';
|
|
21
|
+
import {
|
|
22
|
+
AUTOTEL_SAMPLING_TAIL_KEEP,
|
|
23
|
+
AUTOTEL_SAMPLING_TAIL_EVALUATED,
|
|
24
|
+
} from './sampling';
|
|
21
25
|
|
|
22
26
|
export class TailSamplingSpanProcessor implements SpanProcessor {
|
|
23
27
|
private wrappedProcessor: SpanProcessor;
|
|
@@ -31,8 +35,8 @@ export class TailSamplingSpanProcessor implements SpanProcessor {
|
|
|
31
35
|
}
|
|
32
36
|
|
|
33
37
|
onEnd(span: ReadableSpan): void {
|
|
34
|
-
const tailEvaluated = span.attributes[
|
|
35
|
-
const shouldKeep = span.attributes[
|
|
38
|
+
const tailEvaluated = span.attributes[AUTOTEL_SAMPLING_TAIL_EVALUATED];
|
|
39
|
+
const shouldKeep = span.attributes[AUTOTEL_SAMPLING_TAIL_KEEP];
|
|
36
40
|
|
|
37
41
|
if (tailEvaluated === true && shouldKeep === false) {
|
|
38
42
|
return;
|
package/src/yaml-config.test.ts
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
import { writeFileSync, mkdirSync, rmSync } from 'node:fs';
|
|
8
8
|
import path from 'node:path';
|
|
9
9
|
import { tmpdir } from 'node:os';
|
|
10
|
+
import { AdaptiveSampler } from './sampling';
|
|
10
11
|
|
|
11
12
|
describe('yaml-config', () => {
|
|
12
13
|
const testDir = path.join(tmpdir(), `autotel-yaml-test-${Date.now()}`);
|
|
@@ -207,6 +208,76 @@ service:
|
|
|
207
208
|
it('should throw on non-existent file', () => {
|
|
208
209
|
expect(() => loadYamlConfigFromFile('/non/existent/path.yaml')).toThrow();
|
|
209
210
|
});
|
|
211
|
+
|
|
212
|
+
it('should map sampling preset to config.sampling shorthand', () => {
|
|
213
|
+
const yaml = `
|
|
214
|
+
sampling:
|
|
215
|
+
preset: production
|
|
216
|
+
`;
|
|
217
|
+
const filePath = path.join(testDir, 'sampling-preset.yaml');
|
|
218
|
+
writeFileSync(filePath, yaml);
|
|
219
|
+
|
|
220
|
+
const config = loadYamlConfigFromFile(filePath);
|
|
221
|
+
expect(config.sampling).toBe('production');
|
|
222
|
+
expect(config.sampler).toBeUndefined();
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it('should prefer sampling preset over typed sampler config', () => {
|
|
226
|
+
const yaml = `
|
|
227
|
+
sampling:
|
|
228
|
+
preset: off
|
|
229
|
+
type: adaptive
|
|
230
|
+
baseline_rate: 0.5
|
|
231
|
+
`;
|
|
232
|
+
const filePath = path.join(testDir, 'sampling-preset-precedence.yaml');
|
|
233
|
+
writeFileSync(filePath, yaml);
|
|
234
|
+
|
|
235
|
+
const config = loadYamlConfigFromFile(filePath);
|
|
236
|
+
expect(config.sampling).toBe('off');
|
|
237
|
+
expect(config.sampler).toBeUndefined();
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it('should warn when preset is combined with ignored override fields', () => {
|
|
241
|
+
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
242
|
+
const yaml = `
|
|
243
|
+
sampling:
|
|
244
|
+
preset: production
|
|
245
|
+
type: adaptive
|
|
246
|
+
baseline_rate: 0.5
|
|
247
|
+
slow_threshold_ms: 250
|
|
248
|
+
`;
|
|
249
|
+
const filePath = path.join(testDir, 'sampling-preset-warning.yaml');
|
|
250
|
+
writeFileSync(filePath, yaml);
|
|
251
|
+
|
|
252
|
+
const config = loadYamlConfigFromFile(filePath);
|
|
253
|
+
|
|
254
|
+
expect(config.sampling).toBe('production');
|
|
255
|
+
expect(config.sampler).toBeUndefined();
|
|
256
|
+
expect(warnSpy).toHaveBeenCalledWith(
|
|
257
|
+
expect.stringContaining(
|
|
258
|
+
'sampling.preset="production" ignores these YAML fields',
|
|
259
|
+
),
|
|
260
|
+
);
|
|
261
|
+
expect(warnSpy).toHaveBeenCalledWith(
|
|
262
|
+
expect.stringContaining('type, baseline_rate, slow_threshold_ms'),
|
|
263
|
+
);
|
|
264
|
+
|
|
265
|
+
warnSpy.mockRestore();
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it('should still build sampler from typed YAML config without preset', () => {
|
|
269
|
+
const yaml = `
|
|
270
|
+
sampling:
|
|
271
|
+
type: adaptive
|
|
272
|
+
baseline_rate: 0.25
|
|
273
|
+
`;
|
|
274
|
+
const filePath = path.join(testDir, 'sampling-adaptive.yaml');
|
|
275
|
+
writeFileSync(filePath, yaml);
|
|
276
|
+
|
|
277
|
+
const config = loadYamlConfigFromFile(filePath);
|
|
278
|
+
expect(config.sampler).toBeInstanceOf(AdaptiveSampler);
|
|
279
|
+
expect(config.sampling).toBeUndefined();
|
|
280
|
+
});
|
|
210
281
|
});
|
|
211
282
|
|
|
212
283
|
describe('loadYamlConfig', () => {
|
package/src/yaml-config.ts
CHANGED
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
AlwaysSampler,
|
|
30
30
|
NeverSampler,
|
|
31
31
|
RandomSampler,
|
|
32
|
+
type SamplingPreset,
|
|
32
33
|
} from './sampling';
|
|
33
34
|
|
|
34
35
|
/**
|
|
@@ -63,6 +64,7 @@ export interface YamlConfig {
|
|
|
63
64
|
};
|
|
64
65
|
resource?: Record<string, string | number | boolean>;
|
|
65
66
|
sampling?: {
|
|
67
|
+
preset?: SamplingPreset;
|
|
66
68
|
type?: 'adaptive' | 'always_on' | 'always_off' | 'ratio';
|
|
67
69
|
ratio?: number;
|
|
68
70
|
baseline_rate?: number;
|
|
@@ -189,8 +191,13 @@ function yamlToAutotelConfig(yaml: YamlConfig): Partial<AutotelConfig> {
|
|
|
189
191
|
if (yaml.debug !== undefined) config.debug = yaml.debug;
|
|
190
192
|
|
|
191
193
|
// Sampling configuration
|
|
192
|
-
|
|
193
|
-
|
|
194
|
+
if (yaml.sampling?.preset) {
|
|
195
|
+
warnOnIgnoredPresetOverrides(yaml.sampling);
|
|
196
|
+
config.sampling = yaml.sampling.preset;
|
|
197
|
+
} else {
|
|
198
|
+
const sampler = createSamplerFromYaml(yaml.sampling);
|
|
199
|
+
if (sampler) config.sampler = sampler;
|
|
200
|
+
}
|
|
194
201
|
|
|
195
202
|
return config;
|
|
196
203
|
}
|
|
@@ -199,6 +206,7 @@ function createSamplerFromYaml(
|
|
|
199
206
|
sampling?: YamlConfig['sampling'],
|
|
200
207
|
): AutotelConfig['sampler'] {
|
|
201
208
|
if (!sampling) return undefined;
|
|
209
|
+
if (sampling.preset) return undefined;
|
|
202
210
|
|
|
203
211
|
const type = sampling.type ?? 'adaptive';
|
|
204
212
|
|
|
@@ -242,6 +250,28 @@ function createSamplerFromYaml(
|
|
|
242
250
|
}
|
|
243
251
|
}
|
|
244
252
|
|
|
253
|
+
function warnOnIgnoredPresetOverrides(
|
|
254
|
+
sampling: NonNullable<YamlConfig['sampling']>,
|
|
255
|
+
): void {
|
|
256
|
+
const ignoredFields = [
|
|
257
|
+
'type',
|
|
258
|
+
'ratio',
|
|
259
|
+
'baseline_rate',
|
|
260
|
+
'always_sample_errors',
|
|
261
|
+
'always_sample_slow',
|
|
262
|
+
'slow_threshold_ms',
|
|
263
|
+
].filter((field) => sampling[field as keyof typeof sampling] !== undefined);
|
|
264
|
+
|
|
265
|
+
if (ignoredFields.length === 0) {
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
console.warn(
|
|
270
|
+
`[autotel] sampling.preset="${sampling.preset}" ignores these YAML fields: ${ignoredFields.join(', ')}. ` +
|
|
271
|
+
'Use the programmatic API with sampler or samplingPresets.*(...) for tuned presets.',
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
|
|
245
275
|
/**
|
|
246
276
|
* Load and parse YAML config file (auto-discovery)
|
|
247
277
|
*
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/yaml-config.ts"],"names":["requireModule","path","existsSync","AdaptiveSampler","AlwaysSampler","NeverSampler","RandomSampler","readFileSync"],"mappings":"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","file":"chunk-6YIDHH2S.cjs","sourcesContent":["/**\n * YAML configuration loader for autotel\n *\n * Supports:\n * - Auto-discovery of autotel.yaml in cwd\n * - AUTOTEL_CONFIG_FILE env var override\n * - Environment variable substitution: ${env:VAR} and ${env:VAR:-default}\n *\n * @example Auto-discovery\n * ```yaml\n * # autotel.yaml in project root\n * service:\n * name: my-service\n * exporter:\n * endpoint: ${env:OTEL_EXPORTER_OTLP_ENDPOINT:-http://localhost:4318}\n * ```\n *\n * @example Explicit path\n * ```bash\n * AUTOTEL_CONFIG_FILE=./config/otel.yaml tsx --import autotel/auto src/index.ts\n * ```\n */\n\nimport { readFileSync, existsSync } from 'node:fs';\nimport path from 'node:path';\nimport type { AutotelConfig } from './init';\nimport {\n AdaptiveSampler,\n AlwaysSampler,\n NeverSampler,\n RandomSampler,\n} from './sampling';\n\n/**\n * Lazy-load yaml parser (optional peer dependency)\n * Only loads when a YAML config file is actually found\n */\nimport { requireModule } from './node-require';\n\nfunction loadYamlParser(): (content: string) => unknown {\n try {\n const mod = requireModule<{ parse: (content: string) => unknown }>('yaml');\n return mod.parse;\n } catch {\n throw new Error('YAML parser not found. Install with: pnpm add yaml');\n }\n}\n\n/**\n * YAML config structure\n * Maps to AutotelConfig with user-friendly naming\n */\nexport interface YamlConfig {\n service?: {\n name?: string;\n version?: string;\n environment?: string;\n };\n exporter?: {\n endpoint?: string;\n protocol?: 'http' | 'grpc';\n headers?: Record<string, string>;\n };\n resource?: Record<string, string | number | boolean>;\n sampling?: {\n type?: 'adaptive' | 'always_on' | 'always_off' | 'ratio';\n ratio?: number;\n baseline_rate?: number;\n always_sample_errors?: boolean;\n always_sample_slow?: boolean;\n slow_threshold_ms?: number;\n };\n autoInstrumentations?: string[] | Record<string, { enabled?: boolean }>;\n debug?: boolean;\n}\n\n/**\n * Environment variable substitution regex\n * Matches ${env:VAR_NAME} and ${env:VAR_NAME:-default}\n */\nconst ENV_VAR_PATTERN = /\\$\\{env:([A-Za-z_][A-Za-z0-9_]*)(?::-([^}]*))?\\}/g;\n\n/**\n * Substitute ${env:VAR} and ${env:VAR:-default} in a string\n *\n * @param value - String potentially containing env var references\n * @returns String with env vars substituted\n *\n * @example\n * substituteEnvVars('${env:NODE_ENV:-development}')\n * // Returns 'production' if NODE_ENV=production, else 'development'\n */\nfunction substituteEnvVars(value: string): string {\n return value.replaceAll(\n ENV_VAR_PATTERN,\n (_match, varName: string, defaultValue?: string) => {\n const envValue = process.env[varName];\n if (envValue !== undefined) return envValue;\n if (defaultValue !== undefined) return defaultValue;\n console.warn(\n `[autotel] Environment variable ${varName} not set and no default provided`,\n );\n return '';\n },\n );\n}\n\n/**\n * Recursively substitute env vars in an object\n *\n * @param obj - Object to process\n * @returns Object with all string values having env vars substituted\n */\nfunction substituteEnvVarsDeep(obj: unknown): unknown {\n if (typeof obj === 'string') {\n return substituteEnvVars(obj);\n }\n if (Array.isArray(obj)) {\n return obj.map((item) => substituteEnvVarsDeep(item));\n }\n if (obj && typeof obj === 'object') {\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(obj)) {\n result[key] = substituteEnvVarsDeep(value);\n }\n return result;\n }\n return obj;\n}\n\n/**\n * Find YAML config file path\n *\n * Priority:\n * 1. AUTOTEL_CONFIG_FILE env var (explicit path)\n * 2. autotel.yaml in cwd (convention)\n * 3. autotel.yml in cwd (alternative extension)\n *\n * @returns File path if found, null otherwise\n */\nfunction findConfigFile(): string | null {\n // Check env var first (explicit takes priority)\n const envPath = process.env.AUTOTEL_CONFIG_FILE;\n if (envPath) {\n const resolved = path.resolve(envPath);\n if (existsSync(resolved)) return resolved;\n console.warn(`[autotel] Config file not found: ${envPath}`);\n return null;\n }\n\n // Auto-discover autotel.yaml in cwd\n const conventionPath = path.resolve(process.cwd(), 'autotel.yaml');\n if (existsSync(conventionPath)) return conventionPath;\n\n // Also check .yml extension\n const altPath = path.resolve(process.cwd(), 'autotel.yml');\n if (existsSync(altPath)) return altPath;\n\n return null;\n}\n\n/**\n * Convert YAML config structure to AutotelConfig\n *\n * @param yaml - Parsed and env-substituted YAML config\n * @returns Partial AutotelConfig ready for merging\n */\nfunction yamlToAutotelConfig(yaml: YamlConfig): Partial<AutotelConfig> {\n const config: Partial<AutotelConfig> = {};\n\n // Service configuration\n if (yaml.service?.name) config.service = yaml.service.name;\n if (yaml.service?.version) config.version = yaml.service.version;\n if (yaml.service?.environment) config.environment = yaml.service.environment;\n\n // Exporter configuration\n if (yaml.exporter?.endpoint) config.endpoint = yaml.exporter.endpoint;\n if (yaml.exporter?.protocol) config.protocol = yaml.exporter.protocol;\n if (yaml.exporter?.headers) config.headers = yaml.exporter.headers;\n\n // Resource attributes (flattened)\n if (yaml.resource) config.resourceAttributes = yaml.resource;\n\n // Integrations\n if (yaml.autoInstrumentations)\n config.autoInstrumentations = yaml.autoInstrumentations;\n\n // Debug mode\n if (yaml.debug !== undefined) config.debug = yaml.debug;\n\n // Sampling configuration\n const sampler = createSamplerFromYaml(yaml.sampling);\n if (sampler) config.sampler = sampler;\n\n return config;\n}\n\nfunction createSamplerFromYaml(\n sampling?: YamlConfig['sampling'],\n): AutotelConfig['sampler'] {\n if (!sampling) return undefined;\n\n const type = sampling.type ?? 'adaptive';\n\n try {\n switch (type) {\n case 'adaptive': {\n return new AdaptiveSampler({\n baselineSampleRate: sampling.baseline_rate,\n alwaysSampleErrors: sampling.always_sample_errors,\n alwaysSampleSlow: sampling.always_sample_slow,\n slowThresholdMs: sampling.slow_threshold_ms,\n });\n }\n case 'always_on': {\n return new AlwaysSampler();\n }\n case 'always_off': {\n return new NeverSampler();\n }\n case 'ratio': {\n if (sampling.ratio === undefined) {\n console.warn(\n '[autotel] sampling.ratio missing in YAML sampling config. Falling back to adaptive sampler.',\n );\n return new AdaptiveSampler();\n }\n return new RandomSampler(sampling.ratio);\n }\n default: {\n console.warn(\n `[autotel] Unknown sampling type \"${type}\" in YAML config. Falling back to defaults.`,\n );\n return undefined;\n }\n }\n } catch (error) {\n console.warn(\n `[autotel] Failed to configure sampling from YAML: ${error instanceof Error ? error.message : String(error)}`,\n );\n return undefined;\n }\n}\n\n/**\n * Load and parse YAML config file (auto-discovery)\n *\n * Automatically finds and loads autotel.yaml or uses AUTOTEL_CONFIG_FILE.\n * Returns null if no config file found (not an error - YAML config is optional).\n *\n * @returns Partial AutotelConfig or null if no config file found\n *\n * @example\n * const yamlConfig = loadYamlConfig();\n * if (yamlConfig) {\n * init({ ...yamlConfig, debug: true });\n * }\n */\nexport function loadYamlConfig(): Partial<AutotelConfig> | null {\n const filePath = findConfigFile();\n if (!filePath) return null;\n\n try {\n const content = readFileSync(filePath, 'utf8');\n const parseYaml = loadYamlParser();\n const rawYaml = parseYaml(content) as YamlConfig;\n const substituted = substituteEnvVarsDeep(rawYaml) as YamlConfig;\n return yamlToAutotelConfig(substituted);\n } catch (error) {\n console.error(\n `[autotel] Failed to load YAML config from ${filePath}:`,\n error,\n );\n return null;\n }\n}\n\n/**\n * Load YAML config from a specific file path\n *\n * Unlike loadYamlConfig(), this throws if the file cannot be read.\n *\n * @param filePath - Path to YAML config file\n * @returns Partial AutotelConfig\n * @throws Error if file cannot be read or parsed\n *\n * @example\n * import { loadYamlConfigFromFile } from 'autotel/yaml';\n * import { init } from 'autotel';\n *\n * const config = loadYamlConfigFromFile('./config/otel.yaml');\n * init({ ...config, debug: true });\n */\nexport function loadYamlConfigFromFile(\n filePath: string,\n): Partial<AutotelConfig> {\n const resolved = path.resolve(filePath);\n const content = readFileSync(resolved, 'utf8');\n const parseYaml = loadYamlParser();\n const rawYaml = parseYaml(content) as YamlConfig;\n const substituted = substituteEnvVarsDeep(rawYaml) as YamlConfig;\n return yamlToAutotelConfig(substituted);\n}\n\n/**\n * Check if a YAML config file exists (without loading it)\n *\n * @returns true if a config file would be found by loadYamlConfig()\n */\nexport function hasYamlConfig(): boolean {\n return findConfigFile() !== null;\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/sampling.ts"],"names":["TraceFlags"],"mappings":"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","file":"chunk-BGVKKL2N.cjs","sourcesContent":["/**\n * Sampling Strategies\n *\n * Provides intelligent sampling beyond simple random rates.\n * Helps reduce telemetry costs while capturing critical data.\n *\n * Key strategies:\n * - Always trace errors and slow requests (critical for debugging)\n * - Sample by user ID for consistent request tracing\n * - Adaptive sampling based on load\n * - Sample by feature flags for A/B testing correlation\n *\n * @example\n * ```typescript\n * import { AlwaysOnErrorSampler, UserIdSampler } from './sampling'\n *\n * @Instrumented({\n * serviceName: 'user',\n * sampler: new AlwaysOnErrorSampler(0.1) // 10% baseline, 100% on errors\n * })\n * class UserService { }\n * ```\n */\n\nimport type { Link, Attributes } from '@opentelemetry/api';\nimport { TraceFlags } from '@opentelemetry/api';\nimport { type Logger } from './logger';\n\n/**\n * Sampler interface - return true to trace, false to skip\n */\nexport interface Sampler {\n /**\n * Decide whether to trace this operation\n *\n * @param context - Sampling context\n * @returns true to trace, false to skip\n */\n shouldSample(context: SamplingContext): boolean;\n\n /**\n * Whether this sampler needs tail sampling (post-execution decision)\n * If true, spans are always created and shouldKeepTrace() is called after execution\n *\n * @returns true if this sampler needs to evaluate after operation completes\n */\n needsTailSampling?(): boolean;\n\n /**\n * Re-evaluate sampling decision after operation completes (tail sampling)\n * Only called if needsTailSampling() returns true\n *\n * @param context - Sampling context\n * @param result - Operation result\n * @returns true if this trace should be kept, false to drop it\n */\n shouldKeepTrace?(context: SamplingContext, result: OperationResult): boolean;\n}\n\n/**\n * Context information for sampling decisions\n */\nexport interface SamplingContext {\n /** Operation name */\n operationName: string;\n /** Method arguments (for extracting user IDs, etc.) */\n args: unknown[];\n /** Optional metadata (e.g., feature flags, request headers) */\n metadata?: Record<string, unknown>;\n /** Optional span links for links-based sampling */\n links?: Link[];\n}\n\n/**\n * Result of a trace operation (for post-execution sampling)\n */\nexport interface OperationResult {\n /** Whether the operation succeeded */\n success: boolean;\n /** Duration in milliseconds */\n duration: number;\n /** Error if operation failed */\n error?: Error;\n}\n\n/**\n * Simple random sampler\n *\n * @example\n * ```typescript\n * new RandomSampler(0.1) // Sample 10% of requests\n * ```\n */\nexport class RandomSampler implements Sampler {\n constructor(private readonly sampleRate: number) {\n if (sampleRate < 0 || sampleRate > 1) {\n throw new Error('Sample rate must be between 0 and 1');\n }\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n shouldSample(_context: SamplingContext): boolean {\n return Math.random() < this.sampleRate;\n }\n}\n\n/**\n * Always sample (100% tracing)\n */\nexport class AlwaysSampler implements Sampler {\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n shouldSample(_context: SamplingContext): boolean {\n return true;\n }\n}\n\n/**\n * Never sample (0% tracing)\n */\nexport class NeverSampler implements Sampler {\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n shouldSample(_context: SamplingContext): boolean {\n return false;\n }\n}\n\n/**\n * Adaptive sampler that always traces errors and slow requests\n *\n * This is the recommended sampler for production use.\n * It ensures you never miss critical issues while keeping costs down.\n *\n * Strategy:\n * - Always trace errors (critical for debugging)\n * - Always trace slow requests (performance issues)\n * - Use baseline sample rate for successful fast requests\n *\n * **IMPORTANT - Tail Sampling Requirement:**\n * This sampler uses tail sampling (makes decisions AFTER execution).\n * You MUST use TailSamplingSpanProcessor for it to work correctly:\n *\n * - If using initInstrumentation(): TailSamplingSpanProcessor is auto-configured\n * - If using custom TracerProvider: You MUST manually register TailSamplingSpanProcessor\n *\n * Without TailSamplingSpanProcessor, ALL spans are exported (defeating the cost savings).\n *\n * @see TailSamplingSpanProcessor\n * @see README.md \"Tail Sampling with Custom Providers\" section\n *\n * @example\n * ```typescript\n * new AdaptiveSampler({\n * baselineSampleRate: 0.1, // 10% of normal requests\n * slowThresholdMs: 1000, // Requests > 1s are \"slow\"\n * alwaysSampleErrors: true, // Always trace errors\n * alwaysSampleSlow: true // Always trace slow requests\n * })\n * ```\n */\nexport class AdaptiveSampler implements Sampler {\n private baselineSampleRate: number;\n private slowThresholdMs: number;\n private alwaysSampleErrors: boolean;\n private alwaysSampleSlow: boolean;\n private linksBased: boolean;\n private linksRate: number;\n private logger?: Logger;\n\n // Track whether we should sample this request\n private readonly samplingDecisions = new WeakMap<unknown[], boolean>();\n // Track operation results to enable post-execution decision\n private readonly operationResults = new WeakMap<unknown[], OperationResult>();\n\n constructor(\n options: {\n baselineSampleRate?: number;\n slowThresholdMs?: number;\n alwaysSampleErrors?: boolean;\n alwaysSampleSlow?: boolean;\n /** Enable links-based sampling for event-driven architectures */\n linksBased?: boolean;\n /** Sampling rate for spans linked to sampled spans (0.0-1.0) */\n linksRate?: number;\n logger?: Logger;\n } = {},\n ) {\n this.baselineSampleRate = options.baselineSampleRate ?? 0.1;\n this.slowThresholdMs = options.slowThresholdMs ?? 1000;\n this.alwaysSampleErrors = options.alwaysSampleErrors ?? true;\n this.alwaysSampleSlow = options.alwaysSampleSlow ?? true;\n this.linksBased = options.linksBased ?? false;\n this.linksRate = options.linksRate ?? 1;\n this.logger = options.logger;\n\n if (this.baselineSampleRate < 0 || this.baselineSampleRate > 1) {\n throw new Error('Baseline sample rate must be between 0 and 1');\n }\n if (this.linksRate < 0 || this.linksRate > 1) {\n throw new Error('Links rate must be between 0 and 1');\n }\n }\n\n needsTailSampling(): boolean {\n // AdaptiveSampler ALWAYS needs tail sampling to implement error/slow capture\n return true;\n }\n\n shouldSample(context: SamplingContext): boolean {\n // For tail sampling, we optimistically create spans for all requests\n // The real decision happens in shouldKeepTrace() after execution\n // We still store the baseline decision for shouldKeepTrace() to use\n const baselineDecision = Math.random() < this.baselineSampleRate;\n this.samplingDecisions.set(context.args, baselineDecision);\n\n // Always return true to create the span (tail sampling will decide if we keep it)\n return true;\n }\n\n /**\n * Check if any links point to sampled spans.\n *\n * A span is considered linked to a sampled span if any of its links\n * have trace_flags with the sampled bit set (0x01).\n *\n * @param links - Array of span links to check\n * @returns true if any linked span is sampled, false otherwise\n */\n hasSampledLink(links: Link[]): boolean {\n if (!links || links.length === 0) {\n return false;\n }\n return links.some(\n (link) =>\n link.context && (link.context.traceFlags & TraceFlags.SAMPLED) !== 0,\n );\n }\n\n /**\n * Re-evaluate sampling decision after operation completes\n *\n * This allows us to always capture errors and slow requests,\n * even if they weren't initially sampled.\n *\n * @param context - Sampling context\n * @param result - Operation result\n * @returns true if this operation should be kept (not discarded)\n */\n shouldKeepTrace(context: SamplingContext, result: OperationResult): boolean {\n const baselineDecision = this.samplingDecisions.get(context.args) ?? false;\n\n // Always keep errors\n if (this.alwaysSampleErrors && !result.success) {\n if (!baselineDecision) {\n this.logger?.debug(\n {\n operation: context.operationName,\n error: result.error?.message,\n },\n 'Adaptive sampling: Keeping error trace',\n );\n }\n return true;\n }\n\n // Always keep slow requests\n if (this.alwaysSampleSlow && result.duration >= this.slowThresholdMs) {\n if (!baselineDecision) {\n this.logger?.debug(\n {\n operation: context.operationName,\n duration: result.duration,\n },\n 'Adaptive sampling: Keeping slow trace',\n );\n }\n return true;\n }\n\n // Check for sampled links (links-based sampling for event-driven systems)\n if (\n this.linksBased &&\n context.links &&\n this.hasSampledLink(context.links)\n ) {\n // Use linksRate to decide whether to keep the linked span\n const keepLinked = Math.random() < this.linksRate;\n if (keepLinked && !baselineDecision) {\n this.logger?.debug(\n {\n operation: context.operationName,\n linkCount: context.links.length,\n },\n 'Adaptive sampling: Keeping trace due to sampled link',\n );\n }\n return keepLinked;\n }\n\n // Otherwise, use baseline decision\n return baselineDecision;\n }\n}\n\n/**\n * User-based sampler for consistent tracing\n *\n * Always samples requests from specific user IDs.\n * Useful for debugging specific user issues or monitoring VIP users.\n *\n * @example\n * ```typescript\n * new UserIdSampler({\n * baselineSampleRate: 0.01, // 1% of normal users\n * alwaysSampleUsers: ['vip_123'], // Always trace VIP users\n * extractUserId: (args) => args[0]?.userId // Extract user ID from first arg\n * })\n * ```\n */\nexport class UserIdSampler implements Sampler {\n private baselineSampleRate: number;\n private alwaysSampleUsers: Set<string>;\n private extractUserId: (args: unknown[]) => string | undefined;\n private logger?: Logger;\n\n constructor(options: {\n baselineSampleRate?: number;\n alwaysSampleUsers?: string[];\n extractUserId: (args: unknown[]) => string | undefined;\n logger?: Logger;\n }) {\n this.baselineSampleRate = options.baselineSampleRate ?? 0.1;\n this.alwaysSampleUsers = new Set(options.alwaysSampleUsers || []);\n this.extractUserId = options.extractUserId;\n this.logger = options.logger;\n }\n\n shouldSample(context: SamplingContext): boolean {\n const userId = this.extractUserId(context.args);\n\n // Always sample specific users\n if (userId && this.alwaysSampleUsers.has(userId)) {\n this.logger?.debug(\n {\n operation: context.operationName,\n userId,\n },\n 'Sampling user request',\n );\n return true;\n }\n\n // For consistent per-user sampling, hash the user ID\n if (userId) {\n const hash = this.hashString(userId);\n return hash < this.baselineSampleRate;\n }\n\n // Fallback to random sampling if no user ID\n return Math.random() < this.baselineSampleRate;\n }\n\n /**\n * Add user IDs to always-sample list\n */\n addAlwaysSampleUsers(...userIds: string[]): void {\n for (const userId of userIds) {\n this.alwaysSampleUsers.add(userId);\n }\n }\n\n /**\n * Remove user IDs from always-sample list\n */\n removeAlwaysSampleUsers(...userIds: string[]): void {\n for (const userId of userIds) {\n this.alwaysSampleUsers.delete(userId);\n }\n }\n\n /**\n * Simple hash function for consistent user sampling\n */\n private hashString(str: string): number {\n let hash = 0;\n for (let i = 0; i < str.length; i++) {\n const char = str.codePointAt(i) ?? 0;\n hash = (hash << 5) - hash + char;\n hash = hash & hash; // Convert to 32-bit integer\n }\n return Math.abs(hash) / 2_147_483_647; // Normalize to 0-1\n }\n}\n\n/**\n * Composite sampler that combines multiple samplers\n *\n * Samples if ANY of the child samplers returns true.\n *\n * @example\n * ```typescript\n * new CompositeSampler([\n * new UserIdSampler({ extractUserId: (args) => args[0]?.userId }),\n * new AdaptiveSampler({ baselineSampleRate: 0.1 })\n * ])\n * ```\n */\nexport class CompositeSampler implements Sampler {\n constructor(private readonly samplers: Sampler[]) {\n if (samplers.length === 0) {\n throw new Error('CompositeSampler requires at least one child sampler');\n }\n }\n\n shouldSample(context: SamplingContext): boolean {\n return this.samplers.some((sampler) => sampler.shouldSample(context));\n }\n}\n\n/**\n * Feature flag sampler\n *\n * Always samples requests with specific feature flags enabled.\n * Perfect for correlating A/B test experiments with metrics.\n *\n * @example\n * ```typescript\n * new FeatureFlagSampler({\n * baselineSampleRate: 0.01,\n * alwaysSampleFlags: ['new_checkout', 'experimental_ui'],\n * extractFlags: (args, metadata) => metadata?.featureFlags\n * })\n * ```\n */\nexport class FeatureFlagSampler implements Sampler {\n private baselineSampleRate: number;\n private alwaysSampleFlags: Set<string>;\n private extractFlags: (\n args: unknown[],\n metadata?: Record<string, unknown>,\n ) => string[] | undefined;\n private logger?: Logger;\n\n constructor(options: {\n baselineSampleRate?: number;\n alwaysSampleFlags?: string[];\n extractFlags: (\n args: unknown[],\n metadata?: Record<string, unknown>,\n ) => string[] | undefined;\n logger?: Logger;\n }) {\n this.baselineSampleRate = options.baselineSampleRate ?? 0.1;\n this.alwaysSampleFlags = new Set(options.alwaysSampleFlags || []);\n this.extractFlags = options.extractFlags;\n this.logger = options.logger;\n }\n\n shouldSample(context: SamplingContext): boolean {\n const flags = this.extractFlags(context.args, context.metadata);\n\n // Always sample if any monitored flag is enabled\n if (flags && flags.some((flag) => this.alwaysSampleFlags.has(flag))) {\n this.logger?.debug(\n {\n operation: context.operationName,\n flags,\n },\n 'Sampling feature flag request',\n );\n return true;\n }\n\n // Fallback to random sampling\n return Math.random() < this.baselineSampleRate;\n }\n\n /**\n * Add feature flags to always-sample list\n */\n addAlwaysSampleFlags(...flags: string[]): void {\n for (const flag of flags) {\n this.alwaysSampleFlags.add(flag);\n }\n }\n\n /**\n * Remove feature flags from always-sample list\n */\n removeAlwaysSampleFlags(...flags: string[]): void {\n for (const flag of flags) {\n this.alwaysSampleFlags.delete(flag);\n }\n }\n}\n\n// ============================================================================\n// Link Helper Functions\n// ============================================================================\n\n/**\n * Create a Link from W3C trace context headers (e.g., from a message queue).\n *\n * This is useful for message consumers that need to link to the producer span.\n * The headers should contain at least a `traceparent` header in W3C format.\n *\n * @param headers - Dictionary containing traceparent/tracestate headers\n * @param attributes - Optional attributes for the link\n * @returns Link object if context could be extracted, null otherwise\n *\n * @example\n * ```typescript\n * // In a Kafka consumer\n * const headers = { traceparent: '00-abc123...-def456...-01' };\n * const link = createLinkFromHeaders(headers);\n * if (link) {\n * // Use with tracer.startActiveSpan options or ctx.addLink()\n * tracer.startActiveSpan('process.message', { links: [link] }, span => { ... });\n * }\n * ```\n */\nexport function createLinkFromHeaders(\n headers: Record<string, string>,\n attributes?: Attributes,\n): Link | null {\n // Parse W3C traceparent header directly for reliability\n // Format: version-traceId-spanId-traceFlags (e.g., 00-abc123...-def456...-01)\n const traceparent = headers.traceparent || headers['traceparent'];\n if (!traceparent) {\n return null;\n }\n\n const spanContext = parseTraceparent(traceparent);\n if (!spanContext || !isValidSpanContext(spanContext)) {\n return null;\n }\n\n return {\n context: spanContext,\n attributes: attributes ?? {},\n };\n}\n\n/**\n * Extract Links from a batch of messages for fan-in scenarios.\n *\n * Useful for batch processing where multiple producer spans should be linked.\n * This enables tracing causality in event-driven architectures where a single\n * consumer processes messages from multiple producers.\n *\n * @param messages - List of message objects\n * @param headersKey - Key in each message containing trace headers (default: 'headers')\n * @returns List of Link objects for all valid trace contexts\n *\n * @example\n * ```typescript\n * // Processing a batch of SQS/Kafka messages\n * const messages = [\n * { body: '...', headers: { traceparent: '...' } },\n * { body: '...', headers: { traceparent: '...' } },\n * ];\n * const links = extractLinksFromBatch(messages);\n *\n * tracer.startActiveSpan('process.batch', { links }, span => {\n * for (const msg of messages) {\n * processMessage(msg);\n * }\n * });\n * ```\n */\nexport function extractLinksFromBatch(\n messages: Array<{ [key: string]: unknown }>,\n headersKey: string = 'headers',\n): Link[] {\n const links: Link[] = [];\n\n for (const msg of messages) {\n const msgHeaders = msg[headersKey];\n if (msgHeaders && typeof msgHeaders === 'object' && msgHeaders !== null) {\n const link = createLinkFromHeaders(msgHeaders as Record<string, string>, {\n 'messaging.batch.message_index': links.length,\n });\n if (link) {\n links.push(link);\n }\n }\n }\n\n return links;\n}\n\n/**\n * Parse W3C traceparent header into SpanContext\n * Format: version-traceId-spanId-traceFlags (e.g., 00-abc123...-def456...-01)\n *\n * @see https://www.w3.org/TR/trace-context/#traceparent-header\n */\nfunction parseTraceparent(\n traceparent: string,\n): import('@opentelemetry/api').SpanContext | null {\n // W3C traceparent format: version-traceId-parentId-traceFlags\n // Example: 00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01\n const TRACEPARENT_REGEX =\n /^([0-9a-f]{2})-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})$/i;\n\n const match = traceparent.match(TRACEPARENT_REGEX);\n if (!match || match.length < 5) {\n return null;\n }\n\n const version = match[1];\n const traceId = match[2];\n const spanId = match[3];\n const flags = match[4];\n\n // Validate all parts are present (TypeScript narrowing)\n if (!version || !traceId || !spanId || !flags) {\n return null;\n }\n\n // Version 00 is currently the only version, but we should be forward compatible\n if (version === 'ff') {\n // Version ff is invalid according to spec\n return null;\n }\n\n return {\n traceId,\n spanId,\n traceFlags: Number.parseInt(flags, 16),\n isRemote: true,\n };\n}\n\n/**\n * Check if a SpanContext is valid (has non-zero trace and span IDs)\n */\nfunction isValidSpanContext(\n spanContext: import('@opentelemetry/api').SpanContext | null,\n): spanContext is import('@opentelemetry/api').SpanContext {\n if (!spanContext) return false;\n // TraceId should not be all zeros (00000000000000000000000000000000)\n // SpanId should not be all zeros (0000000000000000)\n return (\n spanContext.traceId !== '00000000000000000000000000000000' &&\n spanContext.spanId !== '0000000000000000'\n );\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/tail-sampling-processor.ts"],"names":[],"mappings":";;;AAqBO,IAAM,4BAAN,MAAyD;AAAA,EACtD,gBAAA;AAAA,EAER,YAAY,gBAAA,EAAiC;AAC3C,IAAA,IAAA,CAAK,gBAAA,GAAmB,gBAAA;AAAA,EAC1B;AAAA,EAEA,OAAA,CAAQ,MAAY,aAAA,EAA8B;AAChD,IAAA,IAAA,CAAK,gBAAA,CAAiB,OAAA,CAAQ,IAAA,EAAM,aAAa,CAAA;AAAA,EACnD;AAAA,EAEA,MAAM,IAAA,EAA0B;AAC9B,IAAA,MAAM,aAAA,GAAgB,IAAA,CAAK,UAAA,CAAW,yBAAyB,CAAA;AAC/D,IAAA,MAAM,UAAA,GAAa,IAAA,CAAK,UAAA,CAAW,oBAAoB,CAAA;AAEvD,IAAA,IAAI,aAAA,KAAkB,IAAA,IAAQ,UAAA,KAAe,KAAA,EAAO;AAClD,MAAA;AAAA,IACF;AAEA,IAAA,IAAA,CAAK,gBAAA,CAAiB,MAAM,IAAI,CAAA;AAAA,EAClC;AAAA,EAEA,UAAA,GAA4B;AAC1B,IAAA,OAAO,IAAA,CAAK,iBAAiB,UAAA,EAAW;AAAA,EAC1C;AAAA,EAEA,QAAA,GAA0B;AACxB,IAAA,OAAO,IAAA,CAAK,iBAAiB,QAAA,EAAS;AAAA,EACxC;AACF","file":"chunk-GVLK7YUU.cjs","sourcesContent":["/**\n * Tail Sampling Span Processor\n *\n * Filters spans based on the `sampling.tail.keep` attribute set during execution.\n * This enables adaptive sampling where we decide whether to keep a span AFTER\n * the operation completes, based on criteria like errors, duration, etc.\n *\n * How it works:\n * 1. Decorator creates span optimistically (head sampling returns true)\n * 2. Operation executes and completes\n * 3. Decorator calls shouldKeepTrace() and sets sampling.tail.keep attribute\n * 4. This processor checks the attribute and drops spans marked as false\n */\n\nimport type {\n SpanProcessor,\n ReadableSpan,\n} from '@opentelemetry/sdk-trace-base';\nimport type { Context } from '@opentelemetry/api';\nimport type { Span } from '@opentelemetry/sdk-trace-base';\n\nexport class TailSamplingSpanProcessor implements SpanProcessor {\n private wrappedProcessor: SpanProcessor;\n\n constructor(wrappedProcessor: SpanProcessor) {\n this.wrappedProcessor = wrappedProcessor;\n }\n\n onStart(span: Span, parentContext: Context): void {\n this.wrappedProcessor.onStart(span, parentContext);\n }\n\n onEnd(span: ReadableSpan): void {\n const tailEvaluated = span.attributes['sampling.tail.evaluated'];\n const shouldKeep = span.attributes['sampling.tail.keep'];\n\n if (tailEvaluated === true && shouldKeep === false) {\n return;\n }\n\n this.wrappedProcessor.onEnd(span);\n }\n\n forceFlush(): Promise<void> {\n return this.wrappedProcessor.forceFlush();\n }\n\n shutdown(): Promise<void> {\n return this.wrappedProcessor.shutdown();\n }\n}\n"]}
|