autotel
Version:
Write Once, Observe Anywhere
485 lines • 17.2 kB
text/typescript
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 };