github.com/hellobchain/third_party@v0.0.0-20230331131523-deb0478a2e52/gorilla/handlers/logging.go (about) 1 // Copyright 2013 The Gorilla Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package handlers 6 7 import ( 8 "github.com/hellobchain/newcryptosm/http" 9 "io" 10 "net" 11 "net/url" 12 "strconv" 13 "time" 14 "unicode/utf8" 15 ) 16 17 // Logging 18 19 // FormatterParams is the structure any formatter will be handed when time to log comes 20 type LogFormatterParams struct { 21 Request *http.Request 22 URL url.URL 23 TimeStamp time.Time 24 StatusCode int 25 Size int 26 } 27 28 // LogFormatter gives the signature of the formatter function passed to CustomLoggingHandler 29 type LogFormatter func(writer io.Writer, params LogFormatterParams) 30 31 // loggingHandler is the http.Handler implementation for LoggingHandlerTo and its 32 // friends 33 34 type loggingHandler struct { 35 writer io.Writer 36 handler http.Handler 37 formatter LogFormatter 38 } 39 40 func (h loggingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { 41 t := time.Now() 42 logger := makeLogger(w) 43 url := *req.URL 44 45 h.handler.ServeHTTP(logger, req) 46 47 params := LogFormatterParams{ 48 Request: req, 49 URL: url, 50 TimeStamp: t, 51 StatusCode: logger.Status(), 52 Size: logger.Size(), 53 } 54 55 h.formatter(h.writer, params) 56 } 57 58 func makeLogger(w http.ResponseWriter) loggingResponseWriter { 59 var logger loggingResponseWriter = &responseLogger{w: w, status: http.StatusOK} 60 if _, ok := w.(http.Hijacker); ok { 61 logger = &hijackLogger{responseLogger{w: w, status: http.StatusOK}} 62 } 63 h, ok1 := logger.(http.Hijacker) 64 c, ok2 := w.(http.CloseNotifier) 65 if ok1 && ok2 { 66 return hijackCloseNotifier{logger, h, c} 67 } 68 if ok2 { 69 return &closeNotifyWriter{logger, c} 70 } 71 return logger 72 } 73 74 type commonLoggingResponseWriter interface { 75 http.ResponseWriter 76 http.Flusher 77 Status() int 78 Size() int 79 } 80 81 const lowerhex = "0123456789abcdef" 82 83 func appendQuoted(buf []byte, s string) []byte { 84 var runeTmp [utf8.UTFMax]byte 85 for width := 0; len(s) > 0; s = s[width:] { 86 r := rune(s[0]) 87 width = 1 88 if r >= utf8.RuneSelf { 89 r, width = utf8.DecodeRuneInString(s) 90 } 91 if width == 1 && r == utf8.RuneError { 92 buf = append(buf, `\x`...) 93 buf = append(buf, lowerhex[s[0]>>4]) 94 buf = append(buf, lowerhex[s[0]&0xF]) 95 continue 96 } 97 if r == rune('"') || r == '\\' { // always backslashed 98 buf = append(buf, '\\') 99 buf = append(buf, byte(r)) 100 continue 101 } 102 if strconv.IsPrint(r) { 103 n := utf8.EncodeRune(runeTmp[:], r) 104 buf = append(buf, runeTmp[:n]...) 105 continue 106 } 107 switch r { 108 case '\a': 109 buf = append(buf, `\a`...) 110 case '\b': 111 buf = append(buf, `\b`...) 112 case '\f': 113 buf = append(buf, `\f`...) 114 case '\n': 115 buf = append(buf, `\n`...) 116 case '\r': 117 buf = append(buf, `\r`...) 118 case '\t': 119 buf = append(buf, `\t`...) 120 case '\v': 121 buf = append(buf, `\v`...) 122 default: 123 switch { 124 case r < ' ': 125 buf = append(buf, `\x`...) 126 buf = append(buf, lowerhex[s[0]>>4]) 127 buf = append(buf, lowerhex[s[0]&0xF]) 128 case r > utf8.MaxRune: 129 r = 0xFFFD 130 fallthrough 131 case r < 0x10000: 132 buf = append(buf, `\u`...) 133 for s := 12; s >= 0; s -= 4 { 134 buf = append(buf, lowerhex[r>>uint(s)&0xF]) 135 } 136 default: 137 buf = append(buf, `\U`...) 138 for s := 28; s >= 0; s -= 4 { 139 buf = append(buf, lowerhex[r>>uint(s)&0xF]) 140 } 141 } 142 } 143 } 144 return buf 145 146 } 147 148 // buildCommonLogLine builds a log entry for req in Apache Common Log Format. 149 // ts is the timestamp with which the entry should be logged. 150 // status and size are used to provide the response HTTP status and size. 151 func buildCommonLogLine(req *http.Request, url url.URL, ts time.Time, status int, size int) []byte { 152 username := "-" 153 if url.User != nil { 154 if name := url.User.Username(); name != "" { 155 username = name 156 } 157 } 158 159 host, _, err := net.SplitHostPort(req.RemoteAddr) 160 161 if err != nil { 162 host = req.RemoteAddr 163 } 164 165 uri := req.RequestURI 166 167 // Requests using the CONNECT method over HTTP/2.0 must use 168 // the authority field (aka r.Host) to identify the target. 169 // Refer: https://httpwg.github.io/specs/rfc7540.html#CONNECT 170 if req.ProtoMajor == 2 && req.Method == "CONNECT" { 171 uri = req.Host 172 } 173 if uri == "" { 174 uri = url.RequestURI() 175 } 176 177 buf := make([]byte, 0, 3*(len(host)+len(username)+len(req.Method)+len(uri)+len(req.Proto)+50)/2) 178 buf = append(buf, host...) 179 buf = append(buf, " - "...) 180 buf = append(buf, username...) 181 buf = append(buf, " ["...) 182 buf = append(buf, ts.Format("02/Jan/2006:15:04:05 -0700")...) 183 buf = append(buf, `] "`...) 184 buf = append(buf, req.Method...) 185 buf = append(buf, " "...) 186 buf = appendQuoted(buf, uri) 187 buf = append(buf, " "...) 188 buf = append(buf, req.Proto...) 189 buf = append(buf, `" `...) 190 buf = append(buf, strconv.Itoa(status)...) 191 buf = append(buf, " "...) 192 buf = append(buf, strconv.Itoa(size)...) 193 return buf 194 } 195 196 // writeLog writes a log entry for req to w in Apache Common Log Format. 197 // ts is the timestamp with which the entry should be logged. 198 // status and size are used to provide the response HTTP status and size. 199 func writeLog(writer io.Writer, params LogFormatterParams) { 200 buf := buildCommonLogLine(params.Request, params.URL, params.TimeStamp, params.StatusCode, params.Size) 201 buf = append(buf, '\n') 202 writer.Write(buf) 203 } 204 205 // writeCombinedLog writes a log entry for req to w in Apache Combined Log Format. 206 // ts is the timestamp with which the entry should be logged. 207 // status and size are used to provide the response HTTP status and size. 208 func writeCombinedLog(writer io.Writer, params LogFormatterParams) { 209 buf := buildCommonLogLine(params.Request, params.URL, params.TimeStamp, params.StatusCode, params.Size) 210 buf = append(buf, ` "`...) 211 buf = appendQuoted(buf, params.Request.Referer()) 212 buf = append(buf, `" "`...) 213 buf = appendQuoted(buf, params.Request.UserAgent()) 214 buf = append(buf, '"', '\n') 215 writer.Write(buf) 216 } 217 218 // CombinedLoggingHandler return a http.Handler that wraps h and logs requests to out in 219 // Apache Combined Log Format. 220 // 221 // See http://httpd.apache.org/docs/2.2/logs.html#combined for a description of this format. 222 // 223 // LoggingHandler always sets the ident field of the log to - 224 func CombinedLoggingHandler(out io.Writer, h http.Handler) http.Handler { 225 return loggingHandler{out, h, writeCombinedLog} 226 } 227 228 // LoggingHandler return a http.Handler that wraps h and logs requests to out in 229 // Apache Common Log Format (CLF). 230 // 231 // See http://httpd.apache.org/docs/2.2/logs.html#common for a description of this format. 232 // 233 // LoggingHandler always sets the ident field of the log to - 234 // 235 // Example: 236 // 237 // r := mux.NewRouter() 238 // r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 239 // w.Write([]byte("This is a catch-all route")) 240 // }) 241 // loggedRouter := handlers.LoggingHandler(os.Stdout, r) 242 // http.ListenAndServe(":1123", loggedRouter) 243 // 244 func LoggingHandler(out io.Writer, h http.Handler) http.Handler { 245 return loggingHandler{out, h, writeLog} 246 } 247 248 // CustomLoggingHandler provides a way to supply a custom log formatter 249 // while taking advantage of the mechanisms in this package 250 func CustomLoggingHandler(out io.Writer, h http.Handler, f LogFormatter) http.Handler { 251 return loggingHandler{out, h, f} 252 }