loghandler.go 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  1. package handler
  2. import (
  3. "bufio"
  4. "bytes"
  5. "context"
  6. "errors"
  7. "fmt"
  8. "io"
  9. "net"
  10. "net/http"
  11. "net/http/httputil"
  12. "strconv"
  13. "strings"
  14. "time"
  15. "github.com/zeromicro/go-zero/core/color"
  16. "github.com/zeromicro/go-zero/core/iox"
  17. "github.com/zeromicro/go-zero/core/logx"
  18. "github.com/zeromicro/go-zero/core/syncx"
  19. "github.com/zeromicro/go-zero/core/timex"
  20. "github.com/zeromicro/go-zero/core/utils"
  21. "github.com/zeromicro/go-zero/rest/httpx"
  22. "github.com/zeromicro/go-zero/rest/internal"
  23. )
  24. const (
  25. limitBodyBytes = 1024
  26. defaultSlowThreshold = time.Millisecond * 500
  27. )
  28. var slowThreshold = syncx.ForAtomicDuration(defaultSlowThreshold)
  29. type loggedResponseWriter struct {
  30. w http.ResponseWriter
  31. r *http.Request
  32. code int
  33. }
  34. func (w *loggedResponseWriter) Flush() {
  35. if flusher, ok := w.w.(http.Flusher); ok {
  36. flusher.Flush()
  37. }
  38. }
  39. func (w *loggedResponseWriter) Header() http.Header {
  40. return w.w.Header()
  41. }
  42. // Hijack implements the http.Hijacker interface.
  43. // This expands the Response to fulfill http.Hijacker if the underlying http.ResponseWriter supports it.
  44. func (w *loggedResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
  45. if hijacked, ok := w.w.(http.Hijacker); ok {
  46. return hijacked.Hijack()
  47. }
  48. return nil, nil, errors.New("server doesn't support hijacking")
  49. }
  50. func (w *loggedResponseWriter) Write(bytes []byte) (int, error) {
  51. return w.w.Write(bytes)
  52. }
  53. func (w *loggedResponseWriter) WriteHeader(code int) {
  54. w.w.WriteHeader(code)
  55. w.code = code
  56. }
  57. // LogHandler returns a middleware that logs http request and response.
  58. func LogHandler(next http.Handler) http.Handler {
  59. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  60. timer := utils.NewElapsedTimer()
  61. logs := new(internal.LogCollector)
  62. lrw := loggedResponseWriter{
  63. w: w,
  64. r: r,
  65. code: http.StatusOK,
  66. }
  67. var dup io.ReadCloser
  68. r.Body, dup = iox.DupReadCloser(r.Body)
  69. next.ServeHTTP(&lrw, r.WithContext(context.WithValue(r.Context(), internal.LogContext, logs)))
  70. r.Body = dup
  71. logBrief(r, lrw.code, timer, logs)
  72. })
  73. }
  74. type detailLoggedResponseWriter struct {
  75. writer *loggedResponseWriter
  76. buf *bytes.Buffer
  77. }
  78. func newDetailLoggedResponseWriter(writer *loggedResponseWriter, buf *bytes.Buffer) *detailLoggedResponseWriter {
  79. return &detailLoggedResponseWriter{
  80. writer: writer,
  81. buf: buf,
  82. }
  83. }
  84. func (w *detailLoggedResponseWriter) Flush() {
  85. w.writer.Flush()
  86. }
  87. func (w *detailLoggedResponseWriter) Header() http.Header {
  88. return w.writer.Header()
  89. }
  90. // Hijack implements the http.Hijacker interface.
  91. // This expands the Response to fulfill http.Hijacker if the underlying http.ResponseWriter supports it.
  92. func (w *detailLoggedResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
  93. if hijacked, ok := w.writer.w.(http.Hijacker); ok {
  94. return hijacked.Hijack()
  95. }
  96. return nil, nil, errors.New("server doesn't support hijacking")
  97. }
  98. func (w *detailLoggedResponseWriter) Write(bs []byte) (int, error) {
  99. w.buf.Write(bs)
  100. return w.writer.Write(bs)
  101. }
  102. func (w *detailLoggedResponseWriter) WriteHeader(code int) {
  103. w.writer.WriteHeader(code)
  104. }
  105. // DetailedLogHandler returns a middleware that logs http request and response in details.
  106. func DetailedLogHandler(next http.Handler) http.Handler {
  107. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  108. timer := utils.NewElapsedTimer()
  109. var buf bytes.Buffer
  110. lrw := newDetailLoggedResponseWriter(&loggedResponseWriter{
  111. w: w,
  112. r: r,
  113. code: http.StatusOK,
  114. }, &buf)
  115. var dup io.ReadCloser
  116. r.Body, dup = iox.DupReadCloser(r.Body)
  117. logs := new(internal.LogCollector)
  118. next.ServeHTTP(lrw, r.WithContext(context.WithValue(r.Context(), internal.LogContext, logs)))
  119. r.Body = dup
  120. logDetails(r, lrw, timer, logs)
  121. })
  122. }
  123. // SetSlowThreshold sets the slow threshold.
  124. func SetSlowThreshold(threshold time.Duration) {
  125. slowThreshold.Set(threshold)
  126. }
  127. func dumpRequest(r *http.Request) string {
  128. reqContent, err := httputil.DumpRequest(r, true)
  129. if err != nil {
  130. return err.Error()
  131. }
  132. return string(reqContent)
  133. }
  134. func isOkResponse(code int) bool {
  135. // not server error
  136. return code < http.StatusInternalServerError
  137. }
  138. func logBrief(r *http.Request, code int, timer *utils.ElapsedTimer, logs *internal.LogCollector) {
  139. var buf bytes.Buffer
  140. duration := timer.Duration()
  141. logger := logx.WithContext(r.Context()).WithDuration(duration)
  142. buf.WriteString(fmt.Sprintf("[HTTP] %s - %s %s - %s - %s",
  143. wrapStatusCode(code), wrapMethod(r.Method), r.RequestURI, httpx.GetRemoteAddr(r), r.UserAgent()))
  144. if duration > slowThreshold.Load() {
  145. logger.Slowf("[HTTP] %s - %s - %s %s - %s - slowcall(%s)",
  146. wrapStatusCode(code), wrapMethod(r.Method), r.RequestURI, httpx.GetRemoteAddr(r), r.UserAgent(),
  147. fmt.Sprintf("slowcall(%s)", timex.ReprOfDuration(duration)))
  148. }
  149. ok := isOkResponse(code)
  150. if !ok {
  151. fullReq := dumpRequest(r)
  152. limitReader := io.LimitReader(strings.NewReader(fullReq), limitBodyBytes)
  153. body, err := io.ReadAll(limitReader)
  154. if err != nil {
  155. buf.WriteString(fmt.Sprintf("\n%s", fullReq))
  156. } else {
  157. buf.WriteString(fmt.Sprintf("\n%s", string(body)))
  158. }
  159. }
  160. body := logs.Flush()
  161. if len(body) > 0 {
  162. buf.WriteString(fmt.Sprintf("\n%s", body))
  163. }
  164. if ok {
  165. logger.Info(buf.String())
  166. } else {
  167. logger.Error(buf.String())
  168. }
  169. }
  170. func logDetails(r *http.Request, response *detailLoggedResponseWriter, timer *utils.ElapsedTimer,
  171. logs *internal.LogCollector) {
  172. var buf bytes.Buffer
  173. duration := timer.Duration()
  174. code := response.writer.code
  175. logger := logx.WithContext(r.Context())
  176. buf.WriteString(fmt.Sprintf("[HTTP] %s - %d - %s - %s\n=> %s\n",
  177. r.Method, code, r.RemoteAddr, timex.ReprOfDuration(duration), dumpRequest(r)))
  178. if duration > defaultSlowThreshold {
  179. logger.Slowf("[HTTP] %s - %d - %s - slowcall(%s)\n=> %s\n", r.Method, code, r.RemoteAddr,
  180. fmt.Sprintf("slowcall(%s)", timex.ReprOfDuration(duration)), dumpRequest(r))
  181. }
  182. body := logs.Flush()
  183. if len(body) > 0 {
  184. buf.WriteString(fmt.Sprintf("%s\n", body))
  185. }
  186. respBuf := response.buf.Bytes()
  187. if len(respBuf) > 0 {
  188. buf.WriteString(fmt.Sprintf("<= %s", respBuf))
  189. }
  190. if isOkResponse(code) {
  191. logger.Info(buf.String())
  192. } else {
  193. logger.Error(buf.String())
  194. }
  195. }
  196. func wrapMethod(method string) string {
  197. var colour color.Color
  198. switch method {
  199. case http.MethodGet:
  200. colour = color.BgBlue
  201. case http.MethodPost:
  202. colour = color.BgCyan
  203. case http.MethodPut:
  204. colour = color.BgYellow
  205. case http.MethodDelete:
  206. colour = color.BgRed
  207. case http.MethodPatch:
  208. colour = color.BgGreen
  209. case http.MethodHead:
  210. colour = color.BgMagenta
  211. case http.MethodOptions:
  212. colour = color.BgWhite
  213. }
  214. if colour == color.NoColor {
  215. return method
  216. }
  217. return logx.WithColorPadding(method, colour)
  218. }
  219. func wrapStatusCode(code int) string {
  220. var colour color.Color
  221. switch {
  222. case code >= http.StatusOK && code < http.StatusMultipleChoices:
  223. colour = color.BgGreen
  224. case code >= http.StatusMultipleChoices && code < http.StatusBadRequest:
  225. colour = color.BgBlue
  226. case code >= http.StatusBadRequest && code < http.StatusInternalServerError:
  227. colour = color.BgMagenta
  228. default:
  229. colour = color.BgYellow
  230. }
  231. return logx.WithColorPadding(strconv.Itoa(code), colour)
  232. }