diff --git a/cmd/frostfs-ir/config.go b/cmd/frostfs-ir/config.go index 19b7f05d6..643fc9c6d 100644 --- a/cmd/frostfs-ir/config.go +++ b/cmd/frostfs-ir/config.go @@ -44,7 +44,11 @@ func reloadConfig() error { if err != nil { return err } - log.Reload(logPrm) + logPrm.AllowedTags = cfg.GetStringSlice("logger.allowed_tags") + err = log.Reload(logPrm) + if err != nil { + return err + } return nil } diff --git a/cmd/frostfs-ir/main.go b/cmd/frostfs-ir/main.go index 114d8e4de..c0f331fb8 100644 --- a/cmd/frostfs-ir/main.go +++ b/cmd/frostfs-ir/main.go @@ -80,6 +80,7 @@ func main() { exitErr(err) logPrm.SamplingHook = metrics.LogMetrics().GetSamplingHook() logPrm.PrependTimestamp = cfg.GetBool("logger.timestamp") + logPrm.AllowedTags = cfg.GetStringSlice("logger.allowed_tags") log, err = logger.NewLogger(logPrm) exitErr(err) diff --git a/cmd/frostfs-node/config.go b/cmd/frostfs-node/config.go index ddaf5b5af..eeefa8d68 100644 --- a/cmd/frostfs-node/config.go +++ b/cmd/frostfs-node/config.go @@ -106,6 +106,7 @@ type applicationConfiguration struct { level string destination string timestamp bool + allowedTags []string } ObjectCfg struct { @@ -230,6 +231,7 @@ func (a *applicationConfiguration) readConfig(c *config.Config) error { a.LoggerCfg.level = loggerconfig.Level(c) a.LoggerCfg.destination = loggerconfig.Destination(c) a.LoggerCfg.timestamp = loggerconfig.Timestamp(c) + a.LoggerCfg.allowedTags = loggerconfig.AllowedTags(c) // Object @@ -1073,6 +1075,7 @@ func (c *cfg) loggerPrm() (logger.Prm, error) { return logger.Prm{}, errors.New("incorrect log destination format: " + c.LoggerCfg.destination) } prm.PrependTimestamp = c.LoggerCfg.timestamp + prm.AllowedTags = c.LoggerCfg.allowedTags return prm, nil } @@ -1374,8 +1377,7 @@ func (c *cfg) getComponents(ctx context.Context) []dCmp { if err != nil { return err } - c.log.Reload(prm) - return nil + return c.log.Reload(prm) }}) components = append(components, dCmp{"runtime", func() error { setRuntimeParameters(ctx, c) diff --git a/cmd/frostfs-node/config/logger/config.go b/cmd/frostfs-node/config/logger/config.go index ba9eeea2b..0d36fecbc 100644 --- a/cmd/frostfs-node/config/logger/config.go +++ b/cmd/frostfs-node/config/logger/config.go @@ -60,6 +60,12 @@ func Timestamp(c *config.Config) bool { return config.BoolSafe(c.Sub(subsection), "timestamp") } +// AllowedTags returns the value of "allowed_tags" config parameter +// from "logger" section. +func AllowedTags(c *config.Config) []string { + return config.StringSliceSafe(c.Sub(subsection), "allowed_tags") +} + // ToLokiConfig extracts loki config. func ToLokiConfig(c *config.Config) loki.Config { hostname, _ := os.Hostname() diff --git a/cmd/frostfs-node/validate.go b/cmd/frostfs-node/validate.go index ae52b9e4a..d722ac6c3 100644 --- a/cmd/frostfs-node/validate.go +++ b/cmd/frostfs-node/validate.go @@ -30,6 +30,11 @@ func validateConfig(c *config.Config) error { return fmt.Errorf("invalid logger destination: %w", err) } + err = logger.ValidateAllowedTags(loggerconfig.AllowedTags(c)) + if err != nil { + return fmt.Errorf("invalid list of allowed tags: %w", err) + } + // shard configuration validation shardNum := 0 diff --git a/pkg/util/logger/allowed_tags.go b/pkg/util/logger/allowed_tags.go new file mode 100644 index 000000000..ea84a4cda --- /dev/null +++ b/pkg/util/logger/allowed_tags.go @@ -0,0 +1,60 @@ +package logger + +import ( + "fmt" + "math" + "strings" + + "go.uber.org/zap/zapcore" +) + +const ( + TagMain uint8 = iota + + tagMain = "main" +) + +// tagToMask return bit mask for the tag, encoded in uint32. +func tagToMask(str string) (uint32, error) { + switch str { + case tagMain: + return 1 << TagMain, nil + default: + return math.MaxUint32, fmt.Errorf("unsupported tag %s", str) + } +} + +// parseAllowedTags returns: +// - parsed allowed tags encoded in uint32, each bit indicates is corresponding tag allowed(1) or not(0); +// - map(always instantiated) of bit mask for the tag to custom log level for that tag; +// - error if it occurred(parsed allowed tags set to math.MaxUint32, map is empty). +func parseAllowedTags(tags []string) (uint32, map[uint32]zapcore.Level, error) { + m := make(map[uint32]zapcore.Level) + if len(tags) == 0 { + return math.MaxUint32, m, nil + } + var v uint32 + for _, str := range tags { + tag, level, _ := strings.Cut(str, ":") + mask, err := tagToMask(tag) + if err != nil { + return 0, nil, err + } + v |= mask + + if len(level) > 0 { + var l zapcore.Level + err = l.UnmarshalText([]byte(level)) + if err != nil { + return 0, nil, err + } + m[mask] = l + } + } + return v, m, nil +} + +func ValidateAllowedTags(tags []string) error { + _, _, err := parseAllowedTags(tags) + return err +} diff --git a/pkg/util/logger/log.go b/pkg/util/logger/log.go index 413b1d9aa..fcd58276d 100644 --- a/pkg/util/logger/log.go +++ b/pkg/util/logger/log.go @@ -6,21 +6,34 @@ import ( "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/tracing" qos "git.frostfs.info/TrueCloudLab/frostfs-qos/tagging" "go.uber.org/zap" + "go.uber.org/zap/zapcore" ) func (l *Logger) Debug(ctx context.Context, msg string, fields ...zap.Field) { + if l.denyLogEntry(zapcore.DebugLevel) { + return + } l.z.Debug(msg, appendContext(ctx, fields...)...) } func (l *Logger) Info(ctx context.Context, msg string, fields ...zap.Field) { + if l.denyLogEntry(zapcore.InfoLevel) { + return + } l.z.Info(msg, appendContext(ctx, fields...)...) } func (l *Logger) Warn(ctx context.Context, msg string, fields ...zap.Field) { + if l.denyLogEntry(zapcore.WarnLevel) { + return + } l.z.Warn(msg, appendContext(ctx, fields...)...) } func (l *Logger) Error(ctx context.Context, msg string, fields ...zap.Field) { + if l.denyLogEntry(zapcore.ErrorLevel) { + return + } l.z.Error(msg, appendContext(ctx, fields...)...) } @@ -33,3 +46,14 @@ func appendContext(ctx context.Context, fields ...zap.Field) []zap.Field { } return fields } + +func (l *Logger) denyLogEntry(level zapcore.Level) bool { + if l.at.Load()&l.m != l.m { + return true + } + tl := l.tl.Load().(map[uint32]zapcore.Level) + if lvl, ok := tl[l.m]; ok { + return level < lvl + } + return level < l.lvl.Level() +} diff --git a/pkg/util/logger/logger.go b/pkg/util/logger/logger.go index 47f9341cb..145b4be75 100644 --- a/pkg/util/logger/logger.go +++ b/pkg/util/logger/logger.go @@ -2,6 +2,8 @@ package logger import ( "fmt" + "math" + "sync/atomic" "git.frostfs.info/TrueCloudLab/zapjournald" "github.com/ssgreg/journald" @@ -14,6 +16,9 @@ import ( type Logger struct { z *zap.Logger lvl zap.AtomicLevel + at *atomic.Uint32 + m uint32 + tl *atomic.Value } // Prm groups Logger's parameters. @@ -35,6 +40,9 @@ type Prm struct { // PrependTimestamp specifies whether to prepend a timestamp in the log PrependTimestamp bool + + // AllowedTags list of allowed tags with log level + AllowedTags []string } const ( @@ -87,10 +95,8 @@ func NewLogger(prm Prm) (*Logger, error) { } func newConsoleLogger(prm Prm) (*Logger, error) { - lvl := zap.NewAtomicLevelAt(prm.level) - c := zap.NewProductionConfig() - c.Level = lvl + c.Level = zap.NewAtomicLevelAt(zapcore.DebugLevel) c.Encoding = "console" if prm.SamplingHook != nil { c.Sampling.Hook = prm.SamplingHook @@ -110,14 +116,23 @@ func newConsoleLogger(prm Prm) (*Logger, error) { return nil, err } - l := &Logger{z: lZap, lvl: lvl} + pat, tl, err := parseAllowedTags(prm.AllowedTags) + if err != nil { + return nil, err + } + var at atomic.Uint32 + at.Store(pat) + + v := atomic.Value{} + v.Store(tl) + + l := &Logger{z: lZap, lvl: zap.NewAtomicLevelAt(prm.level), m: 1 << TagMain, at: &at, tl: &v} return l, nil } func newJournaldLogger(prm Prm) (*Logger, error) { - lvl := zap.NewAtomicLevelAt(prm.level) - + lvl := zap.NewAtomicLevelAt(zapcore.DebugLevel) c := zap.NewProductionConfig() c.Level = lvl c.Encoding = "console" @@ -142,13 +157,32 @@ func newJournaldLogger(prm Prm) (*Logger, error) { lZap := zap.New(coreWithContext, zap.AddStacktrace(zap.NewAtomicLevelAt(zap.FatalLevel)), zap.AddCallerSkip(1)) - l := &Logger{z: lZap, lvl: lvl} + pat, tl, err := parseAllowedTags(prm.AllowedTags) + if err != nil { + return nil, err + } + var at atomic.Uint32 + at.Store(pat) + + v := atomic.Value{} + v.Store(tl) + + l := &Logger{z: lZap, lvl: zap.NewAtomicLevelAt(prm.level), m: 1 << TagMain, at: &at, tl: &v} return l, nil } -func (l *Logger) Reload(prm Prm) { +func (l *Logger) Reload(prm Prm) error { + at, tl, err := parseAllowedTags(prm.AllowedTags) + if err != nil { + return err + } l.lvl.SetLevel(prm.level) + l.at.Store(at) + + l.tl.Store(tl) + + return nil } func (l *Logger) WithOptions(options ...zap.Option) { @@ -156,11 +190,28 @@ func (l *Logger) WithOptions(options ...zap.Option) { } func (l *Logger) With(fields ...zap.Field) *Logger { - return &Logger{z: l.z.With(fields...)} + c := *l + c.z = l.z.With(fields...) + return &c +} + +func (l *Logger) WithTag(bit uint8) *Logger { + c := *l + c.m = uint32(1 << bit) + return &c } func NewLoggerWrapper(z *zap.Logger) *Logger { + at := &atomic.Uint32{} + at.Store(math.MaxUint32) + + tl := &atomic.Value{} + tl.Store(make(map[uint32]zapcore.Level)) + return &Logger{ - z: z.WithOptions(zap.AddCallerSkip(1)), + z: z.WithOptions(zap.AddCallerSkip(1)), + at: at, + tl: tl, + lvl: zap.NewAtomicLevelAt(zapcore.DebugLevel), } }