-
Notifications
You must be signed in to change notification settings - Fork 5k
/
stats.go
115 lines (100 loc) · 3.18 KB
/
stats.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
package middlewares
import (
"bufio"
"net"
"net/http"
"sync"
"time"
)
var (
_ Stateful = &responseRecorder{}
)
// StatsRecorder is an optional middleware that records more details statistics
// about requests and how they are processed. This currently consists of recent
// requests that have caused errors (4xx and 5xx status codes), making it easy
// to pinpoint problems.
type StatsRecorder struct {
mutex sync.RWMutex
numRecentErrors int
recentErrors []*statsError
}
// NewStatsRecorder returns a new StatsRecorder
func NewStatsRecorder(numRecentErrors int) *StatsRecorder {
return &StatsRecorder{
numRecentErrors: numRecentErrors,
}
}
// Stats includes all of the stats gathered by the recorder.
type Stats struct {
RecentErrors []*statsError `json:"recent_errors"`
}
// statsError represents an error that has occurred during request processing.
type statsError struct {
StatusCode int `json:"status_code"`
Status string `json:"status"`
Method string `json:"method"`
Host string `json:"host"`
Path string `json:"path"`
Time time.Time `json:"time"`
}
// responseRecorder captures information from the response and preserves it for
// later analysis.
type responseRecorder struct {
http.ResponseWriter
statusCode int
}
// WriteHeader captures the status code for later retrieval.
func (r *responseRecorder) WriteHeader(status int) {
r.ResponseWriter.WriteHeader(status)
r.statusCode = status
}
// Hijack hijacks the connection
func (r *responseRecorder) Hijack() (net.Conn, *bufio.ReadWriter, error) {
return r.ResponseWriter.(http.Hijacker).Hijack()
}
// CloseNotify returns a channel that receives at most a
// single value (true) when the client connection has gone
// away.
func (r *responseRecorder) CloseNotify() <-chan bool {
return r.ResponseWriter.(http.CloseNotifier).CloseNotify()
}
// Flush sends any buffered data to the client.
func (r *responseRecorder) Flush() {
r.ResponseWriter.(http.Flusher).Flush()
}
// ServeHTTP silently extracts information from the request and response as it
// is processed. If the response is 4xx or 5xx, add it to the list of 10 most
// recent errors.
func (s *StatsRecorder) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
recorder := &responseRecorder{w, http.StatusOK}
next(recorder, r)
if recorder.statusCode >= http.StatusBadRequest {
s.mutex.Lock()
defer s.mutex.Unlock()
s.recentErrors = append([]*statsError{
{
StatusCode: recorder.statusCode,
Status: http.StatusText(recorder.statusCode),
Method: r.Method,
Host: r.Host,
Path: r.URL.Path,
Time: time.Now(),
},
}, s.recentErrors...)
// Limit the size of the list to numRecentErrors
if len(s.recentErrors) > s.numRecentErrors {
s.recentErrors = s.recentErrors[:s.numRecentErrors]
}
}
}
// Data returns a copy of the statistics that have been gathered.
func (s *StatsRecorder) Data() *Stats {
s.mutex.RLock()
defer s.mutex.RUnlock()
// We can't return the slice directly or a race condition might develop
recentErrors := make([]*statsError, len(s.recentErrors))
copy(recentErrors, s.recentErrors)
return &Stats{
RecentErrors: recentErrors,
}
}