UNPKG

autotel

Version:
476 lines 18.2 kB
import { n as TraceContext } from "./trace-context-f7Xq0Q2q.cjs"; import { m as Sampler } from "./sampling-Er217Mge.cjs"; import { Attributes, Span, SpanKind } from "@opentelemetry/api"; //#region src/functional-wrapper.d.ts type WrappedFunction<TArgs extends unknown[], TReturn> = (...args: TArgs) => TReturn | Promise<TReturn>; /** * Constraint alias for `instrument()` and friends. `never[]` parameters make * every concretely-typed function satisfy the constraint under * `strictFunctionTypes` while preserving its inferred call signature. */ type AnyInstrumentable = ((...args: never[]) => unknown) & { displayName?: string; name?: string; }; /** 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 `${serviceName}.${functionName}` when no * explicit name is provided. */ serviceName?: string; /** * Sampling strategy. * @default AlwaysSampler */ sampler?: Sampler; /** * Enable call and duration metrics. * @default false */ withMetrics?: boolean; /** Extract attributes from function arguments. */ attributesFromArgs?: (args: TArgs) => Attributes; /** Extract attributes from the function result. */ /** Receives the resolved value when the function returns a Promise. */ attributesFromResult?: (result: Awaited<TReturn>) => Attributes; /** * Capture arguments on the span as the truncated JSON `autotel.input` * attribute. One argument is captured directly; multiple arguments are an * array. Avoid this for secrets/PII, or pair it with a redacting processor. */ captureInput?: boolean; /** * Capture the result on the span as the truncated JSON `autotel.output` * attribute. The same sensitive-data caveats as {@link captureInput} apply. */ captureOutput?: boolean; /** * Start a new root span instead of creating a child. * Useful for serverless entry points. * @default false */ startNewRoot?: boolean; /** * Flush telemetry when a root span ends. * @default true */ flushOnRootSpanEnd?: boolean; /** * OpenTelemetry span kind for semantic convention compliance. * @default SpanKind.INTERNAL */ spanKind?: SpanKind; /** * Classify a thrown value as a real error. Returning false treats the throw * as expected control flow: the span is marked OK, no exception is recorded, * and the original value is rethrown. This supports framework signals such * as `redirect()` and `notFound()`. * @default every throw is treated as an error */ isError?: (cause: unknown) => boolean; } /** Options for `instrument()` batch instrumentation. */ interface InstrumentOptions<T extends Record<string, AnyInstrumentable> = Record<string, AnyInstrumentable>> extends TracingOptions { /** Object whose function properties should be instrumented. */ functions: T; /** Per-function configuration overrides. */ overrides?: Record<string, Partial<TracingOptions>>; /** * Functions to skip. Supports string keys, regular expressions, and * predicates. Functions whose keys start with `_` are skipped by default. */ skip?: (string | RegExp | ((key: string, fn: Function) => boolean))[]; } /** Options for instrumenting one function with an explicit stable key. */ interface SingleInstrumentOptions<TFunction extends AnyInstrumentable = AnyInstrumentable> extends TracingOptions { /** Stable function key used for span naming. */ key: string; /** Function to instrument. */ fn: TFunction; } //#endregion //#region src/functional.d.ts /** * Complete trace context containing trace identifiers and span methods * * The ctx parameter in trace() functions provides: * - traceId, spanId, correlationId from the active span * - Span manipulation methods (setAttribute, setAttributes, setStatus, recordException) * * For custom context, access it directly in your functions (standard OpenTelemetry pattern). * * @example * ```typescript * import { trace } from 'autotel' * * export const createUser = withTracing({})((ctx) => async (data: CreateUserData) => { * // Get custom context directly (standard OTel approach) * const userId = getCurrentUserId() * const tenantId = getCurrentTenant() * * // Use ctx for span operations and trace IDs * ctx.setAttribute('user.id', data.id) * ctx.setAttribute('user.tenant', tenantId) * console.log(ctx.traceId) // Trace IDs available * }) * ``` */ /** Baggage entries: values that survive the W3C baggage header. */ type BaggageValues = Record<string, string | number | boolean>; /** * Get the autotel {@link TraceContext} for the currently active span. * * This is the ambient accessor for the functional API: instead of threading a * `ctx` parameter through a factory, call this inside any traced function (or a * helper it calls) to reach `setAttribute`, `setUser`, `getBaggage`, and the * rest of the context surface. Returns `undefined` when no span is active. * * @example * ```typescript * const getUser = trace(async function getUser(id: string) { * getActiveTraceContext()?.setAttribute('user.id', id); * return db.users.find(id); * }); * ``` * * @see getActiveSpan for the raw OpenTelemetry span * @see getRequestLogger which reads the active context when called with no args */ declare function getActiveTraceContext<TBaggage extends BaggageValues | undefined = undefined>(): TraceContext<TBaggage> | undefined; declare const ctx: TraceContext; /** * The ambient {@link ctx}, aimed at the **request** span rather than at * whatever span the calling code happens to be inside. * * Framework instrumentation nests spans per layer - express opens one per * middleware and per route handler - so `ctx.setAttribute()` from a shared * middleware records on a span that ends the moment `next()` fires. Attributes * that describe the request as a whole (the authenticated user, the tenant, the * plan) belong on the request span, which is what a backend shows as the * resource and what a canonical log line is built from. * * Falls back to the active span outside a request, and no-ops when nothing is * active at all, exactly as `ctx` does. * * @example * ```typescript * app.use((req, _res, next) => { * requestCtx.setAttributes({ user: req.user }); // user.id, user.plan, ... * next(); * }); * ``` */ declare const requestCtx: TraceContext; /** * Wrap a plain function with a traced wrapper, or run one named operation * immediately with {@link trace.run}. * * Every `trace(...)` call returns a **wrapper**. Nothing runs until you call * what you get back, so a `trace()` call can never execute your function at * module load. Reach the span from inside the body through the ambient * {@link ctx} - it resolves at any depth, so a helper three frames down sees * the same span without being handed anything. * * @example Auto-inferred name * ```typescript * export const createUser = trace(async (data) => { * return await db.users.create(data) * }) * ``` * * @example Explicit name * ```typescript * export const createUser = trace('user.create', async (data) => { * return await db.users.create(data) * }) * ``` * * @example Ambient context, at any depth * ```typescript * import { trace, ctx } from 'autotel' * * export const createUser = trace('user.create', async (data) => { * ctx.setAttribute('user.id', data.id) * return await db.users.create(data) * }) * ``` * * @example Curried, for a shared configuration * ```typescript * const traced = trace({ serviceName: 'users' }) * export const createUser = traced(async (data) => db.users.create(data)) * ``` * * @example One operation, run now - see {@link trace.run} * ```typescript * const user = await trace.run('user.create', async (ctx) => { * ctx.setAttribute('user.id', input.id) * return db.users.create(input) * }) * ``` */ declare function traceImpl<TArgs extends unknown[], TReturn>(fn: (...args: TArgs) => PromiseLike<TReturn>): (...args: TArgs) => Promise<TReturn>; declare function traceImpl<TArgs extends unknown[], TReturn>(fn: (...args: TArgs) => TReturn): (...args: TArgs) => TReturn; declare function traceImpl<TArgs extends unknown[], TReturn>(name: string, fn: (...args: TArgs) => PromiseLike<TReturn>): (...args: TArgs) => Promise<TReturn>; declare function traceImpl<TArgs extends unknown[], TReturn>(name: string, fn: (...args: TArgs) => TReturn): (...args: TArgs) => TReturn; declare function traceImpl<TArgs extends unknown[], TReturn>(options: TracingOptions<TArgs, TReturn>, fn: (...args: TArgs) => TReturn): (...args: TArgs) => TReturn; declare function traceImpl(name: string): <TArgs extends unknown[], TReturn>(fn: (...args: TArgs) => TReturn) => (...args: TArgs) => TReturn; declare function traceImpl<TArgs extends unknown[], TReturn>(options: TracingOptions<TArgs, TReturn>): (fn: (...args: TArgs) => TReturn) => (...args: TArgs) => TReturn; /** * Run one named operation immediately and return its result, with the * {@link TraceContext} passed in. * * This is the counterpart to {@link trace}: `trace(...)` always hands back a * wrapper to call later, `trace.run(...)` runs the operation now. Keeping them * under separate names is deliberate - a single call shape that sometimes * wrapped and sometimes ran is what made dispatch depend on a callback's * parameter name, which a minifier is free to rewrite (#166). * * The body can equally read the ambient {@link ctx}; the parameter is here for * when an explicit binding reads better. * * @example * ```typescript * const user = await trace.run('user.create', async (ctx) => { * ctx.setAttribute('user.id', input.id) * return db.users.create(input) * }) * ``` */ declare function run<TReturn>(name: string, operation: (ctx: TraceContext) => Promise<TReturn>): Promise<TReturn>; declare function run<TReturn>(name: string, operation: (ctx: TraceContext) => TReturn): TReturn; declare function run<TReturn>(options: TracingOptions<[], TReturn>, operation: (ctx: TraceContext) => Promise<TReturn>): Promise<TReturn>; declare function run<TReturn>(options: TracingOptions<[], TReturn>, operation: (ctx: TraceContext) => TReturn): TReturn; declare const trace$1: typeof traceImpl & { run: typeof run; }; /** * 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 tracer = withTracing({ serviceName: 'user' }) * * export const createUser = tracer(ctx => async (data) => { }) * export const updateUser = tracer(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<TCfgArgs extends unknown[] = unknown[], TCfgReturn = unknown>(options?: TracingOptions<TCfgArgs, TCfgReturn>): <TArgs extends TCfgArgs, TReturn extends TCfgReturn>(fnFactory: (ctx: TraceContext) => (...args: TArgs) => TReturn | PromiseLike<TReturn>) => WrappedFunction<TArgs, 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')] * }) * ``` */ type InstrumentedFunction<TFunction extends AnyInstrumentable> = TFunction extends ((...args: infer TArgs) => infer TReturn) ? (...args: TArgs) => TReturn extends PromiseLike<infer TValue> ? Promise<TValue> : TReturn : never; type InstrumentedFunctions<T extends Record<string, AnyInstrumentable>> = { [TKey in keyof T]: InstrumentedFunction<T[TKey]>; }; declare function instrument<TFunction extends AnyInstrumentable>(options: SingleInstrumentOptions<TFunction> & { fn: TFunction & ((...args: Parameters<TFunction>) => PromiseLike<unknown>); }): (...args: Parameters<TFunction>) => Promise<Awaited<ReturnType<TFunction>>>; declare function instrument<TFunction extends AnyInstrumentable>(options: SingleInstrumentOptions<TFunction>): TFunction; declare function instrument<T extends Record<string, AnyInstrumentable>>(options: InstrumentOptions<T>): InstrumentedFunctions<T>; /** * Options for span() function */ interface SpanOptions { /** Span name */ name: string; /** Attributes to set on the span */ attributes?: Record<string, string | number | boolean>; /** OpenTelemetry span kind */ spanKind?: import('@opentelemetry/api').SpanKind; } /** * 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. * * Mirrors `trace()`: pass a span name as the first argument for the common * case, or full `SpanOptions` when you need to attach attributes. * * @example * ```typescript * // Name shorthand * await span('payment.charge', async (span) => { * await chargeCustomer(order); * }) * * // Full options when attributes are needed * await span( * { name: 'payment.charge', attributes: { amount: order.total } }, * async (span) => { * await chargeCustomer(order); * }, * ) * * // Sync * const total = span('calculateTotal', (span) => { * return items.reduce((sum, item) => sum + item.price, 0); * }) * ``` */ declare function span<T = unknown>(name: string, fn: (span: Span) => PromiseLike<T>): Promise<T>; declare function span<T = unknown>(options: SpanOptions, fn: (span: Span) => PromiseLike<T>): Promise<T>; declare function span<T = unknown>(name: string, fn: (span: Span) => T): T; declare function span<T = unknown>(options: SpanOptions, fn: (span: Span) => T): 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 span('webhook.process', 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 { withTracing, withBaggage } from 'autotel'; * * export const createOrder = withTracing({ name: 'order.create' })((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 = withTracing({ name: 'order.process' })((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>; //#endregion export { getActiveTraceContext as a, span as c, withNewContext as d, withTracing as f, TracingOptions as h, ctx as i, trace$1 as l, SingleInstrumentOptions as m, WithBaggageOptions as n, instrument as o, InstrumentOptions as p, WithNewContextOptions as r, requestCtx as s, SpanOptions as t, withBaggage as u };