autotel
Version:
Write Once, Observe Anywhere
490 lines (473 loc) • 19.7 kB
TypeScript
export { A as AutotelConfig, i as init } from './init-FiR_glVc.js';
import { Span, Context } from '@opentelemetry/api';
export { Context, ROOT_CONTEXT, Span, SpanContext, SpanKind, Link as SpanLink, SpanStatusCode, TextMapGetter, TextMapSetter, Tracer, context, trace as otelTrace, propagation } from '@opentelemetry/api';
import { SpanProcessor, ReadableSpan } from '@opentelemetry/sdk-trace-base';
export { FilteringSpanProcessor, FilteringSpanProcessorOptions, SpanFilterPredicate } from './filtering-span-processor.js';
export { NORMALIZER_PATTERNS, NORMALIZER_PRESETS, SpanNameNormalizerConfig, SpanNameNormalizerFn, SpanNameNormalizerPreset, SpanNameNormalizingProcessor, SpanNameNormalizingProcessorOptions } from './span-name-normalizer.js';
import { AttributeRedactorConfig, AttributeRedactorPreset } from './attribute-redacting-processor.js';
export { AttributeRedactingProcessor, AttributeRedactingProcessorOptions, AttributeRedactorFn, REDACTOR_PATTERNS, REDACTOR_PRESETS, ValuePatternConfig, createAttributeRedactor, createRedactedSpan } from './attribute-redacting-processor.js';
export { InstrumentOptions, SpanOptions, WithBaggageOptions, WithNewContextOptions, ctx, instrument, span, trace, withBaggage, withNewContext, withTracing } from './functional.js';
import { EventSubscriber, EventAttributes, AutotelEventContext } from './event-subscriber.js';
export { FunnelStatus, OutcomeStatus } from './event-subscriber.js';
export { CORRELATION_ID_BAGGAGE_KEY, generateCorrelationId, getCorrelationId, getOrCreateCorrelationId, runWithCorrelationId, setCorrelationId, setCorrelationIdInBaggage } from './correlation-id.js';
import { T as TraceContext, A as AttributeValue } from './trace-context-t5X1AP-e.js';
export { d as defineBaggageSchema } from './trace-context-t5X1AP-e.js';
export { ParsedError, parseError } from './parse-error.js';
export { DrainPipelineOptions, PipelineDrainFn, createDrainPipeline } from './drain-pipeline.js';
export { AUTOTEL_SAMPLING_TAIL_EVALUATED, AUTOTEL_SAMPLING_TAIL_KEEP, AdaptiveSampler, AlwaysSampler, NeverSampler, RandomSampler, Sampler, SamplingContext, SamplingPreset, UserIdSampler, createLinkFromHeaders, extractLinksFromBatch, resolveSamplingPreset, samplingPresets } from './sampling.js';
export { Event, EventsOptions, getEvents, resetEvents } from './event.js';
export { Metric, MetricsOptions, getMetrics, resetMetrics } from './metric.js';
export { createCounter, createHistogram, createObservableGauge, createUpDownCounter, getMeter } from './metric-helpers.js';
export { TraceContext as OtelTraceContext, createDeterministicTraceId, enrichWithTraceContext, finalizeSpan, flattenMetadata, getActiveContext, getActiveSpan, getTraceContext, getTracer, isTracing, resolveTraceUrl, runWithSpan } from './trace-helpers.js';
export { getAutotelTracer, getAutotelTracerProvider, setAutotelTracerProvider } from './tracer-provider.js';
export { DBConfig, HTTPConfig, LLMConfig, MessagingConfig, traceDB, traceHTTP, traceLLM, traceMessaging } from './semantic-helpers.js';
export { HTTPAttributes, ServiceAttributes, URLAttributes, httpRequestHeaderAttribute, httpResponseHeaderAttribute } from './semantic-conventions.js';
export { a as AttributeGuardrails, A as AttributePolicy, C as ClientAttrs, b as CloudAttrs, c as CodeAttrs, d as ContainerAttrs, D as DBAttrs, e as DeploymentAttrs, f as DeviceAttrs, E as ErrorAttrs, g as ExceptionAttrs, F as FaaSAttrs, h as FeatureFlagAttrs, G as GenAIAttrs, i as GraphQLAttrs, H as HTTPClientAttrs, j as HTTPServerAttrs, K as K8sAttrs, M as MessagingAttrs, N as NetworkAttrs, O as OTelAttrs, P as PeerAttrs, k as ProcessAttrs, R as RPCAttrs, l as ServerAddressAttrs, m as ServiceAttrs, S as SessionAttrs, T as TLSAttrs, n as ThreadAttrs, o as URLAttrs, U as UserAttrs, p as attrs, q as autoRedactPII, s as dbClient, u as httpClient, v as httpServer, w as identify, x as mergeAttrs, y as mergeServiceResource, z as request, B as safeSetAttributes, I as setDevice, J as setError, L as setException, Q as setSession, V as setUser, W as validateAttribute } from './utils-Buel3cj0.js';
export { ConsumerConfig, ConsumerContext, LagMetricsConfig, MessagingOperation, MessagingSystem, ProducerConfig, ProducerContext, traceConsumer, traceProducer } from './messaging.js';
export { BaggageError, BaggageFieldDefinition, BaggageFieldType, BusinessBaggage, BusinessBaggageValues, SafeBaggageOptions, SafeBaggageSchema, createSafeBaggageSchema } from './business-baggage.js';
export { StepConfig, StepContext, StepStatus, WorkflowConfig, WorkflowContext, WorkflowStatus, getCurrentWorkflowContext, isInWorkflow, traceStep, traceWorkflow } from './workflow.js';
import '@opentelemetry/sdk-node';
import '@opentelemetry/resources';
import './logger.js';
import 'pino';
import '@opentelemetry/sdk-metrics';
import '@opentelemetry/sdk-logs';
import './processors.js';
import 'node:async_hooks';
import './event-testing.js';
import './metric-testing.js';
/** Standalone string redaction for use outside the span processor pipeline. */
type StringRedactor = (value: string) => string;
declare function createStringRedactor(config: AttributeRedactorConfig | AttributeRedactorPreset): StringRedactor;
/**
* Span processor that copies baggage entries to span attributes
*
* This makes baggage visible in trace UIs without manual attribute setting.
* Enabled via init({ baggage: true }) or init({ baggage: 'custom-prefix' })
*/
interface BaggageSpanProcessorOptions {
/**
* Prefix for baggage attributes
* @default 'baggage.'
*/
prefix?: string;
}
/**
* 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
* ```
*/
declare class BaggageSpanProcessor implements SpanProcessor {
private readonly prefix;
constructor(options?: BaggageSpanProcessorOptions);
onStart(span: Span, parentContext: Context): void;
onEnd(_span: ReadableSpan): void;
shutdown(): Promise<void>;
forceFlush(): Promise<void>;
}
/**
* 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.
*/
/**
* Operation context that flows through async operations
*/
interface OperationContext {
/**
* The name of the current operation
* This is set by trace() or span() and can be read by events
*/
name: string;
}
/**
* 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);
* }
* ```
*/
declare function getOperationContext(): OperationContext | undefined;
/**
* 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';
* });
* ```
*/
declare function runInOperationContext<T>(name: string, fn: () => T): T;
/**
* Token bucket rate limiter for event subscribers
*
* Prevents overwhelming downstream events platforms with too many events.
* Uses token bucket algorithm for smooth rate limiting with burst capacity.
*/
interface RateLimiterConfig {
/** Maximum events per second (default: 100) */
maxEventsPerSecond: number;
/** Burst capacity - max events in a single burst (default: 2x rate) */
burstCapacity?: number;
}
/**
* Events event queue with batching, backpressure, retry logic, rate limiting, and OTel metrics
*
* Exposes delivery pipeline metrics for observability:
* - autotel.event_delivery.queue.size - Current queue size
* - autotel.event_delivery.queue.oldest_age_ms - Age of oldest event in queue
* - autotel.event_delivery.queue.delivered - Successfully delivered events
* - autotel.event_delivery.queue.failed - Failed event deliveries
* - autotel.event_delivery.queue.dropped - Dropped events with reason
* - autotel.event_delivery.queue.latency_ms - Delivery latency histogram
* - autotel.event_delivery.subscriber.health - Subscriber health (1=healthy, 0=unhealthy)
*/
interface EventData {
name: string;
attributes?: EventAttributes;
timestamp: number;
/** Internal: correlation ID for debug breadcrumbs */
_correlationId?: string;
/** Internal: trace ID for debug breadcrumbs */
_traceId?: string;
/** Autotel context for trace correlation (passed to subscribers) */
autotel?: AutotelEventContext;
}
interface QueueConfig {
maxSize: number;
batchSize: number;
flushInterval: number;
maxRetries: number;
rateLimit?: RateLimiterConfig;
}
/**
* 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
*/
declare class EventQueue {
private queue;
private flushTimer;
private readonly config;
private readonly subscribers;
private readonly rateLimiter;
private flushPromise;
private isShuttingDown;
private metrics;
private observableCleanups;
private subscriberHealthy;
constructor(subscribers: EventSubscriber[], config?: Partial<QueueConfig>);
/**
* Initialize OTel metrics for queue observability
*/
private initMetrics;
/**
* Record a dropped event with reason and emit debug breadcrumb
*/
private recordDropped;
/**
* Record permanent delivery failure (after all retries exhausted)
* Increments failed counter and logs error
*/
private recordFailed;
/**
* Mark subscriber as unhealthy on transient failure (without incrementing failed counter)
* Used during retry attempts - only recordFailed should increment the counter
*/
private markSubscriberUnhealthy;
/**
* Record successful delivery
*/
private recordDelivered;
/**
* Enqueue an event for sending
*
* Backpressure policy:
* - Drops oldest event and logs warning if queue is full (same behavior in all environments)
*/
enqueue(event: EventData): void;
/**
* Schedule a batch flush if not already scheduled
*/
private scheduleBatchFlush;
/**
* Flush a batch of events
* Uses promise-based concurrency control to prevent race conditions
*/
private flushBatch;
/**
* Internal flush implementation
*/
private doFlushBatch;
/**
* 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.
*/
private sendWithRetry;
/**
* 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).
*/
private sendToSubscribers;
/**
* 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).
*/
private sendEventToSubscribers;
/**
* 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.
*/
flush(): Promise<void>;
/**
* Flush remaining events and permanently disable the queue (reject new events).
* Use for process/SDK shutdown; use flush() for periodic or span-end drain.
*/
shutdown(): Promise<void>;
/**
* Cleanup observable metric callbacks to prevent memory leaks
* Call this when destroying the EventQueue instance
*/
cleanup(): void;
/**
* Get queue size (for testing/debugging)
*/
size(): number;
/**
* Get subscriber health status (for testing/debugging)
*/
getSubscriberHealth(): Map<string, boolean>;
/**
* Check if a specific subscriber is healthy
*/
isSubscriberHealthy(subscriberName: string): boolean;
/**
* Manually mark a subscriber as healthy or unhealthy
* (used for circuit breaker integration)
*/
setSubscriberHealth(subscriberName: string, healthy: boolean): void;
}
/**
* Global track() function for business events
*
* Simple, no instantiation needed, auto-attaches trace context
*/
/**
* 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 })
* }
* }
* ```
*/
declare function track<Events extends Record<string, any> = Record<string, any>>(event: keyof Events & string, data?: Events[typeof event]): void;
/**
* Get events queue (for flush/shutdown)
* @internal
*/
declare function getEventQueue(): EventQueue | null;
/**
* Graceful shutdown with flush and cleanup
*/
/**
* 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
* ```
*/
declare function flush(options?: {
timeout?: number;
forShutdown?: boolean;
}): Promise<void>;
/**
* 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)
* })
* ```
*/
declare function shutdown(): Promise<void>;
declare function runWithRequestContext<T>(ctx: TraceContext, fn: () => T): T;
interface RequestLogger {
set(fields: Record<string, unknown>): void;
info(message: string, fields?: Record<string, unknown>): void;
warn(message: string, fields?: Record<string, unknown>): void;
error(error: Error | string, fields?: Record<string, unknown>): void;
getContext(): Record<string, unknown>;
emitNow(overrides?: Record<string, unknown>): RequestLogSnapshot;
}
interface RequestLogSnapshot {
timestamp: string;
traceId: string;
spanId: string;
correlationId: string;
context: Record<string, unknown>;
}
interface RequestLoggerOptions {
/** Callback invoked by emitNow() for manual fan-out. */
onEmit?: (snapshot: RequestLogSnapshot) => void | Promise<void>;
}
declare function getRequestLogger(ctx?: TraceContext, options?: RequestLoggerOptions): RequestLogger;
interface StructuredErrorInput {
message: string;
why?: string;
fix?: string;
link?: string;
code?: string | number;
status?: number;
cause?: unknown;
details?: Record<string, unknown>;
name?: string;
}
interface StructuredError extends Error {
why?: string;
fix?: string;
link?: string;
code?: string | number;
status?: number;
details?: Record<string, unknown>;
}
declare function createStructuredError(input: StructuredErrorInput): StructuredError;
declare function getStructuredErrorAttributes(error: Error): Record<string, AttributeValue>;
declare function recordStructuredError(ctx: Pick<TraceContext, 'recordException' | 'setAttributes' | 'setStatus'>, error: Error): void;
/**
* Convert an unknown value to an OTel-compatible AttributeValue.
* Returns undefined when the value cannot be represented.
*/
declare function toAttributeValue(value: unknown): AttributeValue | undefined;
/**
* Recursively flatten a nested object into dot-notation OTel attributes.
* Includes circular reference protection via WeakSet.
*/
declare function flattenToAttributes(fields: Record<string, unknown>, prefix?: string): Record<string, AttributeValue>;
/**
* Format milliseconds into a human-readable duration string.
*
* @example
* formatDuration(45) // "45ms"
* formatDuration(1234) // "1.2s"
* formatDuration(65000) // "1m 5s"
*/
declare function formatDuration(ms: number): string;
export { AttributeRedactorConfig, AttributeRedactorPreset, BaggageSpanProcessor, type BaggageSpanProcessorOptions, EventAttributes, EventSubscriber, type OperationContext, type RequestLogSnapshot, type RequestLogger, type RequestLoggerOptions, type StringRedactor, type StructuredError, type StructuredErrorInput, TraceContext, createStringRedactor, createStructuredError, flattenToAttributes, flush, formatDuration, getEventQueue, getOperationContext, getRequestLogger, getStructuredErrorAttributes, recordStructuredError, runInOperationContext, runWithRequestContext, shutdown, toAttributeValue, track };