UNPKG

autotel

Version:
4,310 lines 152 kB
import { _ as readProperty, d as asStringRecord, f as describeValue, h as isFunction, i as asNumber, l as asString, r as asFunction, s as asRecord, t as asBoolean } from "./values-xBtdXtjA.js";
import { getForceFlushableProvider } from "./tracer-provider.js";
import { getConfig as getConfig$1 } from "./config.js";
import { a as flattenToAttributes, o as toAttributeValue, r as recordStructuredError } from "./structured-error-Y9y-1Pae.js";
import { n as safeRequire, t as requireModule } from "./node-require-CkK_xzL-.js";
import { FilteringSpanProcessor } from "./filtering-span-processor.js";
import { PolicyLogRecordProcessor, policySpanFilter, setPolicies, watchPolicyFile } from "./policy.js";
import { SpanNameNormalizingProcessor } from "./span-name-normalizer.js";
import { AttributeRedactingProcessor, REDACTOR_PRESETS, normalizeAttributeRedactorConfig } from "./attribute-redacting-processor.js";
import { t as PrettyConsoleExporter } from "./pretty-console-exporter-DqKl_q9z.js";
import { t as CanonicalLogLineProcessor } from "./canonical-log-line-processor-BQ6NeZAk.js";
import { resetMetrics } from "./metric.js";
import { NodeSDK } from "@opentelemetry/sdk-node";
import { AlwaysOffSampler, AlwaysOnSampler, BatchSpanProcessor, ConsoleSpanExporter, ParentBasedSampler, SamplingDecision, SimpleSpanProcessor, TraceIdRatioBasedSampler } from "@opentelemetry/sdk-trace-base";
import { resourceFromAttributes } from "@opentelemetry/resources";
import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from "@opentelemetry/semantic-conventions";
import { TraceFlags, context, propagation, trace } from "@opentelemetry/api";
import * as nodeAsyncHooks from "node:async_hooks";
import { PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics";
import { BatchLogRecordProcessor } from "@opentelemetry/sdk-logs";
import * as nodeFs from "node:fs";
import path from "node:path";
import { constants } from "node:os";
import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-http";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-http";

//#region src/rate-limiter.ts
/**
* Token bucket rate limiter
*
* Allows bursts up to burstCapacity, then smooths to maxEventsPerSecond.
* Thread-safe for async operations.
*/
var TokenBucketRateLimiter = class {
	tokens;
	maxTokens;
	refillRate;
	lastRefill;
	constructor(config) {
		this.maxTokens = config.burstCapacity || config.maxEventsPerSecond * 2;
		this.tokens = this.maxTokens;
		this.refillRate = config.maxEventsPerSecond / 1e3;
		this.lastRefill = Date.now();
	}
	/**
	* Try to consume a token (allow an event)
	* Returns true if allowed, false if rate limit exceeded
	*/
	tryConsume(count = 1) {
		this.refill();
		if (this.tokens >= count) {
			this.tokens -= count;
			return true;
		}
		return false;
	}
	/**
	* Wait until a token is available (async rate limiting)
	* Returns a promise that resolves when the event can be processed
	*/
	async waitForToken(count = 1) {
		this.refill();
		if (this.tokens >= count) {
			this.tokens -= count;
			return;
		}
		const tokensNeeded = count - this.tokens;
		const waitMs = Math.ceil(tokensNeeded / this.refillRate);
		await new Promise((resolve) => setTimeout(resolve, waitMs));
		return this.waitForToken(count);
	}
	/**
	* Refill tokens based on elapsed time
	*/
	refill() {
		const now = Date.now();
		const tokensToAdd = (now - this.lastRefill) * this.refillRate;
		this.tokens = Math.min(this.maxTokens, this.tokens + tokensToAdd);
		this.lastRefill = now;
	}
	/**
	* Get current available tokens (for testing/debugging)
	*/
	getAvailableTokens() {
		this.refill();
		return Math.floor(this.tokens);
	}
	/**
	* Reset the rate limiter (for testing)
	*/
	reset() {
		this.tokens = this.maxTokens;
		this.lastRefill = Date.now();
	}
};

//#endregion
//#region src/correlation-id.ts
/**
* Correlation ID utilities for event-driven observability
*
* Provides a stable join key across events, logs, and spans even when traces fragment.
* Format: 16 hex chars (64 bits), crypto-random, URL-safe.
*
* Lifecycle:
* 1. Generated at boundary root (HTTP server span, message process span, cron job span)
* 2. Reused within context (nested work shares it via AsyncLocalStorage)
* 3. Propagated via baggage (optional, default OFF to avoid header bloat)
*
* @example Basic usage
* ```typescript
* import { generateCorrelationId, getCorrelationId } from 'autotel/correlation-id';
*
* // Generate a new correlation ID
* const id = generateCorrelationId();
* // Returns: 'a1b2c3d4e5f67890'
*
* // Get current correlation ID from context
* const currentId = getCorrelationId();
* ```
*/
/**
* AsyncLocalStorage for storing correlation ID
* This allows correlation IDs to persist across async boundaries
*/
const correlationStorage = new nodeAsyncHooks.AsyncLocalStorage();
/**
* Baggage key for correlation ID propagation
*/
const CORRELATION_ID_BAGGAGE_KEY = "autotel.correlation_id";
/**
* Generate a new correlation ID
*
* Format: 16 hex chars (64 bits), crypto-random, URL-safe
*
* @returns A new correlation ID
*
* @example
* ```typescript
* const id = generateCorrelationId();
* // Returns: 'a1b2c3d4e5f67890'
* ```
*/
function generateCorrelationId() {
	const bytes = /* @__PURE__ */ new Uint8Array(8);
	crypto.getRandomValues(bytes);
	return [...bytes].map((b) => b.toString(16).padStart(2, "0")).join("");
}
/**
* Get the current correlation ID from context
*
* Resolution order:
* 1. AsyncLocalStorage (from explicit setCorrelationId or runWithCorrelationId)
* 2. Baggage (if propagated from upstream)
* 3. Active span's trace ID (first 16 chars as fallback)
* 4. undefined (if not in any context)
*
* @returns Current correlation ID or undefined
*
* @example
* ```typescript
* const id = getCorrelationId();
* if (id) {
*   console.log('Correlation ID:', id);
* }
* ```
*/
function getCorrelationId() {
	const storedId = correlationStorage.getStore()?.value;
	if (storedId) return storedId;
	const activeContext = context.active();
	const baggageEntry = propagation.getBaggage(activeContext)?.getEntry(CORRELATION_ID_BAGGAGE_KEY);
	if (baggageEntry?.value) return baggageEntry.value;
	const span = trace.getActiveSpan();
	if (span) return span.spanContext().traceId.slice(0, 16);
}
/**
* Get or create a correlation ID
*
* If a correlation ID exists in the current context, returns it.
* Otherwise, generates a new one.
*
* @returns Existing or new correlation ID
*
* @example
* ```typescript
* const id = getOrCreateCorrelationId();
* // Always returns a valid correlation ID
* ```
*/
function getOrCreateCorrelationId() {
	return getCorrelationId() ?? generateCorrelationId();
}
/**
* Run a function with a specific correlation ID in context
*
* The correlation ID will be available via getCorrelationId() throughout
* the execution of the function and any async operations it spawns.
*
* @param correlationId - Correlation ID to use
* @param fn - Function to execute
* @returns The return value of the function
*
* @example
* ```typescript
* await runWithCorrelationId('abc123', async () => {
*   // getCorrelationId() returns 'abc123' here
*   await processRequest();
* });
* ```
*/
function runWithCorrelationId(correlationId, fn) {
	return correlationStorage.run({ value: correlationId }, fn);
}
/**
* Set correlation ID in the current context (mutates context)
*
* Note: This updates the AsyncLocalStorage context. For proper scoping
* across async boundaries, prefer runWithCorrelationId() instead.
*
* @param correlationId - Correlation ID to set
*
* @example
* ```typescript
* setCorrelationId('abc123');
* // Now getCorrelationId() returns 'abc123'
* ```
*/
function setCorrelationId(correlationId) {
	enterOrRun(correlationStorage, correlationId);
}
/**
* Set correlation ID in baggage for propagation
*
* This adds the correlation ID to the W3C baggage header, allowing it
* to be propagated to downstream services.
*
* Note: Only use this when you explicitly want cross-service propagation.
* Default is OFF to avoid header bloat.
*
* @param correlationId - Correlation ID to propagate
* @returns New context with baggage set
*
* @example
* ```typescript
* const newContext = setCorrelationIdInBaggage('abc123');
* context.with(newContext, () => {
*   // Baggage will be propagated in outgoing requests
* });
* ```
*/
function setCorrelationIdInBaggage(correlationId) {
	const activeContext = context.active();
	let baggage = propagation.getBaggage(activeContext) ?? propagation.createBaggage();
	baggage = baggage.setEntry(CORRELATION_ID_BAGGAGE_KEY, { value: correlationId });
	return propagation.setBaggage(activeContext, baggage);
}
/**
* Get the correlation storage instance (for internal use in init/shutdown)
*/
function getCorrelationStorage() {
	return correlationStorage;
}

//#endregion
//#region src/event-queue.ts
const DEFAULT_CONFIG$2 = {
	maxSize: 5e4,
	batchSize: 100,
	flushInterval: 1e4,
	maxRetries: 3,
	rateLimit: {
		maxEventsPerSecond: 100,
		burstCapacity: 200
	}
};
/**
* Get subscriber name for metrics (stable, low-cardinality)
*
* Priority:
* 1. Explicit config: subscriber.name
* 2. Class static property (if available)
* 3. Fallback: lowercase class name without "Subscriber" suffix
*/
function getSubscriberName(subscriber) {
	if (subscriber.name) return subscriber.name.toLowerCase();
	return (subscriber.constructor?.name || "unknown").replace(/Subscriber$/i, "").toLowerCase();
}
/**
* Subscribers whose `shutdown()` has run. Terminal for the client they wrap, so
* a queue rebuilt from the same config must not reuse them.
*/
const shutDownSubscribers = /* @__PURE__ */ new WeakSet();
/**
* Events queue with batching and backpressure
*
* Features:
* - Batches events for efficient sending
* - Bounded queue with drop-oldest policy (prod) or blocking (dev)
* - Exponential backoff retry
* - Rate limiting to prevent overwhelming subscribers
* - Graceful flush on shutdown
*/
var EventQueue = class {
	queue = [];
	flushTimer = null;
	config;
	subscribers;
	rateLimiter;
	flushPromise = null;
	isShuttingDown = false;
	metrics = null;
	observableCleanups = [];
	subscriberHealthy = /* @__PURE__ */ new Map();
	constructor(subscribers, config) {
		const live = subscribers.filter((s) => !shutDownSubscribers.has(s));
		if (live.length < subscribers.length) getLogger().warn({ subscribers: subscribers.filter((s) => shutDownSubscribers.has(s)).map((s) => getSubscriberName(s)) }, "[autotel] Subscriber already shut down, events will not be delivered; pass new subscriber instances to init()");
		this.subscribers = live;
		this.config = {
			...DEFAULT_CONFIG$2,
			...config
		};
		this.rateLimiter = this.config.rateLimit ? new TokenBucketRateLimiter(this.config.rateLimit) : null;
		for (const subscriber of this.subscribers) {
			const name = getSubscriberName(subscriber);
			this.subscriberHealthy.set(name, true);
		}
		this.initMetrics();
	}
	/**
	* Initialize OTel metrics for queue observability
	*/
	initMetrics() {
		const meter = getConfig$1().meter;
		const queueSize = meter.createObservableGauge("autotel.event_delivery.queue.size", {
			description: "Current number of events in the delivery queue",
			unit: "count"
		});
		const queueSizeCallback = (observableResult) => {
			observableResult.observe(this.queue.length);
		};
		queueSize.addCallback(queueSizeCallback);
		this.observableCleanups.push(() => queueSize.removeCallback(queueSizeCallback));
		const oldestAge = meter.createObservableGauge("autotel.event_delivery.queue.oldest_age_ms", {
			description: "Age of the oldest event in the queue in milliseconds",
			unit: "ms"
		});
		const oldestAgeCallback = (observableResult) => {
			if (this.queue.length > 0) {
				const oldest = this.queue[0];
				const ageMs = Date.now() - oldest.timestamp;
				observableResult.observe(ageMs);
			} else observableResult.observe(0);
		};
		oldestAge.addCallback(oldestAgeCallback);
		this.observableCleanups.push(() => oldestAge.removeCallback(oldestAgeCallback));
		const delivered = meter.createCounter("autotel.event_delivery.queue.delivered", {
			description: "Number of events successfully delivered to subscribers",
			unit: "count"
		});
		const failed = meter.createCounter("autotel.event_delivery.queue.failed", {
			description: "Number of events that failed delivery after all retry attempts",
			unit: "count"
		});
		const dropped = meter.createCounter("autotel.event_delivery.queue.dropped", {
			description: "Number of events dropped from the queue",
			unit: "count"
		});
		const latency = meter.createHistogram("autotel.event_delivery.queue.latency_ms", {
			description: "Event delivery latency from enqueue to successful send",
			unit: "ms"
		});
		const subscriberHealth = meter.createObservableGauge("autotel.event_delivery.subscriber.health", {
			description: "Subscriber health status (1=healthy, 0=unhealthy)",
			unit: "1"
		});
		const subscriberHealthCallback = (observableResult) => {
			for (const [subscriberName, isHealthy] of this.subscriberHealthy) observableResult.observe(isHealthy ? 1 : 0, { subscriber: subscriberName });
		};
		subscriberHealth.addCallback(subscriberHealthCallback);
		this.observableCleanups.push(() => subscriberHealth.removeCallback(subscriberHealthCallback));
		this.metrics = {
			queueSize,
			oldestAge,
			delivered,
			failed,
			dropped,
			latency,
			subscriberHealth
		};
	}
	/**
	* Record a dropped event with reason and emit debug breadcrumb
	*/
	recordDropped(reason, event, subscriberName) {
		const attrs = { reason };
		if (subscriberName) attrs.subscriber = subscriberName;
		this.metrics?.dropped.add(1, attrs);
		const logLevel = reason === "payload_invalid" ? "error" : "warn";
		const logger = getLogger();
		if (logLevel === "error") logger.error({
			eventName: event?.name,
			subscriber: subscriberName,
			reason,
			correlationId: event?._correlationId,
			traceId: event?._traceId
		}, `[autotel] Event dropped: ${reason}`);
		else logger.warn({
			eventName: event?.name,
			subscriber: subscriberName,
			reason,
			correlationId: event?._correlationId,
			traceId: event?._traceId
		}, `[autotel] Event dropped: ${reason}`);
	}
	/**
	* Record permanent delivery failure (after all retries exhausted)
	* Increments failed counter and logs error
	*/
	recordFailed(event, subscriberName, error) {
		this.metrics?.failed.add(1, { subscriber: subscriberName });
		this.subscriberHealthy.set(subscriberName, false);
		getLogger().error({
			eventName: event.name,
			subscriber: subscriberName,
			correlationId: event._correlationId,
			traceId: event._traceId,
			err: error
		}, `[autotel] Event delivery failed after all retries`);
	}
	/**
	* Mark subscriber as unhealthy on transient failure (without incrementing failed counter)
	* Used during retry attempts - only recordFailed should increment the counter
	*/
	markSubscriberUnhealthy(subscriberName) {
		this.subscriberHealthy.set(subscriberName, false);
	}
	/**
	* Record successful delivery
	*/
	recordDelivered(event, subscriberName, startTime) {
		const latencyMs = Date.now() - startTime;
		this.metrics?.delivered.add(1, { subscriber: subscriberName });
		this.metrics?.latency.record(latencyMs, { subscriber: subscriberName });
		this.subscriberHealthy.set(subscriberName, true);
	}
	/**
	* Enqueue an event for sending
	*
	* Backpressure policy:
	* - Drops oldest event and logs warning if queue is full (same behavior in all environments)
	*/
	enqueue(event) {
		if (this.isShuttingDown) {
			this.recordDropped("shutdown", event);
			return;
		}
		if (this.queue.length >= this.config.maxSize) {
			const droppedEvent = this.queue.shift();
			this.recordDropped("rate_limit", droppedEvent);
			getLogger().warn({ droppedEvent: droppedEvent?.name }, `[autotel] Events queue full (${this.config.maxSize} events). Dropping oldest event. Events are being produced faster than they can be sent. Check your subscribers or reduce tracking frequency.`);
		}
		const enrichedEvent = {
			...event,
			_correlationId: event._correlationId || getOrCreateCorrelationId()
		};
		this.queue.push(enrichedEvent);
		this.scheduleBatchFlush();
	}
	/**
	* Schedule a batch flush if not already scheduled
	*/
	scheduleBatchFlush() {
		if (this.flushTimer || this.flushPromise) return;
		this.flushTimer = setTimeout(() => {
			this.flushTimer = null;
			this.flushBatch();
		}, this.config.flushInterval);
	}
	/**
	* Flush a batch of events
	* Uses promise-based concurrency control to prevent race conditions
	*/
	async flushBatch() {
		if (this.queue.length === 0) return;
		if (this.flushPromise) {
			await this.flushPromise;
			return;
		}
		this.flushPromise = this.doFlushBatch();
		try {
			await this.flushPromise;
		} finally {
			this.flushPromise = null;
			if (this.queue.length > 0) this.scheduleBatchFlush();
		}
	}
	/**
	* Internal flush implementation
	*/
	async doFlushBatch() {
		const batch = this.queue.splice(0, this.config.batchSize);
		await this.sendWithRetry(batch, this.config.maxRetries);
	}
	/**
	* Send events with exponential backoff retry
	* Tracks per-event, per-subscriber failures so failed counter reflects actual failed deliveries.
	* On retry, only failed (event, subscriber) pairs are re-sent to avoid double-counting delivered.
	*/
	async sendWithRetry(events, retriesLeft, subscribersByEventIndex) {
		const failedDeliveries = await this.sendToSubscribers(events, subscribersByEventIndex);
		if (failedDeliveries.length > 0) if (retriesLeft > 0) {
			const failedEventIndicesOrdered = [...new Set(failedDeliveries.map((f) => f.eventIndex))].toSorted((a, b) => a - b);
			const eventsToRetry = failedEventIndicesOrdered.map((i) => events[i]);
			const failedSubscribersByRetryIndex = /* @__PURE__ */ new Map();
			for (const [j, origIndex] of failedEventIndicesOrdered.entries()) {
				const set = /* @__PURE__ */ new Set();
				for (const { eventIndex, subscriberName } of failedDeliveries) if (eventIndex === origIndex) set.add(subscriberName);
				failedSubscribersByRetryIndex.set(j, set);
			}
			const delay = Math.pow(2, this.config.maxRetries - retriesLeft) * 1e3;
			await new Promise((resolve) => setTimeout(resolve, delay));
			return this.sendWithRetry(eventsToRetry, retriesLeft - 1, failedSubscribersByRetryIndex);
		} else {
			for (const { eventIndex, subscriberName, error } of failedDeliveries) {
				const event = events[eventIndex];
				if (event) this.recordFailed(event, subscriberName, error);
			}
			const failedSubscriberNames = [...new Set(failedDeliveries.map((f) => f.subscriberName))];
			getLogger().error({
				failedSubscribers: failedSubscriberNames,
				retriesAttempted: this.config.maxRetries
			}, "[autotel] Failed to send events after retries");
		}
	}
	/**
	* Send events to configured subscribers with rate limiting and metrics.
	* When subscribersByEventIndex is provided (retry path), only those subscribers are tried per event.
	* Returns per-event, per-subscriber failures (empty if all succeeded).
	*/
	async sendToSubscribers(events, subscribersByEventIndex) {
		const failedDeliveries = [];
		const sendOne = async (event, eventIndex) => {
			const subscriberNames = subscribersByEventIndex?.get(eventIndex);
			const failures = await this.sendEventToSubscribers(event, subscriberNames ?? void 0);
			for (const failure of failures) failedDeliveries.push({
				eventIndex,
				subscriberName: failure.subscriberName,
				error: failure.error
			});
		};
		if (!this.rateLimiter) {
			for (const [i, event] of events.entries()) if (event) await sendOne(event, i);
			return failedDeliveries;
		}
		for (const [i, event] of events.entries()) {
			await this.rateLimiter.waitForToken();
			if (event) await sendOne(event, i);
		}
		return failedDeliveries;
	}
	/**
	* Send a single event to subscribers.
	* - When subscriberNames is undefined (initial attempt): send to all subscribers.
	* - When subscriberNames is provided (retry): send only to those subscribers (never re-send to healthy ones).
	* Returns list of subscribers that failed (empty if all succeeded).
	*/
	async sendEventToSubscribers(event, subscriberNames) {
		const startTime = event.timestamp;
		const failures = [];
		const subscribersToTry = subscriberNames === void 0 ? this.subscribers : this.subscribers.filter((s) => subscriberNames.has(getSubscriberName(s)));
		const results = await Promise.allSettled(subscribersToTry.map(async (subscriber) => {
			const subscriberName = getSubscriberName(subscriber);
			try {
				await subscriber.trackEvent(event.name, event.attributes, {
					autotel: event.autotel,
					schema: event.schema
				});
				this.recordDelivered(event, subscriberName, startTime);
				return {
					subscriberName,
					success: true
				};
			} catch (error) {
				this.markSubscriberUnhealthy(subscriberName);
				return {
					subscriberName,
					success: false,
					error: error instanceof Error ? error : void 0
				};
			}
		}));
		for (const result of results) if (result.status === "fulfilled" && !result.value.success) failures.push({
			subscriberName: result.value.subscriberName,
			error: result.value.error
		});
		return failures;
	}
	/**
	* Flush all remaining events. Queue remains usable after flush (e.g. for
	* auto-flush at root span end). Use shutdown() when tearing down the queue.
	*/
	async flush() {
		if (this.flushTimer) {
			clearTimeout(this.flushTimer);
			this.flushTimer = null;
		}
		if (this.flushPromise) await this.flushPromise;
		while (this.queue.length > 0) await this.doFlushBatch();
	}
	/**
	* Flush remaining events and permanently disable the queue (reject new events).
	* Use for process/SDK shutdown; use flush() for periodic or span-end drain.
	*/
	async shutdown() {
		this.isShuttingDown = true;
		await this.flush();
		const drains = await Promise.allSettled(this.subscribers.map(async (subscriber) => {
			if (!subscriber.shutdown) return;
			shutDownSubscribers.add(subscriber);
			return subscriber.shutdown();
		}));
		for (const [index, drain] of drains.entries()) {
			if (drain.status !== "rejected") continue;
			const subscriberName = getSubscriberName(this.subscribers[index]);
			this.subscriberHealthy.set(subscriberName, false);
			getLogger().error({
				subscriber: subscriberName,
				err: drain.reason
			}, "[autotel] Subscriber shutdown failed, buffered events may be lost");
		}
	}
	/**
	* Cleanup observable metric callbacks to prevent memory leaks
	* Call this when destroying the EventQueue instance
	*/
	cleanup() {
		for (const cleanupFn of this.observableCleanups) try {
			cleanupFn();
		} catch {}
		this.observableCleanups = [];
	}
	/**
	* Get queue size (for testing/debugging)
	*/
	size() {
		return this.queue.length;
	}
	/**
	* Get subscriber health status (for testing/debugging)
	*/
	getSubscriberHealth() {
		return new Map(this.subscriberHealthy);
	}
	/**
	* Check if a specific subscriber is healthy
	*/
	isSubscriberHealthy(subscriberName) {
		return this.subscriberHealthy.get(subscriberName.toLowerCase()) ?? true;
	}
	/**
	* Manually mark a subscriber as healthy or unhealthy
	* (used for circuit breaker integration)
	*/
	setSubscriberHealth(subscriberName, healthy) {
		this.subscriberHealthy.set(subscriberName.toLowerCase(), healthy);
	}
};

//#endregion
//#region src/validation.ts
const DEFAULT_CONFIG$1 = {
	maxEventNameLength: 100,
	maxAttributeKeyLength: 100,
	maxAttributeValueLength: 1e3,
	maxAttributeCount: 50,
	maxNestingDepth: 3,
	sensitivePatterns: [
		/password/i,
		/secret/i,
		/token/i,
		/api[_-]?key/i,
		/access[_-]?key/i,
		/private[_-]?key/i,
		/auth/i,
		/credential/i,
		/ssn/i,
		/credit[_-]?card/i
	]
};
var ValidationError = class extends Error {
	constructor(message) {
		super(message);
		this.name = "ValidationError";
	}
};
/**
* Validate and sanitize event name
* Throws ValidationError if invalid
*/
function validateEventName(eventName, config = DEFAULT_CONFIG$1) {
	if (asString(eventName) === void 0) throw new ValidationError(`Event name must be a string, got ${describeValue(eventName)}`);
	const trimmed = eventName.trim();
	if (trimmed.length === 0) throw new ValidationError("Event name cannot be empty");
	if (trimmed.length > config.maxEventNameLength) throw new ValidationError(`Event name too long (${trimmed.length} chars). Max: ${config.maxEventNameLength}`);
	if (!/^[a-zA-Z0-9._-]+$/.test(trimmed)) throw new ValidationError(`Event name contains invalid characters: "${trimmed}". Use only letters, numbers, dots, underscores, and hyphens.`);
	return trimmed;
}
/**
* Validate and sanitize attributes
* Returns sanitized attributes (sensitive data redacted)
*/
function validateAttributes(attributes, config = DEFAULT_CONFIG$1) {
	if (attributes === void 0 || attributes === null) return;
	if (!asRecord(attributes)) throw new ValidationError("Attributes must be an object");
	const keys = Object.keys(attributes);
	if (keys.length > config.maxAttributeCount) throw new ValidationError(`Too many attributes (${keys.length}). Max: ${config.maxAttributeCount}`);
	const sanitized = {};
	for (const key of keys) {
		if (key.length > config.maxAttributeKeyLength) throw new ValidationError(`Attribute key too long: "${key.slice(0, 20)}..." (${key.length} chars). Max: ${config.maxAttributeKeyLength}`);
		const value = attributes[key];
		if (isSensitiveString(key, value, config)) {
			sanitized[key] = "[REDACTED]";
			continue;
		}
		sanitized[key] = sanitizeValue(value, config, 1);
	}
	return sanitized;
}
/**
* Whether this key/value pair is a credential to redact. Strings only:
* numeric and boolean values are not credentials, and replacing them with the
* literal '[REDACTED]' leaks no signal while breaking every consumer that
* treats them as a number.
*/
function isSensitiveString(key, value, config) {
	return asString(value) !== void 0 && config.sensitivePatterns.some((pattern) => pattern.test(key));
}
/**
* Sanitize attribute value (recursive)
*/
function sanitizeValue(value, config, depth) {
	if (depth > config.maxNestingDepth) return "[MAX_DEPTH_EXCEEDED]";
	if (value === null || value === void 0) return value;
	const text = asString(value);
	if (text !== void 0) return text.length > config.maxAttributeValueLength ? text.slice(0, config.maxAttributeValueLength) + "..." : text;
	const scalar = asNumber(value) ?? asBoolean(value);
	if (scalar !== void 0) return scalar;
	if (Array.isArray(value)) return value.map((item) => sanitizeValue(item, config, depth + 1));
	const record = asRecord(value);
	if (record) try {
		JSON.stringify(record);
		const sanitized = {};
		for (const [key, nested] of Object.entries(record)) sanitized[key] = isSensitiveString(key, nested, config) ? "[REDACTED]" : sanitizeValue(nested, config, depth + 1);
		return sanitized;
	} catch {
		return "[CIRCULAR]";
	}
	return `[${describeValue(value)}]`;
}
/**
* Validate and sanitize an events event
* Returns { eventName, attributes } with sanitized values
*/
function validateEvent(eventName, attributes, config) {
	const fullConfig = {
		...DEFAULT_CONFIG$1,
		...config
	};
	return {
		eventName: validateEventName(eventName, fullConfig),
		attributes: validateAttributes(attributes, fullConfig)
	};
}

//#endregion
//#region src/track.ts
/**
* Global track() function for business events
*
* Simple, no instantiation needed, auto-attaches trace context
*/
let eventsQueue = null;
let warnedNoSubscribers = false;
/**
* Build autotel event context for trace correlation
*
* Works in multiple contexts:
* 1. Inside a span → use current span's trace_id + span_id
* 2. Outside span → use correlation_id only
* 3. With trace URL config → include clickable trace URL
*/
function buildAutotelContext(span) {
	const eventsConfig = getEventsConfig();
	const config = getConfig();
	const correlationId = getOrCreateCorrelationId();
	if (!eventsConfig?.includeTraceContext) return { correlation_id: correlationId };
	const autotelContext = { correlation_id: correlationId };
	const spanContext = span?.spanContext();
	if (spanContext) {
		autotelContext.trace_id = spanContext.traceId;
		autotelContext.span_id = spanContext.spanId;
		autotelContext.trace_flags = spanContext.traceFlags.toString(16).padStart(2, "0");
		const traceState = spanContext.traceState;
		if (traceState) try {
			if (typeof traceState.serialize === "function") {
				const traceStateStr = traceState.serialize();
				if (traceStateStr) autotelContext.trace_state = traceStateStr;
			}
		} catch {}
		if (eventsConfig.traceUrl && config) {
			const traceUrl = eventsConfig.traceUrl({
				traceId: spanContext.traceId,
				spanId: spanContext.spanId,
				correlationId,
				serviceName: config.service,
				environment: config.environment
			});
			if (traceUrl) autotelContext.trace_url = traceUrl;
		}
	} else if (eventsConfig.traceUrl && config) {
		const traceUrl = eventsConfig.traceUrl({
			correlationId,
			serviceName: config.service,
			environment: config.environment
		});
		if (traceUrl) autotelContext.trace_url = traceUrl;
	}
	return autotelContext;
}
/**
* Initialize events queue lazily
*/
function getOrCreateQueue() {
	if (!isInitialized()) {
		warnIfNotInitialized("track()");
		return null;
	}
	if (!eventsQueue) {
		const config = getConfig();
		if (!config?.subscribers || config.subscribers.length === 0) {
			if (!warnedNoSubscribers) {
				warnedNoSubscribers = true;
				getLogger().warn({}, "[autotel] track() dropped an event: init() configured no subscribers. Events (including gen_ai.evaluation.result and audit events) reach a backend through init({ subscribers: [...] }).");
			}
			return null;
		}
		eventsQueue = new EventQueue(config.subscribers);
	}
	return eventsQueue;
}
/**
* Track a business events event
*
* Features:
* - Auto-attaches traceId and spanId if in active span
* - Batched sending with retry
* - Type-safe with optional generic
* - No-op if init() not called or no subscribers configured
*
* @example Basic usage
* ```typescript
* track('user.signup', { userId: '123', plan: 'pro' })
* ```
*
* @example With type safety
* ```typescript
* interface EventDatas {
*   'user.signup': { userId: string; plan: string }
*   'plan.upgraded': { userId: string; revenue: number }
* }
*
* track<EventDatas>('user.signup', { userId: '123', plan: 'pro' })
* ```
*
* @example Trace correlation (automatic)
* ```typescript
* @Instrumented()
* class UserService {
*   async createUser(data: CreateUserData) {
*     // This track call automatically includes traceId + spanId
*     track('user.signup', { userId: data.id })
*   }
* }
* ```
*/
function track(event, data, options) {
	const queue = getOrCreateQueue();
	if (!queue) return;
	const validated = validateEvent(event, data, getValidationConfig() || void 0);
	const span = trace.getActiveSpan();
	const enrichedData = span ? {
		...validated.attributes,
		traceId: span.spanContext().traceId,
		spanId: span.spanContext().spanId
	} : validated.attributes;
	const autotelContext = buildAutotelContext(span);
	queue.enqueue({
		name: validated.eventName,
		attributes: enrichedData,
		timestamp: Date.now(),
		autotel: autotelContext,
		schema: options?.schema
	});
}
/**
* Get events queue (for flush/shutdown)
* @internal
*/
function getEventQueue() {
	return eventsQueue;
}
/**
* Reset events queue (for shutdown/cleanup)
* @internal
*/
function resetEventQueue() {
	eventsQueue = null;
	warnedNoSubscribers = false;
}

//#endregion
//#region src/trace-context.ts
const spansWithExplicitStatus = /* @__PURE__ */ new WeakSet();
/** Whether a TraceContext consumer explicitly chose this span's status. */
function hasExplicitSpanStatus(span) {
	return spansWithExplicitStatus.has(span);
}
/**
* AsyncLocalStorage for storing the active context with baggage
* This allows setters to update the context and have it persist
*/
const contextStorage = new nodeAsyncHooks.AsyncLocalStorage();
/**
* Get the context storage instance (for initialization in functional.ts)
*/
function getContextStorage() {
	return contextStorage;
}
/**
* Get the active OTel context with the latest stored baggage overlaid.
* Span identity always comes from the active OTel scope.
*/
function getActiveContextWithBaggage() {
	const activeContext = context.active();
	const stored = contextStorage.getStore()?.value;
	if (!stored) return activeContext;
	const storedBaggage = propagation.getBaggage(stored);
	return storedBaggage ? propagation.setBaggage(activeContext, storedBaggage) : activeContext;
}
/**
* Set a value in AsyncLocalStorage, preferring enterWith() when available
* (Node.js) and falling back to run() for environments that only support
* run() (e.g. Cloudflare Workers).
*
* On runtimes without enterWith() we mutate the existing run() scope when one
* exists. This is what allows baggage/correlation updates to remain visible
* for the rest of the traced callback in Workers.
*/
function enterOrRun(storage, value) {
	const existingStore = storage.getStore();
	if (existingStore) {
		existingStore.value = value;
		return;
	}
	const boxedValue = { value };
	try {
		storage.enterWith(boxedValue);
	} catch {
		storage.run(boxedValue, () => {});
	}
}
function updateActiveContext(newContext) {
	enterOrRun(contextStorage, newContext);
	const manager = context._getContextManager?.();
	if (!manager) return;
	const asyncLocal = manager._asyncLocalStorage ?? void 0;
	if (asyncLocal?.enterWith) {
		asyncLocal.enterWith(newContext);
		return;
	}
	if (isFunction(manager.with)) manager.with(newContext, () => {});
}
/**
* Create a TraceContext from an OpenTelemetry Span
*
* This utility extracts trace context information from a span
* and provides span manipulation methods and baggage operations in a consistent format.
*
* Note: Baggage methods always operate on the currently active context,
* which may differ from the context when createTraceContext was called.
*/
function createTraceContext(span) {
	const spanContext = span.spanContext();
	if (!contextStorage.getStore()?.value) {
		const activeContext = context.active();
		enterOrRun(contextStorage, activeContext);
	}
	const traceCtx = {
		traceId: spanContext.traceId,
		spanId: spanContext.spanId,
		correlationId: spanContext.traceId.slice(0, 16),
		setAttribute: (key, value) => {
			const attr = toAttributeValue(value);
			if (attr === void 0) {
				span.setAttributes(flattenToAttributes({ [key]: value }));
				return;
			}
			span.setAttribute(key, attr);
		},
		setAttributes: (attrs) => {
			span.setAttributes(flattenToAttributes(attrs));
		},
		setStatus: (status) => {
			spansWithExplicitStatus.add(span);
			span.setStatus(status);
		},
		recordException: span.recordException.bind(span),
		addEvent: span.addEvent.bind(span),
		addLink: span.addLink.bind(span),
		addLinks: span.addLinks.bind(span),
		updateName: span.updateName.bind(span),
		isRecording: span.isRecording.bind(span),
		recordError: (cause) => {
			const err = cause instanceof Error ? cause : new Error(String(cause));
			recordStructuredError(traceCtx, err);
		},
		track: (event, data) => {
			track(event, data);
		},
		getBaggage(key) {
			const activeCtx = context.active();
			let baggage = propagation.getBaggage(activeCtx);
			if (!baggage) {
				const storedContext = contextStorage.getStore()?.value;
				if (storedContext) baggage = propagation.getBaggage(storedContext);
			}
			return baggage?.getEntry(key)?.value;
		},
		setBaggage(key, value) {
			const currentContext = getActiveContextWithBaggage();
			const updated = (propagation.getBaggage(currentContext) ?? propagation.createBaggage()).setEntry(key, { value });
			updateActiveContext(propagation.setBaggage(currentContext, updated));
			return value;
		},
		deleteBaggage(key) {
			const currentContext = getActiveContextWithBaggage();
			const baggage = propagation.getBaggage(currentContext);
			if (baggage) {
				const updated = baggage.removeEntry(key);
				updateActiveContext(propagation.setBaggage(currentContext, updated));
			}
		},
		getAllBaggage() {
			const activeCtx = context.active();
			let baggage = propagation.getBaggage(activeCtx);
			if (!baggage) {
				const storedContext = contextStorage.getStore()?.value;
				if (storedContext) baggage = propagation.getBaggage(storedContext);
			}
			if (!baggage) return /* @__PURE__ */ new Map();
			const entries = /* @__PURE__ */ new Map();
			for (const [key, entry] of baggage.getAllEntries()) entries.set(key, entry);
			return entries;
		},
		getTypedBaggage: ((namespace) => {
			const activeCtx = context.active();
			let baggage = propagation.getBaggage(activeCtx);
			if (!baggage) {
				const storedContext = contextStorage.getStore()?.value;
				if (storedContext) baggage = propagation.getBaggage(storedContext);
			}
			if (!baggage) return;
			const prefix = namespace ? `${namespace}.` : "";
			const entries = [];
			for (const [key, entry] of baggage.getAllEntries()) if (namespace && key.startsWith(prefix)) entries.push([key.slice(prefix.length), entry.value]);
			else if (!namespace) entries.push([key, entry.value]);
			const result = Object.fromEntries(entries);
			return entries.length > 0 ? result : void 0;
		}),
		setTypedBaggage: ((namespace, value) => {
			const currentContext = getActiveContextWithBaggage();
			let baggage = propagation.getBaggage(currentContext) ?? propagation.createBaggage();
			const prefix = namespace ? `${namespace}.` : "";
			for (const [key, val] of Object.entries(value)) if (val !== void 0) {
				const baggageKey = `${prefix}${key}`;
				baggage = baggage.setEntry(baggageKey, { value: String(val) });
			}
			updateActiveContext(propagation.setBaggage(currentContext, baggage));
		})
	};
	return traceCtx;
}
/**
* Define a typed baggage schema for type-safe baggage operations
*
* This helper provides a type-safe API for working with baggage entries.
* The namespace parameter is optional and prefixes all keys to avoid collisions.
*
* @template T - The baggage schema type (all fields are treated as optional)
* @param namespace - Optional namespace to prefix baggage keys
*
* @example Basic usage
* ```typescript
* type TenantBaggage = { tenantId: string; region?: string };
* const tenantBaggage = defineBaggageSchema<TenantBaggage>('tenant');
*
* export const handler = trace<TenantBaggage>((ctx) => async () => {
*   // Get typed baggage
*   const tenant = tenantBaggage.get(ctx);
*   if (tenant?.tenantId) {
*     console.log('Tenant:', tenant.tenantId);
*   }
*
*   // Set typed baggage
*   tenantBaggage.set(ctx, { tenantId: 't1', region: 'us-east-1' });
* });
* ```
*
* @example With withBaggage helper
* ```typescript
* const tenantBaggage = defineBaggageSchema<TenantBaggage>('tenant');
*
* export const handler = trace<TenantBaggage>((ctx) => async () => {
*   return await tenantBaggage.with(ctx, { tenantId: 't1' }, async () => {
*     // Baggage is available here and in child spans
*     const tenant = tenantBaggage.get(ctx);
*   });
* });
* ```
*/
function defineBaggageSchema(namespace) {
	return {
		/**
		* Get typed baggage from context
		* @param ctx - Trace context
		* @returns Partial baggage object or undefined if no baggage is set
		*/
		get: (ctx) => {
			if (!ctx.getTypedBaggage) return void 0;
			return ctx.getTypedBaggage(namespace);
		},
		/**
		* Set typed baggage in context
		*
		* Note: For proper scoping across async boundaries, use the `with` method instead
		*
		* @param ctx - Trace context
		* @param value - Partial baggage object to set
		*/
		set: (ctx, value) => {
			if (!ctx.setTypedBaggage) return;
			ctx.setTypedBaggage(namespace, value);
		},
		/**
		* Run a function with typed baggage properly scoped
		*
		* This is the recommended way to set baggage as it ensures proper
		* scoping across async boundaries.
		*
		* @param ctx - Trace context (can be omitted, will use active context)
		* @param value - Partial baggage object to set
		* @param fn - Function to execute with the baggage
		*/
		with: (ctxOrValue, valueOrFn, maybeFn) => {
			const value = maybeFn ? valueOrFn : ctxOrValue;
			const fn = maybeFn || valueOrFn;
			const prefix = namespace ? `${namespace}.` : "";
			const flatBaggage = {};
			for (const [key, val] of Object.entries(value)) if (val !== void 0) flatBaggage[`${prefix}${key}`] = String(val);
			const currentContext = context.active();
			let baggage = propagation.getBaggage(currentContext) ?? propagation.createBaggage();
			for (const [key, val] of Object.entries(flatBaggage)) baggage = baggage.setEntry(key, { value: val });
			const newContext = propagation.setBaggage(currentContext, baggage);
			return context.with(newContext, fn);
		}
	};
}

//#endregion
//#region src/sampling.ts
/**
* Tail sampling attribute keys (autotel-internal, not OTel semconv)
*/
const AUTOTEL_SAMPLING_TAIL_KEEP = "autotel.sampling.tail.keep";
const AUTOTEL_SAMPLING_TAIL_EVALUATED = "autotel.sampling.tail.evaluated";
/**
* How many events each kept event stands for, expressed as "1 in N".
*
* A query that counts sampled spans undercounts the population. Multiplying
* each kept event by this rate restores the estimate. Autotel records the
* attribute only when N exceeds 1, so fully captured spans stay clean.
*/
const AUTOTEL_SAMPLING_RATE = "autotel.sampling.rate";
/**
* Spans an explicit `forceKeep()` claimed.
*
* The tracing wrapper writes the sampler's tail verdict once the body has
* run, which is after any `forceKeep()` inside it. Without this the verdict
* overwrites the override and the span the caller insisted on is dropped.
*/
/**
* Baggage key that turns on full-fidelity capture for a request.
*
* Baggage arrives on the request and propagates, so a gateway, a proxy, a
* feature flag or a curl can turn this on for one user and it follows them
* across every service. Nobody deploys anything to debug a live problem.
*/
const AUTOTEL_DEBUG_BAGGAGE_KEY = "autotel.debug";
const forceKeptSpans = /* @__PURE__ */ new WeakSet();
function markForceKept(span) {
	forceKeptSpans.add(span);
}
function isForceKept(span) {
	return forceKeptSpans.has(span);
}
/**
* Convert a keep probability into the "1 in N" rate reported on spans.
*
* Keeping 10% of traces means each survivor stands for 10, so the two numbers
* are reciprocals and easy to publish the wrong way round.
*/
function toSampleRate(probability) {
	return probability > 0 ? 1 / probability : 0;
}
/**
* Map a string to a stable, evenly spread position in the unit interval.
*
* Two processes that hash the same key reach the same number, which is what
* lets independent services agree on one trace's sampling decision.
*
* The spread matters as much as the stability. Real sampling keys share long
* prefixes: `user_1000`, `user_1001`, `checkout-trace-0001`. A plain
* multiply-and-add hash lets that shared prefix dominate the high bits, so a
* whole key family lands in one narrow band and a rate of 0.1 keeps all of
* them or none of them. FNV-1a followed by the murmur3 finalizer mixes the
* low bits back through the word, so keys that differ in one character land
* far apart.
*/
function hashUnitInterval(value) {
	let hash = 2166136261;
	for (let i = 0; i < value.length; i++) {
		hash ^= value.codePointAt(i) ?? 0;
		hash = Math.imul(hash, 16777619);
	}
	hash ^= hash >>> 16;
	hash = Math.imul(hash, 2246822507);
	hash ^= hash >>> 13;
	hash = Math.imul(hash, 3266489909);
	hash ^= hash >>> 16;
	return (hash >>> 0) / 4294967296;
}
/**
* Simple random sampler
*
* @example
* ```typescript
* new RandomSampler(0.1) // Sample 10% of requests
* ```
*/
var RandomSampler = class {
	rate;
	constructor(rate) {
		this.rate = rate;
		if (rate < 0 || rate > 1) throw new Error("Sample rate must be between 0 and 1");
	}
	shouldSample(_context) {
		return Math.random() < this.rate;
	}
	sampleRate() {
		return toSampleRate(this.rate);
	}
};
/**
* Always sample (100% tracing)
*/
var AlwaysSampler = class {
	shouldSample(_context) {
		return true;
	}
};
/**
* Never sample (0% tracing)
*/
var NeverSampler = class {
	shouldSample(_context) {
		return false;
	}
};
/**
* Adaptive sampler that always traces errors and slow requests
*
* This is the recommended sampler for production use.
* It ensures you never miss critical issues while keeping costs down.
*
* Strategy:
* - Always trace errors (critical for debugging)
* - Always trace slow requests (performance issues)
* - Use baseline sample rate for successful fast requests
*
* **IMPORTANT - Tail Sampling Requirement:**
* This sampler uses tail sampling (makes decisions AFTER execution).
* You MUST use TailSamplingSpanProcessor for it to work correctly:
*
* - If using initInstrumentation(): TailSamplingSpanProcessor is auto-configured
* - If using custom TracerProvider: You MUST manually register TailSamplingSpanProcessor
*
* Without TailSamplingSpanProcessor, ALL spans are exported (defeating the cost savings).
*
* @see TailSamplingSpanProcessor
* @see README.md "Tail Sampling with Custom Providers" section
*
* @example
* ```typescript
* new AdaptiveSampler({
*   baselineSampleRate: 0.1,    // 10% of normal requests
*   slowThresholdMs: 1000,       // Requests > 1s are "slow"
*   alwaysSampleErrors: true,    // Always trace errors
*   alwaysSampleSlow: true       // Always trace slow requests
* })
* ```
*/
var AdaptiveSampler = class {
	baselineSampleRate;
	slowThresholdMs;
	alwaysSampleErrors;
	alwaysSampleSlow;
	linksBased;
	linksRate;
	logger;
	samplingDecisions = /* @__PURE__ */ new WeakMap();
	operationResults = /* @__PURE__ */ new WeakMap();
	constructor(options = {}) {
		this.baselineSampleRate = options.baselineSampleRate ?? .1;
		this.slowThresholdMs = options.slowThresholdMs ?? 1e3;
		this.alwaysSampleErrors = options.alwaysSampleErrors ?? true;
		this.alwaysSampleSlow = options.alwaysSampleSlow ?? true;
		this.linksBased = options.linksBased ?? false;
		this.linksRate = options.linksRate ?? 1;
		this.logger = options.logger;
		if (this.baselineSampleRate < 0 || this.baselineSampleRate > 1) throw new Error("Baseline sample rate must be between 0 and 1");
		if (this.linksRate < 0 || this.linksRate > 1) throw new Error("Links rate must be between 0 and 1");
	}
	needsTailSampling() {
		return true;
	}
	shouldSample(context) {
		const baselineDecision = Math.random() < this.baselineSampleRate;
		this.samplingDecisions.set(context.args, baselineDecision);
		return true;
	}
	/**
	* Check if any links point to sampled spans.
	*
	* A span is considered linked to a sampled span if any of its links
	* have trace_flags with the sampled bit set (0x01).
	*
	* @param links - Array of span links to check
	* @returns true if any linked span is sampled, false otherwise
	*/
	hasSampledLink(links) {
		if (!links || links.length === 0) return false;
		return links.some((link) => link.context && (link.context.traceFlags & TraceFlags.SAMPLED) !== 0);
	}
	/**
	* Re-evaluate sampling decision after operation completes
	*
	* This allows us to always capture errors and slow requests,
	* even if they weren't initially sampled.
	*
	* @param context - Sampling context
	* @param result - Operation result
	* @returns true if this operation should be kept (not discarded)
	*/
	shouldKeepTrace(context, result) {
		const baselineDecision = this.samplingDecisions.get(context.args) ?? false;
		if (this.alwaysSampleErrors && !result.success) {
			if (!baselineDecision) this.logger?.debug({
				operation: context.operationName,
				error: result.error?.message
			}, "Adaptive sampling: Keeping error trace");
			return true;
		}
		if (this.alwaysSampleSlow && result.duration >= this.slowThresholdMs) {
			if (!baselineDecision) this.logger?.debug({
				operation: context.operationName,
				duration: result.duration
			}, "Adaptive sampling: Keeping slow trace");
			return true;
		}
		if (this.linksBased && context.links && this.hasSampledLink(context.links)) {
			const keepLinked = Math.random() < this.linksRate;
			if (keepLinked && !baselineDecision) this.logger?.debug({
				operation: context.operationName,
				linkCount: context.links.length
			}, "Adaptive sampling: Keeping trace due to sampled link");
			return keepLinked;
		}
		return baselineDecision;
	}
};
/**
* User-based sampler for consistent tracing
*
* Always samples requests from specific user IDs.
* Useful for debugging specific user issues or monitoring VIP users.
*
* @example
* ```typescript
* new UserIdSampler({
*   baselineSampleRate: 0.01,      // 1% of normal users
*   alwaysSampleUsers: ['vip_123'], // Always trace VIP users
*   extractUserId: (args) => args[0]?.userId // Extract user ID from first arg
* })
* ```
*/
var UserIdSampler = class {
	baselineSampleRate;
	alwaysSampleUsers;
	extractUserId;
	logger;
	constructor(options) {
		this.baselineSampleRate = options.baselineSampleRate ?? .1;
		this.alwaysSampleUsers = new Set(options.alwaysSampleUsers || []);
		this.extractUserId = options.extractUserId;
		this.logger = options.logger;
	}
	shouldSample(context) {
		const userId = this.extractUserId(context.args);
		if (userId && this.alwaysSampleUsers.has(userId)) {
			this.logger?.debug({
				operation: context.operationName,
				userId
			}, "Sampling user request");
			return true;
		}
		if (userId) return this.hashString(userId) < this.baselineSampleRate;
		return Math.random() < this.baselineSampleRate;
	}
	/**
	* Add user IDs to always-sample list
	*/
	addAlwaysSampleUsers(...userIds) {
		for (const userId of userIds) this.alwaysSampleUsers.add(userId);
	}
	/**
	* Remove user IDs from always-sample list
	*/
	removeAlwaysSampleUsers(...userIds) {
		for (const userId of userIds) this.alwaysSampleUsers.delete(userId);
	}
	/**
	* Simple hash function for consistent user sampling
	*/
	hashString(str) {
		return hashUnitInterval(str);
	}
};
/**
* Consistent sampler: every service reaches the same verdict for one trace.
*
* `RandomSampler` rolls the dice per process, so an upstream service can keep
* a trace that its downstream drops, leaving a waterfall with holes in it.
* Hashing a key that travels with the request removes the disagreement. Pass
* the trace id, or any identifier every hop already shares.
*
* @example
* ```typescript
* new DeterministicSampler({
*   sampleRate: 0.1,
*   key: (context) => trace.getActiveSpan()?.spanContext().traceId,
* })
* ```
*/
var DeterministicSampler = class {
	rate;
	key;
	constructor(options) {
		if (options.sampleRate < 0 || options.sampleRate > 1) throw new Error("Sample rate must be between 0 and 1");
		this.rate = options.sampleRate;
		this.key = options.key;
	}
	shouldSample(context) {
		const key = this.key(context);
		if (key === void 0) return Math.random() < this.rate;
		return hashUnitInterval(key) < this.rate;
	}
	sampleRate() {
		return toSampleRate(this.rate);
	}
};
/** Bucket for keys seen after the tracked map fills up. */
const OVERFLOW_KEY = "__overflow__";
/**
* Per-key target-rate sampler for workloads with uneven traffic.
*
* A single rate serves a skewed workload badly: 1% floods storage with the
* busiest endpoint and still loses the rare tenant whose failures you need.
* This sampler counts traffic per key over a rolling window, then sets each
* key its own rate so every key contributes roughly `targetPerKey` events.
* Quiet keys survive intact; loud keys get thinned.
*
* The first window keeps everything, because no traffic history exists yet.
* Rates take effect from the second window onward.
*
* @example
* ```typescript
* new KeyTargetRateSampler({
*   key: (context) => context.operationName,
*   targetPerKey: 10,     // ~10 events per key per window
*   windowMs: 30_000,
* })
* ```
*/
var KeyTargetRateSampler = class {
	key;
	targetPerKey;
	windowMs;
	maxKeys;
	counts = /* @__PURE__ */ new Map();
	rates = /* @__PURE__ */ new Map();
	windowStart = Date.now();
	constructor(options) {
		this.key = options.key;
		this.targetPerKey = options.targetPerKey ?? 10;
		this.windowMs = options.windowMs ?? 3e4;
		this.maxKeys = options.maxKeys ?? 1e3;
		if (this.targetPerKey <= 0) throw new Error("Target per key must be greater than 0");
		if (this.windowMs <= 0) throw new Error("Window must be greater than 0");
	}
	/** Turn the window's observed counts into the next window's rates. */
	roll(now) {
		if (now - this.windowStart < this.windowMs) return;
		const rates = /* @__PURE__ */ new Map();
		for (const [key, count] of this.counts) rates.set(key, Math.max(1, count / this.targetPerKey));
		this.rates = rates;
		this.counts = /* @__PURE__ */ new Map();
		this.windowStart = now;
	}
	/**
	* Resolve the key, collapsing into one bucket once the map is full.
	*
	* An unbounded key function would otherwise grow the map without limit,
	* which turns a sampler meant to cut cost into a memory leak.
	*/
	resolveKey(context) {
		const key = this.key(context) ?? OVERFLOW_KEY;
		if (this.counts.has(key) || this.counts.size < this.maxKeys) return key;
		return OVERFLOW_KEY;
	}
	shouldSample(context) {
		this.roll(Date.now());
		const key = this.resolveKey(context);
		this.counts.set(key, (this.counts.get(key) ?? 0) + 1);
		const rate = this.rates.get(key) ?? 1;
		return rate <= 1 || Math.random() < 1 / rate;
	}
	sampleRate(context) {
		return this.rates.get(this.resolveKey(context)) ?? 1;
	}
};
/**
* Composite sampler that combines multiple samplers
*
* Samples if ANY of the child samplers returns true.
*
* @example
* ```typescript
* new CompositeSampler([
*   new UserIdSampler({ extractUserId: (args) => args[0]?.userId }),
*   new AdaptiveSampler({ baselineSampleRate: 0.1 })
* ])
* ```
*/
var CompositeSampler = class {
	samplers;
	constructor(samplers) {
		this.samplers = samplers;
		if (samplers.length === 0) throw new Error("CompositeSampler requires at least one child sampler");
	}
	shouldSample(context) {
		return this.samplers.some((sampler) => sampler.shouldSample(context));
	}
};
/**
* Feature flag sampler
*
* Always samples requests with specific feature flags enabled.
* Perfect for correlating A/B test experiments with metrics.
*
* @example
* ```typescript
* new FeatureFlagSampler({
*   baselineSampleRate: 0.01,
*   alwaysSampleFlags: ['new_checkout', 'experimental_ui'],
*   extractFlags: (args, metadata) => metadata?.featureFlags
* })
* ```
*/
var FeatureFlagSampler = class {
	baselineSampleRate;
	alwaysSampleFlags;
	extractFlags;
	logger;
	constructor(options) {
		this.baselineSampleRate = options.baselineSampleRate ?? .1;
		this.alwaysSampleFlags = new Set(options.alwaysSampleFlags || []);
		this.extractFlags = options.extractFlags;
		this.logger = options.logger;
	}
	shouldSample(context) {
		const flags = this.extractFlags(context.args, context.metadata);
		if (flags && flags.some((flag) => this.alwaysSampleFlags.has(flag))) {
			this.logger?.debug({
				operation: context.operationName,
				flags
			}, "Sampling feature flag request");
			return true;
		}
		return Math.random() < this.baselineSampleRate;
	}
	/**
	* Add feature flags to always-sample list
	*/
	addAlwaysSampleFlags(...flags) {
		for (const flag of flags) this.alwaysSampleFlags.add(flag);
	}
	/**
	* Remove feature flags from always-sample list
	*/
	removeAlwaysSampleFlags(...flags) {
		for (const flag of flags) this.alwaysSampleFlags.delete(flag);
	}
};
/**
* Sampling preset factories.
*
* For most users, the string shorthand on `init()` is simpler:
* ```typescript
* init({ service: 'my-app', sampling: 'production' })
* ```
*
* Use factories when you need to customize:
* ```typescript
* init({ service: 'my-app', sampler: samplingPresets.production({ baselineSampleRate: 0.05 }) })
* ```
*/
const samplingPresets = {
	/** Capture everything — best for local development and debugging */
	development: () => new AlwaysSampler(),
	/** Only bad outcomes — zero baseline, errors always kept */
	errorsOnly: () => new AdaptiveSampler({
		baselineSampleRate: 0,
		alwaysSampleErrors: true
	}),
	/**
	* Balanced production defaults — 10% baseline + errors + slow traces.
	* Pass overrides to tune (uses the same option names as AdaptiveSampler).
	*/
	production: (overrides) => new AdaptiveSampler({
		baselineSampleRate: .1,
		alwaysSampleErrors: true,
		alwaysSampleSlow: true,
		slowThresholdMs: 1e3,
		...overrides
	}),
	/** Disable sampling entirely */
	off: () => new NeverSampler()
};
/**
* Resolve a preset string to a Sampler instance.
* Used internally by `init()` when `sampling` string is provided.
*
* @throws Error if preset is not recognized
*/
function resolveSamplingPreset(preset) {
	switch (preset) {
		case "development": return samplingPresets.development();
		case "errors-only": return samplingPresets.errorsOnly();
		case "production": return samplingPresets.production();
		case "off": return samplingPresets.off();
		default: throw new Error(`Unknown sampling preset: "${preset}". Valid presets: development, errors-only, production, off`);
	}
}
/**
* Create a Link from W3C trace context headers (e.g., from a message queue).
*
* This is useful for message consumers that need to link to the producer span.
* The headers should contain at least a `traceparent` header in W3C format.
*
* @param headers - Dictionary containing traceparent/tracestate headers
* @param attributes - Optional attributes for the link
* @returns Link object if context could be extracted, null otherwise
*
* @example
* ```typescript
* // In a Kafka consumer
* const headers = { traceparent: '00-abc123...-def456...-01' };
* const link = createLinkFromHeaders(headers);
* if (link) {
*   // Use with tracer.startActiveSpan options or ctx.addLink()
*   tracer.startActiveSpan('process.message', { links: [link] }, span => { ... });
* }
* ```
*/
function createLinkFromHeaders(headers, attributes) {
	const traceparent = headers.traceparent || headers["traceparent"];
	if (!traceparent) return null;
	const spanContext = parseTraceparent(traceparent);
	if (!spanContext || !isValidSpanContext(spanContext)) return null;
	return {
		context: spanContext,
		attributes: attributes ?? {}
	};
}
/**
* Extract Links from a batch of messages for fan-in scenarios.
*
* Useful for batch processing where multiple producer spans should be linked.
* This enables tracing causality in event-driven architectures where a single
* consumer processes messages from multiple producers.
*
* @param messages - List of message objects
* @param headersKey - Key in each message containing trace headers (default: 'headers')
* @returns List of Link objects for all valid trace contexts
*
* @example
* ```typescript
* // Processing a batch of SQS/Kafka messages
* const messages = [
*   { body: '...', headers: { traceparent: '...' } },
*   { body: '...', headers: { traceparent: '...' } },
* ];
* const links = extractLinksFromBatch(messages);
*
* tracer.startActiveSpan('process.batch', { links }, span => {
*   for (const msg of messages) {
*     processMessage(msg);
*   }
* });
* ```
*/
function extractLinksFromBatch(messages, headersKey = "headers") {
	const links = [];
	for (const msg of messages) {
		const msgHeaders = asStringRecord(msg[headersKey]);
		if (msgHeaders) {
			const link = createLinkFromHeaders(msgHeaders, { "messaging.batch.message_index": links.length });
			if (link) links.push(link);
		}
	}
	return links;
}
/**
* Parse W3C traceparent header into SpanContext
* Format: version-traceId-spanId-traceFlags (e.g., 00-abc123...-def456...-01)
*
* @see https://www.w3.org/TR/trace-context/#traceparent-header
*/
function parseTraceparent(traceparent) {
	const match = traceparent.match(/^([0-9a-f]{2})-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})$/i);
	if (!match || match.length < 5) return null;
	const version = match[1];
	const traceId = match[2];
	const spanId = match[3];
	const flags = match[4];
	if (!version || !traceId || !spanId || !flags) return null;
	if (version === "ff") return null;
	return {
		traceId,
		spanId,
		traceFlags: Number.parseInt(flags, 16),
		isRemote: true
	};
}
/**
* Check if a SpanContext is valid (has non-zero trace and span IDs)
*/
function isValidSpanContext(spanContext) {
	if (!spanContext) return false;
	return spanContext.traceId !== "00000000000000000000000000000000" && spanContext.spanId !== "0000000000000000";
}
/** True when the request in flight asked for full-fidelity capture. */
function debugCaptureRequested() {
	const entry = propagation.getBaggage(getActiveContextWithBaggage())?.getEntry(AUTOTEL_DEBUG_BAGGAGE_KEY);
	return entry !== void 0 && entry.value !== "" && entry.value !== "0";
}

