Files
sub2api/backend/internal/handler/request_body_read_log.go
T

80 lines
1.9 KiB
Go

package handler
import (
"context"
"errors"
"io"
"net"
"net/http"
"strings"
"syscall"
"go.uber.org/zap"
)
// logRequestBodyReadFailure records a bounded, payload-free reason for a body
// read failure. Clients continue to receive the stable generic error message;
// operators get enough information to distinguish compression failures from a
// disconnected/truncated upload without logging request content.
func logRequestBodyReadFailure(reqLog *zap.Logger, req *http.Request, err error) {
if reqLog == nil || err == nil {
return
}
contentLength := int64(-1)
contentEncoding := "identity"
if req != nil {
contentLength = req.ContentLength
contentEncoding = requestContentEncodingCategory(req.Header.Get("Content-Encoding"))
}
reqLog.Warn("read request body failed",
zap.String("error_kind", requestBodyReadErrorKind(err)),
zap.String("content_encoding", contentEncoding),
zap.Int64("content_length", contentLength),
)
}
func requestContentEncodingCategory(value string) string {
switch strings.ToLower(strings.TrimSpace(value)) {
case "", "identity":
return "identity"
case "gzip", "x-gzip":
return "gzip"
case "zstd":
return "zstd"
case "deflate":
return "deflate"
default:
return "other"
}
}
func requestBodyReadErrorKind(err error) string {
if err == nil {
return "none"
}
var maxErr *http.MaxBytesError
if errors.As(err, &maxErr) {
return "max_bytes"
}
lower := strings.ToLower(err.Error())
if strings.Contains(lower, "decode content-encoding") {
if strings.Contains(lower, "unsupported content-encoding") {
return "unsupported_content_encoding"
}
return "decode_content_encoding"
}
if errors.Is(err, context.Canceled) || errors.Is(err, syscall.ECONNRESET) || errors.Is(err, syscall.EPIPE) {
return "client_disconnect"
}
if errors.Is(err, io.ErrUnexpectedEOF) {
return "truncated_body"
}
var netErr net.Error
if errors.As(err, &netErr) {
return "transport"
}
return "io_read"
}