detached 6a3d757

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 @@
22 package middleware
33
44 import (
5+ "crypto/rand"
6+ "encoding/hex"
57 "encoding/json"
68 "net"
79 "net/http"
@@ -75,6 +77,13 @@ func RequestLogger(log zerowrap.Logger, trustedNets ...[]*net.IPNet) func(http.H
7577 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
7678 start := time.Now()
7779
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+
7887 // Wrap the response writer to capture status and bytes
7988 rw := NewResponseWriter(w)
8089
@@ -96,6 +105,7 @@ func RequestLogger(log zerowrap.Logger, trustedNets ...[]*net.IPNet) func(http.H
96105 log.Info().
97106 Str(zerowrap.FieldLayer, "adapter").
98107 Str(zerowrap.FieldAdapter, "http").
108+ Str("request_id", requestID).
99109 Str(zerowrap.FieldMethod, r.Method).
100110 Str(zerowrap.FieldPath, r.URL.Path).
101111 Str("query", r.URL.RawQuery).
@@ -112,6 +122,13 @@ func RequestLogger(log zerowrap.Logger, trustedNets ...[]*net.IPNet) func(http.H
112122 }
113123 }
114124
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+
115132 // PanicRecovery middleware recovers from panics and logs them.
116133 func PanicRecovery(log zerowrap.Logger) func(http.Handler) http.Handler {
117134 return func(next http.Handler) http.Handler {
@@ -2,6 +2,8 @@
2 package middleware2 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 bytes87 // 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 {

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 7

Tags 106

↑↓ moveenter openesc close