-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathlogger.go
189 lines (163 loc) · 4.55 KB
/
logger.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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
package middlelogger
import (
"fmt"
"net/http"
"sync"
"time"
)
// LogData is the data returned by the logger middleware so that clients can
// perform logging in an appropriate format and location.
type LogData struct {
R *http.Request
W http.ResponseWriter
Status int
Start time.Time
TotalTime time.Duration
BytesWritten int64
}
// RequestLogger defines the interface that custom loggers need to offer.
type RequestLogger interface {
LogRequest(LogData)
}
// PanicLogger defines the interface that clients that also wish to log panics
// need to offer.
type PanicLogger interface {
LogPanic(LogData, interface{})
}
// SlowRequestLogger defines the interface that clients that also wish to log
// slow requests need to offer.
type SlowRequestLogger interface {
Cutoff(*http.Request) time.Duration
MultipleLogs(*http.Request) bool
LogSlowRequest(LogData, int)
}
// loggedRequest maintains the state about a request that should be logged. It
// implements http.ResponseWriter so that the status code written to the client
// and any bytes sent can be accounted for.
type loggedRequest struct {
w http.ResponseWriter
// mtx protects the following fields.
mtx sync.Mutex
status int
wroteHeader bool
bytesWritten int64
}
func (lr *loggedRequest) WriteHeader(code int) {
lr.mtx.Lock()
if lr.wroteHeader {
lr.mtx.Unlock()
return
}
lr.wroteHeader = true
lr.status = code
lr.mtx.Unlock()
lr.w.WriteHeader(code)
}
func (lr *loggedRequest) Header() http.Header {
return lr.w.Header()
}
func (lr *loggedRequest) Write(data []byte) (int, error) {
written, err := lr.w.Write(data)
lr.mtx.Lock()
lr.bytesWritten += int64(written)
lr.mtx.Unlock()
return written, err
}
func (lr *loggedRequest) currentData() (int, int64) {
lr.mtx.Lock()
defer lr.mtx.Unlock()
return lr.status, lr.bytesWritten
}
type logHandler struct {
logger RequestLogger
panicLogger PanicLogger
slowLogger SlowRequestLogger
next http.Handler
}
// slowLog logs slow requests. It MUST be called as a goroutine and expects
// slowLogger to be filled.
func (lh *logHandler) slowLog(cutoff time.Duration, multiple bool,
doneChan chan struct{}, w http.ResponseWriter, r *http.Request,
start time.Time, lr *loggedRequest) {
for i := 0; multiple || i == 0; i++ {
select {
case <-doneChan:
// Done, so no more logging needed.
return
case <-time.After(cutoff):
status, bytesWritten := lr.currentData()
ld := LogData{
R: r,
W: w,
Start: start,
TotalTime: time.Since(start),
Status: status,
BytesWritten: bytesWritten,
}
lh.slowLogger.LogSlowRequest(ld, i)
}
}
}
func (lh *logHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
lr := &loggedRequest{w: w}
start := time.Now()
// Log slow requests if commanded to, so that we don't miss out some
// log messages.
var doneChan chan struct{}
if lh.slowLogger != nil {
cutoff := lh.slowLogger.Cutoff(r)
multiple := lh.slowLogger.MultipleLogs(r)
if cutoff > 0 {
doneChan = make(chan struct{})
go lh.slowLog(cutoff, multiple, doneChan, w, r, start, lr)
}
}
// Log request once complete.
defer func() {
// Cancel the slow logger if needed.
if doneChan != nil {
close(doneChan)
}
status, bytesWritten := lr.currentData()
ld := LogData{
R: r,
W: w,
Start: start,
TotalTime: time.Since(start),
Status: status,
BytesWritten: bytesWritten,
}
// We _only_ attempt to recover from panics if a
// panicLogger was specified, otherwise we might forbid
// someone higher up the stack from catching and handling the
// panic.
if lh.panicLogger != nil {
fmt.Println("got panic logger")
if err := recover(); err != nil {
fmt.Println("got panic", err)
lh.panicLogger.LogPanic(ld, err)
return
}
}
lh.logger.LogRequest(ld)
}()
lh.next.ServeHTTP(lr, r)
}
// LoggerMiddleware is a middleware that provides callers with the ability to
// log relevant request and response data.
//
// The logger value will be called with the data for every request _after_ the
// request is completed.
//
// If logger also implements PanicLogger, then any panics that occur during the
// call to the next handler are recovered from and logged appropriately.
func LoggerMiddleware(next http.Handler, logger RequestLogger) http.Handler {
panicLogger, _ := logger.(PanicLogger)
slowLogger, _ := logger.(SlowRequestLogger)
return &logHandler{
logger: logger,
panicLogger: panicLogger,
slowLogger: slowLogger,
next: next,
}
}