UNPKG

autotel

Version:
380 lines (376 loc) 12.7 kB
import { Link, Attributes } from '@opentelemetry/api'; import { Logger } from './logger.cjs'; import 'pino'; /** * Sampling Strategies * * Provides intelligent sampling beyond simple random rates. * Helps reduce telemetry costs while capturing critical data. * * Key strategies: * - Always trace errors and slow requests (critical for debugging) * - Sample by user ID for consistent request tracing * - Adaptive sampling based on load * - Sample by feature flags for A/B testing correlation * * @example * ```typescript * import { AlwaysOnErrorSampler, UserIdSampler } from './sampling' * * @Instrumented({ * serviceName: 'user', * sampler: new AlwaysOnErrorSampler(0.1) // 10% baseline, 100% on errors * }) * class UserService { } * ``` */ /** * 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"; /** * 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; } /** * 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?: Record<string, unknown>; /** 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 sampleRate; constructor(sampleRate: number); shouldSample(_context: SamplingContext): boolean; } /** * 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; } /** * 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?: Record<string, unknown>) => 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: Array<{ [key: string]: unknown; }>, headersKey?: string): Link[]; export { AUTOTEL_SAMPLING_TAIL_EVALUATED, AUTOTEL_SAMPLING_TAIL_KEEP, AdaptiveSampler, AlwaysSampler, CompositeSampler, FeatureFlagSampler, NeverSampler, type OperationResult, RandomSampler, type Sampler, type SamplingContext, type SamplingPreset, UserIdSampler, createLinkFromHeaders, extractLinksFromBatch, resolveSamplingPreset, samplingPresets };