autotel
Version:
Write Once, Observe Anywhere
1,515 lines (1,510 loc) • 60.1 kB
JavaScript
'use strict';
var chunkGML3FBOT_cjs = require('./chunk-GML3FBOT.cjs');
var chunkD5LMF53P_cjs = require('./chunk-D5LMF53P.cjs');
var chunkJSNUWSBH_cjs = require('./chunk-JSNUWSBH.cjs');
var chunkHZ3FYBJG_cjs = require('./chunk-HZ3FYBJG.cjs');
var chunk563EL6O6_cjs = require('./chunk-563EL6O6.cjs');
var chunkVH77IPJN_cjs = require('./chunk-VH77IPJN.cjs');
var chunkESLWRGAG_cjs = require('./chunk-ESLWRGAG.cjs');
var api = require('@opentelemetry/api');
var fs = require('fs');
var url = require('url');
// src/rate-limiter.ts
var TokenBucketRateLimiter = class {
tokens;
maxTokens;
refillRate;
// tokens per millisecond
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 elapsed = now - this.lastRefill;
const tokensToAdd = elapsed * 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();
}
};
// src/event-queue.ts
var DEFAULT_CONFIG = {
maxSize: 5e4,
batchSize: 100,
flushInterval: 1e4,
maxRetries: 3,
rateLimit: {
maxEventsPerSecond: 100,
burstCapacity: 200
}
};
function getSubscriberName(subscriber) {
if (subscriber.name) {
return subscriber.name.toLowerCase();
}
const className = subscriber.constructor?.name || "unknown";
return className.replace(/Subscriber$/i, "").toLowerCase();
}
var EventQueue = class {
queue = [];
flushTimer = null;
config;
subscribers;
rateLimiter;
flushPromise = null;
isShuttingDown = false;
// Metrics
metrics = null;
// Observable callback cleanup functions
observableCleanups = [];
// Subscriber health tracking (for observable gauges)
subscriberHealthy = /* @__PURE__ */ new Map();
constructor(subscribers, config) {
this.subscribers = subscribers;
this.config = { ...DEFAULT_CONFIG, ...config };
this.rateLimiter = this.config.rateLimit ? new TokenBucketRateLimiter(this.config.rateLimit) : null;
for (const subscriber of subscribers) {
const name = getSubscriberName(subscriber);
this.subscriberHealthy.set(name, true);
}
this.initMetrics();
}
/**
* Initialize OTel metrics for queue observability
*/
initMetrics() {
const runtimeConfig = chunkESLWRGAG_cjs.getConfig();
const meter = runtimeConfig.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 = chunk563EL6O6_cjs.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);
chunk563EL6O6_cjs.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);
chunk563EL6O6_cjs.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 || chunkJSNUWSBH_cjs.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;
void 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 failedEventIndices = new Set(
failedDeliveries.map((f) => f.eventIndex)
);
const failedEventIndicesOrdered = [...failedEventIndices].sort(
(a, b) => a - b
);
const eventsToRetry = failedEventIndicesOrdered.map(
(i) => events[i]
);
const failedSubscribersByRetryIndex = /* @__PURE__ */ new Map();
for (let j = 0; j < failedEventIndicesOrdered.length; j++) {
const origIndex = failedEventIndicesOrdered[j];
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))
];
chunk563EL6O6_cjs.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 (let i = 0; i < events.length; i++) {
const event = events[i];
if (event) await sendOne(event, i);
}
return failedDeliveries;
}
for (let i = 0; i < events.length; i++) {
await this.rateLimiter.waitForToken();
const event = events[i];
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
});
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();
}
/**
* 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);
}
};
// src/track.ts
var eventsQueue = null;
function buildAutotelContext(span2) {
const eventsConfig = chunk563EL6O6_cjs.getEventsConfig();
const config = chunk563EL6O6_cjs.getConfig();
const correlationId = chunkJSNUWSBH_cjs.getOrCreateCorrelationId();
if (!eventsConfig?.includeTraceContext) {
return {
correlation_id: correlationId
};
}
const autotelContext = {
correlation_id: correlationId
};
const spanContext = span2?.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;
}
function getOrCreateQueue() {
if (!chunk563EL6O6_cjs.isInitialized()) {
chunk563EL6O6_cjs.warnIfNotInitialized("track()");
return null;
}
if (!eventsQueue) {
const config = chunk563EL6O6_cjs.getConfig();
if (!config?.subscribers || config.subscribers.length === 0) {
return null;
}
eventsQueue = new EventQueue(config.subscribers);
}
return eventsQueue;
}
function track(event, data) {
const queue = getOrCreateQueue();
if (!queue) return;
const validationConfig = chunk563EL6O6_cjs.getValidationConfig();
const validated = chunkD5LMF53P_cjs.validateEvent(event, data, validationConfig || void 0);
const span2 = api.trace.getActiveSpan();
const enrichedData = span2 ? {
...validated.attributes,
traceId: span2.spanContext().traceId,
spanId: span2.spanContext().spanId
} : validated.attributes;
const autotelContext = buildAutotelContext(span2);
queue.enqueue({
name: validated.eventName,
attributes: enrichedData,
timestamp: Date.now(),
autotel: autotelContext
});
}
function getEventQueue() {
return eventsQueue;
}
function resetEventQueue() {
eventsQueue = null;
}
var inferenceCache = /* @__PURE__ */ new Map();
var MAX_CACHE_SIZE = 50;
function captureStackTrace() {
const originalStackTraceLimit = Error.stackTraceLimit;
Error.stackTraceLimit = 10;
const err = new Error("Stack trace capture");
const stack = err.stack || "";
Error.stackTraceLimit = originalStackTraceLimit;
return stack;
}
function parseCallLocation(stack) {
const lines = stack.split("\n");
let skippedExternalFrame = false;
for (const line of lines) {
if (line.includes("variable-name-inference.ts") || line.includes("variable-name-inference.js") || line.includes("functional.ts") || line.includes("functional.js")) {
continue;
}
const match = line.match(/at\s+(?:.*\s+)?\(?([^:]+):(\d+):(\d+)\)?/) || line.match(/^.*?([^:]+):(\d+):(\d+)/);
if (match) {
let filePath = match[1].trim();
if (filePath.startsWith("file://")) {
try {
filePath = url.fileURLToPath(filePath);
} catch {
continue;
}
}
if (!skippedExternalFrame) {
skippedExternalFrame = true;
continue;
}
return {
file: filePath,
line: Number.parseInt(match[2], 10),
column: Number.parseInt(match[3], 10)
};
}
}
return void 0;
}
function readSourceLine(filePath, lineNumber) {
try {
if (typeof fs.readFileSync !== "function") {
return void 0;
}
const content = fs.readFileSync(filePath, "utf8");
const lines = content.split("\n");
return lines[lineNumber - 1];
} catch {
return void 0;
}
}
function extractVariableName(sourceLine) {
const trimmed = sourceLine.trim();
const patterns = [
// export const varName = anyFunction(
/export\s+const\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=\s*[a-zA-Z_$][a-zA-Z0-9_$]*\s*\(/,
// const varName = anyFunction(
/const\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=\s*[a-zA-Z_$][a-zA-Z0-9_$]*\s*\(/,
// export let varName = anyFunction(
/export\s+let\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=\s*[a-zA-Z_$][a-zA-Z0-9_$]*\s*\(/,
// let varName = anyFunction(
/let\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=\s*[a-zA-Z_$][a-zA-Z0-9_$]*\s*\(/,
// export var varName = anyFunction(
/export\s+var\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=\s*[a-zA-Z_$][a-zA-Z0-9_$]*\s*\(/,
// var varName = anyFunction(
/var\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=\s*[a-zA-Z_$][a-zA-Z0-9_$]*\s*\(/
];
for (const pattern of patterns) {
const match = trimmed.match(pattern);
if (match && match[1]) {
return match[1];
}
}
return void 0;
}
function cacheInference(key, value) {
if (inferenceCache.size >= MAX_CACHE_SIZE) {
const firstKey = inferenceCache.keys().next().value;
if (firstKey) {
inferenceCache.delete(firstKey);
}
}
inferenceCache.set(key, value);
}
function inferVariableNameFromCallStack() {
try {
const stack = captureStackTrace();
const callLocation = parseCallLocation(stack);
if (!callLocation) {
return void 0;
}
const cacheKey = `${callLocation.file}:${callLocation.line}`;
if (inferenceCache.has(cacheKey)) {
return inferenceCache.get(cacheKey);
}
const sourceLine = readSourceLine(callLocation.file, callLocation.line);
if (!sourceLine) {
return void 0;
}
const variableName = extractVariableName(sourceLine);
cacheInference(cacheKey, variableName);
return variableName;
} catch {
return void 0;
}
}
// src/functional.ts
var FACTORY_NAME_HINTS = /* @__PURE__ */ new Set([
"ctx",
"_ctx",
"context",
"tracecontext",
"tracectx"
]);
var TRACE_FACTORY_SET = /* @__PURE__ */ new WeakSet();
var SINGLE_LINE_COMMENT_REGEX = /\/\/.*$/gm;
var MULTI_LINE_COMMENT_REGEX = /\/\*[\s\S]*?\*\//gm;
var PARAM_TOKEN_SANITIZE_REGEX = new RegExp(String.raw`[{}\[\]\s]`, "g");
function markAsTraceFactory(fn) {
TRACE_FACTORY_SET.add(fn);
}
function hasFactoryMark(fn) {
return TRACE_FACTORY_SET.has(fn);
}
function sanitizeParameterToken(token) {
const [firstToken] = token.split("=");
return (firstToken ?? "").replaceAll(PARAM_TOKEN_SANITIZE_REGEX, "").trim();
}
function getFirstParameterToken(fn) {
let source = Function.prototype.toString.call(fn);
source = source.replaceAll(MULTI_LINE_COMMENT_REGEX, "").replaceAll(SINGLE_LINE_COMMENT_REGEX, "").trim();
const arrowMatch = source.match(
/^(?:async\s*)?(?:\(([^)]*)\)|([^=()]+))\s*=>/
);
if (arrowMatch) {
const params = (arrowMatch[1] ?? arrowMatch[2] ?? "").split(",");
const first = params[0]?.trim();
if (first) {
return sanitizeParameterToken(first);
}
return null;
}
const functionMatch = source.match(/^[^(]*\(([^)]*)\)/);
if (functionMatch) {
const params = functionMatch[1]?.split(",");
const first = params?.[0]?.trim();
if (first) {
return sanitizeParameterToken(first);
}
}
return null;
}
function looksLikeTraceFactory(fn) {
if (hasFactoryMark(fn)) {
return true;
}
if (fn.length === 0) {
if (!isAsyncFunction(fn)) {
try {
const result = fn();
return typeof result === "function";
} catch {
return false;
}
}
return false;
}
const firstParam = getFirstParameterToken(fn);
if (!firstParam) {
return false;
}
const normalized = firstParam.toLowerCase();
if (FACTORY_NAME_HINTS.has(normalized) || normalized.startsWith("ctx") || normalized.startsWith("_ctx") || normalized.startsWith("trace") || normalized.endsWith("ctx") || // Match baseCtx, spanCtx, etc.
normalized.includes("context")) {
return true;
}
return false;
}
function isFactoryReturningFunction(fnWithCtx) {
if (isAsyncFunction(fnWithCtx)) {
return false;
}
try {
const result = fnWithCtx(createDummyCtx());
return typeof result === "function";
} catch {
return false;
}
}
function isTraceFactoryFunction(fn) {
if (typeof fn !== "function") {
return false;
}
if (hasFactoryMark(fn)) {
return true;
}
if (looksLikeTraceFactory(fn)) {
markAsTraceFactory(fn);
return true;
}
return false;
}
function ensureTraceFactory(fnOrFactory) {
if (isTraceFactoryFunction(fnOrFactory)) {
return fnOrFactory;
}
const plainFn = fnOrFactory;
const factory = (ctx2) => {
return plainFn;
};
markAsTraceFactory(factory);
return factory;
}
function wrapFactoryWithTracing(fnOrFactory, options, variableName) {
const factory = ensureTraceFactory(fnOrFactory);
const sampleFn = factory(createDummyCtx());
const innerFunctionName = inferFunctionName(
sampleFn
);
const callStackVariableName = innerFunctionName ? void 0 : inferVariableNameFromCallStack();
const factoryName = inferFunctionName(factory);
const effectiveVariableName = variableName || innerFunctionName || callStackVariableName || factoryName;
const useAsyncWrapper = isAsyncFunction(sampleFn);
if (useAsyncWrapper) {
return wrapWithTracing(
factory,
options,
effectiveVariableName
);
}
return wrapWithTracingSync(
factory,
options,
effectiveVariableName
);
}
var MAX_ERROR_MESSAGE_LENGTH = 500;
function createDummyCtx() {
return {
traceId: "",
spanId: "",
correlationId: "",
setAttribute: () => {
},
setAttributes: () => {
},
setStatus: () => {
},
recordException: () => {
},
addEvent: () => {
},
addLink: () => {
},
addLinks: () => {
},
updateName: () => {
},
isRecording: () => false,
getBaggage: () => {
},
setBaggage: () => "",
deleteBaggage: () => {
},
getAllBaggage: () => /* @__PURE__ */ new Map()
};
}
function isAsyncFunction(fn) {
return typeof fn === "function" && fn.constructor?.name === "AsyncFunction";
}
var INSTRUMENTED_SYMBOL = /* @__PURE__ */ Symbol.for("autotel.functional.instrumented");
function hasInstrumentationFlag(value) {
return (typeof value === "function" || typeof value === "object") && value !== null && Boolean(value[INSTRUMENTED_SYMBOL]);
}
function truncateErrorMessage(message) {
if (message.length <= MAX_ERROR_MESSAGE_LENGTH) {
return message;
}
return `${message.slice(0, MAX_ERROR_MESSAGE_LENGTH)}... (truncated)`;
}
function inferFunctionName(fn) {
const displayName = fn.displayName;
if (displayName) {
return displayName;
}
if (fn.name && fn.name !== "anonymous" && fn.name !== "") {
return fn.name;
}
const source = Function.prototype.toString.call(fn);
const match = source.match(/function\s+([^(\s]+)/);
if (match && match[1] && match[1] !== "anonymous") {
return match[1];
}
return void 0;
}
function getSpanName(options, fn, variableName) {
if (options.name) {
return options.name;
}
let fnName = variableName || inferFunctionName(fn);
fnName = fnName || "anonymous";
if (options.serviceName) {
return `${options.serviceName}.${fnName}`;
}
if (fnName && fnName !== "anonymous") {
return fnName;
}
return "unknown";
}
function shouldSkip(key, fn, skip) {
if (key.startsWith("_")) {
return true;
}
if (!skip || skip.length === 0) {
return false;
}
for (const rule of skip) {
if (typeof rule === "string" && key === rule) {
return true;
} else if (rule instanceof RegExp && rule.test(key)) {
return true;
} else if (typeof rule === "function" && rule(key, fn)) {
return true;
}
}
return false;
}
function getCtxValue() {
const activeSpan = api.trace.getActiveSpan();
if (!activeSpan) return null;
return chunkHZ3FYBJG_cjs.createTraceContext(activeSpan);
}
var ctx = new Proxy(
{},
{
get(_target, prop) {
const ctxValue = getCtxValue();
if (!ctxValue) {
return;
}
return ctxValue[prop];
},
has(_target, prop) {
const ctxValue = getCtxValue();
if (!ctxValue) {
return false;
}
return prop in ctxValue;
},
ownKeys() {
const ctxValue = getCtxValue();
if (!ctxValue) {
return [];
}
return Object.keys(ctxValue);
},
getOwnPropertyDescriptor(_target, prop) {
const ctxValue = getCtxValue();
if (!ctxValue) {
return;
}
return Object.getOwnPropertyDescriptor(ctxValue, prop);
}
}
);
function wrapWithTracing(fnFactory, options, variableName) {
if (hasInstrumentationFlag(fnFactory)) ;
const config = chunkESLWRGAG_cjs.getConfig();
const tracer = config.tracer;
const meter = config.meter;
const sampler = options.sampler || new chunkVH77IPJN_cjs.AlwaysSampler();
const tempFn = fnFactory(createDummyCtx());
const spanName = getSpanName(options, tempFn, variableName);
const callCounter = options.withMetrics ? meter.createCounter(`${spanName}.calls`, {
description: `Call count for ${spanName}`,
unit: "1"
}) : void 0;
const durationHistogram = options.withMetrics ? meter.createHistogram(`${spanName}.duration`, {
description: `Duration for ${spanName}`,
unit: "ms"
}) : void 0;
const wrappedFunction = async function wrappedFunction2(...args) {
const samplingContext = {
operationName: spanName,
args,
metadata: {}
};
const shouldSample = sampler.shouldSample(samplingContext);
const needsTailSampling = "needsTailSampling" in sampler && typeof sampler.needsTailSampling === "function" ? sampler.needsTailSampling() : false;
if (!shouldSample && !needsTailSampling) {
const fn = fnFactory(createDummyCtx());
return await fn.call(this, ...args);
}
const startTime = performance.now();
const isRootSpan = options.startNewRoot || api.trace.getActiveSpan() === void 0;
const shouldAutoFlush = options.flushOnRootSpanEnd ?? chunk563EL6O6_cjs.getConfig()?.flushOnRootSpanEnd ?? true;
const shouldAutoFlushSpans = chunk563EL6O6_cjs.getConfig()?.forceFlushOnShutdown ?? false;
const flushIfNeeded = async () => {
if (!shouldAutoFlush || !isRootSpan) return;
try {
const queue = getEventQueue();
if (queue && queue.size() > 0) {
await queue.flush();
}
if (shouldAutoFlushSpans) {
const sdk = chunk563EL6O6_cjs.getSdk();
if (sdk) {
try {
const sdkAny = sdk;
if (typeof sdkAny.getTracerProvider === "function") {
const tracerProvider = sdkAny.getTracerProvider();
if (tracerProvider && typeof tracerProvider.forceFlush === "function") {
await tracerProvider.forceFlush();
}
}
} catch {
}
}
}
} catch (error) {
const initConfig = chunk563EL6O6_cjs.getConfig();
const logger = initConfig?.logger;
if (logger?.error) {
logger.error(
{
err: error instanceof Error ? error : void 0
},
`[autotel] Auto-flush failed${error instanceof Error ? "" : `: ${String(error)}`}`
);
}
}
};
const spanOptions = {};
if (options.startNewRoot) {
spanOptions.root = true;
}
if (options.spanKind !== void 0) {
spanOptions.kind = options.spanKind;
}
const parentContext = chunkHZ3FYBJG_cjs.getActiveContextWithBaggage();
return tracer.startActiveSpan(
spanName,
spanOptions,
parentContext,
async (span2) => {
return chunkD5LMF53P_cjs.runInOperationContext(spanName, async () => {
let shouldKeepSpan = true;
chunkGML3FBOT_cjs.setSpanName(span2, spanName);
const initialContext = api.context.active();
const contextStorage = chunkHZ3FYBJG_cjs.getContextStorage();
if (!contextStorage.getStore()) {
chunkHZ3FYBJG_cjs.enterOrRun(contextStorage, initialContext);
}
const ctxValue = chunkHZ3FYBJG_cjs.createTraceContext(span2);
const fn = fnFactory(ctxValue);
const argsAttributes = options.attributesFromArgs ? options.attributesFromArgs(args) : {};
const handleTailSampling = (success, duration, error) => {
if (needsTailSampling && "shouldKeepTrace" in sampler && typeof sampler.shouldKeepTrace === "function") {
shouldKeepSpan = sampler.shouldKeepTrace(samplingContext, {
success,
duration,
error
});
span2.setAttribute(chunkVH77IPJN_cjs.AUTOTEL_SAMPLING_TAIL_KEEP, shouldKeepSpan);
span2.setAttribute(chunkVH77IPJN_cjs.AUTOTEL_SAMPLING_TAIL_EVALUATED, true);
}
};
const onSuccess = async (result) => {
const duration = performance.now() - startTime;
callCounter?.add(1, {
operation: spanName,
status: "success"
});
durationHistogram?.record(duration, {
operation: spanName,
status: "success"
});
const resultAttributes = options.attributesFromResult ? options.attributesFromResult(result) : {};
span2.setStatus({ code: api.SpanStatusCode.OK });
span2.setAttributes({
...argsAttributes,
...resultAttributes,
"operation.name": spanName,
"code.function": spanName,
"operation.duration": duration,
"operation.success": true
});
handleTailSampling(true, duration);
span2.end();
await flushIfNeeded();
return result;
};
const onError = async (error) => {
const duration = performance.now() - startTime;
callCounter?.add(1, {
operation: spanName,
status: "error"
});
durationHistogram?.record(duration, {
operation: spanName,
status: "error"
});
const errorMessage = error instanceof Error ? error.message : "Unknown error";
const truncatedMessage = truncateErrorMessage(errorMessage);
span2.setStatus({
code: api.SpanStatusCode.ERROR,
message: truncatedMessage
});
span2.setAttributes({
...argsAttributes,
"operation.name": spanName,
"code.function": spanName,
"operation.duration": duration,
"operation.success": false,
error: true,
"exception.type": error instanceof Error ? error.constructor.name : "Error",
"exception.message": truncatedMessage
});
if (error instanceof Error && error.stack) {
span2.setAttribute(
"exception.stack",
error.stack.slice(0, MAX_ERROR_MESSAGE_LENGTH)
);
}
span2.recordException(
error instanceof Error ? error : new Error(String(error))
);
handleTailSampling(false, duration, error);
span2.end();
await flushIfNeeded();
throw error;
};
try {
callCounter?.add(1, {
operation: spanName,
status: "started"
});
const executeWithContext = async () => {
const currentContext = chunkHZ3FYBJG_cjs.getActiveContextWithBaggage();
return api.context.with(currentContext, async () => {
return fn.call(this, ...args);
});
};
const result = await executeWithContext();
return await onSuccess(result);
} catch (error) {
await onError(error);
throw error;
}
});
}
);
};
wrappedFunction[INSTRUMENTED_SYMBOL] = true;
Object.defineProperty(wrappedFunction, "name", {
value: tempFn.name || "trace",
configurable: true
});
return wrappedFunction;
}
function wrapWithTracingSync(fnFactory, options, variableName) {
if (hasInstrumentationFlag(fnFactory)) ;
const config = chunkESLWRGAG_cjs.getConfig();
const tracer = config.tracer;
const meter = config.meter;
const sampler = options.sampler || new chunkVH77IPJN_cjs.AlwaysSampler();
const tempFn = fnFactory(createDummyCtx());
const spanName = getSpanName(options, tempFn, variableName);
const callCounter = options.withMetrics ? meter.createCounter(`${spanName}.calls`, {
description: `Call count for ${spanName}`,
unit: "1"
}) : void 0;
const durationHistogram = options.withMetrics ? meter.createHistogram(`${spanName}.duration`, {
description: `Duration for ${spanName}`,
unit: "ms"
}) : void 0;
function wrappedFunction(...args) {
const samplingContext = {
operationName: spanName,
args,
metadata: {}
};
const shouldSample = sampler.shouldSample(samplingContext);
const needsTailSampling = "needsTailSampling" in sampler && typeof sampler.needsTailSampling === "function" ? sampler.needsTailSampling() : false;
if (!shouldSample && !needsTailSampling) {
const fn = fnFactory(createDummyCtx());
return fn.call(this, ...args);
}
const startTime = performance.now();
const isRootSpan = options.startNewRoot || api.trace.getActiveSpan() === void 0;
const shouldAutoFlush = options.flushOnRootSpanEnd ?? chunk563EL6O6_cjs.getConfig()?.flushOnRootSpanEnd ?? true;
const shouldAutoFlushSpans = chunk563EL6O6_cjs.getConfig()?.forceFlushOnShutdown ?? false;
const flushIfNeeded = () => {
if (!shouldAutoFlush || !isRootSpan) return;
const queue = getEventQueue();
if (queue && queue.size() > 0) {
void queue.flush().catch((error) => {
const initConfig = chunk563EL6O6_cjs.getConfig();
const logger = initConfig?.logger;
if (logger?.error) {
logger.error(
{
err: error instanceof Error ? error : void 0
},
`[autotel] Auto-flush failed${error instanceof Error ? "" : `: ${String(error)}`}`
);
}
});
}
if (shouldAutoFlushSpans) {
const sdk = chunk563EL6O6_cjs.getSdk();
if (sdk) {
try {
const sdkAny = sdk;
if (typeof sdkAny.getTracerProvider === "function") {
const tracerProvider = sdkAny.getTracerProvider();
if (tracerProvider && typeof tracerProvider.forceFlush === "function") {
void tracerProvider.forceFlush().catch((error) => {
const initConfig = chunk563EL6O6_cjs.getConfig();
const logger = initConfig?.logger;
if (logger?.error) {
logger.error(
{
err: error instanceof Error ? error : void 0
},
`[autotel] Span flush failed${error instanceof Error ? "" : `: ${String(error)}`}`
);
}
});
}
}
} catch {
}
}
}
};
const spanOptions = {};
if (options.startNewRoot) {
spanOptions.root = true;
}
if (options.spanKind !== void 0) {
spanOptions.kind = options.spanKind;
}
const parentContext = chunkHZ3FYBJG_cjs.getActiveContextWithBaggage();
return tracer.startActiveSpan(
spanName,
spanOptions,
parentContext,
(span2) => {
return chunkD5LMF53P_cjs.runInOperationContext(spanName, () => {
let shouldKeepSpan = true;
chunkGML3FBOT_cjs.setSpanName(span2, spanName);
const ctxValue = chunkHZ3FYBJG_cjs.createTraceContext(span2);
const fn = fnFactory(ctxValue);
const argsAttributes = options.attributesFromArgs ? options.attributesFromArgs(args) : {};
const handleTailSampling = (success, duration, error) => {
if (needsTailSampling && "shouldKeepTrace" in sampler && typeof sampler.shouldKeepTrace === "function") {
shouldKeepSpan = sampler.shouldKeepTrace(samplingContext, {
success,
duration,
error
});
span2.setAttribute(chunkVH77IPJN_cjs.AUTOTEL_SAMPLING_TAIL_KEEP, shouldKeepSpan);
span2.setAttribute(chunkVH77IPJN_cjs.AUTOTEL_SAMPLING_TAIL_EVALUATED, true);
}
};
const onSuccess = (result) => {
const duration = performance.now() - startTime;
callCounter?.add(1, {
operation: spanName,
status: "success"
});
durationHistogram?.record(duration, {
operation: spanName,
status: "success"
});
const resultAttributes = options.attributesFromResult ? options.attributesFromResult(result) : {};
span2.setStatus({ code: api.SpanStatusCode.OK });
span2.setAttributes({
...argsAttributes,
...resultAttributes,
"operation.name": spanName,
"code.function": spanName,
"operation.duration": duration,
"operation.success": true
});
handleTailSampling(true, duration);
span2.end();
void flushIfNeeded();
return result;
};
const onError = (error) => {
const duration = performance.now() - startTime;
callCounter?.add(1, {
operation: spanName,
status: "error"
});
durationHistogram?.record(duration, {
operation: spanName,
status: "error"
});
const errorMessage = error instanceof Error ? error.message : "Unknown error";
const truncatedMessage = truncateErrorMessage(errorMessage);
span2.setStatus({
code: api.SpanStatusCode.ERROR,
message: truncatedMessage
});
span2.setAttributes({
...argsAttributes,
"operation.name": spanName,
"code.function": spanName,
"operation.duration": duration,
"operation.success": false,
error: true,
"exception.type": error instanceof Error ? error.constructor.name : "Error",
"exception.message": truncatedMessage
});
span2.recordException(
error instanceof Error ? error : new Error(String(error))
);
handleTailSampling(false, duration, error);
span2.end();
void flushIfNeeded();
throw error;
};
try {
callCounter?.add(1, {
operation: spanName,
status: "started"
});
const result = fn.call(this, ...args);
if (result instanceof Promise) {
return result.then(onSuccess, onError);
}
return onSuccess(result);
} catch (error) {
return onError(error);
}
});
}
);
}
wrappedFunction[INSTRUMENTED_SYMBOL] = true;
Object.defineProperty(wrappedFunction, "name", {
value: tempFn.name || "trace",
configurable: true
});
return wrappedFunction;
}
function executeImmediately(fn, options) {
const config = chunkESLWRGAG_cjs.getConfig();
const tracer = config.tracer;
const meter = config.meter;
const sampler = options.sampler || new chunkVH77IPJN_cjs.AlwaysSampler();
const spanName = options.name || "anonymous";
const samplingContext = {
operationName: spanName,
args: [],
metadata: {}
};
const shouldSample = sampler.shouldSample(samplingContext);
const needsTailSampling = "needsTailSampling" in sampler && typeof sampler.needsTailSampling === "function" ? sampler.needsTailSampling() : false;
if (!shouldSample && !needsTailSampling) {
return fn(createDummyCtx());
}
const startTime = performance.now();
const isRootSpan = options.startNewRoot || api.trace.getActiveSpan() === void 0;
const shouldAutoFlush = options.flushOnRootSpanEnd ?? chunk563EL6O6_cjs.getConfig()?.flushOnRootSpanEnd ?? true;
const shouldAutoFlushSpans = chunk563EL6O6_cjs.getConfig()?.forceFlushOnShutdown ?? false;
const callCounter = options.withMetrics ? meter.createCounter(`${spanName}.calls`, {
description: `Call count for ${spanName}`,
unit: "1"
}) : void 0;
const durationHistogram = options.withMetrics ? meter.createHistogram(`${spanName}.duration`, {
description: `Duration for ${spanName}`,
unit: "ms"
}) : void 0;
const flushIfNeeded = async () => {
if (!shouldAutoFlush || !isRootSpan) return;
try {
const queue = getEventQueue();
if (queue && queue.size() > 0) {
await queue.flush();
}
if (shouldAutoFlushSpans) {
const sdk = chunk563EL6O6_cjs.getSdk();
if (sdk) {
try {
const sdkAny = sdk;
if (typeof sdkAny.getTracerProvider === "function") {
const tracerProvider = sdkAny.getTracerProvider();
if (tracerProvider && typeof tracerProvider.forceFlush === "function") {
await tracerProvider.forceFlush();
}
}
} catch {
}
}
}
} catch (error) {
const initConfig = chunk563EL6O6_cjs.getConfig();
const logger = initConfig?.logger;
if (logger?.error) {
logger.error(
{
err: error instanceof Error ? error : void 0
},
`[autotel] Auto-flush failed${error instanceof Error ? "" : `: ${String(error)}`}`
);
}
}
};
const spanOptions = {};
if (options.startNewRoot) {
spanOptions.root = true;
}
if (options.spanKind !== void 0) {
spanOptions.kind = options.spanKind;
}
const parentContext = chunkHZ3FYBJG_cjs.getActiveContextWithBaggage();
return tracer.startActiveSpan(
spanName,
spanOptions,
parentContext,
(span2) => {
return chunkD5LMF53P_cjs.runInOperationContext(spanName, () => {
let shouldKeepSpan = true;
chunkGML3FBOT_cjs.setSpanName(span2, spanName);
const ctxValue = chunkHZ3FYBJG_cjs.createTraceContext(span2);
const handleTailSampling = (success, duration, error) => {
if (needsTailSampling && "shouldKeepTrace" in sampler && typeof sampler.shouldKeepTrace === "function") {
shouldKeepSpan = sampler.shouldKeepTrace(samplingContext, {
success,
duration,
error
});
span2.setAttribute(chunkVH77IPJN_cjs.AUTOTEL_SAMPLING_TAIL_KEEP, shouldKeepSpan);
span2.setAttribute(chunkVH77IPJN_cjs.AUTOTEL_SAMPLING_TAIL_EVALUATED, true);
}
};
const onSuccessSync = (result) => {
const duration = performance.now() - startTime;
callCounter?.add(1, {
operation: spanName,
status: "success"
});
durationHistogram?.record(duration, {
operation: spanName,
status: "success"
});
span2.setStatus({ code: api.SpanStatusCode.OK });
span2.setAttributes({
"operation.name": spanName,
"code.function": spanName,
"operation.duration": duration,
"operation.success": true
});
handleTailSampling(true, duration);
span2.end();
void flushIfNeeded();
return result;
};
const onErrorSync = (error) => {
const duration = performance.now() - startTime;
callCounter?.add(1, {
operation: spanName,
status: "error"
});
durationHistogram?.record(duration, {
operation: spanName,
status: "error"
});
const errorMessage = error instanceof Error ? error.message : "Unknown error";
const truncatedMessage = truncateErrorMessage(er