//#endregion
//#region src/init-logger.ts
const LOG_LEVELS = {
	debug: 0,
	info: 1,
	warn: 2,
	error: 3
};
/** Silent logger used until and unless the application opts into diagnostics. */
const silentLogger = {
	info: () => {},
	warn: () => {},
	error: () => {},
	debug: () => {}
};
/** Apply the resolved silence and minimum-level settings to a logger. */
function wrapLogger(base, silent, minLevel) {
	if (silent) return silentLogger;
	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")
	};
}

//#endregion
//#region src/posthog-logs.ts
var RedactingLogRecordProcessor = class {
	wrapped;
	redact;
	constructor(wrapped, redact) {
		this.wrapped = wrapped;
		this.redact = redact;
	}
	onEmit(logRecord, context) {
		const body = asString(logRecord.body);
		if (body) logRecord.body = this.redact(body);
		if (logRecord.attributes) for (const [key, value] of Object.entries(logRecord.attributes)) {
			const text = asString(value);
			if (text !== void 0) logRecord.attributes[key] = this.redact(text);
			else if (Array.isArray(value)) logRecord.attributes[key] = value.map((item) => {
				const entry = asString(item);
				return entry === void 0 ? item : this.redact(entry);
			});
		}
		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 = safeRequire("@opentelemetry/sdk-logs");
	const exporterModule = 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/tail-sampling-processor.ts
var TailSamplingSpanProcessor = class {
	wrappedProcessor;
	constructor(wrappedProcessor) {
		this.wrappedProcessor = wrappedProcessor;
	}
	onStart(span, parentContext) {
		this.wrappedProcessor.onStart(span, parentContext);
	}
	onEnd(span) {
		const tailEvaluated = span.attributes[AUTOTEL_SAMPLING_TAIL_EVALUATED];
		const shouldKeep = span.attributes[AUTOTEL_SAMPLING_TAIL_KEEP];
		if (tailEvaluated === true && shouldKeep === false) return;
		this.wrappedProcessor.onEnd(span);
	}
	forceFlush() {
		return this.wrappedProcessor.forceFlush();
	}
	shutdown() {
		return this.wrappedProcessor.shutdown();
	}
};

//#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 = propagation.getBaggage(parentContext);
		if (!baggage) baggage = propagation.getBaggage(context.active());
		if (!baggage) try {
			const { getActiveContextWithBaggage } = requireModule("./trace-context");
			const storedContext = getActiveContextWithBaggage();
			baggage = 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" ? 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;
			result = mask ? result.replaceAll(pattern, (match) => mask(match)) : 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 === "http/json") env.OTEL_EXPORTER_OTLP_PROTOCOL = "http";
		else if (value === "http/protobuf" || 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 AlwaysOnSampler();
		case "always_off":
			warnOnUnusedSamplerArg(samplerName, env.OTEL_TRACES_SAMPLER_ARG);
			return new AlwaysOffSampler();
		case "traceidratio": return new TraceIdRatioBasedSampler(parseRatioSamplerArg(samplerName, env.OTEL_TRACES_SAMPLER_ARG));
		case "parentbased_always_on":
			warnOnUnusedSamplerArg(samplerName, env.OTEL_TRACES_SAMPLER_ARG);
			return new ParentBasedSampler({ root: new AlwaysOnSampler() });
		case "parentbased_always_off":
			warnOnUnusedSamplerArg(samplerName, env.OTEL_TRACES_SAMPLER_ARG);
			return new ParentBasedSampler({ root: new AlwaysOffSampler() });
		case "parentbased_traceidratio": return new ParentBasedSampler({ root: new 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/yaml-config.ts
/**
* YAML configuration loader for autotel
*
* Supports:
* - Auto-discovery of autotel.yaml in cwd
* - AUTOTEL_CONFIG_FILE env var override
* - Environment variable substitution: ${env:VAR} and ${env:VAR:-default}
*
* @example Auto-discovery
* ```yaml
* # autotel.yaml in project root
* service:
*   name: my-service
* exporter:
*   endpoint: ${env:OTEL_EXPORTER_OTLP_ENDPOINT:-http://localhost:4318}
* ```
*
* @example Explicit path
* ```bash
* AUTOTEL_CONFIG_FILE=./config/otel.yaml tsx --import autotel/auto src/index.ts
* ```
*/
/**
* Lazy-load yaml parser (optional peer dependency)
* Only loads when a YAML config file is actually found
*/
function loadYamlParser() {
	try {
		return requireModule("yaml").parse;
	} catch {
		throw new Error("YAML parser not found. Install with: pnpm add yaml");
	}
}
/**
* Environment variable substitution regex
* Matches ${env:VAR_NAME} and ${env:VAR_NAME:-default}
*/
const ENV_VAR_PATTERN = /\$\{env:([A-Za-z_][A-Za-z0-9_]*)(?::-([^}]*))?\}/g;
/**
* Substitute ${env:VAR} and ${env:VAR:-default} in a string
*
* @param value - String potentially containing env var references
* @returns String with env vars substituted
*
* @example
* substituteEnvVars('${env:NODE_ENV:-development}')
* // Returns 'production' if NODE_ENV=production, else 'development'
*/
function substituteEnvVars(value) {
	return value.replaceAll(ENV_VAR_PATTERN, (_match, varName, defaultValue) => {
		const envValue = process.env[varName];
		if (envValue !== void 0) return envValue;
		if (defaultValue !== void 0) return defaultValue;
		console.warn(`[autotel] Environment variable ${varName} not set and no default provided`);
		return "";
	});
}
/**
* Recursively substitute env vars in an object
*
* @param obj - Object to process
* @returns Object with all string values having env vars substituted
*/
function substituteEnvVarsDeep(value) {
	if (Array.isArray(value)) return value.map(substituteEnvVarsDeep);
	const mapping = asYamlMapping(value);
	if (mapping) {
		const result = {};
		for (const [key, entry] of Object.entries(mapping)) result[key] = substituteEnvVarsDeep(entry);
		return result;
	}
	const text = asString(value);
	return text === void 0 ? value : substituteEnvVars(text);
}
/** The mapping a YAML value is, when it is one. */
function asYamlMapping(value) {
	return asRecord(value) === void 0 ? void 0 : value;
}
/**
* A YAML document read into the config shape, with `${env:...}` placeholders
* resolved.
*
* This is the boundary: the file is text until here. Every field of
* YamlConfig is optional, so any document satisfies it, and
* `yamlToAutotelConfig` picks out only the fields it understands.
*/
function parseYamlConfig(content) {
	return asYamlMapping(substituteEnvVarsDeep(loadYamlParser()(content))) ?? {};
}
/**
* Find YAML config file path
*
* Priority:
* 1. AUTOTEL_CONFIG_FILE env var (explicit path)
* 2. autotel.yaml in cwd (convention)
* 3. autotel.yml in cwd (alternative extension)
*
* @returns File path if found, null otherwise
*/
function findConfigFile() {
	const envPath = process.env.AUTOTEL_CONFIG_FILE;
	if (envPath) {
		const resolved = path.resolve(envPath);
		if (nodeFs.existsSync(resolved)) return resolved;
		console.warn(`[autotel] Config file not found: ${envPath}`);
		return null;
	}
	const conventionPath = path.resolve(process.cwd(), "autotel.yaml");
	if (nodeFs.existsSync(conventionPath)) return conventionPath;
	const altPath = path.resolve(process.cwd(), "autotel.yml");
	if (nodeFs.existsSync(altPath)) return altPath;
	return null;
}
/**
* Convert YAML config structure to AutotelConfig
*
* @param yaml - Parsed and env-substituted YAML config
* @returns Partial AutotelConfig ready for merging
*/
function yamlToAutotelConfig(yaml) {
	const config = {};
	if (yaml.service?.name) config.service = yaml.service.name;
	if (yaml.service?.version) config.version = yaml.service.version;
	if (yaml.service?.environment) config.environment = yaml.service.environment;
	if (yaml.exporter?.endpoint) config.endpoint = yaml.exporter.endpoint;
	if (yaml.exporter?.protocol) config.protocol = yaml.exporter.protocol;
	if (yaml.exporter?.headers) config.headers = yaml.exporter.headers;
	if (yaml.exporter?.destinations) config.destinations = yaml.exporter.destinations;
	if (yaml.resource) config.resourceAttributes = yaml.resource;
	if (yaml.autoInstrumentations) config.autoInstrumentations = yaml.autoInstrumentations;
	if (yaml.policies) config.policies = yaml.policies;
	if (yaml.debug !== void 0) config.debug = yaml.debug;
	if (yaml.sampling?.preset) {
		warnOnIgnoredPresetOverrides(yaml.sampling);
		config.sampling = yaml.sampling.preset;
	} else {
		const sampler = createSamplerFromYaml(yaml.sampling);
		if (sampler) config.sampler = sampler;
	}
	return config;
}
function createSamplerFromYaml(sampling) {
	if (!sampling) return void 0;
	if (sampling.preset) return void 0;
	const type = sampling.type ?? "adaptive";
	try {
		switch (type) {
			case "adaptive": return new AdaptiveSampler({
				baselineSampleRate: sampling.baseline_rate,
				alwaysSampleErrors: sampling.always_sample_errors,
				alwaysSampleSlow: sampling.always_sample_slow,
				slowThresholdMs: sampling.slow_threshold_ms
			});
			case "always_on": return new AlwaysSampler();
			case "always_off": return new NeverSampler();
			case "ratio":
				if (sampling.ratio === void 0) {
					console.warn("[autotel] sampling.ratio missing in YAML sampling config. Falling back to adaptive sampler.");
					return new AdaptiveSampler();
				}
				return new RandomSampler(sampling.ratio);
			default:
				console.warn(`[autotel] Unknown sampling type "${type}" in YAML config. Falling back to defaults.`);
				return;
		}
	} catch (error) {
		console.warn(`[autotel] Failed to configure sampling from YAML: ${error instanceof Error ? error.message : String(error)}`);
		return;
	}
}
function warnOnIgnoredPresetOverrides(sampling) {
	const ignoredFields = [
		"type",
		"ratio",
		"baseline_rate",
		"always_sample_errors",
		"always_sample_slow",
		"slow_threshold_ms"
	].filter((field) => sampling[field] !== void 0);
	if (ignoredFields.length === 0) return;
	console.warn(`[autotel] sampling.preset="${sampling.preset}" ignores these YAML fields: ${ignoredFields.join(", ")}. Use the programmatic API with sampler or samplingPresets.*(...) for tuned presets.`);
}
/**
* Load and parse YAML config file (auto-discovery)
*
* Automatically finds and loads autotel.yaml or uses AUTOTEL_CONFIG_FILE.
* Returns null if no config file found (not an error - YAML config is optional).
*
* @returns Partial AutotelConfig or null if no config file found
*
* @example
* const yamlConfig = loadYamlConfig();
* if (yamlConfig) {
*   init({ ...yamlConfig, debug: true });
* }
*/
function loadYamlConfig() {
	const filePath = findConfigFile();
	if (!filePath) return null;
	try {
		return yamlToAutotelConfig(parseYamlConfig(nodeFs.readFileSync(filePath, "utf8")));
	} catch (error) {
		console.error(`[autotel] Failed to load YAML config from ${filePath}:`, error);
		return null;
	}
}
/**
* Load YAML config from a specific file path
*
* Unlike loadYamlConfig(), this throws if the file cannot be read.
*
* @param filePath - Path to YAML config file
* @returns Partial AutotelConfig
* @throws Error if file cannot be read or parsed
*
* @example
* import { loadYamlConfigFromFile } from 'autotel/yaml';
* import { init } from 'autotel';
*
* const config = loadYamlConfigFromFile('./config/otel.yaml');
* init({ ...config, debug: true });
*/
function loadYamlConfigFromFile(filePath) {
	const resolved = path.resolve(filePath);
	return yamlToAutotelConfig(parseYamlConfig(nodeFs.readFileSync(resolved, "utf8")));
}
/**
* Check if a YAML config file exists (without loading it)
*
* @returns true if a config file would be found by loadYamlConfig()
*/
function hasYamlConfig() {
	return findConfigFile() !== null;
}

//#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/process-handlers.ts
let removeOwnedHandlers = [];
/**
* Tracked separately from `removeOwnedHandlers` because the exit flush outlives
* a call to `installProcessHandlers`, which clears that list before installing
* its own signal listeners.
*/
let removeExitFlush;
/**
* Shared across every path that can end the process: signals, fatal errors and
* a clean exit. They can overlap — a container stopping a job that has just
* finished its work sends SIGTERM while the exit flush is still draining — and
* a second shutdown would tear down queues the first is still using.
*/
let shutdownInFlight;
/**
* The clean-exit flush, while it runs. Doubles as the latch that keeps a
* re-emitted `beforeExit` from flushing twice, and as the thing a shutdown
* waits on rather than tearing down queues mid-drain.
*/
let exitFlushInFlight;
const DEFAULT_SHUTDOWN_TIMEOUT_MS = 2e3;
function runShutdownOnce(shutdown, timeoutMs) {
	if (shutdownInFlight) return shutdownInFlight;
	let timeoutHandle;
	const shutdownAttempt = Promise.resolve(exitFlushInFlight).then(shutdown).catch(() => void 0);
	const timeout = new Promise((resolve) => {
		timeoutHandle = setTimeout(resolve, timeoutMs);
		timeoutHandle.unref();
	});
	shutdownInFlight = Promise.race([shutdownAttempt, timeout]).then(() => {
		if (timeoutHandle) clearTimeout(timeoutHandle);
	});
	return shutdownInFlight;
}
const DEFAULT_SIGNALS = ["SIGTERM", "SIGINT"];
function signalExitCode(signal) {
	return 128 + constants.signals[signal];
}
/**
* Surface a fatal error before shutting down.
*
* Registering an `uncaughtException` / `unhandledRejection` listener overrides
* Node's default of printing the stack to stderr, so without this a crash under
* `fatalErrors` would exit silently. Autotel's own logger is silent by default,
* so we print to stderr directly to guarantee the crash stays visible.
*/
function reportFatalError(error, event) {
	const err = error instanceof Error ? error : new Error(String(error));
	console.error(`[autotel] ${event}, flushing telemetry then exiting`, err);
}
function installProcessHandlers(config, shutdown) {
	uninstallProcessHandlers();
	const timeoutMs = config.shutdownTimeoutMs ?? DEFAULT_SHUTDOWN_TIMEOUT_MS;
	let exitScheduled = false;
	let resolvedExitCode = 0;
	let fatalLatched = false;
	const shutdownAndExit = (exitCode, fatal) => {
		if (fatal) {
			if (!fatalLatched) {
				resolvedExitCode = exitCode;
				fatalLatched = true;
			}
		} else if (!exitScheduled && !fatalLatched) resolvedExitCode = exitCode;
		if (exitScheduled) return;
		exitScheduled = true;
		runShutdownOnce(shutdown, timeoutMs).then(() => {
			process.exit(resolvedExitCode);
		});
	};
	for (const signal of config.signals ?? DEFAULT_SIGNALS) {
		const listener = () => {
			shutdownAndExit(signalExitCode(signal), false);
		};
		process.on(signal, listener);
		removeOwnedHandlers.push(() => {
			process.removeListener(signal, listener);
		});
	}
	if (config.fatalErrors ?? true) {
		const uncaughtExceptionListener = (error) => {
			reportFatalError(error, "uncaughtException");
			shutdownAndExit(1, true);
		};
		const unhandledRejectionListener = (reason) => {
			reportFatalError(reason, "unhandledRejection");
			shutdownAndExit(1, true);
		};
		process.on("uncaughtException", uncaughtExceptionListener);
		process.on("unhandledRejection", unhandledRejectionListener);
		removeOwnedHandlers.push(() => {
			process.removeListener("uncaughtException", uncaughtExceptionListener);
		}, () => {
			process.removeListener("unhandledRejection", unhandledRejectionListener);
		});
	}
}
/**
* Flush telemetry when the process runs to completion.
*
* The signal and fatal-error handlers above cover a process that is stopped or
* that crashes. Neither fires when a script simply finishes: the event loop
* drains and Node exits, taking whatever the batch span processor was still
* holding with it. `beforeExit` is the only hook for that case, and it is the
* one that matters for CLIs, cron jobs, CI steps and serverless handlers.
*
* A flush, never a shutdown: `beforeExit` fires on *any* event-loop drain, not
* only the final one, so tearing the SDK down here would silently kill
* telemetry in a process that goes on to do more work.
*/
function installExitFlush(flushTelemetry, timeoutMs = DEFAULT_SHUTDOWN_TIMEOUT_MS) {
	removeExitFlush?.();
	exitFlushInFlight = void 0;
	const listener = (code) => {
		if (exitFlushInFlight || shutdownInFlight) return;
		const deadline = setTimeout(() => {
			process.exit(process.exitCode ?? code);
		}, timeoutMs);
		exitFlushInFlight = flushTelemetry().catch(() => void 0).finally(() => {
			clearTimeout(deadline);
		});
	};
	process.on("beforeExit", listener);
	removeExitFlush = () => {
		process.removeListener("beforeExit", listener);
		removeExitFlush = void 0;
	};
}
function uninstallProcessHandlers() {
	for (const removeHandler of removeOwnedHandlers) removeHandler();
	removeOwnedHandlers = [];
	removeExitFlush?.();
	shutdownInFlight = void 0;
	exitFlushInFlight = void 0;
}

//#endregion
//#region src/circuit-breaker.ts
const DEFAULT_CONFIG = {
	failureThreshold: 5,
	resetTimeout: 3e4,
	windowSize: 6e4
};
const CircuitState = {
	CLOSED: "CLOSED",
	OPEN: "OPEN",
	HALF_OPEN: "HALF_OPEN"
};
/**
* Circuit breaker implementation
*
* Tracks failures and automatically opens the circuit to prevent
* overwhelming failing subscribers.
*/
var CircuitBreaker = class {
	state = CircuitState.CLOSED;
	failures = [];
	lastFailureTime = 0;
	config;
	name;
	constructor(name, config) {
		this.name = name;
		this.config = {
			...DEFAULT_CONFIG,
			...config
		};
	}
	/**
	* Execute a function with circuit breaker protection
	* Throws CircuitOpenError if circuit is open
	*/
	async execute(fn) {
		if (this.state === CircuitState.OPEN) {
			const now = Date.now();
			if (now - this.lastFailureTime >= this.config.resetTimeout) this.state = CircuitState.HALF_OPEN;
			else throw new CircuitOpenError(`Circuit breaker is OPEN for ${this.name}. Will retry in ${Math.ceil((this.config.resetTimeout - (now - this.lastFailureTime)) / 1e3)}s`);
		}
		try {
			const result = await fn();
			if (this.state === CircuitState.HALF_OPEN) this.reset();
			return result;
		} catch (error) {
			this.recordFailure(error);
			throw error;
		}
	}
	/**
	* Record a failure and potentially open the circuit
	*/
	recordFailure(error) {
		const now = Date.now();
		this.failures = this.failures.filter((f) => now - f.timestamp < this.config.windowSize);
		this.failures.push({
			timestamp: now,
			error: error instanceof Error ? error.message : String(error)
		});
		this.lastFailureTime = now;
		if (this.failures.length >= this.config.failureThreshold) {
			if (this.state === CircuitState.HALF_OPEN) this.state = CircuitState.OPEN;
			else if (this.state === CircuitState.CLOSED) this.state = CircuitState.OPEN;
		}
	}
	/**
	* Reset the circuit breaker (on success)
	*/
	reset() {
		this.state = CircuitState.CLOSED;
		this.failures = [];
		this.lastFailureTime = 0;
	}
	/**
	* Get current state (for monitoring)
	*/
	getState() {
		return this.state;
	}
	/**
	* Get failure count in current window
	*/
	getFailureCount() {
		const now = Date.now();
		this.failures = this.failures.filter((f) => now - f.timestamp < this.config.windowSize);
		return this.failures.length;
	}
	/**
	* Get recent failures (for debugging)
	*/
	getRecentFailures() {
		const now = Date.now();
		return this.failures.filter((f) => now - f.timestamp < this.config.windowSize);
	}
	/**
	* Manually reset the circuit breaker (for testing or manual intervention)
	*/
	forceReset() {
		this.reset();
	}
	/**
	* Manually open the circuit (for testing or manual intervention)
	*/
	forceOpen() {
		this.state = CircuitState.OPEN;
		this.lastFailureTime = Date.now();
	}
};
/**
* Error thrown when circuit is open
*/
var CircuitOpenError = class extends Error {
	constructor(message) {
		super(message);
		this.name = "CircuitOpenError";
	}
};

//#endregion
//#region src/operation-context.ts
/**
* Operation context tracking using AsyncLocalStorage
*
* This module provides a way to track operation names across async boundaries
* so they can be automatically captured in events events.
*
* We cannot read span attributes from OpenTelemetry's API (it's write-only),
* so we maintain our own async context storage.
*/
/**
* AsyncLocalStorage instance for tracking operation context
*/
const operationStorage = new nodeAsyncHooks.AsyncLocalStorage();
/**
* Get the current operation context (if any)
*
* @returns The current operation context, or undefined if not in an operation
*
* @example
* ```typescript
* const ctx = getOperationContext();
* if (ctx) {
*   console.log('Current operation:', ctx.name);
* }
* ```
*/
function getOperationContext() {
	return operationStorage.getStore();
}
/**
* Run a function within an operation context
*
* This sets the operation name for the duration of the function execution,
* including all async operations spawned from it.
*
* @param name - The operation name to set
* @param fn - The function to execute within the context
* @returns The result of the function
*
* @example
* ```typescript
* const result = await runInOperationContext('user.create', async () => {
*   // Any events.trackEvent() calls here will automatically capture
*   // 'operation.name': 'user.create'
*   await createUser();
*   return 'success';
* });
* ```
*/
function runInOperationContext(name, fn) {
	return operationStorage.run({ name }, fn);
}

//#endregion
//#region src/events-config.ts
/**
* Hash a string value for PII protection
*
* Uses a simple, fast hash function suitable for correlation.
* NOT cryptographically secure - use for PII masking, not security.
*/
function hashValue(value) {
	let hash = 0;
	for (let i = 0; i < value.length; i++) {
		const char = value.charCodeAt(i);
		hash = (hash << 5) - hash + char;
		hash = hash & hash;
	}
	return (hash >>> 0).toString(16).padStart(8, "0");
}

//#endregion
//#region src/event.ts
/**
* Events API for product events platforms
*
* Track user behavior, business events, and critical actions.
* Sends to product events platforms (PostHog, Mixpanel, Amplitude) via subscribers.
* For business people who think in events/funnels.
*
* For OpenTelemetry metrics (Prometheus/Grafana), use the Metrics class instead.
*
* @example Recommended: Configure subscribers in init(), use track() function
* ```typescript
* import { init, track } from 'autotel';
* import { PostHogSubscriber } from 'autotel-posthog/subscriber';
*
* init({
*   service: 'my-app',
*   subscribers: [new PostHogSubscriber({ apiKey: 'phc_...' })]
* });
*
* // Track events - uses subscribers from init()
* track('application.submitted', { jobId: '123', userId: '456' });
* ```
*
* @example Create Event instance (inherits subscribers from init)
* ```typescript
* import { Event } from 'autotel/event';
*
* // Uses subscribers configured in init()
* const event = new Event('job-application');
* event.trackEvent('application.submitted', { jobId: '123' });
* ```
*
* @example Override subscribers for specific Event instance
* ```typescript
* import { Event } from 'autotel/event';
* import { PostHogSubscriber } from 'autotel-posthog/subscriber';
*
* // Override: use different subscribers for this instance
* const event = new Event('job-application', {
*   subscribers: [new PostHogSubscriber({ apiKey: 'phc_different_project' })]
* });
*
* event.trackEvent('application.submitted', { jobId: '123' });
* ```
*/
var Event = class {
	serviceName;
	logger;
	collector;
	subscribers;
	hasSubscribers;
	circuitBreakers;
	/**
	* Create a new Event instance
	*
	* **Note**: Most users should use `init()` + `track()` instead of creating Event instances directly.
	*
	* **Subscriber Resolution**:
	* - If `subscribers` provided in options → uses those (instance override)
	* - If `subscribers` not provided → falls back to subscribers from `init()` (global config)
	* - If neither → no subscribers (events logged only)
	*
	* @param serviceName - Service name for identifying events
	* @param options - Optional configuration (logger, collector, subscribers)
	*
	* @example Recommended: Use track() with init()
	* ```typescript
	* import { init, track } from 'autotel';
	* import { PostHogSubscriber } from 'autotel-posthog/subscriber';
	*
	* init({
	*   service: 'checkout',
	*   subscribers: [new PostHogSubscriber({ apiKey: 'phc_...' })]
	* });
	*
	* track('purchase.completed', { amount: 99.99 });
	* ```
	*
	* @example Inherit subscribers from init()
	* ```typescript
	* // Uses subscribers configured in init()
	* const event = new Event('checkout');
	* event.trackEvent('purchase.completed', { amount: 99.99 });
	* ```
	*
	* @example Override subscribers for this instance
	* ```typescript
	* import { Event } from 'autotel/event';
	* import { PostHogSubscriber } from 'autotel-posthog/subscriber';
	*
	* // Override: use different subscribers for this instance only
	* const event = new Event('checkout', {
	*   subscribers: [new PostHogSubscriber({ apiKey: 'phc_different_project' })]
	* });
	* ```
	*/
	constructor(serviceName, options = {}) {
		this.serviceName = serviceName;
		this.logger = options.logger;
		this.collector = options.collector;
		this.subscribers = options.subscribers === void 0 ? getConfig()?.subscribers || [] : options.subscribers;
		this.hasSubscribers = this.subscribers.length > 0;
		this.circuitBreakers = /* @__PURE__ */ new Map();
		for (const subscriber of this.subscribers) {
			const subscriberName = subscriber.name || "Unknown";
			this.circuitBreakers.set(subscriber, new CircuitBreaker(subscriberName, {
				failureThreshold: 5,
				resetTimeout: 3e4,
				windowSize: 6e4
			}));
		}
	}
	/**
	* Automatically enrich attributes with all available telemetry context
	*
	* Auto-captures:
	* - Resource attributes: service.version, deployment.environment
	* - Trace context: traceId, spanId, correlationId
	* - Operation context: operation.name
	*/
	enrichWithTelemetryContext(attributes = {}) {
		const enriched = {
			service: this.serviceName,
			...attributes
		};
		const config = getConfig();
		if (config) {
			if (config.version) enriched["service.version"] = config.version;
			if (config.environment) {
				enriched["deployment.environment"] = config.environment;
				enriched["deployment.environment.name"] = config.environment;
			}
		}
		const spanContext = trace.getActiveSpan()?.spanContext();
		if (spanContext) {
			enriched.traceId = spanContext.traceId;
			enriched.spanId = spanContext.spanId;
			enriched.correlationId = spanContext.traceId.slice(0, 16);
		}
		const operationContext = getOperationContext();
		if (operationContext) enriched["operation.name"] = operationContext.name;
		return enriched;
	}
	/**
	* Build autotel event context for trace correlation
	*
	* Works in 4 contexts:
	* 1. Inside a span → use current span's trace_id + span_id
	* 2. Outside span but in AsyncLocalStorage context → use trace_id + correlation_id
	* 3. Totally standalone → use correlation_id + service/env/version
	* 4. Batch/fan-in (multiple linked parents) → use count + hash or full array
	*
	* @returns AutotelEventContext or undefined if trace context is disabled
	*/
	buildAutotelContext() {
		const eventsConfig = getEventsConfig();
		if (!eventsConfig?.includeTraceContext) return { correlation_id: getOrCreateCorrelationId() };
		const config = getConfig();
		const spanContext = trace.getActiveSpan()?.spanContext();
		const correlationId = getOrCreateCorrelationId();
		const autotelContext = { correlation_id: correlationId };
		if (spanContext) {
			autotelContext.trace_id = spanContext.traceId;
			autotelContext.span_id = spanContext.spanId;
			autotelContext.trace_flags = spanContext.traceFlags.toString(16).padStart(2, "0");
			const traceState = spanContext.traceState;
			if (traceState) {
				let traceStateStr = "";
				try {
					if (isFunction(traceState.serialize)) traceStateStr = traceState.serialize();
				} catch {}
				if (traceStateStr) autotelContext.trace_state = traceStateStr;
			}
			if (eventsConfig.traceUrl) {
				const traceUrl = eventsConfig.traceUrl({
					traceId: spanContext.traceId,
					spanId: spanContext.spanId,
					correlationId,
					serviceName: config?.service || this.serviceName,
					environment: config?.environment
				});
				if (traceUrl) autotelContext.trace_url = traceUrl;
			}
		} else if (eventsConfig.traceUrl && config) {
			const traceUrl = eventsConfig.traceUrl({
				correlationId,
				serviceName: config.service,
				environment: config.environment
			});
			if (traceUrl) autotelContext.trace_url = traceUrl;
		}
		return autotelContext;
	}
	/**
	* Enrich event attributes from baggage with guardrails
	*
	* @param attributes - Current event attributes
	* @returns Enriched attributes with baggage values
	*/
	enrichFromBaggage(attributes) {
		const enrichConfig = getEventsConfig()?.enrichFromBaggage;
		if (!enrichConfig) return attributes;
		const enriched = { ...attributes };
		const activeContext = context.active();
		const baggage = propagation.getBaggage(activeContext);
		if (!baggage) return enriched;
		let keyCount = 0;
		let byteCount = 0;
		const maxKeys = enrichConfig.maxKeys ?? 10;
		const maxBytes = enrichConfig.maxBytes ?? 1024;
		const prefix = enrichConfig.prefix ?? "";
		for (const [key, entry] of baggage.getAllEntries()) {
			if (!this.isBaggageKeyAllowed(key, enrichConfig)) continue;
			if (keyCount >= maxKeys) break;
			const value = entry.value;
			const transform = enrichConfig.transform?.[key];
			let transformedValue;
			if (transform === "hash") transformedValue = hashValue(value);
			else if (transform === void 0 || transform === "plain") transformedValue = value;
			else transformedValue = transform(value);
			const valueBytes = new TextEncoder().encode(transformedValue).length;
			if (byteCount + valueBytes > maxBytes) continue;
			const enrichedKey = `${prefix}${key}`;
			enriched[enrichedKey] = transformedValue;
			keyCount++;
			byteCount += valueBytes;
		}
		return enriched;
	}
	/**
	* Check if a baggage key is allowed based on config
	*/
	isBaggageKeyAllowed(key, config) {
		if (config.deny) {
			for (const pattern of config.deny) if (this.matchesBaggagePattern(key, pattern)) return false;
		}
		for (const pattern of config.allow) if (this.matchesBaggagePattern(key, pattern)) return true;
		return false;
	}
	/**
	* Check if a key matches a baggage pattern
	* Supports exact matches and wildcard patterns (e.g., 'tenant.*')
	*/
	matchesBaggagePattern(key, pattern) {
		if (pattern.endsWith(".*")) {
			const prefix = pattern.slice(0, -2);
			return key.startsWith(prefix + ".");
		}
		return key === pattern;
	}
	/**
	* Track a business event
	*
	* Use this for tracking user actions, business events, product usage:
	* - "user.signup"
	* - "order.completed"
	* - "feature.used"
	*
	* Events are sent to configured subscribers (PostHog, Mixpanel, etc.).
	*
	* @example
	* ```typescript
	* // Track user signup
	* events.trackEvent('user.signup', {
	*   userId: '123',
	*   plan: 'pro'
	* })
	*
	* // Track order
	* events.trackEvent('order.completed', {
	*   orderId: 'ord_123',
	*   amount: 99.99
	* })
	* ```
	*/
	trackEvent(eventName, attributes) {
		const validated = validateEvent(eventName, attributes, getValidationConfig() || void 0);
		const enrichedAttributes = this.enrichWithTelemetryContext(validated.attributes);
		this.logger?.info({
			event: validated.eventName,
			attributes: enrichedAttributes
		}, "Event tracked");
		this.collector?.recordEvent({
			event: validated.eventName,
			attributes: enrichedAttributes,
			service: this.serviceName,
			timestamp: Date.now()
		});
		if (this.hasSubscribers) {
			const autotelContext = this.buildAutotelContext();
			const finalAttributes = this.enrichFromBaggage(enrichedAttributes);
			this.notifySubscribers((subscriber) => subscriber.trackEvent(validated.eventName, finalAttributes, { autotel: autotelContext }));
		}
	}
	/**
	* Notify all subscribers concurrently without blocking
	* Uses circuit breakers to protect against failing subscribers
	* Uses Promise.allSettled to prevent subscriber errors from affecting other subscribers
	*/
	async notifySubscribers(fn) {
		const promises = this.subscribers.map(async (subscriber) => {
			const circuitBreaker = this.circuitBreakers.get(subscriber);
			if (!circuitBreaker) return;
			try {
				await circuitBreaker.execute(() => fn(subscriber));
			} catch (error) {
				if (error instanceof CircuitOpenError) {
					getLogger().warn({ subscriberName: subscriber.name || "Unknown" }, `[Events] ${error.message}`);
					return;
				}
				getLogger().error({
					err: error instanceof Error ? error : void 0,
					subscriberName: subscriber.name || "Unknown"
				}, `[Events] Subscriber ${subscriber.name || "Unknown"} failed`);
			}
		});
		await Promise.allSettled(promises);
	}
	/**
	* Track conversion funnel steps
	*
	* Monitor where users drop off in multi-step processes.
	*
	* @example
	* ```typescript
	* // Track signup funnel
	* events.trackFunnelStep('signup', 'started', { userId: '123' })
	* events.trackFunnelStep('signup', 'email_verified', { userId: '123' })
	* events.trackFunnelStep('signup', 'completed', { userId: '123' })
	*
	* // Track checkout flow
	* events.trackFunnelStep('checkout', 'started', { cartValue: 99.99 })
	* events.trackFunnelStep('checkout', 'payment_info', { cartValue: 99.99 })
	* events.trackFunnelStep('checkout', 'completed', { cartValue: 99.99 })
	* ```
	*/
	trackFunnelStep(funnelName, status, attributes) {
		const enrichedAttributes = this.enrichWithTelemetryContext(attributes);
		this.logger?.info({
			funnel: funnelName,
			status,
			attributes: enrichedAttributes
		}, "Funnel step tracked");
		this.collector?.recordFunnelStep({
			funnel: funnelName,
			status,
			attributes: enrichedAttributes,
			service: this.serviceName,
			timestamp: Date.now()
		});
		if (this.hasSubscribers) {
			const autotelContext = this.buildAutotelContext();
			const finalAttributes = this.enrichFromBaggage(enrichedAttributes);
			this.notifySubscribers((subscriber) => subscriber.trackFunnelStep(funnelName, status, finalAttributes, { autotel: autotelContext }));
		}
	}
	/**
	* Track outcomes (success/failure/partial)
	*
	* Monitor success rates of critical operations.
	*
	* @example
	* ```typescript
	* // Track email delivery
	* events.trackOutcome('email.delivery', 'success', {
	*   recipientType: 'user',
	*   emailType: 'welcome'
	* })
	*
	* events.trackOutcome('email.delivery', 'failure', {
	*   recipientType: 'user',
	*   errorCode: 'invalid_email'
	* })
	*
	* // Track payment processing
	* events.trackOutcome('payment.process', 'success', { amount: 99.99 })
	* events.trackOutcome('payment.process', 'failure', { error: 'insufficient_funds' })
	* ```
	*/
	trackOutcome(operationName, status, attributes) {
		const enrichedAttributes = this.enrichWithTelemetryContext(attributes);
		this.logger?.info({
			operation: operationName,
			status,
			attributes: enrichedAttributes
		}, "Outcome tracked");
		this.collector?.recordOutcome({
			operation: operationName,
			status,
			attributes: enrichedAttributes,
			service: this.serviceName,
			timestamp: Date.now()
		});
		if (this.hasSubscribers) {
			const autotelContext = this.buildAutotelContext();
			const finalAttributes = this.enrichFromBaggage(enrichedAttributes);
			this.notifySubscribers((subscriber) => subscriber.trackOutcome(operationName, status, finalAttributes, { autotel: autotelContext }));
		}
	}
	/**
	* Track value metrics
	*
	* Record numerical values like revenue, transaction amounts,
	* item counts, processing times, engagement scores, etc.
	*
	* @example
	* ```typescript
	* // Track revenue
	* events.trackValue('order.revenue', 149.99, {
	*   currency: 'USD',
	*   productCategory: 'electronics'
	* })
	*
	* // Track items per cart
	* events.trackValue('cart.item_count', 5, {
	*   userId: '123'
	* })
	*
	* // Track processing time
	* events.trackValue('api.response_time', 250, {
	*   unit: 'ms',
	*   endpoint: '/api/checkout'
	* })
	* ```
	*/
	trackValue(metricName, value, attributes) {
		const enrichedAttributes = this.enrichWithTelemetryContext({
			metric: metricName,
			...attributes
		});
		this.logger?.debug({
			metric: metricName,
			value,
			attributes: enrichedAttributes
		}, "Value tracked");
		this.collector?.recordValue({
			metric: metricName,
			value,
			attributes: enrichedAttributes,
			service: this.serviceName,
			timestamp: Date.now()
		});
		if (this.hasSubscribers) {
			const autotelContext = this.buildAutotelContext();
			const finalAttributes = this.enrichFromBaggage(enrichedAttributes);
			this.notifySubscribers((subscriber) => subscriber.trackValue(metricName, value, finalAttributes, { autotel: autotelContext }));
		}
	}
	/**
	* Flush all subscribers and wait for pending events
	*
	* Call this before shutdown to ensure all events are delivered.
	*
	* @example
	* ```typescript
	* const event =new Event('app', { subscribers: [...] });
	*
	* // Before shutdown
	* await events.flush();
	* ```
	*/
	async flush() {
		if (!this.hasSubscribers) return;
		const shutdownPromises = this.subscribers.map(async (subscriber) => {
			if (subscriber.shutdown) try {
				await subscriber.shutdown();
			} catch (error) {
				getLogger().error({
					err: error instanceof Error ? error : void 0,
					subscriberName: subscriber.name || "Unknown"
				}, `[Events] Failed to shutdown subscriber ${subscriber.name || "Unknown"}`);
			}
		});
		await Promise.allSettled(shutdownPromises);
	}
	/**
	* Shutdown the Event instance and all subscribers
	*
	* Unlike `flush()`, this method:
	* - Shuts down all subscribers
	* - Prevents further event tracking (hasSubscribers becomes false)
	* - Should only be called once at application shutdown
	*
	* @example
	* ```typescript
	* // In Next.js API route with after()
	* import { after } from 'next/server';
	*
	* export async function POST(req: Request) {
	*   const event = new Event('checkout', { subscribers: [...] });
	*   event.trackEvent('order.completed', { orderId: '123' });
	*
	*   after(async () => {
	*     await event.shutdown();
	*   });
	*
	*   return Response.json({ success: true });
	* }
	* ```
	*/
	async shutdown() {
		if (!this.hasSubscribers) return;
		await Promise.allSettled(this.subscribers.map(async (subscriber) => {
			if (subscriber.shutdown) try {
				await subscriber.shutdown();
			} catch (error) {
				getLogger().error({
					err: error instanceof Error ? error : void 0,
					subscriberName: subscriber.name || "Unknown"
				}, `[Events] Failed to shutdown subscriber ${subscriber.name || "Unknown"}`);
			}
		}));
		this.hasSubscribers = false;
	}
	/**
	* Track funnel progression with custom step names
	*
	* Unlike trackFunnelStep which uses FunnelStatus enum values,
	* this method allows any string as the step name for flexible funnel tracking.
	*
	* @param funnelName - Name of the funnel (e.g., "checkout", "onboarding")
	* @param stepName - Custom step name (e.g., "cart_viewed", "payment_entered")
	* @param stepNumber - Optional numeric position in the funnel
	* @param attributes - Optional event attributes
	*
	* @example
	* ```typescript
	* // Track custom checkout steps
	* event.trackFunnelProgression('checkout', 'cart_viewed', 1);
	* event.trackFunnelProgression('checkout', 'shipping_selected', 2);
	* event.trackFunnelProgression('checkout', 'payment_entered', 3);
	* event.trackFunnelProgression('checkout', 'order_confirmed', 4);
	* ```
	*/
	trackFunnelProgression(funnelName, stepName, stepNumber, attributes) {
		const enrichedAttributes = this.enrichWithTelemetryContext(attributes);
		this.logger?.info({
			funnel: funnelName,
			stepName,
			stepNumber,
			attributes: enrichedAttributes
		}, "Funnel progression tracked");
		const stepAttributes = {
			...enrichedAttributes,
			step_name: stepName
		};
		if (stepNumber !== void 0) stepAttributes.step_number = stepNumber;
		const stepAsStatus = stepName;
		this.collector?.recordFunnelStep({
			funnel: funnelName,
			status: stepAsStatus,
			attributes: stepAttributes,
			service: this.serviceName,
			timestamp: Date.now()
		});
		if (this.hasSubscribers) {
			const autotelContext = this.buildAutotelContext();
			const finalAttributes = this.enrichFromBaggage(enrichedAttributes);
			this.notifySubscribers(async (subscriber) => {
				await (subscriber.trackFunnelProgression ? subscriber.trackFunnelProgression(funnelName, stepName, stepNumber, finalAttributes, { autotel: autotelContext }) : subscriber.trackFunnelStep(funnelName, stepAsStatus, {
					...finalAttributes,
					...stepAttributes
				}, { autotel: autotelContext }));
			});
		}
	}
	/**
	* Track multiple events in a batch
	*
	* Useful for bulk event tracking with consistent timestamps.
	* Events are sent to subscribers individually but processed together.
	*
	* @param events - Array of events to track
	*
	* @example
	* ```typescript
	* event.trackBatch([
	*   { name: 'item.viewed', attributes: { itemId: '1' } },
	*   { name: 'item.viewed', attributes: { itemId: '2' } },
	*   { name: 'cart.updated', attributes: { itemCount: 2 } },
	* ]);
	* ```
	*/
	trackBatch(events) {
		for (const event of events) {
			const filteredAttributes = event.attributes ? definedAttributes(event.attributes) : void 0;
			this.trackEvent(event.name, filteredAttributes);
		}
	}
};
/** The attributes with the entries that carry nothing left out. */
function definedAttributes(input) {
	const defined = {};
	for (const [key, value] of Object.entries(input)) if (value !== void 0 && value !== null) defined[key] = value;
	return defined;
}
/**
* Global events instances (singleton pattern)
*/
const eventsInstances = /* @__PURE__ */ new Map();
/**
* Get or create an Events instance for a service
*
* @param serviceName - Service name for identifying events
* @param logger - Optional logger
* @returns Events instance
*
* @example
* ```typescript
* const event =getEvents('job-application')
* events.trackEvent('application.submitted', { jobId: '123' })
* ```
*/
function getEvents(serviceName, logger) {
	if (!eventsInstances.has(serviceName)) eventsInstances.set(serviceName, new Event(serviceName, { logger }));
	return eventsInstances.get(serviceName);
}
/**
* Reset all events instances (mainly for testing)
*/
function resetEvents() {
	eventsInstances.clear();
}

//#endregion
//#region src/shutdown.ts
/**
* Graceful shutdown with flush and cleanup
*/
/**
* Error codes that mean "the OTLP endpoint wasn't reachable" — expected and
* harmless when no collector is configured. Deliberately limited to
* connection-establishment failures (refused / DNS), not post-connection
* errors like ECONNRESET or ETIMEDOUT, which can indicate a real problem
* talking to a configured backend and should surface.
*/
const UNREACHABLE_ENDPOINT_CODES = /* @__PURE__ */ new Set([
	"ECONNREFUSED",
	"ENOTFOUND",
	"EAI_AGAIN"
]);
function errorCode(cause) {
	return asString(readProperty(cause, "code"));
}
/**
* A bare "Flush timeout" left you with a stack trace into bundled internals and
* nothing to act on. Name how long we waited and where we were sending, since
* the usual cause is that nothing is listening at that endpoint.
*/
function flushTimeoutMessage(timeout) {
	const endpoint = getConfig()?.endpoint;
	return `Flush timeout after ${timeout}ms${endpoint === void 0 ? "" : ` sending to ${endpoint}`}. No collector acknowledged the export; check that one is running and reachable.`;
}
/**
* True when the error (or every error it wraps) is an unreachable-endpoint
* failure. Traverses `AggregateError.errors` and the `cause` chain, since the
* SDK often wraps the underlying network error.
*/
function isUnreachableEndpointError(cause, depth = 0) {
	if (depth > 5 || asRecord(cause) === void 0) return false;
	if (cause instanceof AggregateError) return cause.errors.length > 0 && cause.errors.every((e) => isUnreachableEndpointError(e, depth + 1));
	const code = errorCode(cause);
	if (code && UNREACHABLE_ENDPOINT_CODES.has(code)) return true;
	const wrapped = readProperty(cause, "cause");
	return wrapped !== void 0 && wrapped !== cause ? isUnreachableEndpointError(wrapped, depth + 1) : false;
}
/**
* Flush all pending telemetry
*
* Flushes both events events and OpenTelemetry spans to their destinations.
* Includes timeout protection to prevent hanging in serverless environments.
*
* Safe to call multiple times.
*
* @param options - Optional configuration
* @param options.timeout - Timeout in milliseconds (default: 2000ms)
* @param options.forShutdown - If true, permanently disables the events queue after flush (used internally by shutdown())
*
* @example Manual flush in serverless
* ```typescript
* import { flush } from 'autotel';
*
* export const handler = async (event) => {
*   // ... process event
*   await flush(); // Flush before function returns
*   return result;
* };
* ```
*
* @example With custom timeout
* ```typescript
* await flush({ timeout: 5000 }); // 5 second timeout
* ```
*/
async function flush(options) {
	const timeout = options?.timeout ?? 2e3;
	const forShutdown = options?.forShutdown ?? false;
	const doFlush = async () => {
		const eventsQueue = getEventQueue();
		if (eventsQueue) await (forShutdown ? eventsQueue.shutdown() : eventsQueue.flush());
		try {
			const tracerProvider = getForceFlushableProvider(getSdk());
			if (tracerProvider) await tracerProvider.forceFlush();
		} catch {}
	};
	let timeoutHandle;
	try {
		await Promise.race([doFlush().finally(() => {
			if (timeoutHandle) clearTimeout(timeoutHandle);
		}), new Promise((_, reject) => {
			timeoutHandle = setTimeout(() => reject(new Error(flushTimeoutMessage(timeout))), timeout);
			timeoutHandle.unref();
		})]);
	} catch (error) {
		if (timeoutHandle) clearTimeout(timeoutHandle);
		getLogger().error({ err: error instanceof Error ? error : new Error(String(error)) }, "[autotel] Flush error");
		throw error;
	}
}
/**
* Shutdown telemetry and cleanup resources
*
* - Flushes all pending data
* - Shuts down OpenTelemetry SDK
* - Cleans up resources
*
* Call this before process exit.
*
* Always performs cleanup even if flush fails, preventing resource leaks
* in serverless handlers or tests.
*
* @example Express server
* ```typescript
* const server = app.listen(3000)
*
* process.on('SIGTERM', async () => {
*   await server.close()
*   await shutdown()
*   process.exit(0)
* })
* ```
*/
async function shutdown() {
	const logger = getLogger();
	let shutdownError = null;
	try {
		await flush({ forShutdown: true });
	} catch (error) {
		const err = error instanceof Error ? error : new Error(String(error));
		shutdownError = err;
		logger.error({ err }, "[autotel] Flush failed during shutdown, continuing cleanup");
	}
	try {
		const sdk = getSdk();
		if (sdk) await sdk.shutdown();
	} catch (error) {
		const err = error instanceof Error ? error : new Error(String(error));
		if (!isUnreachableEndpointError(error)) {
			if (!shutdownError) shutdownError = err;
			logger.error({ err }, "[autotel] SDK shutdown failed");
		}
	} finally {
		uninstallProcessHandlers();
		await _closeEmbeddedDevtools();
		const eventsQueue = getEventQueue();
		if (isFunction(eventsQueue?.cleanup)) eventsQueue.cleanup();
		resetEvents();
		resetMetrics();
		resetEventQueue();
	}
	if (shutdownError) throw shutdownError;
}

//#endregion
//#region src/otlp-exporters.ts
let OTLPTraceExporterPROTO;
let OTLPMetricExporterPROTO;
let OTLPLogExporterPROTO;
let OTLPTraceExporterGRPC;
let OTLPMetricExporterGRPC;
let OTLPLogExporterGRPC;
/**
* Helper: Lazy-load gRPC trace exporter
*/
function loadGRPCTraceExporter() {
	if (OTLPTraceExporterGRPC) return OTLPTraceExporterGRPC;
	try {
		OTLPTraceExporterGRPC = requireModule("@opentelemetry/exporter-trace-otlp-grpc").OTLPTraceExporter;
		return OTLPTraceExporterGRPC;
	} catch {
		throw new Error("gRPC trace exporter not found. Install @opentelemetry/exporter-trace-otlp-grpc. It is an optional peer dependency, and bundlers (Vercel, Nitro, esbuild) do not follow the lazy require that loads it, so add it as a direct dependency of your application, not just of autotel.");
	}
}
/**
* Helper: Lazy-load gRPC metric exporter
*/
function loadGRPCMetricExporter() {
	if (OTLPMetricExporterGRPC) return OTLPMetricExporterGRPC;
	try {
		OTLPMetricExporterGRPC = requireModule("@opentelemetry/exporter-metrics-otlp-grpc").OTLPMetricExporter;
		return OTLPMetricExporterGRPC;
	} catch {
		throw new Error("gRPC metric exporter not found. Install @opentelemetry/exporter-metrics-otlp-grpc. It is an optional peer dependency, and bundlers (Vercel, Nitro, esbuild) do not follow the lazy require that loads it, so add it as a direct dependency of your application, not just of autotel.");
	}
}
/**
* Helper: Lazy-load protobuf trace exporter
*/
function loadProtoTraceExporter() {
	if (OTLPTraceExporterPROTO) return OTLPTraceExporterPROTO;
	try {
		OTLPTraceExporterPROTO = requireModule("@opentelemetry/exporter-trace-otlp-proto").OTLPTraceExporter;
		return OTLPTraceExporterPROTO;
	} catch {
		throw new Error("Protobuf trace exporter not found. Install @opentelemetry/exporter-trace-otlp-proto. It is an optional peer dependency, and bundlers (Vercel, Nitro, esbuild) do not follow the lazy require that loads it, so add it as a direct dependency of your application, not just of autotel. Or drop `protocol` to use the default JSON exporter, which ships with autotel and is accepted by Grafana Cloud, Honeycomb and the other hosted OTLP gateways.");
	}
}
/**
* Helper: Create trace exporter based on protocol
*/
function createTraceExporter(protocol, config) {
	if (protocol === "grpc") return new (loadGRPCTraceExporter())(config);
	if (protocol === "http/protobuf") return new (loadProtoTraceExporter())(config);
	return new OTLPTraceExporter(config);
}
/**
* Helper: Create metric exporter based on protocol
*/
function createMetricExporter(protocol, config) {
	if (protocol === "grpc") return new (loadGRPCMetricExporter())(config);
	if (protocol === "http/protobuf") {
		if (!OTLPMetricExporterPROTO) try {
			OTLPMetricExporterPROTO = requireModule("@opentelemetry/exporter-metrics-otlp-proto").OTLPMetricExporter;
		} catch {
			throw new Error("Protobuf metric exporter not found. Install @opentelemetry/exporter-metrics-otlp-proto. It is an optional peer dependency, and bundlers (Vercel, Nitro, esbuild) do not follow the lazy require that loads it, so add it as a direct dependency of your application, not just of autotel. Or drop `protocol` to use the default JSON exporter, which ships with autotel and is accepted by Grafana Cloud, Honeycomb and the other hosted OTLP gateways.");
		}
		return new OTLPMetricExporterPROTO(config);
	}
	return new OTLPMetricExporter(config);
}
/**
* Helper: Lazy-load gRPC log exporter
*/
function loadGRPCLogExporter() {
	if (OTLPLogExporterGRPC) return OTLPLogExporterGRPC;
	try {
		OTLPLogExporterGRPC = requireModule("@opentelemetry/exporter-logs-otlp-grpc").OTLPLogExporter;
		return OTLPLogExporterGRPC;
	} catch {
		throw new Error("gRPC log exporter not found. Install @opentelemetry/exporter-logs-otlp-grpc. It is an optional peer dependency, and bundlers (Vercel, Nitro, esbuild) do not follow the lazy require that loads it, so add it as a direct dependency of your application, not just of autotel.");
	}
}
/**
* Helper: Create log exporter based on protocol
*/
function createLogExporter(protocol, config) {
	if (protocol === "grpc") return new (loadGRPCLogExporter())(config);
	if (protocol === "http/protobuf") {
		if (!OTLPLogExporterPROTO) try {
			OTLPLogExporterPROTO = requireModule("@opentelemetry/exporter-logs-otlp-proto").OTLPLogExporter;
		} catch {
			throw new Error("Protobuf log exporter not found. Install @opentelemetry/exporter-logs-otlp-proto. It is an optional peer dependency, and bundlers (Vercel, Nitro, esbuild) do not follow the lazy require that loads it, so add it as a direct dependency of your application, not just of autotel. Or drop `protocol` to use the default JSON exporter, which ships with autotel and is accepted by Grafana Cloud, Honeycomb and the other hosted OTLP gateways.");
		}
		return new OTLPLogExporterPROTO(config);
	}
	return new OTLPLogExporter(config);
}
/**
* Helper: Resolve protocol from config and environment
*/
function resolveProtocol(configProtocol) {
	if (configProtocol === "grpc" || configProtocol === "http" || configProtocol === "http/protobuf") return configProtocol;
	const envProtocol = process.env.OTEL_EXPORTER_OTLP_PROTOCOL;
	if (envProtocol === "grpc") return "grpc";
	if (envProtocol === "http/protobuf") return "http/protobuf";
	if (envProtocol === "http/json" || 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;
}

//#endregion
//#region src/config-resolution.ts
/**
* 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;
}
/**
* Read a duration-in-milliseconds environment variable, ignoring anything that
* is not a positive number so a typo falls back to the SDK default rather than
* throwing at startup.
*/
function readMillisEnv(name) {
	const raw = process.env[name]?.trim();
	if (!raw) return void 0;
	const value = Number(raw);
	return Number.isFinite(value) && value > 0 ? value : 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 ? fallbackEndpoint ? [{
		endpoint: fallbackEndpoint,
		headers: config.headers,
		protocol: config.protocol
	}] : [] : config.destinations).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;
}
/**
* Auto-detect version from package.json
*/
function detectVersion() {
	try {
		const fs = 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 requireModule("node:os").hostname();
	} catch {
		return;
	}
}

//#endregion
//#region src/auto-instrumentations.ts
/**
* 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) {
		const className = asFunction(readProperty(instrumentation, "constructor"))?.name;
		if (className) names.add(className);
	}
	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 = new Map(Object.entries({
	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"
}));
/**
* What autotel configures when the caller has not said otherwise.
*
* `instrumentation-express` opens a span per layer and runs the layer under it,
* so whatever is active inside a middleware is that layer's span - one that
* ends the moment `next()` fires. Request-wide attributes set there would land
* on `middleware - anonymous` rather than on the request span every backend
* shows as the resource. Ignoring the two leaf layer types puts them back on
* the request span and drops a pile of noise spans with them; the route rename
* (`GET /users/:id`) survives, because `rpcMetadata.route` is assigned before
* the ignore check.
*
* Pass your own `ignoreLayersType` - `[]` for the upstream behaviour - to
* override it, or `ignoreLayers` to silence only the noisy paths.
*/
const AUTOTEL_DEFAULTS = { "@opentelemetry/instrumentation-express": { ignoreLayersType: ["middleware", "request_handler"] } };
/** Apply {@link AUTOTEL_DEFAULTS} under whatever the caller already chose. */
function withAutotelDefaults(config) {
	const merged = { ...config };
	for (const [packageName, defaults] of Object.entries(AUTOTEL_DEFAULTS)) {
		const options = merged[packageName];
		if (options?.enabled === false) continue;
		merged[packageName] = {
			...defaults,
			...options
		};
	}
	return merged;
}
/**
* `express` and `@opentelemetry/instrumentation-express` name the same thing.
* `getNodeAutoInstrumentations` only answers to the full name - it
* `diag.error`s anything else and ignores the config - so the short form every
* autotel example uses has to be expanded before it gets there.
*/
function toPackageName(name) {
	return name.startsWith("@opentelemetry/instrumentation-") ? name : `@opentelemetry/instrumentation-${name}`;
}
/**
* Detect if we're running in ESM mode
*/
function isESMMode() {
	try {
		const fs = 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 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.get(className);
		if (packageName) exclusionConfig[packageName] = { enabled: false };
	}
	const config = { ...exclusionConfig };
	const requested = integrations === true ? [] : Array.isArray(integrations) ? integrations.map((name) => [name, { enabled: true }]) : Object.entries(integrations);
	for (const [name, options] of requested) {
		const packageName = toPackageName(name);
		if (packageName in exclusionConfig) continue;
		config[packageName] = options;
	}
	return getNodeAutoInstrumentations(withAutotelDefaults(config));
}

//#endregion
//#region src/init.ts
/**
* Simplified initialization for autotel
*
* Single init() function with sensible defaults.
* Replaces initInstrumentation() and separate events config.
*/
/**
* Records nothing and exports nothing, so a `TracerProvider` is still
* registered when there is no destination. See where it is used below.
*/
var NoopSpanProcessor = class {
	onStart() {}
	onEnd() {}
	forceFlush() {
		return Promise.resolve();
	}
	shutdown() {
		return Promise.resolve();
	}
};
/**
* Adapts an Autotel Sampler to the OTel SDK Sampler interface.
*/
function toOtelSampler(sampler) {
	return {
		shouldSample(_context, _traceId, spanName, _spanKind, _attributes, links) {
			const samplingContext = {
				operationName: spanName,
				args: [],
				links
			};
			const shouldTrace = sampler.shouldSample(samplingContext);
			const rate = sampler.sampleRate?.(samplingContext);
			const result = { decision: shouldTrace ? SamplingDecision.RECORD_AND_SAMPLED : SamplingDecision.NOT_RECORD };
			if (shouldTrace && rate !== void 0 && rate > 1) result.attributes = { [AUTOTEL_SAMPLING_RATE]: rate };
			return result;
		},
		toString() {
			return `AutotelSamplerAdapter`;
		}
	};
}
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;
function acceptsStringRedactor(subscriber) {
	return isFunction(readProperty(subscriber, "setStringRedactor")) ? subscriber : void 0;
}
let _optionalRequire = safeRequire;
let _devtoolsClose = null;
/**
* 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;
}
/**
* 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-posthog/subscriber';
*
* 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 = 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 = 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 canonicalLogger = mergedConfig.canonicalLogLines?.logger ?? mergedConfig.logger;
	const hasCanonicalLogger = Array.isArray(canonicalLogger) ? canonicalLogger.length > 0 : canonicalLogger !== void 0;
	const canonicalUsesOtel = mergedConfig.canonicalLogLines?.enabled === true && (mergedConfig.canonicalLogLines.otel ?? !hasCanonicalLogger);
	const logsEnabled = resolveLogsFlag(mergedConfig.logs ?? (canonicalUsesOtel ? true : "auto"));
	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 = resourceFromAttributes({
		[ATTR_SERVICE_NAME]: mergedConfig.service,
		[ATTR_SERVICE_VERSION]: version,
		"deployment.environment": environment,
		"deployment.environment.name": environment
	});
	if (hostname) resource = resource.merge(resourceFromAttributes({
		"host.name": hostname,
		"datadog.host.name": hostname
	}));
	if (mergedConfig.resource) resource = resource.merge(mergedConfig.resource);
	if (mergedConfig.resourceAttributes) resource = resource.merge(resourceFromAttributes(mergedConfig.resourceAttributes));
	const otlpDestinations = resolveOtlpDestinations(mergedConfig, endpoint);
	const configuredSpanProcessors = mergedConfig.spanProcessors !== void 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 !== void 0) spanProcessors.push(...configuredSpanProcessors);
	else if (configuredSpanExporters && configuredSpanExporters.length > 0) for (const exporter of configuredSpanExporters) spanProcessors.push(new TailSamplingSpanProcessor(new 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 TailSamplingSpanProcessor(new BatchSpanProcessor(traceExporter)));
	}
	if (mergedConfig.baggage || 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 SimpleSpanProcessor(new PrettyConsoleExporter()));
	else if (debugMode === true) spanProcessors.push(new SimpleSpanProcessor(new ConsoleSpanExporter()));
	if (mergedConfig.canonicalLogLines?.enabled) {
		const canonicalOptions = {
			logger: canonicalLogger,
			otel: mergedConfig.canonicalLogLines.otel,
			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 CanonicalLogLineProcessor(canonicalOptions));
	}
	if (mergedConfig.attributeRedactor && spanProcessors.length > 0) {
		const redactor = mergedConfig.attributeRedactor;
		spanProcessors = spanProcessors.map((processor) => new AttributeRedactingProcessor(processor, { redactor }));
	}
	if (mergedConfig.attributeRedactor) _stringRedactor = createStringRedactor(mergedConfig.attributeRedactor);
	if (_stringRedactor && mergedConfig.subscribers) for (const subscriber of mergedConfig.subscribers) acceptsStringRedactor(subscriber)?.setStringRedactor(_stringRedactor);
	if (mergedConfig.spanNameNormalizer && spanProcessors.length > 0) spanProcessors = spanProcessors.map((processor) => new SpanNameNormalizingProcessor(processor, { normalizer: mergedConfig.spanNameNormalizer }));
	if (mergedConfig.policies) {
		if (typeof mergedConfig.policies === "string") watchPolicyFile(mergedConfig.policies);
		else setPolicies(mergedConfig.policies);
		const userSpanFilter = mergedConfig.spanFilter;
		mergedConfig.spanFilter = userSpanFilter ? (span) => userSpanFilter(span) && policySpanFilter(span) : policySpanFilter;
	}
	if (mergedConfig.spanFilter && spanProcessors.length > 0) spanProcessors = spanProcessors.map((processor) => new FilteringSpanProcessor(processor, { filter: mergedConfig.spanFilter }));
	if (mergedConfig.spanEnrichers && mergedConfig.spanEnrichers.length > 0) spanProcessors.unshift(...mergedConfig.spanEnrichers);
	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
		});
		const exportIntervalMillis = readMillisEnv("OTEL_METRIC_EXPORT_INTERVAL");
		const exportTimeoutMillis = readMillisEnv("OTEL_METRIC_EXPORT_TIMEOUT");
		const readerOptions = { exporter: metricExporter };
		if (exportIntervalMillis !== void 0) readerOptions.exportIntervalMillis = exportIntervalMillis;
		if (exportTimeoutMillis !== void 0) readerOptions.exportTimeoutMillis = exportTimeoutMillis;
		metricReaders.push(new PeriodicExportingMetricReader(readerOptions));
	}
	let logRecordProcessors;
	if (configuredLogRecordProcessors && configuredLogRecordProcessors.length > 0) logRecordProcessors = [...configuredLogRecordProcessors];
	if (logsEnabled) {
		for (const destination of otlpDestinations) {
			if (!destinationSupportsSignal(destination, "logs")) continue;
			let processor = new BatchLogRecordProcessor({ exporter: 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");
	}
	if (mergedConfig.policies && logRecordProcessors) logRecordProcessors = logRecordProcessors.map((processor) => new PolicyLogRecordProcessor(processor));
	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 ? resolveSamplingPreset(mergedConfig.sampling) : void 0);
	if (autotelSampler) mergedConfig.sampler = autotelSampler;
	const sampler = autotelSampler ? toOtelSampler(autotelSampler) : envConfig.otelSampler ?? toOtelSampler(samplingPresets.production());
	const sdkOptions = {
		resource,
		serviceName: mergedConfig.service,
		sampler,
		instrumentations: finalInstrumentations
	};
	sdkOptions.spanProcessors = spanProcessors.length > 0 ? spanProcessors : [new NoopSpanProcessor()];
	if (metricReaders.length > 0) sdkOptions.metricReaders = metricReaders;
	if (logRecordProcessors && logRecordProcessors.length > 0) sdkOptions.logRecordProcessors = logRecordProcessors;
	else if (!process.env.OTEL_LOGS_EXPORTER) sdkOptions.logRecordProcessors = [];
	sdk = mergedConfig.sdkFactory ? mergedConfig.sdkFactory(sdkOptions) : new 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 };
			const getTracerProvider = asFunction(readProperty(sdk, "getTracerProvider"));
			if (getTracerProvider) initOptions.tracerProvider = getTracerProvider.call(sdk);
			if (configuredSpanExporters?.[0]) initOptions.exporter = configuredSpanExporters[0];
			if (isFunction(traceloop.initialize)) {
				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;
	const processHandlers = mergedConfig.processHandlers;
	const handlersConfig = processHandlers && processHandlers !== true ? processHandlers : {};
	if (processHandlers) installProcessHandlers(handlersConfig, shutdown);
	else uninstallProcessHandlers();
	if (mergedConfig.flushOnExit !== false) {
		const timeoutMs = handlersConfig.shutdownTimeoutMs;
		installExitFlush(() => flush({
			forShutdown: true,
			timeout: timeoutMs
		}), timeoutMs);
	}
}
/**
* 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;
	}
}
/**
* The sampler `init()` was configured with, or undefined when it was not.
*
* Separate from {@link getDefaultSampler} on purpose: the tracing wrapper needs
* to know whether a sampler was *chosen*. Falling back to the production preset
* there would start dropping spans for every app that never asked to sample.
*
* @internal
*/
function getConfiguredSampler() {
	return config?.sampler;
}
/**
* @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
export { getCorrelationStorage as $, FeatureFlagSampler as A, markForceKept as B, AUTOTEL_SAMPLING_RATE as C, AlwaysSampler as D, AdaptiveSampler as E, createLinkFromHeaders as F, getActiveContextWithBaggage as G, samplingPresets as H, debugCaptureRequested as I, getEventQueue as J, getContextStorage as K, extractLinksFromBatch as L, NeverSampler as M, RandomSampler as N, CompositeSampler as O, UserIdSampler as P, getCorrelationId as Q, hashUnitInterval as R, AUTOTEL_DEBUG_BAGGAGE_KEY as S, AUTOTEL_SAMPLING_TAIL_KEEP as T, createTraceContext as U, resolveSamplingPreset as V, defineBaggageSchema as W, CORRELATION_ID_BAGGAGE_KEY as X, track as Y, generateCorrelationId as Z, loadYamlConfig as _, init as a, BaggageSpanProcessor as b, lockLogger as c, Event as d, getOrCreateCorrelationId as et, getEvents as f, hasYamlConfig as g, runInOperationContext as h, getSdk as i, KeyTargetRateSampler as j, DeterministicSampler as k, flush as l, getOperationContext as m, getConfiguredSampler as n, setCorrelationId as nt, isInitialized as o, resetEvents as p, hasExplicitSpanStatus as q, getLogger as r, setCorrelationIdInBaggage as rt, isLoggerLocked as s, getConfig as t, runWithCorrelationId as tt, shutdown as u, loadYamlConfigFromFile as v, AUTOTEL_SAMPLING_TAIL_EVALUATED as w, TailSamplingSpanProcessor as x, createStringRedactor as y, isForceKept as z };