autotel 4.1.0 → 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (154) hide show
  1. package/package.json +1 -2
  2. package/src/attribute-redacting-processor.test.ts +0 -763
  3. package/src/attribute-redacting-processor.ts +0 -621
  4. package/src/attributes/attachers.ts +0 -161
  5. package/src/attributes/builders.ts +0 -529
  6. package/src/attributes/domains.ts +0 -42
  7. package/src/attributes/index.ts +0 -81
  8. package/src/attributes/registry.ts +0 -323
  9. package/src/attributes/types.ts +0 -211
  10. package/src/attributes/utils.ts +0 -64
  11. package/src/attributes/validators.ts +0 -266
  12. package/src/attributes.test.ts +0 -292
  13. package/src/auto.ts +0 -67
  14. package/src/autotel-logger.test.ts +0 -548
  15. package/src/autotel-logger.ts +0 -364
  16. package/src/baggage-span-processor.test.ts +0 -202
  17. package/src/baggage-span-processor.ts +0 -100
  18. package/src/business-baggage.test.ts +0 -500
  19. package/src/business-baggage.ts +0 -669
  20. package/src/circuit-breaker.test.ts +0 -341
  21. package/src/circuit-breaker.ts +0 -184
  22. package/src/config.test.ts +0 -94
  23. package/src/config.ts +0 -172
  24. package/src/correlated-events.test.ts +0 -151
  25. package/src/correlated-events.ts +0 -47
  26. package/src/correlation-id.test.ts +0 -163
  27. package/src/correlation-id.ts +0 -206
  28. package/src/db.test.ts +0 -252
  29. package/src/db.ts +0 -447
  30. package/src/decorators.test.ts +0 -153
  31. package/src/decorators.ts +0 -188
  32. package/src/define-event.test.ts +0 -41
  33. package/src/define-event.ts +0 -58
  34. package/src/devtools.ts +0 -60
  35. package/src/drain-pipeline.test.ts +0 -68
  36. package/src/drain-pipeline.ts +0 -199
  37. package/src/drain-toolkit.test.ts +0 -113
  38. package/src/drain-toolkit.ts +0 -129
  39. package/src/enricher-toolkit.test.ts +0 -67
  40. package/src/enricher-toolkit.ts +0 -79
  41. package/src/enrichers.test.ts +0 -150
  42. package/src/enrichers.ts +0 -145
  43. package/src/env-config.test.ts +0 -323
  44. package/src/env-config.ts +0 -309
  45. package/src/error-catalog.test.ts +0 -133
  46. package/src/error-catalog.ts +0 -262
  47. package/src/event-queue.test.ts +0 -864
  48. package/src/event-queue.ts +0 -699
  49. package/src/event-subscriber.ts +0 -262
  50. package/src/event-testing.ts +0 -197
  51. package/src/event.test.ts +0 -1104
  52. package/src/event.ts +0 -988
  53. package/src/events-config.ts +0 -235
  54. package/src/exporters.ts +0 -165
  55. package/src/filtering-span-processor.test.ts +0 -281
  56. package/src/filtering-span-processor.ts +0 -111
  57. package/src/flatten-attributes.test.ts +0 -76
  58. package/src/flatten-attributes.ts +0 -80
  59. package/src/functional.strict-types.typecheck.ts +0 -53
  60. package/src/functional.test.ts +0 -1464
  61. package/src/functional.ts +0 -2539
  62. package/src/functional.types.test.ts +0 -135
  63. package/src/hook.mjs +0 -15
  64. package/src/http.test.ts +0 -485
  65. package/src/http.ts +0 -424
  66. package/src/index.ts +0 -433
  67. package/src/init-auto-redactor.test.ts +0 -53
  68. package/src/init-redactor.test.ts +0 -8
  69. package/src/init.customization.test.ts +0 -665
  70. package/src/init.integrations.test.ts +0 -399
  71. package/src/init.openllmetry.test.ts +0 -194
  72. package/src/init.protocol.test.ts +0 -215
  73. package/src/init.ts +0 -2439
  74. package/src/instrumentation.test.ts +0 -108
  75. package/src/instrumentation.ts +0 -319
  76. package/src/logger.test.ts +0 -125
  77. package/src/logger.ts +0 -341
  78. package/src/messaging-adapters.test.ts +0 -595
  79. package/src/messaging-adapters.ts +0 -583
  80. package/src/messaging-testing.test.ts +0 -573
  81. package/src/messaging-testing.ts +0 -935
  82. package/src/messaging.test.ts +0 -1646
  83. package/src/messaging.ts +0 -2245
  84. package/src/metric-helpers.ts +0 -47
  85. package/src/metric-testing.ts +0 -197
  86. package/src/metric.ts +0 -446
  87. package/src/metrics.test.ts +0 -241
  88. package/src/node-require.ts +0 -123
  89. package/src/operation-context.ts +0 -93
  90. package/src/parse-error.test.ts +0 -73
  91. package/src/parse-error.ts +0 -112
  92. package/src/posthog-logs.test.ts +0 -115
  93. package/src/posthog-logs.ts +0 -77
  94. package/src/pretty-console-exporter.test.ts +0 -545
  95. package/src/pretty-console-exporter.ts +0 -413
  96. package/src/pretty-log-formatter.test.ts +0 -123
  97. package/src/pretty-log-formatter.ts +0 -210
  98. package/src/processors/canonical-log-line-processor.test.ts +0 -523
  99. package/src/processors/canonical-log-line-processor.ts +0 -396
  100. package/src/processors.ts +0 -152
  101. package/src/rate-limiter.test.ts +0 -199
  102. package/src/rate-limiter.ts +0 -98
  103. package/src/redact-values.test.ts +0 -90
  104. package/src/redact-values.ts +0 -34
  105. package/src/register.ts +0 -37
  106. package/src/request-logger.test.ts +0 -545
  107. package/src/request-logger.ts +0 -342
  108. package/src/sampling.test.ts +0 -1060
  109. package/src/sampling.ts +0 -737
  110. package/src/security-schema.test.ts +0 -45
  111. package/src/security-schema.ts +0 -107
  112. package/src/semantic-conventions.ts +0 -15
  113. package/src/semantic-helpers.test.ts +0 -226
  114. package/src/semantic-helpers.ts +0 -438
  115. package/src/shutdown.test.ts +0 -364
  116. package/src/shutdown.ts +0 -246
  117. package/src/span-name-normalizer.test.ts +0 -377
  118. package/src/span-name-normalizer.ts +0 -213
  119. package/src/stable-hash.ts +0 -27
  120. package/src/structured-error.test.ts +0 -191
  121. package/src/structured-error.ts +0 -157
  122. package/src/stub.integration.test.ts +0 -361
  123. package/src/tail-sampling-processor.test.ts +0 -230
  124. package/src/tail-sampling-processor.ts +0 -55
  125. package/src/test-span-collector.test.ts +0 -234
  126. package/src/test-span-collector.ts +0 -150
  127. package/src/testing.ts +0 -705
  128. package/src/trace-context.test.ts +0 -73
  129. package/src/trace-context.ts +0 -567
  130. package/src/trace-helpers.new.test.ts +0 -278
  131. package/src/trace-helpers.test.ts +0 -290
  132. package/src/trace-helpers.ts +0 -710
  133. package/src/trace-hybrid.test.ts +0 -42
  134. package/src/trace-hybrid.ts +0 -37
  135. package/src/tracer-provider.test.ts +0 -183
  136. package/src/tracer-provider.ts +0 -266
  137. package/src/track.test.ts +0 -154
  138. package/src/track.ts +0 -216
  139. package/src/validate.test.ts +0 -287
  140. package/src/validate.ts +0 -307
  141. package/src/validation-attributes.ts +0 -43
  142. package/src/validation.test.ts +0 -330
  143. package/src/validation.ts +0 -246
  144. package/src/variable-name-inference.test.ts +0 -178
  145. package/src/variable-name-inference.ts +0 -242
  146. package/src/webhook.test.ts +0 -649
  147. package/src/webhook.ts +0 -637
  148. package/src/workflow-distributed.test.ts +0 -786
  149. package/src/workflow-distributed.ts +0 -916
  150. package/src/workflow.async-safety.integration.test.ts +0 -345
  151. package/src/workflow.test.ts +0 -647
  152. package/src/workflow.ts +0 -810
  153. package/src/yaml-config.test.ts +0 -373
  154. package/src/yaml-config.ts +0 -351
