Batches Unleash SDK impression and custom events in memory and ships them as NDJSON to an ingestion endpoint. Runs in Node (≥20) and the browser.
pnpm add @unleash/sdk-flight-recorderimport { initialize } from 'unleash-client';
import { createFlightRecorder } from '@unleash/sdk-flight-recorder';
const recorder = createFlightRecorder({
url: 'https://ingest.example.com/events',
clientKey: 'your-ingestion-token',
onError: (info) => console.warn('flight recorder:', info),
});
const unleash = initialize({
url: 'https://your-unleash-instance/api/',
appName: 'my-app',
customHeaders: { Authorization: process.env.UNLEASH_API_TOKEN! },
});
// The Unleash Node SDK emits `impression` for every evaluation of a flag that
// has impression data enabled — forward those straight into record().
unleash.on('impression', (event) => recorder.record(event));
// Custom events are caller-originated.
recorder.record({
eventType: 'custom',
context: { userId: 'user-1' },
eventName: 'checkout-completed',
payload: { plan: 'enterprise', amount: 99 },
});
// On process shutdown — flushes what's buffered, then stops.
await recorder.close();record(event) accepts an ImpressionEvent or a CustomEvent; duplicates
within a flush window are dropped. The recorder stamps each event with a
timestamp on record() — events carry no timestamp on the way in. Events
are sent automatically per the batching policy below — flush() is available
for a manual send.
Beyond the required url and clientKey, createFlightRecorder accepts:
batch — when to flush. Defaults to { flushAt: 10_000, flushAfterMs: 10_000 }.
Passing your own batch replaces the whole object, not individual fields.
| Field | Default | Meaning |
|---|---|---|
flushAt |
10_000 |
Flush once the buffer holds this many events. |
flushAfterMs |
10_000 |
Flush at least this often (ms), regardless of buffer size. |
retry — { retries }, default { retries: 2 }. Retries a failed flush
POST with exponential backoff.
onError — failure callback; see Error handling.
A browser caller that bursts past ~180 events between flushes should lower
batch.flushAt — a large keepalive flush on close() exceeds the 64 KB limit.
record() and flush() never throw — the recorder is best-effort, and a flush
failure must not break the code path that produced the event. Failures are
reported through the optional onError callback.
It receives an ErrorInfo with reason: 'persistentFailure' when a flush POST
fails after all retries and the batch is dropped — carrying droppedEventCount
and the underlying error:
createFlightRecorder({
url: 'https://ingest.example.com/events',
clientKey: 'your-ingestion-token',
onError: (info) => {
if (info.reason === 'persistentFailure') {
console.warn(`flight recorder dropped ${info.droppedEventCount} events`, info.error);
}
},
});Dropped events are gone: the recorder does not retry beyond retry.retries, and
the buffer does not survive a restart. Telemetry loss is acceptable by design;
onError is for surfacing it to your metrics or logs, not for recovery.
createFlightRecorder(options)→FlightRecorderFlightRecorder.record(event)— buffer an eventFlightRecorder.flush()— send the buffer nowFlightRecorder.close()— final flush, then stop accepting eventsonError(info)— failure callback; see Error handling