Backend
Following a Parsing Request Through Node.js Async Work
By Vivek Jalondhara · 5 min read
Published
Use request context to connect events across awaits, then distinguish request IDs, job identities, and distributed trace propagation.
A parsing failure is hard to investigate when upload, model-call, and persistence logs contain unrelated messages.
This educational example uses Sheet Parser’s workflow to explain local request correlation in Node.js. It does not claim that a particular logging service, trace backend, or background-job architecture is deployed in the project.
Name the operation you are following
A request ID identifies one HTTP attempt. A job ID identifies durable processing that might outlive that request, and an attempt number distinguishes worker retries. Keep those meanings separate. Reusing a job ID as the request ID makes several separate attempts look like one continuously running request.
Imagine an upload request creating a parse job, followed later by a status request. The two requests should have different request IDs and refer to the same authorized job. That relationship lets an investigator follow the workflow without pretending that the original network connection remained open until the model finished.
Keep context out of global mutable state
A module-level currentRequest variable fails when two asynchronous requests interleave. One request can overwrite the value before another logs its result. Passing an ID explicitly works, but repeatedly threading it through unrelated helpers can obscure their actual inputs.
Node.js AsyncLocalStorage associates a store with asynchronous work created inside a scoped callback. The example creates a fresh store for each invocation of withRequest. A helper reads that store after an await without requiring a global current value. It throws when no request context exists so missing instrumentation is visible during development instead of producing misleading correlations.
Record transitions rather than payloads
Useful events describe what the application attempted and which stage it reached: source accepted, provider request started, response rejected, or result persisted. Give event names a controlled vocabulary. For durations, measure elapsed time with a monotonic clock and report each stage separately; wall-clock timestamps serve a different purpose.
A vendor price sheet can contain commercial details that do not belong in routine logs. Avoid dumping request bodies, model prompts, uploaded content, authorization headers, or arbitrary provider responses. Prefer an access-controlled source reference and a bounded error category. If deeper investigation needs content, make that an explicit, restricted workflow rather than a side effect of every failure.
Carry context across process boundaries deliberately
AsyncLocalStorage is local to its execution environment. A queue message or another service does not automatically inherit it. Persist the durable relationship with the job and create a fresh worker context when processing begins. Use a job ID and attempt identity to connect delayed work to its originating request.
Distributed tracing adds trace and span relationships across services. W3C Trace Context defines a propagation format; a random UUID request header is not that format. Use a compatible tracing implementation when distributed spans are needed. Validate incoming metadata at trust boundaries and avoid treating a caller-supplied trace identifier as proof of identity or access.
Make instrumentation survive error paths
Add completion reporting to both success and failure paths, and distinguish a client disconnect from a backend exception. A disconnected client does not establish that work was canceled. If the operation continues, its events still need the same local context and a clear durable outcome.
The small example uses native promises. Third-party callback systems or custom thenables can require additional context integration, so verify the actual libraries involved. Detached work also needs a defined owner: starting an untracked promise inside a request does not make its failures observable or its execution durable after the process exits.
Test interleaving and keep the cost bounded
Run two requests that pause at different awaits and capture their events. Each pair of start and finish messages should share its own ID, while the two requests use distinct IDs. Log outside either context and confirm the chosen missing-context behavior. Include a rejected promise to make sure error handling keeps the right relationship.
Finally, decide which events are retained, who can query them, and how failure reporting behaves if the log destination slows down. Request IDs belong in searchable event fields, not as an unbounded metric label. Correlation makes a record easier to find; useful stage names and explicit outcomes make that record worth reading.
Preserve a local request ID across an await
Educational JavaScript using Node.js built-ins. Each invocation creates a fresh context. The helper emits controlled stage names only; it is a local correlation example, not an HTTP server or distributed tracer.
Illustrative example · JavaScript · Simplified for explanation
import { AsyncLocalStorage } from 'node:async_hooks';
import { randomUUID } from 'node:crypto';
const requests = new AsyncLocalStorage();
export function withRequest(work) {
return requests.run({ requestId: randomUUID() }, work);
}
export function event(stage) {
const context = requests.getStore();
if (!context) throw new Error('Missing request context');
console.log(JSON.stringify({
event: stage,
requestId: context.requestId,
}));
}
await withRequest(async () => {
event('parse.started');
await Promise.resolve();
event('parse.accepted');
});- Create a request context
- Run asynchronous work
- Record bounded stage events
- Link durable jobs separately
Conceptual flow for the example above.
Tradeoff to keep in mind
This code does not propagate headers, configure a log sink, manage sampling, or make background work durable. Those are separate integration decisions; request correlation alone does not provide distributed tracing.