@@ -1,64 +0,0 @@
1
- /**
2
- * Attribute utility functions
3
- */
4
-
5
- import type { AttributeValue } from '../trace-context';
6
- import {
7
- validateAttribute,
8
- autoRedactPII,
9
- defaultGuardrails,
10
- checkDeprecatedAttribute,
11
- type AttributePolicy,
12
- } from './validators';
13
-
14
- // Type for objects that have setAttributes method (spans or contexts)
15
- // Using a generic parameter to accommodate different AttributeValue types
16
- type AttributeSetter = {
17
- setAttributes: (attrs: Record<string, AttributeValue>) => void;
18
- };
19
-
20
- export function mergeAttrs(
21
- ...attrSets: Array<Record<string, unknown> | undefined>
22
- ): Record<string, unknown> {
23
- const result: Record<string, unknown> = {};
24
- for (const attrSet of attrSets) {
25
- if (attrSet) {
26
- Object.assign(result, attrSet);
27
- }
28
- }
29
- return result;
30
- }
31
-
32
- export function safeSetAttributes(
33
- span: AttributeSetter,
34
- attrs: Record<string, unknown>,
35
- policy?: AttributePolicy,
36
- ): void {
37
- // Merge user-supplied guardrails with defaults so callers can tweak
38
- // a single option without opting out of the rest
39
- const mergedGuardrails = {
40
- ...defaultGuardrails(),
41
- ...policy?.guardrails,
42
- };
43
- const effectivePolicy: AttributePolicy = {
44
- ...policy,
45
- guardrails: mergedGuardrails,
46
- };
47
-
48
- const validated = autoRedactPII(attrs, effectivePolicy);
49
-
50
- const sanitizedAttrs: Record<string, AttributeValue> = {};
51
- for (const [key, value] of Object.entries(validated)) {
52
- if (value !== undefined) {
53
- // Check for deprecated attributes and log warnings
54
- checkDeprecatedAttribute(key, effectivePolicy);
55
- const validatedValue = validateAttribute(key, value, effectivePolicy);
56
- if (validatedValue !== undefined) {
57
- // Cast to AttributeValue since validateAttribute ensures valid types
58
- sanitizedAttrs[key] = validatedValue as AttributeValue;
59
- }
60
- }
61
- }
62
-
63
- span.setAttributes(sanitizedAttrs);
64
- }
@@ -1,266 +0,0 @@
1
- /**
2
- * Attribute validation, PII detection, and guardrails
3
- * Provides safe-by-default attribute handling with configurable policies
4
- */
5
-
6
- import { REDACTOR_PATTERNS } from '../attribute-redacting-processor';
7
-
8
- export interface AttributeGuardrails {
9
- /** How to handle PII in attributes */
10
- pii?: 'allow' | 'redact' | 'hash' | 'block';
11
-
12
- /** Maximum length for attribute values */
13
- maxLength?: number;
14
-
15
- /** Validate enum values against known values */
16
- validateEnum?: boolean;
17
-
18
- /** Log warnings for deprecated attributes instead of throwing */
19
- warnDeprecated?: boolean;
20
-
21
- /** Custom deprecation warnings */
22
- deprecatedWarnings?: Record<string, string>;
23
- }
24
-
25
- export interface AttributePolicy {
26
- guardrails?: AttributeGuardrails;
27
- /** Custom deprecation warnings for specific attributes */
28
- deprecatedWarnings?: Record<string, string>;
29
- }
30
-
31
- const DEPRECATED_ATTRIBUTES = {
32
- 'enduser.id': 'user.id',
33
- 'enduser.role': 'user.roles',
34
- 'enduser.scope': undefined,
35
- 'http.method': 'http.request.method',
36
- 'http.host': 'server.address',
37
- 'http.status_code': 'http.response.status_code',
38
- 'http.target': 'url.path',
39
- 'http.url': 'url.full',
40
- 'http.user_agent': 'user_agent.original',
41
- 'http.flavor': 'network.protocol.name',
42
- 'http.scheme': 'url.scheme',
43
- 'http.server_name': 'server.address',
44
- 'db.name': 'db.namespace',
45
- 'db.operation': 'db.operation.name',
46
- 'db.statement': 'db.query.text',
47
- 'db.system': 'db.system.name',
48
- 'db.collection': 'db.collection.name',
49
- 'db.instance.id': undefined,
50
- 'db.jdbc.driver_classname': undefined,
51
- 'db.mssql.instance_name': 'mssql.instance.name',
52
- 'db.sql.table': 'db.collection.name',
53
- 'http.client_ip': 'client.address',
54
- 'user_agent.original': 'user_agent.original',
55
- } as const;
56
-
57
- const HTTP_METHODS = new Set([
58
- 'GET',
59
- 'POST',
60
- 'PUT',
61
- 'DELETE',
62
- 'PATCH',
63
- 'HEAD',
64
- 'OPTIONS',
65
- 'TRACE',
66
- 'QUERY',
67
- '_OTHER',
68
- ]);
69
-
70
- export function validateAttribute(
71
- key: string,
72
- value: unknown,
73
- policy: AttributePolicy = {},
74
- ): unknown {
75
- const { guardrails = {} } = policy;
76
-
77
- if (value === undefined || value === null) {
78
- return undefined;
79
- }
80
-
81
- // For non-string values that don't need transformation, preserve the original type
82
- if (typeof value !== 'string') {
83
- // PII checks only apply to strings
84
- // maxLength only applies to strings
85
- // validateEnum only applies to strings
86
- return value;
87
- }
88
-
89
- const stringValue = value;
90
-
91
- if (guardrails.pii) {
92
- const piiResult = applyPIIPolicy(key, stringValue, guardrails.pii);
93
- if (piiResult !== stringValue) {
94
- return piiResult;
95
- }
96
- }
97
-
98
- if (guardrails.maxLength && stringValue.length > guardrails.maxLength) {
99
- return truncateValue(key, stringValue, guardrails.maxLength);
100
- }
101
-
102
- if (guardrails.validateEnum && HTTP_METHODS.has(stringValue)) {
103
- const normalizedMethod = normalizeHTTPMethod(stringValue);
104
- if (normalizedMethod !== stringValue) {
105
- return normalizedMethod;
106
- }
107
- }
108
-
109
- return stringValue;
110
- }
111
-
112
- function applyPIIPolicy(
113
- key: string,
114
- value: string,
115
- pii: AttributeGuardrails['pii'],
116
- ): string {
117
- if (pii === 'allow') {
118
- return value;
119
- }
120
-
121
- if (pii === 'redact') {
122
- return redactIfPII(key, value);
123
- }
124
-
125
- if (pii === 'hash') {
126
- return hashIfPII(key, value);
127
- }
128
-
129
- if (pii === 'block' && isPIIKey(key)) {
130
- throw new Error(
131
- `PII attribute "${key}" is blocked by guardrails. Use pii: "allow" to enable it.`,
132
- );
133
- }
134
-
135
- return value;
136
- }
137
-
138
- function isPIIKey(key: string): boolean {
139
- const piiKeyPatterns = [
140
- 'email',
141
- 'phone',
142
- 'ssn',
143
- 'credit_card',
144
- 'password',
145
- 'secret',
146
- 'token',
147
- 'api_key',
148
- 'authorization',
149
- ];
150
- const lowerKey = key.toLowerCase();
151
- return piiKeyPatterns.some((pattern) => lowerKey.includes(pattern));
152
- }
153
-
154
- function redactIfPII(key: string, value: string): string {
155
- if (isPIIKey(key)) {
156
- // REDACTOR_PATTERNS values are RegExp patterns
157
- for (const [, pattern] of Object.entries(REDACTOR_PATTERNS)) {
158
- if (pattern instanceof RegExp && pattern.test(value)) {
159
- return '[REDACTED]';
160
- }
161
- }
162
- // If no pattern matched but key is PII, still redact
163
- return '[REDACTED]';
164
- }
165
- return value;
166
- }
167
-
168
- function hashIfPII(key: string, value: string): string {
169
- if (!isPIIKey(key)) {
170
- return value;
171
- }
172
-
173
- // Use a simple but consistent hash that produces 32-char hex
174
- // FNV-1a hash variant producing 128-bit output (32 hex chars)
175
- const FNV_PRIME = 0x01_00_01_93;
176
- const FNV_OFFSET = 0x81_1c_9d_c5;
177
-
178
- // Generate 4 32-bit hashes to produce 32 hex chars
179
- const hashes: number[] = [];
180
- for (let round = 0; round < 4; round++) {
181
- let hash = FNV_OFFSET;
182
- for (let i = 0; i < value.length; i++) {
183
- hash ^= (value.codePointAt(i) ?? 0) + round;
184
- hash = Math.imul(hash, FNV_PRIME);
185
- }
186
- hashes.push(hash >>> 0); // Convert to unsigned
187
- }
188
-
189
- return `hash_${hashes.map((h) => h.toString(16).padStart(8, '0')).join('')}`;
190
- }
191
-
192
- function truncateValue(key: string, value: string, maxLength: number): string {
193
- if (value.length <= maxLength) {
194
- return value;
195
- }
196
- return value.slice(0, maxLength - 3) + '...';
197
- }
198
-
199
- function normalizeHTTPMethod(method: string): string {
200
- const upper = method.toUpperCase();
201
- if (HTTP_METHODS.has(upper)) {
202
- return upper;
203
- }
204
- return upper;
205
- }
206
-
207
- export function checkDeprecatedAttribute(
208
- key: string,
209
- policy: AttributePolicy = {},
210
- ): string | null {
211
- const { guardrails = {}, deprecatedWarnings = {} } = policy;
212
- const { warnDeprecated = true } = guardrails;
213
-
214
- if (!warnDeprecated) {
215
- return null;
216
- }
217
-
218
- // Check if the key exists in the deprecated attributes map
219
- const isDeprecated = key in DEPRECATED_ATTRIBUTES;
220
- if (isDeprecated) {
221
- const replacement =
222
- DEPRECATED_ATTRIBUTES[key as keyof typeof DEPRECATED_ATTRIBUTES];
223
- if (replacement === undefined) {
224
- // Deprecated with no replacement (e.g., enduser.scope)
225
- console.warn(
226
- `[autotel/attributes] Attribute "${key}" is deprecated and has no replacement. ` +
227
- `Remove or find a replacement in OpenTelemetry semantic conventions.`,
228
- );
229
- } else {
230
- // Deprecated with a known replacement
231
- console.warn(
232
- `[autotel/attributes] Attribute "${key}" is deprecated. Use "${replacement}" instead.`,
233
- );
234
- }
235
- }
236
-
237
- if (deprecatedWarnings[key]) {
238
- console.warn(`[autotel/attributes] ${deprecatedWarnings[key]}`);
239
- }
240
-
241
- const replacement =
242
- DEPRECATED_ATTRIBUTES[key as keyof typeof DEPRECATED_ATTRIBUTES];
243
- return replacement ?? null;
244
- }
245
-
246
- export function autoRedactPII(
247
- attributes: Record<string, unknown>,
248
- policy: AttributePolicy = {},
249
- ): Record<string, unknown> {
250
- const { guardrails = { pii: 'redact' } } = policy;
251
-
252
- const redacted: Record<string, unknown> = {};
253
- for (const [key, value] of Object.entries(attributes)) {
254
- redacted[key] = validateAttribute(key, value, { guardrails });
255
- }
256
- return redacted;
257
- }
258
-
259
- export function defaultGuardrails(): AttributeGuardrails {
260
- return {
261
- pii: 'redact',
262
- maxLength: 255,
263
- validateEnum: true,
264
- warnDeprecated: true,
265
- };
266
- }
@@ -1,292 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach } from 'vitest';
2
- import { createTraceCollector, TraceCollector } from './testing';
3
- import { trace, TraceContext } from './functional';
4
- import {
5
- attrs,
6
- setUser,
7
- identify,
8
- httpServer,
9
- mergeAttrs,
10
- safeSetAttributes,
11
- transaction,
12
- } from './attributes';
13
-
14
- describe('attributes', () => {
15
- let collector: TraceCollector;
16
-
17
- beforeEach(() => {
18
- collector = createTraceCollector();
19
- });
20
-
21
- describe('Pattern A: Key builders', () => {
22
- it('should create user.id attribute', async () => {
23
- const testFn = trace((ctx: TraceContext) => async () => {
24
- ctx.setAttributes(attrs.user.id('user-123'));
25
- });
26
-
27
- await testFn();
28
-
29
- const spans = collector.getSpans();
30
- expect(spans).toHaveLength(1);
31
- expect(spans[0]!.attributes).toMatchObject({
32
- 'user.id': 'user-123',
33
- });
34
- });
35
-
36
- it('should create http.request.method attribute', async () => {
37
- const testFn = trace((ctx: TraceContext) => async () => {
38
- ctx.setAttributes(attrs.http.request.method('GET'));
39
- });
40
-
41
- await testFn();
42
-
43
- const spans = collector.getSpans();
44
- expect(spans).toHaveLength(1);
45
- expect(spans[0]!.attributes).toMatchObject({
46
- 'http.request.method': 'GET',
47
- });
48
- });
49
-
50
- it('should create multiple attributes from key builders', async () => {
51
- const testFn = trace((ctx: TraceContext) => async () => {
52
- ctx.setAttributes(
53
- mergeAttrs(
54
- attrs.user.id('user-123'),
55
- attrs.http.request.method('GET'),
56
- attrs.session.id('session-456'),
57
- ),
58
- );
59
- });
60
-
61
- await testFn();
62
-
63
- const spans = collector.getSpans();
64
- expect(spans).toHaveLength(1);
65
- expect(spans[0]!.attributes).toMatchObject({
66
- 'user.id': 'user-123',
67
- 'http.request.method': 'GET',
68
- 'session.id': 'session-456',
69
- });
70
- });
71
- });
72
-
73
- describe('Pattern B: Object builders', () => {
74
- it('should create user attributes from object', async () => {
75
- const testFn = trace((ctx: TraceContext) => async () => {
76
- const userAttrs = attrs.user.data({
77
- id: 'user-123',
78
- email: 'test@example.com',
79
- });
80
- ctx.setAttributes(userAttrs);
81
- });
82
-
83
- await testFn();
84
-
85
- const spans = collector.getSpans();
86
- expect(spans).toHaveLength(1);
87
- expect(spans[0]!.attributes).toMatchObject({
88
- 'user.id': 'user-123',
89
- 'user.email': 'test@example.com',
90
- });
91
- });
92
-
93
- it('should create HTTP server attributes from object', async () => {
94
- const testFn = trace((ctx: TraceContext) => async () => {
95
- const httpAttrs = attrs.http.server({
96
- method: 'POST',
97
- route: '/users/:id',
98
- statusCode: 200,
99
- });
100
- ctx.setAttributes(httpAttrs);
101
- });
102
-
103
- await testFn();
104
-
105
- const spans = collector.getSpans();
106
- expect(spans).toHaveLength(1);
107
- expect(spans[0]!.attributes).toMatchObject({
108
- 'http.request.method': 'POST',
109
- 'http.route': '/users/:id',
110
- 'http.response.status_code': 200,
111
- });
112
- });
113
- });
114
-
115
- describe('Attachers: attachers', () => {
116
- it('setUser should set user attributes on span (PII redacted by default)', async () => {
117
- const testFn = trace((ctx: TraceContext) => async () => {
118
- setUser(ctx, { id: 'user-123', email: 'test@example.com' });
119
- });
120
-
121
- await testFn();
122
-
123
- const spans = collector.getSpans();
124
- expect(spans).toHaveLength(1);
125
- expect(spans[0]!.attributes).toMatchObject({
126
- 'user.id': 'user-123',
127
- 'user.email': '[REDACTED]', // PII is redacted by default
128
- });
129
- });
130
-
131
- it('httpServer should set HTTP attributes and update span name', async () => {
132
- const testFn = trace((ctx: TraceContext) => async () => {
133
- httpServer(ctx, {
134
- method: 'GET',
135
- route: '/api/users',
136
- statusCode: 200,
137
- });
138
- });
139
-
140
- await testFn();
141
-
142
- const spans = collector.getSpans();
143
- expect(spans).toHaveLength(1);
144
- expect(spans[0]!.attributes).toMatchObject({
145
- 'http.request.method': 'GET',
146
- 'http.route': '/api/users',
147
- 'http.response.status_code': 200,
148
- });
149
- expect(spans[0]!.name).toBe('HTTP GET /api/users');
150
- });
151
-
152
- it('identify should bundle user, session, and device attributes', async () => {
153
- const testFn = trace((ctx: TraceContext) => async () => {
154
- identify(ctx, {
155
- user: { id: 'user-123', name: 'John Doe' },
156
- session: { id: 'session-456' },
157
- device: { id: 'device-789', manufacturer: 'Apple' },
158
- });
159
- });
160
-
161
- await testFn();
162
-
163
- const spans = collector.getSpans();
164
- expect(spans).toHaveLength(1);
165
- expect(spans[0]!.attributes).toMatchObject({
166
- 'user.id': 'user-123',
167
- 'user.name': 'John Doe',
168
- 'session.id': 'session-456',
169
- 'device.id': 'device-789',
170
- 'device.manufacturer': 'Apple',
171
- });
172
- });
173
- });
174
-
175
- describe('Domains: domains', () => {
176
- it('transaction should bundle request attributes', async () => {
177
- const testFn = trace((ctx: TraceContext) => async () => {
178
- transaction(ctx, {
179
- method: 'GET',
180
- route: '/api/users',
181
- statusCode: 200,
182
- clientIp: '192.168.1.1',
183
- });
184
- });
185
-
186
- await testFn();
187
-
188
- const spans = collector.getSpans();
189
- expect(spans).toHaveLength(1);
190
- expect(spans[0]!.attributes).toMatchObject({
191
- 'http.request.method': 'GET',
192
- 'http.route': '/api/users',
193
- 'http.response.status_code': 200,
194
- 'network.peer.address': '192.168.1.1',
195
- });
196
- });
197
- });
198
-
199
- describe('Validators: validators', () => {
200
- it('should redact PII attributes with default policy', async () => {
201
- const testFn = trace((ctx: TraceContext) => async () => {
202
- safeSetAttributes(
203
- ctx,
204
- attrs.user.data({ email: 'sensitive@example.com' }),
205
- {
206
- guardrails: { pii: 'redact' },
207
- },
208
- );
209
- });
210
-
211
- await testFn();
212
-
213
- const spans = collector.getSpans();
214
- expect(spans).toHaveLength(1);
215
- expect(spans[0]!.attributes['user.email']).toBe('[REDACTED]');
216
- });
217
-
218
- it('should allow PII when guardrails pii is "allow"', async () => {
219
- const testFn = trace((ctx: TraceContext) => async () => {
220
- safeSetAttributes(
221
- ctx,
222
- attrs.user.data({ email: 'sensitive@example.com' }),
223
- {
224
- guardrails: { pii: 'allow' },
225
- },
226
- );
227
- });
228
-
229
- await testFn();
230
-
231
- const spans = collector.getSpans();
232
- expect(spans).toHaveLength(1);
233
- expect(spans[0]!.attributes['user.email']).toBe('sensitive@example.com');
234
- });
235
-
236
- it('should hash PII when guardrails pii is "hash"', async () => {
237
- const testFn = trace((ctx: TraceContext) => async () => {
238
- safeSetAttributes(
239
- ctx,
240
- attrs.user.data({ email: 'sensitive@example.com' }),
241
- {
242
- guardrails: { pii: 'hash' },
243
- },
244
- );
245
- });
246
-
247
- await testFn();
248
-
249
- const spans = collector.getSpans();
250
- expect(spans).toHaveLength(1);
251
- expect(spans[0]!.attributes['user.email']).toMatch(/^hash_[a-f0-9]+$/);
252
- });
253
-
254
- it('should log warning for deprecated attributes', async () => {
255
- const consoleWarnSpy = vi.spyOn(console, 'warn');
256
-
257
- const testFn = trace((ctx: TraceContext) => async () => {
258
- // Use an actually deprecated attribute (http.method -> http.request.method)
259
- safeSetAttributes(
260
- ctx,
261
- { 'http.method': 'GET' },
262
- {
263
- guardrails: { warnDeprecated: true },
264
- },
265
- );
266
- });
267
-
268
- await testFn();
269
-
270
- expect(consoleWarnSpy).toHaveBeenCalledWith(
271
- expect.stringContaining('deprecated'),
272
- );
273
- consoleWarnSpy.mockRestore();
274
- });
275
-
276
- it('should truncate values exceeding maxLength', async () => {
277
- const testFn = trace((ctx: TraceContext) => async () => {
278
- safeSetAttributes(ctx, attrs.user.data({ id: 'a'.repeat(300) }), {
279
- guardrails: { maxLength: 255 },
280
- });
281
- });
282
-
283
- await testFn();
284
-
285
- const spans = collector.getSpans();
286
- expect(spans).toHaveLength(1);
287
- const userId = spans[0]!.attributes['user.id'] as string;
288
- expect(userId.length).toBeLessThanOrEqual(255);
289
- expect(userId).toMatch(/\.\.\.$/);
290
- });
291
- });
292
- });
package/src/auto.ts DELETED
@@ -1,67 +0,0 @@
1
- /**
2
- * Zero-config ESM instrumentation with auto-init from YAML/environment variables
3
- *
4
- * This module provides the simplest possible setup for OpenTelemetry instrumentation.
5
- * Just import it and everything is configured from autotel.yaml or environment variables.
6
- *
7
- * Usage with YAML config (recommended):
8
- * ```bash
9
- * # Create autotel.yaml in project root, then:
10
- * tsx --import autotel/auto src/index.ts
11
- * ```
12
- *
13
- * Usage with environment variables:
14
- * ```bash
15
- * OTEL_SERVICE_NAME=my-app tsx --import autotel/auto src/index.ts
16
- * ```
17
- *
18
- * No instrumentation.ts file needed!
19
- *
20
- * Configuration Priority (highest to lowest):
21
- * 1. YAML file (autotel.yaml or AUTOTEL_CONFIG_FILE)
22
- * 2. Environment variables (OTEL_*, AUTOTEL_*)
23
- * 3. Built-in defaults
24
- *
25
- * Environment Variables:
26
- * - OTEL_SERVICE_NAME: Service name (required for meaningful traces)
27
- * - OTEL_EXPORTER_OTLP_ENDPOINT: OTLP collector endpoint (e.g., http://localhost:4318)
28
- * - OTEL_EXPORTER_OTLP_HEADERS: Auth headers (e.g., x-honeycomb-team=YOUR_KEY)
29
- * - AUTOTEL_INTEGRATIONS: Comma-separated list or 'true' for all (default: http,express)
30
- * - AUTOTEL_DEBUG: Set to 'true' to enable console span output
31
- * - AUTOTEL_CONFIG_FILE: Path to YAML config file (overrides autotel.yaml discovery)
32
- *
33
- * @requires Node.js 20.6.0 or later
34
- */
35
-
36
- import { register } from 'node:module';
37
- import { createAddHookMessageChannel } from 'import-in-the-middle';
38
- import { init } from './init';
39
- import { loadYamlConfig } from './yaml-config';
40
-
41
- // Register ESM hooks first (must happen before any instrumented modules load)
42
- const { registerOptions } = createAddHookMessageChannel();
43
- register('import-in-the-middle/hook.mjs', import.meta.url, registerOptions);
44
-
45
- // Load YAML config if present (init.ts will also load it, but we need values here)
46
- const yamlConfig = loadYamlConfig();
47
-
48
- // Parse auto-instrumentations from environment variable (fallback if not in YAML)
49
- const autoInstrumentationsEnv = process.env.AUTOTEL_INTEGRATIONS;
50
- const autoInstrumentations:
51
- | string[]
52
- | boolean
53
- | Record<string, { enabled?: boolean }> =
54
- autoInstrumentationsEnv === 'true'
55
- ? true // Enable all auto-instrumentations
56
- : autoInstrumentationsEnv
57
- ? autoInstrumentationsEnv.split(',').map((s) => s.trim())
58
- : (yamlConfig?.autoInstrumentations ?? ['http', 'express']); // YAML > default
59
-
60
- // Auto-initialize with YAML config merged with env var defaults
61
- // init() will load YAML again and merge properly, but we pass overrides here
62
- init({
63
- service:
64
- yamlConfig?.service ?? process.env.OTEL_SERVICE_NAME ?? 'unknown-service',
65
- debug: yamlConfig?.debug ?? process.env.AUTOTEL_DEBUG === 'true',
66
- autoInstrumentations,
67
- });