Nikita Zinkevich
9ccd191e6e
Declared fileLogger struct in log_http.go. Add ReloadFileLogger method Signed-off-by: Nikita Zinkevich <n.zinkevich@yadro.com>
170 lines
4 KiB
Go
170 lines
4 KiB
Go
//go:build loghttp
|
|
|
|
package middleware
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
|
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs"
|
|
"go.uber.org/zap"
|
|
"go.uber.org/zap/zapcore"
|
|
"gopkg.in/natefinch/lumberjack.v2"
|
|
)
|
|
|
|
type LogHTTPConfig struct {
|
|
Enabled bool
|
|
MaxBody int64
|
|
MaxLogSize int
|
|
OutputPath string
|
|
UseGzip bool
|
|
}
|
|
|
|
type fileLogger struct {
|
|
*zap.Logger
|
|
logRoller *lumberjack.Logger
|
|
}
|
|
|
|
var filelog = fileLogger{}
|
|
|
|
// newFileLogger returns registers zap sink and returns new fileLogger.
|
|
func newFileLogger(conf *LogHTTPConfig) (fileLogger, error) {
|
|
var flog = fileLogger{}
|
|
c := flog.newLoggerConfig()
|
|
c.OutputPaths = []string{conf.OutputPath}
|
|
if conf.OutputPath != StdoutPath && conf.OutputPath != StderrPath && conf.OutputPath != "" {
|
|
err := flog.registerOutputSink(conf)
|
|
if err != nil {
|
|
return flog, err
|
|
}
|
|
c.OutputPaths[0] = SinkName + ":" + conf.OutputPath
|
|
}
|
|
log, err := c.Build()
|
|
if err != nil {
|
|
return flog, err
|
|
}
|
|
flog.Logger = log
|
|
|
|
return flog, nil
|
|
}
|
|
|
|
// registerOutputSink creates and registers sink for logger file output.
|
|
func (f *fileLogger) registerOutputSink(conf *LogHTTPConfig) error {
|
|
f.logRoller = &lumberjack.Logger{
|
|
Filename: conf.OutputPath,
|
|
MaxSize: conf.MaxLogSize,
|
|
Compress: conf.UseGzip,
|
|
}
|
|
err := zap.RegisterSink(SinkName, func(_ *url.URL) (zap.Sink, error) {
|
|
return lumberjackSink{
|
|
Logger: f.logRoller,
|
|
}, nil
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Implementation of zap.Sink for using lumberjack.
|
|
type lumberjackSink struct {
|
|
*lumberjack.Logger
|
|
}
|
|
|
|
func (lumberjackSink) Sync() error {
|
|
return nil
|
|
}
|
|
|
|
func ReloadFileLogger(conf *LogHTTPConfig) error {
|
|
if filelog.logRoller == nil {
|
|
return nil
|
|
}
|
|
filelog.logRoller.MaxSize = conf.MaxLogSize
|
|
filelog.logRoller.Compress = conf.UseGzip
|
|
|
|
if filelog.logRoller.Filename != conf.OutputPath {
|
|
filelog.logRoller.Filename = conf.OutputPath
|
|
if err := filelog.logRoller.Rotate(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// LogHTTP logs http parameters from s3 request.
|
|
func LogHTTP(l *zap.Logger, config *LogHTTPConfig) Func {
|
|
var err error
|
|
filelog, err = newFileLogger(config)
|
|
if err != nil {
|
|
l.Warn(logs.FailedToInitializeHTTPLogger)
|
|
return func(h http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
h.ServeHTTP(w, r)
|
|
})
|
|
}
|
|
}
|
|
return func(h http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if !config.Enabled {
|
|
h.ServeHTTP(w, r)
|
|
return
|
|
}
|
|
var httplog = filelog.With(
|
|
zap.String("from", r.RemoteAddr),
|
|
zap.String("URI", r.RequestURI),
|
|
zap.String("method", r.Method),
|
|
)
|
|
|
|
httplog = withFieldIfExist(httplog, "query", r.URL.Query())
|
|
httplog = withFieldIfExist(httplog, "headers", r.Header)
|
|
if r.ContentLength > 0 && r.ContentLength <= config.MaxBody {
|
|
httplog, err = withBody(httplog, r)
|
|
if err != nil {
|
|
l.Warn(logs.FailedToGetRequestBody, zap.Error(err))
|
|
}
|
|
}
|
|
httplog.Info(logs.RequestHTTP)
|
|
h.ServeHTTP(w, r)
|
|
})
|
|
}
|
|
}
|
|
|
|
// newLoggerConfig creates new zap.Config with disabled base fields.
|
|
func (*fileLogger) newLoggerConfig() zap.Config {
|
|
c := zap.NewProductionConfig()
|
|
c.DisableCaller = true
|
|
c.DisableStacktrace = true
|
|
c.EncoderConfig.MessageKey = zapcore.OmitKey
|
|
c.EncoderConfig.LevelKey = zapcore.OmitKey
|
|
c.EncoderConfig.TimeKey = zapcore.OmitKey
|
|
c.EncoderConfig.FunctionKey = zapcore.OmitKey
|
|
|
|
return c
|
|
}
|
|
|
|
// withBody reads body and attach it to log output.
|
|
func withBody(httplog *zap.Logger, r *http.Request) (*zap.Logger, error) {
|
|
body, err := io.ReadAll(r.Body)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("read body error: %w", err)
|
|
}
|
|
defer r.Body.Close()
|
|
r.Body = io.NopCloser(bytes.NewBuffer(body))
|
|
|
|
httplog = httplog.With(zap.String("body", string(body)))
|
|
|
|
return httplog, nil
|
|
}
|
|
|
|
// withFieldIfExist checks whether data is not empty and attach it to log output.
|
|
func withFieldIfExist(log *zap.Logger, label string, data map[string][]string) *zap.Logger {
|
|
if len(data) != 0 {
|
|
log = log.With(zap.Any(label, data))
|
|
}
|
|
return log
|
|
}
|