request_body_read_log.go 1.9 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879
  1. package handler
  2. import (
  3. "context"
  4. "errors"
  5. "io"
  6. "net"
  7. "net/http"
  8. "strings"
  9. "syscall"
  10. "go.uber.org/zap"
  11. )
  12. // logRequestBodyReadFailure records a bounded, payload-free reason for a body
  13. // read failure. Clients continue to receive the stable generic error message;
  14. // operators get enough information to distinguish compression failures from a
  15. // disconnected/truncated upload without logging request content.
  16. func logRequestBodyReadFailure(reqLog *zap.Logger, req *http.Request, err error) {
  17. if reqLog == nil || err == nil {
  18. return
  19. }
  20. contentLength := int64(-1)
  21. contentEncoding := "identity"
  22. if req != nil {
  23. contentLength = req.ContentLength
  24. contentEncoding = requestContentEncodingCategory(req.Header.Get("Content-Encoding"))
  25. }
  26. reqLog.Warn("read request body failed",
  27. zap.String("error_kind", requestBodyReadErrorKind(err)),
  28. zap.String("content_encoding", contentEncoding),
  29. zap.Int64("content_length", contentLength),
  30. )
  31. }
  32. func requestContentEncodingCategory(value string) string {
  33. switch strings.ToLower(strings.TrimSpace(value)) {
  34. case "", "identity":
  35. return "identity"
  36. case "gzip", "x-gzip":
  37. return "gzip"
  38. case "zstd":
  39. return "zstd"
  40. case "deflate":
  41. return "deflate"
  42. default:
  43. return "other"
  44. }
  45. }
  46. func requestBodyReadErrorKind(err error) string {
  47. if err == nil {
  48. return "none"
  49. }
  50. var maxErr *http.MaxBytesError
  51. if errors.As(err, &maxErr) {
  52. return "max_bytes"
  53. }
  54. lower := strings.ToLower(err.Error())
  55. if strings.Contains(lower, "decode content-encoding") {
  56. if strings.Contains(lower, "unsupported content-encoding") {
  57. return "unsupported_content_encoding"
  58. }
  59. return "decode_content_encoding"
  60. }
  61. if errors.Is(err, context.Canceled) || errors.Is(err, syscall.ECONNRESET) || errors.Is(err, syscall.EPIPE) {
  62. return "client_disconnect"
  63. }
  64. if errors.Is(err, io.ErrUnexpectedEOF) {
  65. return "truncated_body"
  66. }
  67. var netErr net.Error
  68. if errors.As(err, &netErr) {
  69. return "transport"
  70. }
  71. return "io_read"
  72. }