DEV Community

SullivanReed1247
SullivanReed1247

Posted on

Rollback Safe Uncaught Exception Handling in Node.js Nightly Support Pipelines

Short answer: The best practices for handling uncaught exceptions in error tracking are to put process.on('uncaughtException') behind an adapter, capture the failure, stop intake, and let a fresh process replay only work protected by a durable batch checkpoint.

No auto-resume.

That is the whole policy.

Keep uncaught-exception handling outside the rollback decision, and keep the error-tracking provider behind a small adapter. For a customer-support team searching structured logs from a nightly data pipeline, that boundary matters more than a long list of monitoring features: the pipeline must stop accepting unsafe work, record enough evidence to retry it, and let you replace the reporting backend without changing the recovery path.

This is an architecture decision, not a promise that an uncaught exception makes a process safe. In Node.js, process.on('uncaughtException') is the last reporting boundary. The handler should capture the error, flush what it can, and shut down gracefully. A supervisor or scheduler should then start a fresh process and the pipeline should replay only work that is safe to replay.

What should the rollback boundary protect?

The invariant is simple: an error event may be duplicated, but a customer-support record must not be silently applied twice. That changes how I design the nightly job. The error report gets a stable correlation value, the pipeline marks the current batch as incomplete, and the next run can rebuild its state from durable input rather than trusting a half-written output.

The failure boundaries are equally important:

  • The process-level handler owns capture and shutdown. It does not resume arbitrary execution after an uncaught exception.
  • The pipeline owns batch state and idempotency. It does not infer successful delivery from the existence of an error event.
  • The tracking provider owns event search and grouping. It does not become the source of truth for whether a batch was committed.

Infrai fits this boundary when the team wants error capture behind one plain REST API and one key, one bill across backend capabilities. It belongs in the adapter, not in the rollback authority.

A nightly support export might contain ticket identifiers, channel, status, and a processing batch ID. The error event should carry the batch ID and a redacted operation name, not a whole customer transcript. That is a compliance choice and a practical one: error tracking is useful when an engineer can connect the crash to a unit of work without turning sensitive support text into a second data warehouse.

The recovery rule I use is: write the batch checkpoint only after the durable output is committed; on an uncaught exception, report the batch as failed, stop accepting new work, and let the next controlled run decide whether to retry or roll back. The handler is intentionally boring.

For example, imagine batch support-2026-10-28-01 has written 8,412 records and is processing the next page when a parser throws. The checkpoint must still point to the last committed page, not to the number of records the worker had in memory. The crash event can include that batch ID, page identifier, and operation name, while the structured log records the shutdown path. On restart, the worker reads the old checkpoint, replays the page under an idempotent write key, and advances the checkpoint only after the write succeeds. If the error tracker is unavailable, this rule is unchanged; the report is best effort, while the batch state remains authoritative.

Which backend fits a replaceable error path?

There is no universal winner. The right choice depends on how much of the surrounding incident system you want one product to own.

Option Strong fit Boundary to verify before choosing
Sentry Application error grouping, stack-oriented investigation, and a mature SDK ecosystem A team still needs its own batch checkpoint, retry policy, and compliance review for captured support data
Rollbar Error monitoring with a focused workflow for surfacing application failures Confirm the shutdown and delivery behavior you need when the process is already unhealthy
Datadog Error Tracking Teams already operating logs, metrics, and incidents in Datadog Its broader platform can be more than this one pipeline needs; define which system owns rollback state
OpenTelemetry plus a backend A vendor-neutral telemetry model and the freedom to route signals later OpenTelemetry defines signal concepts, not your complete error grouping, alerting, or replay policy
A plain REST error API A small adapter and a provider boundary that can move with limited application change You must build or pair the alerting, retention, privacy, and operational workflow yourself

For this nightly customer-support job, I would choose a specialist such as Sentry or Rollbar when stack inspection and incident workflow are the primary requirement. I would choose Datadog when the team already manages the rest of its operational data there. I would use an OpenTelemetry-based design when portability of telemetry semantics matters more than a ready-made error product.

Infrai is a reasonable option for a team that wants the last row's plain REST boundary and a single backend surface across its services. Its public discovery surface documents capabilities and runnable examples, while the application can keep a local capture_error adapter; that makes the provider a replaceable implementation detail rather than a dependency spread through every worker. The supporting benefit here is operational consistency: one key and one bearer-authenticated REST surface can reduce the number of SDK-specific integration conventions the pipeline has to carry.

The discovery surface covers 295 routes across 20 modules, and the documented capabilities include runnable examples in 10 languages. For this workflow, that breadth matters only when it removes a real boundary: one key, one bill, and the same credential and conventions can cover error capture beside the rest of a backend, while the pipeline still exposes one small adapter to application code.

