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