go
52 lines · 7 steps
Capturing response bodies in Gin middleware
A Gin middleware wraps the response writer to tee output into a buffer, then logs a structured audit record after the handler runs.
Explained by
highlit
1package middleware
2
3import (
4 "bytes"
5 "time"
6
7 "github.com/gin-gonic/gin"
8 "github.com/rs/zerolog/log"
9)
10
11type responseCapture struct {
12 gin.ResponseWriter
13 body *bytes.Buffer
14}
15
16func (w *responseCapture) Write(b []byte) (int, error) {
17 w.body.Write(b)
18 return w.ResponseWriter.Write(b)
19}
20
21func (w *responseCapture) WriteString(s string) (int, error) {
22 w.body.WriteString(s)
23 return w.ResponseWriter.WriteString(s)
24}
25
26func AuditLogger(maxBody int) gin.HandlerFunc {
27 return func(c *gin.Context) {
28 capture := &responseCapture{
29 ResponseWriter: c.Writer,
30 body: bytes.NewBuffer(make([]byte, 0, 1024)),
31 }
32 c.Writer = capture
33
34 start := time.Now()
35 c.Next()
36
37 payload := capture.body.Bytes()
38 if len(payload) > maxBody {
39 payload = payload[:maxBody]
40 }
41
42 log.Info().
43 Str("method", c.Request.Method).
44 Str("path", c.FullPath()).
45 Int("status", c.Writer.Status()).
46 Int("bytes", capture.body.Len()).
47 Dur("latency", time.Since(start)).
48 Str("client_ip", c.ClientIP()).
49 Bytes("response", payload).
50 Msg("request audited")
51 }
52}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Embedding an interface lets you override just the methods you care about while delegating the rest.
- 2Swapping c.Writer before calling c.Next lets middleware observe everything downstream handlers produce.
- 3Truncating captured payloads before logging bounds memory and log volume for large responses.
Related explainers
go
package streaming import ( "bufio"
Streaming NDJSON logs over HTTP in Go
http-streaming
channels
select
Advanced
10 steps
python
import time import uuid from django.utils.deprecation import MiddlewareMixin
Attaching per-request context in Django
middleware
request lifecycle
multi-tenancy
Intermediate
7 steps
go
package api import ( "crypto/sha256"
ETag conditional requests in Gin
http-caching
etag
conditional-requests
Intermediate
6 steps
go
func (w *Watcher) resetDebounce(d time.Duration) { if !w.timer.Stop() { select { case <-w.timer.C:
Debouncing a stream of events in Go
debounce
timers
channels
Advanced
7 steps
go
package logging import ( "context"
Deduplicating log attributes in Go's slog
decorator-pattern
structured-logging
immutability
Intermediate
8 steps
go
package middleware import ( "net/http"
Per-plan export limits in Gin middleware
middleware
rate-limiting
authorization
Intermediate
7 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/capturing-response-bodies-in-gin-middleware-explained-go-13c6/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.