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