Docs Logging and Trace ID

Logging and Trace ID

Logging and traceability are not optional features in production systems.

They are the minimum requirements for understanding behavior, diagnosing failures, and operating services with confidence.

Plumego does not provide logging or tracing by default.
This is intentional.

Instead, Plumego provides clear extension points so that logging and trace identifiers can be implemented explicitly and correctly.


Design Principles

Before looking at code, it is important to understand the guiding principles:

  • Logging is a cross-cutting concern → implemented as middleware
  • Trace IDs are request-scoped → stored in context
  • No global magic → everything is explicit
  • Framework-agnostic logging libraries → Plumego does not prescribe one

This keeps observability flexible and replaceable.


What Is a Trace ID?

A Trace ID is a unique identifier assigned to a single request.

It allows you to:

  • Correlate logs across layers
  • Track a request through middleware and handlers
  • Associate downstream calls with the originating request
  • Debug production issues reliably

Every request should have exactly one Trace ID.


Where Trace IDs Live

In Plumego:

  • The Trace ID is generated (or extracted) in middleware
  • Stored in the request-scoped context
  • Read by handlers, services, and logging utilities
  • Never stored globally

This ensures isolation and correctness.


Step 1: Decide the Trace ID Strategy

A common strategy is:

  1. If the incoming request has a Trace ID header → reuse it
  2. Otherwise → generate a new Trace ID

Typical header names:

  • X-Request-ID
  • X-Trace-ID

The exact name is a system-level decision.


Step 2: Trace ID Middleware (Example)

Below is a conceptual example of a Trace ID middleware.

func TraceIDMiddleware() plumego.Middleware {
	return func(ctx *plumego.Context, next plumego.NextFunc) {
		traceID := ctx.Request().Header.Get("X-Trace-ID")

		if traceID == "" {
			traceID = generateTraceID()
		}

		ctx.Set("trace_id", traceID)

		// Expose Trace ID to the client
		ctx.ResponseWriter().Header().Set("X-Trace-ID", traceID)

		next()
	}
}

Key points:

  • Trace ID is generated once per request
  • Stored in context
  • Propagated to the response
  • Middleware controls the lifecycle

Step 3: Accessing Trace ID in Handlers

Handlers can retrieve the Trace ID from context:

traceID, _ := ctx.Get("trace_id")

This value can be:

  • Included in logs
  • Passed to downstream calls
  • Returned in error responses

Handlers should treat Trace ID as read-only metadata.


Step 4: Structured Logging

Plumego does not prescribe a logging library.

However, it strongly encourages structured logging.

A typical logging call might include:

  • Timestamp
  • Log level
  • Message
  • Trace ID
  • Request metadata

Example (conceptual):

logger.Info("request completed",
	"trace_id", ctx.Get("trace_id"),
	"method", ctx.Request().Method,
	"path", ctx.Request().URL.Path,
	"status", statusCode,
	"duration_ms", duration,
)

The important part is not the library —
it is consistent fields across logs.


Logging Middleware Pattern

Logging is best implemented as middleware that:

  1. Records start time
  2. Calls next()
  3. Logs request outcome

Example structure:

func LoggingMiddleware(logger Logger) plumego.Middleware {
	return func(ctx *plumego.Context, next plumego.NextFunc) {
		start := time.Now()

		next()

		duration := time.Since(start)

		logger.Info("request",
			"trace_id", ctx.Get("trace_id"),
			"method", ctx.Request().Method,
			"path", ctx.Request().URL.Path,
			"duration_ms", duration.Milliseconds(),
		)
	}
}

This keeps logging:

  • Centralized
  • Consistent
  • Decoupled from handlers

Ordering Matters

Middleware order is critical.

Recommended order:

  1. Trace ID middleware
  2. Logging middleware
  3. Authentication / Authorization
  4. Business handlers

This ensures:

  • All logs include a Trace ID
  • Logging captures authentication failures
  • Timing is accurate

Middleware order should be treated as part of system design.


Avoiding Common Logging Mistakes

Logging Inside Domain Logic

Domain code should not log.

Reasons:

  • It pollutes core logic
  • It couples domain to infrastructure
  • It reduces testability

Logging belongs at the edges.


Generating Trace IDs in Multiple Places

There must be exactly one source of truth.

Multiple Trace IDs per request destroy observability.


Using Global Context for Trace IDs

Trace IDs must be request-scoped.

Global storage leads to data races and incorrect correlation.


Propagating Trace IDs Downstream

When calling external systems:

  • Include the Trace ID in outgoing headers
  • Use the same header name consistently

This enables cross-service tracing even without a full tracing system.


Trace ID vs Distributed Tracing

Trace IDs are a foundation.

They can later integrate with:

  • OpenTelemetry
  • Jaeger
  • Zipkin
  • Cloud provider tracing

Plumego’s explicit approach makes this integration straightforward.


Minimal Is Enough

A simple Trace ID + structured logging setup provides:

  • Debuggability
  • Correlation
  • Operational confidence

You do not need a full tracing stack on day one.

Plumego encourages incremental observability.


Summary

In Plumego:

  • Logging is explicit middleware
  • Trace IDs are request-scoped
  • Context is the carrier
  • Libraries are replaceable
  • Behavior is predictable

Observability is not magic — it is structure.


Next

With logging and traceability in place, the next practical concern is:

→ Panic Recovery

This explains how to prevent unexpected crashes from taking down your service.