Files
atlas/internal/httpapp/middleware.go
othman.suseno df475bc85e
Some checks failed
CI / test-build (push) Failing after 2m11s
logging and diagnostic features added
2025-12-15 00:45:14 +07:00

70 lines
1.7 KiB
Go

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[:])
}