package observability import ( "context" "net/http" "github.com/google/uuid" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/propagation" semconv "go.opentelemetry.io/otel/semconv/v1.21.0" "go.opentelemetry.io/otel/trace" ) type contextKey string const RequestIDKey contextKey = "request_id" // responseWriter is a wrapper around http.ResponseWriter to capture the status code. type responseWriter struct { http.ResponseWriter statusCode int } func (rw *responseWriter) WriteHeader(code int) { rw.statusCode = code rw.ResponseWriter.WriteHeader(code) } // RequestIDMiddleware generates a unique request ID and adds it to the request context. func RequestIDMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { requestID := uuid.New().String() ctx := context.WithValue(r.Context(), RequestIDKey, requestID) w.Header().Set("X-Request-ID", requestID) next.ServeHTTP(w, r.WithContext(ctx)) }) } // TracingMiddleware creates a new OpenTelemetry span for each request. func TracingMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ctx := otel.GetTextMapPropagator().Extract(r.Context(), propagation.HeaderCarrier(r.Header)) tracer := otel.Tracer("http-server") ctx, span := tracer.Start(ctx, "HTTP "+r.Method+" "+r.URL.Path, trace.WithAttributes( semconv.HTTPMethodKey.String(r.Method), semconv.HTTPURLKey.String(r.URL.String()), )) defer span.End() rw := &responseWriter{ResponseWriter: w, statusCode: http.StatusOK} next.ServeHTTP(rw, r.WithContext(ctx)) span.SetAttributes(attribute.Int("http.status_code", rw.statusCode)) }) }