Skip to content

Commit df4bdff

Browse files
authored
zrouter: Add logging middleware and request-id (#66)
* Add logging middleware * add request-id middleware * fix applyMiddlewares and add request-id * fix go mod
1 parent 6f6f7af commit df4bdff

File tree

7 files changed

+83
-26
lines changed

7 files changed

+83
-26
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ require (
1010
github.com/go-chi/cors v1.2.1
1111
github.com/go-redis/redis/v8 v8.11.5
1212
github.com/go-redsync/redsync/v4 v4.12.0
13+
github.com/google/uuid v1.6.0
1314
github.com/prometheus/client_golang v1.18.0
1415
github.com/spf13/cobra v1.8.0
1516
github.com/spf13/viper v1.18.2
@@ -33,7 +34,6 @@ require (
3334
github.com/fsnotify/fsnotify v1.7.0 // indirect
3435
github.com/go-faster/city v1.0.1 // indirect
3536
github.com/go-faster/errors v0.6.1 // indirect
36-
github.com/google/uuid v1.6.0 // indirect
3737
github.com/hashicorp/errwrap v1.1.0 // indirect
3838
github.com/hashicorp/go-multierror v1.1.1 // indirect
3939
github.com/hashicorp/go-version v1.6.0 // indirect

pkg/zrouter/zmiddlewares/cache.go

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@ func CacheMiddleware(cache zcache.ZCache, config domain.CacheConfig, logger *zap
2727
return
2828
}
2929

30-
mrw := &metricsResponseWriter{ResponseWriter: w}
31-
next.ServeHTTP(mrw, r) // Important: This line needs to be BEFORE setting the cache.
32-
cacheResponseIfNeeded(mrw, r, cache, key, ttl, logger)
30+
rw := &responseWriter{ResponseWriter: w}
31+
next.ServeHTTP(rw, r) // Important: This line needs to be BEFORE setting the cache.
32+
cacheResponseIfNeeded(rw, r, cache, key, ttl, logger)
3333
}
3434
})
3535
}
@@ -53,17 +53,21 @@ func tryServeFromCache(w http.ResponseWriter, r *http.Request, cache zcache.ZCac
5353
if err == nil && cachedResponse != nil {
5454
w.Header().Set(domain.ContentTypeHeader, domain.ContentTypeApplicationJSON)
5555
_, _ = w.Write(cachedResponse)
56+
requestID := r.Header.Get(RequestIDHeader)
57+
58+
zap.S().Debugf("[Cache] request_id: %s - Method: %s - URL: %s | Status: %d - Response Body: %s",
59+
requestID, r.Method, r.URL.String(), http.StatusOK, string(cachedResponse))
5660
return true
5761
}
5862
return false
5963
}
6064

61-
func cacheResponseIfNeeded(mrw *metricsResponseWriter, r *http.Request, cache zcache.ZCache, key string, ttl time.Duration, logger *zap.SugaredLogger) {
62-
if mrw.status != http.StatusOK {
65+
func cacheResponseIfNeeded(rw *responseWriter, r *http.Request, cache zcache.ZCache, key string, ttl time.Duration, logger *zap.SugaredLogger) {
66+
if rw.status != http.StatusOK {
6367
return
6468
}
6569

66-
responseBody := mrw.Body()
70+
responseBody := rw.Body()
6771
if err := cache.Set(r.Context(), key, responseBody, ttl); err != nil {
6872
logger.Errorf("Internal error when setting cache response: %v\n%s", err, debug.Stack())
6973
}

pkg/zrouter/zmiddlewares/http.go

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,32 @@
11
package zmiddlewares
22

33
import (
4-
"github.com/go-chi/chi/v5/middleware"
4+
"github.com/google/uuid"
5+
"net/http"
6+
)
7+
8+
const (
9+
RequestIDHeader = "X-Request-ID"
510
)
611

712
func RequestID() Middleware {
8-
return middleware.RequestID
13+
return requestIDMiddleware
14+
}
15+
16+
func requestIDMiddleware(next http.Handler) http.Handler {
17+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
18+
requestID := r.Header.Get(RequestIDHeader)
19+
if requestID == "" {
20+
requestID = uuid.New().String()
21+
r.Header.Set(RequestIDHeader, requestID)
22+
}
23+
24+
w.Header().Set(RequestIDHeader, requestID)
25+
rw := &responseWriter{ResponseWriter: w}
26+
next.ServeHTTP(rw, r)
27+
})
928
}
1029

1130
func Logger() Middleware {
12-
return middleware.Logger
31+
return LoggingMiddleware
1332
}

pkg/zrouter/zmiddlewares/logging.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package zmiddlewares
2+
3+
import (
4+
"bytes"
5+
"go.uber.org/zap"
6+
"net/http"
7+
"time"
8+
)
9+
10+
func LoggingMiddleware(next http.Handler) http.Handler {
11+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
12+
buffer := &bytes.Buffer{}
13+
14+
rw := &responseWriter{
15+
ResponseWriter: w,
16+
body: buffer,
17+
}
18+
19+
start := time.Now()
20+
next.ServeHTTP(rw, r)
21+
duration := time.Since(start)
22+
requestID := r.Header.Get(RequestIDHeader)
23+
24+
zap.S().Debugf("request_id: %s - Method: %s - URL: %s | Status: %d - Duration: %s - Response Body: %s",
25+
requestID, r.Method, r.URL.String(), rw.status, duration, rw.Body())
26+
})
27+
}

