Evidence strategy What it lets you reconstruct Main limitation
Structured error events with trace context A failed operation and its recorded business context Successful steps need their own evidence
Error events plus distributed traces Recorded execution paths across instrumented boundaries Sampling and retention can remove the path
Business transition journal plus correlated telemetry Recorded shipment state changes alongside diagnostic evidence Requires a separate persistence contract

Short answer: Set up Node.js error tracking by recording the active span's trace_id and span_id in structured error events, then propagating trace context across HTTP and queue boundaries. For logistics incident reconstruction, pair those events with a small business transition journal when the customer question depends on shipment state. Keep aggregate metrics separate from per-shipment evidence.

Start with the question support must answer: did the shipment update fail before acceptance, during processing, or after the state changed? A stack trace alone cannot establish that sequence. The matrix above compares evidence strategies. The recommendation is a design choice: retain explicit business transitions where they matter, and use trace-linked errors to explain the processing around them.

For a one-person SaaS, apply a revenue-per-hour lens. Ship weekly. Outsource the undifferentiated collection and storage work when appropriate, while keeping ownership of the event fields that explain your customer's shipment.

Can you reconstruct the customer's shipment incident?

The first criterion is evidence completeness. Consider a hypothetical shipment update, shipment-demo-17, accepted as message message-demo-42. Its first processing attempt fails; a later attempt succeeds. These are illustrative identifiers and a test scenario, not a production incident. The useful record distinguishes the shipment, the message, and the processing attempt. A trace identifies an execution context; it does not replace any of those business identities.

Keep those identities separate.

An error event should include a UTC timestamp, operation name, service identity, deployment version, an internal shipment reference, and the message or attempt reference when relevant. Add trace_id and span_id from the active span, plus a bounded error classification. Avoid customer addresses, shipment contents, access tokens, and arbitrary exception payloads. Decide explicitly which error details may leave the process; a stack can contain sensitive values too.

The trap is assuming that a failed attempt proves a shipment never advanced. A diagnostic event describes what one execution observed. A business transition journal should describe a committed state change. If your system needs those two records to agree, define the transaction boundary and recovery behavior rather than writing to two independent destinations and assuming both writes succeed. That persistence design is application work. Tracing cannot supply it. Order also needs care. A timestamp helps navigation, but sorting records from several processes by wall-clock time should not be your only proof of causality. Retain the message identity, attempt identity, and trace relationships so you can distinguish a retry from a duplicate business update. W3C Trace Context defines the tracing identifiers and propagation format; it does not define your shipment state machine.

Will the evidence still exist when support asks?

The second criterion is retrievability. Agree on the support investigation window before selecting telemetry retention. There isn't a universal number of days in this design. The relevant window comes from how long customers can take to report an incorrect shipment status, together with your data handling requirements. A trace retained for less time than its related error event leaves a useful identifier with no execution detail behind it.

Sampling creates another limitation. OpenTelemetry distinguishes a valid span context from a recording span, so an application can have a trace identifier even when detailed spans are not being recorded. A nonempty trace_id is therefore not proof that a trace will be searchable. Record the error through an explicit application logging path, and verify how that path behaves independently of trace sampling.

Test the missing-trace case.

For metrics, keep dimensions bounded: operation, service, and an intentional error category can describe aggregate behavior. Shipment IDs, trace IDs, and unrestricted exception messages belong in searchable event records, not metric labels. Prometheus instrumentation guidance warns about label cardinality; one label value per shipment would make the metric model grow with individual shipments. A dashboard can tell you that failures increased. The evidence record must explain which processing attempt failed.

Storage volume is a consequence of those choices, not the main decision. Preserve the fields needed for reconstruction, then control duplication, retention, and access. Removing a causal link to reduce event size is a poor bargain if it turns a support question into manual detective work.

How do you set up error tracking with trace context?

Initialize a tracing SDK and its context manager before loading instrumented application modules. Configure the service identity, exporter, sampling policy, and a W3C Trace Context propagator. The OpenTelemetry Node.js guidance covers bootstrap and instrumentation ordering; the API package used below supplies interfaces and access to context, not a complete telemetry pipeline by itself.

