{"version":3,"file":"sampling.cjs","names":["TraceFlags"],"sources":["../src/sampling.ts"],"sourcesContent":["/**\n * Sampling Strategies\n *\n * Provides intelligent sampling beyond simple random rates.\n * Helps reduce telemetry costs while capturing critical data.\n *\n * Key strategies:\n * - Always trace errors and slow requests (critical for debugging)\n * - Sample by user ID for consistent request tracing\n * - Adaptive sampling based on load\n * - Sample by feature flags for A/B testing correlation\n *\n * @example\n * ```typescript\n * import { AlwaysOnErrorSampler, UserIdSampler } from './sampling'\n *\n * @Instrumented({\n *   serviceName: 'user',\n *   sampler: new AlwaysOnErrorSampler(0.1) // 10% baseline, 100% on errors\n * })\n * class UserService { }\n * ```\n */\n\nimport type { Link, Attributes } from '@opentelemetry/api';\nimport { TraceFlags } from '@opentelemetry/api';\nimport { type Logger } from './logger';\n\n/**\n * Tail sampling attribute keys (autotel-internal, not OTel semconv)\n */\nexport const AUTOTEL_SAMPLING_TAIL_KEEP = 'autotel.sampling.tail.keep';\nexport const AUTOTEL_SAMPLING_TAIL_EVALUATED =\n  'autotel.sampling.tail.evaluated';\n\n/**\n * How many events each kept event stands for, expressed as \"1 in N\".\n *\n * A query that counts sampled spans undercounts the population. Multiplying\n * each kept event by this rate restores the estimate. Autotel records the\n * attribute only when N exceeds 1, so fully captured spans stay clean.\n */\nexport const AUTOTEL_SAMPLING_RATE = 'autotel.sampling.rate';\n\n/**\n * Convert a keep probability into the \"1 in N\" rate reported on spans.\n *\n * Keeping 10% of traces means each survivor stands for 10, so the two numbers\n * are reciprocals and easy to publish the wrong way round.\n */\nfunction toSampleRate(probability: number): number {\n  return probability > 0 ? 1 / probability : 0;\n}\n\n/**\n * Map a string to a stable, evenly spread position in the unit interval.\n *\n * Two processes that hash the same key reach the same number, which is what\n * lets independent services agree on one trace's sampling decision.\n *\n * The spread matters as much as the stability. Real sampling keys share long\n * prefixes: `user_1000`, `user_1001`, `checkout-trace-0001`. A plain\n * multiply-and-add hash lets that shared prefix dominate the high bits, so a\n * whole key family lands in one narrow band and a rate of 0.1 keeps all of\n * them or none of them. FNV-1a followed by the murmur3 finalizer mixes the\n * low bits back through the word, so keys that differ in one character land\n * far apart.\n */\nexport function hashUnitInterval(value: string): number {\n  let hash = 0x81_1c_9d_c5; // FNV-1a 32-bit offset basis\n  for (let i = 0; i < value.length; i++) {\n    hash ^= value.codePointAt(i) ?? 0;\n    hash = Math.imul(hash, 0x01_00_01_93); // FNV prime\n  }\n\n  // murmur3 fmix32 avalanche\n  hash ^= hash >>> 16;\n  hash = Math.imul(hash, 0x85_eb_ca_6b);\n  hash ^= hash >>> 13;\n  hash = Math.imul(hash, 0xc2_b2_ae_35);\n  hash ^= hash >>> 16;\n\n  return (hash >>> 0) / 4_294_967_296;\n}\n\n/**\n * Sampler interface - return true to trace, false to skip\n */\nexport interface Sampler {\n  /**\n   * Decide whether to trace this operation\n   *\n   * @param context - Sampling context\n   * @returns true to trace, false to skip\n   */\n  shouldSample(context: SamplingContext): boolean;\n\n  /**\n   * Whether this sampler needs tail sampling (post-execution decision)\n   * If true, spans are always created and shouldKeepTrace() is called after execution\n   *\n   * @returns true if this sampler needs to evaluate after operation completes\n   */\n  needsTailSampling?(): boolean;\n\n  /**\n   * Re-evaluate sampling decision after operation completes (tail sampling)\n   * Only called if needsTailSampling() returns true\n   *\n   * @param context - Sampling context\n   * @param result - Operation result\n   * @returns true if this trace should be kept, false to drop it\n   */\n  shouldKeepTrace?(context: SamplingContext, result: OperationResult): boolean;\n\n  /**\n   * How many events a kept event represents, as \"1 in N\".\n   *\n   * Autotel writes the result to {@link AUTOTEL_SAMPLING_RATE} so an analyst\n   * can reweight counts. Return 1 when the sampler keeps everything.\n   *\n   * @param context - Sampling context\n   * @returns Events represented per kept event\n   */\n  sampleRate?(context: SamplingContext): number;\n}\n\n/**\n * Context information for sampling decisions\n */\nexport interface SamplingContext {\n  /** Operation name */\n  operationName: string;\n  /** Method arguments (for extracting user IDs, etc.) */\n  args: unknown[];\n  /** Optional metadata (e.g., feature flags, request headers) */\n  metadata?: Record<string, unknown>;\n  /** Optional span links for links-based sampling */\n  links?: Link[];\n}\n\n/**\n * Result of a trace operation (for post-execution sampling)\n */\nexport interface OperationResult {\n  /** Whether the operation succeeded */\n  success: boolean;\n  /** Duration in milliseconds */\n  duration: number;\n  /** Error if operation failed */\n  error?: Error;\n}\n\n/**\n * Simple random sampler\n *\n * @example\n * ```typescript\n * new RandomSampler(0.1) // Sample 10% of requests\n * ```\n */\nexport class RandomSampler implements Sampler {\n  constructor(private readonly rate: number) {\n    if (rate < 0 || rate > 1) {\n      throw new Error('Sample rate must be between 0 and 1');\n    }\n  }\n\n  shouldSample(_context: SamplingContext): boolean {\n    return Math.random() < this.rate;\n  }\n\n  sampleRate(): number {\n    return toSampleRate(this.rate);\n  }\n}\n\n/**\n * Always sample (100% tracing)\n */\nexport class AlwaysSampler implements Sampler {\n  shouldSample(_context: SamplingContext): boolean {\n    return true;\n  }\n}\n\n/**\n * Never sample (0% tracing)\n */\nexport class NeverSampler implements Sampler {\n  shouldSample(_context: SamplingContext): boolean {\n    return false;\n  }\n}\n\n/**\n * Adaptive sampler that always traces errors and slow requests\n *\n * This is the recommended sampler for production use.\n * It ensures you never miss critical issues while keeping costs down.\n *\n * Strategy:\n * - Always trace errors (critical for debugging)\n * - Always trace slow requests (performance issues)\n * - Use baseline sample rate for successful fast requests\n *\n * **IMPORTANT - Tail Sampling Requirement:**\n * This sampler uses tail sampling (makes decisions AFTER execution).\n * You MUST use TailSamplingSpanProcessor for it to work correctly:\n *\n * - If using initInstrumentation(): TailSamplingSpanProcessor is auto-configured\n * - If using custom TracerProvider: You MUST manually register TailSamplingSpanProcessor\n *\n * Without TailSamplingSpanProcessor, ALL spans are exported (defeating the cost savings).\n *\n * @see TailSamplingSpanProcessor\n * @see README.md \"Tail Sampling with Custom Providers\" section\n *\n * @example\n * ```typescript\n * new AdaptiveSampler({\n *   baselineSampleRate: 0.1,    // 10% of normal requests\n *   slowThresholdMs: 1000,       // Requests > 1s are \"slow\"\n *   alwaysSampleErrors: true,    // Always trace errors\n *   alwaysSampleSlow: true       // Always trace slow requests\n * })\n * ```\n */\nexport class AdaptiveSampler implements Sampler {\n  private baselineSampleRate: number;\n  private slowThresholdMs: number;\n  private alwaysSampleErrors: boolean;\n  private alwaysSampleSlow: boolean;\n  private linksBased: boolean;\n  private linksRate: number;\n  private logger?: Logger;\n\n  // Track whether we should sample this request\n  private readonly samplingDecisions = new WeakMap<unknown[], boolean>();\n  // Track operation results to enable post-execution decision\n  private readonly operationResults = new WeakMap<unknown[], OperationResult>();\n\n  constructor(\n    options: {\n      baselineSampleRate?: number;\n      slowThresholdMs?: number;\n      alwaysSampleErrors?: boolean;\n      alwaysSampleSlow?: boolean;\n      /** Enable links-based sampling for event-driven architectures */\n      linksBased?: boolean;\n      /** Sampling rate for spans linked to sampled spans (0.0-1.0) */\n      linksRate?: number;\n      logger?: Logger;\n    } = {},\n  ) {\n    this.baselineSampleRate = options.baselineSampleRate ?? 0.1;\n    this.slowThresholdMs = options.slowThresholdMs ?? 1000;\n    this.alwaysSampleErrors = options.alwaysSampleErrors ?? true;\n    this.alwaysSampleSlow = options.alwaysSampleSlow ?? true;\n    this.linksBased = options.linksBased ?? false;\n    this.linksRate = options.linksRate ?? 1;\n    this.logger = options.logger;\n\n    if (this.baselineSampleRate < 0 || this.baselineSampleRate > 1) {\n      throw new Error('Baseline sample rate must be between 0 and 1');\n    }\n    if (this.linksRate < 0 || this.linksRate > 1) {\n      throw new Error('Links rate must be between 0 and 1');\n    }\n  }\n\n  needsTailSampling(): boolean {\n    // AdaptiveSampler ALWAYS needs tail sampling to implement error/slow capture\n    return true;\n  }\n\n  shouldSample(context: SamplingContext): boolean {\n    // For tail sampling, we optimistically create spans for all requests\n    // The real decision happens in shouldKeepTrace() after execution\n    // We still store the baseline decision for shouldKeepTrace() to use\n    const baselineDecision = Math.random() < this.baselineSampleRate;\n    this.samplingDecisions.set(context.args, baselineDecision);\n\n    // Always return true to create the span (tail sampling will decide if we keep it)\n    return true;\n  }\n\n  /**\n   * Check if any links point to sampled spans.\n   *\n   * A span is considered linked to a sampled span if any of its links\n   * have trace_flags with the sampled bit set (0x01).\n   *\n   * @param links - Array of span links to check\n   * @returns true if any linked span is sampled, false otherwise\n   */\n  hasSampledLink(links: Link[]): boolean {\n    if (!links || links.length === 0) {\n      return false;\n    }\n    return links.some(\n      (link) =>\n        link.context && (link.context.traceFlags & TraceFlags.SAMPLED) !== 0,\n    );\n  }\n\n  /**\n   * Re-evaluate sampling decision after operation completes\n   *\n   * This allows us to always capture errors and slow requests,\n   * even if they weren't initially sampled.\n   *\n   * @param context - Sampling context\n   * @param result - Operation result\n   * @returns true if this operation should be kept (not discarded)\n   */\n  shouldKeepTrace(context: SamplingContext, result: OperationResult): boolean {\n    const baselineDecision = this.samplingDecisions.get(context.args) ?? false;\n\n    // Always keep errors\n    if (this.alwaysSampleErrors && !result.success) {\n      if (!baselineDecision) {\n        this.logger?.debug(\n          {\n            operation: context.operationName,\n            error: result.error?.message,\n          },\n          'Adaptive sampling: Keeping error trace',\n        );\n      }\n      return true;\n    }\n\n    // Always keep slow requests\n    if (this.alwaysSampleSlow && result.duration >= this.slowThresholdMs) {\n      if (!baselineDecision) {\n        this.logger?.debug(\n          {\n            operation: context.operationName,\n            duration: result.duration,\n          },\n          'Adaptive sampling: Keeping slow trace',\n        );\n      }\n      return true;\n    }\n\n    // Check for sampled links (links-based sampling for event-driven systems)\n    if (\n      this.linksBased &&\n      context.links &&\n      this.hasSampledLink(context.links)\n    ) {\n      // Use linksRate to decide whether to keep the linked span\n      const keepLinked = Math.random() < this.linksRate;\n      if (keepLinked && !baselineDecision) {\n        this.logger?.debug(\n          {\n            operation: context.operationName,\n            linkCount: context.links.length,\n          },\n          'Adaptive sampling: Keeping trace due to sampled link',\n        );\n      }\n      return keepLinked;\n    }\n\n    // Otherwise, use baseline decision\n    return baselineDecision;\n  }\n}\n\n/**\n * User-based sampler for consistent tracing\n *\n * Always samples requests from specific user IDs.\n * Useful for debugging specific user issues or monitoring VIP users.\n *\n * @example\n * ```typescript\n * new UserIdSampler({\n *   baselineSampleRate: 0.01,      // 1% of normal users\n *   alwaysSampleUsers: ['vip_123'], // Always trace VIP users\n *   extractUserId: (args) => args[0]?.userId // Extract user ID from first arg\n * })\n * ```\n */\nexport class UserIdSampler implements Sampler {\n  private baselineSampleRate: number;\n  private alwaysSampleUsers: Set<string>;\n  private extractUserId: (args: unknown[]) => string | undefined;\n  private logger?: Logger;\n\n  constructor(options: {\n    baselineSampleRate?: number;\n    alwaysSampleUsers?: string[];\n    extractUserId: (args: unknown[]) => string | undefined;\n    logger?: Logger;\n  }) {\n    this.baselineSampleRate = options.baselineSampleRate ?? 0.1;\n    this.alwaysSampleUsers = new Set(options.alwaysSampleUsers || []);\n    this.extractUserId = options.extractUserId;\n    this.logger = options.logger;\n  }\n\n  shouldSample(context: SamplingContext): boolean {\n    const userId = this.extractUserId(context.args);\n\n    // Always sample specific users\n    if (userId && this.alwaysSampleUsers.has(userId)) {\n      this.logger?.debug(\n        {\n          operation: context.operationName,\n          userId,\n        },\n        'Sampling user request',\n      );\n      return true;\n    }\n\n    // For consistent per-user sampling, hash the user ID\n    if (userId) {\n      const hash = this.hashString(userId);\n      return hash < this.baselineSampleRate;\n    }\n\n    // Fallback to random sampling if no user ID\n    return Math.random() < this.baselineSampleRate;\n  }\n\n  /**\n   * Add user IDs to always-sample list\n   */\n  addAlwaysSampleUsers(...userIds: string[]): void {\n    for (const userId of userIds) {\n      this.alwaysSampleUsers.add(userId);\n    }\n  }\n\n  /**\n   * Remove user IDs from always-sample list\n   */\n  removeAlwaysSampleUsers(...userIds: string[]): void {\n    for (const userId of userIds) {\n      this.alwaysSampleUsers.delete(userId);\n    }\n  }\n\n  /**\n   * Simple hash function for consistent user sampling\n   */\n  private hashString(str: string): number {\n    return hashUnitInterval(str);\n  }\n}\n\n/**\n * Consistent sampler: every service reaches the same verdict for one trace.\n *\n * `RandomSampler` rolls the dice per process, so an upstream service can keep\n * a trace that its downstream drops, leaving a waterfall with holes in it.\n * Hashing a key that travels with the request removes the disagreement. Pass\n * the trace id, or any identifier every hop already shares.\n *\n * @example\n * ```typescript\n * new DeterministicSampler({\n *   sampleRate: 0.1,\n *   key: (context) => trace.getActiveSpan()?.spanContext().traceId,\n * })\n * ```\n */\nexport class DeterministicSampler implements Sampler {\n  private readonly rate: number;\n  private readonly key: (context: SamplingContext) => string | undefined;\n\n  constructor(options: {\n    /** Fraction of traces to keep, 0-1. */\n    sampleRate: number;\n    /** Identifier shared by every hop of the trace. */\n    key: (context: SamplingContext) => string | undefined;\n  }) {\n    if (options.sampleRate < 0 || options.sampleRate > 1) {\n      throw new Error('Sample rate must be between 0 and 1');\n    }\n    this.rate = options.sampleRate;\n    this.key = options.key;\n  }\n\n  shouldSample(context: SamplingContext): boolean {\n    const key = this.key(context);\n    // No shared key means no agreement to preserve, so fall back to chance.\n    if (key === undefined) {\n      return Math.random() < this.rate;\n    }\n    return hashUnitInterval(key) < this.rate;\n  }\n\n  sampleRate(): number {\n    return toSampleRate(this.rate);\n  }\n}\n\n/** Bucket for keys seen after the tracked map fills up. */\nconst OVERFLOW_KEY = '__overflow__';\n\n/**\n * Per-key target-rate sampler for workloads with uneven traffic.\n *\n * A single rate serves a skewed workload badly: 1% floods storage with the\n * busiest endpoint and still loses the rare tenant whose failures you need.\n * This sampler counts traffic per key over a rolling window, then sets each\n * key its own rate so every key contributes roughly `targetPerKey` events.\n * Quiet keys survive intact; loud keys get thinned.\n *\n * The first window keeps everything, because no traffic history exists yet.\n * Rates take effect from the second window onward.\n *\n * @example\n * ```typescript\n * new KeyTargetRateSampler({\n *   key: (context) => context.operationName,\n *   targetPerKey: 10,     // ~10 events per key per window\n *   windowMs: 30_000,\n * })\n * ```\n */\nexport class KeyTargetRateSampler implements Sampler {\n  private readonly key: (context: SamplingContext) => string | undefined;\n  private readonly targetPerKey: number;\n  private readonly windowMs: number;\n  private readonly maxKeys: number;\n  private counts = new Map<string, number>();\n  private rates = new Map<string, number>();\n  private windowStart = Date.now();\n\n  constructor(options: {\n    /** Groups traffic. Use the operation, route, tenant, or status. */\n    key: (context: SamplingContext) => string | undefined;\n    /** Events to keep per key per window. Default 10. */\n    targetPerKey?: number;\n    /** Length of the counting window in milliseconds. Default 30000. */\n    windowMs?: number;\n    /** Distinct keys to track before overflowing into one bucket. Default 1000. */\n    maxKeys?: number;\n  }) {\n    this.key = options.key;\n    this.targetPerKey = options.targetPerKey ?? 10;\n    this.windowMs = options.windowMs ?? 30_000;\n    this.maxKeys = options.maxKeys ?? 1000;\n\n    if (this.targetPerKey <= 0) {\n      throw new Error('Target per key must be greater than 0');\n    }\n    if (this.windowMs <= 0) {\n      throw new Error('Window must be greater than 0');\n    }\n  }\n\n  /** Turn the window's observed counts into the next window's rates. */\n  private roll(now: number): void {\n    if (now - this.windowStart < this.windowMs) {\n      return;\n    }\n    const rates = new Map<string, number>();\n    for (const [key, count] of this.counts) {\n      rates.set(key, Math.max(1, count / this.targetPerKey));\n    }\n    this.rates = rates;\n    this.counts = new Map();\n    this.windowStart = now;\n  }\n\n  /**\n   * Resolve the key, collapsing into one bucket once the map is full.\n   *\n   * An unbounded key function would otherwise grow the map without limit,\n   * which turns a sampler meant to cut cost into a memory leak.\n   */\n  private resolveKey(context: SamplingContext): string {\n    const key = this.key(context) ?? OVERFLOW_KEY;\n    if (this.counts.has(key) || this.counts.size < this.maxKeys) {\n      return key;\n    }\n    return OVERFLOW_KEY;\n  }\n\n  shouldSample(context: SamplingContext): boolean {\n    this.roll(Date.now());\n    const key = this.resolveKey(context);\n    this.counts.set(key, (this.counts.get(key) ?? 0) + 1);\n    const rate = this.rates.get(key) ?? 1;\n    return rate <= 1 || Math.random() < 1 / rate;\n  }\n\n  sampleRate(context: SamplingContext): number {\n    return this.rates.get(this.resolveKey(context)) ?? 1;\n  }\n}\n\n/**\n * Composite sampler that combines multiple samplers\n *\n * Samples if ANY of the child samplers returns true.\n *\n * @example\n * ```typescript\n * new CompositeSampler([\n *   new UserIdSampler({ extractUserId: (args) => args[0]?.userId }),\n *   new AdaptiveSampler({ baselineSampleRate: 0.1 })\n * ])\n * ```\n */\nexport class CompositeSampler implements Sampler {\n  constructor(private readonly samplers: Sampler[]) {\n    if (samplers.length === 0) {\n      throw new Error('CompositeSampler requires at least one child sampler');\n    }\n  }\n\n  shouldSample(context: SamplingContext): boolean {\n    return this.samplers.some((sampler) => sampler.shouldSample(context));\n  }\n}\n\n/**\n * Feature flag sampler\n *\n * Always samples requests with specific feature flags enabled.\n * Perfect for correlating A/B test experiments with metrics.\n *\n * @example\n * ```typescript\n * new FeatureFlagSampler({\n *   baselineSampleRate: 0.01,\n *   alwaysSampleFlags: ['new_checkout', 'experimental_ui'],\n *   extractFlags: (args, metadata) => metadata?.featureFlags\n * })\n * ```\n */\nexport class FeatureFlagSampler implements Sampler {\n  private baselineSampleRate: number;\n  private alwaysSampleFlags: Set<string>;\n  private extractFlags: (\n    args: unknown[],\n    metadata?: Record<string, unknown>,\n  ) => string[] | undefined;\n  private logger?: Logger;\n\n  constructor(options: {\n    baselineSampleRate?: number;\n    alwaysSampleFlags?: string[];\n    extractFlags: (\n      args: unknown[],\n      metadata?: Record<string, unknown>,\n    ) => string[] | undefined;\n    logger?: Logger;\n  }) {\n    this.baselineSampleRate = options.baselineSampleRate ?? 0.1;\n    this.alwaysSampleFlags = new Set(options.alwaysSampleFlags || []);\n    this.extractFlags = options.extractFlags;\n    this.logger = options.logger;\n  }\n\n  shouldSample(context: SamplingContext): boolean {\n    const flags = this.extractFlags(context.args, context.metadata);\n\n    // Always sample if any monitored flag is enabled\n    if (flags && flags.some((flag) => this.alwaysSampleFlags.has(flag))) {\n      this.logger?.debug(\n        {\n          operation: context.operationName,\n          flags,\n        },\n        'Sampling feature flag request',\n      );\n      return true;\n    }\n\n    // Fallback to random sampling\n    return Math.random() < this.baselineSampleRate;\n  }\n\n  /**\n   * Add feature flags to always-sample list\n   */\n  addAlwaysSampleFlags(...flags: string[]): void {\n    for (const flag of flags) {\n      this.alwaysSampleFlags.add(flag);\n    }\n  }\n\n  /**\n   * Remove feature flags from always-sample list\n   */\n  removeAlwaysSampleFlags(...flags: string[]): void {\n    for (const flag of flags) {\n      this.alwaysSampleFlags.delete(flag);\n    }\n  }\n}\n\n// ============================================================================\n// Sampling Presets\n// ============================================================================\n\n/**\n * Named sampling presets for common environments.\n * Use with `init({ sampling: 'production' })` or directly via factories.\n */\nexport type SamplingPreset =\n  'development' | 'errors-only' | 'production' | 'off';\n\n/**\n * Sampling preset factories.\n *\n * For most users, the string shorthand on `init()` is simpler:\n * ```typescript\n * init({ service: 'my-app', sampling: 'production' })\n * ```\n *\n * Use factories when you need to customize:\n * ```typescript\n * init({ service: 'my-app', sampler: samplingPresets.production({ baselineSampleRate: 0.05 }) })\n * ```\n */\nexport const samplingPresets = {\n  /** Capture everything — best for local development and debugging */\n  development: () => new AlwaysSampler(),\n\n  /** Only bad outcomes — zero baseline, errors always kept */\n  errorsOnly: () =>\n    new AdaptiveSampler({\n      baselineSampleRate: 0,\n      alwaysSampleErrors: true,\n    }),\n\n  /**\n   * Balanced production defaults — 10% baseline + errors + slow traces.\n   * Pass overrides to tune (uses the same option names as AdaptiveSampler).\n   */\n  production: (overrides?: {\n    baselineSampleRate?: number;\n    slowThresholdMs?: number;\n    alwaysSampleErrors?: boolean;\n    alwaysSampleSlow?: boolean;\n  }) =>\n    new AdaptiveSampler({\n      baselineSampleRate: 0.1,\n      alwaysSampleErrors: true,\n      alwaysSampleSlow: true,\n      slowThresholdMs: 1000,\n      ...overrides,\n    }),\n\n  /** Disable sampling entirely */\n  off: () => new NeverSampler(),\n};\n\n/**\n * Resolve a preset string to a Sampler instance.\n * Used internally by `init()` when `sampling` string is provided.\n *\n * @throws Error if preset is not recognized\n */\nexport function resolveSamplingPreset(preset: SamplingPreset): Sampler {\n  switch (preset) {\n    case 'development': {\n      return samplingPresets.development();\n    }\n    case 'errors-only': {\n      return samplingPresets.errorsOnly();\n    }\n    case 'production': {\n      return samplingPresets.production();\n    }\n    case 'off': {\n      return samplingPresets.off();\n    }\n    default: {\n      throw new Error(\n        `Unknown sampling preset: \"${preset}\". Valid presets: development, errors-only, production, off`,\n      );\n    }\n  }\n}\n\n// ============================================================================\n// Link Helper Functions\n// ============================================================================\n\n/**\n * Create a Link from W3C trace context headers (e.g., from a message queue).\n *\n * This is useful for message consumers that need to link to the producer span.\n * The headers should contain at least a `traceparent` header in W3C format.\n *\n * @param headers - Dictionary containing traceparent/tracestate headers\n * @param attributes - Optional attributes for the link\n * @returns Link object if context could be extracted, null otherwise\n *\n * @example\n * ```typescript\n * // In a Kafka consumer\n * const headers = { traceparent: '00-abc123...-def456...-01' };\n * const link = createLinkFromHeaders(headers);\n * if (link) {\n *   // Use with tracer.startActiveSpan options or ctx.addLink()\n *   tracer.startActiveSpan('process.message', { links: [link] }, span => { ... });\n * }\n * ```\n */\nexport function createLinkFromHeaders(\n  headers: Record<string, string>,\n  attributes?: Attributes,\n): Link | null {\n  // Parse W3C traceparent header directly for reliability\n  // Format: version-traceId-spanId-traceFlags (e.g., 00-abc123...-def456...-01)\n  const traceparent = headers.traceparent || headers['traceparent'];\n  if (!traceparent) {\n    return null;\n  }\n\n  const spanContext = parseTraceparent(traceparent);\n  if (!spanContext || !isValidSpanContext(spanContext)) {\n    return null;\n  }\n\n  return {\n    context: spanContext,\n    attributes: attributes ?? {},\n  };\n}\n\n/**\n * Extract Links from a batch of messages for fan-in scenarios.\n *\n * Useful for batch processing where multiple producer spans should be linked.\n * This enables tracing causality in event-driven architectures where a single\n * consumer processes messages from multiple producers.\n *\n * @param messages - List of message objects\n * @param headersKey - Key in each message containing trace headers (default: 'headers')\n * @returns List of Link objects for all valid trace contexts\n *\n * @example\n * ```typescript\n * // Processing a batch of SQS/Kafka messages\n * const messages = [\n *   { body: '...', headers: { traceparent: '...' } },\n *   { body: '...', headers: { traceparent: '...' } },\n * ];\n * const links = extractLinksFromBatch(messages);\n *\n * tracer.startActiveSpan('process.batch', { links }, span => {\n *   for (const msg of messages) {\n *     processMessage(msg);\n *   }\n * });\n * ```\n */\nexport function extractLinksFromBatch(\n  messages: Array<{ [key: string]: unknown }>,\n  headersKey: string = 'headers',\n): Link[] {\n  const links: Link[] = [];\n\n  for (const msg of messages) {\n    const msgHeaders = msg[headersKey];\n    if (msgHeaders && typeof msgHeaders === 'object' && msgHeaders !== null) {\n      const link = createLinkFromHeaders(msgHeaders as Record<string, string>, {\n        'messaging.batch.message_index': links.length,\n      });\n      if (link) {\n        links.push(link);\n      }\n    }\n  }\n\n  return links;\n}\n\n/**\n * Parse W3C traceparent header into SpanContext\n * Format: version-traceId-spanId-traceFlags (e.g., 00-abc123...-def456...-01)\n *\n * @see https://www.w3.org/TR/trace-context/#traceparent-header\n */\nfunction parseTraceparent(\n  traceparent: string,\n): import('@opentelemetry/api').SpanContext | null {\n  // W3C traceparent format: version-traceId-parentId-traceFlags\n  // Example: 00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01\n  const TRACEPARENT_REGEX =\n    /^([0-9a-f]{2})-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})$/i;\n\n  const match = traceparent.match(TRACEPARENT_REGEX);\n  if (!match || match.length < 5) {\n    return null;\n  }\n\n  const version = match[1];\n  const traceId = match[2];\n  const spanId = match[3];\n  const flags = match[4];\n\n  // Validate all parts are present (TypeScript narrowing)\n  if (!version || !traceId || !spanId || !flags) {\n    return null;\n  }\n\n  // Version 00 is currently the only version, but we should be forward compatible\n  if (version === 'ff') {\n    // Version ff is invalid according to spec\n    return null;\n  }\n\n  return {\n    traceId,\n    spanId,\n    traceFlags: Number.parseInt(flags, 16),\n    isRemote: true,\n  };\n}\n\n/**\n * Check if a SpanContext is valid (has non-zero trace and span IDs)\n */\nfunction isValidSpanContext(\n  spanContext: import('@opentelemetry/api').SpanContext | null,\n): spanContext is import('@opentelemetry/api').SpanContext {\n  if (!spanContext) return false;\n  // TraceId should not be all zeros (00000000000000000000000000000000)\n  // SpanId should not be all zeros (0000000000000000)\n  return (\n    spanContext.traceId !== '00000000000000000000000000000000' &&\n    spanContext.spanId !== '0000000000000000'\n  );\n}\n"],"mappings":";;;;;;;AA+BA,MAAa,6BAA6B;AAC1C,MAAa,kCACX;;;;;;;;AASF,MAAa,wBAAwB;;;;;;;AAQrC,SAAS,aAAa,aAA6B;CACjD,OAAO,cAAc,IAAI,IAAI,cAAc;AAC7C;;;;;;;;;;;;;;;AAgBA,SAAgB,iBAAiB,OAAuB;CACtD,IAAI,OAAO;CACX,KAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;EACrC,QAAQ,MAAM,YAAY,CAAC,KAAK;EAChC,OAAO,KAAK,KAAK,MAAM,QAAa;CACtC;CAGA,QAAQ,SAAS;CACjB,OAAO,KAAK,KAAK,MAAM,UAAa;CACpC,QAAQ,SAAS;CACjB,OAAO,KAAK,KAAK,MAAM,UAAa;CACpC,QAAQ,SAAS;CAEjB,QAAQ,SAAS,KAAK;AACxB;;;;;;;;;AA8EA,IAAa,gBAAb,MAA8C;CACf;CAA7B,YAAY,AAAiB,MAAc;EAAd;EAC3B,IAAI,OAAO,KAAK,OAAO,GACrB,MAAM,IAAI,MAAM,qCAAqC;CAEzD;CAEA,aAAa,UAAoC;EAC/C,OAAO,KAAK,OAAO,IAAI,KAAK;CAC9B;CAEA,aAAqB;EACnB,OAAO,aAAa,KAAK,IAAI;CAC/B;AACF;;;;AAKA,IAAa,gBAAb,MAA8C;CAC5C,aAAa,UAAoC;EAC/C,OAAO;CACT;AACF;;;;AAKA,IAAa,eAAb,MAA6C;CAC3C,aAAa,UAAoC;EAC/C,OAAO;CACT;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmCA,IAAa,kBAAb,MAAgD;CAC9C,AAAQ;CACR,AAAQ;CACR,AAAQ;CACR,AAAQ;CACR,AAAQ;CACR,AAAQ;CACR,AAAQ;CAGR,AAAiB,oCAAoB,IAAI,QAA4B;CAErE,AAAiB,mCAAmB,IAAI,QAAoC;CAE5E,YACE,UAUI,CAAC,GACL;EACA,KAAK,qBAAqB,QAAQ,sBAAsB;EACxD,KAAK,kBAAkB,QAAQ,mBAAmB;EAClD,KAAK,qBAAqB,QAAQ,sBAAsB;EACxD,KAAK,mBAAmB,QAAQ,oBAAoB;EACpD,KAAK,aAAa,QAAQ,cAAc;EACxC,KAAK,YAAY,QAAQ,aAAa;EACtC,KAAK,SAAS,QAAQ;EAEtB,IAAI,KAAK,qBAAqB,KAAK,KAAK,qBAAqB,GAC3D,MAAM,IAAI,MAAM,8CAA8C;EAEhE,IAAI,KAAK,YAAY,KAAK,KAAK,YAAY,GACzC,MAAM,IAAI,MAAM,oCAAoC;CAExD;CAEA,oBAA6B;EAE3B,OAAO;CACT;CAEA,aAAa,SAAmC;EAI9C,MAAM,mBAAmB,KAAK,OAAO,IAAI,KAAK;EAC9C,KAAK,kBAAkB,IAAI,QAAQ,MAAM,gBAAgB;EAGzD,OAAO;CACT;;;;;;;;;;CAWA,eAAe,OAAwB;EACrC,IAAI,CAAC,SAAS,MAAM,WAAW,GAC7B,OAAO;EAET,OAAO,MAAM,MACV,SACC,KAAK,YAAY,KAAK,QAAQ,aAAaA,8BAAW,aAAa,CACvE;CACF;;;;;;;;;;;CAYA,gBAAgB,SAA0B,QAAkC;EAC1E,MAAM,mBAAmB,KAAK,kBAAkB,IAAI,QAAQ,IAAI,KAAK;EAGrE,IAAI,KAAK,sBAAsB,CAAC,OAAO,SAAS;GAC9C,IAAI,CAAC,kBACH,KAAK,QAAQ,MACX;IACE,WAAW,QAAQ;IACnB,OAAO,OAAO,OAAO;GACvB,GACA,wCACF;GAEF,OAAO;EACT;EAGA,IAAI,KAAK,oBAAoB,OAAO,YAAY,KAAK,iBAAiB;GACpE,IAAI,CAAC,kBACH,KAAK,QAAQ,MACX;IACE,WAAW,QAAQ;IACnB,UAAU,OAAO;GACnB,GACA,uCACF;GAEF,OAAO;EACT;EAGA,IACE,KAAK,cACL,QAAQ,SACR,KAAK,eAAe,QAAQ,KAAK,GACjC;GAEA,MAAM,aAAa,KAAK,OAAO,IAAI,KAAK;GACxC,IAAI,cAAc,CAAC,kBACjB,KAAK,QAAQ,MACX;IACE,WAAW,QAAQ;IACnB,WAAW,QAAQ,MAAM;GAC3B,GACA,sDACF;GAEF,OAAO;EACT;EAGA,OAAO;CACT;AACF;;;;;;;;;;;;;;;;AAiBA,IAAa,gBAAb,MAA8C;CAC5C,AAAQ;CACR,AAAQ;CACR,AAAQ;CACR,AAAQ;CAER,YAAY,SAKT;EACD,KAAK,qBAAqB,QAAQ,sBAAsB;EACxD,KAAK,oBAAoB,IAAI,IAAI,QAAQ,qBAAqB,CAAC,CAAC;EAChE,KAAK,gBAAgB,QAAQ;EAC7B,KAAK,SAAS,QAAQ;CACxB;CAEA,aAAa,SAAmC;EAC9C,MAAM,SAAS,KAAK,cAAc,QAAQ,IAAI;EAG9C,IAAI,UAAU,KAAK,kBAAkB,IAAI,MAAM,GAAG;GAChD,KAAK,QAAQ,MACX;IACE,WAAW,QAAQ;IACnB;GACF,GACA,uBACF;GACA,OAAO;EACT;EAGA,IAAI,QAEF,OADa,KAAK,WAAW,MACnB,IAAI,KAAK;EAIrB,OAAO,KAAK,OAAO,IAAI,KAAK;CAC9B;;;;CAKA,qBAAqB,GAAG,SAAyB;EAC/C,KAAK,MAAM,UAAU,SACnB,KAAK,kBAAkB,IAAI,MAAM;CAErC;;;;CAKA,wBAAwB,GAAG,SAAyB;EAClD,KAAK,MAAM,UAAU,SACnB,KAAK,kBAAkB,OAAO,MAAM;CAExC;;;;CAKA,AAAQ,WAAW,KAAqB;EACtC,OAAO,iBAAiB,GAAG;CAC7B;AACF;;;;;;;;;;;;;;;;;AAkBA,IAAa,uBAAb,MAAqD;CACnD,AAAiB;CACjB,AAAiB;CAEjB,YAAY,SAKT;EACD,IAAI,QAAQ,aAAa,KAAK,QAAQ,aAAa,GACjD,MAAM,IAAI,MAAM,qCAAqC;EAEvD,KAAK,OAAO,QAAQ;EACpB,KAAK,MAAM,QAAQ;CACrB;CAEA,aAAa,SAAmC;EAC9C,MAAM,MAAM,KAAK,IAAI,OAAO;EAE5B,IAAI,QAAQ,QACV,OAAO,KAAK,OAAO,IAAI,KAAK;EAE9B,OAAO,iBAAiB,GAAG,IAAI,KAAK;CACtC;CAEA,aAAqB;EACnB,OAAO,aAAa,KAAK,IAAI;CAC/B;AACF;;AAGA,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;AAuBrB,IAAa,uBAAb,MAAqD;CACnD,AAAiB;CACjB,AAAiB;CACjB,AAAiB;CACjB,AAAiB;CACjB,AAAQ,yBAAS,IAAI,IAAoB;CACzC,AAAQ,wBAAQ,IAAI,IAAoB;CACxC,AAAQ,cAAc,KAAK,IAAI;CAE/B,YAAY,SAST;EACD,KAAK,MAAM,QAAQ;EACnB,KAAK,eAAe,QAAQ,gBAAgB;EAC5C,KAAK,WAAW,QAAQ,YAAY;EACpC,KAAK,UAAU,QAAQ,WAAW;EAElC,IAAI,KAAK,gBAAgB,GACvB,MAAM,IAAI,MAAM,uCAAuC;EAEzD,IAAI,KAAK,YAAY,GACnB,MAAM,IAAI,MAAM,+BAA+B;CAEnD;;CAGA,AAAQ,KAAK,KAAmB;EAC9B,IAAI,MAAM,KAAK,cAAc,KAAK,UAChC;EAEF,MAAM,wBAAQ,IAAI,IAAoB;EACtC,KAAK,MAAM,CAAC,KAAK,UAAU,KAAK,QAC9B,MAAM,IAAI,KAAK,KAAK,IAAI,GAAG,QAAQ,KAAK,YAAY,CAAC;EAEvD,KAAK,QAAQ;EACb,KAAK,yBAAS,IAAI,IAAI;EACtB,KAAK,cAAc;CACrB;;;;;;;CAQA,AAAQ,WAAW,SAAkC;EACnD,MAAM,MAAM,KAAK,IAAI,OAAO,KAAK;EACjC,IAAI,KAAK,OAAO,IAAI,GAAG,KAAK,KAAK,OAAO,OAAO,KAAK,SAClD,OAAO;EAET,OAAO;CACT;CAEA,aAAa,SAAmC;EAC9C,KAAK,KAAK,KAAK,IAAI,CAAC;EACpB,MAAM,MAAM,KAAK,WAAW,OAAO;EACnC,KAAK,OAAO,IAAI,MAAM,KAAK,OAAO,IAAI,GAAG,KAAK,KAAK,CAAC;EACpD,MAAM,OAAO,KAAK,MAAM,IAAI,GAAG,KAAK;EACpC,OAAO,QAAQ,KAAK,KAAK,OAAO,IAAI,IAAI;CAC1C;CAEA,WAAW,SAAkC;EAC3C,OAAO,KAAK,MAAM,IAAI,KAAK,WAAW,OAAO,CAAC,KAAK;CACrD;AACF;;;;;;;;;;;;;;AAeA,IAAa,mBAAb,MAAiD;CAClB;CAA7B,YAAY,AAAiB,UAAqB;EAArB;EAC3B,IAAI,SAAS,WAAW,GACtB,MAAM,IAAI,MAAM,sDAAsD;CAE1E;CAEA,aAAa,SAAmC;EAC9C,OAAO,KAAK,SAAS,MAAM,YAAY,QAAQ,aAAa,OAAO,CAAC;CACtE;AACF;;;;;;;;;;;;;;;;AAiBA,IAAa,qBAAb,MAAmD;CACjD,AAAQ;CACR,AAAQ;CACR,AAAQ;CAIR,AAAQ;CAER,YAAY,SAQT;EACD,KAAK,qBAAqB,QAAQ,sBAAsB;EACxD,KAAK,oBAAoB,IAAI,IAAI,QAAQ,qBAAqB,CAAC,CAAC;EAChE,KAAK,eAAe,QAAQ;EAC5B,KAAK,SAAS,QAAQ;CACxB;CAEA,aAAa,SAAmC;EAC9C,MAAM,QAAQ,KAAK,aAAa,QAAQ,MAAM,QAAQ,QAAQ;EAG9D,IAAI,SAAS,MAAM,MAAM,SAAS,KAAK,kBAAkB,IAAI,IAAI,CAAC,GAAG;GACnE,KAAK,QAAQ,MACX;IACE,WAAW,QAAQ;IACnB;GACF,GACA,+BACF;GACA,OAAO;EACT;EAGA,OAAO,KAAK,OAAO,IAAI,KAAK;CAC9B;;;;CAKA,qBAAqB,GAAG,OAAuB;EAC7C,KAAK,MAAM,QAAQ,OACjB,KAAK,kBAAkB,IAAI,IAAI;CAEnC;;;;CAKA,wBAAwB,GAAG,OAAuB;EAChD,KAAK,MAAM,QAAQ,OACjB,KAAK,kBAAkB,OAAO,IAAI;CAEtC;AACF;;;;;;;;;;;;;;AA0BA,MAAa,kBAAkB;;CAE7B,mBAAmB,IAAI,cAAc;;CAGrC,kBACE,IAAI,gBAAgB;EAClB,oBAAoB;EACpB,oBAAoB;CACtB,CAAC;;;;;CAMH,aAAa,cAMX,IAAI,gBAAgB;EAClB,oBAAoB;EACpB,oBAAoB;EACpB,kBAAkB;EAClB,iBAAiB;EACjB,GAAG;CACL,CAAC;;CAGH,WAAW,IAAI,aAAa;AAC9B;;;;;;;AAQA,SAAgB,sBAAsB,QAAiC;CACrE,QAAQ,QAAR;EACE,KAAK,eACH,OAAO,gBAAgB,YAAY;EAErC,KAAK,eACH,OAAO,gBAAgB,WAAW;EAEpC,KAAK,cACH,OAAO,gBAAgB,WAAW;EAEpC,KAAK,OACH,OAAO,gBAAgB,IAAI;EAE7B,SACE,MAAM,IAAI,MACR,6BAA6B,OAAO,4DACtC;CAEJ;AACF;;;;;;;;;;;;;;;;;;;;;;AA2BA,SAAgB,sBACd,SACA,YACa;CAGb,MAAM,cAAc,QAAQ,eAAe,QAAQ;CACnD,IAAI,CAAC,aACH,OAAO;CAGT,MAAM,cAAc,iBAAiB,WAAW;CAChD,IAAI,CAAC,eAAe,CAAC,mBAAmB,WAAW,GACjD,OAAO;CAGT,OAAO;EACL,SAAS;EACT,YAAY,cAAc,CAAC;CAC7B;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BA,SAAgB,sBACd,UACA,aAAqB,WACb;CACR,MAAM,QAAgB,CAAC;CAEvB,KAAK,MAAM,OAAO,UAAU;EAC1B,MAAM,aAAa,IAAI;EACvB,IAAI,cAAc,OAAO,eAAe,YAAY,eAAe,MAAM;GACvE,MAAM,OAAO,sBAAsB,YAAsC,EACvE,iCAAiC,MAAM,OACzC,CAAC;GACD,IAAI,MACF,MAAM,KAAK,IAAI;EAEnB;CACF;CAEA,OAAO;AACT;;;;;;;AAQA,SAAS,iBACP,aACiD;CAMjD,MAAM,QAAQ,YAAY,MAAM,8DAAiB;CACjD,IAAI,CAAC,SAAS,MAAM,SAAS,GAC3B,OAAO;CAGT,MAAM,UAAU,MAAM;CACtB,MAAM,UAAU,MAAM;CACtB,MAAM,SAAS,MAAM;CACrB,MAAM,QAAQ,MAAM;CAGpB,IAAI,CAAC,WAAW,CAAC,WAAW,CAAC,UAAU,CAAC,OACtC,OAAO;CAIT,IAAI,YAAY,MAEd,OAAO;CAGT,OAAO;EACL;EACA;EACA,YAAY,OAAO,SAAS,OAAO,EAAE;EACrC,UAAU;CACZ;AACF;;;;AAKA,SAAS,mBACP,aACyD;CACzD,IAAI,CAAC,aAAa,OAAO;CAGzB,OACE,YAAY,YAAY,sCACxB,YAAY,WAAW;AAE3B"}