autotel 4.0.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.
- package/README.md +26 -1
- package/dist/auto.cjs +2 -2
- package/dist/auto.js +1 -1
- package/dist/correlation-id.cjs +1 -1
- package/dist/correlation-id.js +1 -1
- package/dist/decorators.cjs +1 -1
- package/dist/decorators.js +1 -1
- package/dist/{event-Dlqr4ZNL.cjs → event-BhHREDJk.cjs} +3 -3
- package/dist/{event-Dlqr4ZNL.cjs.map → event-BhHREDJk.cjs.map} +1 -1
- package/dist/{event-_58ryBjh.js → event-ByBTV9M2.js} +3 -3
- package/dist/{event-_58ryBjh.js.map → event-ByBTV9M2.js.map} +1 -1
- package/dist/event.cjs +1 -1
- package/dist/event.js +1 -1
- package/dist/{functional-BGkT8J-h.js → functional-DtI0u4vx.js} +19 -19
- package/dist/functional-DtI0u4vx.js.map +1 -0
- package/dist/{functional-C4CzoVrX.cjs → functional-zpzNLhky.cjs} +4 -4
- package/dist/{functional-C4CzoVrX.cjs.map → functional-zpzNLhky.cjs.map} +1 -1
- package/dist/functional.cjs +1 -1
- package/dist/functional.js +1 -1
- package/dist/http.cjs +1 -1
- package/dist/http.js +1 -1
- package/dist/index.cjs +5 -5
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +5 -5
- package/dist/{init-DJQOdVlN.d.ts → init-B7u-DjxM.d.ts} +57 -2
- package/dist/init-B7u-DjxM.d.ts.map +1 -0
- package/dist/{init-DvapOXCc.cjs → init-BX7AmFRl.cjs} +40 -21
- package/dist/init-BX7AmFRl.cjs.map +1 -0
- package/dist/{init-Ch6t7MNI.js → init-D-jnNMix.js} +39 -20
- package/dist/init-D-jnNMix.js.map +1 -0
- package/dist/{init-CNp-ee80.d.cts → init-DSrRmVnz.d.cts} +57 -2
- package/dist/init-DSrRmVnz.d.cts.map +1 -0
- package/dist/instrumentation.cjs +1 -1
- package/dist/instrumentation.js +1 -1
- package/dist/logger-D3Ej3DII.js +446 -0
- package/dist/logger-D3Ej3DII.js.map +1 -0
- package/dist/logger-thMPLpOG.cjs +487 -0
- package/dist/logger-thMPLpOG.cjs.map +1 -0
- package/dist/logger.cjs +8 -236
- package/dist/logger.js +2 -204
- package/dist/messaging.cjs +1 -1
- package/dist/messaging.js +1 -1
- package/dist/semantic-helpers.cjs +1 -1
- package/dist/semantic-helpers.js +1 -1
- package/dist/{track-3HY4NGV-.cjs → track-D59FfpL0.cjs} +2 -2
- package/dist/{track-3HY4NGV-.cjs.map → track-D59FfpL0.cjs.map} +1 -1
- package/dist/{track-nsKVy-pj.js → track-wc0HafS_.js} +6 -6
- package/dist/track-wc0HafS_.js.map +1 -0
- package/dist/webhook.cjs +1 -1
- package/dist/webhook.js +1 -1
- package/dist/workflow-distributed.cjs +1 -1
- package/dist/workflow-distributed.js +1 -1
- package/dist/workflow.cjs +1 -1
- package/dist/workflow.js +1 -1
- package/dist/{yaml-config-B3dQ82GR.cjs → yaml-config-Ck2uB0Dp.cjs} +2 -1
- package/dist/yaml-config-Ck2uB0Dp.cjs.map +1 -0
- package/dist/yaml-config.cjs +1 -1
- package/dist/yaml-config.d.cts +7 -1
- package/dist/yaml-config.d.cts.map +1 -1
- package/dist/yaml-config.d.ts +7 -1
- package/dist/yaml-config.d.ts.map +1 -1
- package/dist/yaml-config.js +1 -0
- package/dist/yaml-config.js.map +1 -1
- package/package.json +1 -2
- package/skills/autotel-core/SKILL.md +2 -0
- package/skills/autotel-instrumentation/SKILL.md +25 -0
- package/skills/debug-missing-spans/SKILL.md +3 -1
- package/skills/migrate-to-autotel/SKILL.md +24 -23
- package/skills/review-otel-patterns/SKILL.md +5 -4
- package/dist/functional-BGkT8J-h.js.map +0 -1
- package/dist/init-CNp-ee80.d.cts.map +0 -1
- package/dist/init-Ch6t7MNI.js.map +0 -1
- package/dist/init-DJQOdVlN.d.ts.map +0 -1
- package/dist/init-DvapOXCc.cjs.map +0 -1
- package/dist/logger.cjs.map +0 -1
- package/dist/logger.js.map +0 -1
- package/dist/track-nsKVy-pj.js.map +0 -1
- package/dist/yaml-config-B3dQ82GR.cjs.map +0 -1
- package/src/attribute-redacting-processor.test.ts +0 -763
- package/src/attribute-redacting-processor.ts +0 -621
- package/src/attributes/attachers.ts +0 -161
- package/src/attributes/builders.ts +0 -529
- package/src/attributes/domains.ts +0 -42
- package/src/attributes/index.ts +0 -81
- package/src/attributes/registry.ts +0 -323
- package/src/attributes/types.ts +0 -211
- package/src/attributes/utils.ts +0 -64
- package/src/attributes/validators.ts +0 -266
- package/src/attributes.test.ts +0 -292
- package/src/auto.ts +0 -67
- package/src/autotel-logger.test.ts +0 -548
- package/src/autotel-logger.ts +0 -364
- package/src/baggage-span-processor.test.ts +0 -202
- package/src/baggage-span-processor.ts +0 -100
- package/src/business-baggage.test.ts +0 -500
- package/src/business-baggage.ts +0 -669
- package/src/circuit-breaker.test.ts +0 -341
- package/src/circuit-breaker.ts +0 -184
- package/src/config.test.ts +0 -94
- package/src/config.ts +0 -172
- package/src/correlated-events.test.ts +0 -151
- package/src/correlated-events.ts +0 -47
- package/src/correlation-id.test.ts +0 -163
- package/src/correlation-id.ts +0 -206
- package/src/db.test.ts +0 -252
- package/src/db.ts +0 -447
- package/src/decorators.test.ts +0 -153
- package/src/decorators.ts +0 -188
- package/src/define-event.test.ts +0 -41
- package/src/define-event.ts +0 -58
- package/src/devtools.ts +0 -60
- package/src/drain-pipeline.test.ts +0 -68
- package/src/drain-pipeline.ts +0 -199
- package/src/drain-toolkit.test.ts +0 -113
- package/src/drain-toolkit.ts +0 -129
- package/src/enricher-toolkit.test.ts +0 -67
- package/src/enricher-toolkit.ts +0 -79
- package/src/enrichers.test.ts +0 -150
- package/src/enrichers.ts +0 -145
- package/src/env-config.test.ts +0 -323
- package/src/env-config.ts +0 -309
- package/src/error-catalog.test.ts +0 -133
- package/src/error-catalog.ts +0 -262
- package/src/event-queue.test.ts +0 -864
- package/src/event-queue.ts +0 -699
- package/src/event-subscriber.ts +0 -262
- package/src/event-testing.ts +0 -197
- package/src/event.test.ts +0 -1104
- package/src/event.ts +0 -988
- package/src/events-config.ts +0 -235
- package/src/exporters.ts +0 -165
- package/src/filtering-span-processor.test.ts +0 -281
- package/src/filtering-span-processor.ts +0 -111
- package/src/flatten-attributes.test.ts +0 -76
- package/src/flatten-attributes.ts +0 -80
- package/src/functional.strict-types.typecheck.ts +0 -53
- package/src/functional.test.ts +0 -1464
- package/src/functional.ts +0 -2539
- package/src/functional.types.test.ts +0 -135
- package/src/hook.mjs +0 -15
- package/src/http.test.ts +0 -485
- package/src/http.ts +0 -424
- package/src/index.ts +0 -433
- package/src/init-auto-redactor.test.ts +0 -53
- package/src/init-redactor.test.ts +0 -8
- package/src/init.customization.test.ts +0 -594
- package/src/init.integrations.test.ts +0 -399
- package/src/init.openllmetry.test.ts +0 -194
- package/src/init.protocol.test.ts +0 -215
- package/src/init.ts +0 -2312
- package/src/instrumentation.test.ts +0 -108
- package/src/instrumentation.ts +0 -319
- package/src/logger.test.ts +0 -125
- package/src/logger.ts +0 -341
- package/src/messaging-adapters.test.ts +0 -595
- package/src/messaging-adapters.ts +0 -583
- package/src/messaging-testing.test.ts +0 -573
- package/src/messaging-testing.ts +0 -935
- package/src/messaging.test.ts +0 -1646
- package/src/messaging.ts +0 -2245
- package/src/metric-helpers.ts +0 -47
- package/src/metric-testing.ts +0 -197
- package/src/metric.ts +0 -446
- package/src/metrics.test.ts +0 -241
- package/src/node-require.ts +0 -123
- package/src/operation-context.ts +0 -93
- package/src/parse-error.test.ts +0 -73
- package/src/parse-error.ts +0 -112
- package/src/posthog-logs.test.ts +0 -115
- package/src/posthog-logs.ts +0 -77
- package/src/pretty-console-exporter.test.ts +0 -545
- package/src/pretty-console-exporter.ts +0 -413
- package/src/pretty-log-formatter.test.ts +0 -123
- package/src/pretty-log-formatter.ts +0 -210
- package/src/processors/canonical-log-line-processor.test.ts +0 -523
- package/src/processors/canonical-log-line-processor.ts +0 -396
- package/src/processors.ts +0 -152
- package/src/rate-limiter.test.ts +0 -199
- package/src/rate-limiter.ts +0 -98
- package/src/redact-values.test.ts +0 -90
- package/src/redact-values.ts +0 -34
- package/src/register.ts +0 -37
- package/src/request-logger.test.ts +0 -545
- package/src/request-logger.ts +0 -342
- package/src/sampling.test.ts +0 -1060
- package/src/sampling.ts +0 -737
- package/src/security-schema.test.ts +0 -45
- package/src/security-schema.ts +0 -107
- package/src/semantic-conventions.ts +0 -15
- package/src/semantic-helpers.test.ts +0 -226
- package/src/semantic-helpers.ts +0 -438
- package/src/shutdown.test.ts +0 -364
- package/src/shutdown.ts +0 -246
- package/src/span-name-normalizer.test.ts +0 -377
- package/src/span-name-normalizer.ts +0 -213
- package/src/stable-hash.ts +0 -27
- package/src/structured-error.test.ts +0 -191
- package/src/structured-error.ts +0 -157
- package/src/stub.integration.test.ts +0 -361
- package/src/tail-sampling-processor.test.ts +0 -230
- package/src/tail-sampling-processor.ts +0 -55
- package/src/test-span-collector.test.ts +0 -234
- package/src/test-span-collector.ts +0 -150
- package/src/testing.ts +0 -705
- package/src/trace-context.test.ts +0 -73
- package/src/trace-context.ts +0 -567
- package/src/trace-helpers.new.test.ts +0 -278
- package/src/trace-helpers.test.ts +0 -290
- package/src/trace-helpers.ts +0 -710
- package/src/trace-hybrid.test.ts +0 -42
- package/src/trace-hybrid.ts +0 -37
- package/src/tracer-provider.test.ts +0 -183
- package/src/tracer-provider.ts +0 -266
- package/src/track.test.ts +0 -154
- package/src/track.ts +0 -216
- package/src/validate.test.ts +0 -287
- package/src/validate.ts +0 -307
- package/src/validation-attributes.ts +0 -43
- package/src/validation.test.ts +0 -330
- package/src/validation.ts +0 -246
- package/src/variable-name-inference.test.ts +0 -178
- package/src/variable-name-inference.ts +0 -242
- package/src/webhook.test.ts +0 -649
- package/src/webhook.ts +0 -637
- package/src/workflow-distributed.test.ts +0 -786
- package/src/workflow-distributed.ts +0 -916
- package/src/workflow.async-safety.integration.test.ts +0 -345
- package/src/workflow.test.ts +0 -647
- package/src/workflow.ts +0 -810
- package/src/yaml-config.test.ts +0 -337
- package/src/yaml-config.ts +0 -342
package/src/yaml-config.test.ts
DELETED
|
@@ -1,337 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
-
import {
|
|
3
|
-
loadYamlConfigFromFile,
|
|
4
|
-
loadYamlConfig,
|
|
5
|
-
hasYamlConfig,
|
|
6
|
-
} from './yaml-config';
|
|
7
|
-
import { writeFileSync, mkdirSync, rmSync } from 'node:fs';
|
|
8
|
-
import path from 'node:path';
|
|
9
|
-
import { tmpdir } from 'node:os';
|
|
10
|
-
import { AdaptiveSampler } from './sampling';
|
|
11
|
-
|
|
12
|
-
describe('yaml-config', () => {
|
|
13
|
-
const testDir = path.join(tmpdir(), `autotel-yaml-test-${Date.now()}`);
|
|
14
|
-
|
|
15
|
-
beforeEach(() => {
|
|
16
|
-
mkdirSync(testDir, { recursive: true });
|
|
17
|
-
// Reset environment variables
|
|
18
|
-
delete process.env.AUTOTEL_CONFIG_FILE;
|
|
19
|
-
delete process.env.TEST_SERVICE_NAME;
|
|
20
|
-
delete process.env.TEST_ENDPOINT;
|
|
21
|
-
delete process.env.UNDEFINED_VAR;
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
afterEach(() => {
|
|
25
|
-
// Cleanup test directory
|
|
26
|
-
try {
|
|
27
|
-
rmSync(testDir, { recursive: true, force: true });
|
|
28
|
-
} catch {
|
|
29
|
-
// Ignore cleanup errors
|
|
30
|
-
}
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
describe('loadYamlConfigFromFile', () => {
|
|
34
|
-
it('should parse basic YAML config', () => {
|
|
35
|
-
const yaml = `
|
|
36
|
-
service:
|
|
37
|
-
name: test-service
|
|
38
|
-
version: 1.0.0
|
|
39
|
-
environment: production
|
|
40
|
-
debug: true
|
|
41
|
-
`;
|
|
42
|
-
const filePath = path.join(testDir, 'test.yaml');
|
|
43
|
-
writeFileSync(filePath, yaml);
|
|
44
|
-
|
|
45
|
-
const config = loadYamlConfigFromFile(filePath);
|
|
46
|
-
expect(config.service).toBe('test-service');
|
|
47
|
-
expect(config.version).toBe('1.0.0');
|
|
48
|
-
expect(config.environment).toBe('production');
|
|
49
|
-
expect(config.debug).toBe(true);
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
it('should parse exporter configuration', () => {
|
|
53
|
-
const yaml = `
|
|
54
|
-
exporter:
|
|
55
|
-
endpoint: http://localhost:4318
|
|
56
|
-
protocol: grpc
|
|
57
|
-
headers:
|
|
58
|
-
x-api-key: secret-key
|
|
59
|
-
x-custom: value
|
|
60
|
-
`;
|
|
61
|
-
const filePath = path.join(testDir, 'exporter.yaml');
|
|
62
|
-
writeFileSync(filePath, yaml);
|
|
63
|
-
|
|
64
|
-
const config = loadYamlConfigFromFile(filePath);
|
|
65
|
-
expect(config.endpoint).toBe('http://localhost:4318');
|
|
66
|
-
expect(config.protocol).toBe('grpc');
|
|
67
|
-
expect(config.headers).toEqual({
|
|
68
|
-
'x-api-key': 'secret-key',
|
|
69
|
-
'x-custom': 'value',
|
|
70
|
-
});
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
it('should parse resource attributes', () => {
|
|
74
|
-
const yaml = `
|
|
75
|
-
resource:
|
|
76
|
-
deployment.environment: production
|
|
77
|
-
team: backend
|
|
78
|
-
version: 2.0.0
|
|
79
|
-
`;
|
|
80
|
-
const filePath = path.join(testDir, 'resource.yaml');
|
|
81
|
-
writeFileSync(filePath, yaml);
|
|
82
|
-
|
|
83
|
-
const config = loadYamlConfigFromFile(filePath);
|
|
84
|
-
expect(config.resourceAttributes).toEqual({
|
|
85
|
-
'deployment.environment': 'production',
|
|
86
|
-
team: 'backend',
|
|
87
|
-
version: '2.0.0',
|
|
88
|
-
});
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
it('should parse autoInstrumentations as array', () => {
|
|
92
|
-
const yaml = `
|
|
93
|
-
autoInstrumentations:
|
|
94
|
-
- express
|
|
95
|
-
- http
|
|
96
|
-
- pino
|
|
97
|
-
`;
|
|
98
|
-
const filePath = path.join(testDir, 'autoInstrumentations.yaml');
|
|
99
|
-
writeFileSync(filePath, yaml);
|
|
100
|
-
|
|
101
|
-
const config = loadYamlConfigFromFile(filePath);
|
|
102
|
-
expect(config.autoInstrumentations).toEqual(['express', 'http', 'pino']);
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
it('should parse autoInstrumentations as object', () => {
|
|
106
|
-
const yaml = `
|
|
107
|
-
autoInstrumentations:
|
|
108
|
-
express:
|
|
109
|
-
enabled: true
|
|
110
|
-
http:
|
|
111
|
-
enabled: false
|
|
112
|
-
`;
|
|
113
|
-
const filePath = path.join(testDir, 'autoInstrumentations-obj.yaml');
|
|
114
|
-
writeFileSync(filePath, yaml);
|
|
115
|
-
|
|
116
|
-
const config = loadYamlConfigFromFile(filePath);
|
|
117
|
-
expect(config.autoInstrumentations).toEqual({
|
|
118
|
-
express: { enabled: true },
|
|
119
|
-
http: { enabled: false },
|
|
120
|
-
});
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
it('should substitute environment variables', () => {
|
|
124
|
-
process.env.TEST_SERVICE_NAME = 'from-env';
|
|
125
|
-
process.env.TEST_ENDPOINT = 'http://env-endpoint:4318';
|
|
126
|
-
|
|
127
|
-
const yaml = `
|
|
128
|
-
service:
|
|
129
|
-
name: \${env:TEST_SERVICE_NAME}
|
|
130
|
-
exporter:
|
|
131
|
-
endpoint: \${env:TEST_ENDPOINT}
|
|
132
|
-
`;
|
|
133
|
-
const filePath = path.join(testDir, 'env-test.yaml');
|
|
134
|
-
writeFileSync(filePath, yaml);
|
|
135
|
-
|
|
136
|
-
const config = loadYamlConfigFromFile(filePath);
|
|
137
|
-
expect(config.service).toBe('from-env');
|
|
138
|
-
expect(config.endpoint).toBe('http://env-endpoint:4318');
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
it('should use default value when env var not set', () => {
|
|
142
|
-
const yaml = `
|
|
143
|
-
service:
|
|
144
|
-
name: \${env:UNDEFINED_VAR:-default-service}
|
|
145
|
-
environment: \${env:NODE_ENV:-development}
|
|
146
|
-
`;
|
|
147
|
-
const filePath = path.join(testDir, 'default-test.yaml');
|
|
148
|
-
writeFileSync(filePath, yaml);
|
|
149
|
-
|
|
150
|
-
const config = loadYamlConfigFromFile(filePath);
|
|
151
|
-
expect(config.service).toBe('default-service');
|
|
152
|
-
// NODE_ENV might be set in test environment, so just check it's a string
|
|
153
|
-
expect(typeof config.environment).toBe('string');
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
it('should handle nested env var substitution', () => {
|
|
157
|
-
process.env.TEST_SERVICE_NAME = 'nested-service';
|
|
158
|
-
|
|
159
|
-
const yaml = `
|
|
160
|
-
service:
|
|
161
|
-
name: \${env:TEST_SERVICE_NAME}
|
|
162
|
-
exporter:
|
|
163
|
-
headers:
|
|
164
|
-
x-api-key: \${env:TEST_API_KEY:-fallback-key}
|
|
165
|
-
`;
|
|
166
|
-
const filePath = path.join(testDir, 'nested-env.yaml');
|
|
167
|
-
writeFileSync(filePath, yaml);
|
|
168
|
-
|
|
169
|
-
const config = loadYamlConfigFromFile(filePath);
|
|
170
|
-
expect(config.service).toBe('nested-service');
|
|
171
|
-
expect(config.headers).toEqual({
|
|
172
|
-
'x-api-key': 'fallback-key',
|
|
173
|
-
});
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
it('should warn on missing env var without default', () => {
|
|
177
|
-
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
178
|
-
|
|
179
|
-
const yaml = `
|
|
180
|
-
service:
|
|
181
|
-
name: \${env:DEFINITELY_NOT_SET}
|
|
182
|
-
`;
|
|
183
|
-
const filePath = path.join(testDir, 'missing-env.yaml');
|
|
184
|
-
writeFileSync(filePath, yaml);
|
|
185
|
-
|
|
186
|
-
const config = loadYamlConfigFromFile(filePath);
|
|
187
|
-
// Empty string from missing env var results in undefined (filtered out as falsy)
|
|
188
|
-
expect(config.service).toBeUndefined();
|
|
189
|
-
expect(warnSpy).toHaveBeenCalledWith(
|
|
190
|
-
expect.stringContaining('DEFINITELY_NOT_SET'),
|
|
191
|
-
);
|
|
192
|
-
|
|
193
|
-
warnSpy.mockRestore();
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
it('should throw on invalid YAML', () => {
|
|
197
|
-
const yaml = `
|
|
198
|
-
service:
|
|
199
|
-
name: test
|
|
200
|
-
invalid yaml: [unclosed
|
|
201
|
-
`;
|
|
202
|
-
const filePath = path.join(testDir, 'invalid.yaml');
|
|
203
|
-
writeFileSync(filePath, yaml);
|
|
204
|
-
|
|
205
|
-
expect(() => loadYamlConfigFromFile(filePath)).toThrow();
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
it('should throw on non-existent file', () => {
|
|
209
|
-
expect(() => loadYamlConfigFromFile('/non/existent/path.yaml')).toThrow();
|
|
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
|
-
});
|
|
281
|
-
});
|
|
282
|
-
|
|
283
|
-
describe('loadYamlConfig', () => {
|
|
284
|
-
it('should return null when no config file exists', () => {
|
|
285
|
-
// No AUTOTEL_CONFIG_FILE set and no autotel.yaml in cwd
|
|
286
|
-
const result = loadYamlConfig();
|
|
287
|
-
// Result depends on whether autotel.yaml exists in cwd
|
|
288
|
-
// This test just verifies the function doesn't throw
|
|
289
|
-
expect(result === null || typeof result === 'object').toBe(true);
|
|
290
|
-
});
|
|
291
|
-
|
|
292
|
-
it('should load from AUTOTEL_CONFIG_FILE env var', () => {
|
|
293
|
-
const yaml = `
|
|
294
|
-
service:
|
|
295
|
-
name: from-env-path
|
|
296
|
-
`;
|
|
297
|
-
const filePath = path.join(testDir, 'env-config.yaml');
|
|
298
|
-
writeFileSync(filePath, yaml);
|
|
299
|
-
process.env.AUTOTEL_CONFIG_FILE = filePath;
|
|
300
|
-
|
|
301
|
-
const config = loadYamlConfig();
|
|
302
|
-
expect(config).not.toBeNull();
|
|
303
|
-
expect(config?.service).toBe('from-env-path');
|
|
304
|
-
});
|
|
305
|
-
|
|
306
|
-
it('should warn when AUTOTEL_CONFIG_FILE points to non-existent file', () => {
|
|
307
|
-
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
308
|
-
process.env.AUTOTEL_CONFIG_FILE = '/non/existent/file.yaml';
|
|
309
|
-
|
|
310
|
-
const config = loadYamlConfig();
|
|
311
|
-
expect(config).toBeNull();
|
|
312
|
-
expect(warnSpy).toHaveBeenCalledWith(
|
|
313
|
-
expect.stringContaining('Config file not found'),
|
|
314
|
-
);
|
|
315
|
-
|
|
316
|
-
warnSpy.mockRestore();
|
|
317
|
-
});
|
|
318
|
-
});
|
|
319
|
-
|
|
320
|
-
describe('hasYamlConfig', () => {
|
|
321
|
-
it('should return false when no config exists', () => {
|
|
322
|
-
// Delete any AUTOTEL_CONFIG_FILE
|
|
323
|
-
delete process.env.AUTOTEL_CONFIG_FILE;
|
|
324
|
-
// This test depends on cwd not having autotel.yaml
|
|
325
|
-
// Just verify it returns a boolean
|
|
326
|
-
expect(typeof hasYamlConfig()).toBe('boolean');
|
|
327
|
-
});
|
|
328
|
-
|
|
329
|
-
it('should return true when AUTOTEL_CONFIG_FILE exists', () => {
|
|
330
|
-
const filePath = path.join(testDir, 'has-config.yaml');
|
|
331
|
-
writeFileSync(filePath, 'service:\n name: test\n');
|
|
332
|
-
process.env.AUTOTEL_CONFIG_FILE = filePath;
|
|
333
|
-
|
|
334
|
-
expect(hasYamlConfig()).toBe(true);
|
|
335
|
-
});
|
|
336
|
-
});
|
|
337
|
-
});
|
package/src/yaml-config.ts
DELETED
|
@@ -1,342 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* YAML configuration loader for autotel
|
|
3
|
-
*
|
|
4
|
-
* Supports:
|
|
5
|
-
* - Auto-discovery of autotel.yaml in cwd
|
|
6
|
-
* - AUTOTEL_CONFIG_FILE env var override
|
|
7
|
-
* - Environment variable substitution: ${env:VAR} and ${env:VAR:-default}
|
|
8
|
-
*
|
|
9
|
-
* @example Auto-discovery
|
|
10
|
-
* ```yaml
|
|
11
|
-
* # autotel.yaml in project root
|
|
12
|
-
* service:
|
|
13
|
-
* name: my-service
|
|
14
|
-
* exporter:
|
|
15
|
-
* endpoint: ${env:OTEL_EXPORTER_OTLP_ENDPOINT:-http://localhost:4318}
|
|
16
|
-
* ```
|
|
17
|
-
*
|
|
18
|
-
* @example Explicit path
|
|
19
|
-
* ```bash
|
|
20
|
-
* AUTOTEL_CONFIG_FILE=./config/otel.yaml tsx --import autotel/auto src/index.ts
|
|
21
|
-
* ```
|
|
22
|
-
*/
|
|
23
|
-
|
|
24
|
-
import { readFileSync, existsSync } from 'node:fs';
|
|
25
|
-
import path from 'node:path';
|
|
26
|
-
import type { AutotelConfig } from './init';
|
|
27
|
-
import {
|
|
28
|
-
AdaptiveSampler,
|
|
29
|
-
AlwaysSampler,
|
|
30
|
-
NeverSampler,
|
|
31
|
-
RandomSampler,
|
|
32
|
-
type SamplingPreset,
|
|
33
|
-
} from './sampling';
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Lazy-load yaml parser (optional peer dependency)
|
|
37
|
-
* Only loads when a YAML config file is actually found
|
|
38
|
-
*/
|
|
39
|
-
import { requireModule } from './node-require';
|
|
40
|
-
|
|
41
|
-
function loadYamlParser(): (content: string) => unknown {
|
|
42
|
-
try {
|
|
43
|
-
const mod = requireModule<{ parse: (content: string) => unknown }>('yaml');
|
|
44
|
-
return mod.parse;
|
|
45
|
-
} catch {
|
|
46
|
-
throw new Error('YAML parser not found. Install with: pnpm add yaml');
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* YAML config structure
|
|
52
|
-
* Maps to AutotelConfig with user-friendly naming
|
|
53
|
-
*/
|
|
54
|
-
export interface YamlConfig {
|
|
55
|
-
service?: {
|
|
56
|
-
name?: string;
|
|
57
|
-
version?: string;
|
|
58
|
-
environment?: string;
|
|
59
|
-
};
|
|
60
|
-
exporter?: {
|
|
61
|
-
endpoint?: string;
|
|
62
|
-
protocol?: 'http' | 'grpc';
|
|
63
|
-
headers?: Record<string, string>;
|
|
64
|
-
};
|
|
65
|
-
resource?: Record<string, string | number | boolean>;
|
|
66
|
-
sampling?: {
|
|
67
|
-
preset?: SamplingPreset;
|
|
68
|
-
type?: 'adaptive' | 'always_on' | 'always_off' | 'ratio';
|
|
69
|
-
ratio?: number;
|
|
70
|
-
baseline_rate?: number;
|
|
71
|
-
always_sample_errors?: boolean;
|
|
72
|
-
always_sample_slow?: boolean;
|
|
73
|
-
slow_threshold_ms?: number;
|
|
74
|
-
};
|
|
75
|
-
autoInstrumentations?: string[] | Record<string, { enabled?: boolean }>;
|
|
76
|
-
debug?: boolean;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Environment variable substitution regex
|
|
81
|
-
* Matches ${env:VAR_NAME} and ${env:VAR_NAME:-default}
|
|
82
|
-
*/
|
|
83
|
-
const ENV_VAR_PATTERN = /\$\{env:([A-Za-z_][A-Za-z0-9_]*)(?::-([^}]*))?\}/g;
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Substitute ${env:VAR} and ${env:VAR:-default} in a string
|
|
87
|
-
*
|
|
88
|
-
* @param value - String potentially containing env var references
|
|
89
|
-
* @returns String with env vars substituted
|
|
90
|
-
*
|
|
91
|
-
* @example
|
|
92
|
-
* substituteEnvVars('${env:NODE_ENV:-development}')
|
|
93
|
-
* // Returns 'production' if NODE_ENV=production, else 'development'
|
|
94
|
-
*/
|
|
95
|
-
function substituteEnvVars(value: string): string {
|
|
96
|
-
return value.replaceAll(
|
|
97
|
-
ENV_VAR_PATTERN,
|
|
98
|
-
(_match, varName: string, defaultValue?: string) => {
|
|
99
|
-
const envValue = process.env[varName];
|
|
100
|
-
if (envValue !== undefined) return envValue;
|
|
101
|
-
if (defaultValue !== undefined) return defaultValue;
|
|
102
|
-
console.warn(
|
|
103
|
-
`[autotel] Environment variable ${varName} not set and no default provided`,
|
|
104
|
-
);
|
|
105
|
-
return '';
|
|
106
|
-
},
|
|
107
|
-
);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Recursively substitute env vars in an object
|
|
112
|
-
*
|
|
113
|
-
* @param obj - Object to process
|
|
114
|
-
* @returns Object with all string values having env vars substituted
|
|
115
|
-
*/
|
|
116
|
-
function substituteEnvVarsDeep(obj: unknown): unknown {
|
|
117
|
-
if (typeof obj === 'string') {
|
|
118
|
-
return substituteEnvVars(obj);
|
|
119
|
-
}
|
|
120
|
-
if (Array.isArray(obj)) {
|
|
121
|
-
return obj.map((item) => substituteEnvVarsDeep(item));
|
|
122
|
-
}
|
|
123
|
-
if (obj && typeof obj === 'object') {
|
|
124
|
-
const result: Record<string, unknown> = {};
|
|
125
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
126
|
-
result[key] = substituteEnvVarsDeep(value);
|
|
127
|
-
}
|
|
128
|
-
return result;
|
|
129
|
-
}
|
|
130
|
-
return obj;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* Find YAML config file path
|
|
135
|
-
*
|
|
136
|
-
* Priority:
|
|
137
|
-
* 1. AUTOTEL_CONFIG_FILE env var (explicit path)
|
|
138
|
-
* 2. autotel.yaml in cwd (convention)
|
|
139
|
-
* 3. autotel.yml in cwd (alternative extension)
|
|
140
|
-
*
|
|
141
|
-
* @returns File path if found, null otherwise
|
|
142
|
-
*/
|
|
143
|
-
function findConfigFile(): string | null {
|
|
144
|
-
// Check env var first (explicit takes priority)
|
|
145
|
-
const envPath = process.env.AUTOTEL_CONFIG_FILE;
|
|
146
|
-
if (envPath) {
|
|
147
|
-
const resolved = path.resolve(envPath);
|
|
148
|
-
if (existsSync(resolved)) return resolved;
|
|
149
|
-
console.warn(`[autotel] Config file not found: ${envPath}`);
|
|
150
|
-
return null;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// Auto-discover autotel.yaml in cwd
|
|
154
|
-
const conventionPath = path.resolve(process.cwd(), 'autotel.yaml');
|
|
155
|
-
if (existsSync(conventionPath)) return conventionPath;
|
|
156
|
-
|
|
157
|
-
// Also check .yml extension
|
|
158
|
-
const altPath = path.resolve(process.cwd(), 'autotel.yml');
|
|
159
|
-
if (existsSync(altPath)) return altPath;
|
|
160
|
-
|
|
161
|
-
return null;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* Convert YAML config structure to AutotelConfig
|
|
166
|
-
*
|
|
167
|
-
* @param yaml - Parsed and env-substituted YAML config
|
|
168
|
-
* @returns Partial AutotelConfig ready for merging
|
|
169
|
-
*/
|
|
170
|
-
function yamlToAutotelConfig(yaml: YamlConfig): Partial<AutotelConfig> {
|
|
171
|
-
const config: Partial<AutotelConfig> = {};
|
|
172
|
-
|
|
173
|
-
// Service configuration
|
|
174
|
-
if (yaml.service?.name) config.service = yaml.service.name;
|
|
175
|
-
if (yaml.service?.version) config.version = yaml.service.version;
|
|
176
|
-
if (yaml.service?.environment) config.environment = yaml.service.environment;
|
|
177
|
-
|
|
178
|
-
// Exporter configuration
|
|
179
|
-
if (yaml.exporter?.endpoint) config.endpoint = yaml.exporter.endpoint;
|
|
180
|
-
if (yaml.exporter?.protocol) config.protocol = yaml.exporter.protocol;
|
|
181
|
-
if (yaml.exporter?.headers) config.headers = yaml.exporter.headers;
|
|
182
|
-
|
|
183
|
-
// Resource attributes (flattened)
|
|
184
|
-
if (yaml.resource) config.resourceAttributes = yaml.resource;
|
|
185
|
-
|
|
186
|
-
// Integrations
|
|
187
|
-
if (yaml.autoInstrumentations)
|
|
188
|
-
config.autoInstrumentations = yaml.autoInstrumentations;
|
|
189
|
-
|
|
190
|
-
// Debug mode
|
|
191
|
-
if (yaml.debug !== undefined) config.debug = yaml.debug;
|
|
192
|
-
|
|
193
|
-
// Sampling configuration
|
|
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
|
-
}
|
|
201
|
-
|
|
202
|
-
return config;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
function createSamplerFromYaml(
|
|
206
|
-
sampling?: YamlConfig['sampling'],
|
|
207
|
-
): AutotelConfig['sampler'] {
|
|
208
|
-
if (!sampling) return undefined;
|
|
209
|
-
if (sampling.preset) return undefined;
|
|
210
|
-
|
|
211
|
-
const type = sampling.type ?? 'adaptive';
|
|
212
|
-
|
|
213
|
-
try {
|
|
214
|
-
switch (type) {
|
|
215
|
-
case 'adaptive': {
|
|
216
|
-
return new AdaptiveSampler({
|
|
217
|
-
baselineSampleRate: sampling.baseline_rate,
|
|
218
|
-
alwaysSampleErrors: sampling.always_sample_errors,
|
|
219
|
-
alwaysSampleSlow: sampling.always_sample_slow,
|
|
220
|
-
slowThresholdMs: sampling.slow_threshold_ms,
|
|
221
|
-
});
|
|
222
|
-
}
|
|
223
|
-
case 'always_on': {
|
|
224
|
-
return new AlwaysSampler();
|
|
225
|
-
}
|
|
226
|
-
case 'always_off': {
|
|
227
|
-
return new NeverSampler();
|
|
228
|
-
}
|
|
229
|
-
case 'ratio': {
|
|
230
|
-
if (sampling.ratio === undefined) {
|
|
231
|
-
console.warn(
|
|
232
|
-
'[autotel] sampling.ratio missing in YAML sampling config. Falling back to adaptive sampler.',
|
|
233
|
-
);
|
|
234
|
-
return new AdaptiveSampler();
|
|
235
|
-
}
|
|
236
|
-
return new RandomSampler(sampling.ratio);
|
|
237
|
-
}
|
|
238
|
-
default: {
|
|
239
|
-
console.warn(
|
|
240
|
-
`[autotel] Unknown sampling type "${type}" in YAML config. Falling back to defaults.`,
|
|
241
|
-
);
|
|
242
|
-
return undefined;
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
} catch (error) {
|
|
246
|
-
console.warn(
|
|
247
|
-
`[autotel] Failed to configure sampling from YAML: ${error instanceof Error ? error.message : String(error)}`,
|
|
248
|
-
);
|
|
249
|
-
return undefined;
|
|
250
|
-
}
|
|
251
|
-
}
|
|
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
|
-
|
|
275
|
-
/**
|
|
276
|
-
* Load and parse YAML config file (auto-discovery)
|
|
277
|
-
*
|
|
278
|
-
* Automatically finds and loads autotel.yaml or uses AUTOTEL_CONFIG_FILE.
|
|
279
|
-
* Returns null if no config file found (not an error - YAML config is optional).
|
|
280
|
-
*
|
|
281
|
-
* @returns Partial AutotelConfig or null if no config file found
|
|
282
|
-
*
|
|
283
|
-
* @example
|
|
284
|
-
* const yamlConfig = loadYamlConfig();
|
|
285
|
-
* if (yamlConfig) {
|
|
286
|
-
* init({ ...yamlConfig, debug: true });
|
|
287
|
-
* }
|
|
288
|
-
*/
|
|
289
|
-
export function loadYamlConfig(): Partial<AutotelConfig> | null {
|
|
290
|
-
const filePath = findConfigFile();
|
|
291
|
-
if (!filePath) return null;
|
|
292
|
-
|
|
293
|
-
try {
|
|
294
|
-
const content = readFileSync(filePath, 'utf8');
|
|
295
|
-
const parseYaml = loadYamlParser();
|
|
296
|
-
const rawYaml = parseYaml(content) as YamlConfig;
|
|
297
|
-
const substituted = substituteEnvVarsDeep(rawYaml) as YamlConfig;
|
|
298
|
-
return yamlToAutotelConfig(substituted);
|
|
299
|
-
} catch (error) {
|
|
300
|
-
console.error(
|
|
301
|
-
`[autotel] Failed to load YAML config from ${filePath}:`,
|
|
302
|
-
error,
|
|
303
|
-
);
|
|
304
|
-
return null;
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
/**
|
|
309
|
-
* Load YAML config from a specific file path
|
|
310
|
-
*
|
|
311
|
-
* Unlike loadYamlConfig(), this throws if the file cannot be read.
|
|
312
|
-
*
|
|
313
|
-
* @param filePath - Path to YAML config file
|
|
314
|
-
* @returns Partial AutotelConfig
|
|
315
|
-
* @throws Error if file cannot be read or parsed
|
|
316
|
-
*
|
|
317
|
-
* @example
|
|
318
|
-
* import { loadYamlConfigFromFile } from 'autotel/yaml';
|
|
319
|
-
* import { init } from 'autotel';
|
|
320
|
-
*
|
|
321
|
-
* const config = loadYamlConfigFromFile('./config/otel.yaml');
|
|
322
|
-
* init({ ...config, debug: true });
|
|
323
|
-
*/
|
|
324
|
-
export function loadYamlConfigFromFile(
|
|
325
|
-
filePath: string,
|
|
326
|
-
): Partial<AutotelConfig> {
|
|
327
|
-
const resolved = path.resolve(filePath);
|
|
328
|
-
const content = readFileSync(resolved, 'utf8');
|
|
329
|
-
const parseYaml = loadYamlParser();
|
|
330
|
-
const rawYaml = parseYaml(content) as YamlConfig;
|
|
331
|
-
const substituted = substituteEnvVarsDeep(rawYaml) as YamlConfig;
|
|
332
|
-
return yamlToAutotelConfig(substituted);
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
/**
|
|
336
|
-
* Check if a YAML config file exists (without loading it)
|
|
337
|
-
*
|
|
338
|
-
* @returns true if a config file would be found by loadYamlConfig()
|
|
339
|
-
*/
|
|
340
|
-
export function hasYamlConfig(): boolean {
|
|
341
|
-
return findConfigFile() !== null;
|
|
342
|
-
}
|