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.
- package/package.json +1 -2
- 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 -665
- 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 -2439
- 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 -373
- package/src/yaml-config.ts +0 -351
package/src/attributes/utils.ts
DELETED
|
@@ -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
|
-
}
|
package/src/attributes.test.ts
DELETED
|
@@ -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
|
-
});
|