70 lines
1.7 KiB
Go
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[:])
|
|
}
|