/** * Traces that are never lost to a collector that is down: a batch the collector does not accept is * spooled to an operation store, retried with backoff, and dead-lettered only once it is too old or * has failed too often, under a bound on how much is kept. Every replica on the same store drains the * same spool, each batch once, and a process that stops leaves its spool for the next. */ import type { OperationStore } from '../types/operations.js'; import type { Run, TraceExporter } from '../types/tracing.js'; /** Sends finished runs to a receiver, and rejects when the receiver did not take them. */ export interface TraceBatchSender { /** Sends one batch. `OtlpTraceExporter.send()` is one. */ send(runs: Run[], signal?: AbortSignal): Promise; } /** Options for `spoolingExporter()`. */ export interface SpoolingExporterOptions { /** Where batches go: an OTLP exporter, or any `TraceBatchSender`. */ sender: TraceBatchSender; /** Where refused batches are kept: any operation store, shared by every replica that drains them. */ store: OperationStore; /** Runs per batch. Defaults to 100. */ maxBatch?: number; /** How long a run waits for others to batch with, in milliseconds. Defaults to 2,000. */ flushIntervalMs?: number; /** * Writes each batch to the spool before sending it, and removes it once sent, so a process that * stops between the two loses nothing either. Off by default: a store write per batch. */ writeAhead?: boolean; /** * The most batches the spool keeps. Spooling one more drops the oldest waiting, counted as dropped. * Defaults to 10,000. */ maxPendingBatches?: number; /** A batch older than this, in milliseconds, is dead-lettered instead of tried again. Defaults to 24 hours. */ maxAgeMs?: number; /** Attempts a batch gets before it is dead-lettered. Defaults to 1,000. */ maxAttempts?: number; /** The wait before a spooled batch's second attempt, in milliseconds, doubling to `maxDelayMs`. Defaults to 1,000. */ baseDelayMs?: number; /** The longest wait between attempts, in milliseconds. Defaults to 5 minutes. */ maxDelayMs?: number; /** Drains the spool this often, in milliseconds. Defaults to 15 seconds; 0 drains only when asked. */ drainEveryMs?: number; /** How long a replica holds a batch it is sending, in milliseconds. Defaults to 60 seconds. */ leaseMs?: number; /** This replica's name in the leases it takes. Defaults to a random one. */ owner?: string; /** The operation kind spooled batches are kept under. Defaults to `nexus.trace.spool`. */ kind?: string; /** Receives failures of the spool itself, such as a store that cannot be written. */ onError?: (error: unknown) => void; /** Replaces the system clock, in milliseconds, for tests. */ now?: () => number; } /** What a spooling exporter has done, counted in runs. */ export interface SpoolStats { /** Runs the receiver took, at once or from the spool. */ exported: number; /** Of those, the runs taken from the spool, after the receiver had refused them. */ retried: number; /** Runs written to the spool because the receiver refused them, or ahead of sending. */ spooled: number; /** Runs dropped because the spool was full: the oldest waiting go first. */ dropped: number; /** Runs dead-lettered: too old, or tried too often. They stay in the store, marked, and are never sent. */ deadLettered: number; /** Batches waiting in the spool now, across every replica on the store. */ pendingBatches: number; /** How long the oldest waiting batch has waited, in milliseconds, when one does. */ oldestPendingAgeMs?: number; } /** What one `drain()` did. */ export interface SpoolDrainReport { /** Batches the receiver took. */ sent: number; /** Batches it refused again, waiting for their next attempt. */ failed: number; /** Batches dead-lettered. */ deadLettered: number; } /** A trace exporter that spools what its receiver refuses, from `spoolingExporter()`. */ export interface SpoolingExporter extends TraceExporter { /** Sends the batch waiting now, spooling it if the receiver refuses it. */ flush(): Promise; /** Tries the spooled batches whose next attempt is due, up to `limit` (100). */ drain(options?: { limit?: number; }): Promise; /** What it has done, and what waits in the spool. */ stats(): Promise; /** Flushes, and stops its timers. The spool stays in the store for any replica to drain. */ close(): Promise; } /** * Wraps a receiver of traces, such as `OtlpTraceExporter`, so that nothing it refuses is lost. * * Runs are batched and sent as they finish. A batch the receiver refuses is kept in the operation * store and tried again, with backoff, every `drainEveryMs`, by every replica sharing the store: each * batch is claimed before it is tried, so it is sent once. One that waits past `maxAgeMs`, or fails * `maxAttempts` times, is dead-lettered, and at most `maxPendingBatches` wait; `stats()` reports what * was exported, retried, spooled, dropped, and dead-lettered, and the age of the oldest batch waiting. * * ```ts * const exporter = spoolingExporter({ * sender: new OtlpTraceExporter({ endpoint: 'https://collector.internal:4318' }), * store: new PostgresOperationStore(client), * }); * const tracer = new Tracer({ store: traces, exporters: [exporter] }); * ``` */ export declare function spoolingExporter(options: SpoolingExporterOptions): SpoolingExporter;