UNPKG

autotel

Version:
485 lines 17.2 kB
import { t as UnknownRecord } from "./values-CeMH-mYG.cjs"; import { s as Logger } from "./logger-S1Fgl0g3.cjs"; import { Attributes, Link } from "@opentelemetry/api"; //#region src/sampling.d.ts /** * Tail sampling attribute keys (autotel-internal, not OTel semconv) */ declare const AUTOTEL_SAMPLING_TAIL_KEEP = "autotel.sampling.tail.keep"; declare const AUTOTEL_SAMPLING_TAIL_EVALUATED = "autotel.sampling.tail.evaluated"; /** * How many events each kept event stands for, expressed as "1 in N". * * A query that counts sampled spans undercounts the population. Multiplying * each kept event by this rate restores the estimate. Autotel records the * attribute only when N exceeds 1, so fully captured spans stay clean. */ declare const AUTOTEL_SAMPLING_RATE = "autotel.sampling.rate"; /** * Spans an explicit `forceKeep()` claimed. * * The tracing wrapper writes the sampler's tail verdict once the body has * run, which is after any `forceKeep()` inside it. Without this the verdict * overwrites the override and the span the caller insisted on is dropped. */ /** * Baggage key that turns on full-fidelity capture for a request. * * Baggage arrives on the request and propagates, so a gateway, a proxy, a * feature flag or a curl can turn this on for one user and it follows them * across every service. Nobody deploys anything to debug a live problem. */ declare const AUTOTEL_DEBUG_BAGGAGE_KEY = "autotel.debug"; declare function markForceKept(span: object): void; declare function isForceKept(span: object): boolean; /** * Map a string to a stable, evenly spread position in the unit interval. * * Two processes that hash the same key reach the same number, which is what * lets independent services agree on one trace's sampling decision. * * The spread matters as much as the stability. Real sampling keys share long * prefixes: `user_1000`, `user_1001`, `checkout-trace-0001`. A plain * multiply-and-add hash lets that shared prefix dominate the high bits, so a * whole key family lands in one narrow band and a rate of 0.1 keeps all of * them or none of them. FNV-1a followed by the murmur3 finalizer mixes the * low bits back through the word, so keys that differ in one character land * far apart. */ declare function hashUnitInterval(value: string): number; /** * Sampler interface - return true to trace, false to skip */ interface Sampler { /** * Decide whether to trace this operation * * @param context - Sampling context * @returns true to trace, false to skip */ shouldSample(context: SamplingContext): boolean; /** * Whether this sampler needs tail sampling (post-execution decision) * If true, spans are always created and shouldKeepTrace() is called after execution * * @returns true if this sampler needs to evaluate after operation completes */ needsTailSampling?(): boolean; /** * Re-evaluate sampling decision after operation completes (tail sampling) * Only called if needsTailSampling() returns true * * @param context - Sampling context * @param result - Operation result * @returns true if this trace should be kept, false to drop it */ shouldKeepTrace?(context: SamplingContext, result: OperationResult): boolean; /** * How many events a kept event represents, as "1 in N". * * Autotel writes the result to {@link AUTOTEL_SAMPLING_RATE} so an analyst * can reweight counts. Return 1 when the sampler keeps everything. * * @param context - Sampling context * @returns Events represented per kept event */ sampleRate?(context: SamplingContext): number; } /** * Context information for sampling decisions */ interface SamplingContext { /** Operation name */ operationName: string; /** Method arguments (for extracting user IDs, etc.) */ args: unknown[]; /** Optional metadata (e.g., feature flags, request headers) */ metadata?: UnknownRecord; /** Optional span links for links-based sampling */ links?: Link[]; } /** * Result of a trace operation (for post-execution sampling) */ interface OperationResult { /** Whether the operation succeeded */ success: boolean; /** Duration in milliseconds */ duration: number; /** Error if operation failed */ error?: Error; } /** * Simple random sampler * * @example * ```typescript * new RandomSampler(0.1) // Sample 10% of requests * ``` */ declare class RandomSampler implements Sampler { private readonly rate; constructor(rate: number); shouldSample(_context: SamplingContext): boolean; sampleRate(): number; } /** * Always sample (100% tracing) */ declare class AlwaysSampler implements Sampler { shouldSample(_context: SamplingContext): boolean; } /** * Never sample (0% tracing) */ declare class NeverSampler implements Sampler { shouldSample(_context: SamplingContext): boolean; } /** * Adaptive sampler that always traces errors and slow requests * * This is the recommended sampler for production use. * It ensures you never miss critical issues while keeping costs down. * * Strategy: * - Always trace errors (critical for debugging) * - Always trace slow requests (performance issues) * - Use baseline sample rate for successful fast requests * * **IMPORTANT - Tail Sampling Requirement:** * This sampler uses tail sampling (makes decisions AFTER execution). * You MUST use TailSamplingSpanProcessor for it to work correctly: * * - If using initInstrumentation(): TailSamplingSpanProcessor is auto-configured * - If using custom TracerProvider: You MUST manually register TailSamplingSpanProcessor * * Without TailSamplingSpanProcessor, ALL spans are exported (defeating the cost savings). * * @see TailSamplingSpanProcessor * @see README.md "Tail Sampling with Custom Providers" section * * @example * ```typescript * new AdaptiveSampler({ * baselineSampleRate: 0.1, // 10% of normal requests * slowThresholdMs: 1000, // Requests > 1s are "slow" * alwaysSampleErrors: true, // Always trace errors * alwaysSampleSlow: true // Always trace slow requests * }) * ``` */ declare class AdaptiveSampler implements Sampler { private baselineSampleRate; private slowThresholdMs; private alwaysSampleErrors; private alwaysSampleSlow; private linksBased; private linksRate; private logger?; private readonly samplingDecisions; private readonly operationResults; constructor(options?: { baselineSampleRate?: number; slowThresholdMs?: number; alwaysSampleErrors?: boolean; alwaysSampleSlow?: boolean; /** Enable links-based sampling for event-driven architectures */ linksBased?: boolean; /** Sampling rate for spans linked to sampled spans (0.0-1.0) */ linksRate?: number; logger?: Logger; }); needsTailSampling(): boolean; shouldSample(context: SamplingContext): boolean; /** * Check if any links point to sampled spans. * * A span is considered linked to a sampled span if any of its links * have trace_flags with the sampled bit set (0x01). * * @param links - Array of span links to check * @returns true if any linked span is sampled, false otherwise */ hasSampledLink(links: Link[]): boolean; /** * Re-evaluate sampling decision after operation completes * * This allows us to always capture errors and slow requests, * even if they weren't initially sampled. * * @param context - Sampling context * @param result - Operation result * @returns true if this operation should be kept (not discarded) */ shouldKeepTrace(context: SamplingContext, result: OperationResult): boolean; } /** * User-based sampler for consistent tracing * * Always samples requests from specific user IDs. * Useful for debugging specific user issues or monitoring VIP users. * * @example * ```typescript * new UserIdSampler({ * baselineSampleRate: 0.01, // 1% of normal users * alwaysSampleUsers: ['vip_123'], // Always trace VIP users * extractUserId: (args) => args[0]?.userId // Extract user ID from first arg * }) * ``` */ declare class UserIdSampler implements Sampler { private baselineSampleRate; private alwaysSampleUsers; private extractUserId; private logger?; constructor(options: { baselineSampleRate?: number; alwaysSampleUsers?: string[]; extractUserId: (args: unknown[]) => string | undefined; logger?: Logger; }); shouldSample(context: SamplingContext): boolean; /** * Add user IDs to always-sample list */ addAlwaysSampleUsers(...userIds: string[]): void; /** * Remove user IDs from always-sample list */ removeAlwaysSampleUsers(...userIds: string[]): void; /** * Simple hash function for consistent user sampling */ private hashString; } /** * Consistent sampler: every service reaches the same verdict for one trace. * * `RandomSampler` rolls the dice per process, so an upstream service can keep * a trace that its downstream drops, leaving a waterfall with holes in it. * Hashing a key that travels with the request removes the disagreement. Pass * the trace id, or any identifier every hop already shares. * * @example * ```typescript * new DeterministicSampler({ * sampleRate: 0.1, * key: (context) => trace.getActiveSpan()?.spanContext().traceId, * }) * ``` */ declare class DeterministicSampler implements Sampler { private readonly rate; private readonly key; constructor(options: { /** Fraction of traces to keep, 0-1. */ sampleRate: number; /** Identifier shared by every hop of the trace. */ key: (context: SamplingContext) => string | undefined; }); shouldSample(context: SamplingContext): boolean; sampleRate(): number; } /** * Per-key target-rate sampler for workloads with uneven traffic. * * A single rate serves a skewed workload badly: 1% floods storage with the * busiest endpoint and still loses the rare tenant whose failures you need. * This sampler counts traffic per key over a rolling window, then sets each * key its own rate so every key contributes roughly `targetPerKey` events. * Quiet keys survive intact; loud keys get thinned. * * The first window keeps everything, because no traffic history exists yet. * Rates take effect from the second window onward. * * @example * ```typescript * new KeyTargetRateSampler({ * key: (context) => context.operationName, * targetPerKey: 10, // ~10 events per key per window * windowMs: 30_000, * }) * ``` */ declare class KeyTargetRateSampler implements Sampler { private readonly key; private readonly targetPerKey; private readonly windowMs; private readonly maxKeys; private counts; private rates; private windowStart; constructor(options: { /** Groups traffic. Use the operation, route, tenant, or status. */ key: (context: SamplingContext) => string | undefined; /** Events to keep per key per window. Default 10. */ targetPerKey?: number; /** Length of the counting window in milliseconds. Default 30000. */ windowMs?: number; /** Distinct keys to track before overflowing into one bucket. Default 1000. */ maxKeys?: number; }); /** Turn the window's observed counts into the next window's rates. */ private roll; /** * Resolve the key, collapsing into one bucket once the map is full. * * An unbounded key function would otherwise grow the map without limit, * which turns a sampler meant to cut cost into a memory leak. */ private resolveKey; shouldSample(context: SamplingContext): boolean; sampleRate(context: SamplingContext): number; } /** * Composite sampler that combines multiple samplers * * Samples if ANY of the child samplers returns true. * * @example * ```typescript * new CompositeSampler([ * new UserIdSampler({ extractUserId: (args) => args[0]?.userId }), * new AdaptiveSampler({ baselineSampleRate: 0.1 }) * ]) * ``` */ declare class CompositeSampler implements Sampler { private readonly samplers; constructor(samplers: Sampler[]); shouldSample(context: SamplingContext): boolean; } /** * Feature flag sampler * * Always samples requests with specific feature flags enabled. * Perfect for correlating A/B test experiments with metrics. * * @example * ```typescript * new FeatureFlagSampler({ * baselineSampleRate: 0.01, * alwaysSampleFlags: ['new_checkout', 'experimental_ui'], * extractFlags: (args, metadata) => metadata?.featureFlags * }) * ``` */ declare class FeatureFlagSampler implements Sampler { private baselineSampleRate; private alwaysSampleFlags; private extractFlags; private logger?; constructor(options: { baselineSampleRate?: number; alwaysSampleFlags?: string[]; extractFlags: (args: unknown[], metadata?: UnknownRecord) => string[] | undefined; logger?: Logger; }); shouldSample(context: SamplingContext): boolean; /** * Add feature flags to always-sample list */ addAlwaysSampleFlags(...flags: string[]): void; /** * Remove feature flags from always-sample list */ removeAlwaysSampleFlags(...flags: string[]): void; } /** * Named sampling presets for common environments. * Use with `init({ sampling: 'production' })` or directly via factories. */ type SamplingPreset = 'development' | 'errors-only' | 'production' | 'off'; /** * Sampling preset factories. * * For most users, the string shorthand on `init()` is simpler: * ```typescript * init({ service: 'my-app', sampling: 'production' }) * ``` * * Use factories when you need to customize: * ```typescript * init({ service: 'my-app', sampler: samplingPresets.production({ baselineSampleRate: 0.05 }) }) * ``` */ declare const samplingPresets: { /** Capture everything — best for local development and debugging */ development: () => AlwaysSampler; /** Only bad outcomes — zero baseline, errors always kept */ errorsOnly: () => AdaptiveSampler; /** * Balanced production defaults — 10% baseline + errors + slow traces. * Pass overrides to tune (uses the same option names as AdaptiveSampler). */ production: (overrides?: { baselineSampleRate?: number; slowThresholdMs?: number; alwaysSampleErrors?: boolean; alwaysSampleSlow?: boolean; }) => AdaptiveSampler; /** Disable sampling entirely */ off: () => NeverSampler; }; /** * Resolve a preset string to a Sampler instance. * Used internally by `init()` when `sampling` string is provided. * * @throws Error if preset is not recognized */ declare function resolveSamplingPreset(preset: SamplingPreset): Sampler; /** * Create a Link from W3C trace context headers (e.g., from a message queue). * * This is useful for message consumers that need to link to the producer span. * The headers should contain at least a `traceparent` header in W3C format. * * @param headers - Dictionary containing traceparent/tracestate headers * @param attributes - Optional attributes for the link * @returns Link object if context could be extracted, null otherwise * * @example * ```typescript * // In a Kafka consumer * const headers = { traceparent: '00-abc123...-def456...-01' }; * const link = createLinkFromHeaders(headers); * if (link) { * // Use with tracer.startActiveSpan options or ctx.addLink() * tracer.startActiveSpan('process.message', { links: [link] }, span => { ... }); * } * ``` */ declare function createLinkFromHeaders(headers: Record<string, string>, attributes?: Attributes): Link | null; /** * Extract Links from a batch of messages for fan-in scenarios. * * Useful for batch processing where multiple producer spans should be linked. * This enables tracing causality in event-driven architectures where a single * consumer processes messages from multiple producers. * * @param messages - List of message objects * @param headersKey - Key in each message containing trace headers (default: 'headers') * @returns List of Link objects for all valid trace contexts * * @example * ```typescript * // Processing a batch of SQS/Kafka messages * const messages = [ * { body: '...', headers: { traceparent: '...' } }, * { body: '...', headers: { traceparent: '...' } }, * ]; * const links = extractLinksFromBatch(messages); * * tracer.startActiveSpan('process.batch', { links }, span => { * for (const msg of messages) { * processMessage(msg); * } * }); * ``` */ declare function extractLinksFromBatch(messages: UnknownRecord[], headersKey?: string): Link[]; /** True when the request in flight asked for full-fidelity capture. */ declare function debugCaptureRequested(): boolean; //#endregion export { markForceKept as C, isForceKept as S, samplingPresets as T, UserIdSampler as _, AdaptiveSampler as a, extractLinksFromBatch as b, DeterministicSampler as c, NeverSampler as d, OperationResult as f, SamplingPreset as g, SamplingContext as h, AUTOTEL_SAMPLING_TAIL_KEEP as i, FeatureFlagSampler as l, Sampler as m, AUTOTEL_SAMPLING_RATE as n, AlwaysSampler as o, RandomSampler as p, AUTOTEL_SAMPLING_TAIL_EVALUATED as r, CompositeSampler as s, AUTOTEL_DEBUG_BAGGAGE_KEY as t, KeyTargetRateSampler as u, createLinkFromHeaders as v, resolveSamplingPreset as w, hashUnitInterval as x, debugCaptureRequested as y };