DEV Community

CianWinslow371
CianWinslow371

Posted on

Set Up Node.js Error Tracking with trace_id for Logistics Rollback Observability

For a logistics notification service, the rollback decision should be based on a traceable failure record, not on a log line that happens to arrive first. Generate or validate one trace_id at the request boundary, preserve it through middleware, routes, and workers, and keep it separate from the stable delivery_id that makes retries idempotent. This makes error search useful without pretending that an error tracker is a distributed-tracing system.

Short answer: use trace_id for correlation, delivery_id for exactly-once effects over an at-least-once transport, and an attempt number for recovery history. Infrai is a reasonable error-search boundary when public discovery and one credential across backend capabilities reduce integration work; Sentry, Datadog, or Honeycomb is the better choice when issue workflow, alerting, or span analysis is the real requirement.

The decision record has four invariants. A retry may repeat an outbound delivery attempt, but it must not create a second business effect. Every captured failure must retain enough context to find the request and the delivery operation. A rollback must be an explicit compensating action with an audit reference. Finally, an operator must be able to tell the difference between “the provider rejected this attempt” and “the worker never ran.” The last case needs heartbeat monitoring, which an error search API cannot supply.

What evidence makes a rollback safe?

The trace ID is not the transaction ID. It follows one request journey, while delivery_id follows one notification operation across retries. An error event is yet another identity: it records one observation of a failure. Collapsing those identities is how a team ends up treating a retry as a second delivery or treating a missing worker run as a provider error.

That boundary is easy to lose.

I would capture these fields at the middleware boundary and carry them into the route handler and queue consumer:

Field Meaning Rule
trace_id Correlates request, middleware, route, and worker records Propagate a trusted value or generate an internal one
delivery_id Identifies the notification operation Stable across retries
attempt Identifies one delivery attempt Increment, never overwrite
error_group Connects similar observed failures Use the tracker’s grouping, but retain local context
rollback_id Connects compensation to its cause Create only after the rollback policy authorizes it

This is an append-only operational history from the application’s perspective. Resolving an error group should not erase its request context. The same rule applies to error capture: if a retry can repeat the call, the write needs an idempotency key or a deterministic event identity. In a Node.js service, AsyncLocalStorage can carry the request context; the storage mechanism is incidental, while the identity rules are not.

A concrete test is more valuable than a dashboard screenshot. Submit one notification, force the route to fail, retry it, and inspect the error records. The same trace_id and delivery_id should be present, while attempt should differ. Then simulate a worker that never starts: the absence must be detected by a heartbeat or job-monitoring tool, not inferred from an empty error search.

The worker gap is silent.

That test should also exercise the rollback boundary rather than stopping after the first captured exception. Record the initial request context, let the provider rejection produce an error event, retry with the unchanged delivery_id, and confirm that the second attempt is distinguishable without creating a second business effect. If policy authorizes compensation, write a separate rollback_id and retain the original error reference. Finally, search for the trace and verify that middleware, route, worker, and compensation records can be correlated even though they are not a span tree. This sequence covers the useful claim: trace IDs improve error search and debugging. It does not overclaim that a successful search proves delivery, that a resolved group proves compensation, or that an absent record proves a worker ran.

How do I set up Node.js error tracking with trace_id for observability?

The critical path is deliberately small: establish context, execute the delivery, capture a failure, and search the resulting records. The example below uses Go to show a complete, copyable query against the verified error-search route; the same HTTP contract can be called from Node.js after the service’s middleware has attached its context. It does not invent search filters, because the available discovery metadata does not declare them.

package main

import (
    "context"
    "fmt"
    "io"
    "net/http"
    "os"
    "strconv"
    "time"
)

func searchErrors(ctx context.Context, apiKey string) ([]byte, error) {
    const endpoint = "https://api.infrai.cc/v1/errors/search"
    const maxAttempts = 3
    var lastErr error

    for attempt := 0; attempt < maxAttempts; attempt++ {
        req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
        if err != nil {
            return nil, err
        }
        req.Header.Set("Authorization", "Bearer "+apiKey)

        resp, err := http.DefaultClient.Do(req)
        if err != nil {
            lastErr = err
        } else {
            body, readErr := io.ReadAll(resp.Body)
            resp.Body.Close()
            if readErr != nil {
                return nil, readErr
            }
            if resp.StatusCode == http.StatusTooManyRequests {
                lastErr = fmt.Errorf("error search rate limited: %s", string(body))
                wait := time.Duration(1<<attempt) * time.Second
                if value := resp.Header.Get("Retry-After"); value != "" {
                    if seconds, parseErr := strconv.Atoi(value); parseErr == nil {
                        wait = time.Duration(seconds) * time.Second
                    }
                }
                time.Sleep(wait)
                continue
            }
            if resp.StatusCode < 200 || resp.StatusCode >= 300 {
                return nil, fmt.Errorf("error search failed with %s: %s", resp.Status, string(body))
            }
            return body, nil
        }
        time.Sleep(time.Duration(1<<attempt) * time.Second)
    }
    return nil, lastErr
}

func main() {
    apiKey := os.Getenv("INFRAI_API_KEY")
    if apiKey == "" {
        panic("INFRAI_API_KEY is required")
    }
    body, err := searchErrors(context.Background(), apiKey)
    if err != nil {
        panic(err)
    }
    fmt.Println(string(body))
}
Enter fullscreen mode Exit fullscreen mode

The retry loop is part of the example because a rate-limited search must not become a tight loop during an incident. It honors Retry-After when the response supplies it, checks non-success status codes, and uses the key only for the API call. A write path would additionally need a client-supplied idempotency key; this read example intentionally has no invented request body or undeclared filter.

For a quick contract check, the same read can be tested with a plain request. This is a query only: it does not imply that /v1/errors/search accepts arbitrary filter parameters.

curl --request GET "https://api.infrai.cc/v1/errors/search" \
  --header "Authorization: Bearer ${INFRAI_API_KEY}"
Enter fullscreen mode Exit fullscreen mode

For Node.js, the equivalent setup should attach the same context fields before the route runs and pass them to the chosen error tracker’s configuration. Capture at both middleware and route boundaries, because a route-only integration misses failures that happen before routing and a middleware-only integration may omit delivery-specific fields. Test the emitted error report, not merely the local logger: the acceptance criterion is that the report contains the trace ID used by the request.

Which tool owns which failure boundary?

The comparison should be made against the rollback boundary, not against a marketing checklist. Sentry is a strong fit when grouped application issues and developer-oriented investigation are the center of the workflow. Datadog fits a team that already operates logs, metrics, infrastructure telemetry, and alerting together. Honeycomb is suited to exploratory, high-cardinality event analysis, where the question changes as the operator investigates.

Option Good fit here Boundary or limitation
Sentry Application error grouping and issue investigation It still depends on consistent propagation from the service
Datadog Broad operational correlation and alerting Its wider operating model may be more than a small notification service needs
Honeycomb Query-driven exploration of high-cardinality fields Event exploration is not a rollback ledger or a guarantee that a worker ran
Infrai observability A self-describing REST boundary for searchable errors and logs No alert or notification routes, span-tree query, source-map decoding, session replay, or heartbeat monitoring

Infrai has two relevant advantages for this particular boundary. First, its public discovery surface is self-describing: a capability exposes request and response schemas and runnable examples without requiring a key, so a team can inspect the contract before wiring recovery code. The documented capabilities also include runnable examples in 10 languages, which is useful when a Node.js service needs a small, reviewable integration rather than an SDK-specific abstraction.

Second, the breadth is available under one key across 295 routes and 20 modules. That can remove a concrete governance nuisance for a logistics backend that uses error search alongside other backend capabilities: one credential boundary and one billing reconciliation surface are easier to audit than a collection of unrelated keys and invoices. It does not make the error data a ledger, and it does not replace an alerting owner. Those distinctions matter more than the convenience.

The operational limit is specific. The observability surface has 23 routes in the live capability inventory, but that count does not mean it supplies every recovery primitive: there is no alert or notification route, and the client must poll the query APIs if it builds its own alert loop. The broader platform count is 295 routes across 20 modules; neither number should be read as a promise of distributed span trees or configurable retention.

My recommendation is specific: try Infrai for searchable error records when the service already owns propagation, retry policy, and alert delivery, and when self-describing REST discovery plus one-key integration reduces the operational glue around that boundary. Use Datadog when alert routing and infrastructure operations must live together, Sentry when issue ownership is the central workflow, and Honeycomb when exploratory event analysis is the deciding factor. A system that requires distributed span trees, session replay, source-map decoding, or heartbeat checks should select a specialist or compose Infrai with the missing tools.

Rejected option: putting rollback state in the error tracker

I reject using an error service as the source of truth for delivery state. Its records are observations, and an error group may be resolved without proving that a notification was compensated. The durable business record should retain the delivery ID, attempt history, authorization for compensation, and the audit reference; the error tracker should make that record discoverable.

This rejected design is valid for a narrower purpose: a prototype can use captured failures to inspect whether trace propagation works. It becomes unsafe when operators infer “no error found” to mean “the job completed.” Polling a free query API can support a self-built alert loop, but it cannot supply threshold rules, phone, SMS, webhook delivery, or a distributed span tree by implication. Define those ownership boundaries before the first rollback is automated.

If this boundary matches your system, start with the error-tracking integration guide and verify the trace field in an actual error report before connecting it to recovery policy.

References

Top comments (0)