autotel 7.10.0 → 8.0.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 (80) hide show
  1. package/README.md +40 -32
  2. package/dist/auto.cjs +1 -1
  3. package/dist/auto.js +1 -1
  4. package/dist/{canonical-log-line-processor-DDGzm-wi.d.ts → canonical-log-line-processor-BaFqHO5l.d.ts} +1 -2
  5. package/dist/{canonical-log-line-processor-DLeAQjgb.d.cts → canonical-log-line-processor-C-RdV5af.d.cts} +1 -2
  6. package/dist/{canonical-log-line-processor-BU03jJeA.cjs → canonical-log-line-processor-CBRe-VsO.cjs} +0 -1
  7. package/dist/{canonical-log-line-processor-BQ6NeZAk.js → canonical-log-line-processor-CntKMtpJ.js} +0 -1
  8. package/dist/config.d.cts +1 -1
  9. package/dist/config.d.ts +1 -1
  10. package/dist/correlation-id.cjs +1 -1
  11. package/dist/correlation-id.js +1 -1
  12. package/dist/decorators.cjs +1 -1
  13. package/dist/decorators.d.cts +1 -1
  14. package/dist/decorators.d.ts +1 -1
  15. package/dist/decorators.js +1 -1
  16. package/dist/event.cjs +1 -1
  17. package/dist/event.d.cts +1 -1
  18. package/dist/event.d.ts +1 -1
  19. package/dist/event.js +1 -1
  20. package/dist/{functional-CjZT7Jur.cjs → functional-BliCZgwO.cjs} +12 -7
  21. package/dist/{functional-DHn-xGtR.js → functional-C-D0KQg6.js} +12 -7
  22. package/dist/{functional-j8Joyrwd.d.cts → functional-DDunG47Y.d.cts} +1 -1
  23. package/dist/{functional-CbcInXMJ.d.ts → functional-DU7NXyih.d.ts} +1 -1
  24. package/dist/functional.cjs +1 -1
  25. package/dist/functional.d.cts +1 -1
  26. package/dist/functional.d.ts +1 -1
  27. package/dist/functional.js +1 -1
  28. package/dist/http.cjs +1 -1
  29. package/dist/http.js +1 -1
  30. package/dist/index.cjs +3 -3
  31. package/dist/index.d.cts +4 -4
  32. package/dist/index.d.ts +4 -4
  33. package/dist/index.js +3 -3
  34. package/dist/{init-rVPOUqng.js → init-CKHEf5g2.js} +101 -27
  35. package/dist/{init-BtYaU5n9.d.cts → init-D5k8hBRg.d.cts} +25 -30
  36. package/dist/{init-zb68UvOW.cjs → init-DF4YrpSB.cjs} +106 -32
  37. package/dist/{init-DUIFqRRS.d.ts → init-DMjyzzbM.d.ts} +25 -30
  38. package/dist/instrumentation.cjs +1 -1
  39. package/dist/instrumentation.js +1 -1
  40. package/dist/{logger-B2VPcBgZ.js → logger-8Ah2EkH1.js} +10 -18
  41. package/dist/{logger-DLjHQAus.cjs → logger-C_3xDBOz.cjs} +10 -18
  42. package/dist/{logger-C9UT1aZ_.d.cts → logger-b0qgWFy7.d.cts} +10 -18
  43. package/dist/{logger-C9UT1aZ_.d.ts → logger-b0qgWFy7.d.ts} +10 -18
  44. package/dist/logger.cjs +1 -1
  45. package/dist/logger.d.cts +1 -1
  46. package/dist/logger.d.ts +1 -1
  47. package/dist/logger.js +1 -1
  48. package/dist/messaging.cjs +2 -2
  49. package/dist/messaging.js +2 -2
  50. package/dist/metric.d.cts +1 -1
  51. package/dist/metric.d.ts +1 -1
  52. package/dist/processors.cjs +1 -1
  53. package/dist/processors.d.cts +1 -1
  54. package/dist/processors.d.ts +1 -1
  55. package/dist/processors.js +1 -1
  56. package/dist/register.cjs +1 -0
  57. package/dist/register.js +1 -0
  58. package/dist/{sampling-Dud4XU2m.d.cts → sampling-CLJmXMw0.d.cts} +1 -1
  59. package/dist/{sampling-B_8orhSB.d.ts → sampling-DBlX8Nv5.d.ts} +1 -1
  60. package/dist/sampling.cjs +1 -1
  61. package/dist/sampling.d.cts +1 -1
  62. package/dist/sampling.d.ts +1 -1
  63. package/dist/sampling.js +1 -1
  64. package/dist/semantic-helpers.cjs +1 -1
  65. package/dist/semantic-helpers.js +1 -1
  66. package/dist/tail-sampling-processor.cjs +1 -1
  67. package/dist/tail-sampling-processor.js +1 -1
  68. package/dist/testing.d.cts +1 -1
  69. package/dist/testing.d.ts +1 -1
  70. package/dist/webhook.cjs +1 -1
  71. package/dist/webhook.js +1 -1
  72. package/dist/workflow-distributed.cjs +1 -1
  73. package/dist/workflow-distributed.js +1 -1
  74. package/dist/workflow.cjs +1 -1
  75. package/dist/workflow.js +1 -1
  76. package/dist/yaml-config.cjs +1 -1
  77. package/dist/yaml-config.d.cts +2 -2
  78. package/dist/yaml-config.d.ts +2 -2
  79. package/dist/yaml-config.js +1 -1
  80. package/package.json +1 -1
