package httpapp import ( "context" "crypto/rand" "encoding/hex" "log" "net/http" "time" ) type ctxKey string const requestIDKey ctxKey = "reqid" func requestID(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { id := r.Header.Get("X-Request-Id") if id == "" { id = newReqID() } w.Header().Set("X-Request-Id", id) ctx := context.WithValue(r.Context(), requestIDKey, id) next.ServeHTTP(w, r.WithContext(ctx)) }) } func logging(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { start := time.Now() // Create response writer wrapper to capture status code rw := &responseWriterWrapper{ ResponseWriter: w, statusCode: http.StatusOK, } next.ServeHTTP(rw, r) d := time.Since(start) id, _ := r.Context().Value(requestIDKey).(string) // Use structured logging if available, otherwise fallback to standard log log.Printf("%s %s %s status=%d rid=%s dur=%s", r.RemoteAddr, r.Method, r.URL.Path, rw.statusCode, id, d) }) } // responseWriterWrapper wraps http.ResponseWriter to capture status code // Note: This is different from the one in audit_middleware.go to avoid conflicts type responseWriterWrapper struct { http.ResponseWriter statusCode int } func (rw *responseWriterWrapper) WriteHeader(code int) { rw.statusCode = code rw.ResponseWriter.WriteHeader(code) } func newReqID() string { var b [16]byte if _, err := rand.Read(b[:]); err != nil { // Fallback to timestamp-based ID if crypto/rand fails (extremely rare) log.Printf("rand.Read failed, using fallback: %v", err) return hex.EncodeToString([]byte(time.Now().Format(time.RFC3339Nano))) } return hex.EncodeToString(b[:]) }