tercul-backend/internal/observability/middleware.go
Damir Mukimov d50722dad5
Some checks failed
Test / Integration Tests (push) Successful in 4s
Build / Build Binary (push) Failing after 2m9s
Docker Build / Build Docker Image (push) Failing after 2m32s
Test / Unit Tests (push) Failing after 3m12s
Lint / Go Lint (push) Failing after 1m0s
Refactor ID handling to use UUIDs across the application
- Updated database models and repositories to replace uint IDs with UUIDs.
- Modified test fixtures to generate and use UUIDs for authors, translations, users, and works.
- Adjusted mock implementations to align with the new UUID structure.
- Ensured all relevant functions and methods are updated to handle UUIDs correctly.
- Added necessary imports for UUID handling in various files.
2025-12-27 00:33:34 +01:00

92 lines
3.0 KiB
Go

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"
)
// ContextKey is the type for context keys to avoid collisions.
type ContextKey string
const (
// RequestIDKey is the key for the request ID in the context.
RequestIDKey ContextKey = "request_id"
// LoggerContextKey is the key for the logger in the context.
LoggerContextKey ContextKey = "logger"
)
// 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))
})
}
// LoggingMiddleware creates a request-scoped logger and injects it into the context.
func LoggingMiddleware(log *Logger) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Start with a logger that has trace and span IDs.
requestLogger := log.Ctx(r.Context())
// Add request_id to logger context.
if reqID, ok := r.Context().Value(RequestIDKey).(string); ok {
requestLogger = requestLogger.With("request_id", reqID)
}
// Add the logger to the context.
ctx := context.WithValue(r.Context(), LoggerContextKey, requestLogger)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
}
// LoggerFromContext retrieves the request-scoped logger from the context.
// If no logger is found, it returns a default logger.
func LoggerFromContext(ctx context.Context) *Logger {
if logger, ok := ctx.Value(LoggerContextKey).(*Logger); ok {
return logger
}
// Fallback to a default logger if none is found in context.
return NewLogger("tercul-fallback", "development")
}
// 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))
})
}