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
package/src/init.ts DELETED
@@ -1,2312 +0,0 @@
1
- /**
2
- * Simplified initialization for autotel
3
- *
4
- * Single init() function with sensible defaults.
5
- * Replaces initInstrumentation() and separate events config.
6
- */
7
-
8
- import { NodeSDK } from '@opentelemetry/sdk-node';
9
- import type { NodeSDKConfiguration } from '@opentelemetry/sdk-node';
10
- import {
11
- BatchSpanProcessor,
12
- type SpanProcessor,
13
- SimpleSpanProcessor,
14
- ConsoleSpanExporter,
15
- SamplingDecision,
16
- type SpanExporter,
17
- type Sampler as OtelSampler,
18
- type SamplingResult,
19
- } from '@opentelemetry/sdk-trace-base';
20
- import {
21
- resourceFromAttributes,
22
- type Resource,
23
- } from '@opentelemetry/resources';
24
- import {
25
- ATTR_SERVICE_NAME,
26
- ATTR_SERVICE_VERSION,
27
- } from '@opentelemetry/semantic-conventions';
28
- import type { Sampler, SamplingPreset } from './sampling';
29
- import { samplingPresets, resolveSamplingPreset } from './sampling';
30
- import type { EventSubscriber } from './event-subscriber';
31
- import type { Logger } from './logger';
32
- import type { Attributes, Context, SpanKind, Link } from '@opentelemetry/api';
33
- import type { ValidationConfig } from './validation';
34
- import {
35
- PeriodicExportingMetricReader,
36
- type MetricReader,
37
- } from '@opentelemetry/sdk-metrics';
38
- import { OTLPMetricExporter as OTLPMetricExporterHTTP } from '@opentelemetry/exporter-metrics-otlp-http';
39
- import { OTLPTraceExporter as OTLPTraceExporterHTTP } from '@opentelemetry/exporter-trace-otlp-http';
40
- import { OTLPLogExporter as OTLPLogExporterHTTP } from '@opentelemetry/exporter-logs-otlp-http';
41
- import type { PushMetricExporter } from '@opentelemetry/sdk-metrics';
42
- import {
43
- BatchLogRecordProcessor,
44
- type LogRecordExporter,
45
- type LogRecordProcessor,
46
- } from '@opentelemetry/sdk-logs';
47
- import {
48
- buildPostHogLogProcessors,
49
- RedactingLogRecordProcessor,
50
- } from './posthog-logs';
51
- import { TailSamplingSpanProcessor } from './tail-sampling-processor';
52
- import { BaggageSpanProcessor } from './baggage-span-processor';
53
- import {
54
- FilteringSpanProcessor,
55
- type SpanFilterPredicate,
56
- } from './filtering-span-processor';
57
- import {
58
- SpanNameNormalizingProcessor,
59
- type SpanNameNormalizerConfig,
60
- } from './span-name-normalizer';
61
- import {
62
- AttributeRedactingProcessor,
63
- normalizeAttributeRedactorConfig,
64
- type AttributeRedactorConfig,
65
- type AttributeRedactorPreset,
66
- } from './attribute-redacting-processor';
67
- import { createStringRedactor, type StringRedactor } from './redact-values';
68
- import { PrettyConsoleExporter } from './pretty-console-exporter';
69
- import { resolveConfigFromEnv } from './env-config';
70
- import { loadYamlConfig } from './yaml-config';
71
- import { requireModule, safeRequire } from './node-require';
72
- import {
73
- CanonicalLogLineProcessor,
74
- type CanonicalLogLineOptions,
75
- } from './processors/canonical-log-line-processor';
76
- import type { EventsConfig } from './events-config';
77
- import { resolveDevtoolsConfig, type AutotelDevtoolsConfig } from './devtools';
78
-
79
- /**
80
- * Silent logger (no-op) - used as default when user doesn't provide one.
81
- * Internal autotel logs are silent by default to avoid spam.
82
- * Users can import { autotelLogger } from 'autotel/logger' to create their own.
83
- */
84
- const silentLogger: Logger = {
85
- info: () => {},
86
- warn: () => {},
87
- error: () => {},
88
- debug: () => {},
89
- };
90
-
91
- /**
92
- * Adapts an Autotel Sampler to the OTel SDK Sampler interface.
93
- */
94
- function toOtelSampler(sampler: Sampler): OtelSampler {
95
- return {
96
- shouldSample(
97
- _context: Context,
98
- _traceId: string,
99
- spanName: string,
100
- _spanKind: SpanKind,
101
- _attributes: Attributes,
102
- links: Link[],
103
- ): SamplingResult {
104
- const shouldTrace = sampler.shouldSample({
105
- operationName: spanName,
106
- args: [],
107
- links,
108
- });
109
- return {
110
- decision: shouldTrace
111
- ? SamplingDecision.RECORD_AND_SAMPLED
112
- : SamplingDecision.NOT_RECORD,
113
- };
114
- },
115
- toString(): string {
116
- return `AutotelSamplerAdapter`;
117
- },
118
- };
119
- }
120
-
121
- // Type imports for exporters
122
- type OTLPExporterConfig = {
123
- url?: string;
124
- headers?: Record<string, string>;
125
- timeoutMillis?: number;
126
- concurrencyLimit?: number;
127
- };
128
-
129
- // Lazy-load gRPC exporters (optional peer dependencies)
130
- let OTLPTraceExporterGRPC:
131
- | (new (config: OTLPExporterConfig) => SpanExporter)
132
- | undefined;
133
- let OTLPMetricExporterGRPC:
134
- | (new (config: OTLPExporterConfig) => PushMetricExporter)
135
- | undefined;
136
- let OTLPLogExporterGRPC:
137
- | (new (config: OTLPExporterConfig) => LogRecordExporter)
138
- | undefined;
139
-
140
- /**
141
- * Helper: Lazy-load gRPC trace exporter
142
- */
143
- function loadGRPCTraceExporter(): new (
144
- config: OTLPExporterConfig,
145
- ) => SpanExporter {
146
- if (OTLPTraceExporterGRPC) return OTLPTraceExporterGRPC;
147
-
148
- try {
149
- // Dynamic import for optional peer dependency
150
- const grpcModule = requireModule<{
151
- OTLPTraceExporter: new (config: OTLPExporterConfig) => SpanExporter;
152
- }>('@opentelemetry/exporter-trace-otlp-grpc');
153
- OTLPTraceExporterGRPC = grpcModule.OTLPTraceExporter;
154
- return OTLPTraceExporterGRPC;
155
- } catch {
156
- throw new Error(
157
- 'gRPC trace exporter not found. Install @opentelemetry/exporter-trace-otlp-grpc',
158
- );
159
- }
160
- }
161
-
162
- /**
163
- * Helper: Lazy-load gRPC metric exporter
164
- */
165
- function loadGRPCMetricExporter(): new (
166
- config: OTLPExporterConfig,
167
- ) => PushMetricExporter {
168
- if (OTLPMetricExporterGRPC) return OTLPMetricExporterGRPC;
169
-
170
- try {
171
- // Dynamic import for optional peer dependency
172
- const grpcModule = requireModule<{
173
- OTLPMetricExporter: new (
174
- config: OTLPExporterConfig,
175
- ) => PushMetricExporter;
176
- }>('@opentelemetry/exporter-metrics-otlp-grpc');
177
- OTLPMetricExporterGRPC = grpcModule.OTLPMetricExporter;
178
- return OTLPMetricExporterGRPC;
179
- } catch {
180
- throw new Error(
181
- 'gRPC metric exporter not found. Install @opentelemetry/exporter-metrics-otlp-grpc',
182
- );
183
- }
184
- }
185
-
186
- /**
187
- * Helper: Create trace exporter based on protocol
188
- */
189
- function createTraceExporter(
190
- protocol: 'http' | 'grpc',
191
- config: OTLPExporterConfig,
192
- ): SpanExporter {
193
- if (protocol === 'grpc') {
194
- const Exporter = loadGRPCTraceExporter();
195
- return new Exporter(config);
196
- }
197
-
198
- // Default: HTTP
199
- return new OTLPTraceExporterHTTP(config);
200
- }
201
-
202
- /**
203
- * Helper: Create metric exporter based on protocol
204
- */
205
- function createMetricExporter(
206
- protocol: 'http' | 'grpc',
207
- config: OTLPExporterConfig,
208
- ): PushMetricExporter {
209
- if (protocol === 'grpc') {
210
- const Exporter = loadGRPCMetricExporter();
211
- return new Exporter(config);
212
- }
213
-
214
- // Default: HTTP
215
- return new OTLPMetricExporterHTTP(config);
216
- }
217
-
218
- /**
219
- * Helper: Lazy-load gRPC log exporter
220
- */
221
- function loadGRPCLogExporter(): new (
222
- config: OTLPExporterConfig,
223
- ) => LogRecordExporter {
224
- if (OTLPLogExporterGRPC) return OTLPLogExporterGRPC;
225
-
226
- try {
227
- const grpcModule = requireModule<{
228
- OTLPLogExporter: new (config: OTLPExporterConfig) => LogRecordExporter;
229
- }>('@opentelemetry/exporter-logs-otlp-grpc');
230
- OTLPLogExporterGRPC = grpcModule.OTLPLogExporter;
231
- return OTLPLogExporterGRPC;
232
- } catch {
233
- throw new Error(
234
- 'gRPC log exporter not found. Install @opentelemetry/exporter-logs-otlp-grpc',
235
- );
236
- }
237
- }
238
-
239
- /**
240
- * Helper: Create log exporter based on protocol
241
- */
242
- function createLogExporter(
243
- protocol: 'http' | 'grpc',
244
- config: OTLPExporterConfig,
245
- ): LogRecordExporter {
246
- if (protocol === 'grpc') {
247
- const Exporter = loadGRPCLogExporter();
248
- return new Exporter(config);
249
- }
250
-
251
- // Default: HTTP
252
- return new OTLPLogExporterHTTP(config);
253
- }
254
-
255
- /**
256
- * Helper: Resolve protocol from config and environment
257
- */
258
- function resolveProtocol(configProtocol?: 'http' | 'grpc'): 'http' | 'grpc' {
259
- // 1. Check config parameter (highest priority)
260
- if (configProtocol === 'grpc' || configProtocol === 'http') {
261
- return configProtocol;
262
- }
263
-
264
- // 2. Check OTEL_EXPORTER_OTLP_PROTOCOL env var
265
- const envProtocol = process.env.OTEL_EXPORTER_OTLP_PROTOCOL;
266
- if (envProtocol === 'grpc') return 'grpc';
267
- if (envProtocol === 'http/protobuf' || envProtocol === 'http') return 'http';
268
-
269
- // 3. Default to HTTP
270
- return 'http';
271
- }
272
-
273
- /**
274
- * Helper: Adjust endpoint URL for protocol
275
- * gRPC exporters don't need the /v1/traces or /v1/metrics path
276
- * HTTP exporters need the full path
277
- */
278
- function formatEndpointUrl(
279
- endpoint: string,
280
- signal: 'traces' | 'metrics' | 'logs',
281
- protocol: 'http' | 'grpc',
282
- ): string {
283
- if (protocol === 'grpc') {
284
- // gRPC: strip any paths, return base endpoint
285
- return endpoint.replace(/\/(v1\/)?(traces|metrics|logs)$/, '');
286
- }
287
-
288
- // HTTP: append signal path if not present
289
- if (!endpoint.endsWith(`/v1/${signal}`)) {
290
- return `${endpoint}/v1/${signal}`;
291
- }
292
-
293
- return endpoint;
294
- }
295
-
296
- // Built-in logger is created dynamically in init() with service name
297
-
298
- export interface AutotelConfig {
299
- /** Service name (required) */
300
- service: string;
301
-
302
- /**
303
- * Local developer UX for autotel-devtools.
304
- *
305
- * - `true`: send traces, metrics, and logs to `http://127.0.0.1:4318`
306
- * - `{ embedded: true }`: attempt to start `autotel-devtools` automatically
307
- *
308
- * When enabled:
309
- * - `endpoint` defaults to the local devtools URL
310
- * - `logs` default to `true` unless explicitly set
311
- *
312
- * This keeps production config unchanged while making local debugging
313
- * effectively zero-config.
314
- */
315
- devtools?: boolean | AutotelDevtoolsConfig;
316
-
317
- /** Event subscribers - bring your own (PostHog, Mixpanel, etc.) */
318
- subscribers?: EventSubscriber[];
319
-
320
- /**
321
- * Additional OpenTelemetry instrumentations to register (raw OTel classes).
322
- * Useful when you need custom instrumentation configs or instrumentations
323
- * not covered by autoInstrumentations.
324
- *
325
- * **Important:** If you need custom instrumentation configs (like `requireParentSpan: false`),
326
- * use EITHER manual instrumentations OR autoInstrumentations, not both for the same library.
327
- * Manual instrumentations always take precedence over auto-instrumentations.
328
- *
329
- * @example Manual instrumentations with custom config
330
- * ```typescript
331
- * import { MongoDBInstrumentation } from '@opentelemetry/instrumentation-mongodb'
332
- *
333
- * init({
334
- * service: 'my-app',
335
- * autoInstrumentations: false, // Disable auto-instrumentations
336
- * instrumentations: [
337
- * new MongoDBInstrumentation({
338
- * requireParentSpan: false // Custom config
339
- * })
340
- * ]
341
- * })
342
- * ```
343
- *
344
- * @example Mix auto + manual (auto for most, manual for specific configs)
345
- * ```typescript
346
- * import { MongoDBInstrumentation } from '@opentelemetry/instrumentation-mongodb'
347
- *
348
- * init({
349
- * service: 'my-app',
350
- * autoInstrumentations: ['http', 'express'], // Auto for these
351
- * instrumentations: [
352
- * new MongoDBInstrumentation({
353
- * requireParentSpan: false // Manual config for MongoDB
354
- * })
355
- * ]
356
- * })
357
- * ```
358
- */
359
- instrumentations?: NodeSDKConfiguration['instrumentations'];
360
-
361
- /**
362
- * Simple names for auto-instrumentation.
363
- * Uses @opentelemetry/auto-instrumentations-node (peer dependency).
364
- *
365
- * **Important:** If you provide manual instrumentations for the same library,
366
- * the manual config takes precedence and auto-instrumentation for that library is disabled.
367
- *
368
- * @example Enable all auto-instrumentations (simple approach)
369
- * ```typescript
370
- * init({
371
- * service: 'my-app',
372
- * autoInstrumentations: true // Enable all with defaults
373
- * })
374
- * ```
375
- *
376
- * @example Enable specific auto-instrumentations
377
- * ```typescript
378
- * init({
379
- * service: 'my-app',
380
- * autoInstrumentations: ['express', 'pino', 'http']
381
- * })
382
- * ```
383
- *
384
- * @example Configure specific auto-instrumentations
385
- * ```typescript
386
- * init({
387
- * service: 'my-app',
388
- * autoInstrumentations: {
389
- * express: { enabled: true },
390
- * pino: { enabled: true },
391
- * http: { enabled: false }
392
- * }
393
- * })
394
- * ```
395
- *
396
- * @example Manual config when you need custom settings
397
- * ```typescript
398
- * import { MongoDBInstrumentation } from '@opentelemetry/instrumentation-mongodb'
399
- *
400
- * init({
401
- * service: 'my-app',
402
- * autoInstrumentations: false, // Use manual control
403
- * instrumentations: [
404
- * new MongoDBInstrumentation({
405
- * requireParentSpan: false // Custom config not available with auto
406
- * })
407
- * ]
408
- * })
409
- * ```
410
- */
411
- autoInstrumentations?:
412
- | string[]
413
- | boolean
414
- | Record<string, { enabled?: boolean }>;
415
-
416
- /**
417
- * OTLP endpoint for traces/metrics/logs
418
- * Only used if you don't provide custom exporters/processors
419
- * @default process.env.OTLP_ENDPOINT || 'http://localhost:4318'
420
- */
421
- endpoint?: string;
422
-
423
- /**
424
- * Custom span processors for traces (supports multiple processors)
425
- * Allows you to use any backend: Jaeger, Zipkin, Datadog, New Relic, etc.
426
- * If not provided, defaults to OTLP with tail sampling
427
- *
428
- * @example Multiple processors
429
- * ```typescript
430
- * import { JaegerExporter } from '@opentelemetry/exporter-jaeger'
431
- * import { BatchSpanProcessor, SimpleSpanProcessor, ConsoleSpanExporter } from '@opentelemetry/sdk-trace-base'
432
- *
433
- * init({
434
- * service: 'my-app',
435
- * spanProcessors: [
436
- * new BatchSpanProcessor(new JaegerExporter()),
437
- * new SimpleSpanProcessor(new ConsoleSpanExporter()) // Debug alongside production
438
- * ]
439
- * })
440
- * ```
441
- *
442
- * @example Single processor
443
- * ```typescript
444
- * import { ConsoleSpanExporter, SimpleSpanProcessor } from '@opentelemetry/sdk-trace-base'
445
- *
446
- * init({
447
- * service: 'my-app',
448
- * spanProcessors: [new SimpleSpanProcessor(new ConsoleSpanExporter())]
449
- * })
450
- * ```
451
- */
452
- spanProcessors?: SpanProcessor[];
453
-
454
- /**
455
- * Custom span processor for traces (single-item alias of spanProcessors)
456
- * @deprecated Use spanProcessors for consistency with other multi-value config fields
457
- */
458
- spanProcessor?: SpanProcessor;
459
-
460
- /**
461
- * Custom span exporters for traces (alternative to spanProcessors, supports multiple exporters)
462
- * Provide either spanProcessors OR spanExporters, not both
463
- * Each exporter will be wrapped in TailSamplingSpanProcessor + BatchSpanProcessor
464
- *
465
- * @example Multiple exporters
466
- * ```typescript
467
- * import { ZipkinExporter } from '@opentelemetry/exporter-zipkin'
468
- * import { JaegerExporter } from '@opentelemetry/exporter-jaeger'
469
- *
470
- * init({
471
- * service: 'my-app',
472
- * spanExporters: [
473
- * new ZipkinExporter({ url: 'http://localhost:9411/api/v2/spans' }),
474
- * new JaegerExporter() // Send to multiple backends simultaneously
475
- * ]
476
- * })
477
- * ```
478
- *
479
- * @example Single exporter
480
- * ```typescript
481
- * import { ZipkinExporter } from '@opentelemetry/exporter-zipkin'
482
- *
483
- * init({
484
- * service: 'my-app',
485
- * spanExporters: [new ZipkinExporter({ url: 'http://localhost:9411/api/v2/spans' })]
486
- * })
487
- * ```
488
- */
489
- spanExporters?: SpanExporter[];
490
-
491
- /**
492
- * Custom span exporter for traces (single-item alias of spanExporters)
493
- * @deprecated Use spanExporters for consistency with other multi-value config fields
494
- */
495
- spanExporter?: SpanExporter;
496
-
497
- /**
498
- * Custom metric readers (supports multiple readers)
499
- * Allows sending metrics to multiple backends: OTLP, Prometheus, custom readers
500
- * Defaults to OTLP metrics exporter when metrics are enabled.
501
- *
502
- * @example Multiple metric readers
503
- * ```typescript
504
- * import { PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics'
505
- * import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-http'
506
- * import { PrometheusExporter } from '@opentelemetry/exporter-prometheus'
507
- *
508
- * init({
509
- * service: 'my-app',
510
- * metricReaders: [
511
- * new PeriodicExportingMetricReader({ exporter: new OTLPMetricExporter() }),
512
- * new PrometheusExporter() // Export to multiple backends
513
- * ]
514
- * })
515
- * ```
516
- */
517
- metricReaders?: MetricReader[];
518
-
519
- /**
520
- * Custom metric reader (single-item alias of metricReaders)
521
- * @deprecated Use metricReaders for consistency with other multi-value config fields
522
- */
523
- metricReader?: MetricReader;
524
-
525
- /**
526
- * Custom log record processors. When omitted, logs are not configured.
527
- */
528
- logRecordProcessors?: LogRecordProcessor[];
529
-
530
- /**
531
- * Custom log record processor (single-item alias of logRecordProcessors)
532
- * @deprecated Use logRecordProcessors for consistency with other multi-value config fields
533
- */
534
- logRecordProcessor?: LogRecordProcessor;
535
-
536
- /**
537
- * PostHog integration - auto-configures OTLP log exporter.
538
- *
539
- * @example
540
- * ```typescript
541
- * init({
542
- * service: 'my-app',
543
- * posthog: { url: 'https://us.i.posthog.com/i/v1/logs?token=phc_xxx' }
544
- * });
545
- * ```
546
- *
547
- * Also reads from POSTHOG_LOGS_URL environment variable as fallback.
548
- */
549
- posthog?: { url: string };
550
-
551
- /** Additional resource attributes to merge with defaults. */
552
- resourceAttributes?: Attributes;
553
-
554
- /** Provide a fully custom Resource to merge (advanced use case). */
555
- resource?: Resource;
556
-
557
- /**
558
- * Headers for OTLP exporters. Accepts either an object map or
559
- * a "key=value" comma separated string.
560
- *
561
- * @example
562
- * ```typescript
563
- * init({
564
- * service: 'my-app',
565
- * endpoint: 'https://api.honeycomb.io',
566
- * headers: { 'x-honeycomb-team': 'YOUR_API_KEY' }
567
- * })
568
- * ```
569
- */
570
- headers?: Record<string, string> | string;
571
-
572
- /**
573
- * OTLP protocol to use for traces, metrics, and logs
574
- * - 'http': HTTP/protobuf (default, uses port 4318)
575
- * - 'grpc': gRPC (uses port 4317)
576
- *
577
- * Can be overridden with OTEL_EXPORTER_OTLP_PROTOCOL env var.
578
- *
579
- * Note: gRPC exporters are optional peer dependencies. Install them with:
580
- * ```bash
581
- * pnpm add @opentelemetry/exporter-trace-otlp-grpc @opentelemetry/exporter-metrics-otlp-grpc
582
- * ```
583
- *
584
- * @example HTTP (default)
585
- * ```typescript
586
- * init({
587
- * service: 'my-app',
588
- * protocol: 'http', // or omit (defaults to http)
589
- * endpoint: 'http://localhost:4318'
590
- * })
591
- * ```
592
- *
593
- * @example gRPC
594
- * ```typescript
595
- * init({
596
- * service: 'my-app',
597
- * protocol: 'grpc',
598
- * endpoint: 'grpc://localhost:4317'
599
- * })
600
- * ```
601
- *
602
- * @default 'http'
603
- */
604
- protocol?: 'http' | 'grpc';
605
-
606
- /**
607
- * Optional factory to build a customised NodeSDK instance from our defaults.
608
- */
609
- sdkFactory?: (defaults: Partial<NodeSDKConfiguration>) => NodeSDK;
610
-
611
- /**
612
- * Infrastructure metrics configuration
613
- * - true: always enabled (default)
614
- * - false: always disabled
615
- * - 'auto': always enabled (same as true)
616
- *
617
- * Can be overridden with AUTOTEL_METRICS=on|off env var
618
- */
619
- metrics?: boolean | 'auto';
620
-
621
- /**
622
- * OTLP logs configuration
623
- * - true: auto-configure OTLP log exporter from endpoint
624
- * - false: disabled (default)
625
- * - 'auto': same as false (opt-in only)
626
- *
627
- * When enabled and an endpoint is configured, autotel will automatically
628
- * create a BatchLogRecordProcessor with an OTLPLogExporter - no manual
629
- * imports needed. Works alongside logRecordProcessors (additive).
630
- *
631
- * Requires @opentelemetry/sdk-logs and @opentelemetry/exporter-logs-otlp-http
632
- * (or -grpc) as peer dependencies.
633
- *
634
- * Can be overridden with AUTOTEL_LOGS=on|off env var.
635
- *
636
- * @example
637
- * ```typescript
638
- * init({
639
- * service: 'my-app',
640
- * endpoint: 'http://localhost:4318',
641
- * logs: true,
642
- * });
643
- * ```
644
- */
645
- logs?: boolean | 'auto';
646
-
647
- /** Sampling strategy - takes precedence over `sampling` preset */
648
- sampler?: Sampler;
649
-
650
- /**
651
- * Sampling preset shorthand — resolves to a pre-configured sampler.
652
- * If both `sampler` and `sampling` are provided, `sampler` takes precedence.
653
- *
654
- * @default 'production' (10% baseline + always-on for errors/slow)
655
- *
656
- * **Footgun for one-shot scripts:** the default samples ~90% of spans away,
657
- * which means a fixture-capture script that emits a single normal span
658
- * almost always sees zero output. For local debugging and capture use:
659
- *
660
- * ```typescript
661
- * init({
662
- * service: 'fixture-capture',
663
- * spanProcessors: [new SimpleSpanProcessor(new InMemorySpanExporter())],
664
- * sampling: 'development', // capture EVERY span
665
- * });
666
- * ```
667
- *
668
- * Read `exporter.getFinishedSpans()` **before** calling `shutdown()` —
669
- * `InMemorySpanExporter.shutdown()` resets state.
670
- */
671
- sampling?: SamplingPreset;
672
-
673
- /** Service version (default: auto-detect from package.json or '1.0.0') */
674
- version?: string;
675
-
676
- /** Environment (default: process.env.NODE_ENV || 'development') */
677
- environment?: string;
678
-
679
- /**
680
- * Logger instance for internal autotel diagnostic messages
681
- *
682
- * This logger is used by autotel internally to log initialization, warnings,
683
- * and debug information. Any logger with info/warn/error/debug methods works.
684
- *
685
- * **For OTel instrumentation of your application logs**, use the `autoInstrumentations` option:
686
- * - `autoInstrumentations: ['pino']` - Injects traceId/spanId into Pino logs
687
- * - `autoInstrumentations: ['winston']` - Injects traceId/spanId into Winston logs
688
- *
689
- * Default: silent logger (no-op)
690
- *
691
- * @example Pino with OTel instrumentation
692
- * ```typescript
693
- * import pino from 'pino'
694
- * import { init } from 'autotel'
695
- *
696
- * const logger = pino({ level: 'info' })
697
- * init({
698
- * service: 'my-app',
699
- * logger, // For autotel's internal logs
700
- * autoInstrumentations: ['pino'] // For OTel trace context in YOUR logs
701
- * })
702
- * ```
703
- *
704
- * @example Custom logger for autotel diagnostics
705
- * ```typescript
706
- * const logger = {
707
- * info: (msg, extra) => console.log(msg, extra),
708
- * warn: (msg, extra) => console.warn(msg, extra),
709
- * error: (msg, err, extra) => console.error(msg, err, extra),
710
- * debug: (msg, extra) => console.debug(msg, extra),
711
- * }
712
- * init({ service: 'my-app', logger })
713
- * ```
714
- */
715
- logger?: Logger;
716
-
717
- /**
718
- * Flush events queue when root spans end
719
- * - true: Flush on root span completion (default)
720
- * - false: Use batching (events flush every 10 seconds automatically)
721
- *
722
- * Only flushes on root spans to avoid excessive network calls.
723
- * Default is true for serverless/short-lived processes. Set to false
724
- * for long-running services where batching is more efficient.
725
- */
726
- flushOnRootSpanEnd?: boolean;
727
-
728
- /**
729
- * Force-flush OpenTelemetry spans on shutdown (default: false)
730
- *
731
- * When enabled, spans are force-flushed along with events on root
732
- * span completion. This is useful for serverless/short-lived processes where
733
- * spans may not export before the process ends.
734
- *
735
- * - true: Force-flush spans on root span completion (~50-200ms latency)
736
- * - false: Spans export via normal batch processor (default behavior)
737
- *
738
- * Only applies when flushOnRootSpanEnd is also enabled.
739
- *
740
- * Note: For edge runtimes (Cloudflare Workers, Vercel Edge), use the
741
- * 'autotel-edge' package instead, which handles this automatically.
742
- *
743
- * @example Serverless with force-flush
744
- * ```typescript
745
- * init({
746
- * service: 'my-lambda',
747
- * flushOnRootSpanEnd: true,
748
- * forceFlushOnShutdown: true, // Force-flush spans
749
- * });
750
- * ```
751
- */
752
- forceFlushOnShutdown?: boolean;
753
-
754
- /**
755
- * Automatically copy baggage entries to span attributes
756
- *
757
- * When enabled, all baggage entries are automatically added as span attributes,
758
- * making them visible in trace UIs (Jaeger, Grafana, DataDog, etc.) without
759
- * manually calling ctx.setAttribute() for each entry.
760
- *
761
- * - `true`: adds baggage with 'baggage.' prefix (e.g. baggage.tenant.id)
762
- * - `string`: uses custom prefix (e.g. 'ctx' → ctx.tenant.id, '' → tenant.id)
763
- * - `false` or omit: disabled (default)
764
- *
765
- * @default false
766
- *
767
- * @example Enable with default prefix
768
- * ```typescript
769
- * init({
770
- * service: 'my-app',
771
- * baggage: true
772
- * });
773
- *
774
- * // Now baggage automatically appears as span attributes
775
- * await withBaggage({
776
- * baggage: { 'tenant.id': 't1', 'user.id': 'u1' },
777
- * fn: async () => {
778
- * // Span has baggage.tenant.id and baggage.user.id attributes!
779
- * }
780
- * });
781
- * ```
782
- *
783
- * @example Custom prefix
784
- * ```typescript
785
- * init({
786
- * service: 'my-app',
787
- * baggage: 'ctx' // Uses 'ctx.' prefix
788
- * });
789
- * // Creates attributes: ctx.tenant.id, ctx.user.id
790
- * ```
791
- *
792
- * @example No prefix
793
- * ```typescript
794
- * init({
795
- * service: 'my-app',
796
- * baggage: '' // No prefix
797
- * });
798
- * // Creates attributes: tenant.id, user.id
799
- * ```
800
- */
801
- baggage?: boolean | string;
802
-
803
- /**
804
- * Validation configuration for events events
805
- * - Override default sensitive field patterns for redaction
806
- * - Customize max lengths, nesting depth, etc.
807
- *
808
- * @example Disable redaction for development
809
- * ```typescript
810
- * init({
811
- * service: 'my-app',
812
- * validation: {
813
- * sensitivePatterns: [] // Disable all redaction
814
- * }
815
- * })
816
- * ```
817
- *
818
- * @example Add custom patterns
819
- * ```typescript
820
- * init({
821
- * service: 'my-app',
822
- * validation: {
823
- * sensitivePatterns: [
824
- * /password/i,
825
- * /apiKey/i,
826
- * /customSecret/i // Your custom pattern
827
- * ]
828
- * }
829
- * })
830
- * ```
831
- */
832
- validation?: Partial<ValidationConfig>;
833
-
834
- /**
835
- * Events configuration for trace context, correlation IDs, and enrichment
836
- *
837
- * Controls how product events integrate with distributed tracing:
838
- * - `includeTraceContext`: Automatically include trace context in events
839
- * - `includeLinkedTraceIds`: Include full array of linked trace IDs (for batch/fan-in)
840
- * - `traceUrl`: Generate clickable trace URLs in events
841
- * - `enrichFromBaggage`: Auto-enrich events from baggage with guardrails
842
- *
843
- * @example Basic trace context
844
- * ```typescript
845
- * init({
846
- * service: 'my-app',
847
- * events: {
848
- * includeTraceContext: true
849
- * }
850
- * });
851
- * // Events now include autotel.trace_id, autotel.span_id, autotel.correlation_id
852
- * ```
853
- *
854
- * @example With clickable trace URLs
855
- * ```typescript
856
- * init({
857
- * service: 'my-app',
858
- * events: {
859
- * includeTraceContext: true,
860
- * traceUrl: (ctx) => `https://grafana.internal/explore?traceId=${ctx.traceId}`
861
- * }
862
- * });
863
- * ```
864
- *
865
- * @example With baggage enrichment
866
- * ```typescript
867
- * init({
868
- * service: 'my-app',
869
- * events: {
870
- * includeTraceContext: true,
871
- * enrichFromBaggage: {
872
- * allow: ['tenant.id', 'user.id'],
873
- * prefix: 'ctx.',
874
- * maxKeys: 10,
875
- * maxBytes: 1024
876
- * }
877
- * }
878
- * });
879
- * ```
880
- */
881
- events?: EventsConfig;
882
-
883
- /**
884
- * Debug mode for local span inspection.
885
- * Enables console output to help you see spans as they're created.
886
- *
887
- * - `true`: Raw JSON output (ConsoleSpanExporter)
888
- * - `'pretty'`: Colorized, hierarchical output (PrettyConsoleExporter)
889
- * - `false`/undefined: No console output (default)
890
- *
891
- * When enabled: Outputs spans to console AND sends to backend (if endpoint/exporter configured)
892
- *
893
- * Perfect for progressive development:
894
- * - Start with debug: 'pretty' (no endpoint) → see traces immediately with nice formatting
895
- * - Add endpoint later → console + backend, verify before choosing provider
896
- * - Remove debug in production → backend only, clean production config
897
- *
898
- * Can be overridden with AUTOTEL_DEBUG environment variable.
899
- *
900
- * @example Pretty debug output (recommended for development)
901
- * ```typescript
902
- * init({
903
- * service: 'my-app',
904
- * debug: 'pretty' // Colorized, hierarchical output
905
- * })
906
- * ```
907
- *
908
- * @example Raw JSON output (verbose)
909
- * ```typescript
910
- * init({
911
- * service: 'my-app',
912
- * debug: true // Raw ConsoleSpanExporter output
913
- * })
914
- * ```
915
- *
916
- * @example Environment variable
917
- * ```bash
918
- * AUTOTEL_DEBUG=pretty node server.js
919
- * AUTOTEL_DEBUG=true node server.js
920
- * ```
921
- */
922
- debug?: boolean | 'pretty';
923
-
924
- /**
925
- * Filter predicate to drop unwanted spans before processing.
926
- *
927
- * Useful for filtering out noisy spans from specific instrumentations
928
- * (e.g., Next.js internal spans, health check endpoints).
929
- *
930
- * The filter runs on completed spans (onEnd), so you have access to:
931
- * - `span.name` - Span name
932
- * - `span.attributes` - All span attributes
933
- * - `span.instrumentationScope` - `{ name, version }` of the instrumentation
934
- * - `span.status` - Span status code and message
935
- * - `span.duration` - Span duration as `[seconds, nanoseconds]`
936
- *
937
- * Return `true` to keep the span, `false` to drop it.
938
- *
939
- * @example Filter out Next.js instrumentation spans
940
- * ```typescript
941
- * init({
942
- * service: 'my-app',
943
- * spanFilter: (span) => span.instrumentationScope.name !== 'next.js'
944
- * })
945
- * ```
946
- *
947
- * @example Filter out health check spans
948
- * ```typescript
949
- * init({
950
- * service: 'my-app',
951
- * spanFilter: (span) => !span.name.includes('/health')
952
- * })
953
- * ```
954
- *
955
- * @example Complex filtering (multiple conditions)
956
- * ```typescript
957
- * init({
958
- * service: 'my-app',
959
- * spanFilter: (span) => {
960
- * // Drop Next.js internal spans
961
- * if (span.instrumentationScope.name === 'next.js') return false;
962
- * // Drop health checks
963
- * if (span.name.includes('/health')) return false;
964
- * // Drop very short spans (less than 1ms)
965
- * const [secs, nanos] = span.duration;
966
- * if (secs === 0 && nanos < 1_000_000) return false;
967
- * return true;
968
- * }
969
- * })
970
- * ```
971
- */
972
- spanFilter?: SpanFilterPredicate;
973
-
974
- /**
975
- * Normalize span names to reduce cardinality from dynamic path segments.
976
- *
977
- * High-cardinality span names (e.g., `/users/123/posts/456`) cause issues:
978
- * - Cost explosions in observability backends
979
- * - Cardinality limits exceeded
980
- * - Poor UX when searching/filtering traces
981
- *
982
- * The normalizer transforms dynamic segments into placeholders:
983
- * - `/users/123` → `/users/:id`
984
- * - `/items/550e8400-e29b-...` → `/items/:uuid`
985
- *
986
- * Provide either a custom function or use a built-in preset:
987
- * - `'rest-api'` - Numeric IDs, UUIDs, ObjectIds, dates, timestamps, emails
988
- * - `'graphql'` - GraphQL operation name normalization
989
- * - `'minimal'` - Only numeric IDs and UUIDs
990
- *
991
- * @example Custom normalizer function
992
- * ```typescript
993
- * init({
994
- * service: 'my-app',
995
- * spanNameNormalizer: (name) => {
996
- * return name
997
- * .replace(/\/[0-9]+/g, '/:id')
998
- * .replace(/\/[a-f0-9-]{36}/gi, '/:uuid');
999
- * }
1000
- * })
1001
- * ```
1002
- *
1003
- * @example Using built-in preset
1004
- * ```typescript
1005
- * init({
1006
- * service: 'my-app',
1007
- * spanNameNormalizer: 'rest-api'
1008
- * })
1009
- * ```
1010
- *
1011
- * @example Combining with spanFilter
1012
- * ```typescript
1013
- * init({
1014
- * service: 'my-app',
1015
- * spanNameNormalizer: 'rest-api',
1016
- * spanFilter: (span) => span.instrumentationScope.name !== 'next.js'
1017
- * })
1018
- * ```
1019
- */
1020
- spanNameNormalizer?: SpanNameNormalizerConfig;
1021
-
1022
- /**
1023
- * Automatically redact PII and sensitive data from span attributes before export.
1024
- * Critical for compliance (GDPR, PCI-DSS, HIPAA) and data security.
1025
- *
1026
- * Auto-enabled in production: when this is left unset and the resolved
1027
- * environment is `production`, the `'default'` preset is applied. Override
1028
- * with the `AUTOTEL_REDACT_PII` env var (`off` / `strict` / `pci-dss` / ...)
1029
- * or pass `false` to disable redaction entirely.
1030
- *
1031
- * Can be a preset name, custom configuration, or `false` to disable:
1032
- * - `'default'`: Emails, phones, SSNs, credit cards, sensitive keys (password, secret, token)
1033
- * - `'strict'`: Default + Bearer tokens, JWTs, API keys in values
1034
- * - `'pci-dss'`: Payment card industry focus (credit cards, CVV, card-related keys)
1035
- *
1036
- * @example Use default preset
1037
- * ```typescript
1038
- * init({
1039
- * service: 'my-app',
1040
- * attributeRedactor: 'default'
1041
- * })
1042
- * ```
1043
- *
1044
- * @example Custom patterns
1045
- * ```typescript
1046
- * init({
1047
- * service: 'my-app',
1048
- * attributeRedactor: {
1049
- * keyPatterns: [/password/i, /secret/i],
1050
- * valuePatterns: [
1051
- * { name: 'customerId', pattern: /CUST-\d{8}/g, replacement: 'CUST-***' }
1052
- * ]
1053
- * }
1054
- * })
1055
- * ```
1056
- *
1057
- * @example Custom redactor function
1058
- * ```typescript
1059
- * init({
1060
- * service: 'my-app',
1061
- * attributeRedactor: {
1062
- * redactor: (key, value) => {
1063
- * if (key === 'user.email' && typeof value === 'string') {
1064
- * return value.replace(/@.+/, '@[REDACTED]');
1065
- * }
1066
- * return value;
1067
- * }
1068
- * }
1069
- * })
1070
- * ```
1071
- */
1072
- attributeRedactor?: AttributeRedactorConfig | AttributeRedactorPreset | false;
1073
-
1074
- /**
1075
- * OpenLLMetry integration for LLM observability.
1076
- * Requires @traceloop/node-server-sdk as an optional peer dependency.
1077
- *
1078
- * @example Enable OpenLLMetry with default settings
1079
- * ```typescript
1080
- * init({
1081
- * service: 'my-app',
1082
- * openllmetry: { enabled: true }
1083
- * })
1084
- * ```
1085
- *
1086
- * @example Enable with custom options
1087
- * ```typescript
1088
- * init({
1089
- * service: 'my-app',
1090
- * openllmetry: {
1091
- * enabled: true,
1092
- * options: {
1093
- * disableBatch: process.env.NODE_ENV !== 'production',
1094
- * apiKey: process.env.TRACELOOP_API_KEY
1095
- * }
1096
- * }
1097
- * })
1098
- * ```
1099
- */
1100
- openllmetry?: {
1101
- enabled: boolean;
1102
- options?: Record<string, unknown>;
1103
- };
1104
-
1105
- /**
1106
- * Canonical log lines - automatically emit spans as wide events (canonical log lines)
1107
- *
1108
- * When enabled, each span (or root span only) is automatically emitted as a
1109
- * comprehensive log record with ALL span attributes. This implements the
1110
- * "canonical log line" pattern: one comprehensive event per request with all context.
1111
- *
1112
- * **Benefits:**
1113
- * - One log line per request with all context (wide event)
1114
- * - High-cardinality, high-dimensionality data for powerful queries
1115
- * - Automatic - no manual logging needed
1116
- * - Queryable as structured data instead of string search
1117
- *
1118
- * @example Basic usage (one canonical log line per request)
1119
- * ```typescript
1120
- * init({
1121
- * service: 'checkout-api',
1122
- * canonicalLogLines: {
1123
- * enabled: true,
1124
- * rootSpansOnly: true, // One canonical log line per request
1125
- * },
1126
- * });
1127
- * ```
1128
- *
1129
- * @example With custom logger
1130
- * ```typescript
1131
- * import pino from 'pino';
1132
- * const logger = pino();
1133
- * init({
1134
- * service: 'my-app',
1135
- * logger,
1136
- * canonicalLogLines: {
1137
- * enabled: true,
1138
- * logger, // Use Pino for canonical log lines
1139
- * rootSpansOnly: true,
1140
- * },
1141
- * });
1142
- * ```
1143
- *
1144
- * @example Custom message format
1145
- * ```typescript
1146
- * init({
1147
- * service: 'my-app',
1148
- * canonicalLogLines: {
1149
- * enabled: true,
1150
- * messageFormat: (span) => {
1151
- * const status = span.status.code === 2 ? 'ERROR' : 'SUCCESS';
1152
- * return `${span.name} [${status}]`;
1153
- * },
1154
- * },
1155
- * });
1156
- * ```
1157
- */
1158
- canonicalLogLines?: {
1159
- enabled: boolean;
1160
- /** Logger to use for emitting canonical log lines (defaults to OTel Logs API) */
1161
- logger?: Logger;
1162
- /** Only emit canonical log lines for root spans (default: false) */
1163
- rootSpansOnly?: boolean;
1164
- /** Minimum log level for canonical log lines (default: 'info') */
1165
- minLevel?: 'debug' | 'info' | 'warn' | 'error';
1166
- /** Custom message format (default: uses span name) */
1167
- messageFormat?: (
1168
- span: import('@opentelemetry/sdk-trace-base').ReadableSpan,
1169
- ) => string;
1170
- /** Whether to include resource attributes (default: true) */
1171
- includeResourceAttributes?: boolean;
1172
- /** Predicate to decide whether to emit (runs after event is built). */
1173
- shouldEmit?: CanonicalLogLineOptions['shouldEmit'];
1174
- /**
1175
- * Declarative tail sampling conditions (OR logic).
1176
- * Ignored when `shouldEmit` is provided.
1177
- * @example keep: [{ status: 500 }, { durationMs: 1000 }]
1178
- */
1179
- keep?: CanonicalLogLineOptions['keep'];
1180
- /** Callback invoked after emit for custom fan-out. */
1181
- drain?: CanonicalLogLineOptions['drain'];
1182
- /** Handler for drain failures. */
1183
- onDrainError?: CanonicalLogLineOptions['onDrainError'];
1184
- /**
1185
- * Pretty-print canonical log lines to console.
1186
- * Defaults to true when NODE_ENV is 'development'.
1187
- */
1188
- pretty?: boolean;
1189
- };
1190
-
1191
- /**
1192
- * Suppress console output while keeping OTel exporters running.
1193
- * Useful for platforms like GCP Cloud Run / AWS Lambda where stdout
1194
- * is managed externally by the platform's log collector.
1195
- *
1196
- * @default false
1197
- */
1198
- silent?: boolean;
1199
-
1200
- /**
1201
- * Minimum log level for internal autotel diagnostic messages.
1202
- * Messages below this level are dropped before processing.
1203
- *
1204
- * @default 'info'
1205
- */
1206
- minLevel?: 'debug' | 'info' | 'warn' | 'error';
1207
- }
1208
-
1209
- // Internal state
1210
- let initialized = false;
1211
- let locked = false;
1212
- let config: AutotelConfig | null = null;
1213
- let sdk: NodeSDK | null = null;
1214
- let warnedOnce = false;
1215
- let logger: Logger = silentLogger; // Silent by default - no spam
1216
- let validationConfig: Partial<ValidationConfig> | null = null;
1217
- let eventsConfig: EventsConfig | null = null;
1218
- let _stringRedactor: StringRedactor | null = null;
1219
- let _optionalRequire: typeof safeRequire = safeRequire;
1220
- let _devtoolsClose: (() => Promise<void> | void) | null = null;
1221
-
1222
- const LOG_LEVELS = { debug: 0, info: 1, warn: 2, error: 3 } as const;
1223
- type LogLevelKey = keyof typeof LOG_LEVELS;
1224
-
1225
- /**
1226
- * Lock the logger to prevent further `init()` calls.
1227
- * Use this when framework plugins set up instrumentation and you want
1228
- * to prevent accidental re-initialization from user code.
1229
- */
1230
- export function lockLogger(): void {
1231
- locked = true;
1232
- }
1233
-
1234
- /**
1235
- * Check if the logger has been locked.
1236
- */
1237
- export function isLoggerLocked(): boolean {
1238
- return locked;
1239
- }
1240
-
1241
- function createSilentLogger(): Logger {
1242
- return {
1243
- info: () => {},
1244
- warn: () => {},
1245
- error: () => {},
1246
- debug: () => {},
1247
- };
1248
- }
1249
-
1250
- function wrapLogger(
1251
- base: Logger,
1252
- silent: boolean,
1253
- minLevel: LogLevelKey,
1254
- ): Logger {
1255
- if (silent) return createSilentLogger();
1256
- const threshold = LOG_LEVELS[minLevel];
1257
- const wrap = (fn: Logger['info'], level: LogLevelKey): Logger['info'] => {
1258
- if (LOG_LEVELS[level] < threshold) {
1259
- return (() => {}) as Logger['info'];
1260
- }
1261
- return ((...args: Parameters<Logger['info']>) =>
1262
- fn(...args)) as Logger['info'];
1263
- };
1264
- return {
1265
- debug: wrap(base.debug, 'debug'),
1266
- info: wrap(base.info, 'info'),
1267
- warn: wrap(base.warn, 'warn'),
1268
- error: wrap(base.error, 'error'),
1269
- };
1270
- }
1271
-
1272
- /**
1273
- * Resolve the effective attribute redactor. Explicit config wins (`false`
1274
- * disables). Otherwise the `AUTOTEL_REDACT_PII` env var controls it, and as a
1275
- * final default PII redaction is auto-enabled in production.
1276
- */
1277
- export function resolveAttributeRedactor(
1278
- explicit:
1279
- | AttributeRedactorConfig
1280
- | AttributeRedactorPreset
1281
- | false
1282
- | undefined,
1283
- environment: string,
1284
- ): AttributeRedactorConfig | AttributeRedactorPreset | undefined {
1285
- if (explicit === false) return undefined;
1286
- if (explicit !== undefined) return explicit;
1287
-
1288
- const flag = process.env.AUTOTEL_REDACT_PII?.trim().toLowerCase();
1289
- if (flag) {
1290
- if (['off', 'false', '0', 'none', 'disabled'].includes(flag)) {
1291
- return undefined;
1292
- }
1293
- if (flag === 'default' || flag === 'strict' || flag === 'pci-dss') {
1294
- return flag;
1295
- }
1296
- if (['on', 'true', '1', 'enabled'].includes(flag)) {
1297
- return 'default';
1298
- }
1299
- }
1300
-
1301
- return environment === 'production' ? 'default' : undefined;
1302
- }
1303
-
1304
- function detectEnvironmentAttributes(): Record<string, string> {
1305
- const attrs: Record<string, string> = {};
1306
-
1307
- const commitSha =
1308
- process.env.COMMIT_SHA ||
1309
- process.env.GITHUB_SHA ||
1310
- process.env.VERCEL_GIT_COMMIT_SHA ||
1311
- process.env.CF_PAGES_COMMIT_SHA ||
1312
- process.env.AWS_CODEPIPELINE_EXECUTION_ID;
1313
- if (commitSha) attrs['service.commit.sha'] = commitSha;
1314
-
1315
- const region =
1316
- process.env.VERCEL_REGION ||
1317
- process.env.AWS_REGION ||
1318
- process.env.AWS_DEFAULT_REGION ||
1319
- process.env.FLY_REGION ||
1320
- process.env.CF_REGION ||
1321
- process.env.GOOGLE_CLOUD_REGION;
1322
- if (region) attrs['service.region'] = region;
1323
-
1324
- const version =
1325
- process.env.APP_VERSION ||
1326
- process.env.HEROKU_RELEASE_VERSION ||
1327
- process.env.VERCEL_GIT_COMMIT_REF;
1328
- if (version) attrs['service.deploy.version'] = version;
1329
-
1330
- return attrs;
1331
- }
1332
-
1333
- /**
1334
- * Resolve metrics flag with env var override support
1335
- */
1336
- export function resolveMetricsFlag(
1337
- configFlag: boolean | 'auto' = 'auto',
1338
- ): boolean {
1339
- // 1. Check env var override (highest priority)
1340
- const envFlag = process.env.AUTOTEL_METRICS;
1341
- if (envFlag === 'on' || envFlag === 'true') return true;
1342
- if (envFlag === 'off' || envFlag === 'false') return false;
1343
-
1344
- // 2. Check config flag
1345
- if (configFlag === true) return true;
1346
- if (configFlag === false) return false;
1347
-
1348
- // 3. Default: enabled in all environments (simpler)
1349
- return true;
1350
- }
1351
-
1352
- /**
1353
- * Resolve logs flag with env var override support.
1354
- * Defaults to disabled (opt-in only) to avoid unexpected log export
1355
- * and to preserve the upstream SDK's OTEL_LOGS_EXPORTER handling.
1356
- */
1357
- export function resolveLogsFlag(
1358
- configFlag: boolean | 'auto' = 'auto',
1359
- ): boolean {
1360
- // 1. Check env var override (highest priority)
1361
- const envFlag = process.env.AUTOTEL_LOGS;
1362
- if (envFlag === 'on' || envFlag === 'true') return true;
1363
- if (envFlag === 'off' || envFlag === 'false') return false;
1364
-
1365
- // 2. Check config flag
1366
- if (configFlag === true) return true;
1367
- if (configFlag === false) return false;
1368
-
1369
- // 3. Default: disabled (opt-in only)
1370
- return false;
1371
- }
1372
-
1373
- /**
1374
- * Resolve debug flag with env var override support
1375
- *
1376
- * Supports:
1377
- * - `'pretty'`: Colorized, hierarchical output (PrettyConsoleExporter)
1378
- * - `true` / `'true'` / `'1'`: Raw JSON output (ConsoleSpanExporter)
1379
- * - `false` / `'false'` / `'0'`: Disabled
1380
- */
1381
- export function resolveDebugFlag(
1382
- configFlag?: boolean | 'pretty',
1383
- ): boolean | 'pretty' {
1384
- // 1. Check env var override (highest priority)
1385
- const envFlag = process.env.AUTOTEL_DEBUG;
1386
- if (envFlag === 'pretty') return 'pretty';
1387
- if (envFlag === 'true' || envFlag === '1') return true;
1388
- if (envFlag === 'false' || envFlag === '0') return false;
1389
-
1390
- // 2. Return config flag (defaults to false)
1391
- return configFlag ?? false;
1392
- }
1393
-
1394
- function normalizeOtlpHeaders(
1395
- headers?: Record<string, string> | string,
1396
- ): Record<string, string> | undefined {
1397
- if (!headers) return undefined;
1398
- if (typeof headers !== 'string') return headers;
1399
-
1400
- const parsed: Record<string, string> = {};
1401
- for (const pair of headers.split(',')) {
1402
- const [key, ...valueParts] = pair.split('=');
1403
- if (!key || valueParts.length === 0) continue;
1404
- parsed[key.trim()] = valueParts.join('=').trim();
1405
- }
1406
- return parsed;
1407
- }
1408
-
1409
- /**
1410
- * Initialize autotel - Write Once, Observe Everywhere
1411
- *
1412
- * Follows OpenTelemetry standards: opinionated defaults with full flexibility
1413
- * Idempotent: multiple calls are safe, last one wins
1414
- *
1415
- * @example Minimal setup (OTLP default)
1416
- * ```typescript
1417
- * init({ service: 'my-app' })
1418
- * ```
1419
- *
1420
- * @example With events (observe in PostHog, Mixpanel, etc.)
1421
- * ```typescript
1422
- * import { PostHogSubscriber } from 'autotel-subscribers/posthog';
1423
- *
1424
- * init({
1425
- * service: 'my-app',
1426
- * subscribers: [new PostHogSubscriber({ apiKey: '...' })]
1427
- * })
1428
- * ```
1429
- *
1430
- * @example Observe in Jaeger
1431
- * ```typescript
1432
- * import { JaegerExporter } from '@opentelemetry/exporter-jaeger'
1433
- *
1434
- * init({
1435
- * service: 'my-app',
1436
- * spanExporter: new JaegerExporter({ endpoint: 'http://localhost:14268/api/traces' })
1437
- * })
1438
- * ```
1439
- *
1440
- * @example Observe in Zipkin
1441
- * ```typescript
1442
- * import { ZipkinExporter } from '@opentelemetry/exporter-zipkin'
1443
- *
1444
- * init({
1445
- * service: 'my-app',
1446
- * spanExporter: new ZipkinExporter({ url: 'http://localhost:9411/api/v2/spans' })
1447
- * })
1448
- * ```
1449
- *
1450
- * @example Observe in Datadog
1451
- * ```typescript
1452
- * import { DatadogSpanProcessor } from '@opentelemetry/exporter-datadog'
1453
- *
1454
- * init({
1455
- * service: 'my-app',
1456
- * spanProcessor: new DatadogSpanProcessor({ ... })
1457
- * })
1458
- * ```
1459
- *
1460
- * @example Console output (dev)
1461
- * ```typescript
1462
- * import { ConsoleSpanExporter, SimpleSpanProcessor } from '@opentelemetry/sdk-trace-base'
1463
- *
1464
- * init({
1465
- * service: 'my-app',
1466
- * spanProcessor: new SimpleSpanProcessor(new ConsoleSpanExporter())
1467
- * })
1468
- * ```
1469
- */
1470
-
1471
- export function init(cfg: AutotelConfig): void {
1472
- if (locked) {
1473
- return;
1474
- }
1475
-
1476
- // Resolve configs in priority order: explicit > yaml > env > defaults
1477
- const envConfig = resolveConfigFromEnv();
1478
- const yamlConfig = loadYamlConfig() ?? {};
1479
-
1480
- // Merge configs: explicit config > yaml file > env vars > defaults
1481
- const mergedConfig: AutotelConfig = {
1482
- ...envConfig, // Environment variables (lowest priority)
1483
- ...yamlConfig, // YAML file (middle priority)
1484
- ...cfg, // Explicit config (highest priority)
1485
- // Deep merge for resourceAttributes
1486
- resourceAttributes: {
1487
- ...envConfig.resourceAttributes,
1488
- ...yamlConfig.resourceAttributes,
1489
- ...detectEnvironmentAttributes(),
1490
- ...cfg.resourceAttributes,
1491
- },
1492
- // Handle headers merge (can be string or object)
1493
- headers: cfg.headers ?? yamlConfig.headers ?? envConfig.headers,
1494
- } as AutotelConfig;
1495
-
1496
- const resolvedRedactor = resolveAttributeRedactor(
1497
- mergedConfig.attributeRedactor,
1498
- mergedConfig.environment || process.env.NODE_ENV || 'development',
1499
- );
1500
- if (resolvedRedactor === undefined) {
1501
- mergedConfig.attributeRedactor = undefined;
1502
- } else {
1503
- const normalizedRedactor =
1504
- normalizeAttributeRedactorConfig(resolvedRedactor);
1505
- if (!normalizedRedactor) {
1506
- throw new Error('Invalid attributeRedactor config');
1507
- }
1508
- mergedConfig.attributeRedactor = normalizedRedactor;
1509
- }
1510
-
1511
- const devtoolsConfig = resolveDevtoolsConfig(mergedConfig.devtools);
1512
- if (devtoolsConfig.enabled && mergedConfig.logs === undefined) {
1513
- mergedConfig.logs = true;
1514
- }
1515
-
1516
- const silent = mergedConfig.silent ?? false;
1517
- const minLevel = mergedConfig.minLevel ?? 'info';
1518
- const baseLogger = mergedConfig.logger || silentLogger;
1519
- logger = wrapLogger(baseLogger, silent, minLevel);
1520
-
1521
- // Warn if re-initializing (same behavior in all environments)
1522
- if (initialized) {
1523
- logger.warn(
1524
- {},
1525
- '[autotel] init() called again - last config wins. This may cause unexpected behavior.',
1526
- );
1527
- }
1528
-
1529
- config = mergedConfig;
1530
- validationConfig = mergedConfig.validation || null;
1531
- eventsConfig = mergedConfig.events || null;
1532
-
1533
- // Initialize OpenTelemetry
1534
- // Only use endpoint if explicitly configured (no default fallback)
1535
- let endpoint = mergedConfig.endpoint ?? devtoolsConfig.endpoint;
1536
- const otlpHeaders = normalizeOtlpHeaders(mergedConfig.headers);
1537
- const version = mergedConfig.version || detectVersion();
1538
- const environment =
1539
- mergedConfig.environment || process.env.NODE_ENV || 'development';
1540
- const metricsEnabled = resolveMetricsFlag(mergedConfig.metrics);
1541
- const logsEnabled = resolveLogsFlag(mergedConfig.logs);
1542
-
1543
- if (devtoolsConfig.enabled && devtoolsConfig.embedded) {
1544
- const devtoolsModule = _optionalRequire<{
1545
- createDevtools?: (options?: {
1546
- port?: number;
1547
- host?: string;
1548
- verbose?: boolean;
1549
- }) => { port: number; close: () => Promise<void> | void };
1550
- }>('autotel-devtools');
1551
-
1552
- if (devtoolsModule?.createDevtools) {
1553
- const devtoolsInstance = devtoolsModule.createDevtools({
1554
- port: devtoolsConfig.port,
1555
- host: devtoolsConfig.host,
1556
- verbose: devtoolsConfig.verbose,
1557
- });
1558
- _devtoolsClose = devtoolsInstance.close;
1559
- endpoint = `http://${devtoolsConfig.host}:${devtoolsInstance.port}`;
1560
- logger.info(
1561
- {},
1562
- `[autotel] autotel-devtools embedded server started at ${endpoint}`,
1563
- );
1564
- } else {
1565
- logger.warn(
1566
- {},
1567
- '[autotel] devtools.embedded requested but autotel-devtools is not installed. Falling back to endpoint-only mode.',
1568
- );
1569
- }
1570
- }
1571
-
1572
- // Detect hostname for proper Datadog correlation and Service Catalog discovery
1573
- const hostname = detectHostname();
1574
-
1575
- let resource = resourceFromAttributes({
1576
- [ATTR_SERVICE_NAME]: mergedConfig.service,
1577
- [ATTR_SERVICE_VERSION]: version,
1578
- // Support both old and new OpenTelemetry semantic conventions for environment
1579
- 'deployment.environment': environment, // Deprecated but widely supported
1580
- 'deployment.environment.name': environment, // OTel v1.27.0+ standard
1581
- });
1582
-
1583
- // Add hostname attributes for Datadog Service Catalog and infrastructure correlation
1584
- if (hostname) {
1585
- resource = resource.merge(
1586
- resourceFromAttributes({
1587
- 'host.name': hostname, // OpenTelemetry standard
1588
- 'datadog.host.name': hostname, // Datadog-specific, highest priority for Datadog
1589
- }),
1590
- );
1591
- }
1592
-
1593
- if (mergedConfig.resource) {
1594
- resource = resource.merge(mergedConfig.resource);
1595
- }
1596
-
1597
- if (mergedConfig.resourceAttributes) {
1598
- resource = resource.merge(
1599
- resourceFromAttributes(mergedConfig.resourceAttributes),
1600
- );
1601
- }
1602
-
1603
- // Resolve OTLP protocol (http or grpc)
1604
- const protocol = resolveProtocol(mergedConfig.protocol);
1605
-
1606
- // Backward-compatible singular aliases. Plural forms take precedence when both are provided.
1607
- const configuredSpanProcessors =
1608
- mergedConfig.spanProcessors && mergedConfig.spanProcessors.length > 0
1609
- ? mergedConfig.spanProcessors
1610
- : mergedConfig.spanProcessor
1611
- ? [mergedConfig.spanProcessor]
1612
- : undefined;
1613
- const configuredSpanExporters =
1614
- mergedConfig.spanExporters && mergedConfig.spanExporters.length > 0
1615
- ? mergedConfig.spanExporters
1616
- : mergedConfig.spanExporter
1617
- ? [mergedConfig.spanExporter]
1618
- : undefined;
1619
- const configuredMetricReaders =
1620
- mergedConfig.metricReaders && mergedConfig.metricReaders.length > 0
1621
- ? mergedConfig.metricReaders
1622
- : mergedConfig.metricReader
1623
- ? [mergedConfig.metricReader]
1624
- : undefined;
1625
- const configuredLogRecordProcessors =
1626
- mergedConfig.logRecordProcessors &&
1627
- mergedConfig.logRecordProcessors.length > 0
1628
- ? mergedConfig.logRecordProcessors
1629
- : mergedConfig.logRecordProcessor
1630
- ? [mergedConfig.logRecordProcessor]
1631
- : undefined;
1632
-
1633
- // Build array of span processors (supports multiple)
1634
- let spanProcessors: SpanProcessor[] = [];
1635
-
1636
- if (configuredSpanProcessors && configuredSpanProcessors.length > 0) {
1637
- // User provided custom processors (full control)
1638
- spanProcessors.push(...configuredSpanProcessors);
1639
- } else if (configuredSpanExporters && configuredSpanExporters.length > 0) {
1640
- // User provided custom exporters (wrap each with tail sampling)
1641
- for (const exporter of configuredSpanExporters) {
1642
- spanProcessors.push(
1643
- new TailSamplingSpanProcessor(new BatchSpanProcessor(exporter)),
1644
- );
1645
- }
1646
- } else if (endpoint) {
1647
- // Default: OTLP with tail sampling (only if endpoint is configured)
1648
- const traceExporter = createTraceExporter(protocol, {
1649
- url: formatEndpointUrl(endpoint, 'traces', protocol),
1650
- headers: otlpHeaders,
1651
- });
1652
-
1653
- spanProcessors.push(
1654
- new TailSamplingSpanProcessor(new BatchSpanProcessor(traceExporter)),
1655
- );
1656
- }
1657
- // If no endpoint and no custom processors/exporters, array remains empty
1658
- // SDK will still work but won't export traces
1659
-
1660
- // Add baggage span processor if enabled
1661
- if (mergedConfig.baggage) {
1662
- const prefix =
1663
- typeof mergedConfig.baggage === 'string'
1664
- ? mergedConfig.baggage
1665
- ? `${mergedConfig.baggage}.`
1666
- : ''
1667
- : 'baggage.';
1668
- spanProcessors.push(new BaggageSpanProcessor({ prefix }));
1669
- }
1670
-
1671
- // Apply debug mode configuration
1672
- const debugMode = resolveDebugFlag(mergedConfig.debug);
1673
-
1674
- if (debugMode === 'pretty') {
1675
- // Pretty debug: colorized, hierarchical output
1676
- spanProcessors.push(new SimpleSpanProcessor(new PrettyConsoleExporter()));
1677
- } else if (debugMode === true) {
1678
- // Raw debug: JSON output
1679
- spanProcessors.push(new SimpleSpanProcessor(new ConsoleSpanExporter()));
1680
- }
1681
-
1682
- // Add canonical log line processor BEFORE wrapping processors
1683
- // This ensures it gets wrapped with the same filter/normalizer/redactor as other processors,
1684
- // so canonical logs respect spanFilter (filtered spans aren't logged), spanNameNormalizer
1685
- // (normalized names are used), and attributeRedactor (sensitive data is redacted).
1686
- if (mergedConfig.canonicalLogLines?.enabled) {
1687
- const canonicalOptions: CanonicalLogLineOptions = {
1688
- logger: mergedConfig.canonicalLogLines.logger || mergedConfig.logger,
1689
- rootSpansOnly: mergedConfig.canonicalLogLines.rootSpansOnly,
1690
- minLevel: mergedConfig.canonicalLogLines.minLevel,
1691
- messageFormat: mergedConfig.canonicalLogLines.messageFormat,
1692
- includeResourceAttributes:
1693
- mergedConfig.canonicalLogLines.includeResourceAttributes,
1694
- shouldEmit: mergedConfig.canonicalLogLines.shouldEmit,
1695
- keep: mergedConfig.canonicalLogLines.keep,
1696
- drain: mergedConfig.canonicalLogLines.drain,
1697
- onDrainError: mergedConfig.canonicalLogLines.onDrainError,
1698
- pretty: mergedConfig.canonicalLogLines.pretty,
1699
- };
1700
- spanProcessors.push(new CanonicalLogLineProcessor(canonicalOptions));
1701
- }
1702
-
1703
- // Wrap processors in order: redactor (innermost) → normalizer → filter (outermost)
1704
- // This ensures onEnd() execution order is: filter → normalizer → redactor
1705
- // So filtering sees original attributes, and redaction happens last before export.
1706
-
1707
- // Step 1: Wrap with AttributeRedactingProcessor (innermost - executes last in onEnd)
1708
- if (mergedConfig.attributeRedactor && spanProcessors.length > 0) {
1709
- const redactor = mergedConfig.attributeRedactor;
1710
- spanProcessors = spanProcessors.map(
1711
- (processor) =>
1712
- new AttributeRedactingProcessor(processor, {
1713
- redactor,
1714
- }),
1715
- );
1716
- }
1717
-
1718
- // Store string redactor for use by PostHog log/subscriber paths
1719
- if (mergedConfig.attributeRedactor) {
1720
- _stringRedactor = createStringRedactor(mergedConfig.attributeRedactor);
1721
- }
1722
-
1723
- // Wire string redactor to subscribers that support it (e.g., PostHogSubscriber)
1724
- if (_stringRedactor && mergedConfig.subscribers) {
1725
- for (const subscriber of mergedConfig.subscribers) {
1726
- if (
1727
- 'setStringRedactor' in subscriber &&
1728
- typeof (subscriber as any).setStringRedactor === 'function'
1729
- ) {
1730
- (subscriber as any).setStringRedactor(_stringRedactor);
1731
- }
1732
- }
1733
- }
1734
-
1735
- // Step 2: Wrap with SpanNameNormalizingProcessor (middle)
1736
- // Normalizer runs in onStart(), so span names are normalized before any onEnd processing
1737
- if (mergedConfig.spanNameNormalizer && spanProcessors.length > 0) {
1738
- spanProcessors = spanProcessors.map(
1739
- (processor) =>
1740
- new SpanNameNormalizingProcessor(processor, {
1741
- normalizer: mergedConfig.spanNameNormalizer!,
1742
- }),
1743
- );
1744
- }
1745
-
1746
- // Step 3: Wrap with FilteringSpanProcessor (outermost - executes first in onEnd)
1747
- // Filter sees original (unredacted) attributes, so it can match on sensitive values
1748
- if (mergedConfig.spanFilter && spanProcessors.length > 0) {
1749
- spanProcessors = spanProcessors.map(
1750
- (processor) =>
1751
- new FilteringSpanProcessor(processor, {
1752
- filter: mergedConfig.spanFilter!,
1753
- }),
1754
- );
1755
- }
1756
-
1757
- // Build array of metric readers (supports multiple)
1758
- const metricReaders: MetricReader[] = [];
1759
-
1760
- if (configuredMetricReaders && configuredMetricReaders.length > 0) {
1761
- // User provided custom metric readers
1762
- metricReaders.push(...configuredMetricReaders);
1763
- } else if (metricsEnabled && endpoint) {
1764
- // Default: OTLP metrics exporter (only if endpoint is configured)
1765
- const metricExporter = createMetricExporter(protocol, {
1766
- url: formatEndpointUrl(endpoint, 'metrics', protocol),
1767
- headers: otlpHeaders,
1768
- });
1769
-
1770
- metricReaders.push(
1771
- new PeriodicExportingMetricReader({
1772
- exporter: metricExporter,
1773
- }),
1774
- );
1775
- }
1776
-
1777
- let logRecordProcessors: LogRecordProcessor[] | undefined;
1778
- if (
1779
- configuredLogRecordProcessors &&
1780
- configuredLogRecordProcessors.length > 0
1781
- ) {
1782
- logRecordProcessors = [...configuredLogRecordProcessors];
1783
- }
1784
-
1785
- // Auto-configure OTLP log exporter when logs are enabled and endpoint is set
1786
- if (logsEnabled && endpoint) {
1787
- const logExporter = createLogExporter(protocol, {
1788
- url: formatEndpointUrl(endpoint, 'logs', protocol),
1789
- headers: otlpHeaders,
1790
- });
1791
-
1792
- let processor: LogRecordProcessor = new BatchLogRecordProcessor(
1793
- logExporter,
1794
- );
1795
- if (_stringRedactor) {
1796
- processor = new RedactingLogRecordProcessor(processor, _stringRedactor);
1797
- }
1798
- if (!logRecordProcessors) {
1799
- logRecordProcessors = [];
1800
- }
1801
- logRecordProcessors.push(processor);
1802
- logger.info({}, '[autotel] OTLP log exporter configured');
1803
- }
1804
-
1805
- // PostHog OTLP logs integration
1806
- const posthogProcessors = buildPostHogLogProcessors(
1807
- mergedConfig.posthog,
1808
- _stringRedactor,
1809
- );
1810
- if (posthogProcessors.length > 0) {
1811
- if (!logRecordProcessors) {
1812
- logRecordProcessors = [];
1813
- }
1814
- logRecordProcessors.push(...posthogProcessors);
1815
- logger.info({}, '[autotel] PostHog OTLP logs configured');
1816
- }
1817
-
1818
- // Handle instrumentations: merge manual instrumentations with auto-instrumentations
1819
- let finalInstrumentations: NodeSDKConfiguration['instrumentations'] =
1820
- mergedConfig.instrumentations ? [...mergedConfig.instrumentations] : [];
1821
-
1822
- if (
1823
- mergedConfig.autoInstrumentations !== undefined &&
1824
- mergedConfig.autoInstrumentations !== false
1825
- ) {
1826
- // Check for ESM mode and provide guidance
1827
- const isESM = isESMMode();
1828
- if (isESM) {
1829
- logger.info(
1830
- {},
1831
- '[autotel] ESM mode detected. For auto-instrumentation to work:\n' +
1832
- ' 1. Install @opentelemetry/auto-instrumentations-node as a direct dependency\n' +
1833
- ' 2. Import autotel/register FIRST in your instrumentation file\n' +
1834
- ' 3. Use getNodeAutoInstrumentations() directly instead of autoInstrumentations\n' +
1835
- ' See: https://github.com/jagreehal/autotel#esm-setup',
1836
- );
1837
- }
1838
-
1839
- try {
1840
- // Detect manual instrumentations to avoid conflicts
1841
- const manualInstrumentationNames = getInstrumentationNames(
1842
- mergedConfig.instrumentations ?? [],
1843
- );
1844
-
1845
- // Warn if both autoInstrumentations and manual instrumentations are provided
1846
- if (manualInstrumentationNames.size > 0) {
1847
- const manualNames = [...manualInstrumentationNames].join(', ');
1848
- logger.info(
1849
- {},
1850
- `[autotel] Detected manual instrumentations (${manualNames}). ` +
1851
- 'These will take precedence over auto-instrumentations. ' +
1852
- 'Tip: Set autoInstrumentations:false if you want full manual control, or remove manual configs to use auto-instrumentations.',
1853
- );
1854
- }
1855
-
1856
- const autoInstrumentations = getAutoInstrumentations(
1857
- mergedConfig.autoInstrumentations,
1858
- manualInstrumentationNames,
1859
- );
1860
- if (autoInstrumentations && autoInstrumentations.length > 0) {
1861
- // Cast to proper type - getNodeAutoInstrumentations returns the correct type
1862
- finalInstrumentations = [
1863
- ...finalInstrumentations,
1864
- ...(autoInstrumentations as NodeSDKConfiguration['instrumentations']),
1865
- ];
1866
- }
1867
- } catch (error) {
1868
- logger.warn(
1869
- {},
1870
- `[autotel] Failed to configure auto-instrumentations: ${error instanceof Error ? error.message : String(error)}`,
1871
- );
1872
- }
1873
- }
1874
-
1875
- const autotelSampler =
1876
- mergedConfig.sampler ??
1877
- (mergedConfig.sampling
1878
- ? resolveSamplingPreset(mergedConfig.sampling)
1879
- : undefined);
1880
- if (autotelSampler) {
1881
- mergedConfig.sampler = autotelSampler;
1882
- }
1883
- const sampler: OtelSampler = autotelSampler
1884
- ? toOtelSampler(autotelSampler)
1885
- : (envConfig.otelSampler ?? toOtelSampler(samplingPresets.production()));
1886
-
1887
- const sdkOptions: Partial<NodeSDKConfiguration> = {
1888
- resource,
1889
- sampler,
1890
- instrumentations: finalInstrumentations,
1891
- };
1892
-
1893
- if (spanProcessors.length > 0) {
1894
- sdkOptions.spanProcessors = spanProcessors;
1895
- }
1896
-
1897
- if (metricReaders.length > 0) {
1898
- sdkOptions.metricReaders = metricReaders;
1899
- }
1900
-
1901
- if (logRecordProcessors && logRecordProcessors.length > 0) {
1902
- sdkOptions.logRecordProcessors = logRecordProcessors;
1903
- }
1904
-
1905
- sdk = mergedConfig.sdkFactory
1906
- ? mergedConfig.sdkFactory(sdkOptions)
1907
- : new NodeSDK(sdkOptions);
1908
-
1909
- if (!sdk) {
1910
- throw new Error('[autotel] sdkFactory must return a NodeSDK instance');
1911
- }
1912
-
1913
- sdk.start();
1914
-
1915
- // Initialize OpenLLMetry if enabled (after SDK starts to reuse tracer provider)
1916
- if (mergedConfig.openllmetry?.enabled) {
1917
- const traceloop = _optionalRequire<{
1918
- initialize?: (options?: Record<string, unknown>) => void;
1919
- }>('@traceloop/node-server-sdk');
1920
-
1921
- if (traceloop) {
1922
- const initOptions: Record<string, unknown> = {
1923
- ...mergedConfig.openllmetry.options,
1924
- };
1925
-
1926
- // Reuse autotel's tracer provider
1927
- try {
1928
- // Type assertion needed as getTracerProvider is not in the public NodeSDK interface
1929
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1930
- const tracerProvider = (sdk as any).getTracerProvider();
1931
- initOptions.tracerProvider = tracerProvider;
1932
- } catch {
1933
- // Ignore if tracer provider not available
1934
- }
1935
-
1936
- // Pass span exporter to OpenLLMetry if provided
1937
- if (configuredSpanExporters?.[0]) {
1938
- initOptions.exporter = configuredSpanExporters[0];
1939
- }
1940
-
1941
- if (typeof traceloop.initialize === 'function') {
1942
- traceloop.initialize(initOptions);
1943
- logger.info({}, '[autotel] OpenLLMetry initialized successfully');
1944
- } else {
1945
- logger.warn(
1946
- {},
1947
- '[autotel] OpenLLMetry initialize function not found. Check @traceloop/node-server-sdk version.',
1948
- );
1949
- }
1950
- } else {
1951
- logger.warn(
1952
- {},
1953
- '[autotel] OpenLLMetry enabled but @traceloop/node-server-sdk is not installed. ' +
1954
- 'Install it as a peer dependency to use OpenLLMetry integration.',
1955
- );
1956
- }
1957
- }
1958
-
1959
- initialized = true;
1960
- }
1961
-
1962
- /**
1963
- * Extract instrumentation class names from instrumentation instances
1964
- * Used to detect duplicates between manual and auto instrumentations
1965
- */
1966
- function getInstrumentationNames(
1967
- instrumentations: NodeSDKConfiguration['instrumentations'],
1968
- ): Set<string> {
1969
- const names = new Set<string>();
1970
-
1971
- if (!instrumentations) return names;
1972
-
1973
- for (const instrumentation of instrumentations) {
1974
- if (instrumentation && typeof instrumentation === 'object') {
1975
- names.add(instrumentation.constructor.name);
1976
- }
1977
- }
1978
-
1979
- return names;
1980
- }
1981
-
1982
- /**
1983
- * Map common instrumentation class names to their package names
1984
- * Used to disable auto-instrumentations when user provides manual configs
1985
- */
1986
- const INSTRUMENTATION_CLASS_TO_PACKAGE: Record<string, string> = {
1987
- HttpInstrumentation: '@opentelemetry/instrumentation-http',
1988
- HttpsInstrumentation: '@opentelemetry/instrumentation-http',
1989
- ExpressInstrumentation: '@opentelemetry/instrumentation-express',
1990
- FastifyInstrumentation: '@opentelemetry/instrumentation-fastify',
1991
- MongoDBInstrumentation: '@opentelemetry/instrumentation-mongodb',
1992
- MongooseInstrumentation: '@opentelemetry/instrumentation-mongoose',
1993
- PrismaInstrumentation: '@opentelemetry/instrumentation-prisma',
1994
- PinoInstrumentation: '@opentelemetry/instrumentation-pino',
1995
- WinstonInstrumentation: '@opentelemetry/instrumentation-winston',
1996
- RedisInstrumentation: '@opentelemetry/instrumentation-redis',
1997
- GraphQLInstrumentation: '@opentelemetry/instrumentation-graphql',
1998
- GrpcInstrumentation: '@opentelemetry/instrumentation-grpc',
1999
- IORedisInstrumentation: '@opentelemetry/instrumentation-ioredis',
2000
- KnexInstrumentation: '@opentelemetry/instrumentation-knex',
2001
- NestJsInstrumentation: '@opentelemetry/instrumentation-nestjs-core',
2002
- PgInstrumentation: '@opentelemetry/instrumentation-pg',
2003
- MySQLInstrumentation: '@opentelemetry/instrumentation-mysql',
2004
- MySQL2Instrumentation: '@opentelemetry/instrumentation-mysql2',
2005
- };
2006
-
2007
- /**
2008
- * Type for the auto-instrumentations loader function
2009
- * @internal Used for testing injection
2010
- */
2011
- export type AutoInstrumentationsLoader = (
2012
- config?: Record<string, { enabled?: boolean }>,
2013
- ) => unknown[];
2014
-
2015
- /**
2016
- * Detect if we're running in ESM mode
2017
- */
2018
- function isESMMode(): boolean {
2019
- // Check if we're in an ESM context by looking for common ESM indicators
2020
- try {
2021
- // In ESM, module.exports doesn't exist in the global scope the same way
2022
- // Also check if the package.json type is "module"
2023
- const fs = requireModule<typeof import('node:fs')>('node:fs');
2024
- try {
2025
- const pkg = JSON.parse(
2026
- fs.readFileSync(`${process.cwd()}/package.json`, 'utf8'),
2027
- );
2028
- return pkg.type === 'module';
2029
- } catch {
2030
- return false;
2031
- }
2032
- } catch {
2033
- return false;
2034
- }
2035
- }
2036
-
2037
- /**
2038
- * Lazy-load auto-instrumentations (optional peer dependency)
2039
- * Only loads when integrations config is truthy, avoiding ~40+ package imports at startup.
2040
- */
2041
- function loadNodeAutoInstrumentations(): AutoInstrumentationsLoader {
2042
- try {
2043
- const mod = requireModule<{
2044
- getNodeAutoInstrumentations: AutoInstrumentationsLoader;
2045
- }>('@opentelemetry/auto-instrumentations-node');
2046
- return mod.getNodeAutoInstrumentations;
2047
- } catch {
2048
- const isESM = isESMMode();
2049
- const baseMessage = '@opentelemetry/auto-instrumentations-node not found.';
2050
-
2051
- if (isESM) {
2052
- throw new Error(
2053
- `${baseMessage}\n\n` +
2054
- 'ESM Setup Required:\n' +
2055
- '1. Install as a direct dependency: pnpm add @opentelemetry/auto-instrumentations-node\n' +
2056
- '2. Create instrumentation.mjs with:\n' +
2057
- " import 'autotel/register'; // MUST be first!\n" +
2058
- " import { init } from 'autotel';\n" +
2059
- " import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';\n" +
2060
- ' init({ service: "my-app", instrumentations: getNodeAutoInstrumentations() });\n' +
2061
- '3. Run with: tsx --import ./instrumentation.mjs src/index.ts\n\n' +
2062
- 'See: https://github.com/jagreehal/autotel#esm-setup',
2063
- );
2064
- }
2065
-
2066
- throw new Error(
2067
- `${baseMessage} Install it: pnpm add @opentelemetry/auto-instrumentations-node`,
2068
- );
2069
- }
2070
- }
2071
-
2072
- /**
2073
- * Injectable loader for testing. Set to override the default loader.
2074
- * @internal
2075
- */
2076
- let _autoInstrumentationsLoader: (() => AutoInstrumentationsLoader) | null =
2077
- null;
2078
-
2079
- /**
2080
- * @internal Set custom loader (for testing)
2081
- */
2082
- export function _setAutoInstrumentationsLoader(
2083
- loader: (() => AutoInstrumentationsLoader) | null,
2084
- ): void {
2085
- _autoInstrumentationsLoader = loader;
2086
- }
2087
-
2088
- /**
2089
- * @internal Reset loader to default (for testing cleanup)
2090
- */
2091
- export function _resetAutoInstrumentationsLoader(): void {
2092
- _autoInstrumentationsLoader = null;
2093
- }
2094
-
2095
- /**
2096
- * Get auto-instrumentations based on simple integration names
2097
- * Excludes instrumentations that are manually provided to avoid conflicts
2098
- */
2099
- function getAutoInstrumentations(
2100
- integrations: string[] | boolean | Record<string, { enabled?: boolean }>,
2101
- manualInstrumentationNames: Set<string> = new Set(),
2102
- ): unknown[] {
2103
- if (integrations === false) {
2104
- return [];
2105
- }
2106
-
2107
- // Use injected loader if set (for testing), otherwise lazy-load
2108
- const getNodeAutoInstrumentations = _autoInstrumentationsLoader
2109
- ? _autoInstrumentationsLoader()
2110
- : loadNodeAutoInstrumentations();
2111
-
2112
- // Build exclusion config for manual instrumentations
2113
- const exclusionConfig: Record<string, { enabled: boolean }> = {};
2114
- for (const className of manualInstrumentationNames) {
2115
- const packageName = INSTRUMENTATION_CLASS_TO_PACKAGE[className];
2116
- if (packageName) {
2117
- exclusionConfig[packageName] = { enabled: false };
2118
- }
2119
- }
2120
-
2121
- if (integrations === true) {
2122
- // If exclusions exist, pass them to getNodeAutoInstrumentations
2123
- if (Object.keys(exclusionConfig).length > 0) {
2124
- return getNodeAutoInstrumentations(exclusionConfig);
2125
- }
2126
- return getNodeAutoInstrumentations();
2127
- }
2128
-
2129
- if (Array.isArray(integrations)) {
2130
- const config: Record<string, { enabled: boolean }> = { ...exclusionConfig };
2131
- for (const name of integrations) {
2132
- const packageName = `@opentelemetry/instrumentation-${name}`;
2133
- // Don't override exclusions
2134
- if (!exclusionConfig[packageName]) {
2135
- config[packageName] = { enabled: true };
2136
- }
2137
- }
2138
- return getNodeAutoInstrumentations(config);
2139
- }
2140
-
2141
- const config: Record<string, { enabled?: boolean }> = {
2142
- ...exclusionConfig,
2143
- ...integrations,
2144
- };
2145
-
2146
- // Override any integrations that conflict with manual instrumentations
2147
- for (const packageName of Object.keys(exclusionConfig)) {
2148
- const integrationsKey = Object.keys(integrations).find((key) =>
2149
- packageName.includes(key),
2150
- );
2151
- if (integrationsKey) {
2152
- // Manual instrumentation takes precedence
2153
- config[packageName] = { enabled: false };
2154
- }
2155
- }
2156
-
2157
- return getNodeAutoInstrumentations(config);
2158
- }
2159
-
2160
- /**
2161
- * Check if autotel has been initialized
2162
- */
2163
- export function isInitialized(): boolean {
2164
- return initialized;
2165
- }
2166
-
2167
- /**
2168
- * Get current config (internal use)
2169
- */
2170
- export function getConfig(): AutotelConfig | null {
2171
- return config;
2172
- }
2173
-
2174
- /**
2175
- * Get current logger (internal use)
2176
- */
2177
- export function getLogger(): Logger {
2178
- return logger;
2179
- }
2180
-
2181
- /**
2182
- * Get validation config (internal use)
2183
- */
2184
- export function getValidationConfig(): Partial<ValidationConfig> | null {
2185
- return validationConfig;
2186
- }
2187
-
2188
- /**
2189
- * Get events config (internal use)
2190
- */
2191
- export function getEventsConfig(): EventsConfig | null {
2192
- return eventsConfig;
2193
- }
2194
-
2195
- /**
2196
- * Warn once if not initialized (same behavior in all environments)
2197
- */
2198
- export function warnIfNotInitialized(context: string): void {
2199
- if (!initialized && !warnedOnce) {
2200
- logger.warn(
2201
- {},
2202
- `[autotel] ${context} used before init() called. ` +
2203
- 'Call init({ service: "..." }) first. See: https://docs.autotel.dev/quickstart',
2204
- );
2205
- warnedOnce = true;
2206
- }
2207
- }
2208
-
2209
- /**
2210
- * Get default sampler
2211
- */
2212
- export function getDefaultSampler(): Sampler {
2213
- return config?.sampler || samplingPresets.production();
2214
- }
2215
-
2216
- /**
2217
- * Auto-detect version from package.json
2218
- */
2219
- function detectVersion(): string {
2220
- try {
2221
- // Try to read package.json from cwd using fs
2222
- const fs = requireModule<typeof import('node:fs')>('node:fs');
2223
- const pkg = JSON.parse(
2224
- fs.readFileSync(`${process.cwd()}/package.json`, 'utf8'),
2225
- );
2226
- return pkg.version || '1.0.0';
2227
- } catch {
2228
- return '1.0.0';
2229
- }
2230
- }
2231
-
2232
- /**
2233
- * Detect hostname for resource attributes.
2234
- * Supports Datadog conventions (DD_HOSTNAME) and falls back to system hostname.
2235
- *
2236
- * Priority order:
2237
- * 1. DD_HOSTNAME environment variable (Datadog convention)
2238
- * 2. HOSTNAME environment variable (common Unix convention)
2239
- * 3. os.hostname() (system hostname)
2240
- *
2241
- * @returns hostname string or undefined if detection fails
2242
- */
2243
- function detectHostname(): string | undefined {
2244
- // Priority 1: DD_HOSTNAME (Datadog convention)
2245
- if (process.env.DD_HOSTNAME) {
2246
- return process.env.DD_HOSTNAME;
2247
- }
2248
-
2249
- // Priority 2: HOSTNAME (common in containers and Unix systems)
2250
- if (process.env.HOSTNAME) {
2251
- return process.env.HOSTNAME;
2252
- }
2253
-
2254
- // Priority 3: System hostname
2255
- try {
2256
- const os = requireModule<typeof import('node:os')>('node:os');
2257
- return os.hostname();
2258
- } catch {
2259
- // os module not available (edge runtime, browser, etc.)
2260
- return undefined;
2261
- }
2262
- }
2263
-
2264
- /**
2265
- * Get the string redactor configured via init({ attributeRedactor }).
2266
- * Returns null if no redactor was configured.
2267
- */
2268
- export function getStringRedactor(): StringRedactor | null {
2269
- return _stringRedactor;
2270
- }
2271
-
2272
- /**
2273
- * @internal Override optional require for deterministic tests.
2274
- */
2275
- export function _setOptionalRequireForTesting(
2276
- loader: typeof safeRequire,
2277
- ): void {
2278
- _optionalRequire = loader;
2279
- }
2280
-
2281
- /**
2282
- * @internal Reset optional require override.
2283
- */
2284
- export function _resetOptionalRequireForTesting(): void {
2285
- _optionalRequire = safeRequire;
2286
- }
2287
-
2288
- /**
2289
- * @internal Close embedded devtools if running.
2290
- */
2291
- export async function _closeEmbeddedDevtools(): Promise<void> {
2292
- if (_devtoolsClose) {
2293
- await _devtoolsClose();
2294
- _devtoolsClose = null;
2295
- }
2296
- }
2297
-
2298
- /**
2299
- * @internal Get embedded devtools close handle.
2300
- */
2301
- export function _getEmbeddedDevtoolsCloseForTesting():
2302
- | (() => Promise<void> | void)
2303
- | null {
2304
- return _devtoolsClose;
2305
- }
2306
-
2307
- /**
2308
- * Get SDK instance (for shutdown)
2309
- */
2310
- export function getSdk(): NodeSDK | null {
2311
- return sdk;
2312
- }