您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
 
 
 
 

247 行
7.0 KiB

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