UNPKG

autotel

Version:
611 lines (605 loc) 21.7 kB
export { createDrainPipeline } from './chunk-KFOHQK7X.js'; export { getCurrentWorkflowContext, isInWorkflow, traceStep, traceWorkflow } from './chunk-H5YFU4SF.js'; export { attrs, autoRedactPII, dbClient, httpClient, httpServer, identify, mergeAttrs, mergeServiceResource, request, safeSetAttributes, setDevice, setError, setException, setSession, setUser, validateAttribute } from './chunk-DDXIUZEG.js'; export { httpRequestHeaderAttribute, httpResponseHeaderAttribute } from './chunk-7552UTQW.js'; export { HTTPAttributes, ServiceAttributes, URLAttributes } from './chunk-4A53YIAX.js'; export { parseError } from './chunk-J7VGRIAJ.js'; export { traceConsumer, traceProducer } from './chunk-ZJ5E5OFB.js'; import { emitCorrelatedEvent } from './chunk-KIL5CUN6.js'; export { BusinessBaggage, createSafeBaggageSchema } from './chunk-4IFSYQVX.js'; import { resetMetrics } from './chunk-7SAWIN74.js'; export { Metric, getMetrics, resetMetrics } from './chunk-7SAWIN74.js'; import './chunk-5ZN622AO.js'; export { createCounter, createHistogram, createObservableGauge, createUpDownCounter, getMeter } from './chunk-TQ5UWA7S.js'; export { traceDB, traceHTTP, traceLLM, traceMessaging } from './chunk-HLAVN3JR.js'; import { trace as trace$1 } from './chunk-S4S4AINO.js'; export { ctx, instrument, span, withBaggage, withNewContext, withTracing } from './chunk-S4S4AINO.js'; export { createDeterministicTraceId, enrichWithTraceContext, finalizeSpan, flattenMetadata, getActiveContext, getActiveSpan, getTraceContext, getTracer, isTracing, resolveTraceUrl, runWithSpan } from './chunk-DSMSIVTG.js'; import { resetEvents } from './chunk-OM4OSBOP.js'; export { Event, getEvents, resetEvents } from './chunk-OM4OSBOP.js'; import './chunk-LITNXTTT.js'; import './chunk-BZHG5IZ4.js'; export { getOperationContext, runInOperationContext } from './chunk-SEO6NAQT.js'; import { getEventQueue, resetEventQueue, createTraceContext, flattenToAttributes, recordStructuredError } from './chunk-Z7VAOK5X.js'; export { CORRELATION_ID_BAGGAGE_KEY, createStructuredError, defineBaggageSchema, flattenToAttributes, generateCorrelationId, getCorrelationId, getEventQueue, getOrCreateCorrelationId, getStructuredErrorAttributes, recordStructuredError, runWithCorrelationId, setCorrelationId, setCorrelationIdInBaggage, structuredErrorToJSON, toAttributeValue, track } from './chunk-Z7VAOK5X.js'; import { getLogger, getSdk, _closeEmbeddedDevtools } from './chunk-ZDPIWKWD.js'; export { BaggageSpanProcessor, createStringRedactor, init, isLoggerLocked, lockLogger } from './chunk-ZDPIWKWD.js'; import './chunk-3SDILILG.js'; import './chunk-A4E5AQFK.js'; export { FilteringSpanProcessor } from './chunk-WGWSHJ2N.js'; export { NORMALIZER_PATTERNS, NORMALIZER_PRESETS, SpanNameNormalizingProcessor } from './chunk-GYR5K654.js'; export { AttributeRedactingProcessor, REDACTOR_PATTERNS, REDACTOR_PRESETS, builtinPatterns, createAttributeRedactor, createRedactedSpan, normalizeAttributeRedactorConfig } from './chunk-JVWJDHDB.js'; import './chunk-6UQRVUN3.js'; export { formatDuration } from './chunk-3QXBFGKP.js'; import './chunk-33WTKH7X.js'; export { AUTOTEL_SAMPLING_TAIL_EVALUATED, AUTOTEL_SAMPLING_TAIL_KEEP, AdaptiveSampler, AlwaysSampler, NeverSampler, RandomSampler, UserIdSampler, createLinkFromHeaders, extractLinksFromBatch, resolveSamplingPreset, samplingPresets } from './chunk-DPSA4QLA.js'; import './chunk-55ER2KD5.js'; import './chunk-J5QENANM.js'; export { getAutotelTracer, getAutotelTracerProvider, setAutotelTracerProvider } from './chunk-HA2WBOGQ.js'; import './chunk-DGUM43GV.js'; import { trace } from '@opentelemetry/api'; export { ROOT_CONTEXT, SpanKind, SpanStatusCode, context, trace as otelTrace, propagation } from '@opentelemetry/api'; import { AsyncLocalStorage } from 'async_hooks'; import { AggregationType } from '@opentelemetry/sdk-metrics'; var otelMethods = { // Class methods on TraceAPI — bind to the singleton. setGlobalTracerProvider: trace.setGlobalTracerProvider.bind(trace), getTracerProvider: trace.getTracerProvider.bind(trace), getTracer: trace.getTracer.bind(trace), disable: trace.disable.bind(trace), // Instance fields on TraceAPI — already standalone, copy by reference. wrapSpanContext: trace.wrapSpanContext, isSpanContextValid: trace.isSpanContextValid, deleteSpan: trace.deleteSpan, getSpan: trace.getSpan, getActiveSpan: trace.getActiveSpan, getSpanContext: trace.getSpanContext, setSpan: trace.setSpan, setSpanContext: trace.setSpanContext }; var trace2 = Object.assign( trace$1, otelMethods ); // src/shutdown.ts async function flush(options) { const timeout = options?.timeout ?? 2e3; const forShutdown = options?.forShutdown ?? false; const doFlush = async () => { const eventsQueue = getEventQueue(); if (eventsQueue) { if (forShutdown) { await eventsQueue.shutdown(); } else { await eventsQueue.flush(); } } const sdk = 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 { } } }; let timeoutHandle; try { await Promise.race([ doFlush().finally(() => { if (timeoutHandle) { clearTimeout(timeoutHandle); } }), new Promise((_, reject) => { timeoutHandle = setTimeout( () => reject(new Error("Flush timeout")), timeout ); timeoutHandle.unref(); }) ]); } catch (error) { if (timeoutHandle) { clearTimeout(timeoutHandle); } const logger = getLogger(); logger.error( { err: error instanceof Error ? error : new Error(String(error)) }, "[autotel] Flush error" ); throw error; } } 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)); const isConnectionRefused = typeof error === "object" && error !== null && "code" in error && error.code === "ECONNREFUSED"; if (!isConnectionRefused) { if (!shutdownError) { shutdownError = err; } logger.error({ err }, "[autotel] SDK shutdown failed"); } } finally { await _closeEmbeddedDevtools(); const eventsQueue = getEventQueue(); if (eventsQueue && typeof eventsQueue.cleanup === "function") { eventsQueue.cleanup(); } resetEvents(); resetMetrics(); resetEventQueue(); } if (shutdownError) { throw shutdownError; } } function registerShutdownHooks() { if (typeof process === "undefined") return; const signals = ["SIGTERM", "SIGINT"]; let shuttingDown = false; for (const signal of signals) { process.on(signal, async () => { if (shuttingDown) return; shuttingDown = true; if (process.env.NODE_ENV !== "test") { getLogger().info( {}, `[autotel] Received ${signal}, flushing telemetry...` ); } try { await shutdown(); } catch (error) { getLogger().error( { err: error instanceof Error ? error : void 0 }, "[autotel] Error during shutdown" ); } finally { process.exit(0); } }); } } registerShutdownHooks(); var POST_EMIT_FORK_HINT = "For intentional background work tied to this request, use log.fork('label', fn) when available."; function warnPostEmit(method, detail) { console.warn( `[autotel] ${method} called after the wide event was emitted \u2014 ${detail} This data will not appear in observability. ${POST_EMIT_FORK_HINT}` ); } function mergeInto(target, source) { for (const key in source) { const sourceVal = source[key]; if (sourceVal === void 0) continue; const targetVal = target[key]; if (sourceVal !== null && typeof sourceVal === "object" && !Array.isArray(sourceVal) && targetVal !== null && typeof targetVal === "object" && !Array.isArray(targetVal)) { mergeInto( targetVal, sourceVal ); } else if (Array.isArray(targetVal) && Array.isArray(sourceVal)) { target[key] = [...targetVal, ...sourceVal]; } else { target[key] = sourceVal; } } } var requestContextStore = new AsyncLocalStorage(); function runWithRequestContext(ctx2, fn) { return requestContextStore.run(ctx2, fn); } function resolveContext(ctx2) { if (ctx2) return ctx2; const stored = requestContextStore.getStore(); if (stored) return stored; const span2 = trace.getActiveSpan(); if (!span2) { throw new Error( "[autotel] getRequestLogger() requires an active span or runWithRequestContext(). Wrap your handler with trace() or use runWithRequestContext()." ); } return createTraceContext(span2); } function getRequestLogger(ctx2, options) { const activeContext = resolveContext(ctx2); let contextState = {}; let emitted = false; let lastSnapshot = null; const addLogEvent = (level, message, fields) => { const attrs2 = fields ? flattenToAttributes(fields) : void 0; emitCorrelatedEvent(activeContext, `log.${level}`, { message, ...attrs2 ?? {} }); }; const sealCheck = (method, keys) => { if (emitted) { warnPostEmit( method, `Keys dropped: ${keys.length ? keys.join(", ") : "(empty)"}.` ); } }; return { set(fields) { sealCheck("log.set()", Object.keys(fields)); if (emitted) return; mergeInto(contextState, fields); activeContext.setAttributes(flattenToAttributes(fields)); }, info(message, fields) { const keys = fields ? ["message", ...Object.keys(fields).filter((k) => k !== "requestLogs")] : ["message"]; sealCheck("log.info()", keys); if (emitted) return; addLogEvent("info", message, fields); if (fields) { mergeInto(contextState, fields); activeContext.setAttributes(flattenToAttributes(fields)); } }, warn(message, fields) { const keys = fields ? ["message", ...Object.keys(fields).filter((k) => k !== "requestLogs")] : ["message"]; sealCheck("log.warn()", keys); if (emitted) return; addLogEvent("warn", message, fields); activeContext.setAttribute("autotel.log.level", "warn"); if (fields) { mergeInto(contextState, fields); activeContext.setAttributes(flattenToAttributes(fields)); } }, error(error, fields) { const keys = fields ? [...Object.keys(fields), "error"] : ["error"]; sealCheck("log.error()", keys); if (emitted) return; const err = typeof error === "string" ? new Error(error) : error; recordStructuredError(activeContext, err); addLogEvent("error", err.message, fields); if (fields) { mergeInto(contextState, fields); activeContext.setAttributes(flattenToAttributes(fields)); } activeContext.setAttribute("autotel.log.level", "error"); }, getContext() { return { ...contextState }; }, emitNow(overrides) { if (emitted) { warnPostEmit("log.emitNow()", "Ignoring duplicate emit."); return lastSnapshot; } const mergedContext = { ...contextState, ...overrides ?? {} }; const flattened = flattenToAttributes(mergedContext); activeContext.setAttributes(flattened); const snapshot = { timestamp: (/* @__PURE__ */ new Date()).toISOString(), traceId: activeContext.traceId, spanId: activeContext.spanId, correlationId: activeContext.correlationId, context: mergedContext }; emitCorrelatedEvent(activeContext, "log.emit.manual", { ...flattened }); if (options?.onEmit) { Promise.resolve(options.onEmit(snapshot)).catch((error) => { console.warn("[autotel] request logger onEmit failed:", error); }); } emitted = true; lastSnapshot = snapshot; return snapshot; }, fork(label, fn, forkOptions) { const parentRequestId = activeContext.correlationId; if (typeof parentRequestId !== "string" || parentRequestId.length === 0) { throw new Error( "[autotel] log.fork() requires the parent logger to have a correlationId. Ensure the request was created by autotel middleware." ); } const tracer = trace.getTracer("autotel.request-logger"); const lifecycle = forkOptions?.lifecycle; void tracer.startActiveSpan(`request.fork:${label}`, (childSpan) => { const childContext = { ...createTraceContext(childSpan), correlationId: crypto.randomUUID() }; requestContextStore.run(childContext, () => { const childLog = getRequestLogger(childContext); childLog.set({ operation: label, _parentCorrelationId: parentRequestId }); lifecycle?.onChildEnter?.(childLog); void Promise.resolve().then(() => fn()).then(() => { childLog.emitNow(); }).catch((err) => { const error = err instanceof Error ? err : new Error(String(err)); childLog.error(error); childLog.emitNow(); }).finally(() => { try { lifecycle?.onChildExit?.(childLog); } catch (hookError) { console.warn( "[autotel] fork onChildExit hook threw:", hookError ); } childSpan.end(); }); }); }); } }; } // src/drain-toolkit.ts var DEFAULT_TIMEOUT_MS = 5e3; var DEFAULT_RETRIES = 2; function delay(ms) { return new Promise((resolve) => { const t = setTimeout(resolve, ms); t.unref?.(); }); } async function postWithRetry(options) { const { name, request: request2, timeoutMs, retries } = options; const attempts = Math.max(1, retries); let lastError; for (let attempt = 1; attempt <= attempts; attempt++) { const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), timeoutMs); timeout.unref?.(); try { const response = await fetch(request2.url, { method: "POST", headers: request2.headers, body: request2.body, signal: controller.signal }); if (!response.ok) { throw new Error( `[autotel/${name}] HTTP ${response.status} draining ${request2.url}` ); } return; } catch (error) { lastError = error; if (attempt < attempts) { await delay(100 * attempt); } } finally { clearTimeout(timeout); } } throw lastError; } function defineDrain(options) { return async (ctx2) => { const contexts = Array.isArray(ctx2) ? ctx2 : [ctx2]; if (contexts.length === 0) return; const config = await options.resolve(); if (!config) return; const payloads = options.transform ? options.transform(contexts) : contexts; if (payloads.length === 0) return; try { await options.send(payloads, config); } catch (error) { console.error(`[autotel/${options.name}] drain failed:`, error); } }; } function defineHttpDrain(options) { return defineDrain({ name: options.name, resolve: options.resolve, transform: options.transform, send: async (payloads, config) => { const request2 = options.encode(payloads, config); if (!request2) return; const timeoutMs = options.resolveTimeoutMs?.(config) ?? options.timeoutMs ?? DEFAULT_TIMEOUT_MS; const retries = options.resolveRetries?.(config) ?? options.retries ?? DEFAULT_RETRIES; await postWithRetry({ name: options.name, request: request2, timeoutMs, retries }); } }); } // src/enricher-toolkit.ts function isPlainObject(value) { return value !== null && typeof value === "object" && !Array.isArray(value); } function mergeInto2(target, source) { for (const key in source) { const sourceVal = source[key]; if (sourceVal === void 0) continue; const targetVal = target[key]; if (isPlainObject(sourceVal) && isPlainObject(targetVal)) { mergeInto2(targetVal, sourceVal); } else { target[key] = sourceVal; } } } function defineEnricher(def, options = {}) { return (ctx2) => { let computed; try { computed = def.compute(ctx2); } catch (error) { console.error(`[autotel/${def.name}] enrich failed:`, error); return; } if (!computed) return; if (options.overwrite || !isPlainObject(ctx2.event[def.field])) { ctx2.event[def.field] = computed; return; } mergeInto2( ctx2.event[def.field], computed ); }; } var GEN_AI_DURATION_BUCKETS_SECONDS = Object.freeze( [0.01, 0.05, 0.1, 0.25, 0.5, 1, 2, 5, 10, 20, 30, 60, 120, 300] ); var GEN_AI_TOKEN_USAGE_BUCKETS = Object.freeze([ 1, 4, 16, 64, 256, 1024, 4096, 16384, 65536, 262144, 1048576, 4194304 ]); var GEN_AI_COST_USD_BUCKETS = Object.freeze([ 1e-5, 1e-4, 1e-3, 5e-3, 0.01, 0.05, 0.1, 0.5, 1, 5, 10, 50 ]); function llmHistogramAdvice(kind) { const boundaries = kind === "duration" ? GEN_AI_DURATION_BUCKETS_SECONDS : kind === "tokens" ? GEN_AI_TOKEN_USAGE_BUCKETS : GEN_AI_COST_USD_BUCKETS; return { advice: { explicitBucketBoundaries: [...boundaries] } }; } function genAiMetricViews(extra = []) { const defaults = [ { instrumentName: "gen_ai.client.operation.duration", kind: "duration" }, { instrumentName: "gen_ai.client.token.usage", kind: "tokens" }, // Autotel-emitted cost metric. No-op if you don't emit it. { instrumentName: "gen_ai.client.cost.usd", kind: "cost" } ]; return [...defaults, ...extra].map( ({ instrumentName, kind }) => ({ instrumentName, aggregation: { type: AggregationType.EXPLICIT_BUCKET_HISTOGRAM, options: { boundaries: kind === "duration" ? [...GEN_AI_DURATION_BUCKETS_SECONDS] : kind === "tokens" ? [...GEN_AI_TOKEN_USAGE_BUCKETS] : [...GEN_AI_COST_USD_BUCKETS] } } }) ); } // src/gen-ai-events.ts function recordPromptSent(ctx2, event = {}) { emitCorrelatedEvent(ctx2, "gen_ai.prompt.sent", buildPromptSentAttrs(event)); } function recordResponseReceived(ctx2, event = {}) { emitCorrelatedEvent( ctx2, "gen_ai.response.received", buildResponseAttrs(event) ); } function recordRetry(ctx2, event) { emitCorrelatedEvent(ctx2, "gen_ai.retry", buildRetryAttrs(event)); } function recordToolCall(ctx2, event) { emitCorrelatedEvent(ctx2, "gen_ai.tool.call", buildToolCallAttrs(event)); } function recordStreamFirstToken(ctx2, event = {}) { emitCorrelatedEvent( ctx2, "gen_ai.stream.first_token", buildStreamFirstTokenAttrs(event) ); } function buildPromptSentAttrs(event) { const attrs2 = {}; if (event.model) attrs2["gen_ai.request.model"] = event.model; if (event.promptTokens !== void 0) attrs2["gen_ai.usage.input_tokens"] = event.promptTokens; if (event.messageCount !== void 0) attrs2["gen_ai.request.message_count"] = event.messageCount; if (event.operation) attrs2["gen_ai.operation.name"] = event.operation; return attrs2; } function buildResponseAttrs(event) { const attrs2 = {}; if (event.model) attrs2["gen_ai.response.model"] = event.model; if (event.promptTokens !== void 0) attrs2["gen_ai.usage.input_tokens"] = event.promptTokens; if (event.completionTokens !== void 0) attrs2["gen_ai.usage.output_tokens"] = event.completionTokens; if (event.totalTokens !== void 0) attrs2["gen_ai.usage.total_tokens"] = event.totalTokens; if (event.finishReasons && event.finishReasons.length > 0) { attrs2["gen_ai.response.finish_reasons"] = event.finishReasons.join(","); } return attrs2; } function buildRetryAttrs(event) { const attrs2 = { "retry.attempt": event.attempt }; if (event.reason) attrs2["retry.reason"] = event.reason; if (event.delayMs !== void 0) attrs2["retry.delay_ms"] = event.delayMs; if (event.statusCode !== void 0) attrs2["http.response.status_code"] = event.statusCode; return attrs2; } function buildToolCallAttrs(event) { const attrs2 = { "gen_ai.tool.name": event.toolName }; if (event.toolCallId) attrs2["gen_ai.tool.call.id"] = event.toolCallId; if (event.arguments) attrs2["gen_ai.tool.arguments"] = event.arguments; return attrs2; } function buildStreamFirstTokenAttrs(event) { const attrs2 = {}; if (event.tokensSoFar !== void 0) attrs2["gen_ai.stream.tokens_so_far"] = event.tokensSoFar; return attrs2; } export { GEN_AI_COST_USD_BUCKETS, GEN_AI_DURATION_BUCKETS_SECONDS, GEN_AI_TOKEN_USAGE_BUCKETS, defineDrain, defineEnricher, defineHttpDrain, flush, genAiMetricViews, getRequestLogger, llmHistogramAdvice, recordPromptSent, recordResponseReceived, recordRetry, recordStreamFirstToken, recordToolCall, runWithRequestContext, shutdown, trace2 as trace }; //# sourceMappingURL=index.js.map //# sourceMappingURL=index.js.map