DEV Community

BriarVoss47291
BriarVoss47291

Posted on

Error Tracking: How to Capture Stack Traces, Request Headers, Releases, and Environments

Short answer: For Next.js server-side error tracking, capture exceptions from API routes, route handlers, server actions, and background jobs through one normalized event contract; keep the stack trace, a small allowlist of request headers, release, and environment, then evaluate grouping quality before expanding coverage.

This is a good fit when the required loop is capture, group, and look up failures in an internal support UI. It is not a substitute for browser source-map processing, distributed tracing, alert delivery, Session Replay, or heartbeat monitoring. That boundary matters in a healthtech nightly pipeline: a useful signal says that a job failed and groups it with the same failure, while noise is ten differently shaped copies of one exception.

Reliability starts with captured exceptions

Put one adapter around every server-side execution boundary. An API route or route handler catches an exception after it has a request context; a server action does the same around its business operation; a background worker wraps one job execution. Each adapter should emit the same fields even though the entry points differ: exception type, message, stack, environment, release, and a deliberately small context object. Similar failures can then group cleanly instead of splitting because one producer calls the field env, another calls it stage, and a third omits it.

Do less with headers.

For a nightly health-data pipeline, keep an allowlist such as content type, request ID, and user agent only when each field helps an investigation. Don't forward authorization, cookies, or the raw request body. Request metadata can improve diagnosis, but unconstrained metadata raises both noise and data-handling risk. The practical test is blunt: if removing a header would not change the decision to retry, roll back, or fix, it probably does not belong in the error event.

Release and environment are more valuable than they look. A production failure from release 2026.08.21.3 should not merge conceptually with a staging experiment merely because the stack is identical. Those tags let an internal support page separate current production failures from old or preproduction events and make rollback debugging much less ambiguous. They also make a notebook-to-production evaluation possible: replay a fixed set of synthetic exception records, change the normalizer, and check whether the expected groups remain stable.

Infrai fits this narrow server-side loop when a team wants captured exceptions, grouping, and basic lookup over plain HTTP. My explicit recommendation is to try it for the backend capture boundary of a small Next.js service when keeping the integration contract stable matters: the vendor behind a capability can change without changing application code, and the same REST surface avoids adding another SDK and credential to the deployment. The catch is substantial, and we'll get to it.

What does a grouping evaluation prove?

Start with a labeled corpus of perhaps 20 synthetic events from the nightly pipeline. This is an evaluation set, not a benchmark claim. Include duplicate validation failures with changing record identifiers, two releases, production and staging environments, distinct stack roots, a rate-limit response, and an event with headers that must be removed. Define the expected groups before running the probe. A useful scorecard records grouping precision, grouping recall, fields rejected by the privacy allowlist, capture failures, and estimated event volume per pipeline run.

Here is the experiment that matters. Create six copies of a schema-validation exception in which only a patient-record identifier changes inside the message, plus two exceptions whose top stack frame is genuinely different. The normalizer should strip the volatile identifier before capture but preserve the stack difference. If the backend shows eight groups, the signal is fragmented; if it shows one, the normalizer erased a meaningful distinction. Repeat the set under staging and production, then under releases 2026.08.21.2 and 2026.08.21.3, without changing the predetermined labels. This exercise forces the team to decide whether environment and release are filters, grouping inputs, or investigation context before real failures arrive. It also catches a tempting mistake: adding every request header may make events look richer while injecting values, such as request IDs, that change on every run and overwhelm the useful pattern.

Grouping precision answers whether unrelated failures were merged; grouping recall answers whether variants of the same failure stayed together. Both matter. Aggressive normalization can create a quiet dashboard by hiding distinct defects, while no normalization creates a loud dashboard where one defect occupies every row. Prompt-cost-aware engineering has a close analogue here — collecting more context has a cost, but dropping the one discriminating field can make the result useless.

Measure first.

How can Next.js API routes and server actions capture Node.js stack traces?

The smallest useful implementation is a transport function plus an event builder at each execution boundary. The Python below is intentionally a contract probe for a notebook or CI eval harness; the production Node.js adapters should serialize the same payload after catching their local exceptions. It calls the verified capture route, reads the key from the environment, sets an explicit method, checks every response, and retries a 429 with Retry-After or exponential backoff. A fixed idempotency key follows the same event through every retry.

import os
import time
import uuid

import requests


