trace
A lightweight Go package providing HTTP request trace propagation, trace ID generation, and context-based trace ID correlation.
Usage
Injecting Trace Middleware
Integrate TraceMiddleware into your HTTP handler pipeline to automatically extract, validate, and propagate request trace IDs:
import (
"net/http"
"github.com/OpenNSW/core/trace"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/api/v1/resource", handleResource)
// Wrap the mux with the trace middleware
handler := trace.TraceMiddleware(mux)
http.ListenAndServe(":8080", handler)
}
Logging with Trace IDs
Wrap an slog.Handler with logging.NewHandler (in the trace/logging subpackage) to automatically attach the request's trace ID as a "traceId" attribute on every log record produced via a *Context slog method (InfoContext, WarnContext, ErrorContext, ...). Call sites no longer need to add traceId manually:
import (
"log/slog"
"net/http"
"os"
"github.com/OpenNSW/core/trace"
"github.com/OpenNSW/core/trace/logging"
)
func main() {
base := slog.NewJSONHandler(os.Stdout, nil)
logger := slog.New(logging.NewHandler(base))
mux := http.NewServeMux()
mux.HandleFunc("/api/v1/resource", func(w http.ResponseWriter, r *http.Request) {
// r.Context() carries the trace ID injected by TraceMiddleware below.
logger.ErrorContext(r.Context(), "request failed") // includes "traceId" automatically
})
http.ListenAndServe(":8080", trace.TraceMiddleware(mux))
}
If the context passed to the logging call has no trace ID, the record is logged unchanged.
Context Helpers
You can manually get or set the trace ID within a context.Context using the provided helpers:
import (
"context"
"fmt"
"github.com/OpenNSW/core/trace"
)
func process(ctx context.Context) {
// Extract the trace ID from the context
traceID := trace.GetTraceID(ctx)
if traceID != "" {
fmt.Printf("Processing request with Trace ID: %s\n", traceID)
}
// Inject a trace ID into a new context
newCtx := trace.ContextWithTraceID(ctx, "custom-trace-id")
_ = newCtx
}
Behavior
The middleware inspects incoming request headers in the following order to resolve a trace or correlation ID:
X-Trace-ID
X-Correlation-ID
X-Request-ID
The first non-empty header value found is selected as the trace ID candidate.
2. Validation Constraints
To prevent header injection or trace ID corruption, resolved candidate IDs are validated. A valid trace ID:
- Must have a length between 1 and 64 characters.
- Must contain only alphanumeric characters (
a-zA-Z0-9) or the following safe special characters: -, _, :, ., /, =.
If the candidate ID fails validation, it is discarded.
3. Fallback Generation
If no candidate ID is found in the headers, or if the candidate ID is invalid, the middleware automatically generates a fallback trace ID using a cryptographically secure random 16-byte array, formatted as a 32-character hexadecimal string.
Once validated or generated, the trace ID is injected into the response headers under:
This ensures that the client receives the exact trace ID that was used to process their request, facilitating end-to-end debugging.
Testing
Run the package tests using (from within the trace directory):
go test ./...