Skip to content

Delivery and flushing

The export queue, flush(), flushWithResult(), shutdown(), head sampling and quota back-pressure.

On this page

Spans are batched and exported over HTTP in the background, never inline with your request. That is what keeps recording off your latency budget — and what makes flushing necessary in any process that can end abruptly.

The queue

Capacity
10 MB, including trace envelopes
Batch size
Up to 100 spans
Interval
Two seconds
When full
Oldest queued spans are dropped

The exporter retries transient failures, honours Retry-After, and splits oversized batches. Dropping the oldest spans rather than blocking is deliberate: a telemetry backlog must never become your application's backlog.

init() returns a client with stats() for queued and dropped counts.

flush()

Before a short-lived process returnsTypeScript
import { flush } from "@tracehatch/sdk"

await flush()

flush() waits for pending export with a bounded timeout — five seconds by default — and never rejects. It also waits for runs whose streams are still being read.

Call it before a serverless handler returns, before a script exits, and at the end of a queue job. Do not call it per request in a long-lived server: the batcher is already doing that job, and flushing per request gives up the batching.

flushWithResult()

When you need to know whether it landedTypeScript
import { flushWithResult } from "@tracehatch/sdk"

const result = await flushWithResult()
if (result.status !== "accepted") console.warn("Tracehatch delivery", result)

Returns status, acceptedSpans, droppedSpans, pendingSpans and an optional lastError: { code, httpStatus? } — a safe SDK or API code, never request details or arbitrary server text.

StatusMeaning
disabledNo initialised client. Usually a missing or rejected key.
emptyNothing has been recorded yet for delivery.
acceptedAll queued work was acknowledged with no loss.
timeoutWork is still pending when the timeout elapsed.
failedSomething was lost.

accepted requires the API's 202 receipt with a matching span count. It proves ingestion accepted the batch; the project's trace view confirms it was processed. A web page at the wrong address, a rejected key, a timeout and disabled tracing each produce a distinct status rather than a false pass — which is what makes this the right call for a connection check.

shutdown()

At the very end of the processTypeScript
import { shutdown } from "@tracehatch/sdk"

await shutdown()

shutdown() flushes, stops the timers and removes the capture hooks. Reserve it for process shutdown — calling it per request stops recording for everything after it.

Automatic flushing at exit

With flushOnExit (the default), the queue is flushed on beforeExit and, with a two-second bound, on SIGTERM and SIGINT. When nothing else handles the signal, the SDK re-raises it afterwards so the process still exits. Open automatic runs are ended first.

Vercel functions receive the final flush through waitUntil. On AWS Lambda, Netlify and similar hosts, await flush() before the handler returns.

Head sampling

Record a quarter of runsTypeScript
init({ sampling: { rate: 0.25 } })

The decision is made per run when it starts, so spans of an unsampled run are never sent. That saves the work as well as the volume — and means an unsampled failure is invisible, because the outcome is not known yet when the decision is taken.

Quota back-pressure

While a workspace is over its monthly trace allowance, the API answers each batch with a lower advisory sampling rate. The SDK applies it to new runs, logs one warning, and returns to your configured rate as soon as the hint stops.

At three times the allowance the API rejects batches with 429 quota_exceeded and a Retry-After. The SDK retries with backoff and eventually drops those spans. Nothing in your application fails.

Server-side, above the allowance the ingest worker samples non-failed traces deterministically — failed traces are always kept — and sampled traces keep their counters and metrics but no span detail. See Plans, quotas and retention.

Errors never reach your code

A capture failure does not change the response or the error your application receives. Export problems are logged by the SDK — with debug: true for the detail — and reported through flushWithResult(). They are never thrown into your request path.