UNPKG

autotel

Version:
451 lines (448 loc) 18.7 kB
import * as _opentelemetry_api from '@opentelemetry/api'; import { Span } from '@opentelemetry/api'; import { Sampler } from './sampling.js'; import { T as TraceContext } from './trace-context-t5X1AP-e.js'; import './logger.js'; import 'pino'; /** * Helper type to extract function signature from factory pattern * This helps TypeScript infer types correctly for factory functions */ type ExtractFunctionSignature<T> = T extends (ctx: TraceContext) => infer F ? F extends (...args: infer Args) => infer Return ? (...args: Args) => Return : never : never; /** * Helper type to exclude functions that return functions from immediate execution overloads */ type ExcludeFactoryReturn<T> = T extends (ctx: TraceContext) => infer F ? F extends (...args: any[]) => any ? never : T : T; /** * Common options for functional tracing */ interface TracingOptions<TArgs extends unknown[] = unknown[], TReturn = unknown> { /** * Span name (highest priority) * If provided, this is used as the span name */ name?: string; /** * Service name (used to compose final span name) * If name not provided, span name becomes: ${serviceName}.${functionName} */ serviceName?: string; /** * Sampling strategy * @default AlwaysSampler */ sampler?: Sampler; /** * Enable metrics collection (counter, histogram) * @default false */ withMetrics?: boolean; /** * Extract attributes from function arguments */ attributesFromArgs?: (args: TArgs) => Record<string, unknown>; /** * Extract attributes from function result */ attributesFromResult?: (result: TReturn) => Record<string, unknown>; /** * Start a new root span instead of creating a child * Useful for serverless entry points * @default false */ startNewRoot?: boolean; /** * Flush events queue when span ends * Only flushes on root spans (to avoid excessive flushing) * @default true */ flushOnRootSpanEnd?: boolean; /** * Span kind for semantic convention compliance * Used for messaging (PRODUCER/CONSUMER), HTTP (CLIENT/SERVER), etc. * @default SpanKind.INTERNAL */ spanKind?: _opentelemetry_api.SpanKind; } /** * Options for instrument() batch instrumentation */ interface InstrumentOptions<T extends Record<string, InstrumentableFunction> = Record<string, InstrumentableFunction>> extends TracingOptions { /** Functions to instrument */ functions: T; /** * Per-function configuration overrides */ overrides?: Record<string, Partial<TracingOptions>>; /** * Functions to skip (won't be instrumented) * Supports: * - String keys: 'functionName' * - RegExp: /^_internal/ * - Predicate: (key, fn) => boolean * * By default, functions starting with _ are skipped */ skip?: (string | RegExp | ((key: string, fn: Function) => boolean))[]; } type InstrumentableFunction<TArgs extends unknown[] = unknown[], TReturn = unknown> = ((...args: TArgs) => TReturn | Promise<TReturn>) & { displayName?: string; name?: string; }; /** * Context object that lazily evaluates the active span on property access * * Access trace context directly without function call syntax. * * @example * ```typescript * import { trace, ctx } from 'autotel' * * export const createUser = trace(async (data) => { * // Direct property access - no function call! * if (ctx.traceId) { * ctx.setAttribute('user.id', data.id) * console.log('Trace:', ctx.traceId) * } * }) * ``` */ declare const ctx: {}; /** * Approach 1: trace() - Zero-ceremony HOF * * Wrap a single function with automatic tracing. * The function receives a context object as the first parameter. * * Supports two patterns: * 1. **Factory pattern** - Returns a traced function: `trace(ctx => (...args) => result)` * 2. **Immediate execution** - Executes immediately with tracing: `trace(ctx => result)` * * @example Auto-inferred name - Plain function * ```typescript * export const createUser = trace(async (data) => { * return await db.users.create(data) * }) * // → Traced as "createUser" * ``` * * @example Auto-inferred name - Factory pattern (with ctx access) * ```typescript * export const createUser = trace(ctx => async (data) => { * ctx.setAttribute('user.id', data.id) * return await db.users.create(data) * }) * // → Traced as "createUser", returns wrapped function * ``` * * @example Immediate execution - Execute once with tracing * ```typescript * // Wraps an existing function and executes immediately * function timed<T>(fn: () => Promise<T>): Promise<T> { * return trace(async (ctx) => { * ctx.setAttribute('operation', 'timed') * return await fn() * }) * } * // → Executes immediately, returns result directly * ``` * * @example Custom name - Plain function * ```typescript * export const createUser = trace('user.create', async (data) => { * return await db.users.create(data) * }) * // → Traced as "user.create" * ``` * * @example Custom name - Factory pattern * ```typescript * export const createUser = trace('user.create', ctx => async (data) => { * ctx.setAttribute('user.id', data.id) * return await db.users.create(data) * }) * // → Traced as "user.create" * ``` * * @example Custom name - Immediate execution * ```typescript * const result = trace('fetch.user', async (ctx) => { * ctx.setAttribute('userId', '123') * return await fetchUser('123') * }) * // → Executes immediately with span name "fetch.user" * ``` * * @example Full options - Plain function * ```typescript * export const createUser = trace({ * name: 'user.create', * sampler: new AdaptiveSampler(), * withMetrics: true * }, async (data) => { * return await db.users.create(data) * }) * ``` * * @example Full options - Factory pattern * ```typescript * export const createUser = trace({ * name: 'user.create', * sampler: new AdaptiveSampler(), * withMetrics: true * }, ctx => async (data) => { * ctx.setAttribute('user.id', data.id) * return await db.users.create(data) * }) * ``` */ declare function trace<TBaggage extends Record<string, unknown> | undefined = undefined, TReturn = unknown>(fn: (ctx: TraceContext<TBaggage>) => TReturn): TReturn; declare function trace<TBaggage extends Record<string, unknown> | undefined = undefined>(fnFactory: (ctx: TraceContext<TBaggage>) => () => unknown): () => unknown; declare function trace<TBaggage extends Record<string, unknown> | undefined = undefined, TArgs extends unknown[] = unknown[], TReturn = unknown>(fnFactory: (ctx: TraceContext<TBaggage>) => (...args: TArgs) => TReturn): (...args: TArgs) => TReturn; declare function trace<TReturn = unknown>(fnFactory: (ctx: TraceContext) => () => TReturn): () => TReturn; declare function trace<TFactory extends (ctx: TraceContext) => (...args: unknown[]) => unknown>(fnFactory: TFactory): ExtractFunctionSignature<TFactory>; declare function trace<TArgs extends unknown[], TReturn = unknown>(fnFactory: (ctx: TraceContext) => (...args: TArgs) => TReturn): (...args: TArgs) => TReturn; declare function trace<TReturn = unknown>(fn: () => TReturn): () => TReturn; declare function trace<TArgs extends unknown[], TReturn = unknown>(fn: (...args: TArgs) => TReturn): (...args: TArgs) => TReturn; declare function trace<TReturn = unknown>(name: string, fn: ExcludeFactoryReturn<(ctx: TraceContext) => TReturn>): TReturn; declare function trace<TReturn = unknown>(name: string, fnFactory: (ctx: TraceContext) => () => TReturn): () => TReturn; declare function trace<TArgs extends unknown[], TReturn>(name: string, fnFactory: (ctx: TraceContext) => (...args: TArgs) => TReturn): (...args: TArgs) => TReturn; declare function trace<TFactory extends (ctx: TraceContext) => (...args: unknown[]) => unknown>(name: string, fnFactory: TFactory): ExtractFunctionSignature<TFactory>; declare function trace<TArgs extends unknown[] = unknown[], TReturn = unknown>(name: string, fnFactory: (ctx: TraceContext) => (...args: TArgs) => TReturn): (...args: TArgs) => TReturn; declare function trace<TArgs extends unknown[] = unknown[], TReturn = unknown>(name: string, fn: (...args: TArgs) => TReturn): (...args: TArgs) => TReturn; declare function trace<TReturn = unknown>(options: TracingOptions<[], TReturn>, fn: (ctx: TraceContext) => TReturn): TReturn; declare function trace<TReturn = unknown>(options: TracingOptions<[], TReturn>, fnFactory: (ctx: TraceContext) => () => TReturn): () => TReturn; declare function trace<TArgs extends unknown[], TReturn>(options: TracingOptions<TArgs, TReturn>, fnFactory: (ctx: TraceContext) => (...args: TArgs) => TReturn): (...args: TArgs) => TReturn; declare function trace<TArgs extends unknown[] = unknown[], TReturn = unknown>(options: TracingOptions<TArgs, TReturn>, fnFactory: (ctx: TraceContext) => (...args: TArgs) => TReturn): (...args: TArgs) => TReturn; declare function trace<TArgs extends unknown[] = unknown[], TReturn = unknown>(options: TracingOptions<TArgs, TReturn>, fn: (...args: TArgs) => TReturn): (...args: TArgs) => TReturn; declare function trace<TReturn = unknown>(fn: (ctx: TraceContext) => Promise<TReturn>): Promise<TReturn>; declare function trace(fnFactory: (ctx: TraceContext) => () => Promise<unknown>): () => Promise<unknown>; declare function trace<TArgs extends unknown[], TReturn>(fnFactory: (ctx: TraceContext) => (...args: TArgs) => Promise<TReturn>): (...args: TArgs) => Promise<TReturn>; declare function trace<TReturn = unknown>(fnFactory: (ctx: TraceContext) => () => Promise<TReturn>): () => Promise<TReturn>; declare function trace<TFactory extends (ctx: TraceContext) => (...args: unknown[]) => Promise<unknown>>(fnFactory: TFactory): ExtractFunctionSignature<TFactory>; declare function trace<TArgs extends unknown[] = unknown[], TReturn = unknown>(fnFactory: (ctx: TraceContext) => (...args: TArgs) => Promise<TReturn>): (...args: TArgs) => Promise<TReturn>; declare function trace<TReturn = unknown>(fn: () => Promise<TReturn>): () => Promise<TReturn>; declare function trace<TArgs extends unknown[] = unknown[], TReturn = unknown>(fn: (...args: TArgs) => Promise<TReturn>): (...args: TArgs) => Promise<TReturn>; declare function trace<TReturn = unknown>(name: string, fn: ExcludeFactoryReturn<(ctx: TraceContext) => Promise<TReturn>>): Promise<TReturn>; declare function trace<TReturn = unknown>(name: string, fnFactory: (ctx: TraceContext) => () => Promise<TReturn>): () => Promise<TReturn>; declare function trace<TArgs extends unknown[], TReturn>(name: string, fnFactory: (ctx: TraceContext) => (...args: TArgs) => Promise<TReturn>): (...args: TArgs) => Promise<TReturn>; declare function trace<TFactory extends (ctx: TraceContext) => (...args: unknown[]) => Promise<unknown>>(name: string, fnFactory: TFactory): ExtractFunctionSignature<TFactory>; declare function trace<TArgs extends unknown[] = unknown[], TReturn = unknown>(name: string, fnFactory: (ctx: TraceContext) => (...args: TArgs) => Promise<TReturn>): (...args: TArgs) => Promise<TReturn>; declare function trace<TArgs extends unknown[] = unknown[], TReturn = unknown>(name: string, fn: (...args: TArgs) => Promise<TReturn>): (...args: TArgs) => Promise<TReturn>; declare function trace<TReturn = unknown>(options: TracingOptions<[], TReturn>, fn: (ctx: TraceContext) => Promise<TReturn>): Promise<TReturn>; declare function trace<TReturn = unknown>(options: TracingOptions<[], TReturn>, fnFactory: (ctx: TraceContext) => () => Promise<TReturn>): () => Promise<TReturn>; declare function trace<TArgs extends unknown[], TReturn>(options: TracingOptions<TArgs, TReturn>, fnFactory: (ctx: TraceContext) => (...args: TArgs) => Promise<TReturn>): (...args: TArgs) => Promise<TReturn>; declare function trace<TArgs extends unknown[] = unknown[], TReturn = unknown>(options: TracingOptions<TArgs, TReturn>, fnFactory: (ctx: TraceContext) => (...args: TArgs) => Promise<TReturn>): (...args: TArgs) => Promise<TReturn>; declare function trace<TArgs extends unknown[] = unknown[], TReturn = unknown>(options: TracingOptions<TArgs, TReturn>, fn: (...args: TArgs) => Promise<TReturn>): (...args: TArgs) => Promise<TReturn>; /** * Approach 2: withTracing() - Middleware-style composable wrapper * * Returns a HOF that wraps functions with tracing. * Perfect for composition and reusable configuration. * * @example Standard usage * ```typescript * export const createUser = withTracing({ * name: 'user.create' * })(ctx => async (data) => { * ctx.setAttribute('user.id', data.id) * return await db.users.create(data) * }) * ``` * * @example Composable * ```typescript * const trace = withTracing({ serviceName: 'user' }) * * export const createUser = trace(ctx => async (data) => { }) * export const updateUser = trace(ctx => async (id, data) => { }) * ``` * * @example With other middleware * ```typescript * export const createUser = compose( * withAuth({ role: 'admin' }), * withTracing({ name: 'user.create' }), * withRateLimit({ max: 100 }) * )(ctx => async (data) => { }) * ``` */ declare function withTracing<TArgs extends unknown[] = unknown[], TReturn = unknown>(options?: TracingOptions<TArgs, TReturn>): (fnFactory: (ctx: TraceContext) => (...args: TArgs) => TReturn | Promise<TReturn>) => (...args: TArgs) => TReturn | Promise<TReturn>; /** * Approach 3: instrument() - Batch auto-instrumentation * * Instrument an entire module/object at once. * Closest to @Instrumented decorator pattern. * * @example Basic usage * ```typescript * export default instrument({ * functions: { * createUser: async (data) => { }, * updateUser: async (id, data) => { }, * deleteUser: async (id) => { } * }, * serviceName: 'user', * sampler: new AdaptiveSampler() * }) * // → Traced as "user.createUser", "user.updateUser", "user.deleteUser" * ``` * * @example Per-function overrides * ```typescript * export default instrument({ * functions: { * createUser: async (data) => { }, * deleteUser: async (id) => { } * }, * serviceName: 'user', * overrides: { * deleteUser: { * sampler: new AlwaysSampler(), * withMetrics: true * } * } * }) * ``` * * @example Skip functions * ```typescript * export default instrument({ * functions: { * createUser: async (data) => { }, * _internal: async () => { }, // Auto-skipped (_-prefix) * deleteUser: async (id) => { } * }, * serviceName: 'user', * skip: [/^test/, (key) => key.includes('debug')] * }) * ``` */ declare function instrument<T extends Record<string, InstrumentableFunction>>(options: InstrumentOptions<T>): T; /** * Options for span() function */ interface SpanOptions { /** Span name */ name: string; /** Attributes to set on the span */ attributes?: Record<string, string | number | boolean>; } /** * Execute a function within a named span * * Useful for adding tracing to specific code blocks without wrapping * the entire function. Supports both synchronous and asynchronous functions. * * @example * ```typescript * // Async function * async function processOrder(order: Order) { * await span({ * name: 'payment.charge', * attributes: { amount: order.total } * }, async (span) => { * await chargeCustomer(order); * }) * } * * // Sync function * function calculateTotal(items: Item[]) { * return span({ * name: 'calculateTotal', * attributes: { itemCount: items.length } * }, (span) => { * return items.reduce((sum, item) => sum + item.price, 0); * }) * } * ``` */ declare function span<T = unknown>(options: SpanOptions, fn: (span: Span) => T): T; declare function span<T = unknown>(options: SpanOptions, fn: (span: Span) => Promise<T>): Promise<T>; /** * Options for withNewContext() function */ interface WithNewContextOptions<T = unknown> { /** Function to execute in new root context */ fn: () => Promise<T>; } /** * Execute a function in a new root context (prevents span propagation) * * Useful when you want to start a completely new trace without * parent-child relationships. * * @example * ```typescript * async function handleWebhook(payload: WebhookPayload) { * // This creates a new root trace, not connected to the HTTP request trace * await withNewContext({ * fn: async () => { * await trace(ctx => async () => { * await processWebhookPayload(payload) * })() * } * }) * } * ``` */ declare function withNewContext<T = unknown>(options: WithNewContextOptions<T>): Promise<T>; /** * Options for withBaggage() function */ interface WithBaggageOptions<T = unknown> { /** Baggage entries to set (key-value pairs) */ baggage: Record<string, string>; /** Function to execute with the updated baggage */ fn: () => T | Promise<T>; } /** * Execute a function with updated baggage entries * * Baggage is immutable in OpenTelemetry, so this helper creates a new context * with the specified baggage entries and runs the function within that context. * All child spans created within the function will inherit the baggage. * * @example Setting baggage for downstream services * ```typescript * import { trace, withBaggage } from 'autotel'; * * export const createOrder = trace((ctx) => async (order: Order) => { * // Set baggage that will be propagated to downstream HTTP calls * return await withBaggage({ * baggage: { * 'tenant.id': order.tenantId, * 'user.id': order.userId, * }, * fn: async () => { * // This HTTP call will include the baggage in headers * await fetch('/api/charge', { * method: 'POST', * body: JSON.stringify(order), * }); * }, * }); * }); * ``` * * @example Using with existing baggage * ```typescript * export const processOrder = trace((ctx) => async (order: Order) => { * // Read existing baggage * const tenantId = ctx.getBaggage('tenant.id'); * * // Add additional baggage entries * return await withBaggage({ * baggage: { * 'order.id': order.id, * 'order.amount': String(order.amount), * }, * fn: async () => { * await charge(order); * }, * }); * }); * ``` */ declare function withBaggage<T = unknown>(options: WithBaggageOptions<T>): T | Promise<T>; export { type InstrumentOptions, type SpanOptions, TraceContext, type TracingOptions, type WithBaggageOptions, type WithNewContextOptions, ctx, instrument, span, trace, withBaggage, withNewContext, withTracing };