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,710 +0,0 @@
1
- /**
2
- * Trace context helpers - Core primitives for trace correlation
3
- *
4
- * These are the building blocks that allow users to bring their own logger
5
- * (bunyan, log4js, custom, etc.) and add trace correlation.
6
- *
7
- * @example Using with bunyan
8
- * ```typescript
9
- * import bunyan from 'bunyan';
10
- * import { enrichWithTraceContext } from 'autotel/trace-helpers';
11
- *
12
- * const bunyanLogger = bunyan.createLogger({ name: 'myapp' });
13
- *
14
- * const logger = {
15
- * info: (msg: string, extra?: object) => {
16
- * bunyanLogger.info(enrichWithTraceContext(extra || {}), msg);
17
- * }
18
- * };
19
- * ```
20
- *
21
- * @example Using with log4js
22
- * ```typescript
23
- * import log4js from 'log4js';
24
- * import { getTraceContext } from 'autotel/trace-helpers';
25
- *
26
- * const log4jsLogger = log4js.getLogger();
27
- *
28
- * function logWithTrace(level: string, msg: string, extra?: object) {
29
- * const context = getTraceContext();
30
- * log4jsLogger[level](msg, { ...extra, ...context });
31
- * }
32
- * ```
33
- */
34
-
35
- import { trace, context, SpanStatusCode } from '@opentelemetry/api';
36
- import type { Span, Tracer, Context } from '@opentelemetry/api';
37
- import { requireModule } from './node-require';
38
-
39
- /**
40
- * WeakMap to store span names for active spans
41
- * This allows us to retrieve the span name even though OpenTelemetry
42
- * doesn't expose it through the public API
43
- */
44
- const spanNameMap = new WeakMap<Span, string>();
45
-
46
- /**
47
- * Store span name for a given span
48
- * Called internally when spans are created
49
- */
50
- export function setSpanName(span: Span, name: string): void {
51
- spanNameMap.set(span, name);
52
- }
53
-
54
- /**
55
- * Trace context extracted from active span
56
- */
57
- export interface TraceContext {
58
- /** Full 32-character hex trace ID */
59
- traceId: string;
60
- /** 16-character hex span ID */
61
- spanId: string;
62
- /** First 16 characters of trace ID (for log grouping/correlation) */
63
- correlationId: string;
64
- /** Function/operation name (OpenTelemetry semantic convention: code.function) */
65
- 'code.function'?: string;
66
- /** Datadog trace ID in decimal format (lower 64 bits) for log-trace correlation */
67
- 'dd.trace_id'?: string;
68
- /** Datadog span ID in decimal format for log-trace correlation */
69
- 'dd.span_id'?: string;
70
- }
71
-
72
- /**
73
- * Convert hex string to decimal string representation
74
- * Handles 64-bit unsigned integers for Datadog correlation
75
- *
76
- * @param hex - Hex string (up to 16 characters for 64-bit)
77
- * @returns Decimal string representation
78
- */
79
- function hexToDecimal(hex: string): string {
80
- // For 64-bit values, use BigInt to avoid precision loss
81
- return BigInt('0x' + hex).toString(10);
82
- }
83
-
84
- /**
85
- * Get current trace context from active span
86
- *
87
- * Returns null if no span is active (e.g., outside of trace operation)
88
- *
89
- * Includes both OpenTelemetry standard fields (hex) and Datadog-specific
90
- * fields (decimal) for maximum compatibility.
91
- *
92
- * @returns Trace context with traceId, spanId, correlationId, and Datadog decimal IDs, or null
93
- *
94
- * @example
95
- * ```typescript
96
- * import { getTraceContext } from 'autotel/trace-helpers';
97
- *
98
- * const context = getTraceContext();
99
- * if (context) {
100
- * console.log('Current trace:', context.traceId);
101
- * // Current trace: 4bf92f3577b34da6a3ce929d0e0e4736
102
- * console.log('Datadog trace ID:', context['dd.trace_id']);
103
- * // Datadog trace ID: 12007117331170166582 (decimal for log correlation)
104
- * }
105
- * ```
106
- */
107
- export function getTraceContext(): TraceContext | null {
108
- const span = trace.getActiveSpan();
109
- if (!span) return null;
110
-
111
- const spanContext = span.spanContext();
112
- const traceId = spanContext.traceId;
113
- const spanId = spanContext.spanId;
114
-
115
- // Get span name from WeakMap (set when span is created)
116
- // Map to OpenTelemetry semantic convention: code.function
117
- const spanName = spanNameMap.get(span);
118
-
119
- // Datadog uses the lower 64 bits of the 128-bit OpenTelemetry trace ID
120
- // Convert from hex to decimal for Datadog's log-trace correlation
121
- const traceIdLower64 = traceId.slice(-16); // Last 16 hex chars = lower 64 bits
122
- const ddTraceId = hexToDecimal(traceIdLower64);
123
- const ddSpanId = hexToDecimal(spanId);
124
-
125
- return {
126
- traceId,
127
- spanId,
128
- correlationId: traceId.slice(0, 16),
129
- ...(spanName && { 'code.function': spanName }),
130
- // Datadog-specific fields for log-trace correlation
131
- 'dd.trace_id': ddTraceId,
132
- 'dd.span_id': ddSpanId,
133
- };
134
- }
135
-
136
- /**
137
- * Enrich object with trace context (traceId, spanId, correlationId, and Datadog fields)
138
- *
139
- * If no span is active, returns the object unchanged.
140
- * This prevents "undefined" or "null" values in logs.
141
- *
142
- * Automatically adds both OpenTelemetry standard fields (hex) and Datadog-specific
143
- * fields (decimal) for maximum compatibility with observability backends.
144
- *
145
- * @param obj - Object to enrich (e.g., log metadata)
146
- * @returns Object with trace context merged in, or unchanged if no active span
147
- *
148
- * @example
149
- * ```typescript
150
- * import { enrichWithTraceContext } from 'autotel/trace-helpers';
151
- *
152
- * // Inside a trace operation:
153
- * const enriched = enrichWithTraceContext({ userId: '123' });
154
- * // {
155
- * // userId: '123',
156
- * // traceId: '4bf92f3577b34da6a3ce929d0e0e4736',
157
- * // spanId: '00f067aa0ba902b7',
158
- * // correlationId: '4bf92f3577b34da6',
159
- * // 'dd.trace_id': '12007117331170166582', // Datadog decimal format
160
- * // 'dd.span_id': '67667974448284583' // Datadog decimal format
161
- * // }
162
- *
163
- * // Outside trace operation:
164
- * const unchanged = enrichWithTraceContext({ userId: '123' });
165
- * // { userId: '123' } - no trace fields added
166
- * ```
167
- */
168
- export function enrichWithTraceContext<T extends Record<string, unknown>>(
169
- obj: T,
170
- ): T {
171
- const context = getTraceContext();
172
- return context ? ({ ...obj, ...context } as T) : obj;
173
- }
174
-
175
- /**
176
- * Check if currently in a trace context
177
- *
178
- * Useful for conditional logic based on trace presence
179
- *
180
- * @returns true if active span exists, false otherwise
181
- *
182
- * @example
183
- * ```typescript
184
- * import { isTracing } from 'autotel/trace-helpers';
185
- *
186
- * if (isTracing()) {
187
- * // Add expensive debug metadata only when tracing
188
- * logger.debug('Detailed context', expensiveDebugData());
189
- * }
190
- * ```
191
- */
192
- export function isTracing(): boolean {
193
- return trace.getActiveSpan() !== undefined;
194
- }
195
-
196
- /**
197
- * Get a tracer instance for creating custom spans
198
- *
199
- * Use this when you need low-level control over span lifecycle.
200
- * For most use cases, prefer trace(), span(), or instrument() instead.
201
- *
202
- * @param name - Tracer name (usually your service or module name)
203
- * @param version - Optional version string
204
- * @returns OpenTelemetry Tracer instance
205
- *
206
- * @example Basic usage
207
- * ```typescript
208
- * import { getTracer } from 'autotel';
209
- *
210
- * const tracer = getTracer('my-service');
211
- * const span = tracer.startSpan('custom.operation');
212
- * try {
213
- * // Your logic
214
- * span.setAttribute('key', 'value');
215
- * } finally {
216
- * span.end();
217
- * }
218
- * ```
219
- *
220
- * @example With AI SDK
221
- * ```typescript
222
- * import { getTracer } from 'autotel';
223
- * import { generateText } from 'ai';
224
- *
225
- * const tracer = getTracer('ai-agent');
226
- * const result = await generateText({
227
- * model: myModel,
228
- * prompt: 'Hello',
229
- * experimental_telemetry: {
230
- * isEnabled: true,
231
- * tracer,
232
- * },
233
- * });
234
- * ```
235
- */
236
- export function getTracer(name: string, version?: string): Tracer {
237
- return trace.getTracer(name, version);
238
- }
239
-
240
- /**
241
- * Get the currently active span
242
- *
243
- * Returns undefined if no span is currently active.
244
- * Useful for adding attributes to the current span.
245
- *
246
- * @returns Active span or undefined
247
- *
248
- * @example Adding attributes to active span
249
- * ```typescript
250
- * import { getActiveSpan } from 'autotel';
251
- *
252
- * const span = getActiveSpan();
253
- * if (span) {
254
- * span.setAttribute('user.id', userId);
255
- * span.setAttribute('user.action', 'click');
256
- * }
257
- * ```
258
- *
259
- * @example Checking span status
260
- * ```typescript
261
- * import { getActiveSpan, SpanStatusCode } from 'autotel';
262
- *
263
- * const span = getActiveSpan();
264
- * if (span?.isRecording()) {
265
- * span.setStatus({ code: SpanStatusCode.OK });
266
- * }
267
- * ```
268
- */
269
- export function getActiveSpan(): Span | undefined {
270
- return trace.getActiveSpan();
271
- }
272
-
273
- /**
274
- * Get the currently active OpenTelemetry context
275
- *
276
- * The context contains the active span and any baggage.
277
- * Useful for context propagation and custom instrumentation.
278
- *
279
- * @returns Current active context
280
- *
281
- * @example Propagating context
282
- * ```typescript
283
- * import { getActiveContext } from 'autotel';
284
- *
285
- * const currentContext = getActiveContext();
286
- * // Pass context to another function or service
287
- * ```
288
- *
289
- * @example With context injection
290
- * ```typescript
291
- * import { getActiveContext, injectTraceContext } from 'autotel';
292
- *
293
- * const headers = {};
294
- * injectTraceContext(headers);
295
- * // Headers now contain trace propagation data
296
- * ```
297
- */
298
- export function getActiveContext(): Context {
299
- // Check stored context first (from baggage setters), then fall back to active context
300
- // This ensures ctx.setBaggage() changes are visible to OpenTelemetry operations
301
- try {
302
- const { getActiveContextWithBaggage } = requireModule<{
303
- getActiveContextWithBaggage: () => Context;
304
- }>('./trace-context');
305
- return getActiveContextWithBaggage();
306
- } catch {
307
- // Fallback if trace-context isn't available
308
- return context.active();
309
- }
310
- }
311
-
312
- /**
313
- * Run a function with a specific span set as active
314
- *
315
- * This is a convenience wrapper around the two-step process of
316
- * setting a span in context and running code within that context.
317
- *
318
- * @param span - The span to set as active
319
- * @param fn - Function to execute with the span active
320
- * @returns The return value of the function
321
- *
322
- * @example Running code with a custom span
323
- * ```typescript
324
- * import { getTracer, runWithSpan } from 'autotel';
325
- *
326
- * const tracer = getTracer('my-service');
327
- * const span = tracer.startSpan('background.job');
328
- *
329
- * try {
330
- * const result = await runWithSpan(span, async () => {
331
- * // Any spans created here will be children of 'background.job'
332
- * await processData();
333
- * return { success: true };
334
- * });
335
- * console.log(result);
336
- * } finally {
337
- * span.end();
338
- * }
339
- * ```
340
- *
341
- * @example Testing with custom spans
342
- * ```typescript
343
- * import { runWithSpan, otelTrace } from 'autotel';
344
- *
345
- * const tracer = otelTrace.getTracer('test');
346
- * const span = tracer.startSpan('test.operation');
347
- *
348
- * const result = runWithSpan(span, () => {
349
- * // Code under test runs with this span as active
350
- * return myFunction();
351
- * });
352
- *
353
- * span.end();
354
- * ```
355
- */
356
- export function runWithSpan<T>(span: Span, fn: () => T): T {
357
- const ctx = trace.setSpan(context.active(), span);
358
- return context.with(ctx, fn);
359
- }
360
-
361
- /**
362
- * Finalize a span with appropriate status and optional error recording
363
- *
364
- * This is a convenience function that:
365
- * - Records exceptions if an error is provided
366
- * - Sets span status to ERROR if error exists, OK otherwise
367
- * - Ends the span
368
- *
369
- * @param span - The span to finalize
370
- * @param error - Optional error to record
371
- *
372
- * @example Without error (success case)
373
- * ```typescript
374
- * import { getTracer, finalizeSpan } from 'autotel';
375
- *
376
- * const tracer = getTracer('my-service');
377
- * const span = tracer.startSpan('operation');
378
- *
379
- * try {
380
- * await doWork();
381
- * finalizeSpan(span);
382
- * } catch (error) {
383
- * finalizeSpan(span, error);
384
- * throw error;
385
- * }
386
- * ```
387
- *
388
- * @example With error
389
- * ```typescript
390
- * import { getTracer, finalizeSpan } from 'autotel';
391
- *
392
- * const tracer = getTracer('my-service');
393
- * const span = tracer.startSpan('operation');
394
- *
395
- * try {
396
- * await riskyOperation();
397
- * finalizeSpan(span);
398
- * } catch (error) {
399
- * finalizeSpan(span, error); // Records exception and sets ERROR status
400
- * throw error;
401
- * }
402
- * ```
403
- *
404
- * @example In instrumentation
405
- * ```typescript
406
- * import { getTracer, runWithSpan, finalizeSpan } from 'autotel';
407
- *
408
- * function instrumentedQuery(query: string) {
409
- * const tracer = getTracer('db');
410
- * const span = tracer.startSpan('db.query');
411
- *
412
- * return runWithSpan(span, () => {
413
- * try {
414
- * const result = executeQuery(query);
415
- * finalizeSpan(span);
416
- * return result;
417
- * } catch (error) {
418
- * finalizeSpan(span, error);
419
- * throw error;
420
- * }
421
- * });
422
- * }
423
- * ```
424
- */
425
- export function finalizeSpan(span: Span, error?: unknown): void {
426
- if (error) {
427
- if (error instanceof Error) {
428
- span.recordException(error);
429
- } else {
430
- span.recordException(new Error(String(error)));
431
- }
432
- span.setStatus({ code: SpanStatusCode.ERROR });
433
- } else {
434
- span.setStatus({ code: SpanStatusCode.OK });
435
- }
436
- span.end();
437
- }
438
-
439
- /**
440
- * Creates a deterministic trace ID from a seed string.
441
- *
442
- * Generates a consistent 128-bit trace ID (32 hex characters) from an input seed
443
- * using SHA-256 hashing. Useful for correlating external system IDs (request IDs,
444
- * order IDs, session IDs) with OpenTelemetry trace IDs.
445
- *
446
- * **Use Cases:**
447
- * - Correlate external request IDs with traces
448
- * - Link customer support tickets to trace data
449
- * - Associate business entities (orders, sessions) with observability data
450
- * - Debug specific user flows by deterministic trace lookup
451
- *
452
- * **Important:** Only use this when you need deterministic trace IDs for correlation.
453
- * For normal tracing, let OpenTelemetry generate random trace IDs automatically.
454
- *
455
- * **Runtime Support:**
456
- * - Node.js 15+ (native crypto.subtle)
457
- * - All modern browsers
458
- * - Edge runtimes (Cloudflare Workers, Deno, etc.)
459
- *
460
- * @param seed - Input string to generate trace ID from (e.g., request ID, order ID)
461
- * @returns Promise resolving to a 32-character hex trace ID (128 bits)
462
- *
463
- * @example Correlate external request ID with trace
464
- * ```typescript
465
- * import { createDeterministicTraceId } from 'autotel/trace-helpers'
466
- * import { trace, context } from '@opentelemetry/api'
467
- *
468
- * // In middleware or request handler
469
- * const requestId = req.headers['x-request-id']
470
- * const traceId = await createDeterministicTraceId(requestId)
471
- *
472
- * // Use with manual span creation (advanced - not needed with trace/span functions)
473
- * const tracer = trace.getTracer('my-service')
474
- * const spanContext = {
475
- * traceId,
476
- * spanId: '0123456789abcdef', // Still random
477
- * traceFlags: 1
478
- * }
479
- * ```
480
- *
481
- * @example Link customer support tickets to traces
482
- * ```typescript
483
- * import { createDeterministicTraceId } from 'autotel/trace-helpers'
484
- *
485
- * // Support dashboard integration
486
- * const ticketId = 'TICKET-12345'
487
- * const traceId = await createDeterministicTraceId(ticketId)
488
- *
489
- * // Generate direct link to traces in observability backend
490
- * const traceUrl = `https://your-otel-backend.com/traces/${traceId}`
491
- * console.log(`View related traces: ${traceUrl}`)
492
- * ```
493
- *
494
- * @example Session-based correlation
495
- * ```typescript
496
- * import { createDeterministicTraceId } from 'autotel/trace-helpers'
497
- *
498
- * // Track all operations for a user session
499
- * const sessionId = req.session.id
500
- * const traceId = await createDeterministicTraceId(sessionId)
501
- *
502
- * // All operations in this session share the same trace ID
503
- * // Makes it easy to find all activity for a specific session
504
- * ```
505
- *
506
- * @public
507
- */
508
- export async function createDeterministicTraceId(
509
- seed: string,
510
- ): Promise<string> {
511
- // Encode seed string to bytes
512
- const encoder = new TextEncoder();
513
- const data = encoder.encode(seed);
514
-
515
- // Generate SHA-256 hash (256 bits)
516
- const hashBuffer = await crypto.subtle.digest('SHA-256', data);
517
-
518
- // Convert to hex string and truncate to 32 characters (128 bits)
519
- // OpenTelemetry trace IDs are 128 bits (16 bytes, 32 hex characters)
520
- const hashArray = new Uint8Array(hashBuffer);
521
- return [...hashArray]
522
- .map((byte) => byte.toString(16).padStart(2, '0'))
523
- .join('')
524
- .slice(0, 32);
525
- }
526
-
527
- /**
528
- * Flattens nested metadata objects into dot-notation span attributes.
529
- *
530
- * Converts complex nested objects into flat key-value pairs suitable for
531
- * OpenTelemetry span attributes. Non-string values are JSON serialized.
532
- * Handles serialization failures gracefully with a fallback value.
533
- *
534
- * **Use Cases:**
535
- * - Structured metadata with nested objects
536
- * - User context with multiple properties
537
- * - Request/response metadata
538
- * - Business entity attributes
539
- *
540
- * **Note:** Filters out null/undefined values automatically to keep spans clean.
541
- *
542
- * @param metadata - Nested metadata object to flatten
543
- * @param prefix - Prefix for all attribute keys (default: 'metadata')
544
- * @returns Flattened attributes as { [key: string]: string }
545
- *
546
- * @example Basic metadata flattening
547
- * ```typescript
548
- * import { flattenMetadata } from 'autotel/trace-helpers'
549
- * import { trace } from 'autotel'
550
- *
551
- * export const processOrder = trace(ctx => async (orderId: string) => {
552
- * const order = await getOrder(orderId)
553
- *
554
- * // Flatten complex order metadata
555
- * const flattened = flattenMetadata({
556
- * user: { id: order.userId, tier: 'premium' },
557
- * payment: { method: 'card', processor: 'stripe' },
558
- * items: order.items.length
559
- * })
560
- *
561
- * ctx.setAttributes(flattened)
562
- * // Results in:
563
- * // {
564
- * // 'metadata.user.id': 'user-123',
565
- * // 'metadata.user.tier': 'premium',
566
- * // 'metadata.payment.method': 'card',
567
- * // 'metadata.payment.processor': 'stripe',
568
- * // 'metadata.items': '5'
569
- * // }
570
- * })
571
- * ```
572
- *
573
- * @example Custom prefix for semantic conventions
574
- * ```typescript
575
- * import { flattenMetadata } from 'autotel/trace-helpers'
576
- * import { trace } from 'autotel'
577
- *
578
- * export const fetchUser = trace(ctx => async (userId: string) => {
579
- * const user = await db.users.findOne({ id: userId })
580
- *
581
- * // Use semantic convention prefix
582
- * const userAttrs = flattenMetadata(
583
- * {
584
- * id: user.id,
585
- * email: user.email,
586
- * plan: user.subscription.plan
587
- * },
588
- * 'user' // Custom prefix
589
- * )
590
- *
591
- * ctx.setAttributes(userAttrs)
592
- * // Results in:
593
- * // {
594
- * // 'user.id': 'user-123',
595
- * // 'user.email': 'user@example.com',
596
- * // 'user.plan': 'enterprise'
597
- * // }
598
- * })
599
- * ```
600
- *
601
- * @example With complex objects (auto-serialized)
602
- * ```typescript
603
- * import { flattenMetadata } from 'autotel/trace-helpers'
604
- * import { trace } from 'autotel'
605
- *
606
- * export const analyzeRequest = trace(ctx => async (req: Request) => {
607
- * const metadata = flattenMetadata({
608
- * headers: req.headers, // Object - will be JSON serialized
609
- * query: req.query, // Object - will be JSON serialized
610
- * timestamp: new Date() // Non-string - will be JSON serialized
611
- * })
612
- *
613
- * ctx.setAttributes(metadata)
614
- * // Results in:
615
- * // {
616
- * // 'metadata.headers': '{"accept":"application/json",...}',
617
- * // 'metadata.query': '{"page":"1","limit":"10"}',
618
- * // 'metadata.timestamp': '"2024-01-15T12:00:00.000Z"'
619
- * // }
620
- * })
621
- * ```
622
- *
623
- * @example Error handling
624
- * ```typescript
625
- * import { flattenMetadata } from 'autotel/trace-helpers'
626
- *
627
- * // Objects with circular references are handled gracefully
628
- * const circular: any = { a: 1 }
629
- * circular.self = circular
630
- *
631
- * const flattened = flattenMetadata({ data: circular })
632
- * // Results in:
633
- * // { 'metadata.data': '<serialization-failed>' }
634
- * ```
635
- *
636
- * @public
637
- */
638
- /**
639
- * Resolve a trace URL from a template string and trace ID.
640
- *
641
- * Templates use `{traceId}` as placeholder. Falls back to `OTEL_TRACE_URL_TEMPLATE` env var.
642
- *
643
- * @example
644
- * resolveTraceUrl('https://grafana.example.com/explore?traceId={traceId}', 'abc123')
645
- * // => 'https://grafana.example.com/explore?traceId=abc123'
646
- */
647
- export function resolveTraceUrl(
648
- template: string | undefined,
649
- traceId: string,
650
- ): string | undefined {
651
- const t = template ?? process.env.OTEL_TRACE_URL_TEMPLATE;
652
- if (!t) return undefined;
653
- return t.replace(/\{traceId\}/g, traceId);
654
- }
655
-
656
- export function flattenMetadata(
657
- metadata: Record<string, unknown>,
658
- prefix = 'metadata',
659
- ): Record<string, string> {
660
- const flattened: Record<string, string> = {};
661
- const seen = new WeakSet<object>(); // Track visited objects to detect cycles
662
-
663
- function flatten(obj: Record<string, unknown>, currentPrefix: string): void {
664
- for (const [key, value] of Object.entries(obj)) {
665
- // Skip null/undefined values
666
- if (value == null) continue;
667
-
668
- const attributeKey = `${currentPrefix}.${key}`;
669
-
670
- // Handle primitives directly (string, number, boolean)
671
- if (typeof value === 'string') {
672
- flattened[attributeKey] = value;
673
- continue;
674
- }
675
- if (typeof value === 'number' || typeof value === 'boolean') {
676
- flattened[attributeKey] = String(value);
677
- continue;
678
- }
679
-
680
- // Recursively flatten plain objects (with cycle detection)
681
- if (
682
- typeof value === 'object' &&
683
- value !== null &&
684
- value.constructor === Object
685
- ) {
686
- // Detect circular references
687
- if (seen.has(value)) {
688
- flattened[attributeKey] = '<circular-reference>';
689
- continue;
690
- }
691
-
692
- // Mark as visited and recursively flatten
693
- seen.add(value);
694
- flatten(value as Record<string, unknown>, attributeKey);
695
- continue;
696
- }
697
-
698
- // Serialize arrays and other non-plain objects to JSON
699
- try {
700
- flattened[attributeKey] = JSON.stringify(value);
701
- } catch {
702
- // Handle circular references or non-serializable objects
703
- flattened[attributeKey] = '<serialization-failed>';
704
- }
705
- }
706
- }
707
-
708
- flatten(metadata, prefix);
709
- return flattened;
710
- }