autotel
Version:
Write Once, Observe Anywhere
451 lines (448 loc) • 18.7 kB
text/typescript
import * as _opentelemetry_api from '@opentelemetry/api';
import { Span } from '@opentelemetry/api';
import { Sampler } from './sampling.cjs';
import { T as TraceContext } from './trace-context-t5X1AP-e.cjs';
import './logger.cjs';
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 };