2025-02-06 13:18:16 +03:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
|
|
|
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs"
|
|
|
|
"git.frostfs.info/TrueCloudLab/zapjournald"
|
|
|
|
"github.com/spf13/viper"
|
|
|
|
"github.com/ssgreg/journald"
|
|
|
|
"go.uber.org/zap"
|
|
|
|
"go.uber.org/zap/zapcore"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
destinationStdout string = "stdout"
|
|
|
|
destinationJournald string = "journald"
|
|
|
|
)
|
|
|
|
|
2025-02-11 16:11:39 +03:00
|
|
|
var defaultTags = []string{logs.TagApp, logs.TagDatapath, logs.TagExternalStorage, logs.TagExternalStorageTree, logs.TagExternalBlockchain}
|
2025-02-06 13:18:16 +03:00
|
|
|
|
|
|
|
type Logger struct {
|
|
|
|
logger *zap.Logger
|
|
|
|
lvl zap.AtomicLevel
|
|
|
|
}
|
|
|
|
|
|
|
|
func pickLogger(v *viper.Viper, loggerSettings LoggerAppSettings, tagSettings TagFilterSettings) *Logger {
|
|
|
|
dest := v.GetString(cfgLoggerDestination)
|
|
|
|
|
|
|
|
switch dest {
|
|
|
|
case destinationStdout:
|
|
|
|
return newStdoutLogger(v, loggerSettings, tagSettings)
|
|
|
|
case destinationJournald:
|
|
|
|
return newJournaldLogger(v, loggerSettings, tagSettings)
|
|
|
|
default:
|
|
|
|
panic(fmt.Sprintf("wrong destination for logger: %s", dest))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// newStdoutLogger constructs a Logger instance for the current application.
|
|
|
|
// Panics on failure.
|
|
|
|
//
|
|
|
|
// Logger contains a logger is built from zap's production logging configuration with:
|
|
|
|
// - parameterized level (debug by default)
|
|
|
|
// - console encoding
|
|
|
|
// - ISO8601 time encoding
|
|
|
|
// - sampling intervals
|
|
|
|
//
|
|
|
|
// and atomic log level to dynamically change it.
|
|
|
|
//
|
|
|
|
// Logger records a stack trace for all messages at or above fatal level.
|
|
|
|
//
|
|
|
|
// See also zapcore.Level, zap.NewProductionConfig, zap.AddStacktrace.
|
|
|
|
func newStdoutLogger(v *viper.Viper, loggerSettings LoggerAppSettings, tagSettings TagFilterSettings) *Logger {
|
|
|
|
c := newZapLogConfig(v)
|
|
|
|
|
|
|
|
out, errSink, err := openZapSinks(c)
|
|
|
|
if err != nil {
|
|
|
|
panic(fmt.Sprintf("open zap sinks: %v", err.Error()))
|
|
|
|
}
|
|
|
|
|
|
|
|
core := zapcore.NewCore(zapcore.NewConsoleEncoder(c.EncoderConfig), out, c.Level)
|
|
|
|
core = applyZapCoreMiddlewares(core, v, loggerSettings, tagSettings)
|
|
|
|
l := zap.New(core, zap.AddStacktrace(zap.NewAtomicLevelAt(zap.FatalLevel)), zap.ErrorOutput(errSink))
|
|
|
|
|
|
|
|
return &Logger{logger: l, lvl: c.Level}
|
|
|
|
}
|
|
|
|
|
|
|
|
func newJournaldLogger(v *viper.Viper, logSettings LoggerAppSettings, tagSettings TagFilterSettings) *Logger {
|
|
|
|
c := newZapLogConfig(v)
|
|
|
|
|
|
|
|
// We can use NewJSONEncoder instead if, say, frontend
|
|
|
|
// would like to access journald logs and parse them easily.
|
|
|
|
encoder := zapjournald.NewPartialEncoder(zapcore.NewConsoleEncoder(c.EncoderConfig), zapjournald.SyslogFields)
|
|
|
|
|
|
|
|
journalCore := zapjournald.NewCore(c.Level, encoder, &journald.Journal{}, zapjournald.SyslogFields)
|
|
|
|
core := journalCore.With([]zapcore.Field{
|
|
|
|
zapjournald.SyslogFacility(zapjournald.LogDaemon),
|
|
|
|
zapjournald.SyslogIdentifier(),
|
|
|
|
zapjournald.SyslogPid(),
|
|
|
|
})
|
|
|
|
core = applyZapCoreMiddlewares(core, v, logSettings, tagSettings)
|
|
|
|
l := zap.New(core, zap.AddStacktrace(zap.NewAtomicLevelAt(zap.FatalLevel)))
|
|
|
|
return &Logger{logger: l, lvl: c.Level}
|
|
|
|
}
|
|
|
|
|
|
|
|
func openZapSinks(cfg zap.Config) (zapcore.WriteSyncer, zapcore.WriteSyncer, error) {
|
|
|
|
sink, closeOut, err := zap.Open(cfg.OutputPaths...)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
errSink, _, err := zap.Open(cfg.ErrorOutputPaths...)
|
|
|
|
if err != nil {
|
|
|
|
closeOut()
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
return sink, errSink, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
var _ zapcore.Core = (*zapCoreTagFilterWrapper)(nil)
|
|
|
|
|
|
|
|
type zapCoreTagFilterWrapper struct {
|
|
|
|
core zapcore.Core
|
|
|
|
settings TagFilterSettings
|
|
|
|
extra []zap.Field
|
|
|
|
}
|
|
|
|
|
|
|
|
type TagFilterSettings interface {
|
|
|
|
LevelEnabled(tag string, lvl zapcore.Level) bool
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *zapCoreTagFilterWrapper) Enabled(level zapcore.Level) bool {
|
|
|
|
return c.core.Enabled(level)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *zapCoreTagFilterWrapper) With(fields []zapcore.Field) zapcore.Core {
|
|
|
|
return &zapCoreTagFilterWrapper{
|
|
|
|
core: c.core.With(fields),
|
|
|
|
settings: c.settings,
|
|
|
|
extra: append(c.extra, fields...),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *zapCoreTagFilterWrapper) Check(entry zapcore.Entry, checked *zapcore.CheckedEntry) *zapcore.CheckedEntry {
|
|
|
|
if c.core.Enabled(entry.Level) {
|
|
|
|
return checked.AddCore(entry, c)
|
|
|
|
}
|
|
|
|
return checked
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *zapCoreTagFilterWrapper) Write(entry zapcore.Entry, fields []zapcore.Field) error {
|
|
|
|
if c.shouldSkip(entry, fields) || c.shouldSkip(entry, c.extra) {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return c.core.Write(entry, fields)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *zapCoreTagFilterWrapper) shouldSkip(entry zapcore.Entry, fields []zap.Field) bool {
|
|
|
|
for _, field := range fields {
|
|
|
|
if field.Key == logs.TagFieldName && field.Type == zapcore.StringType {
|
|
|
|
if !c.settings.LevelEnabled(field.String, entry.Level) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *zapCoreTagFilterWrapper) Sync() error {
|
|
|
|
return c.core.Sync()
|
|
|
|
}
|
|
|
|
|
|
|
|
func applyZapCoreMiddlewares(core zapcore.Core, v *viper.Viper, appSettings LoggerAppSettings, tagSettings TagFilterSettings) zapcore.Core {
|
|
|
|
core = &zapCoreTagFilterWrapper{
|
|
|
|
core: core,
|
|
|
|
settings: tagSettings,
|
|
|
|
}
|
|
|
|
|
|
|
|
if v.GetBool(cfgLoggerSamplingEnabled) {
|
|
|
|
core = zapcore.NewSamplerWithOptions(core,
|
|
|
|
v.GetDuration(cfgLoggerSamplingInterval),
|
|
|
|
v.GetInt(cfgLoggerSamplingInitial),
|
|
|
|
v.GetInt(cfgLoggerSamplingThereafter),
|
|
|
|
zapcore.SamplerHook(func(_ zapcore.Entry, dec zapcore.SamplingDecision) {
|
|
|
|
if dec&zapcore.LogDropped > 0 {
|
|
|
|
appSettings.DroppedLogsInc()
|
|
|
|
}
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
|
|
|
|
return core
|
|
|
|
}
|
|
|
|
|
|
|
|
func newZapLogConfig(v *viper.Viper) zap.Config {
|
|
|
|
lvl, err := getLogLevel(v)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
c := zap.Config{
|
|
|
|
Level: zap.NewAtomicLevelAt(lvl),
|
|
|
|
EncoderConfig: zap.NewProductionEncoderConfig(),
|
|
|
|
OutputPaths: []string{"stderr"},
|
|
|
|
ErrorOutputPaths: []string{"stderr"},
|
|
|
|
}
|
|
|
|
c.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
|
|
|
|
|
|
|
|
return c
|
|
|
|
}
|
|
|
|
|
|
|
|
func getLogLevel(v *viper.Viper) (zapcore.Level, error) {
|
|
|
|
var lvl zapcore.Level
|
|
|
|
lvlStr := v.GetString(cfgLoggerLevel)
|
|
|
|
err := lvl.UnmarshalText([]byte(lvlStr))
|
|
|
|
if err != nil {
|
|
|
|
return lvl, fmt.Errorf("incorrect logger level configuration %s (%v), "+
|
|
|
|
"value should be one of %v", lvlStr, err, [...]zapcore.Level{
|
|
|
|
zapcore.DebugLevel,
|
|
|
|
zapcore.InfoLevel,
|
|
|
|
zapcore.WarnLevel,
|
|
|
|
zapcore.ErrorLevel,
|
|
|
|
zapcore.DPanicLevel,
|
|
|
|
zapcore.PanicLevel,
|
|
|
|
zapcore.FatalLevel,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
return lvl, nil
|
|
|
|
}
|