1
2
3
4
5 package handlers
6
7 import (
8 "io"
9 "net"
10 "net/http"
11 "net/url"
12 "strconv"
13 "time"
14 "unicode/utf8"
15
16 "github.com/felixge/httpsnoop"
17 )
18
19
20
21
22 type LogFormatterParams struct {
23 Request *http.Request
24 URL url.URL
25 TimeStamp time.Time
26 StatusCode int
27 Size int
28 }
29
30
31 type LogFormatter func(writer io.Writer, params LogFormatterParams)
32
33
34
35
36 type loggingHandler struct {
37 writer io.Writer
38 handler http.Handler
39 formatter LogFormatter
40 }
41
42 func (h loggingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
43 t := time.Now()
44 logger, w := makeLogger(w)
45 url := *req.URL
46
47 h.handler.ServeHTTP(w, req)
48 if req.MultipartForm != nil {
49 req.MultipartForm.RemoveAll()
50 }
51
52 params := LogFormatterParams{
53 Request: req,
54 URL: url,
55 TimeStamp: t,
56 StatusCode: logger.Status(),
57 Size: logger.Size(),
58 }
59
60 h.formatter(h.writer, params)
61 }
62
63 func makeLogger(w http.ResponseWriter) (*responseLogger, http.ResponseWriter) {
64 logger := &responseLogger{w: w, status: http.StatusOK}
65 return logger, httpsnoop.Wrap(w, httpsnoop.Hooks{
66 Write: func(httpsnoop.WriteFunc) httpsnoop.WriteFunc {
67 return logger.Write
68 },
69 WriteHeader: func(httpsnoop.WriteHeaderFunc) httpsnoop.WriteHeaderFunc {
70 return logger.WriteHeader
71 },
72 })
73 }
74
75 const lowerhex = "0123456789abcdef"
76
77 func appendQuoted(buf []byte, s string) []byte {
78 var runeTmp [utf8.UTFMax]byte
79 for width := 0; len(s) > 0; s = s[width:] {
80 r := rune(s[0])
81 width = 1
82 if r >= utf8.RuneSelf {
83 r, width = utf8.DecodeRuneInString(s)
84 }
85 if width == 1 && r == utf8.RuneError {
86 buf = append(buf, `\x`...)
87 buf = append(buf, lowerhex[s[0]>>4])
88 buf = append(buf, lowerhex[s[0]&0xF])
89 continue
90 }
91 if r == rune('"') || r == '\\' {
92 buf = append(buf, '\\')
93 buf = append(buf, byte(r))
94 continue
95 }
96 if strconv.IsPrint(r) {
97 n := utf8.EncodeRune(runeTmp[:], r)
98 buf = append(buf, runeTmp[:n]...)
99 continue
100 }
101 switch r {
102 case '\a':
103 buf = append(buf, `\a`...)
104 case '\b':
105 buf = append(buf, `\b`...)
106 case '\f':
107 buf = append(buf, `\f`...)
108 case '\n':
109 buf = append(buf, `\n`...)
110 case '\r':
111 buf = append(buf, `\r`...)
112 case '\t':
113 buf = append(buf, `\t`...)
114 case '\v':
115 buf = append(buf, `\v`...)
116 default:
117 switch {
118 case r < ' ':
119 buf = append(buf, `\x`...)
120 buf = append(buf, lowerhex[s[0]>>4])
121 buf = append(buf, lowerhex[s[0]&0xF])
122 case r > utf8.MaxRune:
123 r = 0xFFFD
124 fallthrough
125 case r < 0x10000:
126 buf = append(buf, `\u`...)
127 for s := 12; s >= 0; s -= 4 {
128 buf = append(buf, lowerhex[r>>uint(s)&0xF])
129 }
130 default:
131 buf = append(buf, `\U`...)
132 for s := 28; s >= 0; s -= 4 {
133 buf = append(buf, lowerhex[r>>uint(s)&0xF])
134 }
135 }
136 }
137 }
138 return buf
139 }
140
141
142
143
144 func buildCommonLogLine(req *http.Request, url url.URL, ts time.Time, status int, size int) []byte {
145 username := "-"
146 if url.User != nil {
147 if name := url.User.Username(); name != "" {
148 username = name
149 }
150 }
151
152 host, _, err := net.SplitHostPort(req.RemoteAddr)
153 if err != nil {
154 host = req.RemoteAddr
155 }
156
157 uri := req.RequestURI
158
159
160
161
162 if req.ProtoMajor == 2 && req.Method == "CONNECT" {
163 uri = req.Host
164 }
165 if uri == "" {
166 uri = url.RequestURI()
167 }
168
169 buf := make([]byte, 0, 3*(len(host)+len(username)+len(req.Method)+len(uri)+len(req.Proto)+50)/2)
170 buf = append(buf, host...)
171 buf = append(buf, " - "...)
172 buf = append(buf, username...)
173 buf = append(buf, " ["...)
174 buf = append(buf, ts.Format("02/Jan/2006:15:04:05 -0700")...)
175 buf = append(buf, `] "`...)
176 buf = append(buf, req.Method...)
177 buf = append(buf, " "...)
178 buf = appendQuoted(buf, uri)
179 buf = append(buf, " "...)
180 buf = append(buf, req.Proto...)
181 buf = append(buf, `" `...)
182 buf = append(buf, strconv.Itoa(status)...)
183 buf = append(buf, " "...)
184 buf = append(buf, strconv.Itoa(size)...)
185 return buf
186 }
187
188
189
190
191 func writeLog(writer io.Writer, params LogFormatterParams) {
192 buf := buildCommonLogLine(params.Request, params.URL, params.TimeStamp, params.StatusCode, params.Size)
193 buf = append(buf, '\n')
194 writer.Write(buf)
195 }
196
197
198
199
200 func writeCombinedLog(writer io.Writer, params LogFormatterParams) {
201 buf := buildCommonLogLine(params.Request, params.URL, params.TimeStamp, params.StatusCode, params.Size)
202 buf = append(buf, ` "`...)
203 buf = appendQuoted(buf, params.Request.Referer())
204 buf = append(buf, `" "`...)
205 buf = appendQuoted(buf, params.Request.UserAgent())
206 buf = append(buf, '"', '\n')
207 writer.Write(buf)
208 }
209
210
211
212
213
214
215
216 func CombinedLoggingHandler(out io.Writer, h http.Handler) http.Handler {
217 return loggingHandler{out, h, writeCombinedLog}
218 }
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236 func LoggingHandler(out io.Writer, h http.Handler) http.Handler {
237 return loggingHandler{out, h, writeLog}
238 }
239
240
241
242 func CustomLoggingHandler(out io.Writer, h http.Handler, f LogFormatter) http.Handler {
243 return loggingHandler{out, h, f}
244 }
245
View as plain text