This TypeScript helper is an application-level failure boundary for the hypothetical shipment worker. Call it inside the processing span. The emit dependency must accept an event without throwing, apply your redaction policy, and provide an explicit delivery policy. Its implementation is deliberately outside the helper because local buffering, durable delivery, and shutdown flushing have different contracts.

import { context, isSpanContextValid, SpanStatusCode, trace, } from "@opentelemetry/api"; interface ShipmentAttempt { shipment_ref: string; message_ref: string; attempt_ref: string; } interface FailureEvent extends ShipmentAttempt { timestamp: string; operation: string; service: string; deployment: string; error_type: string; trace_id?: string; span_id?: string; } export async function trackShipmentAttempt<T>( ids: ShipmentAttempt, deployment: string, run: () => Promise<T>, emit: (event: FailureEvent) => void, ): Promise<T> { const span = trace.getSpan(context.active()); const spanContext = span?.spanContext(); try { return await run(); } catch (error: unknown) { span?.setStatus({ code: SpanStatusCode.ERROR }); emit({ ...ids, timestamp: new Date().toISOString(), operation: "shipment.update", service: "shipment-worker", deployment, error_type: "shipment_processing_failed", ...(spanContext && isSpanContextValid(spanContext) ? { trace_id: spanContext.traceId, span_id: spanContext.spanId, } : {}), }); throw error; } } 

The helper preserves the original thrown value and marks the active span as failed. It intentionally emits a bounded classification rather than serializing an arbitrary exception. Add approved diagnostic details through the logging policy if your investigators need them.

Span ownership stays explicit.

The SDK or the code that created the span owns ending it; this helper doesn't assume it owns the span lifecycle.

Notice the absent-context behavior: the event still exists, with no fabricated tracing identifiers. Generating an unrelated ID in the catch block would give the appearance of correlation without establishing a relationship to the upstream request. Missing context should be visible in verification, because the next fix belongs at instrumentation or propagation boundaries.

Queue handling needs its own wiring. At publication, inject the current context into the message's metadata using the configured propagator. At consumption, extract that metadata and create the processing span under the intended context; run this helper inside that span. OpenTelemetry messaging conventions describe parent relationships and span links for different messaging patterns. Choose deliberately for retries and batches, retain the message identity, and don't assume HTTP instrumentation also covers your queue client. Treat incoming trace metadata as correlation data, never as authorization or a trusted customer identity.

Verify the reconstruction before the weekly release

Use a synthetic shipment to exercise the complete evidence path. Check four cases: successful processing, a failed processing attempt, a retry, and a failure whose trace is not retained. For each, start from the internal shipment reference and find the event, the attempt, and any retained span. Then explain the final shipment state from committed business records. This checks the actual customer question. W3C Trace Context encodes a trace ID as 32 lowercase hexadecimal characters and a parent ID as 16; an all-zero value is invalid. Merely checking a log line's character count cannot establish that the event belongs to the correct processing attempt.

Include a test where queue metadata is missing. The error should remain findable through the business references even though the trace relationship is unavailable. Also verify that the event sink satisfies its nonthrowing contract, and test process shutdown with buffered events. Otherwise, the helper's error handling assumptions have never been exercised.

Deployment metadata earns its place here. A failed attempt before a release and a successful retry afterward are different executions; record the deployed version so the timeline can distinguish them without guessing from release times. Give investigation queries the same attention as instrumentation: confirm that an authorized operator can search by shipment reference and pivot to a trace without exposing customer payloads.

Keep this rehearsal small enough to run with weekly shipping. Its value is a repeatable answer to a support question, not a larger telemetry dashboard.

When the simpler alternative is enough

Structured error events without a separate transition journal are the runner-up. They can be sufficient when the application's existing database already preserves the business history needed for investigation, and the diagnostic question concerns one failed operation. In that case, link events to those records and document the limitation: the event explains processing, while the database establishes state.

Choose a dedicated transition journal when reconstructing state changes is itself a requirement and existing records do not preserve that history. Add distributed tracing where cross-service execution detail changes an investigation decision. For a solo operator, the useful endpoint is clear: enough retained evidence to explain a customer's shipment, with a collection path that fits the weekly release routine.

References