feat(middleware) add X-Request-ID header for request tracing
- commit
- 6a3d757d3370f4c36575e667465ac75c20f4d390
- author
- bnema · · 2026-02-06 21:28 UTC
- parent
- fdda3c0
- signature
- unsigned
1 file · +17 -0
M internal/adapters/in/http/middleware/logging.go +17 -0
| @@ -2,6 +2,8 @@ | |||
| 2 | 2 | package middleware | |
| 3 | 3 | ||
| 4 | 4 | import ( | |
| 5 | + | "crypto/rand" | |
| 6 | + | "encoding/hex" | |
| 5 | 7 | "encoding/json" | |
| 6 | 8 | "net" | |
| 7 | 9 | "net/http" | |
| @@ -75,6 +77,13 @@ func RequestLogger(log zerowrap.Logger, trustedNets ...[]*net.IPNet) func(http.H | |||
| 75 | 77 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |
| 76 | 78 | start := time.Now() | |
| 77 | 79 | ||
| 80 | + | // Generate or reuse X-Request-ID for request tracing | |
| 81 | + | requestID := r.Header.Get("X-Request-ID") | |
| 82 | + | if requestID == "" { | |
| 83 | + | requestID = generateRequestID() | |
| 84 | + | } | |
| 85 | + | w.Header().Set("X-Request-ID", requestID) | |
| 86 | + | ||
| 78 | 87 | // Wrap the response writer to capture status and bytes | |
| 79 | 88 | rw := NewResponseWriter(w) | |
| 80 | 89 | ||
| @@ -96,6 +105,7 @@ func RequestLogger(log zerowrap.Logger, trustedNets ...[]*net.IPNet) func(http.H | |||
| 96 | 105 | log.Info(). | |
| 97 | 106 | Str(zerowrap.FieldLayer, "adapter"). | |
| 98 | 107 | Str(zerowrap.FieldAdapter, "http"). | |
| 108 | + | Str("request_id", requestID). | |
| 99 | 109 | Str(zerowrap.FieldMethod, r.Method). | |
| 100 | 110 | Str(zerowrap.FieldPath, r.URL.Path). | |
| 101 | 111 | Str("query", r.URL.RawQuery). | |
| @@ -112,6 +122,13 @@ func RequestLogger(log zerowrap.Logger, trustedNets ...[]*net.IPNet) func(http.H | |||
| 112 | 122 | } | |
| 113 | 123 | } | |
| 114 | 124 | ||
| 125 | + | // generateRequestID creates a random 16-byte hex-encoded request ID. | |
| 126 | + | func generateRequestID() string { | |
| 127 | + | b := make([]byte, 16) | |
| 128 | + | _, _ = rand.Read(b) | |
| 129 | + | return hex.EncodeToString(b) | |
| 130 | + | } | |
| 131 | + | ||
| 115 | 132 | // PanicRecovery middleware recovers from panics and logs them. | |
| 116 | 133 | func PanicRecovery(log zerowrap.Logger) func(http.Handler) http.Handler { | |
| 117 | 134 | return func(next http.Handler) http.Handler { | |
| @@ -2,6 +2,8 @@ | |||||
| 2 | package middleware | 2 | package middleware | ||
| 3 | 3 | ||||
| 4 | import ( | 4 | import ( | ||
| 5 | + | "crypto/rand" | |||
| 6 | + | "encoding/hex" | |||
| 5 | "encoding/json" | 7 | "encoding/json" | ||
| 6 | "net" | 8 | "net" | ||
| 7 | "net/http" | 9 | "net/http" | ||
| @@ -75,6 +77,13 @@ func RequestLogger(log zerowrap.Logger, trustedNets ...[]*net.IPNet) func(http.H | |||||
| 75 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | 77 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
| 76 | start := time.Now() | 78 | start := time.Now() | ||
| 77 | 79 | ||||
| 80 | + | // Generate or reuse X-Request-ID for request tracing | |||
| 81 | + | requestID := r.Header.Get("X-Request-ID") | |||
| 82 | + | if requestID == "" { | |||
| 83 | + | requestID = generateRequestID() | |||
| 84 | + | } | |||
| 85 | + | w.Header().Set("X-Request-ID", requestID) | |||
| 86 | + | ||||
| 78 | // Wrap the response writer to capture status and bytes | 87 | // Wrap the response writer to capture status and bytes | ||
| 79 | rw := NewResponseWriter(w) | 88 | rw := NewResponseWriter(w) | ||
| 80 | 89 | ||||
| @@ -96,6 +105,7 @@ func RequestLogger(log zerowrap.Logger, trustedNets ...[]*net.IPNet) func(http.H | |||||
| 96 | log.Info(). | 105 | log.Info(). | ||
| 97 | Str(zerowrap.FieldLayer, "adapter"). | 106 | Str(zerowrap.FieldLayer, "adapter"). | ||
| 98 | Str(zerowrap.FieldAdapter, "http"). | 107 | Str(zerowrap.FieldAdapter, "http"). | ||
| 108 | + | Str("request_id", requestID). | |||
| 99 | Str(zerowrap.FieldMethod, r.Method). | 109 | Str(zerowrap.FieldMethod, r.Method). | ||
| 100 | Str(zerowrap.FieldPath, r.URL.Path). | 110 | Str(zerowrap.FieldPath, r.URL.Path). | ||
| 101 | Str("query", r.URL.RawQuery). | 111 | Str("query", r.URL.RawQuery). | ||
| @@ -112,6 +122,13 @@ func RequestLogger(log zerowrap.Logger, trustedNets ...[]*net.IPNet) func(http.H | |||||
| 112 | } | 122 | } | ||
| 113 | } | 123 | } | ||
| 114 | 124 | ||||
| 125 | + | // generateRequestID creates a random 16-byte hex-encoded request ID. | |||
| 126 | + | func generateRequestID() string { | |||
| 127 | + | b := make([]byte, 16) | |||
| 128 | + | _, _ = rand.Read(b) | |||
| 129 | + | return hex.EncodeToString(b) | |||
| 130 | + | } | |||
| 131 | + | ||||
| 115 | // PanicRecovery middleware recovers from panics and logs them. | 132 | // PanicRecovery middleware recovers from panics and logs them. | ||
| 116 | func PanicRecovery(log zerowrap.Logger) func(http.Handler) http.Handler { | 133 | func PanicRecovery(log zerowrap.Logger) func(http.Handler) http.Handler { | ||
| 117 | return func(next http.Handler) http.Handler { | 134 | return func(next http.Handler) http.Handler { | ||