package main import ( "fmt" "strings" "git.frostfs.info/TrueCloudLab/zapjournald" "github.com/nspcc-dev/neo-go/cli/options" "github.com/spf13/viper" "github.com/ssgreg/journald" "go.uber.org/zap" "go.uber.org/zap/zapcore" ) const ( destinationStdout string = "stdout" destinationJournald string = "journald" ) type Logger struct { logger *zap.Logger lvl zap.AtomicLevel } func pickLogger(v *viper.Viper) *Logger { switch dest := v.GetString(cfgLoggerDestination); dest { case destinationStdout: return newStdoutLogger(v) case destinationJournald: return newJournaldLogger(v) default: panic(fmt.Sprintf("wrong destination for logger: %s", dest)) } } func newStdoutLogger(v *viper.Viper) *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 = options.NewFilteringCore(core, filteringLogOption(v)) l := zap.New(core, zap.AddStacktrace(zap.NewAtomicLevelAt(zap.FatalLevel)), zap.ErrorOutput(errSink)) 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 } func newJournaldLogger(v *viper.Viper) *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) core := zapjournald.NewCore(c.Level, encoder, &journald.Journal{}, zapjournald.SyslogFields) filteringCore := options.NewFilteringCore(core, filteringLogOption(v)) coreWithContext := filteringCore.With([]zapcore.Field{ zapjournald.SyslogFacility(zapjournald.LogDaemon), zapjournald.SyslogIdentifier(), zapjournald.SyslogPid(), }) l := zap.New(coreWithContext, zap.AddStacktrace(zap.NewAtomicLevelAt(zap.FatalLevel))) return &Logger{logger: l, lvl: c.Level} } func newZapLogConfig(v *viper.Viper) zap.Config { lvl, err := getLogLevel(v.GetString(cfgLoggerLevel)) if err != nil { panic(err) } c := zap.NewProductionConfig() c.Level = zap.NewAtomicLevelAt(lvl) c.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder if v.GetBool(cfgLoggerSamplingEnabled) { c.Sampling = &zap.SamplingConfig{ Initial: v.GetInt(cfgLoggerSamplingInitial), Thereafter: v.GetInt(cfgLoggerSamplingThereafter), } } else { c.Sampling = nil } return c } func filteringLogOption(v *viper.Viper) options.FilterFunc { tags := v.GetStringSlice(cfgLoggerTags) return func(entry zapcore.Entry) bool { if !strings.HasPrefix(entry.Message, "tag:") { return true } msg := entry.Message[4:] // len("tag:") for _, tag := range tags { if msg == tag { return true } } return false } } func getLogLevel(lvlStr string) (zapcore.Level, error) { var lvl zapcore.Level 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 }