/
accesslogger.go
104 lines (89 loc) · 2.78 KB
/
accesslogger.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
package requestlog
import (
"bytes"
"encoding/json"
"net/http"
"time"
"github.com/rs/zerolog"
"github.com/rs/zerolog/hlog"
)
const MaxRequestSizeLog = 4 * 1024
const MaxStringRequestSizeLog = MaxRequestSizeLog / 2
func AccessLogger(logOptions bool) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log := hlog.FromRequest(r)
crw := &CountingResponseWriter{
ResponseWriter: w,
ResponseLength: -1,
StatusCode: -1,
}
start := time.Now()
next.ServeHTTP(crw, r)
requestDuration := time.Since(start)
if r.Method == http.MethodOptions && !logOptions {
return
}
var requestLog *zerolog.Event
if crw.StatusCode >= 500 {
requestLog = log.Error()
} else if crw.StatusCode >= 400 {
requestLog = log.Warn()
} else {
requestLog = log.Info()
}
if userAgent := r.UserAgent(); userAgent != "" {
requestLog.Str("user_agent", userAgent)
}
if referer := r.Referer(); referer != "" {
requestLog.Str("referer", referer)
}
remoteAddr := r.RemoteAddr
requestLog.Str("remote_addr", remoteAddr)
requestLog.Str("method", r.Method)
requestLog.Str("proto", r.Proto)
requestLog.Int64("request_length", r.ContentLength)
requestLog.Str("host", r.Host)
requestLog.Str("request_uri", r.RequestURI)
if r.Method != http.MethodGet && r.Method != http.MethodHead {
requestLog.Str("request_content_type", r.Header.Get("Content-Type"))
if crw.RequestBody != nil {
logRequestMaybeJSON(requestLog, "request_body", crw.RequestBody.Bytes())
}
}
// response
requestLog.Int64("request_time_ms", requestDuration.Milliseconds())
requestLog.Int("status_code", crw.StatusCode)
requestLog.Int("response_length", crw.ResponseLength)
requestLog.Str("response_content_type", crw.Header().Get("Content-Type"))
if crw.ResponseBody != nil {
logRequestMaybeJSON(requestLog, "response_body", crw.ResponseBody.Bytes())
}
// don't log successful health requests
if r.URL.Path == "/health" && crw.StatusCode == http.StatusNoContent {
return
}
requestLog.Msg("Access")
})
}
}
func logRequestMaybeJSON(evt *zerolog.Event, key string, data []byte) {
data = removeNewlines(data)
if json.Valid(data) {
evt.RawJSON(key, data)
} else {
// Logging as a string will create lots of escaping and it's not valid json anyway, so cut off a bit more
if len(data) > MaxStringRequestSizeLog {
data = data[:MaxStringRequestSizeLog]
}
evt.Bytes(key+"_invalid", data)
}
}
func removeNewlines(data []byte) []byte {
data = bytes.TrimSpace(data)
if bytes.ContainsRune(data, '\n') {
data = bytes.ReplaceAll(data, []byte{'\n'}, []byte{})
data = bytes.ReplaceAll(data, []byte{'\r'}, []byte{})
}
return data
}