pkg/zrouter/zmiddlewares/metrics.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ func RequestMetrics(appName string, metricsServer metrics.TaskMetrics) Middlewar
6060
_ = metricsServer.UpdateMetric(activeConnectionsMetricName, float64(activeConnections))
6161
mu.Unlock()
6262

63-
mrw := &metricsResponseWriter{ResponseWriter: w}
63+
mrw := &responseWriter{ResponseWriter: w}
6464
next.ServeHTTP(mrw, r)
6565

6666
mu.Lock()

pkg/zrouter/zmiddlewares/middleware.go

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,31 +7,31 @@ import (
77

88
type Middleware func(next http.Handler) http.Handler
99

10-
type metricsResponseWriter struct {
10+
type responseWriter struct {
1111
http.ResponseWriter
1212
status int
1313
written int64
1414
body *bytes.Buffer
1515
}
1616

17-
func (mrw *metricsResponseWriter) WriteHeader(statusCode int) {
18-
mrw.status = statusCode
19-
mrw.ResponseWriter.WriteHeader(statusCode)
17+
func (rw *responseWriter) WriteHeader(statusCode int) {
18+
rw.status = statusCode
19+
rw.ResponseWriter.WriteHeader(statusCode)
2020
}
2121

22-
func (mrw *metricsResponseWriter) Write(p []byte) (int, error) {
23-
if mrw.body == nil {
24-
mrw.body = new(bytes.Buffer)
22+
func (rw *responseWriter) Write(p []byte) (int, error) {
23+
if rw.body == nil {
24+
rw.body = new(bytes.Buffer)
2525
}
26-
mrw.body.Write(p)
27-
n, err := mrw.ResponseWriter.Write(p)
28-
mrw.written += int64(n)
26+
rw.body.Write(p)
27+
n, err := rw.ResponseWriter.Write(p)
28+
rw.written += int64(n)
2929
return n, err
3030
}
3131

32-
func (mrw *metricsResponseWriter) Body() []byte {
33-
if mrw.body != nil {
34-
return mrw.body.Bytes()
32+
func (rw *responseWriter) Body() []byte {
33+
if rw.body != nil {
34+
return rw.body.Bytes()
3535
}
3636
return nil
3737
}

pkg/zrouter/zrouter.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ func (r *zrouter) SetDefaultMiddlewares() {
9999
}
100100

101101
r.Use(zmiddlewares.RequestMetrics(r.appName, r.metricsServer))
102+
r.Use(zmiddlewares.Logger())
102103
}
103104

104105
func (r *zrouter) Group(prefix string) Routes {
@@ -132,14 +133,20 @@ func (r *zrouter) Run(addr ...string) error {
132133

133134
func (r *zrouter) applyMiddlewares(handler http.HandlerFunc, middlewares ...zmiddlewares.Middleware) http.Handler {
134135
var wrappedHandler http.Handler = handler
136+
// The order of middleware application is crucial: Route-specific middlewares are applied first,
137+
// followed by router-level general middlewares. This ensures that general middlewares, which often
138+
// handle logging, security, etc... are executed first. This sequence is
139+
// important to maintain consistency in logging and to apply security measures before route-specific
140+
// logic is executed.
135141

136-
for _, mw := range r.middlewares {
142+
for _, mw := range middlewares {
137143
wrappedHandler = mw(wrappedHandler)
138144
}
139145

140-
for _, mw := range middlewares {
146+
for _, mw := range r.middlewares {
141147
wrappedHandler = mw(wrappedHandler)
142148
}
149+
143150
return wrappedHandler
144151
}
145152

0 commit comments

Comments
 (0)