[#1619] logger: Filter entries by tags provided in config

Signed-off-by: Anton Nikiforov <an.nikiforov@yadro.com>
This commit is contained in:
Anton Nikiforov 2025-01-28 15:14:45 +03:00
parent c692fc9071
commit fc00efad23
8 changed files with 166 additions and 13 deletions

View file

@ -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
}

View file

@ -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)

View file

@ -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)

View file

@ -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()

View file

@ -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

View file

@ -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
}

View file

@ -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()
}

View file

@ -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),
}
}