/
http.go
108 lines (91 loc) · 2.73 KB
/
http.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
package logging
import (
"fmt"
"io"
"net/http"
"time"
"github.com/go-chi/chi/v5/middleware"
"go.uber.org/zap"
)
// Request is an HTTP middleware to log requests and responses
func Request(logger *zap.Logger) func(next http.Handler) http.Handler {
var f middleware.LogFormatter = &requestLogger{logger}
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodGet && r.URL.Path == "/health" {
next.ServeHTTP(w, r)
return
}
entry := f.NewLogEntry(r)
ww := middleware.NewWrapResponseWriter(w, r.ProtoMajor)
buf := newLimitBuffer(512)
ww.Tee(buf)
start := time.Now()
defer func() {
var respBody []byte
if ww.Status() >= 400 {
respBody, _ = io.ReadAll(buf)
}
entry.Write(ww.Status(), ww.BytesWritten(), ww.Header(), time.Since(start), respBody)
}()
next.ServeHTTP(ww, middleware.WithLogEntry(r, entry))
})
}
}
type requestLogger struct {
Logger *zap.Logger
}
func (l *requestLogger) NewLogEntry(r *http.Request) middleware.LogEntry {
entry := &RequestLoggerEntry{}
entry.Logger = l.Logger.With(
zap.String("path", r.URL.Path),
zap.String("method", r.Method),
zap.String("remote", r.RemoteAddr),
zap.String("id", middleware.GetReqID(r.Context())),
zap.String("version", r.Proto),
)
entry.Logger.Info("started processing request")
return entry
}
type RequestLoggerEntry struct {
Logger *zap.Logger
}
func (l *RequestLoggerEntry) Write(status, bytes int, headers http.Header, elapsed time.Duration, _ interface{}) {
latency := float64(elapsed.Nanoseconds()) / 1000000.0
logger := l.Logger.With(zap.Int("status", status), zap.Int("bytes", bytes), zap.Float64("latency", latency))
// Check if hijacked for websocket upgrade
if status == 0 && bytes == 0 && len(headers) == 0 {
logger.Info("upgraded to websocket connection")
} else {
logFunc := logLevelForStatus(logger, status)
logFunc("finished processing request")
}
}
func (l *RequestLoggerEntry) Panic(v interface{}, stack []byte) {
stacktrace := "#"
if !developmentOption {
stacktrace = string(stack)
}
fields := []zap.Field{zap.String("stacktrace", stacktrace)}
if developmentOption {
fields = append(fields, zap.String("panic", fmt.Sprintf("%+v", v)))
}
l.Logger.Error("request handler panicked", fields...)
if developmentOption {
middleware.PrintPrettyStack(v)
}
}
func logLevelForStatus(logger *zap.Logger, status int) func(msg string, fields ...zap.Field) {
switch {
case status <= 0:
return logger.Warn
case status < 400:
return logger.Info
case status >= 400 && status < 500:
return logger.Warn
case status >= 500:
return logger.Error
default:
return logger.Info
}
}