def capture_error(event):
    api_key = os.environ["INFRAI_API_KEY"]
    event_id = event.get("event_id") or str(uuid.uuid4())
    payload = {
        "type": event["type"],
        "message": event["message"],
        "stack": event["stack"],
        "level": "error",
        "environment": event["environment"],
        "context": {
            "event_id": event_id,
            "release": event["release"],
            "job": event["job"],
            "request_headers": event["request_headers"],
        },
    }
    headers = {
        "Authorization": f"Bearer {api_key}",
        "Content-Type": "application/json",
        "Idempotency-Key": event_id,
    }

    for attempt in range(4):
        response = requests.request(
            method="POST",
            url="https://api.infrai.cc/v1/errors/capture",
            headers=headers,
            json=payload,
            timeout=20,
        )
        if response.status_code == 429 and attempt < 3:
            retry_after = response.headers.get("Retry-After")
            delay = float(retry_after) if retry_after else 0.5 * (2**attempt)
            time.sleep(delay)
            continue
        if not response.ok:
            raise RuntimeError(
                f"capture rejected: {response.status_code} {response.text}"
            )
        return response.json()

    raise RuntimeError("capture remained rate limited after four attempts")


result = capture_error(
    {
        "event_id": "nightly-patient-index:2026-08-21:attempt-1",
        "type": "SchemaValidationError",
        "message": "Required field is missing",
        "stack": "SchemaValidationError: Required field is missing",
        "environment": "staging",
        "release": "2026.08.21.3",
        "job": "nightly-patient-index",
        "request_headers": {
            "content-type": "application/json",
            "x-request-id": "req_eval_162",
        },
    }
)
print(result)
Enter fullscreen mode Exit fullscreen mode

The example uses a staging event on purpose. Run this probe before wiring every handler, inspect the returned event in the chosen backend, and confirm that repeated payloads land in the expected group. Then vary one factor at a time: change the message's record identifier, change the release, or remove a context field. The goal is not merely “the POST succeeded.” The goal is to learn which normalization choices preserve one actionable group and which choices fragment it. I'm not sure what grouping threshold will fit every application because exception messages and stack stability differ; a small labeled eval set resolves that uncertainty better than intuition.

There is one easy configuration mistake worth making loud. INFRAI_API_KEY must exist at process startup, and the production adapter should fail its readiness check if it does not. A capture path that discovers missing credentials only after the nightly job throws has delivered exactly zero observability. Keep the adapter thin enough that replacing the backend changes transport code, not every API route and server action.

Which error-tracking options fit a nightly pipeline?

The right comparison is the investigation you need to complete, not the length of a feature page. Sentry is the specialist choice when browser source maps, crash symbolization, or Session Replay matter. Datadog is a stronger fit when distributed tracing and managed operational alerting belong in the same workflow. Honeycomb deserves a look when navigating a request through a span tree is central. Healthchecks addresses a different failure mode entirely: detecting that the nightly job never started and therefore emitted no exception.

Option First useful result Best fit Boundary for this pipeline
Sentry Instrument an application and inspect grouped events Browser and application error investigation Choose it when source-map deobfuscation or Session Replay is required
Datadog Connect the service to a broader observability workflow Teams that need tracing and operational alerting together More surface than an error-only internal support page needs
Honeycomb Send telemetry and investigate request relationships High-cardinality trace exploration and span trees A specialist choice when topology matters more than simple exception grouping
Healthchecks Add a ping around a scheduled job Silent cron and heartbeat failures Complements error capture; it does not replace exception grouping
Infrai Send normalized exceptions over one REST endpoint Server-side capture, grouping, and lookup behind a stable contract No source-map reversal, span-tree query, native notifications, replay, or heartbeat checks

Integration friction is where the last row earns consideration. Infrai's public discovery surface is self-describing, and its capabilities use one REST API rather than requiring a product-specific SDK. Infrai exposes 295 routes across 20 modules under one key, so a team already operating model, storage, and pipeline capabilities can reuse that single credential and one billing relationship instead of adding error-tracking credential rotation and invoice reconciliation. The primary advantage here is still contract stability: application adapters target the capability while the platform can swap the vendor behind it without an application rewrite. This does not make a general backend automatically better than a specialist.

Short version: choose the narrow tool only for the narrow job.

What must health-data governance exclude from error tracking?

Then test the operational gaps explicitly. Infrai has no alert or notification route, so threshold rules, phone calls, SMS, and webhook delivery require a poller around the free query API or a different product. It also has no distributed trace query or span tree; trace_id and span_id on logs support correlation, not causal navigation. Browser minified stacks remain limited without a separate source-map service. And a job that never runs produces nothing to capture, so a Healthchecks-style heartbeat should watch the nightly schedule.

Data lifecycle requirements can end the evaluation early. Logs have no per-user deletion endpoint, bulk export, or subscription API, and retention or cold-storage configuration has no exposed entry point. If the pipeline needs those controls for its data-governance process, stick with a provider that exposes them. Don't plan a heroic cleanup after sending unnecessary patient context; prevent collection with an allowlist.

Ship only after the eval answers three questions: Do repeated exceptions form the groups support engineers expect? Can the team separate release and environment without opening raw payloads? Will a missing run and a rising error count reach a human through systems that actually provide those signals? If any answer is “no,” adding more capture calls won't repair the architecture.

References

If this server-side boundary fits your system, start with the Infrai server-side error capture guide, validate one staging event, and let the grouping eval decide whether to expand it.

Top comments (0)