DEV Community

EvanderPierce8279
EvanderPierce8279

Posted on

NestJS Error Tracking: How to Capture HTTP, Cron, and Worker Failures

Short answer: use one global NestJS exception filter for HTTP failures, explicit catch boundaries around cron and worker execution, and process-level handlers for uncaughtException and unhandledRejection; send every path through the same error-capture adapter, then retain enough grouped evidence to reconstruct a property-management incident without storing every duplicate forever.

That is the least complex shape that covers the three places production errors escape. It also makes cost attribution possible: each captured event can carry the property, workload class, and execution surface that produced it, while grouping and deliberate retention keep repeated failures from turning into an unowned storage bill.

Don't confuse error capture with proof that a job ran. A cron task that stops without throwing emits no exception, so it needs a separate heartbeat monitor.

The capture path starts at four boundaries

For error tracking, the dominant controllable term is usually stored event volume, not the number of exception classes. Write the estimate before choosing a product:

stored bytes = events per day x average event bytes x retained days

Then split events per day by HTTP, cron, and workers, and attribute each stream to a property or customer account. This is where cardinality matters. surface=http|cron|worker is bounded. A property identifier is useful for cost allocation. A raw request URL, lease ID, stack trace, or resident email used as a label can create a near-unique series and should stay in the event body instead. Prometheus gives the same warning for metrics labels: every label set creates another time series.

The first material reduction is grouping repeated exceptions by a stable fingerprint and retaining representative events, rather than indexing every varying value as a dimension. The second is sampling repetitions after the first few events while never sampling away the initial occurrence, a state transition, or the final event needed to establish incident duration. Sampling cuts bytes. It also weakens the evidence: a retained count can show frequency, but discarded event bodies can no longer answer which property or worker invocation was affected.

Keep that loss explicit.

For a property-management system, I would assign retention by evidentiary value: authentication and rent-posting failures deserve a longer window than a noisy retry from a noncritical enrichment worker. I'm not sure what the correct window is for your contracts or regulatory obligations; legal requirements and the longest credible complaint delay should resolve it. The engineering point is narrower: retention is a policy decision, not a default that should quietly compound.

How can a NestJS filter and interceptor capture HTTP exceptions, cron job failures, and worker errors?

Start with a single application-owned capture adapter. Its input contract should be stable across all execution surfaces: exception type, message, stack when present, timestamp, environment, release, surface, operation name, and the minimum identifiers needed to reconstruct the incident. Redact secrets and resident data before the adapter sends anything. Do not use a high-cardinality value as a grouping key merely because it is convenient.

Register a global exception filter with NestJS so every uncaught HTTP exception reaches that adapter before the framework produces its response. Preserve the original HTTP status and response behavior; tracking must observe the exception, not redefine the API contract. A request for a route that does not exist is a useful smoke test because it should remain a normal 404 while producing one captured event:

curl --request GET \
  --silent \
  --show-error \
  --output /dev/null \
  --write-out '%{http_code}\n' \
  http://localhost:3000/route-that-does-not-exist
Enter fullscreen mode Exit fullscreen mode

Cron and queue workers need a different boundary because they don't pass through the HTTP filter. Wrap each scheduled handler and worker processor at the place where NestJS invokes application code: catch the error, await the same capture adapter, and then rethrow it so the scheduler or queue retains its native retry and failure semantics. Record surface=cron or surface=worker, plus a bounded operation name such as lease-renewal-scan; keep the job ID in event context, not in a cardinality-sensitive grouping field.

Finally, install process-level handlers for uncaughtException and unhandledRejection. They are a last capture boundary, not a recovery strategy. After a fatal uncaught exception, stop accepting work and let the process supervisor restart the instance; continuing in an unknown state can produce a second incident. Avoid double reporting by marking errors already captured at the HTTP, cron, or worker boundary.

One nuance matters here — capture delivery can fail under rate limiting. The adapter should treat HTTP 429 as retryable, honor Retry-After, and otherwise apply exponential backoff. It should surface other 4xx responses with their bodies because those responses explain invalid input. If capture is a write, use the provider's supported idempotency mechanism so a retry cannot create duplicate evidence.

Test unresolved-group alerts as state transitions

