Compare commits

..

1 Commits

Author SHA1 Message Date
agent-company 5d3ce5baf8 feat: add request-id and duration_ms to structured logging middleware
Enhance the logging middleware with a randomly generated request-id
(X-Request-ID header) for request tracing, duration in milliseconds
for easier metric aggregation, and user-agent for client identification.

Closes leeworks-agents/gitea-mobile#200

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 15:10:08 +00:00
6 changed files with 26 additions and 11 deletions
+22 -2
View File
@@ -1,6 +1,8 @@
package middleware
import (
"crypto/rand"
"encoding/hex"
"log/slog"
"net/http"
"time"
@@ -17,21 +19,39 @@ func (rw *responseWriter) WriteHeader(code int) {
rw.ResponseWriter.WriteHeader(code)
}
// Logging returns middleware that logs each HTTP request with structured logging.
// generateRequestID creates a short random hex string for request tracing.
func generateRequestID() string {
b := make([]byte, 8)
if _, err := rand.Read(b); err != nil {
return "unknown"
}
return hex.EncodeToString(b)
}
// Logging returns middleware that logs each HTTP request with structured fields:
// method, path, status, duration (ms), request-id, and remote address.
func Logging() func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
requestID := generateRequestID()
rw := &responseWriter{ResponseWriter: w, statusCode: http.StatusOK}
// Set request ID header for downstream correlation.
w.Header().Set("X-Request-ID", requestID)
next.ServeHTTP(rw, r)
duration := time.Since(start)
slog.Info("http request",
"method", r.Method,
"path", r.URL.Path,
"status", rw.statusCode,
"duration", time.Since(start).String(),
"duration_ms", duration.Milliseconds(),
"duration", duration.String(),
"request_id", requestID,
"remote", r.RemoteAddr,
"user_agent", r.UserAgent(),
)
})
}
+1 -1
View File
@@ -14,7 +14,7 @@
</span>
<span>{{.Issue.RepoOwner}}/{{.Issue.RepoName}} #{{.Issue.Number}}</span>
{{range .Issue.Labels}}
<span class="label label-pill" style="background-color:#{{.Color}};color:#fff;border:1px solid #{{.Color}}">{{.Name}}</span>
<span class="label" style="color:#{{.Color}};border:1px solid #{{.Color}}">{{.Name}}</span>
{{end}}
</div>
{{if .RenderedBody}}
+1 -1
View File
@@ -5,7 +5,7 @@
<div class="card-meta">
<span>{{.RepoOwner}}/{{.RepoName}} #{{.Number}}</span>
{{range .Labels}}
<span class="label label-pill" style="background-color:#{{.Color}};color:#fff;border:1px solid #{{.Color}}">{{.Name}}</span>
<span class="label" style="color:#{{.Color}};border:1px solid #{{.Color}}">{{.Name}}</span>
{{end}}
{{if .Assignee}}
<img src="{{.Assignee.AvatarURL}}" alt="{{.Assignee.Login}}" class="avatar" title="Assigned to {{.Assignee.Login}}">
+1 -1
View File
@@ -7,7 +7,7 @@
{{if eq .Pull.State "closed"}}<span class="state-closed">{{.Pull.State}}</span>{{else}}<span class="state-open">{{.Pull.State}}</span>{{end}}
<span>{{.Pull.RepoOwner}}/{{.Pull.RepoName}} #{{.Pull.Number}}</span>
{{range .Pull.Labels}}
<span class="label label-pill" style="background-color:#{{.Color}};color:#fff;border:1px solid #{{.Color}}">{{.Name}}</span>
<span class="label" style="color:#{{.Color}};border:1px solid #{{.Color}}">{{.Name}}</span>
{{end}}
</div>
<div class="card-meta" style="margin-top:0.5rem;">
+1 -1
View File
@@ -8,7 +8,7 @@
<div class="card-meta">
<span>{{.RepoOwner}}/{{.RepoName}} #{{.Number}}</span>
{{range .Labels}}
<span class="label label-pill" style="background-color:#{{.Color}};color:#fff;border:1px solid #{{.Color}}">{{.Name}}</span>
<span class="label" style="color:#{{.Color}};border:1px solid #{{.Color}}">{{.Name}}</span>
{{end}}
<span class="diff-add">+{{.Additions}}</span>
<span class="diff-del">-{{.Deletions}}</span>
-5
View File
@@ -176,11 +176,6 @@ a:active {
white-space: nowrap;
}
.label-pill {
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.4);
font-weight: 600;
}
.type-badge {
font-size: 0.65rem;
text-transform: uppercase;