Deploy & Ops
Instrument a request with timed spans and one durable trace row: best-effort writes that never throw into the caller, per-thread grouping, and a waterfall the UI can render.
1 file
Description
Instrument a request with timed spans and one durable trace row: best-effort writes that never throw into the caller, per-thread grouping, and a waterfall the UI can render.
A route does several timed steps (auth, a model call, a storage write) and you want a durable record of each step's duration, the model used, and the credits spent, grouped by conversation so a UI can chart per-thread timelines.
Construct a tracer at the start, time named sub-steps with span(), record the model and credits as you go, then write exactly one row on end(). A span stores its start offset from the request start plus its duration, which is all a waterfall needs.
const t = startTrace({ clerkId, threadId, route, method });
const reply = await t.span("model", () => streamText({ model, messages }));
t.setModel(model);
t.addCredits(cost);
await t.end(); // writes one request_traces row
The span helper records the span whether the inner function resolves or throws (record in finally, then rethrow), so a failed step still shows up in the waterfall.
Every write is best-effort: a failed insert is swallowed, never thrown into the caller. Tracing that can crash the request it measures is worse than no tracing. Wrap all persistence so the worst case is a missing row, not a 500.
threadId (the logical conversation or session) on every trace so the UI can roll up per-thread stats and render a timeline.kind derived from the route prefix (voice, image, upload, chat, other) so daily activity charts by family.finally.Added 2026-07-01. Back to the Skill Library.

New tutorials, open-source projects, and deep dives on coding agents - delivered weekly.