package main import ( "fmt" "os" "git.frostfs.info/TrueCloudLab/frostfs-http-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" ) 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 } 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, loggerSettings LoggerAppSettings, tagSetting TagFilterSettings) zapcore.Core { core = &zapCoreTagFilterWrapper{ core: core, settings: tagSetting, } 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 { loggerSettings.DroppedLogsInc() } })) } return core } func newLogEncoder() zapcore.Encoder { c := zap.NewProductionEncoderConfig() c.EncodeTime = zapcore.ISO8601TimeEncoder return zapcore.NewConsoleEncoder(c) } // newStdoutLogger constructs a zap.Logger instance for current application. // Panics on failure. // // Logger is built from zap's production logging configuration with: // - parameterized level (debug by default) // - console encoding // - ISO8601 time encoding // // 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, lvl zap.AtomicLevel, loggerSettings LoggerAppSettings, tagSetting TagFilterSettings) *Logger { stdout := zapcore.AddSync(os.Stderr) consoleOutCore := zapcore.NewCore(newLogEncoder(), stdout, lvl) consoleOutCore = applyZapCoreMiddlewares(consoleOutCore, v, loggerSettings, tagSetting) return &Logger{ logger: zap.New(consoleOutCore, zap.AddStacktrace(zap.NewAtomicLevelAt(zap.FatalLevel))), lvl: lvl, } } func newJournaldLogger(v *viper.Viper, lvl zap.AtomicLevel, loggerSettings LoggerAppSettings, tagSetting TagFilterSettings) *Logger { encoder := zapjournald.NewPartialEncoder(newLogEncoder(), zapjournald.SyslogFields) core := zapjournald.NewCore(lvl, encoder, &journald.Journal{}, zapjournald.SyslogFields) coreWithContext := core.With([]zapcore.Field{ zapjournald.SyslogFacility(zapjournald.LogDaemon), zapjournald.SyslogIdentifier(), zapjournald.SyslogPid(), }) coreWithContext = applyZapCoreMiddlewares(coreWithContext, v, loggerSettings, tagSetting) return &Logger{ logger: zap.New(coreWithContext, zap.AddStacktrace(zap.NewAtomicLevelAt(zap.FatalLevel))), lvl: lvl, } } type LoggerAppSettings interface { DroppedLogsInc() } func pickLogger(v *viper.Viper, lvl zap.AtomicLevel, loggerSettings LoggerAppSettings, tagSettings TagFilterSettings) *Logger { dest := v.GetString(cfgLoggerDestination) switch dest { case destinationStdout: return newStdoutLogger(v, lvl, loggerSettings, tagSettings) case destinationJournald: return newJournaldLogger(v, lvl, loggerSettings, tagSettings) default: panic(fmt.Sprintf("wrong destination for logger: %s", dest)) } }