forked from spaceweasel/mango
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlogging.go
More file actions
158 lines (146 loc) · 4.69 KB
/
logging.go
File metadata and controls
158 lines (146 loc) · 4.69 KB
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
package mango
import (
"fmt"
"net/http"
"time"
)
// NewWatchedResponse returns an initialized instance of a WatchedResponse.
func NewWatchedResponse(w http.ResponseWriter) *WatchedResponse {
wr := WatchedResponse{
rw: w,
status: 200,
}
return &wr
}
// WatchedResponse implements the http.ResponseWriter interface and
// wraps the ResponseWriter provided to the ServeHTTP method. It's
// purpose is to collect data on the information written to provide
// more informative logging.
type WatchedResponse struct {
rw http.ResponseWriter
byteCount int
status int
readonly bool
responded bool
headersSent bool
}
// Header returns the header map that will be sent by
// WriteHeader.
// See http.ResponseWriter interface for more information.
func (w *WatchedResponse) Header() http.Header {
if w.headersSent || w.readonly {
// return a copy of the map
h := http.Header{}
origMap := map[string][]string(w.rw.Header())
for k, s := range origMap {
for _, v := range s {
h.Add(k, v)
}
}
return h
}
return w.rw.Header()
}
// WriteHeader sends an HTTP response header with status code
// to the underlying http.ResponseWriter. Status code is
// recorded to provide more informative logging.
// See http.ResponseWriter interface for more information.
func (w *WatchedResponse) WriteHeader(status int) {
if w.headersSent || w.readonly {
return
}
w.headersSent = true
w.responded = true
w.status = status
w.rw.WriteHeader(status)
}
// Write writes the data to the underlying http.ResponseWriter
// connection as part of an HTTP reply. The cumulative number
// of bytes is recorded to provide more informative logging.
// See http.ResponseWriter interface for more information.
func (w *WatchedResponse) Write(b []byte) (int, error) {
if w.readonly {
return 0, fmt.Errorf("write method has been called already")
}
// TODO: check the error before updating anything
i, err := w.rw.Write(b)
w.byteCount += i
w.headersSent = true
w.responded = true
return i, err
}
// extract Now() to own func to facilitate testing
var nowUTC = func() time.Time {
return time.Now().UTC()
}
// NewRequestLog returns an initialized *RequestLog populated with information
// extracted from req.
func NewRequestLog(req *http.Request) *RequestLog {
log := RequestLog{
Start: nowUTC(),
RemoteAddr: req.RemoteAddr,
AccessRequest: req.Method + " " + req.URL.RequestURI() + " " + req.Proto,
Host: req.Host,
Referer: req.Referer(),
UserAgent: req.UserAgent(),
UserID: "-",
}
return &log
}
// RequestLog is the structure used to record data about a request
// (and response). In addition to information extracted from the
// request object, this struct holds details about the duration,
// status and amount of data returned.
type RequestLog struct {
// Start is the time the request was received
Start time.Time
// Finish is the time the request was completed
Finish time.Time
// Host is the host on which the requested resource resides.
// Format is "host:port" although port is omitted for standard
// ports.
// Example: www.somedomain.com
Host string
// AccessRequest is a concatenation of request information, in the
// format: METHOD Path&Query protocol
//
// e.g. GET /somepath/more/thing.gif HTTP/1.1
AccessRequest string
// Status is the response status code
Status int
// BytesOut is the number of bytes returned by the response
BytesOut int
// Duration is the time taken to process the request.
Duration time.Duration
// UserAgent is the client's user agent string (if provided)
UserAgent string
// RemoteAddr is the network address that sent the request.
// Format is "IP:port"
RemoteAddr string
// Referer is the referring URL (if provided).
// Referer is misspelled deliberately to match HTTP specification.
Referer string
// UserID returns the UserID of the authenticated user making the
// request. Returns "-" if the request user has not been authenticated.
UserID string
}
// CommonFormat returns request data as a string in W3C Common Log Format.
// (https://en.wikipedia.org/wiki/Common_Log_Format)
func (r *RequestLog) CommonFormat() string {
timeStamp := r.Start.Format("02/Jan/2006:15:04:05 -0700")
s := fmt.Sprintf("%s - %s [%s] \"%s\" %d %d",
r.RemoteAddr, r.UserID, timeStamp, r.AccessRequest, r.Status, r.BytesOut)
return s
}
// CombinedFormat returns request data as a string in Combined Log Format.
// Combined Log Format is similar to Common Log Format, with the addition
// of the Referer and UserAgent.
func (r *RequestLog) CombinedFormat() string {
s := fmt.Sprintf("%s \"%s\" \"%s\"",
r.CommonFormat(), r.Referer, r.UserAgent)
return s
}
func (r *RequestLog) stop() {
r.Finish = nowUTC()
r.Duration = r.Finish.Sub(r.Start)
}