| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354 |
- package handler
- import (
- "strconv"
- "github.com/Wei-Shaw/sub2api/internal/service"
- "go.uber.org/zap"
- )
- // parseFailureSnippetLen bounds the head/tail snippets logged on body parse
- // failure. 256 bytes is enough to see the structural context (model field,
- // first content block / trailing brace) without dumping user payloads.
- const parseFailureSnippetLen = 256
- // logRequestBodyParseFailure records the real reason a request body failed
- // JSON parsing/validation. The client keeps receiving the generic
- // "Failed to parse request body"; the sanitized diagnostics (underlying
- // error with byte offset, body length, escaped head/tail snippets) land in
- // the server log only, so operators can distinguish genuinely invalid JSON
- // from a truncated or partially consumed body.
- //
- // err may be nil for call sites that validate with gjson.ValidBytes directly;
- // the diagnostic error is derived from the body in that case.
- func logRequestBodyParseFailure(reqLog *zap.Logger, body []byte, err error) {
- if reqLog == nil {
- return
- }
- if err == nil {
- err = service.DescribeInvalidJSON(body)
- }
- head := body
- var tail []byte
- if len(body) > parseFailureSnippetLen {
- head = body[:parseFailureSnippetLen]
- tail = body[len(body)-parseFailureSnippetLen:]
- }
- fields := []zap.Field{
- zap.Error(err),
- zap.Int("body_len", len(body)),
- zap.String("body_head", sanitizeBodySnippet(head)),
- }
- if len(tail) > 0 {
- fields = append(fields, zap.String("body_tail", sanitizeBodySnippet(tail)))
- }
- reqLog.Warn("parse request body failed", fields...)
- }
- // sanitizeBodySnippet escapes control characters and invalid UTF-8 so the
- // snippet is always a single printable log line.
- func sanitizeBodySnippet(b []byte) string {
- return strconv.Quote(string(b))
- }
|