[#369] HTTP status code logging
All checks were successful
/ DCO (pull_request) Successful in 1m13s
/ Vulncheck (pull_request) Successful in 1m10s
/ Builds (1.20) (pull_request) Successful in 1m33s
/ Builds (1.21) (pull_request) Successful in 1m26s
/ Lint (pull_request) Successful in 2m24s
/ Tests (1.20) (pull_request) Successful in 1m43s
/ Tests (1.21) (pull_request) Successful in 1m38s

Signed-off-by: Nikita Zinkevich <n.zinkevich@yadro.com>
This commit is contained in:
Nikita Zinkevich 2024-08-05 09:27:38 +03:00
parent b389899447
commit 88486bb0d5

View file

@ -7,6 +7,7 @@ import (
"io" "io"
"net/http" "net/http"
"net/url" "net/url"
"sync"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/playback/utils" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/playback/utils"
@ -15,18 +16,30 @@ import (
"gopkg.in/natefinch/lumberjack.v2" "gopkg.in/natefinch/lumberjack.v2"
) )
type LogHTTPConfig struct { type (
LogHTTPConfig struct {
Enabled bool Enabled bool
MaxBody int64 MaxBody int64
MaxLogSize int MaxLogSize int
OutputPath string OutputPath string
UseGzip bool UseGzip bool
} }
fileLogger struct {
type fileLogger struct {
*zap.Logger *zap.Logger
logRoller *lumberjack.Logger logRoller *lumberjack.Logger
} }
// Implementation of zap.Sink for using lumberjack.
lumberjackSink struct {
*lumberjack.Logger
}
// responseReadWriter helps read http response body.
responseReadWriter struct {
sync.Once
http.ResponseWriter
response *bytes.Buffer
statusCode int
}
)
const ( const (
payloadLabel = "payload" payloadLabel = "payload"
@ -75,11 +88,6 @@ func (f *fileLogger) registerOutputSink(conf *LogHTTPConfig) error {
return nil return nil
} }
// Implementation of zap.Sink for using lumberjack.
type lumberjackSink struct {
*lumberjack.Logger
}
func (lumberjackSink) Sync() error { func (lumberjackSink) Sync() error {
return nil return nil
} }
@ -101,17 +109,18 @@ func ReloadFileLogger(conf *LogHTTPConfig) error {
return nil return nil
} }
// responseReadWriter helps read http response body.
type responseReadWriter struct {
http.ResponseWriter
response *bytes.Buffer
}
func (ww *responseReadWriter) Write(data []byte) (int, error) { func (ww *responseReadWriter) Write(data []byte) (int, error) {
ww.response.Write(data) ww.response.Write(data)
return ww.ResponseWriter.Write(data) return ww.ResponseWriter.Write(data)
} }
func (ww *responseReadWriter) WriteHeader(code int) {
ww.Do(func() {
ww.statusCode = code
ww.ResponseWriter.WriteHeader(code)
})
}
func (ww *responseReadWriter) Flush() { func (ww *responseReadWriter) Flush() {
if f, ok := ww.ResponseWriter.(http.Flusher); ok { if f, ok := ww.ResponseWriter.(http.Flusher); ok {
f.Flush() f.Flush()
@ -137,43 +146,36 @@ func LogHTTP(l *zap.Logger, config *LogHTTPConfig) Func {
return return
} }
httplog := filelog.getHTTPLogger(r) httplog := filelog.getHTTPLogger(r).
httplog.withFieldIfExist("query", r.URL.Query()) withFieldIfExist("query", r.URL.Query()).
httplog.withFieldIfExist("headers", r.Header) withFieldIfExist("headers", r.Header)
payload, err := getBody(r.Body) payload := getBody(r.Body, l)
if err != nil {
l.Error(logs.FailedToGetHTTPBody,
zap.Error(err),
zap.String("body type", payloadLabel))
return
}
r.Body = io.NopCloser(bytes.NewReader(payload)) r.Body = io.NopCloser(bytes.NewReader(payload))
payloadReader := io.LimitReader(bytes.NewReader(payload), config.MaxBody)
httplog = httplog.withProcessedBody(payloadLabel, payloadReader, l)
respBuf := &bytes.Buffer{} respBuf := &bytes.Buffer{}
wr := &responseReadWriter{ResponseWriter: w, response: respBuf} wr := &responseReadWriter{ResponseWriter: w, response: respBuf}
h.ServeHTTP(wr, r) h.ServeHTTP(wr, r)
payloadReader := io.LimitReader(bytes.NewReader(payload), config.MaxBody)
httplog, err = httplog.withProcessedBody(payloadLabel, payloadReader)
if err != nil {
l.Error(logs.FailedToProcessHTTPBody,
zap.Error(err),
zap.String("body type", payloadLabel))
}
respReader := io.LimitReader(respBuf, config.MaxBody) respReader := io.LimitReader(respBuf, config.MaxBody)
httplog, err = httplog.withProcessedBody(responseLabel, respReader) httplog = httplog.withProcessedBody(responseLabel, respReader, l)
if err != nil { httplog = httplog.with(zap.Int("status", wr.statusCode))
l.Error(logs.FailedToProcessHTTPBody,
zap.Error(err),
zap.String("body type", responseLabel))
}
httplog.Info(logs.LogHTTP) httplog.Info(logs.LogHTTP)
}) })
} }
} }
func (f *fileLogger) with(fields ...zap.Field) *fileLogger {
return &fileLogger{
Logger: f.Logger.With(fields...),
logRoller: f.logRoller,
}
}
func (f *fileLogger) getHTTPLogger(r *http.Request) *fileLogger { func (f *fileLogger) getHTTPLogger(r *http.Request) *fileLogger {
return &fileLogger{ return &fileLogger{
Logger: f.Logger.With( Logger: f.Logger.With(
@ -186,14 +188,16 @@ func (f *fileLogger) getHTTPLogger(r *http.Request) *fileLogger {
} }
} }
func (f *fileLogger) withProcessedBody(label string, bodyReader io.Reader) (*fileLogger, error) { func (f *fileLogger) withProcessedBody(label string, bodyReader io.Reader, l *zap.Logger) *fileLogger {
resp, err := ProcessBody(bodyReader) resp, err := ProcessBody(bodyReader)
if err != nil { if err != nil {
return f, err l.Error(logs.FailedToProcessHTTPBody,
zap.Error(err),
zap.String("body type", payloadLabel))
return f
} }
f.Logger = f.Logger.With(zap.ByteString(label, resp))
return f, nil return f.with(zap.ByteString(label, resp))
} }
// newLoggerConfig creates new zap.Config with disabled base fields. // newLoggerConfig creates new zap.Config with disabled base fields.
@ -209,15 +213,17 @@ func (f *fileLogger) newLoggerConfig() zap.Config {
return c return c
} }
// getBody reads http.Body and returns its io.Reader. func getBody(httpBody io.ReadCloser, l *zap.Logger) []byte {
func getBody(httpBody io.ReadCloser) ([]byte, error) {
defer httpBody.Close() defer httpBody.Close()
body, err := io.ReadAll(httpBody) body, err := io.ReadAll(httpBody)
if err != nil { if err != nil {
return nil, err l.Error(logs.FailedToGetHTTPBody,
zap.Error(err),
zap.String("body type", payloadLabel))
return nil
} }
return body, nil return body
} }
// ProcessBody reads body and base64 encode it if it's not XML. // ProcessBody reads body and base64 encode it if it's not XML.
@ -239,7 +245,7 @@ func ProcessBody(bodyReader io.Reader) ([]byte, error) {
// withFieldIfExist checks whether data is not empty and attach it to log output. // withFieldIfExist checks whether data is not empty and attach it to log output.
func (f *fileLogger) withFieldIfExist(label string, data map[string][]string) *fileLogger { func (f *fileLogger) withFieldIfExist(label string, data map[string][]string) *fileLogger {
if len(data) != 0 { if len(data) != 0 {
f.Logger = f.Logger.With(zap.Any(label, data)) return f.with(zap.Any(label, data))
} }
return f return f
} }