@@ -8,7 +8,7 @@ import { PolicyLogRecordProcessor, policySpanFilter, setPolicies, watchPolicyFil
8
8
  import { SpanNameNormalizingProcessor } from "./span-name-normalizer.js";
9
9
  import { AttributeRedactingProcessor, REDACTOR_PRESETS, normalizeAttributeRedactorConfig } from "./attribute-redacting-processor.js";
10
10
  import { t as PrettyConsoleExporter } from "./pretty-console-exporter-DqKl_q9z.js";
11
- import { t as CanonicalLogLineProcessor } from "./canonical-log-line-processor-BQ6NeZAk.js";
11
+ import { t as CanonicalLogLineProcessor } from "./canonical-log-line-processor-CntKMtpJ.js";
12
12
  import { n as subscribeChannel } from "./channel-BobQKsZd.js";
13
13
  import { resetMetrics } from "./metric.js";
14
14
  import { DiagConsoleLogger, DiagLogLevel, TraceFlags, context, createContextKey, diag, metrics, propagation, trace } from "@opentelemetry/api";
@@ -1075,10 +1075,10 @@ function resetEventQueue() {
1075
1075
 
1076
1076
  //#endregion
1077
1077
  //#region src/trace-context.ts
1078
- const spansWithExplicitStatus = /* @__PURE__ */ new WeakSet();
1079
- /** Whether a TraceContext consumer explicitly chose this span's status. */
1080
- function hasExplicitSpanStatus(span) {
1081
- return spansWithExplicitStatus.has(span);
1078
+ const explicitSpanStatuses = /* @__PURE__ */ new WeakMap();
1079
+ /** The status a TraceContext consumer explicitly chose for this span, if any. */
1080
+ function explicitSpanStatus(span) {
1081
+ return explicitSpanStatuses.get(span);
1082
1082
  }
1083
1083
  /**
1084
1084
  * AsyncLocalStorage for storing the active context with baggage
@@ -1166,7 +1166,7 @@ function createTraceContext(span) {
1166
1166
  span.setAttributes(flattenToAttributes(attrs));
1167
1167
  },
1168
1168
  setStatus: (status) => {
1169
- spansWithExplicitStatus.add(span);
1169
+ explicitSpanStatuses.set(span, status.code);
1170
1170
  span.setStatus(status);
1171
1171
  },
1172
1172
  recordException: span.recordException.bind(span),
@@ -1959,15 +1959,15 @@ const silentLogger = {
1959
1959
  function wrapLogger(base, silent, minLevel) {
1960
1960
  if (silent) return silentLogger;
1961
1961
  const threshold = LOG_LEVELS[minLevel];
1962
- const wrap = (fn, level) => {
1962
+ const wrap = (level) => {
1963
1963
  if (LOG_LEVELS[level] < threshold) return (() => {});
1964
- return ((...args) => fn(...args));
1964
+ return ((...args) => base[level](...args));
1965
1965
  };
1966
1966
  return {
1967
- debug: wrap(base.debug, "debug"),
1968
- info: wrap(base.info, "info"),
1969
- warn: wrap(base.warn, "warn"),
1970
- error: wrap(base.error, "error")
1967
+ debug: wrap("debug"),
1968
+ info: wrap("info"),
1969
+ warn: wrap("warn"),
1970
+ error: wrap("error")
1971
1971
  };
1972
1972
  }
1973
1973
 
@@ -4137,20 +4137,68 @@ function toPackageName(name) {
4137
4137
  return name.startsWith("@opentelemetry/instrumentation-") ? name : `@opentelemetry/instrumentation-${name}`;
4138
4138
  }
4139
4139
  /**
4140
- * Detect if we're running in ESM mode
4140
+ * Whether the app's entry runs as ESM: its extension when it has an explicit
4141
+ * one (`.mjs`/`.cjs`, and `.mts`/`.cts` under tsx), else the cwd
4142
+ * package.json's `type`.
4141
4143
  */
4142
- function isESMMode() {
4144
+ function isESMMode(entry = process.argv[1] ?? "") {
4145
+ if (/\.c[jt]s$/.test(entry)) return false;
4146
+ if (/\.m[jt]s$/.test(entry)) return true;
4143
4147
  try {
4144
4148
  const fs = requireModule("node:fs");
4145
- try {
4146
- return JSON.parse(fs.readFileSync(`${process.cwd()}/package.json`, "utf8")).type === "module";
4147
- } catch {
4148
- return false;
4149
- }
4149
+ return JSON.parse(fs.readFileSync(`${process.cwd()}/package.json`, "utf8")).type === "module";
4150
4150
  } catch {
4151
4151
  return false;
4152
4152
  }
4153
4153
  }
4154
+ /** Set by `autotel/register` so `init()` can tell the ESM hook is in place. */
4155
+ const REGISTER_FLAG = Symbol.for("autotel.register");
4156
+ /**
4157
+ * Whether the OTel ESM loader hook is registered: by `autotel/register`, or
4158
+ * by pointing node at a `hook.mjs` directly.
4159
+ */
4160
+ function isEsmHookLoaded(flags = `${process.execArgv.join(" ")} ${process.env.NODE_OPTIONS ?? ""}`) {
4161
+ return globalThis[REGISTER_FLAG] === true || flags.includes("hook.mjs");
4162
+ }
4163
+ const LOGGER_PACKAGES = [
4164
+ "pino",
4165
+ "winston",
4166
+ "bunyan"
4167
+ ];
4168
+ function loadedModulePaths() {
4169
+ try {
4170
+ const { createRequire } = requireModule("node:module");
4171
+ return Object.entries(createRequire(`${process.cwd()}/`).cache).filter(([, mod]) => mod?.loaded).map(([path]) => path);
4172
+ } catch {
4173
+ return [];
4174
+ }
4175
+ }
4176
+ /**
4177
+ * Names in the array form that loaded nothing: a typo, or a library
4178
+ * auto-instrumentations-node has no instrumentation for (`fastify`, `next`).
4179
+ * Upstream only reports these through `diag`, which is silent by default.
4180
+ * Names a manual instrumentation replaced are not missing.
4181
+ */
4182
+ function missingInstrumentations(names, loaded, manualInstrumentationNames = /* @__PURE__ */ new Set()) {
4183
+ const loadedNames = new Set(loaded.map((i) => i.instrumentationName));
4184
+ const replaced = new Set([...manualInstrumentationNames].map((className) => INSTRUMENTATION_CLASS_TO_PACKAGE.get(className)));
4185
+ return names.filter((name) => {
4186
+ const packageName = toPackageName(name);
4187
+ return !loadedNames.has(packageName) && !replaced.has(packageName);
4188
+ });
4189
+ }
4190
+ /**
4191
+ * Logger packages the config instruments that were already loaded when
4192
+ * `init()` ran. Instrumentations patch a package as it loads, so these never
4193
+ * get trace context: the logger module was imported before `init()`.
4194
+ */
4195
+ function loggersLoadedBeforeInit(integrations, loadedPaths = loadedModulePaths()) {
4196
+ if (integrations === false) return [];
4197
+ const requested = integrations === true ? LOGGER_PACKAGES : Array.isArray(integrations) ? integrations : Object.entries(integrations).filter(([, options]) => options.enabled !== false).map(([name]) => name);
4198
+ const wanted = new Set(requested.map((name) => toPackageName(name)));
4199
+ const paths = loadedPaths.map((path) => path.replaceAll("\\", "/"));
4200
+ return LOGGER_PACKAGES.filter((name) => wanted.has(toPackageName(name)) && paths.some((path) => path.includes(`/node_modules/${name}/`)));
4201
+ }
4154
4202
  /**
4155
4203
  * Lazy-load auto-instrumentations (optional peer dependency)
4156
4204
  * Only loads when integrations config is truthy, avoiding ~40+ package imports at startup.
@@ -4179,11 +4227,12 @@ See: https://github.com/jagreehal/autotel#esm-setup`);
4179
4227
  * @internal
4180
4228
  */
4181
4229
  let _autoInstrumentationsLoader = null;
4230
+ const HTTP_PACKAGE = "@opentelemetry/instrumentation-http";
4182
4231
  /**
4183
4232
  * Get auto-instrumentations based on simple integration names
4184
4233
  * Excludes instrumentations that are manually provided to avoid conflicts
4185
4234
  */
4186
- function getAutoInstrumentations(integrations, manualInstrumentationNames = /* @__PURE__ */ new Set()) {
4235
+ function getAutoInstrumentations(integrations, manualInstrumentationNames = /* @__PURE__ */ new Set(), ignoredServerPorts = []) {
4187
4236
  if (integrations === false) return [];
4188
4237
  const getNodeAutoInstrumentations = _autoInstrumentationsLoader ? _autoInstrumentationsLoader() : loadNodeAutoInstrumentations();
4189
4238
  const exclusionConfig = {};
@@ -4198,7 +4247,18 @@ function getAutoInstrumentations(integrations, manualInstrumentationNames = /* @
4198
4247
  if (packageName in exclusionConfig) continue;
4199
4248
  config[packageName] = options;
4200
4249
  }
4201
- return getNodeAutoInstrumentations(withAutotelDefaults(config));
4250
+ const http = config[HTTP_PACKAGE] ?? {};
4251
+ if (ignoredServerPorts.length > 0 && http.enabled !== false) {
4252
+ const userHook = http.ignoreIncomingRequestHook;
4253
+ config[HTTP_PACKAGE] = {
4254
+ ...http,
4255
+ ignoreIncomingRequestHook: (request) => ignoredServerPorts.includes(request.socket?.localPort ?? -1) || (userHook?.(request) ?? false)
4256
+ };
4257
+ }
4258
+ const resolved = withAutotelDefaults(config);
4259
+ if (!Array.isArray(integrations)) return getNodeAutoInstrumentations(resolved);
4260
+ const listed = new Set(Object.keys(config).filter((name) => config[name]?.enabled === true));
4261
+ return getNodeAutoInstrumentations(new Proxy(resolved, { get: (target, name) => typeof name === "string" && listed.has(name) ? target[name] : { enabled: false } }));
4202
4262
  }
4203
4263
 
4204
4264
  //#endregion
@@ -4363,6 +4423,10 @@ function init(cfg) {
4363
4423
  const minLevel = mergedConfig.minLevel ?? "info";
4364
4424
  const baseLogger = mergedConfig.logger || silentLogger;
4365
4425
  logger = wrapLogger(baseLogger, silent, minLevel);
4426
+ const setupWarn = (msg) => {
4427
+ if (mergedConfig.logger) logger.warn({}, msg);
4428
+ else if (!silent) console.warn(msg);
4429
+ };
4366
4430
  if (initialized) logger.warn({}, "[autotel] init() called again - last config wins. This may cause unexpected behavior.");
4367
4431
  config = mergedConfig;
4368
4432
  validationConfig = mergedConfig.validation || null;
@@ -4371,19 +4435,24 @@ function init(cfg) {
4371
4435
  const version = mergedConfig.version || detectVersion();
4372
4436
  const environment = mergedConfig.environment || process.env.NODE_ENV || "development";
4373
4437
  const metricsEnabled = resolveMetricsFlag(mergedConfig.metrics);
4374
- const canonicalLogger = mergedConfig.canonicalLogLines?.logger ?? mergedConfig.logger;
4438
+ const canonicalLogger = mergedConfig.canonicalLogLines?.logger;
4375
4439
  const hasCanonicalLogger = Array.isArray(canonicalLogger) ? canonicalLogger.length > 0 : canonicalLogger !== void 0;
4376
4440
  const canonicalUsesOtel = mergedConfig.canonicalLogLines?.enabled === true && (mergedConfig.canonicalLogLines.otel ?? !hasCanonicalLogger);
4377
4441
  const logsEnabled = resolveLogsFlag(mergedConfig.logs ?? (canonicalUsesOtel ? true : "auto"));
4442
+ let devtoolsPort;
4378
4443
  if (devtoolsConfig.enabled && devtoolsConfig.embedded) {
4379
4444
  const devtoolsModule = _optionalRequire("autotel-devtools");
4380
4445
  if (devtoolsModule?.createDevtools) {
4446
+ if (devtoolsConfig.port === 0) throw new Error("[autotel] devtools.embedded needs a fixed devtools.port; 0 is not supported");
4381
4447
  const devtoolsInstance = devtoolsModule.createDevtools({
4382
4448
  port: devtoolsConfig.port,
4383
4449
  host: devtoolsConfig.host,
4384
- verbose: devtoolsConfig.verbose
4450
+ verbose: devtoolsConfig.verbose,
4451
+ maxPortTries: 1
4385
4452
  });
4453
+ devtoolsInstance.ready?.catch((error) => setupWarn(`[autotel] autotel-devtools could not start (${error instanceof Error ? error.message : String(error)}). Telemetry is still sent to ${endpoint}; free the port or set devtools.port.`));
4386
4454
  _devtoolsClose = devtoolsInstance.close;
4455
+ devtoolsPort = devtoolsInstance.port;
4387
4456
  endpoint = `http://${devtoolsConfig.host}:${devtoolsInstance.port}`;
4388
4457
  logger.info({}, `[autotel] autotel-devtools embedded server started at ${endpoint}`);
4389
4458
  } else logger.warn({}, "[autotel] devtools.embedded requested but autotel-devtools is not installed. Falling back to endpoint-only mode.");
@@ -4496,17 +4565,22 @@ function init(cfg) {
4496
4565
  if (mergedConfig.policies && logRecordProcessors) logRecordProcessors = logRecordProcessors.map((processor) => new PolicyLogRecordProcessor(processor));
4497
4566
  let finalInstrumentations = mergedConfig.instrumentations ? [...mergedConfig.instrumentations] : [];
4498
4567
  if (mergedConfig.autoInstrumentations !== void 0 && mergedConfig.autoInstrumentations !== false) {
4499
- if (isESMMode()) logger.info({}, "[autotel] ESM mode detected. For auto-instrumentation to work:\n 1. Install @opentelemetry/auto-instrumentations-node as a direct dependency\n 2. Import autotel/register FIRST in your instrumentation file\n 3. Use getNodeAutoInstrumentations() directly instead of autoInstrumentations\n See: https://github.com/jagreehal/autotel#esm-setup");
4568
+ if (isESMMode() && !isEsmHookLoaded()) setupWarn("[autotel] ESM app without the OTel loader hook: packages loaded with `import` (pino, express, ...) will not be instrumented. Start node with `--import autotel/register`. See: https://github.com/jagreehal/autotel#esm-setup");
4569
+ for (const name of loggersLoadedBeforeInit(mergedConfig.autoInstrumentations)) setupWarn(`[autotel] ${name} was loaded before init(), so its logs get no trace context. Run init() first, and don't create your ${name} logger in the module that calls it.`);
4500
4570
  try {
4501
4571
  const manualInstrumentationNames = getInstrumentationNames(mergedConfig.instrumentations ?? []);
4502
4572
  if (manualInstrumentationNames.size > 0) {
4503
4573
  const manualNames = [...manualInstrumentationNames].join(", ");
4504
4574
  logger.info({}, `[autotel] Detected manual instrumentations (${manualNames}). These will take precedence over auto-instrumentations. Tip: Set autoInstrumentations:false if you want full manual control, or remove manual configs to use auto-instrumentations.`);
4505
4575
  }
4506
- const autoInstrumentations = getAutoInstrumentations(mergedConfig.autoInstrumentations, manualInstrumentationNames);
4576
+ const autoInstrumentations = getAutoInstrumentations(mergedConfig.autoInstrumentations, manualInstrumentationNames, devtoolsPort === void 0 ? [] : [devtoolsPort]);
4577
+ if (Array.isArray(mergedConfig.autoInstrumentations)) {
4578
+ const missing = missingInstrumentations(mergedConfig.autoInstrumentations, autoInstrumentations ?? [], manualInstrumentationNames);
4579
+ if (missing.length > 0) setupWarn(`[autotel] autoInstrumentations: no instrumentation loaded for ${missing.join(", ")}. Check the spelling; @opentelemetry/auto-instrumentations-node has none for some libraries (fastify, next).`);
4580
+ }
4507
4581
  if (autoInstrumentations && autoInstrumentations.length > 0) finalInstrumentations = [...finalInstrumentations, ...autoInstrumentations];
4508
4582
  } catch (error) {
4509
- logger.warn({}, `[autotel] Failed to configure auto-instrumentations: ${error instanceof Error ? error.message : String(error)}`);
4583
+ setupWarn(`[autotel] Failed to configure auto-instrumentations: ${error instanceof Error ? error.message : String(error)}`);
4510
4584
  }
4511
4585
  }
4512
4586
  const autotelSampler = mergedConfig.sampler ?? (mergedConfig.sampling ? resolveSamplingPreset(mergedConfig.sampling) : void 0);
@@ -4623,4 +4697,4 @@ function getSdk() {
4623
4697
  }
4624
4698
 
4625
4699
  //#endregion
4626
- export { generateCorrelationId as $, CompositeSampler as A, hashUnitInterval as B, TailSamplingSpanProcessor as C, AUTOTEL_SAMPLING_TAIL_KEEP as D, AUTOTEL_SAMPLING_TAIL_EVALUATED as E, RandomSampler as F, createTraceContext as G, markForceKept as H, UserIdSampler as I, getContextStorage as J, defineBaggageSchema as K, createLinkFromHeaders as L, FeatureFlagSampler as M, KeyTargetRateSampler as N, AdaptiveSampler as O, NeverSampler as P, CORRELATION_ID_BAGGAGE_KEY as Q, debugCaptureRequested as R, BaggageSpanProcessor as S, AUTOTEL_SAMPLING_RATE as T, resolveSamplingPreset as U, isForceKept as V, samplingPresets as W, getEventQueue as X, hasExplicitSpanStatus as Y, track as Z, loadYamlConfig as _, init as a, setCorrelationIdInBaggage as at, getRequestSpanIn as b, lockLogger as c, Event as d, getCorrelationId as et, getEvents as f, hasYamlConfig as g, runInOperationContext as h, getSdk as i, setCorrelationId as it, DeterministicSampler as j, AlwaysSampler as k, flush as l, getOperationContext as m, getConfiguredSampler as n, getOrCreateCorrelationId as nt, isInitialized as o, resetEvents as p, getActiveContextWithBaggage as q, getLogger as r, runWithCorrelationId as rt, isLoggerLocked as s, getConfig as t, getCorrelationStorage as tt, shutdown as u, loadYamlConfigFromFile as v, AUTOTEL_DEBUG_BAGGAGE_KEY as w, rememberRequestAttributes as x, createStringRedactor as y, extractLinksFromBatch as z };
4700
+ export { generateCorrelationId as $, CompositeSampler as A, hashUnitInterval as B, TailSamplingSpanProcessor as C, AUTOTEL_SAMPLING_TAIL_KEEP as D, AUTOTEL_SAMPLING_TAIL_EVALUATED as E, RandomSampler as F, createTraceContext as G, markForceKept as H, UserIdSampler as I, getActiveContextWithBaggage as J, defineBaggageSchema as K, createLinkFromHeaders as L, FeatureFlagSampler as M, KeyTargetRateSampler as N, AdaptiveSampler as O, NeverSampler as P, CORRELATION_ID_BAGGAGE_KEY as Q, debugCaptureRequested as R, BaggageSpanProcessor as S, AUTOTEL_SAMPLING_RATE as T, resolveSamplingPreset as U, isForceKept as V, samplingPresets as W, getEventQueue as X, getContextStorage as Y, track as Z, loadYamlConfig as _, init as a, setCorrelationIdInBaggage as at, getRequestSpanIn as b, lockLogger as c, Event as d, getCorrelationId as et, getEvents as f, hasYamlConfig as g, runInOperationContext as h, getSdk as i, setCorrelationId as it, DeterministicSampler as j, AlwaysSampler as k, flush as l, getOperationContext as m, getConfiguredSampler as n, getOrCreateCorrelationId as nt, isInitialized as o, resetEvents as p, explicitSpanStatus as q, getLogger as r, runWithCorrelationId as rt, isLoggerLocked as s, getConfig as t, getCorrelationStorage as tt, shutdown as u, loadYamlConfigFromFile as v, AUTOTEL_DEBUG_BAGGAGE_KEY as w, rememberRequestAttributes as x, createStringRedactor as y, extractLinksFromBatch as z };
@@ -1,11 +1,11 @@
1
1
  import { a as AttributeRedactorPreset, r as AttributeRedactorConfig } from "./attribute-redacting-processor-CvBE0z1p.cjs";
2
- import { s as Logger } from "./logger-C9UT1aZ_.cjs";
3
- import { g as SamplingPreset, m as Sampler$1 } from "./sampling-Dud4XU2m.cjs";
2
+ import { s as Logger } from "./logger-b0qgWFy7.cjs";
3
+ import { g as SamplingPreset, m as Sampler$1 } from "./sampling-CLJmXMw0.cjs";
4
4
  import { o as EventSubscriber } from "./event-subscriber-DMACQA-1.cjs";
5
5
  import { r as SpanFilterPredicate } from "./filtering-span-processor-BYvLMiNz.cjs";
6
6
  import { r as Policy } from "./policy-C4QcZnqv.cjs";
7
7
  import { r as SpanNameNormalizerConfig } from "./span-name-normalizer-CN1-8C76.cjs";
8
- import { t as CanonicalLogLineOptions } from "./canonical-log-line-processor-DLeAQjgb.cjs";
8
+ import { t as CanonicalLogLineOptions } from "./canonical-log-line-processor-C-RdV5af.cjs";
9
9
  import { BasicTracerProvider, ReadableSpan, SpanExporter, SpanProcessor } from "@opentelemetry/sdk-trace-base";
10
10
  import { Attributes, Sampler, TextMapPropagator } from "@opentelemetry/api";
11
11
  import { Resource, ResourceDetector } from "@opentelemetry/resources";
@@ -289,10 +289,11 @@ interface CanonicalLogLinesConfig {
289
289
  enabled: boolean;
290
290
  /**
291
291
  * Logger(s) to emit canonical log lines through. Pass an array to fan out to
292
- * several. When omitted, lines go to the OTel Logs API instead.
292
+ * several. When omitted, lines go to the OTel Logs API instead. The
293
+ * top-level `logger` is for autotel's diagnostics and is not used here.
293
294
  *
294
- * Note this falls back to the top-level `logger` on AutotelConfig, so
295
- * setting that alone also diverts canonical log lines away from OTLP.
295
+ * Called as `logger[level](fields, message)`, pino's signature (bunyan's
296
+ * too). Winston takes `(message, meta)`, so wrap it.
296
297
  */
297
298
  logger?: Logger | Logger[];
298
299
  /**
@@ -488,7 +489,7 @@ interface AutotelConfig {
488
489
  * })
489
490
  * ```
490
491
  *
491
- * @example Enable specific auto-instrumentations
492
+ * @example Enable only these (the array is an allowlist; include `'http'` for request spans)
492
493
  * ```typescript
493
494
  * init({
494
495
  * service: 'my-app',
@@ -496,7 +497,7 @@ interface AutotelConfig {
496
497
  * })
497
498
  * ```
498
499
  *
499
- * @example Configure specific auto-instrumentations
500
+ * @example Everything, with per-package overrides (unlisted packages still load)
500
501
  * ```typescript
501
502
  * init({
502
503
  * service: 'my-app',
@@ -867,7 +868,8 @@ interface AutotelConfig {
867
868
  * Logger instance for internal autotel diagnostic messages
868
869
  *
869
870
  * This logger is used by autotel internally to log initialization, warnings,
870
- * and debug information. Any logger with info/warn/error/debug methods works.
871
+ * and debug information. It is called pino-style, `(fields, message)`, so pino
872
+ * and bunyan work as is; wrap a `(message, meta)` logger such as winston.
871
873
  *
872
874
  * **For OTel instrumentation of your application logs**, use the `autoInstrumentations` option:
873
875
  * - `autoInstrumentations: ['pino']` - Injects traceId/spanId into Pino logs
@@ -875,18 +877,17 @@ interface AutotelConfig {
875
877
  *
876
878
  * Default: silent logger (no-op)
877
879
  *
878
- * @example Pino with OTel instrumentation
880
+ * @example Pino with trace context (no `logger` needed)
879
881
  * ```typescript
880
- * import pino from 'pino'
882
+ * // telemetry.ts - imported first, before anything that creates the logger
881
883
  * import { init } from 'autotel'
884
+ * init({ service: 'my-app', autoInstrumentations: ['pino'], logs: true })
882
885
  *
883
- * const logger = pino({ level: 'info' })
884
- * init({
885
- * service: 'my-app',
886
- * logger, // For autotel's internal logs
887
- * autoInstrumentations: ['pino'] // For OTel trace context in YOUR logs
888
- * })
886
+ * // logger.ts - created after init(), so pino is patched as it loads
887
+ * import pino from 'pino'
888
+ * export const logger = pino()
889
889
  * ```
890
+ * Under ESM, start node with `--import autotel/register` too.
890
891
  *
891
892
  * @example Custom logger for autotel diagnostics
892
893
  * ```typescript
@@ -900,18 +901,10 @@ interface AutotelConfig {
900
901
  * ```
901
902
  *
902
903
  * @remarks
903
- * This is also the fallback logger for canonical log lines, so setting it
904
- * sends them here INSTEAD of the OTel Logs API — and therefore stops them
905
- * reaching an OTLP logs backend such as Loki. On a platform whose log view
906
- * reads stdout you usually want both, which is what
907
- * `canonicalLogLines.otel` is for:
908
- *
909
- * ```typescript
910
- * init({
911
- * service: 'my-app',
912
- * canonicalLogLines: { enabled: true, logger, otel: true },
913
- * })
914
- * ```
904
+ * Don't pass your app logger here to get trace context: creating it before
905
+ * `init()` loads its package before the instrumentation, so it is never
906
+ * patched. Canonical log lines do not use this logger either; set
907
+ * `canonicalLogLines.logger` (or `pretty`) for those.
915
908
  */
916
909
  logger?: Logger;
917
910
  /**
@@ -919,7 +912,9 @@ interface AutotelConfig {
919
912
  * - true: Flush on root span completion (default)
920
913
  * - false: Use batching (events flush every 10 seconds automatically)
921
914
  *
922
- * Only flushes on root spans to avoid excessive network calls.
915
+ * Only flushes on root spans to avoid excessive network calls. A span whose
916
+ * parent arrived from another process (an extracted `traceparent`) counts as
917
+ * a root, so a server flushes once per incoming request.
923
918
  * Default is true for serverless/short-lived processes. Set to false
924
919
  * for long-running services where batching is more efficient.
925
920
  */
@@ -9,7 +9,7 @@ const require_policy = require('./policy.cjs');
9
9
  const require_span_name_normalizer = require('./span-name-normalizer.cjs');
10
10
  const require_attribute_redacting_processor = require('./attribute-redacting-processor.cjs');
11
11
  const require_pretty_console_exporter = require('./pretty-console-exporter-CMzlrRNg.cjs');
12
- const require_canonical_log_line_processor = require('./canonical-log-line-processor-BU03jJeA.cjs');
12
+ const require_canonical_log_line_processor = require('./canonical-log-line-processor-CBRe-VsO.cjs');
13
13
  const require_channel = require('./channel-D0eaVqn3.cjs');
14
14
  const require_metric = require('./metric.cjs');
15
15
  let _opentelemetry_api = require("@opentelemetry/api");
@@ -1080,10 +1080,10 @@ function resetEventQueue() {
1080
1080
 
1081
1081
  //#endregion
1082
1082
  //#region src/trace-context.ts
1083
- const spansWithExplicitStatus = /* @__PURE__ */ new WeakSet();
1084
- /** Whether a TraceContext consumer explicitly chose this span's status. */
1085
- function hasExplicitSpanStatus(span) {
1086
- return spansWithExplicitStatus.has(span);
1083
+ const explicitSpanStatuses = /* @__PURE__ */ new WeakMap();
1084
+ /** The status a TraceContext consumer explicitly chose for this span, if any. */
1085
+ function explicitSpanStatus(span) {
1086
+ return explicitSpanStatuses.get(span);
1087
1087
  }
1088
1088
  /**
1089
1089
  * AsyncLocalStorage for storing the active context with baggage
@@ -1171,7 +1171,7 @@ function createTraceContext(span) {
1171
1171
  span.setAttributes(require_structured_error.flattenToAttributes(attrs));
1172
1172
  },
1173
1173
  setStatus: (status) => {
1174
- spansWithExplicitStatus.add(span);
1174
+ explicitSpanStatuses.set(span, status.code);
1175
1175
  span.setStatus(status);
1176
1176
  },
1177
1177
  recordException: span.recordException.bind(span),
@@ -1964,15 +1964,15 @@ const silentLogger = {
1964
1964
  function wrapLogger(base, silent, minLevel) {
1965
1965
  if (silent) return silentLogger;
1966
1966
  const threshold = LOG_LEVELS[minLevel];
1967
- const wrap = (fn, level) => {
1967
+ const wrap = (level) => {
1968
1968
  if (LOG_LEVELS[level] < threshold) return (() => {});
1969
- return ((...args) => fn(...args));
1969
+ return ((...args) => base[level](...args));
1970
1970
  };
1971
1971
  return {
1972
- debug: wrap(base.debug, "debug"),
1973
- info: wrap(base.info, "info"),
1974
- warn: wrap(base.warn, "warn"),
1975
- error: wrap(base.error, "error")
1972
+ debug: wrap("debug"),
1973
+ info: wrap("info"),
1974
+ warn: wrap("warn"),
1975
+ error: wrap("error")
1976
1976
  };
1977
1977
  }
1978
1978
 
@@ -4142,20 +4142,68 @@ function toPackageName(name) {
4142
4142
  return name.startsWith("@opentelemetry/instrumentation-") ? name : `@opentelemetry/instrumentation-${name}`;
4143
4143
  }
4144
4144
  /**
4145
- * Detect if we're running in ESM mode
4145
+ * Whether the app's entry runs as ESM: its extension when it has an explicit
4146
+ * one (`.mjs`/`.cjs`, and `.mts`/`.cts` under tsx), else the cwd
4147
+ * package.json's `type`.
4146
4148
  */
4147
- function isESMMode() {
4149
+ function isESMMode(entry = process.argv[1] ?? "") {
4150
+ if (/\.c[jt]s$/.test(entry)) return false;
4151
+ if (/\.m[jt]s$/.test(entry)) return true;
4148
4152
  try {
4149
4153
  const fs = require_node_require.requireModule("node:fs");
4150
- try {
4151
- return JSON.parse(fs.readFileSync(`${process.cwd()}/package.json`, "utf8")).type === "module";
4152
- } catch {
4153
- return false;
4154
- }
4154
+ return JSON.parse(fs.readFileSync(`${process.cwd()}/package.json`, "utf8")).type === "module";
4155
4155
  } catch {
4156
4156
  return false;
4157
4157
  }
4158
4158
  }
4159
+ /** Set by `autotel/register` so `init()` can tell the ESM hook is in place. */
4160
+ const REGISTER_FLAG = Symbol.for("autotel.register");
4161
+ /**
4162
+ * Whether the OTel ESM loader hook is registered: by `autotel/register`, or
4163
+ * by pointing node at a `hook.mjs` directly.
4164
+ */
4165
+ function isEsmHookLoaded(flags = `${process.execArgv.join(" ")} ${process.env.NODE_OPTIONS ?? ""}`) {
4166
+ return globalThis[REGISTER_FLAG] === true || flags.includes("hook.mjs");
4167
+ }
4168
+ const LOGGER_PACKAGES = [
4169
+ "pino",
4170
+ "winston",
4171
+ "bunyan"
4172
+ ];
4173
+ function loadedModulePaths() {
4174
+ try {
4175
+ const { createRequire } = require_node_require.requireModule("node:module");
4176
+ return Object.entries(createRequire(`${process.cwd()}/`).cache).filter(([, mod]) => mod?.loaded).map(([path]) => path);
4177
+ } catch {
4178
+ return [];
4179
+ }
4180
+ }
4181
+ /**
4182
+ * Names in the array form that loaded nothing: a typo, or a library
4183
+ * auto-instrumentations-node has no instrumentation for (`fastify`, `next`).
4184
+ * Upstream only reports these through `diag`, which is silent by default.
4185
+ * Names a manual instrumentation replaced are not missing.
4186
+ */
4187
+ function missingInstrumentations(names, loaded, manualInstrumentationNames = /* @__PURE__ */ new Set()) {
4188
+ const loadedNames = new Set(loaded.map((i) => i.instrumentationName));
4189
+ const replaced = new Set([...manualInstrumentationNames].map((className) => INSTRUMENTATION_CLASS_TO_PACKAGE.get(className)));
4190
+ return names.filter((name) => {
4191
+ const packageName = toPackageName(name);
4192
+ return !loadedNames.has(packageName) && !replaced.has(packageName);
4193
+ });
4194
+ }
4195
+ /**
4196
+ * Logger packages the config instruments that were already loaded when
4197
+ * `init()` ran. Instrumentations patch a package as it loads, so these never
4198
+ * get trace context: the logger module was imported before `init()`.
4199
+ */
4200
+ function loggersLoadedBeforeInit(integrations, loadedPaths = loadedModulePaths()) {
4201
+ if (integrations === false) return [];
4202
+ const requested = integrations === true ? LOGGER_PACKAGES : Array.isArray(integrations) ? integrations : Object.entries(integrations).filter(([, options]) => options.enabled !== false).map(([name]) => name);
4203
+ const wanted = new Set(requested.map((name) => toPackageName(name)));
4204
+ const paths = loadedPaths.map((path) => path.replaceAll("\\", "/"));
4205
+ return LOGGER_PACKAGES.filter((name) => wanted.has(toPackageName(name)) && paths.some((path) => path.includes(`/node_modules/${name}/`)));
4206
+ }
4159
4207
  /**
4160
4208
  * Lazy-load auto-instrumentations (optional peer dependency)
4161
4209
  * Only loads when integrations config is truthy, avoiding ~40+ package imports at startup.
@@ -4184,11 +4232,12 @@ See: https://github.com/jagreehal/autotel#esm-setup`);
4184
4232
  * @internal
4185
4233
  */
4186
4234
  let _autoInstrumentationsLoader = null;
4235
+ const HTTP_PACKAGE = "@opentelemetry/instrumentation-http";
4187
4236
  /**
4188
4237
  * Get auto-instrumentations based on simple integration names
4189
4238
  * Excludes instrumentations that are manually provided to avoid conflicts
4190
4239
  */
4191
- function getAutoInstrumentations(integrations, manualInstrumentationNames = /* @__PURE__ */ new Set()) {
4240
+ function getAutoInstrumentations(integrations, manualInstrumentationNames = /* @__PURE__ */ new Set(), ignoredServerPorts = []) {
4192
4241
  if (integrations === false) return [];
4193
4242
  const getNodeAutoInstrumentations = _autoInstrumentationsLoader ? _autoInstrumentationsLoader() : loadNodeAutoInstrumentations();
4194
4243
  const exclusionConfig = {};
@@ -4203,7 +4252,18 @@ function getAutoInstrumentations(integrations, manualInstrumentationNames = /* @
4203
4252
  if (packageName in exclusionConfig) continue;
4204
4253
  config[packageName] = options;
4205
4254
  }
4206
- return getNodeAutoInstrumentations(withAutotelDefaults(config));
4255
+ const http = config[HTTP_PACKAGE] ?? {};
4256
+ if (ignoredServerPorts.length > 0 && http.enabled !== false) {
4257
+ const userHook = http.ignoreIncomingRequestHook;
4258
+ config[HTTP_PACKAGE] = {
4259
+ ...http,
4260
+ ignoreIncomingRequestHook: (request) => ignoredServerPorts.includes(request.socket?.localPort ?? -1) || (userHook?.(request) ?? false)
4261
+ };
4262
+ }
4263
+ const resolved = withAutotelDefaults(config);
4264
+ if (!Array.isArray(integrations)) return getNodeAutoInstrumentations(resolved);
4265
+ const listed = new Set(Object.keys(config).filter((name) => config[name]?.enabled === true));
4266
+ return getNodeAutoInstrumentations(new Proxy(resolved, { get: (target, name) => typeof name === "string" && listed.has(name) ? target[name] : { enabled: false } }));
4207
4267
  }
4208
4268
 
4209
4269
  //#endregion
@@ -4368,6 +4428,10 @@ function init(cfg) {
4368
4428
  const minLevel = mergedConfig.minLevel ?? "info";
4369
4429
  const baseLogger = mergedConfig.logger || silentLogger;
4370
4430
  logger = wrapLogger(baseLogger, silent, minLevel);
4431
+ const setupWarn = (msg) => {
4432
+ if (mergedConfig.logger) logger.warn({}, msg);
4433
+ else if (!silent) console.warn(msg);
4434
+ };
4371
4435
  if (initialized) logger.warn({}, "[autotel] init() called again - last config wins. This may cause unexpected behavior.");
4372
4436
  config = mergedConfig;
4373
4437
  validationConfig = mergedConfig.validation || null;
@@ -4376,19 +4440,24 @@ function init(cfg) {
4376
4440
  const version = mergedConfig.version || detectVersion();
4377
4441
  const environment = mergedConfig.environment || process.env.NODE_ENV || "development";
4378
4442
  const metricsEnabled = resolveMetricsFlag(mergedConfig.metrics);
4379
- const canonicalLogger = mergedConfig.canonicalLogLines?.logger ?? mergedConfig.logger;
4443
+ const canonicalLogger = mergedConfig.canonicalLogLines?.logger;
4380
4444
  const hasCanonicalLogger = Array.isArray(canonicalLogger) ? canonicalLogger.length > 0 : canonicalLogger !== void 0;
4381
4445
  const canonicalUsesOtel = mergedConfig.canonicalLogLines?.enabled === true && (mergedConfig.canonicalLogLines.otel ?? !hasCanonicalLogger);
4382
4446
  const logsEnabled = resolveLogsFlag(mergedConfig.logs ?? (canonicalUsesOtel ? true : "auto"));
4447
+ let devtoolsPort;
4383
4448
  if (devtoolsConfig.enabled && devtoolsConfig.embedded) {
4384
4449
  const devtoolsModule = _optionalRequire("autotel-devtools");
4385
4450
  if (devtoolsModule?.createDevtools) {
4451
+ if (devtoolsConfig.port === 0) throw new Error("[autotel] devtools.embedded needs a fixed devtools.port; 0 is not supported");
4386
4452
  const devtoolsInstance = devtoolsModule.createDevtools({
4387
4453
  port: devtoolsConfig.port,
4388
4454
  host: devtoolsConfig.host,
4389
- verbose: devtoolsConfig.verbose
4455
+ verbose: devtoolsConfig.verbose,
4456
+ maxPortTries: 1
4390
4457
  });
4458
+ devtoolsInstance.ready?.catch((error) => setupWarn(`[autotel] autotel-devtools could not start (${error instanceof Error ? error.message : String(error)}). Telemetry is still sent to ${endpoint}; free the port or set devtools.port.`));
4391
4459
  _devtoolsClose = devtoolsInstance.close;
4460
+ devtoolsPort = devtoolsInstance.port;
4392
4461
  endpoint = `http://${devtoolsConfig.host}:${devtoolsInstance.port}`;
4393
4462
  logger.info({}, `[autotel] autotel-devtools embedded server started at ${endpoint}`);
4394
4463
  } else logger.warn({}, "[autotel] devtools.embedded requested but autotel-devtools is not installed. Falling back to endpoint-only mode.");
@@ -4501,17 +4570,22 @@ function init(cfg) {
4501
4570
  if (mergedConfig.policies && logRecordProcessors) logRecordProcessors = logRecordProcessors.map((processor) => new require_policy.PolicyLogRecordProcessor(processor));
4502
4571
  let finalInstrumentations = mergedConfig.instrumentations ? [...mergedConfig.instrumentations] : [];
4503
4572
  if (mergedConfig.autoInstrumentations !== void 0 && mergedConfig.autoInstrumentations !== false) {
4504
- if (isESMMode()) logger.info({}, "[autotel] ESM mode detected. For auto-instrumentation to work:\n 1. Install @opentelemetry/auto-instrumentations-node as a direct dependency\n 2. Import autotel/register FIRST in your instrumentation file\n 3. Use getNodeAutoInstrumentations() directly instead of autoInstrumentations\n See: https://github.com/jagreehal/autotel#esm-setup");
4573
+ if (isESMMode() && !isEsmHookLoaded()) setupWarn("[autotel] ESM app without the OTel loader hook: packages loaded with `import` (pino, express, ...) will not be instrumented. Start node with `--import autotel/register`. See: https://github.com/jagreehal/autotel#esm-setup");
4574
+ for (const name of loggersLoadedBeforeInit(mergedConfig.autoInstrumentations)) setupWarn(`[autotel] ${name} was loaded before init(), so its logs get no trace context. Run init() first, and don't create your ${name} logger in the module that calls it.`);
4505
4575
  try {
4506
4576
  const manualInstrumentationNames = getInstrumentationNames(mergedConfig.instrumentations ?? []);
4507
4577
  if (manualInstrumentationNames.size > 0) {
4508
4578
  const manualNames = [...manualInstrumentationNames].join(", ");
4509
4579
  logger.info({}, `[autotel] Detected manual instrumentations (${manualNames}). These will take precedence over auto-instrumentations. Tip: Set autoInstrumentations:false if you want full manual control, or remove manual configs to use auto-instrumentations.`);
4510
4580
  }
4511
- const autoInstrumentations = getAutoInstrumentations(mergedConfig.autoInstrumentations, manualInstrumentationNames);
4581
+ const autoInstrumentations = getAutoInstrumentations(mergedConfig.autoInstrumentations, manualInstrumentationNames, devtoolsPort === void 0 ? [] : [devtoolsPort]);
4582
+ if (Array.isArray(mergedConfig.autoInstrumentations)) {
4583
+ const missing = missingInstrumentations(mergedConfig.autoInstrumentations, autoInstrumentations ?? [], manualInstrumentationNames);
4584
+ if (missing.length > 0) setupWarn(`[autotel] autoInstrumentations: no instrumentation loaded for ${missing.join(", ")}. Check the spelling; @opentelemetry/auto-instrumentations-node has none for some libraries (fastify, next).`);
4585
+ }
4512
4586
  if (autoInstrumentations && autoInstrumentations.length > 0) finalInstrumentations = [...finalInstrumentations, ...autoInstrumentations];
4513
4587
  } catch (error) {
4514
- logger.warn({}, `[autotel] Failed to configure auto-instrumentations: ${error instanceof Error ? error.message : String(error)}`);
4588
+ setupWarn(`[autotel] Failed to configure auto-instrumentations: ${error instanceof Error ? error.message : String(error)}`);
4515
4589
  }
4516
4590
  }
4517
4591
  const autotelSampler = mergedConfig.sampler ?? (mergedConfig.sampling ? resolveSamplingPreset(mergedConfig.sampling) : void 0);
@@ -4760,6 +4834,12 @@ Object.defineProperty(exports, 'defineBaggageSchema', {
4760
4834
  return defineBaggageSchema;
4761
4835
  }
4762
4836
  });
4837
+ Object.defineProperty(exports, 'explicitSpanStatus', {
4838
+ enumerable: true,
4839
+ get: function () {
4840
+ return explicitSpanStatus;
4841
+ }
4842
+ });
4763
4843
  Object.defineProperty(exports, 'extractLinksFromBatch', {
4764
4844
  enumerable: true,
4765
4845
  get: function () {
@@ -4856,12 +4936,6 @@ Object.defineProperty(exports, 'getSdk', {
4856
4936
  return getSdk;
4857
4937
  }
4858
4938
  });
4859
- Object.defineProperty(exports, 'hasExplicitSpanStatus', {
4860
- enumerable: true,
4861
- get: function () {
4862
- return hasExplicitSpanStatus;
4863
- }
4864
- });
4865
4939
  Object.defineProperty(exports, 'hasYamlConfig', {
4866
4940
  enumerable: true,
4867
4941
  get: function () {