[#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
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:
parent
b389899447
commit
88486bb0d5
1 changed files with 61 additions and 55 deletions
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue