DEV Community

DarkveilCorvyn26
DarkveilCorvyn26

Posted on

Five Signal Rules for JavaScript and API Error Tracking with Trace ID Correlation

Short answer: use one trace ID from the browser request through the Node.js API, record errors as structured events at both edges, and page only on a small set of correlated symptoms. For a marketplace pricing-rule rollout, that gives you a way to tell a bad flag decision from a noisy browser exception without waking someone for every failed click.

The useful unit is not a dashboard tile. It is a request you can follow at 3am: buyer opens checkout, pricing flag is evaluated, API responds, and the browser renders the result. If any link is missing, the alert says “something is red” and the pager still asks you to guess.

Page first.

Reliability starts with a small error event contract

Start with a deliberately small event contract. Every browser error and API error should carry trace_id, a UTC timestamp, environment, release, route, and a safe actor identifier. Add the pricing flag name and its evaluated variant for requests that touch the new rule. Do not put an email address, token, or full cart payload in the event; those fields turn debugging data into an incident of its own.

The browser catches uncaught exceptions and rejected promises, but it also needs an explicit hook around fetch calls. Capture the response status, method, and pathname, then preserve the server's trace ID. A 401 from an expired session is usually a user-flow metric, not an on-call page. A burst of 5xx responses sharing one trace prefix is different.

On the API, log the same ID at request start and at the error boundary. Keep the event name stable (pricing.evaluate.failed, for example) and put changing values in fields. Stable names make aggregation possible; free-form messages make a very expensive search box.

Here is a minimal Go shape for the server-side event. It writes JSON to a generic collector, so the storage choice can change without changing application code.

package telemetry

import (
    "encoding/json"
    "io"
    "time"
)

type ErrorEvent struct {
    Name       string `json:"name"`
    TraceID    string `json:"trace_id"`
    Route      string `json:"route"`
    Status     int    `json:"status"`
    Release    string `json:"release"`
    Flag       string `json:"pricing_flag,omitempty"`
    Variant    string `json:"pricing_variant,omitempty"`
    OccurredAt string `json:"occurred_at"`
}

func WriteError(w io.Writer, event ErrorEvent) error {
    if event.OccurredAt == "" {
        event.OccurredAt = time.Now().UTC().Format(time.RFC3339Nano)
    }
    return json.NewEncoder(w).Encode(event)
}
Enter fullscreen mode Exit fullscreen mode

The collector should accept retries without multiplying incidents. Give each event an event_id, or derive one from trace ID, event name, and a short time bucket. Deduplication is an operational control, not a cosmetic feature: one browser retry can otherwise look like five customers.

How should a marketplace correlate JavaScript and API errors with trace IDs?

Generate the ID at the first service boundary and propagate it in a request header such as traceparent, following W3C Trace Context. If an upstream already supplied a valid context, continue it; otherwise create a new one. The response should echo a readable correlation value for support tooling, while logs retain the full context needed by your tracing system.

The client wrapper below illustrates the important order: send the context, read the response context, and attach it to either a response error or a JavaScript exception. It is intentionally plain Go-like pseudocode in a Go block so the data contract is unambiguous; the production browser implementation can use the Fetch API and the W3C propagation library.

type APIResult struct {
    TraceID string
    Status  int
    Err     error
}

func CallPricing(endpoint, traceID string) APIResult {
    // The browser request includes traceparent; the API returns the same context.
    resp, err := doRequest(endpoint, map[string]string{
        "traceparent": traceID,
    })
    if err != nil {
        return APIResult{TraceID: traceID, Err: err}
    }
    if resp.Status >= 500 {
        return APIResult{TraceID: resp.TraceID, Status: resp.Status,
            Err: fmt.Errorf("pricing API status %d", resp.Status)}
    }
    return APIResult{TraceID: resp.TraceID, Status: resp.Status}
}
Enter fullscreen mode Exit fullscreen mode

One caveat: a trace ID is correlation, not proof of causation. A shared CDN, a queued retry, or a second API call can put several operations under one trace. Keep a span or operation name beside the ID, and sample long-lived traces with a policy you can explain. I'm not sure any team gets the sampling ratio right on the first release; validate it against a replay of real checkout traffic before trusting the page.

That distinction mattered during a pricing-rule rehearsal. The browser reported a rejected render, while the API graph looked healthy because retries were counted as successful requests. Looking up the shared trace IDs exposed the sequence: the first response was a 422, the retry used a stale cart version, and the final 200 never reached the component that had already unmounted. No single chart described that path. The event contract did. We changed the alert to count distinct traces and added the cart version as a bounded field; the page became quiet without hiding the validation signal.

Governance for a noisy flag rollout

During a flag rollout, the tempting alert is “JavaScript errors increased.” That fires on a browser extension, a malformed experiment assignment, and a genuine pricing regression alike. The better signal is a conjunction: the new variant has an elevated API failure rate, the corresponding browser render error is present, and both events share a trace context within a short window.

Use a control cohort. For each five-minute window, compare pricing_variant=control with pricing_variant=new on request count, 4xx rate, 5xx rate, and client render failures. A percentage without its denominator is theater. A low-volume variant should produce a review notification, not an automatic rollback.

The table is a runbook, not a promise of universal thresholds:

Signal Likely meaning First action
Client exception only Rendering or extension noise Group by release and browser; do not page yet
API 4xx only Validation, auth, or stale cart Check contract and user journey
API 5xx plus new variant Server-side rule path Freeze exposure and inspect traces
Both edges, same trace IDs End-to-end regression Roll back the flag, then preserve samples

That last row is the decision rule I want at night: a small number of high-confidence traces beats a flood of disconnected stack traces.

Rollout boundaries and test evidence before rollback

Test propagation in CI with a fake request that starts in the browser adapter and ends at the pricing handler. Assert that the value is unchanged, that a missing incoming context creates one, and that an error response still emits an event. Add a redaction test for authorization headers and cart fields. These are cheap tests; missing correlation is expensive to discover during checkout.

In staging, force three cases: a client render exception, a rejected pricing request with a 422, and a server failure in the new variant. Verify that the collector receives three distinct event names, that the 422 does not page, and that the server failure can be opened from the browser event by trace ID. Check clock skew too. RFC 5424's severity vocabulary is useful for consistent log levels, but it does not define your alert policy.

Rollback should be a flag operation with a bounded blast radius. Disable the new variant, leave collection running, and retain a sample of affected traces for the postmortem. Do not delete the evidence when the graph turns green. If the rule cannot be disabled independently, the deployment is harder to reason about and should wait for a safer control boundary.

The catch is that this setup is not suitable when you need full session replay, long-term business analytics, or a vendor-specific workflow with zero instrumentation work. In those cases, choose a system that explicitly provides that capability, or accept the added agent and data-governance cost. Stick with simple structured logs and trace propagation when the main question is “what page fired, and did the API agree?”

References

Top comments (0)