2023-02-20 02:00:00+00:00

As microservice counts grow, developer velocity can stall due to fragmentation. When each team writes their own logging wrappers, recovery handlers, and HTTP clients, debugging across service boundaries becomes a nightmare. Creating a shared developer experience library (like devex-lib) ensures uniformity while significantly boosting bootstrap speeds.

However, importing shared libraries in Go requires careful planning. If the shared library pulls in too many third-party dependencies, it can lead to dependency hell. The goal is to keep the library lean, relying primarily on Go's standard library.


1. Correlation ID Propagation

Distributed tracing begins with a unique request identifier (Correlation ID). Our library provides a HTTP middleware that inspects incoming headers, generates a UUID if missing, and injects it into the request context:

const CorrelationKey = "correlation_id"

func CorrelationMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        corrID := r.Header.Get("X-Correlation-ID")
        if corrID == "" {
            corrID = uuid.New().String()
        }
        
        ctx := context.WithValue(r.Context(), CorrelationKey, corrID)
        w.Header().Set("X-Correlation-ID", corrID)
        next.ServeHTTP(w, r.WithContext(ctx))
    })
}

2. Structured Logging with Context

By leveraging structured loggers (like zerolog or standard slog), we can automatically bind the correlation ID from context to all log events. This ensures that a single request can be traced across 10+ independent services with one search query in Datadog or Kibana.

Standardizing these utilities saves developers days of repetitive setup while providing SRE teams with consistent logs and metrics.