frostfs-s3-gw/api/middleware/log_http.go
Nikita Zinkevich 9ccd191e6e [#369] Add fileLogger config reloading after SIGHUP
Declared fileLogger struct in log_http.go. Add ReloadFileLogger method

Signed-off-by: Nikita Zinkevich <n.zinkevich@yadro.com>
2024-08-30 09:06:45 +03:00

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
}