UNPKG

autotel

Version:
1,111 lines (1,103 loc) 43.7 kB
const require_sampling = require('./sampling.cjs'); const require_node_require = require('./node-require-CZ_PU448.cjs'); const require_tail_sampling_processor = require('./tail-sampling-processor.cjs'); const require_filtering_span_processor = require('./filtering-span-processor.cjs'); const require_span_name_normalizer = require('./span-name-normalizer.cjs'); const require_attribute_redacting_processor = require('./attribute-redacting-processor.cjs'); const require_pretty_console_exporter = require('./pretty-console-exporter-CMzlrRNg.cjs'); const require_yaml_config = require('./yaml-config.cjs'); const require_canonical_log_line_processor = require('./canonical-log-line-processor--RlFDHhm.cjs'); let _opentelemetry_sdk_node = require("@opentelemetry/sdk-node"); let _opentelemetry_sdk_trace_base = require("@opentelemetry/sdk-trace-base"); let _opentelemetry_resources = require("@opentelemetry/resources"); let _opentelemetry_semantic_conventions = require("@opentelemetry/semantic-conventions"); let _opentelemetry_api = require("@opentelemetry/api"); let _opentelemetry_sdk_metrics = require("@opentelemetry/sdk-metrics"); let _opentelemetry_exporter_metrics_otlp_http = require("@opentelemetry/exporter-metrics-otlp-http"); let _opentelemetry_exporter_trace_otlp_http = require("@opentelemetry/exporter-trace-otlp-http"); let _opentelemetry_exporter_logs_otlp_http = require("@opentelemetry/exporter-logs-otlp-http"); let _opentelemetry_sdk_logs = require("@opentelemetry/sdk-logs"); //#region src/posthog-logs.ts var RedactingLogRecordProcessor = class { wrapped; redact; constructor(wrapped, redact) { this.wrapped = wrapped; this.redact = redact; } onEmit(logRecord, context) { if (logRecord.body && typeof logRecord.body === "string") logRecord.body = this.redact(logRecord.body); if (logRecord.attributes) { for (const [key, value] of Object.entries(logRecord.attributes)) if (typeof value === "string") logRecord.attributes[key] = this.redact(value); else if (Array.isArray(value)) logRecord.attributes[key] = value.map((item) => typeof item === "string" ? this.redact(item) : item); } this.wrapped.onEmit(logRecord, context); } shutdown() { return this.wrapped.shutdown(); } forceFlush() { return this.wrapped.forceFlush(); } }; /** * Build log record processors for PostHog OTLP logs integration. * * Resolution order: * 1. config.url if provided * 2. POSTHOG_LOGS_URL env var * 3. Empty array (disabled) */ function buildPostHogLogProcessors(config, stringRedactor) { const url = config?.url || process.env.POSTHOG_LOGS_URL; if (!url) return []; const sdkLogs = require_node_require.safeRequire("@opentelemetry/sdk-logs"); const exporterModule = require_node_require.safeRequire("@opentelemetry/exporter-logs-otlp-http"); if (!sdkLogs || !exporterModule) return []; const exporter = new exporterModule.OTLPLogExporter({ url }); let processor = new sdkLogs.BatchLogRecordProcessor(exporter); if (stringRedactor) processor = new RedactingLogRecordProcessor(processor, stringRedactor); return [processor]; } //#endregion //#region src/baggage-span-processor.ts /** * Span processor that automatically copies baggage entries to span attributes * * This makes baggage visible in trace UIs (Jaeger, Grafana, DataDog, etc.) * without manually calling ctx.setAttribute() for each baggage entry. * * @example Enable in init() * ```typescript * init({ * service: 'my-app', * baggage: true // Uses default 'baggage.' prefix * }); * * // Now baggage automatically appears as span attributes * await withBaggage({ * baggage: { 'tenant.id': 't1', 'user.id': 'u1' }, * fn: async () => { * // Span has baggage.tenant.id and baggage.user.id attributes! * } * }); * ``` * * @example Custom prefix * ```typescript * init({ * service: 'my-app', * baggage: 'ctx' // Uses 'ctx.' prefix * }); * // Creates attributes: ctx.tenant.id, ctx.user.id * ``` */ var BaggageSpanProcessor = class { prefix; constructor(options = {}) { this.prefix = options.prefix ?? "baggage."; } onStart(span, parentContext) { let baggage = _opentelemetry_api.propagation.getBaggage(parentContext); if (!baggage) baggage = _opentelemetry_api.propagation.getBaggage(_opentelemetry_api.context.active()); if (!baggage) try { const { getActiveContextWithBaggage } = require_node_require.requireModule("./trace-context"); const storedContext = getActiveContextWithBaggage(); baggage = _opentelemetry_api.propagation.getBaggage(storedContext); } catch {} if (!baggage) return; for (const [key, entry] of baggage.getAllEntries()) span.setAttribute(`${this.prefix}${key}`, entry.value); } onEnd(_span) {} async shutdown() {} async forceFlush() {} }; //#endregion //#region src/redact-values.ts /** Standalone string redaction for use outside the span processor pipeline. */ function createStringRedactor(config) { const resolved = typeof config === "string" ? require_attribute_redacting_processor.REDACTOR_PRESETS[config] : config; const valuePatterns = resolved.valuePatterns ?? []; const defaultReplacement = resolved.replacement ?? "[REDACTED]"; return (value) => { let result = value; for (const { pattern, replacement, mask } of valuePatterns) { pattern.lastIndex = 0; if (mask) result = result.replaceAll(pattern, (match) => mask(match)); else result = result.replaceAll(pattern, replacement ?? defaultReplacement); } return result; }; } //#endregion //#region src/env-config.ts /** * Validate URL format */ function isValidUrl(urlString) { try { const url = new URL(urlString); return url.protocol === "http:" || url.protocol === "https:"; } catch { return false; } } /** * Resolve OpenTelemetry environment variables from process.env */ function resolveOtelEnv() { const env = {}; if (process.env.OTEL_SERVICE_NAME) { const value = process.env.OTEL_SERVICE_NAME.trim(); if (value) env.OTEL_SERVICE_NAME = value; } if (process.env.OTEL_EXPORTER_OTLP_ENDPOINT) { const value = process.env.OTEL_EXPORTER_OTLP_ENDPOINT.trim(); if (value && isValidUrl(value)) env.OTEL_EXPORTER_OTLP_ENDPOINT = value; } if (process.env.OTEL_EXPORTER_OTLP_HEADERS) { const value = process.env.OTEL_EXPORTER_OTLP_HEADERS.trim(); if (value) env.OTEL_EXPORTER_OTLP_HEADERS = value; } if (process.env.OTEL_RESOURCE_ATTRIBUTES) { const value = process.env.OTEL_RESOURCE_ATTRIBUTES.trim(); if (value) env.OTEL_RESOURCE_ATTRIBUTES = value; } if (process.env.OTEL_EXPORTER_OTLP_PROTOCOL) { const value = process.env.OTEL_EXPORTER_OTLP_PROTOCOL.trim().toLowerCase(); if (value === "http" || value === "grpc") env.OTEL_EXPORTER_OTLP_PROTOCOL = value; } if (process.env.OTEL_TRACES_SAMPLER) { const value = process.env.OTEL_TRACES_SAMPLER.trim(); if (value) env.OTEL_TRACES_SAMPLER = value; } if (process.env.OTEL_TRACES_SAMPLER_ARG) { const value = process.env.OTEL_TRACES_SAMPLER_ARG.trim(); if (value) env.OTEL_TRACES_SAMPLER_ARG = value; } return env; } function parseRatioSamplerArg(samplerName, samplerArg) { if (samplerArg === void 0) return 1; const ratio = Number(samplerArg); if (!Number.isFinite(ratio) || ratio < 0 || ratio > 1) { console.error(`[autotel] Invalid OTEL_TRACES_SAMPLER_ARG="${samplerArg}" for ${samplerName}. Expected a number in [0..1]. Falling back to 1.0.`); return 1; } return ratio; } function warnOnUnusedSamplerArg(samplerName, samplerArg) { if (samplerArg !== void 0) console.error(`[autotel] OTEL_TRACES_SAMPLER_ARG is not used by OTEL_TRACES_SAMPLER="${samplerName}". Ignoring value "${samplerArg}".`); } function createSamplerFromEnv(env) { const samplerName = env.OTEL_TRACES_SAMPLER; if (!samplerName) return; switch (samplerName) { case "always_on": warnOnUnusedSamplerArg(samplerName, env.OTEL_TRACES_SAMPLER_ARG); return new _opentelemetry_sdk_trace_base.AlwaysOnSampler(); case "always_off": warnOnUnusedSamplerArg(samplerName, env.OTEL_TRACES_SAMPLER_ARG); return new _opentelemetry_sdk_trace_base.AlwaysOffSampler(); case "traceidratio": return new _opentelemetry_sdk_trace_base.TraceIdRatioBasedSampler(parseRatioSamplerArg(samplerName, env.OTEL_TRACES_SAMPLER_ARG)); case "parentbased_always_on": warnOnUnusedSamplerArg(samplerName, env.OTEL_TRACES_SAMPLER_ARG); return new _opentelemetry_sdk_trace_base.ParentBasedSampler({ root: new _opentelemetry_sdk_trace_base.AlwaysOnSampler() }); case "parentbased_always_off": warnOnUnusedSamplerArg(samplerName, env.OTEL_TRACES_SAMPLER_ARG); return new _opentelemetry_sdk_trace_base.ParentBasedSampler({ root: new _opentelemetry_sdk_trace_base.AlwaysOffSampler() }); case "parentbased_traceidratio": return new _opentelemetry_sdk_trace_base.ParentBasedSampler({ root: new _opentelemetry_sdk_trace_base.TraceIdRatioBasedSampler(parseRatioSamplerArg(samplerName, env.OTEL_TRACES_SAMPLER_ARG)) }); case "jaeger_remote": case "parentbased_jaeger_remote": case "xray": console.error(`[autotel] OTEL_TRACES_SAMPLER="${samplerName}" is not supported yet by autotel. Falling back to the next sampler source.`); return; default: console.error(`[autotel] Unknown OTEL_TRACES_SAMPLER="${samplerName}". Falling back to the next sampler source.`); return; } } /** * Parse OTEL_RESOURCE_ATTRIBUTES from comma-separated key=value pairs * Example: "service.version=1.0.0,deployment.environment=production" */ function parseResourceAttributes(input) { if (!input || input.trim() === "") return {}; const attributes = {}; const pairs = input.split(","); for (const pair of pairs) { const trimmedPair = pair.trim(); if (!trimmedPair) continue; const equalIndex = trimmedPair.indexOf("="); if (equalIndex === -1) continue; const key = trimmedPair.slice(0, equalIndex).trim(); const value = trimmedPair.slice(equalIndex + 1).trim(); if (key && value) attributes[key] = value; } return attributes; } /** * Parse OTEL_EXPORTER_OTLP_HEADERS from comma-separated key=value pairs * Example: "api-key=secret123,x-custom-header=value" */ function parseOtlpHeaders(input) { if (!input || input.trim() === "") return {}; const headers = {}; const pairs = input.split(","); for (const pair of pairs) { const trimmedPair = pair.trim(); if (!trimmedPair) continue; const equalIndex = trimmedPair.indexOf("="); if (equalIndex === -1) continue; const key = trimmedPair.slice(0, equalIndex).trim(); const value = trimmedPair.slice(equalIndex + 1).trim(); if (key && value) headers[key] = value; } return headers; } /** * Convert resolved environment variables to config */ function envToConfig(env) { const config = {}; if (env.OTEL_SERVICE_NAME) config.service = env.OTEL_SERVICE_NAME; if (env.OTEL_EXPORTER_OTLP_ENDPOINT) config.endpoint = env.OTEL_EXPORTER_OTLP_ENDPOINT; if (env.OTEL_EXPORTER_OTLP_PROTOCOL) config.protocol = env.OTEL_EXPORTER_OTLP_PROTOCOL; if (env.OTEL_EXPORTER_OTLP_HEADERS) config.headers = parseOtlpHeaders(env.OTEL_EXPORTER_OTLP_HEADERS); const resourceAttrs = parseResourceAttributes(env.OTEL_RESOURCE_ATTRIBUTES); if (Object.keys(resourceAttrs).length > 0) config.resourceAttributes = resourceAttrs; const sampler = createSamplerFromEnv(env); if (sampler) config.otelSampler = sampler; return config; } /** * Main function to resolve config from environment variables */ function resolveConfigFromEnv() { return envToConfig(resolveOtelEnv()); } //#endregion //#region src/devtools.ts const defaultHost = "127.0.0.1"; const defaultPort = 4318; function resolveDevtoolsConfig(config) { if (!config) return { enabled: false, endpoint: void 0, embedded: false, host: defaultHost, port: defaultPort, verbose: false }; if (config === true) return { enabled: true, endpoint: `http://${defaultHost}:${defaultPort}`, embedded: false, host: defaultHost, port: defaultPort, verbose: false }; const enabled = config.enabled ?? true; const host = config.host ?? defaultHost; const port = config.port ?? defaultPort; const endpoint = config.endpoint ?? `http://${host}:${port}`; return { enabled, endpoint: enabled ? endpoint : void 0, embedded: enabled && (config.embedded ?? false), host, port, verbose: config.verbose ?? false }; } //#endregion //#region src/init.ts /** * Simplified initialization for autotel * * Single init() function with sensible defaults. * Replaces initInstrumentation() and separate events config. */ /** * Silent logger (no-op) - used as default when user doesn't provide one. * Internal autotel logs are silent by default to avoid spam. * Users can import { autotelLogger } from 'autotel/logger' to create their own. */ const silentLogger = { info: () => {}, warn: () => {}, error: () => {}, debug: () => {} }; /** * Adapts an Autotel Sampler to the OTel SDK Sampler interface. */ function toOtelSampler(sampler) { return { shouldSample(_context, _traceId, spanName, _spanKind, _attributes, links) { return { decision: sampler.shouldSample({ operationName: spanName, args: [], links }) ? _opentelemetry_sdk_trace_base.SamplingDecision.RECORD_AND_SAMPLED : _opentelemetry_sdk_trace_base.SamplingDecision.NOT_RECORD }; }, toString() { return `AutotelSamplerAdapter`; } }; } let OTLPTraceExporterGRPC; let OTLPMetricExporterGRPC; let OTLPLogExporterGRPC; /** * Helper: Lazy-load gRPC trace exporter */ function loadGRPCTraceExporter() { if (OTLPTraceExporterGRPC) return OTLPTraceExporterGRPC; try { OTLPTraceExporterGRPC = require_node_require.requireModule("@opentelemetry/exporter-trace-otlp-grpc").OTLPTraceExporter; return OTLPTraceExporterGRPC; } catch { throw new Error("gRPC trace exporter not found. Install @opentelemetry/exporter-trace-otlp-grpc"); } } /** * Helper: Lazy-load gRPC metric exporter */ function loadGRPCMetricExporter() { if (OTLPMetricExporterGRPC) return OTLPMetricExporterGRPC; try { OTLPMetricExporterGRPC = require_node_require.requireModule("@opentelemetry/exporter-metrics-otlp-grpc").OTLPMetricExporter; return OTLPMetricExporterGRPC; } catch { throw new Error("gRPC metric exporter not found. Install @opentelemetry/exporter-metrics-otlp-grpc"); } } /** * Helper: Create trace exporter based on protocol */ function createTraceExporter(protocol, config) { if (protocol === "grpc") return new (loadGRPCTraceExporter())(config); return new _opentelemetry_exporter_trace_otlp_http.OTLPTraceExporter(config); } /** * Helper: Create metric exporter based on protocol */ function createMetricExporter(protocol, config) { if (protocol === "grpc") return new (loadGRPCMetricExporter())(config); return new _opentelemetry_exporter_metrics_otlp_http.OTLPMetricExporter(config); } /** * Helper: Lazy-load gRPC log exporter */ function loadGRPCLogExporter() { if (OTLPLogExporterGRPC) return OTLPLogExporterGRPC; try { OTLPLogExporterGRPC = require_node_require.requireModule("@opentelemetry/exporter-logs-otlp-grpc").OTLPLogExporter; return OTLPLogExporterGRPC; } catch { throw new Error("gRPC log exporter not found. Install @opentelemetry/exporter-logs-otlp-grpc"); } } /** * Helper: Create log exporter based on protocol */ function createLogExporter(protocol, config) { if (protocol === "grpc") return new (loadGRPCLogExporter())(config); return new _opentelemetry_exporter_logs_otlp_http.OTLPLogExporter(config); } /** * Helper: Resolve protocol from config and environment */ function resolveProtocol(configProtocol) { if (configProtocol === "grpc" || configProtocol === "http") return configProtocol; const envProtocol = process.env.OTEL_EXPORTER_OTLP_PROTOCOL; if (envProtocol === "grpc") return "grpc"; if (envProtocol === "http/protobuf" || envProtocol === "http") return "http"; return "http"; } /** * Helper: Adjust endpoint URL for protocol * gRPC exporters don't need the /v1/traces or /v1/metrics path * HTTP exporters need the full path */ function formatEndpointUrl(endpoint, signal, protocol) { if (protocol === "grpc") return endpoint.replace(/\/(v1\/)?(traces|metrics|logs)$/, ""); if (!endpoint.endsWith(`/v1/${signal}`)) return `${endpoint}/v1/${signal}`; return endpoint; } let initialized = false; let locked = false; let config = null; let sdk = null; let warnedOnce = false; let logger = silentLogger; let validationConfig = null; let eventsConfig = null; let _stringRedactor = null; let _optionalRequire = require_node_require.safeRequire; let _devtoolsClose = null; const LOG_LEVELS = { debug: 0, info: 1, warn: 2, error: 3 }; /** * Lock the logger to prevent further `init()` calls. * Use this when framework plugins set up instrumentation and you want * to prevent accidental re-initialization from user code. */ function lockLogger() { locked = true; } /** * Check if the logger has been locked. */ function isLoggerLocked() { return locked; } function createSilentLogger() { return { info: () => {}, warn: () => {}, error: () => {}, debug: () => {} }; } function wrapLogger(base, silent, minLevel) { if (silent) return createSilentLogger(); const threshold = LOG_LEVELS[minLevel]; const wrap = (fn, level) => { if (LOG_LEVELS[level] < threshold) return (() => {}); return ((...args) => fn(...args)); }; return { debug: wrap(base.debug, "debug"), info: wrap(base.info, "info"), warn: wrap(base.warn, "warn"), error: wrap(base.error, "error") }; } /** * Resolve the effective attribute redactor. Explicit config wins (`false` * disables). Otherwise the `AUTOTEL_REDACT_PII` env var controls it, and as a * final default PII redaction is auto-enabled in production. */ function resolveAttributeRedactor(explicit, environment) { if (explicit === false) return void 0; if (explicit !== void 0) return explicit; const flag = process.env.AUTOTEL_REDACT_PII?.trim().toLowerCase(); if (flag) { if ([ "off", "false", "0", "none", "disabled" ].includes(flag)) return; if (flag === "default" || flag === "strict" || flag === "pci-dss") return flag; if ([ "on", "true", "1", "enabled" ].includes(flag)) return "default"; } return environment === "production" ? "default" : void 0; } function detectEnvironmentAttributes() { const attrs = {}; const commitSha = process.env.COMMIT_SHA || process.env.GITHUB_SHA || process.env.VERCEL_GIT_COMMIT_SHA || process.env.CF_PAGES_COMMIT_SHA || process.env.AWS_CODEPIPELINE_EXECUTION_ID; if (commitSha) attrs["service.commit.sha"] = commitSha; const region = process.env.VERCEL_REGION || process.env.AWS_REGION || process.env.AWS_DEFAULT_REGION || process.env.FLY_REGION || process.env.CF_REGION || process.env.GOOGLE_CLOUD_REGION; if (region) attrs["service.region"] = region; const version = process.env.APP_VERSION || process.env.HEROKU_RELEASE_VERSION || process.env.VERCEL_GIT_COMMIT_REF; if (version) attrs["service.deploy.version"] = version; return attrs; } /** * Resolve metrics flag with env var override support */ function resolveMetricsFlag(configFlag = "auto") { const envFlag = process.env.AUTOTEL_METRICS; if (envFlag === "on" || envFlag === "true") return true; if (envFlag === "off" || envFlag === "false") return false; if (configFlag === true) return true; if (configFlag === false) return false; return true; } /** * Resolve logs flag with env var override support. * Defaults to disabled (opt-in only) to avoid unexpected log export * and to preserve the upstream SDK's OTEL_LOGS_EXPORTER handling. */ function resolveLogsFlag(configFlag = "auto") { const envFlag = process.env.AUTOTEL_LOGS; if (envFlag === "on" || envFlag === "true") return true; if (envFlag === "off" || envFlag === "false") return false; if (configFlag === true) return true; if (configFlag === false) return false; return false; } /** * Resolve debug flag with env var override support * * Supports: * - `'pretty'`: Colorized, hierarchical output (PrettyConsoleExporter) * - `true` / `'true'` / `'1'`: Raw JSON output (ConsoleSpanExporter) * - `false` / `'false'` / `'0'`: Disabled */ function resolveDebugFlag(configFlag) { const envFlag = process.env.AUTOTEL_DEBUG; if (envFlag === "pretty") return "pretty"; if (envFlag === "true" || envFlag === "1") return true; if (envFlag === "false" || envFlag === "0") return false; return configFlag ?? false; } function normalizeOtlpHeaders(headers) { if (!headers) return void 0; if (typeof headers !== "string") return headers; const parsed = {}; for (const pair of headers.split(",")) { const [key, ...valueParts] = pair.split("="); if (!key || valueParts.length === 0) continue; parsed[key.trim()] = valueParts.join("=").trim(); } return parsed; } function resolveOtlpDestinations(config, fallbackEndpoint) { return (config.destinations !== void 0 ? config.destinations : fallbackEndpoint ? [{ endpoint: fallbackEndpoint, headers: config.headers, protocol: config.protocol }] : []).map((destination) => ({ endpoint: destination.endpoint, protocol: resolveProtocol(destination.protocol ?? config.protocol), headers: normalizeOtlpHeaders(destination.headers ?? config.headers), signals: destination.signals ? new Set(destination.signals) : void 0 })); } function destinationSupportsSignal(destination, signal) { return destination.signals ? destination.signals.has(signal) : true; } /** * Initialize autotel - Write Once, Observe Everywhere * * Follows OpenTelemetry standards: opinionated defaults with full flexibility * Idempotent: multiple calls are safe, last one wins * * @example Minimal setup (OTLP default) * ```typescript * init({ service: 'my-app' }) * ``` * * @example With events (observe in PostHog, Mixpanel, etc.) * ```typescript * import { PostHogSubscriber } from 'autotel-subscribers/posthog'; * * init({ * service: 'my-app', * subscribers: [new PostHogSubscriber({ apiKey: '...' })] * }) * ``` * * @example Observe in Jaeger * ```typescript * import { JaegerExporter } from '@opentelemetry/exporter-jaeger' * * init({ * service: 'my-app', * spanExporter: new JaegerExporter({ endpoint: 'http://localhost:14268/api/traces' }) * }) * ``` * * @example Observe in Zipkin * ```typescript * import { ZipkinExporter } from '@opentelemetry/exporter-zipkin' * * init({ * service: 'my-app', * spanExporter: new ZipkinExporter({ url: 'http://localhost:9411/api/v2/spans' }) * }) * ``` * * @example Observe in Datadog * ```typescript * import { DatadogSpanProcessor } from '@opentelemetry/exporter-datadog' * * init({ * service: 'my-app', * spanProcessor: new DatadogSpanProcessor({ ... }) * }) * ``` * * @example Console output (dev) * ```typescript * import { ConsoleSpanExporter, SimpleSpanProcessor } from '@opentelemetry/sdk-trace-base' * * init({ * service: 'my-app', * spanProcessor: new SimpleSpanProcessor(new ConsoleSpanExporter()) * }) * ``` */ function init(cfg) { if (locked) return; const envConfig = resolveConfigFromEnv(); const yamlConfig = require_yaml_config.loadYamlConfig() ?? {}; const mergedConfig = { ...envConfig, ...yamlConfig, ...cfg, resourceAttributes: { ...envConfig.resourceAttributes, ...yamlConfig.resourceAttributes, ...detectEnvironmentAttributes(), ...cfg.resourceAttributes }, headers: cfg.headers ?? yamlConfig.headers ?? envConfig.headers }; const resolvedRedactor = resolveAttributeRedactor(mergedConfig.attributeRedactor, mergedConfig.environment || process.env.NODE_ENV || "development"); if (resolvedRedactor === void 0) mergedConfig.attributeRedactor = void 0; else { const normalizedRedactor = require_attribute_redacting_processor.normalizeAttributeRedactorConfig(resolvedRedactor); if (!normalizedRedactor) throw new Error("Invalid attributeRedactor config"); mergedConfig.attributeRedactor = normalizedRedactor; } const devtoolsConfig = resolveDevtoolsConfig(mergedConfig.devtools); if (devtoolsConfig.enabled && mergedConfig.logs === void 0) mergedConfig.logs = true; const silent = mergedConfig.silent ?? false; const minLevel = mergedConfig.minLevel ?? "info"; logger = wrapLogger(mergedConfig.logger || silentLogger, silent, minLevel); if (initialized) logger.warn({}, "[autotel] init() called again - last config wins. This may cause unexpected behavior."); config = mergedConfig; validationConfig = mergedConfig.validation || null; eventsConfig = mergedConfig.events || null; let endpoint = mergedConfig.endpoint ?? devtoolsConfig.endpoint; const version = mergedConfig.version || detectVersion(); const environment = mergedConfig.environment || process.env.NODE_ENV || "development"; const metricsEnabled = resolveMetricsFlag(mergedConfig.metrics); const logsEnabled = resolveLogsFlag(mergedConfig.logs); if (devtoolsConfig.enabled && devtoolsConfig.embedded) { const devtoolsModule = _optionalRequire("autotel-devtools"); if (devtoolsModule?.createDevtools) { const devtoolsInstance = devtoolsModule.createDevtools({ port: devtoolsConfig.port, host: devtoolsConfig.host, verbose: devtoolsConfig.verbose }); _devtoolsClose = devtoolsInstance.close; endpoint = `http://${devtoolsConfig.host}:${devtoolsInstance.port}`; logger.info({}, `[autotel] autotel-devtools embedded server started at ${endpoint}`); } else logger.warn({}, "[autotel] devtools.embedded requested but autotel-devtools is not installed. Falling back to endpoint-only mode."); } const hostname = detectHostname(); let resource = (0, _opentelemetry_resources.resourceFromAttributes)({ [_opentelemetry_semantic_conventions.ATTR_SERVICE_NAME]: mergedConfig.service, [_opentelemetry_semantic_conventions.ATTR_SERVICE_VERSION]: version, "deployment.environment": environment, "deployment.environment.name": environment }); if (hostname) resource = resource.merge((0, _opentelemetry_resources.resourceFromAttributes)({ "host.name": hostname, "datadog.host.name": hostname })); if (mergedConfig.resource) resource = resource.merge(mergedConfig.resource); if (mergedConfig.resourceAttributes) resource = resource.merge((0, _opentelemetry_resources.resourceFromAttributes)(mergedConfig.resourceAttributes)); const otlpDestinations = resolveOtlpDestinations(mergedConfig, endpoint); const configuredSpanProcessors = mergedConfig.spanProcessors && mergedConfig.spanProcessors.length > 0 ? mergedConfig.spanProcessors : mergedConfig.spanProcessor ? [mergedConfig.spanProcessor] : void 0; const configuredSpanExporters = mergedConfig.spanExporters && mergedConfig.spanExporters.length > 0 ? mergedConfig.spanExporters : mergedConfig.spanExporter ? [mergedConfig.spanExporter] : void 0; const configuredMetricReaders = mergedConfig.metricReaders && mergedConfig.metricReaders.length > 0 ? mergedConfig.metricReaders : mergedConfig.metricReader ? [mergedConfig.metricReader] : void 0; const configuredLogRecordProcessors = mergedConfig.logRecordProcessors && mergedConfig.logRecordProcessors.length > 0 ? mergedConfig.logRecordProcessors : mergedConfig.logRecordProcessor ? [mergedConfig.logRecordProcessor] : void 0; let spanProcessors = []; if (configuredSpanProcessors && configuredSpanProcessors.length > 0) spanProcessors.push(...configuredSpanProcessors); else if (configuredSpanExporters && configuredSpanExporters.length > 0) for (const exporter of configuredSpanExporters) spanProcessors.push(new require_tail_sampling_processor.TailSamplingSpanProcessor(new _opentelemetry_sdk_trace_base.BatchSpanProcessor(exporter))); else for (const destination of otlpDestinations) { if (!destinationSupportsSignal(destination, "traces")) continue; const traceExporter = createTraceExporter(destination.protocol, { url: formatEndpointUrl(destination.endpoint, "traces", destination.protocol), headers: destination.headers }); spanProcessors.push(new require_tail_sampling_processor.TailSamplingSpanProcessor(new _opentelemetry_sdk_trace_base.BatchSpanProcessor(traceExporter))); } if (mergedConfig.baggage) { const prefix = typeof mergedConfig.baggage === "string" ? mergedConfig.baggage ? `${mergedConfig.baggage}.` : "" : "baggage."; spanProcessors.push(new BaggageSpanProcessor({ prefix })); } const debugMode = resolveDebugFlag(mergedConfig.debug); if (debugMode === "pretty") spanProcessors.push(new _opentelemetry_sdk_trace_base.SimpleSpanProcessor(new require_pretty_console_exporter.PrettyConsoleExporter())); else if (debugMode === true) spanProcessors.push(new _opentelemetry_sdk_trace_base.SimpleSpanProcessor(new _opentelemetry_sdk_trace_base.ConsoleSpanExporter())); if (mergedConfig.canonicalLogLines?.enabled) { const canonicalOptions = { logger: mergedConfig.canonicalLogLines.logger || mergedConfig.logger, rootSpansOnly: mergedConfig.canonicalLogLines.rootSpansOnly, minLevel: mergedConfig.canonicalLogLines.minLevel, messageFormat: mergedConfig.canonicalLogLines.messageFormat, includeResourceAttributes: mergedConfig.canonicalLogLines.includeResourceAttributes, shouldEmit: mergedConfig.canonicalLogLines.shouldEmit, keep: mergedConfig.canonicalLogLines.keep, drain: mergedConfig.canonicalLogLines.drain, onDrainError: mergedConfig.canonicalLogLines.onDrainError, pretty: mergedConfig.canonicalLogLines.pretty }; spanProcessors.push(new require_canonical_log_line_processor.CanonicalLogLineProcessor(canonicalOptions)); } if (mergedConfig.attributeRedactor && spanProcessors.length > 0) { const redactor = mergedConfig.attributeRedactor; spanProcessors = spanProcessors.map((processor) => new require_attribute_redacting_processor.AttributeRedactingProcessor(processor, { redactor })); } if (mergedConfig.attributeRedactor) _stringRedactor = createStringRedactor(mergedConfig.attributeRedactor); if (_stringRedactor && mergedConfig.subscribers) { for (const subscriber of mergedConfig.subscribers) if ("setStringRedactor" in subscriber && typeof subscriber.setStringRedactor === "function") subscriber.setStringRedactor(_stringRedactor); } if (mergedConfig.spanNameNormalizer && spanProcessors.length > 0) spanProcessors = spanProcessors.map((processor) => new require_span_name_normalizer.SpanNameNormalizingProcessor(processor, { normalizer: mergedConfig.spanNameNormalizer })); if (mergedConfig.spanFilter && spanProcessors.length > 0) spanProcessors = spanProcessors.map((processor) => new require_filtering_span_processor.FilteringSpanProcessor(processor, { filter: mergedConfig.spanFilter })); const metricReaders = []; if (configuredMetricReaders && configuredMetricReaders.length > 0) metricReaders.push(...configuredMetricReaders); else if (metricsEnabled) for (const destination of otlpDestinations) { if (!destinationSupportsSignal(destination, "metrics")) continue; const metricExporter = createMetricExporter(destination.protocol, { url: formatEndpointUrl(destination.endpoint, "metrics", destination.protocol), headers: destination.headers }); metricReaders.push(new _opentelemetry_sdk_metrics.PeriodicExportingMetricReader({ exporter: metricExporter })); } let logRecordProcessors; if (configuredLogRecordProcessors && configuredLogRecordProcessors.length > 0) logRecordProcessors = [...configuredLogRecordProcessors]; if (logsEnabled) { for (const destination of otlpDestinations) { if (!destinationSupportsSignal(destination, "logs")) continue; let processor = new _opentelemetry_sdk_logs.BatchLogRecordProcessor(createLogExporter(destination.protocol, { url: formatEndpointUrl(destination.endpoint, "logs", destination.protocol), headers: destination.headers })); if (_stringRedactor) processor = new RedactingLogRecordProcessor(processor, _stringRedactor); if (!logRecordProcessors) logRecordProcessors = []; logRecordProcessors.push(processor); } if (otlpDestinations.some((destination) => destinationSupportsSignal(destination, "logs"))) logger.info({}, "[autotel] OTLP log exporter configured"); } const posthogProcessors = buildPostHogLogProcessors(mergedConfig.posthog, _stringRedactor); if (posthogProcessors.length > 0) { if (!logRecordProcessors) logRecordProcessors = []; logRecordProcessors.push(...posthogProcessors); logger.info({}, "[autotel] PostHog OTLP logs configured"); } let finalInstrumentations = mergedConfig.instrumentations ? [...mergedConfig.instrumentations] : []; if (mergedConfig.autoInstrumentations !== void 0 && mergedConfig.autoInstrumentations !== false) { 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"); try { const manualInstrumentationNames = getInstrumentationNames(mergedConfig.instrumentations ?? []); if (manualInstrumentationNames.size > 0) { const manualNames = [...manualInstrumentationNames].join(", "); 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.`); } const autoInstrumentations = getAutoInstrumentations(mergedConfig.autoInstrumentations, manualInstrumentationNames); if (autoInstrumentations && autoInstrumentations.length > 0) finalInstrumentations = [...finalInstrumentations, ...autoInstrumentations]; } catch (error) { logger.warn({}, `[autotel] Failed to configure auto-instrumentations: ${error instanceof Error ? error.message : String(error)}`); } } const autotelSampler = mergedConfig.sampler ?? (mergedConfig.sampling ? require_sampling.resolveSamplingPreset(mergedConfig.sampling) : void 0); if (autotelSampler) mergedConfig.sampler = autotelSampler; const sampler = autotelSampler ? toOtelSampler(autotelSampler) : envConfig.otelSampler ?? toOtelSampler(require_sampling.samplingPresets.production()); const sdkOptions = { resource, sampler, instrumentations: finalInstrumentations }; if (spanProcessors.length > 0) sdkOptions.spanProcessors = spanProcessors; if (metricReaders.length > 0) sdkOptions.metricReaders = metricReaders; if (logRecordProcessors && logRecordProcessors.length > 0) sdkOptions.logRecordProcessors = logRecordProcessors; sdk = mergedConfig.sdkFactory ? mergedConfig.sdkFactory(sdkOptions) : new _opentelemetry_sdk_node.NodeSDK(sdkOptions); if (!sdk) throw new Error("[autotel] sdkFactory must return a NodeSDK instance"); sdk.start(); if (mergedConfig.openllmetry?.enabled) { const traceloop = _optionalRequire("@traceloop/node-server-sdk"); if (traceloop) { const initOptions = { ...mergedConfig.openllmetry.options }; try { initOptions.tracerProvider = sdk.getTracerProvider(); } catch {} if (configuredSpanExporters?.[0]) initOptions.exporter = configuredSpanExporters[0]; if (typeof traceloop.initialize === "function") { traceloop.initialize(initOptions); logger.info({}, "[autotel] OpenLLMetry initialized successfully"); } else logger.warn({}, "[autotel] OpenLLMetry initialize function not found. Check @traceloop/node-server-sdk version."); } else logger.warn({}, "[autotel] OpenLLMetry enabled but @traceloop/node-server-sdk is not installed. Install it as a peer dependency to use OpenLLMetry integration."); } initialized = true; } /** * Extract instrumentation class names from instrumentation instances * Used to detect duplicates between manual and auto instrumentations */ function getInstrumentationNames(instrumentations) { const names = /* @__PURE__ */ new Set(); if (!instrumentations) return names; for (const instrumentation of instrumentations) if (instrumentation && typeof instrumentation === "object") names.add(instrumentation.constructor.name); return names; } /** * Map common instrumentation class names to their package names * Used to disable auto-instrumentations when user provides manual configs */ const INSTRUMENTATION_CLASS_TO_PACKAGE = { HttpInstrumentation: "@opentelemetry/instrumentation-http", HttpsInstrumentation: "@opentelemetry/instrumentation-http", ExpressInstrumentation: "@opentelemetry/instrumentation-express", FastifyInstrumentation: "@opentelemetry/instrumentation-fastify", MongoDBInstrumentation: "@opentelemetry/instrumentation-mongodb", MongooseInstrumentation: "@opentelemetry/instrumentation-mongoose", PrismaInstrumentation: "@opentelemetry/instrumentation-prisma", PinoInstrumentation: "@opentelemetry/instrumentation-pino", WinstonInstrumentation: "@opentelemetry/instrumentation-winston", RedisInstrumentation: "@opentelemetry/instrumentation-redis", GraphQLInstrumentation: "@opentelemetry/instrumentation-graphql", GrpcInstrumentation: "@opentelemetry/instrumentation-grpc", IORedisInstrumentation: "@opentelemetry/instrumentation-ioredis", KnexInstrumentation: "@opentelemetry/instrumentation-knex", NestJsInstrumentation: "@opentelemetry/instrumentation-nestjs-core", PgInstrumentation: "@opentelemetry/instrumentation-pg", MySQLInstrumentation: "@opentelemetry/instrumentation-mysql", MySQL2Instrumentation: "@opentelemetry/instrumentation-mysql2" }; /** * Detect if we're running in ESM mode */ function isESMMode() { try { const fs = require_node_require.requireModule("node:fs"); try { return JSON.parse(fs.readFileSync(`${process.cwd()}/package.json`, "utf8")).type === "module"; } catch { return false; } } catch { return false; } } /** * Lazy-load auto-instrumentations (optional peer dependency) * Only loads when integrations config is truthy, avoiding ~40+ package imports at startup. */ function loadNodeAutoInstrumentations() { try { return require_node_require.requireModule("@opentelemetry/auto-instrumentations-node").getNodeAutoInstrumentations; } catch { const isESM = isESMMode(); const baseMessage = "@opentelemetry/auto-instrumentations-node not found."; if (isESM) throw new Error(`${baseMessage}\n\nESM Setup Required: 1. Install as a direct dependency: pnpm add @opentelemetry/auto-instrumentations-node 2. Create instrumentation.mjs with: import 'autotel/register'; // MUST be first! import { init } from 'autotel'; import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node'; init({ service: "my-app", instrumentations: getNodeAutoInstrumentations() }); 3. Run with: tsx --import ./instrumentation.mjs src/index.ts See: https://github.com/jagreehal/autotel#esm-setup`); throw new Error(`${baseMessage} Install it: pnpm add @opentelemetry/auto-instrumentations-node`); } } /** * Injectable loader for testing. Set to override the default loader. * @internal */ let _autoInstrumentationsLoader = null; /** * Get auto-instrumentations based on simple integration names * Excludes instrumentations that are manually provided to avoid conflicts */ function getAutoInstrumentations(integrations, manualInstrumentationNames = /* @__PURE__ */ new Set()) { if (integrations === false) return []; const getNodeAutoInstrumentations = _autoInstrumentationsLoader ? _autoInstrumentationsLoader() : loadNodeAutoInstrumentations(); const exclusionConfig = {}; for (const className of manualInstrumentationNames) { const packageName = INSTRUMENTATION_CLASS_TO_PACKAGE[className]; if (packageName) exclusionConfig[packageName] = { enabled: false }; } if (integrations === true) { if (Object.keys(exclusionConfig).length > 0) return getNodeAutoInstrumentations(exclusionConfig); return getNodeAutoInstrumentations(); } if (Array.isArray(integrations)) { const config = { ...exclusionConfig }; for (const name of integrations) { const packageName = `@opentelemetry/instrumentation-${name}`; if (!exclusionConfig[packageName]) config[packageName] = { enabled: true }; } return getNodeAutoInstrumentations(config); } const config = { ...exclusionConfig, ...integrations }; for (const packageName of Object.keys(exclusionConfig)) if (Object.keys(integrations).find((key) => packageName.includes(key))) config[packageName] = { enabled: false }; return getNodeAutoInstrumentations(config); } /** * Check if autotel has been initialized */ function isInitialized() { return initialized; } /** * Get current config (internal use) */ function getConfig() { return config; } /** * Get current logger (internal use) */ function getLogger() { return logger; } /** * Get validation config (internal use) */ function getValidationConfig() { return validationConfig; } /** * Get events config (internal use) */ function getEventsConfig() { return eventsConfig; } /** * Warn once if not initialized (same behavior in all environments) */ function warnIfNotInitialized(context) { if (!initialized && !warnedOnce) { logger.warn({}, `[autotel] ${context} used before init() called. Call init({ service: "..." }) first. See: https://docs.autotel.dev/quickstart`); warnedOnce = true; } } /** * Auto-detect version from package.json */ function detectVersion() { try { const fs = require_node_require.requireModule("node:fs"); return JSON.parse(fs.readFileSync(`${process.cwd()}/package.json`, "utf8")).version || "1.0.0"; } catch { return "1.0.0"; } } /** * Detect hostname for resource attributes. * Supports Datadog conventions (DD_HOSTNAME) and falls back to system hostname. * * Priority order: * 1. DD_HOSTNAME environment variable (Datadog convention) * 2. HOSTNAME environment variable (common Unix convention) * 3. os.hostname() (system hostname) * * @returns hostname string or undefined if detection fails */ function detectHostname() { if (process.env.DD_HOSTNAME) return process.env.DD_HOSTNAME; if (process.env.HOSTNAME) return process.env.HOSTNAME; try { return require_node_require.requireModule("node:os").hostname(); } catch { return; } } /** * @internal Close embedded devtools if running. */ async function _closeEmbeddedDevtools() { if (_devtoolsClose) { await _devtoolsClose(); _devtoolsClose = null; } } /** * Get SDK instance (for shutdown) */ function getSdk() { return sdk; } //#endregion Object.defineProperty(exports, 'BaggageSpanProcessor', { enumerable: true, get: function () { return BaggageSpanProcessor; } }); Object.defineProperty(exports, '_closeEmbeddedDevtools', { enumerable: true, get: function () { return _closeEmbeddedDevtools; } }); Object.defineProperty(exports, 'createStringRedactor', { enumerable: true, get: function () { return createStringRedactor; } }); Object.defineProperty(exports, 'getConfig', { enumerable: true, get: function () { return getConfig; } }); Object.defineProperty(exports, 'getEventsConfig', { enumerable: true, get: function () { return getEventsConfig; } }); Object.defineProperty(exports, 'getLogger', { enumerable: true, get: function () { return getLogger; } }); Object.defineProperty(exports, 'getSdk', { enumerable: true, get: function () { return getSdk; } }); Object.defineProperty(exports, 'getValidationConfig', { enumerable: true, get: function () { return getValidationConfig; } }); Object.defineProperty(exports, 'init', { enumerable: true, get: function () { return init; } }); Object.defineProperty(exports, 'isInitialized', { enumerable: true, get: function () { return isInitialized; } }); Object.defineProperty(exports, 'isLoggerLocked', { enumerable: true, get: function () { return isLoggerLocked; } }); Object.defineProperty(exports, 'lockLogger', { enumerable: true, get: function () { return lockLogger; } }); Object.defineProperty(exports, 'warnIfNotInitialized', { enumerable: true, get: function () { return warnIfNotInitialized; } }); //# sourceMappingURL=init-BXiuPK6j.cjs.map