request_body_parse_log.go 1.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354
  1. package handler
  2. import (
  3. "strconv"
  4. "github.com/Wei-Shaw/sub2api/internal/service"
  5. "go.uber.org/zap"
  6. )
  7. // parseFailureSnippetLen bounds the head/tail snippets logged on body parse
  8. // failure. 256 bytes is enough to see the structural context (model field,
  9. // first content block / trailing brace) without dumping user payloads.
  10. const parseFailureSnippetLen = 256
  11. // logRequestBodyParseFailure records the real reason a request body failed
  12. // JSON parsing/validation. The client keeps receiving the generic
  13. // "Failed to parse request body"; the sanitized diagnostics (underlying
  14. // error with byte offset, body length, escaped head/tail snippets) land in
  15. // the server log only, so operators can distinguish genuinely invalid JSON
  16. // from a truncated or partially consumed body.
  17. //
  18. // err may be nil for call sites that validate with gjson.ValidBytes directly;
  19. // the diagnostic error is derived from the body in that case.
  20. func logRequestBodyParseFailure(reqLog *zap.Logger, body []byte, err error) {
  21. if reqLog == nil {
  22. return
  23. }
  24. if err == nil {
  25. err = service.DescribeInvalidJSON(body)
  26. }
  27. head := body
  28. var tail []byte
  29. if len(body) > parseFailureSnippetLen {
  30. head = body[:parseFailureSnippetLen]
  31. tail = body[len(body)-parseFailureSnippetLen:]
  32. }
  33. fields := []zap.Field{
  34. zap.Error(err),
  35. zap.Int("body_len", len(body)),
  36. zap.String("body_head", sanitizeBodySnippet(head)),
  37. }
  38. if len(tail) > 0 {
  39. fields = append(fields, zap.String("body_tail", sanitizeBodySnippet(tail)))
  40. }
  41. reqLog.Warn("parse request body failed", fields...)
  42. }
  43. // sanitizeBodySnippet escapes control characters and invalid UTF-8 so the
  44. // snippet is always a single printable log line.
  45. func sanitizeBodySnippet(b []byte) string {
  46. return strconv.Quote(string(b))
  47. }