UNPKG

autotel

Version:
4,655 lines 162 kB
const require_rolldown_runtime = require('./rolldown-runtime-C_NdSu1c.cjs');
const require_values = require('./values-tm4bi0FF.cjs');
const require_tracer_provider = require('./tracer-provider.cjs');
const require_config = require('./config.cjs');
const require_structured_error = require('./structured-error-BAXhglRE.cjs');
const require_node_require = require('./node-require-B_IVapV3.cjs');
const require_filtering_span_processor = require('./filtering-span-processor.cjs');
const require_policy = require('./policy.cjs');
const require_span_name_normalizer = require('./span-name-normalizer.cjs');
const require_attribute_redacting_processor = require('./attribute-redacting-processor.cjs');
const require_pretty_console_exporter = require('./pretty-console-exporter-CMzlrRNg.cjs');
const require_canonical_log_line_processor = require('./canonical-log-line-processor-Bvgb1lE4.cjs');
const require_metric = require('./metric.cjs');
let _opentelemetry_sdk_node = require("@opentelemetry/sdk-node");
let _opentelemetry_sdk_trace_base = require("@opentelemetry/sdk-trace-base");
let _opentelemetry_resources = require("@opentelemetry/resources");
let _opentelemetry_semantic_conventions = require("@opentelemetry/semantic-conventions");
let _opentelemetry_api = require("@opentelemetry/api");
let node_async_hooks = require("node:async_hooks");
node_async_hooks = require_rolldown_runtime.__toESM(node_async_hooks, 1);
let _opentelemetry_sdk_metrics = require("@opentelemetry/sdk-metrics");
let _opentelemetry_sdk_logs = require("@opentelemetry/sdk-logs");
let node_fs = require("node:fs");
node_fs = require_rolldown_runtime.__toESM(node_fs, 1);
let node_path = require("node:path");
node_path = require_rolldown_runtime.__toESM(node_path, 1);
let node_os = require("node:os");
let _opentelemetry_exporter_metrics_otlp_http = require("@opentelemetry/exporter-metrics-otlp-http");
let _opentelemetry_exporter_trace_otlp_http = require("@opentelemetry/exporter-trace-otlp-http");
let _opentelemetry_exporter_logs_otlp_http = require("@opentelemetry/exporter-logs-otlp-http");

