-
Notifications
You must be signed in to change notification settings - Fork 91
/
logging.go
146 lines (125 loc) · 4.63 KB
/
logging.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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
package middleware
import (
"bytes"
"context"
"errors"
"net/http"
"time"
"github.com/weaveworks/common/logging"
"github.com/weaveworks/common/tracing"
"github.com/weaveworks/common/user"
)
// Log middleware logs http requests
type Log struct {
Log logging.Interface
DisableRequestSuccessLog bool
LogRequestHeaders bool // LogRequestHeaders true -> dump http headers at debug log level
LogRequestAtInfoLevel bool // LogRequestAtInfoLevel true -> log requests at info log level
SourceIPs *SourceIPExtractor
HttpHeadersToExclude map[string]bool
}
var defaultExcludedHeaders = map[string]bool{
"Cookie": true,
"X-Csrf-Token": true,
"Authorization": true,
}
func NewLogMiddleware(log logging.Interface, logRequestHeaders bool, logRequestAtInfoLevel bool, sourceIPs *SourceIPExtractor, headersList []string) Log {
httpHeadersToExclude := map[string]bool{}
for header := range defaultExcludedHeaders {
httpHeadersToExclude[header] = true
}
for _, header := range headersList {
httpHeadersToExclude[header] = true
}
return Log{
Log: log,
LogRequestHeaders: logRequestHeaders,
LogRequestAtInfoLevel: logRequestAtInfoLevel,
SourceIPs: sourceIPs,
HttpHeadersToExclude: httpHeadersToExclude,
}
}
// logWithRequest information from the request and context as fields.
func (l Log) logWithRequest(r *http.Request) logging.Interface {
localLog := l.Log
traceID, ok := tracing.ExtractTraceID(r.Context())
if ok {
localLog = localLog.WithField("traceID", traceID)
}
if l.SourceIPs != nil {
ips := l.SourceIPs.Get(r)
if ips != "" {
localLog = localLog.WithField("sourceIPs", ips)
}
}
return user.LogWith(r.Context(), localLog)
}
// Wrap implements Middleware
func (l Log) Wrap(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
begin := time.Now()
uri := r.RequestURI // capture the URI before running next, as it may get rewritten
requestLog := l.logWithRequest(r)
// Log headers before running 'next' in case other interceptors change the data.
headers, err := dumpRequest(r, l.HttpHeadersToExclude)
if err != nil {
headers = nil
requestLog.Errorf("Could not dump request headers: %v", err)
}
var buf bytes.Buffer
wrapped := newBadResponseLoggingWriter(w, &buf)
next.ServeHTTP(wrapped, r)
statusCode, writeErr := wrapped.getStatusCode(), wrapped.getWriteError()
if writeErr != nil {
if errors.Is(writeErr, context.Canceled) {
if l.LogRequestAtInfoLevel {
requestLog.Infof("%s %s %s, request cancelled: %s ws: %v; %s", r.Method, uri, time.Since(begin), writeErr, IsWSHandshakeRequest(r), headers)
} else {
requestLog.Debugf("%s %s %s, request cancelled: %s ws: %v; %s", r.Method, uri, time.Since(begin), writeErr, IsWSHandshakeRequest(r), headers)
}
} else {
requestLog.Warnf("%s %s %s, error: %s ws: %v; %s", r.Method, uri, time.Since(begin), writeErr, IsWSHandshakeRequest(r), headers)
}
return
}
switch {
// success and shouldn't log successful requests.
case statusCode >= 200 && statusCode < 300 && l.DisableRequestSuccessLog:
return
case 100 <= statusCode && statusCode < 500 || statusCode == http.StatusBadGateway || statusCode == http.StatusServiceUnavailable:
if l.LogRequestAtInfoLevel {
requestLog.Infof("%s %s (%d) %s", r.Method, uri, statusCode, time.Since(begin))
if l.LogRequestHeaders && headers != nil {
requestLog.Infof("ws: %v; %s", IsWSHandshakeRequest(r), string(headers))
}
return
}
requestLog.Debugf("%s %s (%d) %s", r.Method, uri, statusCode, time.Since(begin))
if l.LogRequestHeaders && headers != nil {
requestLog.Debugf("ws: %v; %s", IsWSHandshakeRequest(r), string(headers))
}
default:
requestLog.Warnf("%s %s (%d) %s Response: %q ws: %v; %s",
r.Method, uri, statusCode, time.Since(begin), buf.Bytes(), IsWSHandshakeRequest(r), headers)
}
})
}
// Logging middleware logs each HTTP request method, path, response code and
// duration for all HTTP requests.
var Logging = Log{
Log: logging.Global(),
}
func dumpRequest(req *http.Request, httpHeadersToExclude map[string]bool) ([]byte, error) {
var b bytes.Buffer
// In case users initialize the Log middleware using the exported struct, skip the default headers anyway
if len(httpHeadersToExclude) == 0 {
httpHeadersToExclude = defaultExcludedHeaders
}
// Exclude some headers for security, or just that we don't need them when debugging
err := req.Header.WriteSubset(&b, httpHeadersToExclude)
if err != nil {
return nil, err
}
ret := bytes.Replace(b.Bytes(), []byte("\r\n"), []byte("; "), -1)
return ret, nil
}