autotel
Version:
Write Once, Observe Anywhere
434 lines • 14.7 kB
TypeScript
import { n as TraceContext } from "./trace-context-IuyoPyNq.js";
import { p as Sampler } from "./sampling-CP3e7-Yr.js";
import { Span } from "@opentelemetry/api";
//#region src/functional.d.ts
type WrappedFunction<TArgs extends unknown[], TReturn> = (...args: TArgs) => TReturn | Promise<TReturn>;
/**
* 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>;
/**
* Capture the function arguments onto the span as `autotel.input`
* (JSON, truncated). One arg is captured directly; multiple are captured as
* an array. Off by default — opt in per call. Tools (visualizers, devtools)
* read this alongside `ai.toolCall.args` to show function I/O uniformly.
* Avoid on args with secrets/PII, or pair with a redacting processor.
*/
captureInput?: boolean;
/**
* Capture the function return value onto the span as `autotel.output`
* (JSON, truncated). Off by default. Same caveats as {@link captureInput}.
*/
captureOutput?: boolean;
/**
* 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?: import('@opentelemetry/api').SpanKind;
/**
* Classify a thrown value as a real error. Return `false` to treat the throw
* as expected control flow: the span is marked OK, no exception is recorded,
* and the value is rethrown untouched. Use this for framework control-flow
* signals that propagate via `throw` but are not failures — e.g. TanStack
* Router / Next.js `redirect()` and `notFound()`.
* @default every throw is treated as an error
*/
isError?: (error: 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 (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))[];
}
/**
* 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;
}
/**
* Constraint alias for `instrument()` and friends. `never[]` parameters make
* every concretely-typed function (e.g. `(name: string) => Promise<User>`)
* satisfy the constraint under `strictFunctionTypes` without resorting to
* `any`. Use ONLY in constraint positions — the concrete `T` is still
* inferred from the actual argument, so call-site argument and return types
* are fully preserved.
*/
type AnyInstrumentable = ((...args: never[]) => unknown) & {
displayName?: string;
name?: string;
};
/**
* 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('getUser', async (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 Record<string, unknown> | undefined = undefined>(): TraceContext<TBaggage> | undefined;
/**
* 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: TraceContext;
/**
* Approach 1: trace() - Zero-ceremony HOF
*
* Wrap a plain function with automatic tracing. The function receives its real
* arguments; no context parameter is injected. Use
* {@link getActiveTraceContext} inside the function, or use {@link withTracing}
* for the explicit `(ctx) => (...args) => result` factory form.
*
* @example Auto-inferred name - Plain function
* ```typescript
* export const createUser = trace(async (data) => {
* return await db.users.create(data)
* })
* // → Traced as "createUser"
* ```
*
* @example Ambient context access
* ```typescript
* export const createUser = trace(async (data) => {
* getActiveTraceContext()?.setAttribute('user.id', data.id)
* return await db.users.create(data)
* })
* ```
*
* @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 Explicit factory context with withTracing()
* ```typescript
* export const createUser = withTracing({ name: 'user.create' })((ctx) => async (data) => {
* ctx.setAttribute('user.id', data.id)
* return await db.users.create(data)
* })
* ```
*
* @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)
* })
* ```
*
*/
declare function trace<TArgs extends unknown[], TReturn>(fn: (...args: TArgs) => TReturn): (...args: TArgs) => TReturn;
declare function trace<TArgs extends unknown[], TReturn>(name: string, fn: (...args: TArgs) => TReturn): (...args: TArgs) => TReturn;
declare function trace<TArgs extends unknown[], TReturn>(options: TracingOptions<TArgs, TReturn>, fn: (...args: TArgs) => TReturn): (...args: TArgs) => 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 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 | Promise<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')]
* })
* ```
*/
declare function instrument<TFunction extends AnyInstrumentable>(options: SingleInstrumentOptions<TFunction>): TFunction;
declare function instrument<T extends Record<string, AnyInstrumentable>>(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>;
/** 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) => T): T;
declare function span<T = unknown>(name: string, fn: (span: Span) => Promise<T>): Promise<T>;
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 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 { InstrumentOptions, SingleInstrumentOptions, SpanOptions, type TraceContext, TracingOptions, WithBaggageOptions, WithNewContextOptions, ctx, getActiveTraceContext, instrument, span, trace, withBaggage, withNewContext, withTracing };
//# sourceMappingURL=functional.d.ts.map