Reading Node stack traces under load
When traffic spikes, the stack trace attached to an error log is often the only artefact you have before the process restarts. Under load, those traces lie—not maliciously, but because async boundaries and log pipelines strip context.
Start with the error name and code
ERR_HTTP_HEADERS_SENT and ECONNRESET point to different classes of fixes. Note whether the message repeats across workers or isolates to one pod; that split tells you if you are chasing shared state or a single bad deploy.
Async stacks truncate at await
Node 18+ improves async stack traces, but production loggers frequently capture only the rejection site. Re-run the failing request in staging with NODE_OPTIONS=--async-stack-traces and compare depth. If staging is shallow too, insert temporary logging at each await in the suspect handler—not every route, just the branch that matches the request ID from access logs.
Correlate with request IDs
Without a request ID propagated through your logger, stack traces from concurrent requests get merged mentally. Ensure req.id appears in every line before you invite an external consultant; it saves the first hour of engagement.
When to capture a heap snapshot instead
If the trace ends inside native code or Timeout._onTimeout with no application frames, suspect timers holding closures over large objects. A heap snapshot at the moment of elevated memory is more informative than another stack print.
Book a debugging block if traces stay opaque after these steps.