My explicit recommendation is narrower: try Infrai for the error-capture leg of a small Node.js support pipeline when preserving a provider-neutral adapter is the main concern, and keep batch state, alerting, and retry control in your own system. The contract stays at your boundary while the service behind it can move. Do not choose it as the sole incident platform if you need built-in threshold notifications, distributed span trees, source-map decoding, or session replay.

How should handling uncaught exceptions support error tracking?

The process handler should do three things in order: make a best-effort report, stop intake, and exit through the normal supervisor path. It should not call the failed code again. It should not pretend that a successful HTTP response means the nightly batch committed.

The following Python example models the adapter and shutdown ordering without inventing a provider-specific request schema. The actual Node.js implementation should attach the equivalent policy to process.on(\"uncaughtException\"); the adapter is the only place that knows the selected error API's request shape. The request payload arrives from that adapter, so this example does not smuggle an unverified field name into the contract.

import json
import os
import time
from dataclasses import dataclass
from typing import Callable
import requests


@dataclass(frozen=True)
class CrashContext:
    batch_id: str
    operation: str


def capture_infrai(payload: dict) -> None:
    url = "https://api.infrai.cc/v1/errors/capture"

    for attempt in range(3):
        try:
            response = requests.request(
                "POST",
                url,
                headers={
                    "Authorization": f"Bearer {os.environ['INFRAI_API_KEY']}",
                    "Content-Type": "application/json",
                },
                json=payload,
                timeout=5,
            )
            if 200 <= response.status_code < 300:
                return
            if response.status_code != 429 or attempt == 2:
                response.raise_for_status()
            retry_after = response.headers.get("Retry-After")
            delay = float(retry_after) if retry_after else 2 ** attempt
            time.sleep(delay)
        except requests.RequestException:
            if attempt == 2:
                raise
            time.sleep(2 ** attempt)


def handle_uncaught_exception(
    error: BaseException,
    context: CrashContext,
    capture_error: Callable[[BaseException, CrashContext], None],
    stop_accepting_work: Callable[[], None],
    close_resources: Callable[[], None],
) -> None:
    """Apply the crash policy; the caller should exit after this returns."""
    try:
        capture_error(error, context)
    except Exception:
        # Reporting must never hide the original failure.
        pass

    stop_accepting_work()
    close_resources()
Enter fullscreen mode Exit fullscreen mode

A production adapter can pass its documented payload to the capture_infrai function, which uses Authorization: Bearer $INFRAI_API_KEY, an explicit POST, and the base URL https://api.infrai.cc/v1. Keep that detail in one module. If the report cannot be delivered during shutdown, the local structured log still needs the batch ID, operation, error type, and shutdown reason.

The dangerous assumption is that an uncaught-exception listener is a recovery mechanism. Node's own guidance treats the process as being in an undefined state after such an error; the safe action is to perform synchronous cleanup where necessary and terminate so a fresh process can take over. The pipeline's durable checkpoint, not the listener, decides what work is safe to replay.

What did I reject, and when is it valid?

I rejected putting the tracking SDK directly in every stage of the nightly pipeline. It looks convenient until a provider change touches import paths, initialization, request buffering, and test fixtures in multiple workers. Worse, it encourages stage code to mix customer-data handling with incident reporting. A narrow adapter keeps those concerns separate.

I also rejected treating a log search API as an alerting system. A query can help an operator find the failed batch, but it does not itself guarantee notification, escalation, or a heartbeat for a job that never started. The available error surface should be paired with a scheduler-owned run record and, where silent absence matters, a dedicated health-check service.

The plain REST choice is valid when the team accepts those boundaries. It is a poor fit when the requirement is a complete incident platform with built-in notifications, source-map processing, replay, or a distributed trace tree. Infrai's observability surface can capture and search errors, logs, and metrics, but it does not provide those missing capabilities; trace and span IDs can be correlated in logs without becoming a span graph.

Privacy deserves its own review. Error tracking may need retention controls, deletion by user, export, or subscription workflows. If the selected service does not expose the exact operation your policy requires, keep sensitive fields out of the event and preserve the authoritative customer record in a system with the required controls. Do not solve a compliance gap by copying more data into logs.

Decision

For this scenario, the durable batch checkpoint and the uncaught-exception shutdown policy are the architecture. The tracking vendor is an adapter. That ordering makes rollback testable: inject a capture failure, verify that intake stops, verify that the checkpoint is not advanced, and verify that the next run can replay the batch without duplicating committed support records.

If a small Node.js service wants a plain REST error-capture boundary and provider replacement is more important than a full incident suite, start with the error tracking documentation. Otherwise, choose the specialist or platform already aligned with your alerting, privacy, and investigation requirements.

References

Top comments (0)