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.
Files changed (232) hide show
  1. package/README.md +26 -1
  2. package/dist/auto.cjs +2 -2
  3. package/dist/auto.js +1 -1
  4. package/dist/correlation-id.cjs +1 -1
  5. package/dist/correlation-id.js +1 -1
  6. package/dist/decorators.cjs +1 -1
  7. package/dist/decorators.js +1 -1
  8. package/dist/{event-Dlqr4ZNL.cjs → event-BhHREDJk.cjs} +3 -3
  9. package/dist/{event-Dlqr4ZNL.cjs.map → event-BhHREDJk.cjs.map} +1 -1
  10. package/dist/{event-_58ryBjh.js → event-ByBTV9M2.js} +3 -3
  11. package/dist/{event-_58ryBjh.js.map → event-ByBTV9M2.js.map} +1 -1
  12. package/dist/event.cjs +1 -1
  13. package/dist/event.js +1 -1
  14. package/dist/{functional-BGkT8J-h.js → functional-DtI0u4vx.js} +19 -19
  15. package/dist/functional-DtI0u4vx.js.map +1 -0
  16. package/dist/{functional-C4CzoVrX.cjs → functional-zpzNLhky.cjs} +4 -4
  17. package/dist/{functional-C4CzoVrX.cjs.map → functional-zpzNLhky.cjs.map} +1 -1
  18. package/dist/functional.cjs +1 -1
  19. package/dist/functional.js +1 -1
  20. package/dist/http.cjs +1 -1
  21. package/dist/http.js +1 -1
  22. package/dist/index.cjs +5 -5
  23. package/dist/index.d.cts +1 -1
  24. package/dist/index.d.ts +1 -1
  25. package/dist/index.js +5 -5
  26. package/dist/{init-DJQOdVlN.d.ts → init-B7u-DjxM.d.ts} +57 -2
  27. package/dist/init-B7u-DjxM.d.ts.map +1 -0
  28. package/dist/{init-DvapOXCc.cjs → init-BX7AmFRl.cjs} +40 -21
  29. package/dist/init-BX7AmFRl.cjs.map +1 -0
  30. package/dist/{init-Ch6t7MNI.js → init-D-jnNMix.js} +39 -20
  31. package/dist/init-D-jnNMix.js.map +1 -0
  32. package/dist/{init-CNp-ee80.d.cts → init-DSrRmVnz.d.cts} +57 -2
  33. package/dist/init-DSrRmVnz.d.cts.map +1 -0
  34. package/dist/instrumentation.cjs +1 -1
  35. package/dist/instrumentation.js +1 -1
  36. package/dist/logger-D3Ej3DII.js +446 -0
  37. package/dist/logger-D3Ej3DII.js.map +1 -0
  38. package/dist/logger-thMPLpOG.cjs +487 -0
  39. package/dist/logger-thMPLpOG.cjs.map +1 -0
  40. package/dist/logger.cjs +8 -236
  41. package/dist/logger.js +2 -204
  42. package/dist/messaging.cjs +1 -1
  43. package/dist/messaging.js +1 -1
  44. package/dist/semantic-helpers.cjs +1 -1
  45. package/dist/semantic-helpers.js +1 -1
  46. package/dist/{track-3HY4NGV-.cjs → track-D59FfpL0.cjs} +2 -2
  47. package/dist/{track-3HY4NGV-.cjs.map → track-D59FfpL0.cjs.map} +1 -1
  48. package/dist/{track-nsKVy-pj.js → track-wc0HafS_.js} +6 -6
  49. package/dist/track-wc0HafS_.js.map +1 -0
  50. package/dist/webhook.cjs +1 -1
  51. package/dist/webhook.js +1 -1
  52. package/dist/workflow-distributed.cjs +1 -1
  53. package/dist/workflow-distributed.js +1 -1
  54. package/dist/workflow.cjs +1 -1
  55. package/dist/workflow.js +1 -1
  56. package/dist/{yaml-config-B3dQ82GR.cjs → yaml-config-Ck2uB0Dp.cjs} +2 -1
  57. package/dist/yaml-config-Ck2uB0Dp.cjs.map +1 -0
  58. package/dist/yaml-config.cjs +1 -1
  59. package/dist/yaml-config.d.cts +7 -1
  60. package/dist/yaml-config.d.cts.map +1 -1
  61. package/dist/yaml-config.d.ts +7 -1
  62. package/dist/yaml-config.d.ts.map +1 -1
  63. package/dist/yaml-config.js +1 -0
  64. package/dist/yaml-config.js.map +1 -1
  65. package/package.json +1 -2
  66. package/skills/autotel-core/SKILL.md +2 -0
  67. package/skills/autotel-instrumentation/SKILL.md +25 -0
  68. package/skills/debug-missing-spans/SKILL.md +3 -1
  69. package/skills/migrate-to-autotel/SKILL.md +24 -23
  70. package/skills/review-otel-patterns/SKILL.md +5 -4
  71. package/dist/functional-BGkT8J-h.js.map +0 -1
  72. package/dist/init-CNp-ee80.d.cts.map +0 -1
  73. package/dist/init-Ch6t7MNI.js.map +0 -1
  74. package/dist/init-DJQOdVlN.d.ts.map +0 -1
  75. package/dist/init-DvapOXCc.cjs.map +0 -1
  76. package/dist/logger.cjs.map +0 -1
  77. package/dist/logger.js.map +0 -1
  78. package/dist/track-nsKVy-pj.js.map +0 -1
  79. package/dist/yaml-config-B3dQ82GR.cjs.map +0 -1
  80. package/src/attribute-redacting-processor.test.ts +0 -763
  81. package/src/attribute-redacting-processor.ts +0 -621
  82. package/src/attributes/attachers.ts +0 -161
  83. package/src/attributes/builders.ts +0 -529
  84. package/src/attributes/domains.ts +0 -42
  85. package/src/attributes/index.ts +0 -81
  86. package/src/attributes/registry.ts +0 -323
  87. package/src/attributes/types.ts +0 -211
  88. package/src/attributes/utils.ts +0 -64
  89. package/src/attributes/validators.ts +0 -266
  90. package/src/attributes.test.ts +0 -292
  91. package/src/auto.ts +0 -67
  92. package/src/autotel-logger.test.ts +0 -548
  93. package/src/autotel-logger.ts +0 -364
  94. package/src/baggage-span-processor.test.ts +0 -202
  95. package/src/baggage-span-processor.ts +0 -100
  96. package/src/business-baggage.test.ts +0 -500
  97. package/src/business-baggage.ts +0 -669
  98. package/src/circuit-breaker.test.ts +0 -341
  99. package/src/circuit-breaker.ts +0 -184
  100. package/src/config.test.ts +0 -94
  101. package/src/config.ts +0 -172
  102. package/src/correlated-events.test.ts +0 -151
  103. package/src/correlated-events.ts +0 -47
  104. package/src/correlation-id.test.ts +0 -163
  105. package/src/correlation-id.ts +0 -206
  106. package/src/db.test.ts +0 -252
  107. package/src/db.ts +0 -447
  108. package/src/decorators.test.ts +0 -153
  109. package/src/decorators.ts +0 -188
  110. package/src/define-event.test.ts +0 -41
  111. package/src/define-event.ts +0 -58
  112. package/src/devtools.ts +0 -60
  113. package/src/drain-pipeline.test.ts +0 -68
  114. package/src/drain-pipeline.ts +0 -199
  115. package/src/drain-toolkit.test.ts +0 -113
  116. package/src/drain-toolkit.ts +0 -129
  117. package/src/enricher-toolkit.test.ts +0 -67
  118. package/src/enricher-toolkit.ts +0 -79
  119. package/src/enrichers.test.ts +0 -150
  120. package/src/enrichers.ts +0 -145
  121. package/src/env-config.test.ts +0 -323
  122. package/src/env-config.ts +0 -309
  123. package/src/error-catalog.test.ts +0 -133
  124. package/src/error-catalog.ts +0 -262
  125. package/src/event-queue.test.ts +0 -864
  126. package/src/event-queue.ts +0 -699
  127. package/src/event-subscriber.ts +0 -262
  128. package/src/event-testing.ts +0 -197
  129. package/src/event.test.ts +0 -1104
  130. package/src/event.ts +0 -988
  131. package/src/events-config.ts +0 -235
  132. package/src/exporters.ts +0 -165
  133. package/src/filtering-span-processor.test.ts +0 -281
  134. package/src/filtering-span-processor.ts +0 -111
  135. package/src/flatten-attributes.test.ts +0 -76
  136. package/src/flatten-attributes.ts +0 -80
  137. package/src/functional.strict-types.typecheck.ts +0 -53
  138. package/src/functional.test.ts +0 -1464
  139. package/src/functional.ts +0 -2539
  140. package/src/functional.types.test.ts +0 -135
  141. package/src/hook.mjs +0 -15
  142. package/src/http.test.ts +0 -485
  143. package/src/http.ts +0 -424
  144. package/src/index.ts +0 -433
  145. package/src/init-auto-redactor.test.ts +0 -53
  146. package/src/init-redactor.test.ts +0 -8
  147. package/src/init.customization.test.ts +0 -594
  148. package/src/init.integrations.test.ts +0 -399
  149. package/src/init.openllmetry.test.ts +0 -194
  150. package/src/init.protocol.test.ts +0 -215
  151. package/src/init.ts +0 -2312
  152. package/src/instrumentation.test.ts +0 -108
  153. package/src/instrumentation.ts +0 -319
  154. package/src/logger.test.ts +0 -125
  155. package/src/logger.ts +0 -341
  156. package/src/messaging-adapters.test.ts +0 -595
  157. package/src/messaging-adapters.ts +0 -583
  158. package/src/messaging-testing.test.ts +0 -573
  159. package/src/messaging-testing.ts +0 -935
  160. package/src/messaging.test.ts +0 -1646
  161. package/src/messaging.ts +0 -2245
  162. package/src/metric-helpers.ts +0 -47
  163. package/src/metric-testing.ts +0 -197
  164. package/src/metric.ts +0 -446
  165. package/src/metrics.test.ts +0 -241
  166. package/src/node-require.ts +0 -123
  167. package/src/operation-context.ts +0 -93
  168. package/src/parse-error.test.ts +0 -73
  169. package/src/parse-error.ts +0 -112
  170. package/src/posthog-logs.test.ts +0 -115
  171. package/src/posthog-logs.ts +0 -77
  172. package/src/pretty-console-exporter.test.ts +0 -545
  173. package/src/pretty-console-exporter.ts +0 -413
  174. package/src/pretty-log-formatter.test.ts +0 -123
  175. package/src/pretty-log-formatter.ts +0 -210
  176. package/src/processors/canonical-log-line-processor.test.ts +0 -523
  177. package/src/processors/canonical-log-line-processor.ts +0 -396
  178. package/src/processors.ts +0 -152
  179. package/src/rate-limiter.test.ts +0 -199
  180. package/src/rate-limiter.ts +0 -98
  181. package/src/redact-values.test.ts +0 -90
  182. package/src/redact-values.ts +0 -34
  183. package/src/register.ts +0 -37
  184. package/src/request-logger.test.ts +0 -545
  185. package/src/request-logger.ts +0 -342
  186. package/src/sampling.test.ts +0 -1060
  187. package/src/sampling.ts +0 -737
  188. package/src/security-schema.test.ts +0 -45
  189. package/src/security-schema.ts +0 -107
  190. package/src/semantic-conventions.ts +0 -15
  191. package/src/semantic-helpers.test.ts +0 -226
  192. package/src/semantic-helpers.ts +0 -438
  193. package/src/shutdown.test.ts +0 -364
  194. package/src/shutdown.ts +0 -246
  195. package/src/span-name-normalizer.test.ts +0 -377
  196. package/src/span-name-normalizer.ts +0 -213
  197. package/src/stable-hash.ts +0 -27
  198. package/src/structured-error.test.ts +0 -191
  199. package/src/structured-error.ts +0 -157
  200. package/src/stub.integration.test.ts +0 -361
  201. package/src/tail-sampling-processor.test.ts +0 -230
  202. package/src/tail-sampling-processor.ts +0 -55
  203. package/src/test-span-collector.test.ts +0 -234
  204. package/src/test-span-collector.ts +0 -150
  205. package/src/testing.ts +0 -705
  206. package/src/trace-context.test.ts +0 -73
  207. package/src/trace-context.ts +0 -567
  208. package/src/trace-helpers.new.test.ts +0 -278
  209. package/src/trace-helpers.test.ts +0 -290
  210. package/src/trace-helpers.ts +0 -710
  211. package/src/trace-hybrid.test.ts +0 -42
  212. package/src/trace-hybrid.ts +0 -37
  213. package/src/tracer-provider.test.ts +0 -183
  214. package/src/tracer-provider.ts +0 -266
  215. package/src/track.test.ts +0 -154
  216. package/src/track.ts +0 -216
  217. package/src/validate.test.ts +0 -287
  218. package/src/validate.ts +0 -307
  219. package/src/validation-attributes.ts +0 -43
  220. package/src/validation.test.ts +0 -330
  221. package/src/validation.ts +0 -246
  222. package/src/variable-name-inference.test.ts +0 -178
  223. package/src/variable-name-inference.ts +0 -242
  224. package/src/webhook.test.ts +0 -649
  225. package/src/webhook.ts +0 -637
  226. package/src/workflow-distributed.test.ts +0 -786
  227. package/src/workflow-distributed.ts +0 -916
  228. package/src/workflow.async-safety.integration.test.ts +0 -345
  229. package/src/workflow.test.ts +0 -647
  230. package/src/workflow.ts +0 -810
  231. package/src/yaml-config.test.ts +0 -337
  232. package/src/yaml-config.ts +0 -342
@@ -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
- });