traceid

traceid captures a request-scoped trace ID at the service boundary: it reads the ID from an inbound HTTP header, propagates it through the context.Context for the lifetime of the request, and writes it back into outbound HTTP headers when calling downstream services, without coupling business logic to any particular tracing framework.

Part of nurago, a collection of independent Go packages for backend services.

import "github.com/tecnickcom/nurago/pkg/traceid"

Package traceid captures a request-scoped trace ID at the service boundary: it reads the ID from an inbound HTTP header, propagates it through the context.Context for the lifetime of the request, and writes it back into outbound HTTP headers when calling downstream services, without coupling business logic to any particular tracing framework.

How It Works

The package uses an unexported struct type (ctxKey) as the context key, guaranteeing no collision with any other package’s context values.

  • NewContext stores the trace ID in the context. If an ID is already present the context is returned unchanged, making it safe to call at every layer without overwriting an upstream-supplied ID.
  • ForceContext stores the trace ID unconditionally, overwriting any ID already present; use it when the authoritative ID has just been determined and the context must agree with what is propagated downstream.
  • FromContext retrieves the stored ID, falling back to a caller-supplied default (typically DefaultValue) when none is set.
  • FromHTTPRequestHeader extracts the trace ID from an incoming HTTP request header and validates it with Valid (1 to MaxIDLen characters from the set [0-9A-Za-z._-]). Any value that does not match (including an empty or absent header) is replaced with the caller-supplied default, preventing header-injection.
  • SetHTTPRequestHeaderFromContext reads the ID from the context and writes it onto an outgoing [*http.Request] header, completing the propagation loop to downstream services.

The context stored under the key is not validated (see NewContext); callers that ingest an ID from an untrusted origin should validate it with Valid, or obtain it through FromHTTPRequestHeader, before storing or logging it.

The package also exports the conventional defaults DefaultHeader (“X-Request-ID”), DefaultValue (""), and DefaultLogKey (“traceid”) so all services in a system can share consistent naming without hardcoding strings.

Usage

At the inbound boundary (e.g. an HTTP middleware):

id := traceid.FromHTTPRequestHeader(r, traceid.DefaultHeader, traceid.DefaultValue)
ctx := traceid.NewContext(r.Context(), id)
// pass ctx to all downstream handlers

Anywhere in the call chain (logging, metrics, business logic):

id := traceid.FromContext(ctx, traceid.DefaultValue)
logger.With(traceid.DefaultLogKey, id).Info("processing request")

At an outbound boundary (e.g. before calling a downstream service):

traceid.SetHTTPRequestHeaderFromContext(ctx, outboundReq, traceid.DefaultHeader, traceid.DefaultValue)

When To Use

  • Log lines across a request must share one correlation ID.
  • You want trace propagation without coupling business logic to a tracing SDK.
  • Downstream services expect the ID in a specific header.

Example

// a well-formed trace ID (1 to MaxIDLen chars from [0-9A-Za-z._-])
fmt.Println(traceid.Valid("0191b2f1-8f3a-7c2d-9e4b-1a2b3c4d5e6f"))

// an id carrying control characters is rejected, preventing header/log injection
fmt.Println(traceid.Valid("evil\r\nX-Injected: 1"))

// Output:
// true
// false

Full source is in example_traceid_test.go. More runnable examples are on pkg.go.dev.

Dependencies

This package reaches no external module: it uses only the Go standard library.