Infrai is a practical option when this error stream is one part of a broader backend integration problem because its consistent REST contract covers 295 routes across 20 modules with one key and one bill. Adding another supported capability therefore does not require another SDK, credential set, or invoice reconciliation path. Its public discovery surface is self-describing, returns the full request and response JSON Schema, and supplies runnable examples in 10 languages; that gives the capture adapter a concrete contract to validate during integration. For this workflow, POST /v1/errors/capture accepts captured failures and GET /v1/errors/groups provides the grouping surface.

There is no native notification routing, so alerting requires polling recent unresolved groups and delivering notifications through infrastructure you own. Poll at an interval justified by the incident response target, persist the last observed group state, and alert on transitions rather than every poll. A one-minute poll across many properties is not free merely because the query has no charge: it still consumes requests, compute, and on-call attention.

The following request is the minimal polling primitive. Set INFRAI_API_BASE in deployment configuration and keep the key in the environment. --retry-all-errors includes 429, curl honors Retry-After when the server supplies it, and --fail-with-body preserves the reason for other 4xx responses:

curl --request GET \
  --header "Authorization: Bearer ${INFRAI_API_KEY}" \
  --header "Accept: application/json" \
  --fail-with-body \
  --retry 4 \
  --retry-all-errors \
  --retry-max-time 60 \
  "${INFRAI_API_BASE}/v1/errors/groups"
Enter fullscreen mode Exit fullscreen mode

This is also where the platform boundary becomes decisive. It has no distributed trace query or span tree, although log records can carry trace_id and span_id; it also has no source-map decoding, crash symbolication, Electron minidump parsing, or Session Replay. It is not suitable when those are requirements. Use a dedicated error-tracking product instead, after validating its data residency, retention, and framework support against your system.

Compare who owns each operational boundary

The products below represent different operational shapes. This isn't a universal ranking. The right choice follows from who owns capture delivery, alert routing, heartbeat evidence, and retention.

Option Best fit in this design Trade-off to verify before committing
Infrai Teams that value plain HTTP and one consistent contract across multiple backend capabilities You must build alert polling; tracing trees, source maps, symbolication, minidumps, and replay are outside this fit
Sentry A dedicated error-tracking evaluation where richer debugging requirements drive the decision Validate SDK behavior, retention, alert routing, and cost attribution with your event shape
Datadog An evaluation for teams considering error evidence alongside a wider observability estate Test the event model and property-level attribution rather than assuming broad coverage guarantees useful grouping
Grafana An evaluation for teams that want error evidence near their existing dashboards Confirm which components will own ingestion, grouping, notification delivery, and retention
Better Stack Another hosted observability option worth testing with the NestJS workload Validate grouping quality and the controls needed for property-level cost allocation
Healthchecks-style monitor Evidence that cron jobs ran when expected Complements exception capture; it does not replace HTTP or worker error tracking

Stick with Sentry when a dedicated product proves materially better for the debugging or notification capabilities your responders require. Evaluate Datadog, Grafana, and Better Stack when their broader operational model aligns with infrastructure the team already owns. Add a Healthchecks-style tool whenever “the task should have run but did not” is an incident condition. No exception pipeline can infer an event that never occurred.

The comparison should be run with a fixed acceptance set: one HTTP exception, one cron exception, one worker rejection, one duplicate, one redacted resident field, and one silent missed schedule. Check which evidence remains after the proposed retention window. Check who receives an alert. Then calculate stored bytes by property and surface. A polished dashboard cannot compensate for an event model that makes those costs impossible to assign.

Budget retained evidence by property

The completed design has four capture boundaries but one event policy. HTTP exceptions pass through the global filter; cron and worker failures pass through explicit catch-and-rethrow boundaries; fatal process errors pass through last-resort handlers. All four converge on bounded grouping dimensions, redaction, and a retention rule tied to incident reconstruction.

What should you deliberately stop keeping? Repeated bodies that add no new state, unbounded values promoted to labels, and low-value noise beyond its investigation window. The catch is that aggressive sampling can remove the one event that distinguishes a property-wide outage from a single bad lease record. Preserve first occurrences and meaningful state changes, measure the retained bytes, and document the uncertainty created by every sampling rule.

Small is good.

The final production test is not “did an exception appear?” It is “can an engineer reconstruct who was affected, which execution surface failed, when the state changed, and why the retained evidence costs what it costs?” If the answer is yes, the system is observable enough for this job. If silent jobs still disappear, add heartbeat monitoring rather than increasing exception retention.

References

Top comments (0)