autotel
Version:
Write Once, Observe Anywhere
1,111 lines (1,103 loc) • 43.7 kB
JavaScript
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