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.
- Avoid package-level globals; pass logger instances or use contextual logs.
- Ensure panic recoveries write stack traces to error channels before exiting.
Standardizing these utilities saves developers days of repetitive setup while providing SRE teams with consistent logs and metrics.