//#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 node_async_hooks.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 = _opentelemetry_api.context.active();
	const baggageEntry = _opentelemetry_api.propagation.getBaggage(activeContext)?.getEntry(CORRELATION_ID_BAGGAGE_KEY);
	if (baggageEntry?.value) return baggageEntry.value;
	const span = _opentelemetry_api.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 = _opentelemetry_api.context.active();
	let baggage = _opentelemetry_api.propagation.getBaggage(activeContext) ?? _opentelemetry_api.propagation.createBaggage();
	baggage = baggage.setEntry(CORRELATION_ID_BAGGAGE_KEY, { value: correlationId });
	return _opentelemetry_api.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 = require_config.getConfig().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 (require_values.asString(eventName) === void 0) throw new ValidationError(`Event name must be a string, got ${require_values.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 (!require_values.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 require_values.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 = require_values.asString(value);
	if (text !== void 0) return text.length > config.maxAttributeValueLength ? text.slice(0, config.maxAttributeValueLength) + "..." : text;
	const scalar = require_values.asNumber(value) ?? require_values.asBoolean(value);
	if (scalar !== void 0) return scalar;
	if (Array.isArray(value)) return value.map((item) => sanitizeValue(item, config, depth + 1));
	const record = require_values.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 `[${require_values.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 = _opentelemetry_api.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 node_async_hooks.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 = _opentelemetry_api.context.active();
	const stored = contextStorage.getStore()?.value;
	if (!stored) return activeContext;
	const storedBaggage = _opentelemetry_api.propagation.getBaggage(stored);
	return storedBaggage ? _opentelemetry_api.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 = _opentelemetry_api.context._getContextManager?.();
	if (!manager) return;
	const asyncLocal = manager._asyncLocalStorage ?? void 0;
	if (asyncLocal?.enterWith) {
		asyncLocal.enterWith(newContext);
		return;
	}
	if (require_values.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 = _opentelemetry_api.context.active();
		enterOrRun(contextStorage, activeContext);
	}
	const traceCtx = {
		traceId: spanContext.traceId,
		spanId: spanContext.spanId,
		correlationId: spanContext.traceId.slice(0, 16),
		setAttribute: (key, value) => {
			const attr = require_structured_error.toAttributeValue(value);
			if (attr === void 0) {
				span.setAttributes(require_structured_error.flattenToAttributes({ [key]: value }));
				return;
			}
			span.setAttribute(key, attr);
		},
		setAttributes: (attrs) => {
			span.setAttributes(require_structured_error.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));
			require_structured_error.recordStructuredError(traceCtx, err);
		},
		track: (event, data) => {
			track(event, data);
		},
		getBaggage(key) {
			const activeCtx = _opentelemetry_api.context.active();
			let baggage = _opentelemetry_api.propagation.getBaggage(activeCtx);
			if (!baggage) {
				const storedContext = contextStorage.getStore()?.value;
				if (storedContext) baggage = _opentelemetry_api.propagation.getBaggage(storedContext);
			}
			return baggage?.getEntry(key)?.value;
		},
		setBaggage(key, value) {
			const currentContext = getActiveContextWithBaggage();
			const updated = (_opentelemetry_api.propagation.getBaggage(currentContext) ?? _opentelemetry_api.propagation.createBaggage()).setEntry(key, { value });
			updateActiveContext(_opentelemetry_api.propagation.setBaggage(currentContext, updated));
			return value;
		},
		deleteBaggage(key) {
			const currentContext = getActiveContextWithBaggage();
			const baggage = _opentelemetry_api.propagation.getBaggage(currentContext);
			if (baggage) {
				const updated = baggage.removeEntry(key);
				updateActiveContext(_opentelemetry_api.propagation.setBaggage(currentContext, updated));
			}
		},
		getAllBaggage() {
			const activeCtx = _opentelemetry_api.context.active();
			let baggage = _opentelemetry_api.propagation.getBaggage(activeCtx);
			if (!baggage) {
				const storedContext = contextStorage.getStore()?.value;
				if (storedContext) baggage = _opentelemetry_api.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 = _opentelemetry_api.context.active();
			let baggage = _opentelemetry_api.propagation.getBaggage(activeCtx);
			if (!baggage) {
				const storedContext = contextStorage.getStore()?.value;
				if (storedContext) baggage = _opentelemetry_api.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 = _opentelemetry_api.propagation.getBaggage(currentContext) ?? _opentelemetry_api.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(_opentelemetry_api.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 = _opentelemetry_api.context.active();
			let baggage = _opentelemetry_api.propagation.getBaggage(currentContext) ?? _opentelemetry_api.propagation.createBaggage();
			for (const [key, val] of Object.entries(flatBaggage)) baggage = baggage.setEntry(key, { value: val });
			const newContext = _opentelemetry_api.propagation.setBaggage(currentContext, baggage);
			return _opentelemetry_api.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 & _opentelemetry_api.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 = require_values.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 = _opentelemetry_api.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 = require_values.asString(logRecord.body);
		if (body) logRecord.body = this.redact(body);
		if (logRecord.attributes) for (const [key, value] of Object.entries(logRecord.attributes)) {
			const text = require_values.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 = require_values.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 = require_node_require.safeRequire("@opentelemetry/sdk-logs");
	const exporterModule = require_node_require.safeRequire("@opentelemetry/exporter-logs-otlp-http");
	if (!sdkLogs || !exporterModule) return [];
	const exporter = new exporterModule.OTLPLogExporter({ url });
	let processor = new sdkLogs.BatchLogRecordProcessor({ exporter });
	if (stringRedactor) processor = new RedactingLogRecordProcessor(processor, stringRedactor);
	return [processor];
}

//#endregion
//#region src/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 = _opentelemetry_api.propagation.getBaggage(parentContext);
		if (!baggage) baggage = _opentelemetry_api.propagation.getBaggage(_opentelemetry_api.context.active());
		if (!baggage) try {
			const { getActiveContextWithBaggage } = require_node_require.requireModule("./trace-context");
			const storedContext = getActiveContextWithBaggage();
			baggage = _opentelemetry_api.propagation.getBaggage(storedContext);
		} catch {}
		if (!baggage) return;
		for (const [key, entry] of baggage.getAllEntries()) span.setAttribute(`${this.prefix}${key}`, entry.value);
	}
	onEnd(_span) {}
	async shutdown() {}
	async forceFlush() {}
};

//#endregion
//#region src/redact-values.ts
/** Standalone string redaction for use outside the span processor pipeline. */
function createStringRedactor(config) {
	const resolved = typeof config === "string" ? require_attribute_redacting_processor.REDACTOR_PRESETS[config] : config;
	const valuePatterns = resolved.valuePatterns ?? [];
	const defaultReplacement = resolved.replacement ?? "[REDACTED]";
	return (value) => {
		let result = value;
		for (const { pattern, replacement, mask } of valuePatterns) {
			pattern.lastIndex = 0;
			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 _opentelemetry_sdk_trace_base.AlwaysOnSampler();
		case "always_off":
			warnOnUnusedSamplerArg(samplerName, env.OTEL_TRACES_SAMPLER_ARG);
			return new _opentelemetry_sdk_trace_base.AlwaysOffSampler();
		case "traceidratio": return new _opentelemetry_sdk_trace_base.TraceIdRatioBasedSampler(parseRatioSamplerArg(samplerName, env.OTEL_TRACES_SAMPLER_ARG));
		case "parentbased_always_on":
			warnOnUnusedSamplerArg(samplerName, env.OTEL_TRACES_SAMPLER_ARG);
			return new _opentelemetry_sdk_trace_base.ParentBasedSampler({ root: new _opentelemetry_sdk_trace_base.AlwaysOnSampler() });
		case "parentbased_always_off":
			warnOnUnusedSamplerArg(samplerName, env.OTEL_TRACES_SAMPLER_ARG);
			return new _opentelemetry_sdk_trace_base.ParentBasedSampler({ root: new _opentelemetry_sdk_trace_base.AlwaysOffSampler() });
		case "parentbased_traceidratio": return new _opentelemetry_sdk_trace_base.ParentBasedSampler({ root: new _opentelemetry_sdk_trace_base.TraceIdRatioBasedSampler(parseRatioSamplerArg(samplerName, env.OTEL_TRACES_SAMPLER_ARG)) });
		case "jaeger_remote":
		case "parentbased_jaeger_remote":
		case "xray":
			console.error(`[autotel] OTEL_TRACES_SAMPLER="${samplerName}" is not supported yet by autotel. Falling back to the next sampler source.`);
			return;
		default:
			console.error(`[autotel] Unknown OTEL_TRACES_SAMPLER="${samplerName}". Falling back to the next sampler source.`);
			return;
	}
}
/**
* Parse OTEL_RESOURCE_ATTRIBUTES from comma-separated key=value pairs
* Example: "service.version=1.0.0,deployment.environment=production"
*/
function parseResourceAttributes(input) {
	if (!input || input.trim() === "") return {};
	const attributes = {};
	const pairs = input.split(",");
	for (const pair of pairs) {
		const trimmedPair = pair.trim();
		if (!trimmedPair) continue;
		const equalIndex = trimmedPair.indexOf("=");
		if (equalIndex === -1) continue;
		const key = trimmedPair.slice(0, equalIndex).trim();
		const value = trimmedPair.slice(equalIndex + 1).trim();
		if (key && value) attributes[key] = value;
	}
	return attributes;
}
/**
* Parse OTEL_EXPORTER_OTLP_HEADERS from comma-separated key=value pairs
* Example: "api-key=secret123,x-custom-header=value"
*/
function parseOtlpHeaders(input) {
	if (!input || input.trim() === "") return {};
	const headers = {};
	const pairs = input.split(",");
	for (const pair of pairs) {
		const trimmedPair = pair.trim();
		if (!trimmedPair) continue;
		const equalIndex = trimmedPair.indexOf("=");
		if (equalIndex === -1) continue;
		const key = trimmedPair.slice(0, equalIndex).trim();
		const value = trimmedPair.slice(equalIndex + 1).trim();
		if (key && value) headers[key] = value;
	}
	return headers;
}
/**
* Convert resolved environment variables to config
*/
function envToConfig(env) {
	const config = {};
	if (env.OTEL_SERVICE_NAME) config.service = env.OTEL_SERVICE_NAME;
	if (env.OTEL_EXPORTER_OTLP_ENDPOINT) config.endpoint = env.OTEL_EXPORTER_OTLP_ENDPOINT;
	if (env.OTEL_EXPORTER_OTLP_PROTOCOL) config.protocol = env.OTEL_EXPORTER_OTLP_PROTOCOL;
	if (env.OTEL_EXPORTER_OTLP_HEADERS) config.headers = parseOtlpHeaders(env.OTEL_EXPORTER_OTLP_HEADERS);
	const resourceAttrs = parseResourceAttributes(env.OTEL_RESOURCE_ATTRIBUTES);
	if (Object.keys(resourceAttrs).length > 0) config.resourceAttributes = resourceAttrs;
	const sampler = createSamplerFromEnv(env);
	if (sampler) config.otelSampler = sampler;
	return config;
}
/**
* Main function to resolve config from environment variables
*/
function resolveConfigFromEnv() {
	return envToConfig(resolveOtelEnv());
}

//#endregion
//#region src/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 require_node_require.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 = require_values.asString(value);
	return text === void 0 ? value : substituteEnvVars(text);
}
/** The mapping a YAML value is, when it is one. */
function asYamlMapping(value) {
	return require_values.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 = node_path.default.resolve(envPath);
		if (node_fs.existsSync(resolved)) return resolved;
		console.warn(`[autotel] Config file not found: ${envPath}`);
		return null;
	}
	const conventionPath = node_path.default.resolve(process.cwd(), "autotel.yaml");
	if (node_fs.existsSync(conventionPath)) return conventionPath;
	const altPath = node_path.default.resolve(process.cwd(), "autotel.yml");
	if (node_fs.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(node_fs.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 = node_path.default.resolve(filePath);
	return yamlToAutotelConfig(parseYamlConfig(node_fs.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 + node_os.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 node_async_hooks.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 = _opentelemetry_api.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 = _opentelemetry_api.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 (require_values.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 = _opentelemetry_api.context.active();
		const baggage = _opentelemetry_api.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 require_values.asString(require_values.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 || require_values.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 = require_values.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 = require_tracer_provider.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 (require_values.isFunction(eventsQueue?.cleanup)) eventsQueue.cleanup();
		resetEvents();
		require_metric.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 = require_node_require.requireModule("@opentelemetry/exporter-trace-otlp-grpc").OTLPTraceExporter;
		return OTLPTraceExporterGRPC;
	} catch {
		throw new Error("gRPC trace exporter not found. Install @opentelemetry/exporter-trace-otlp-grpc. 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 = require_node_require.requireModule("@opentelemetry/exporter-metrics-otlp-grpc").OTLPMetricExporter;
		return OTLPMetricExporterGRPC;
	} catch {
		throw new Error("gRPC metric exporter not found. Install @opentelemetry/exporter-metrics-otlp-grpc. 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 = require_node_require.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 _opentelemetry_exporter_trace_otlp_http.OTLPTraceExporter(config);
}
/**
* Helper: Create metric exporter based on protocol
*/
function createMetricExporter(protocol, config) {
	if (protocol === "grpc") return new (loadGRPCMetricExporter())(config);
	if (protocol === "http/protobuf") {
		if (!OTLPMetricExporterPROTO) try {
			OTLPMetricExporterPROTO = require_node_require.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 _opentelemetry_exporter_metrics_otlp_http.OTLPMetricExporter(config);
}
/**
* Helper: Lazy-load gRPC log exporter
*/
function loadGRPCLogExporter() {
	if (OTLPLogExporterGRPC) return OTLPLogExporterGRPC;
	try {
		OTLPLogExporterGRPC = require_node_require.requireModule("@opentelemetry/exporter-logs-otlp-grpc").OTLPLogExporter;
		return OTLPLogExporterGRPC;
	} catch {
		throw new Error("gRPC log exporter not found. Install @opentelemetry/exporter-logs-otlp-grpc. 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 = require_node_require.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 _opentelemetry_exporter_logs_otlp_http.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 = require_node_require.requireModule("node:fs");
		return JSON.parse(fs.readFileSync(`${process.cwd()}/package.json`, "utf8")).version || "1.0.0";
	} catch {
		return "1.0.0";
	}
}
/**
* Detect hostname for resource attributes.
* Supports Datadog conventions (DD_HOSTNAME) and falls back to system hostname.
*
* Priority order:
* 1. DD_HOSTNAME environment variable (Datadog convention)
* 2. HOSTNAME environment variable (common Unix convention)
* 3. os.hostname() (system hostname)
*
* @returns hostname string or undefined if detection fails
*/
function detectHostname() {
	if (process.env.DD_HOSTNAME) return process.env.DD_HOSTNAME;
	if (process.env.HOSTNAME) return process.env.HOSTNAME;
	try {
		return require_node_require.requireModule("node:os").hostname();
	} catch {
		return;
	}
}

//#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 = require_values.asFunction(require_values.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 = require_node_require.requireModule("node:fs");
		try {
			return JSON.parse(fs.readFileSync(`${process.cwd()}/package.json`, "utf8")).type === "module";
		} catch {
			return false;
		}
	} catch {
		return false;
	}
}
/**
* Lazy-load auto-instrumentations (optional peer dependency)
* Only loads when integrations config is truthy, avoiding ~40+ package imports at startup.
*/
function loadNodeAutoInstrumentations() {
	try {
		return require_node_require.requireModule("@opentelemetry/auto-instrumentations-node").getNodeAutoInstrumentations;
	} catch {
		const isESM = isESMMode();
		const baseMessage = "@opentelemetry/auto-instrumentations-node not found.";
		if (isESM) throw new Error(`${baseMessage}\n\nESM Setup Required:
1. Install as a direct dependency: pnpm add @opentelemetry/auto-instrumentations-node
2. Create instrumentation.mjs with:
   import 'autotel/register';  // MUST be first!
   import { init } from 'autotel';
   import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
   init({ service: "my-app", instrumentations: getNodeAutoInstrumentations() });
3. Run with: tsx --import ./instrumentation.mjs src/index.ts

See: https://github.com/jagreehal/autotel#esm-setup`);
		throw new Error(`${baseMessage} Install it: pnpm add @opentelemetry/auto-instrumentations-node`);
	}
}
/**
* Injectable loader for testing. Set to override the default loader.
* @internal
*/
let _autoInstrumentationsLoader = null;
/**
* Get auto-instrumentations based on simple integration names
* Excludes instrumentations that are manually provided to avoid conflicts
*/
function getAutoInstrumentations(integrations, manualInstrumentationNames = /* @__PURE__ */ new Set()) {
	if (integrations === false) return [];
	const getNodeAutoInstrumentations = _autoInstrumentationsLoader ? _autoInstrumentationsLoader() : loadNodeAutoInstrumentations();
	const exclusionConfig = {};
	for (const className of manualInstrumentationNames) {
		const packageName = INSTRUMENTATION_CLASS_TO_PACKAGE.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 ? _opentelemetry_sdk_trace_base.SamplingDecision.RECORD_AND_SAMPLED : _opentelemetry_sdk_trace_base.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 require_values.isFunction(require_values.readProperty(subscriber, "setStringRedactor")) ? subscriber : void 0;
}
let _optionalRequire = require_node_require.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 = require_attribute_redacting_processor.normalizeAttributeRedactorConfig(resolvedRedactor);
		if (!normalizedRedactor) throw new Error("Invalid attributeRedactor config");
		mergedConfig.attributeRedactor = normalizedRedactor;
	}
	const devtoolsConfig = resolveDevtoolsConfig(mergedConfig.devtools);
	if (devtoolsConfig.enabled && mergedConfig.logs === void 0) mergedConfig.logs = true;
	const silent = mergedConfig.silent ?? false;
	const minLevel = mergedConfig.minLevel ?? "info";
	logger = wrapLogger(mergedConfig.logger || silentLogger, silent, minLevel);
	if (initialized) logger.warn({}, "[autotel] init() called again - last config wins. This may cause unexpected behavior.");
	config = mergedConfig;
	validationConfig = mergedConfig.validation || null;
	eventsConfig = mergedConfig.events || null;
	let endpoint = mergedConfig.endpoint ?? devtoolsConfig.endpoint;
	const version = mergedConfig.version || detectVersion();
	const environment = mergedConfig.environment || process.env.NODE_ENV || "development";
	const metricsEnabled = resolveMetricsFlag(mergedConfig.metrics);
	const 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 = (0, _opentelemetry_resources.resourceFromAttributes)({
		[_opentelemetry_semantic_conventions.ATTR_SERVICE_NAME]: mergedConfig.service,
		[_opentelemetry_semantic_conventions.ATTR_SERVICE_VERSION]: version,
		"deployment.environment": environment,
		"deployment.environment.name": environment
	});
	if (hostname) resource = resource.merge((0, _opentelemetry_resources.resourceFromAttributes)({
		"host.name": hostname,
		"datadog.host.name": hostname
	}));
	if (mergedConfig.resource) resource = resource.merge(mergedConfig.resource);
	if (mergedConfig.resourceAttributes) resource = resource.merge((0, _opentelemetry_resources.resourceFromAttributes)(mergedConfig.resourceAttributes));
	const otlpDestinations = resolveOtlpDestinations(mergedConfig, endpoint);
	const configuredSpanProcessors = mergedConfig.spanProcessors !== 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 _opentelemetry_sdk_trace_base.BatchSpanProcessor(exporter)));
	else for (const destination of otlpDestinations) {
		if (!destinationSupportsSignal(destination, "traces")) continue;
		const traceExporter = createTraceExporter(destination.protocol, {
			url: formatEndpointUrl(destination.endpoint, "traces", destination.protocol),
			headers: destination.headers
		});
		spanProcessors.push(new TailSamplingSpanProcessor(new _opentelemetry_sdk_trace_base.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 _opentelemetry_sdk_trace_base.SimpleSpanProcessor(new require_pretty_console_exporter.PrettyConsoleExporter()));
	else if (debugMode === true) spanProcessors.push(new _opentelemetry_sdk_trace_base.SimpleSpanProcessor(new _opentelemetry_sdk_trace_base.ConsoleSpanExporter()));
	if (mergedConfig.canonicalLogLines?.enabled) {
		const canonicalOptions = {
			logger: 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 require_canonical_log_line_processor.CanonicalLogLineProcessor(canonicalOptions));
	}
	if (mergedConfig.attributeRedactor && spanProcessors.length > 0) {
		const redactor = mergedConfig.attributeRedactor;
		spanProcessors = spanProcessors.map((processor) => new require_attribute_redacting_processor.AttributeRedactingProcessor(processor, { redactor }));
	}
	if (mergedConfig.attributeRedactor) _stringRedactor = createStringRedactor(mergedConfig.attributeRedactor);
	if (_stringRedactor && mergedConfig.subscribers) for (const subscriber of mergedConfig.subscribers) acceptsStringRedactor(subscriber)?.setStringRedactor(_stringRedactor);
	if (mergedConfig.spanNameNormalizer && spanProcessors.length > 0) spanProcessors = spanProcessors.map((processor) => new require_span_name_normalizer.SpanNameNormalizingProcessor(processor, { normalizer: mergedConfig.spanNameNormalizer }));
	if (mergedConfig.policies) {
		if (typeof mergedConfig.policies === "string") require_policy.watchPolicyFile(mergedConfig.policies);
		else require_policy.setPolicies(mergedConfig.policies);
		const userSpanFilter = mergedConfig.spanFilter;
		mergedConfig.spanFilter = userSpanFilter ? (span) => userSpanFilter(span) && require_policy.policySpanFilter(span) : require_policy.policySpanFilter;
	}
	if (mergedConfig.spanFilter && spanProcessors.length > 0) spanProcessors = spanProcessors.map((processor) => new require_filtering_span_processor.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 _opentelemetry_sdk_metrics.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 _opentelemetry_sdk_logs.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 require_policy.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 _opentelemetry_sdk_node.NodeSDK(sdkOptions);
	if (!sdk) throw new Error("[autotel] sdkFactory must return a NodeSDK instance");
	sdk.start();
	if (mergedConfig.openllmetry?.enabled) {
		const traceloop = _optionalRequire("@traceloop/node-server-sdk");
		if (traceloop) {
			const initOptions = { ...mergedConfig.openllmetry.options };
			const getTracerProvider = require_values.asFunction(require_values.readProperty(sdk, "getTracerProvider"));
			if (getTracerProvider) initOptions.tracerProvider = getTracerProvider.call(sdk);
			if (configuredSpanExporters?.[0]) initOptions.exporter = configuredSpanExporters[0];
			if (require_values.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
Object.defineProperty(exports, 'AUTOTEL_DEBUG_BAGGAGE_KEY', {
  enumerable: true,
  get: function () {
    return AUTOTEL_DEBUG_BAGGAGE_KEY;
  }
});
Object.defineProperty(exports, 'AUTOTEL_SAMPLING_RATE', {
  enumerable: true,
  get: function () {
    return AUTOTEL_SAMPLING_RATE;
  }
});
Object.defineProperty(exports, 'AUTOTEL_SAMPLING_TAIL_EVALUATED', {
  enumerable: true,
  get: function () {
    return AUTOTEL_SAMPLING_TAIL_EVALUATED;
  }
});
Object.defineProperty(exports, 'AUTOTEL_SAMPLING_TAIL_KEEP', {
  enumerable: true,
  get: function () {
    return AUTOTEL_SAMPLING_TAIL_KEEP;
  }
});
Object.defineProperty(exports, 'AdaptiveSampler', {
  enumerable: true,
  get: function () {
    return AdaptiveSampler;
  }
});
Object.defineProperty(exports, 'AlwaysSampler', {
  enumerable: true,
  get: function () {
    return AlwaysSampler;
  }
});
Object.defineProperty(exports, 'BaggageSpanProcessor', {
  enumerable: true,
  get: function () {
    return BaggageSpanProcessor;
  }
});
Object.defineProperty(exports, 'CORRELATION_ID_BAGGAGE_KEY', {
  enumerable: true,
  get: function () {
    return CORRELATION_ID_BAGGAGE_KEY;
  }
});
Object.defineProperty(exports, 'CompositeSampler', {
  enumerable: true,
  get: function () {
    return CompositeSampler;
  }
});
Object.defineProperty(exports, 'DeterministicSampler', {
  enumerable: true,
  get: function () {
    return DeterministicSampler;
  }
});
Object.defineProperty(exports, 'Event', {
  enumerable: true,
  get: function () {
    return Event;
  }
});
Object.defineProperty(exports, 'FeatureFlagSampler', {
  enumerable: true,
  get: function () {
    return FeatureFlagSampler;
  }
});
Object.defineProperty(exports, 'KeyTargetRateSampler', {
  enumerable: true,
  get: function () {
    return KeyTargetRateSampler;
  }
});
Object.defineProperty(exports, 'NeverSampler', {
  enumerable: true,
  get: function () {
    return NeverSampler;
  }
});
Object.defineProperty(exports, 'RandomSampler', {
  enumerable: true,
  get: function () {
    return RandomSampler;
  }
});
Object.defineProperty(exports, 'TailSamplingSpanProcessor', {
  enumerable: true,
  get: function () {
    return TailSamplingSpanProcessor;
  }
});
Object.defineProperty(exports, 'UserIdSampler', {
  enumerable: true,
  get: function () {
    return UserIdSampler;
  }
});
Object.defineProperty(exports, 'createLinkFromHeaders', {
  enumerable: true,
  get: function () {
    return createLinkFromHeaders;
  }
});
Object.defineProperty(exports, 'createStringRedactor', {
  enumerable: true,
  get: function () {
    return createStringRedactor;
  }
});
Object.defineProperty(exports, 'createTraceContext', {
  enumerable: true,
  get: function () {
    return createTraceContext;
  }
});
Object.defineProperty(exports, 'debugCaptureRequested', {
  enumerable: true,
  get: function () {
    return debugCaptureRequested;
  }
});
Object.defineProperty(exports, 'defineBaggageSchema', {
  enumerable: true,
  get: function () {
    return defineBaggageSchema;
  }
});
Object.defineProperty(exports, 'extractLinksFromBatch', {
  enumerable: true,
  get: function () {
    return extractLinksFromBatch;
  }
});
Object.defineProperty(exports, 'flush', {
  enumerable: true,
  get: function () {
    return flush;
  }
});
Object.defineProperty(exports, 'generateCorrelationId', {
  enumerable: true,
  get: function () {
    return generateCorrelationId;
  }
});
Object.defineProperty(exports, 'getActiveContextWithBaggage', {
  enumerable: true,
  get: function () {
    return getActiveContextWithBaggage;
  }
});
Object.defineProperty(exports, 'getConfig', {
  enumerable: true,
  get: function () {
    return getConfig;
  }
});
Object.defineProperty(exports, 'getConfiguredSampler', {
  enumerable: true,
  get: function () {
    return getConfiguredSampler;
  }
});
Object.defineProperty(exports, 'getContextStorage', {
  enumerable: true,
  get: function () {
    return getContextStorage;
  }
});
Object.defineProperty(exports, 'getCorrelationId', {
  enumerable: true,
  get: function () {
    return getCorrelationId;
  }
});
Object.defineProperty(exports, 'getCorrelationStorage', {
  enumerable: true,
  get: function () {
    return getCorrelationStorage;
  }
});
Object.defineProperty(exports, 'getEventQueue', {
  enumerable: true,
  get: function () {
    return getEventQueue;
  }
});
Object.defineProperty(exports, 'getEvents', {
  enumerable: true,
  get: function () {
    return getEvents;
  }
});
Object.defineProperty(exports, 'getLogger', {
  enumerable: true,
  get: function () {
    return getLogger;
  }
});
Object.defineProperty(exports, 'getOperationContext', {
  enumerable: true,
  get: function () {
    return getOperationContext;
  }
});
Object.defineProperty(exports, 'getOrCreateCorrelationId', {
  enumerable: true,
  get: function () {
    return getOrCreateCorrelationId;
  }
});
Object.defineProperty(exports, 'getSdk', {
  enumerable: true,
  get: function () {
    return getSdk;
  }
});
Object.defineProperty(exports, 'hasExplicitSpanStatus', {
  enumerable: true,
  get: function () {
    return hasExplicitSpanStatus;
  }
});
Object.defineProperty(exports, 'hasYamlConfig', {
  enumerable: true,
  get: function () {
    return hasYamlConfig;
  }
});
Object.defineProperty(exports, 'hashUnitInterval', {
  enumerable: true,
  get: function () {
    return hashUnitInterval;
  }
});
Object.defineProperty(exports, 'init', {
  enumerable: true,
  get: function () {
    return init;
  }
});
Object.defineProperty(exports, 'isForceKept', {
  enumerable: true,
  get: function () {
    return isForceKept;
  }
});
Object.defineProperty(exports, 'isInitialized', {
  enumerable: true,
  get: function () {
    return isInitialized;
  }
});
Object.defineProperty(exports, 'isLoggerLocked', {
  enumerable: true,
  get: function () {
    return isLoggerLocked;
  }
});
Object.defineProperty(exports, 'loadYamlConfig', {
  enumerable: true,
  get: function () {
    return loadYamlConfig;
  }
});
Object.defineProperty(exports, 'loadYamlConfigFromFile', {
  enumerable: true,
  get: function () {
    return loadYamlConfigFromFile;
  }
});
Object.defineProperty(exports, 'lockLogger', {
  enumerable: true,
  get: function () {
    return lockLogger;
  }
});
Object.defineProperty(exports, 'markForceKept', {
  enumerable: true,
  get: function () {
    return markForceKept;
  }
});
Object.defineProperty(exports, 'resetEvents', {
  enumerable: true,
  get: function () {
    return resetEvents;
  }
});
Object.defineProperty(exports, 'resolveSamplingPreset', {
  enumerable: true,
  get: function () {
    return resolveSamplingPreset;
  }
});
Object.defineProperty(exports, 'runInOperationContext', {
  enumerable: true,
  get: function () {
    return runInOperationContext;
  }
});
Object.defineProperty(exports, 'runWithCorrelationId', {
  enumerable: true,
  get: function () {
    return runWithCorrelationId;
  }
});
Object.defineProperty(exports, 'samplingPresets', {
  enumerable: true,
  get: function () {
    return samplingPresets;
  }
});
Object.defineProperty(exports, 'setCorrelationId', {
  enumerable: true,
  get: function () {
    return setCorrelationId;
  }
});
Object.defineProperty(exports, 'setCorrelationIdInBaggage', {
  enumerable: true,
  get: function () {
    return setCorrelationIdInBaggage;
  }
});
Object.defineProperty(exports, 'shutdown', {
  enumerable: true,
  get: function () {
    return shutdown;
  }
});
Object.defineProperty(exports, 'track', {
  enumerable: true,
  get: function () {
    return track;
  }
});