Skip to main content
Two methods drain the queue:
logger.flush(timeoutMs?)     // drain in-flight; logger stays active
logger.shutdown(timeoutMs?)  // drain + stop timer + disable logger (idempotent)
Both return a Promise<void>. Both honor the timeout — if events are still buffered when the deadline hits, they’re dropped and onError fires. Without a timeout, they wait as long as it takes (unbounded).

What the SDK wires up for you

By default (flushOnExit: true), createBoundaryLogger attaches listeners to runtime lifecycle hooks that are safe to attach silently:
RuntimeHookBehavior
Nodeprocess.beforeExitFires once the event loop is otherwise empty. Node waits for awaited work inside the handler, so this is the ideal drain point.
Browserdocument.visibilitychange (hidden) + pagehideBest-effort — if the browser is killing the tab, the flush may not finish.
Edge / WorkersnoneRuntime freezes between invocations; there is no reliable hook. You must await logger.flush() per request.

What the SDK does not do

The SDK deliberately does not attach handlers for SIGTERM or SIGINT. Those signals belong to your application’s lifecycle — web servers, database clients, and queue consumers install their own handlers, and a silent SDK listener would either race with yours or keep the process alive past what Ctrl+C should do. For signal coverage, install your own handler and call shutdown():
process.once("SIGTERM", async () => {
  await server.close();
  await logger.shutdown(2000);  // flush with a 2s cap
  process.exit(0);
});

process.once("SIGINT", async () => {
  await logger.shutdown(2000);
  process.exit(0);
});

flush() vs shutdown()

flush()shutdown()
Drains the queueyesyes
Stops the periodic timernoyes
Disables further sendsnoyes (idempotent)
Use whenyou want to checkpoint mid-processyou’re about to exit
Safe to call shutdown() multiple times — every call after the first resolves immediately.

Timeout semantics

await logger.shutdown(2000);
  • Waits up to 2000ms for in-flight writes to finish.
  • Events still queued after the deadline are dropped; onError fires with a ShutdownTimeoutError-like diagnostic.
  • Without a timeout, shutdown() waits indefinitely — fine for short-lived scripts, not for signal handlers (you’ll block Ctrl+C).
Rule of thumb: pass a timeout in every signal handler and every serverless request.

Opting out of the exit hooks

If you own all shutdown pathways yourself and don’t want the SDK attaching anything:
createBoundaryLogger({
  flushOnExit: false,
});
Now nothing gets drained unless you call flush() or shutdown() explicitly. Good for tightly-controlled runtimes (custom process supervisors, test harnesses).

Recipes by runtime

Long-running Node server

const logger = createBoundaryLogger({ apiKey });

for (const signal of ["SIGTERM", "SIGINT"] as const) {
  process.once(signal, async () => {
    await server.close();
    await logger.shutdown(2000);
    process.exit(0);
  });
}

AWS Lambda / Vercel Function

export async function handler(event) {
  try {
    return await handleRequest(event);
  } finally {
    await logger.flush(1000);
  }
}

Cloudflare Workers

export default {
  async fetch(request, env, ctx) {
    const res = await handleRequest(request);
    ctx.waitUntil(logger.flush(1000));
    return res;
  },
};
ctx.waitUntil lets the runtime keep the isolate alive until the flush resolves, without blocking the response.

Next.js App Router route handler

export async function POST(request: Request) {
  try {
    const body = await request.json();
    return Response.json(await handle(body));
  } finally {
    await logger.flush(1000);
  }
}

See also

Node

Long-running servers, beforeExit

Cloudflare / Workers / Edge

ctx.waitUntil pattern

Lambda / Vercel Functions

Per-invocation flush

Browser

pagehide + visibilitychange