Write a logging middleware that records the response status code. What are the pitfalls of wrapping http.ResponseWriter?
Question 445HardGo 1.22 to 1.25
Middleware is just func(http.Handler) http.Handler. To capture the status you wrap the ResponseWriter. The pitfall: the concrete writer implements optional interfaces (http.Flusher, http.Hijacker, io.ReaderFrom), and a naive wrapper hides them, breaking SSE streaming or WebSockets. Since Go 1.20, provide Unwrap() so http.ResponseController can reach the underlying writer.
type statusRecorder struct {
http.ResponseWriter
status int
bytes int
}
func (s *statusRecorder) WriteHeader(code int) {
s.status = code
s.ResponseWriter.WriteHeader(code)
}
func (s *statusRecorder) Write(b []byte) (int, error) {
if s.status == 0 {
s.status = http.StatusOK // implicit 200 on first Write
}
n, err := s.ResponseWriter.Write(b)
s.bytes += n
return n, err
}
func (s *statusRecorder) Unwrap() http.ResponseWriter { return s.ResponseWriter }
func Logging(log *slog.Logger) 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()
rec := &statusRecorder{ResponseWriter: w}
next.ServeHTTP(rec, r)
log.Info("request", "method", r.Method, "path", r.URL.Path,
"status", rec.status, "bytes", rec.bytes, "dur", time.Since(start))
})
}
}
// Chain: handler := Logging(log)(Recover(Auth(mux)))
Order matters: the outermost middleware runs first. Put panic recovery near the outside, and remember a handler that writes nothing yields status 0 in the recorder (treat it as 200).
More on Standard Library, HTTP & Systems Design in Go
- Q443How does net/http's server handle concurrency internally? What are the consequences for handler code?
- Q444Explain the Go 1.22 ServeMux enhancements: method matching, wildcards, and precedence rules.
- Q446Why is http.ListenAndServe(":8080", nil) not production-ready? Explain each server timeout.
- Q447How does http.Client connection pooling work, and what are common mistakes with http.Client?
- Q448Why must you close resp.Body, and why is closing alone sometimes not enough?
- Q449How is context used in net/http on both server and client sides? What happens when a client disconnects mid-request?