feat(logging)_: introduce namespace filtering core

iterates: #6128
This commit is contained in:
Patryk Osmaczko 2024-11-26 21:58:29 +01:00 committed by osmaczko
parent ae121486ff
commit 1b9e8fdafc
3 changed files with 204 additions and 0 deletions

View File

@ -0,0 +1,55 @@
package logutils
import "go.uber.org/zap/zapcore"
type filterFunc func(ent zapcore.Entry) bool
type filteringCore struct {
parent zapcore.Core
levelEnabler zapcore.LevelEnabler
filterFunc filterFunc
}
var (
_ zapcore.Core = (*filteringCore)(nil)
)
func newFilteringCore(core zapcore.Core, levelEnabler zapcore.LevelEnabler, filterFunc filterFunc) *filteringCore {
return &filteringCore{
parent: core,
levelEnabler: levelEnabler,
filterFunc: filterFunc,
}
}
func (core *filteringCore) Enabled(lvl zapcore.Level) bool {
return core.levelEnabler.Enabled(lvl)
}
func (core *filteringCore) With(fields []zapcore.Field) zapcore.Core {
return &filteringCore{
parent: core.parent.With(fields),
levelEnabler: core.levelEnabler,
filterFunc: core.filterFunc,
}
}
func (core *filteringCore) Check(ent zapcore.Entry, ce *zapcore.CheckedEntry) *zapcore.CheckedEntry {
if core.filterFunc(ent) {
return ce.AddCore(ent, core)
}
return ce
}
func (core *filteringCore) Write(ent zapcore.Entry, fields []zapcore.Field) error {
return core.parent.Write(ent, fields)
}
func (core *filteringCore) Sync() error {
return core.parent.Sync()
}
func (core *filteringCore) Parent() zapcore.Core {
return core.parent
}

View File

@ -0,0 +1,36 @@
package logutils
import (
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
type namespaceFilteringCore struct {
*filteringCore
*namespacesTree
}
func newNamespaceFilteringCore(core zapcore.Core) *namespaceFilteringCore {
namespacesTree := newNamespacesTree()
levelEnablerFunc := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
minLvl := namespacesTree.MinLevel()
if minLvl != zapcore.InvalidLevel {
return minLvl.Enabled(lvl)
}
return zapcore.LevelOf(core).Enabled(lvl)
})
filterFunc := func(ent zapcore.Entry) bool {
namespaceLvl := namespacesTree.LevelFor(ent.LoggerName)
if namespaceLvl != zapcore.InvalidLevel {
return namespaceLvl.Enabled(ent.Level)
}
return zapcore.LevelOf(core).Enabled(ent.Level)
}
return &namespaceFilteringCore{
filteringCore: newFilteringCore(core, levelEnablerFunc, filterFunc),
namespacesTree: namespacesTree,
}
}

View File

@ -0,0 +1,113 @@
package logutils
import (
"bytes"
"strconv"
"strings"
"testing"
"github.com/stretchr/testify/require"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
func TestNamespaceFilteringCore(t *testing.T) {
level := zap.NewAtomicLevelAt(zap.InfoLevel)
buffer := bytes.NewBuffer(nil)
core := zapcore.NewCore(
zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig()),
zapcore.AddSync(buffer),
level,
)
filteringCore := newNamespaceFilteringCore(core)
logger := zap.New(filteringCore)
err := filteringCore.Rebuild("namespaceA:error,namespaceA.namespaceB:debug")
require.NoError(t, err)
logger.Info("one") // OK
logger.Debug("two") // not OK
logger.Named("unregistered").Info("three") // OK
logger.Named("unregistered").Debug("four") // not OK
logger.Named("namespaceA").Error("five") // OK
logger.Named("namespaceA").Info("six") // not OK
logger.Named("namespaceA").Named("unregistered").Error("seven") // OK
logger.Named("namespaceA").Named("unregistered").Info("eight") // not OK
logger.Named("namespaceA").Named("namespaceB").Debug("nine") // OK
logger.Named("namespaceA").Named("namespaceB").Named("unregistered").Debug("ten") // OK
require.Contains(t, buffer.String(), "one", "three", "five", "seven", "nine", "ten")
require.NotContains(t, buffer.String(), "two", "four", "six", "eight")
err = filteringCore.Rebuild("") // Remove filtering
require.NoError(t, err)
buffer.Reset()
logger.Info("one") // OK
logger.Named("namespaceA").Named("namespaceB").Debug("two") // not OK
require.Contains(t, buffer.String(), "one")
require.NotContains(t, buffer.String(), "two")
}
func generateNamespaces(n, depth int, level string) (namespaces string, deepestNamespace string) {
namespace := ""
for i := 0; i < n; i++ {
namespace = strconv.Itoa(i)
for d := 0; d < depth; d++ {
namespace += "." + strconv.Itoa(d)
namespaces += namespace + ":" + level + ","
}
}
namespaces = strings.TrimSuffix(namespaces, ",")
deepestNamespace = namespace
return namespaces, deepestNamespace
}
func benchmarkNamespaces(b *testing.B, core zapcore.Core, setupFilter func(namespaces string) error) {
// Creates complex namespaces filter:
// "0.0:info,0.0.1:info,0.0.1.2:info,0.0.1.2.3:info,1.0:info,1.0.1:info,1.0.1.2:info,1.0.1.2.3:info,2.0:info,2.0.1:info,2.0.1.2:info,2.0.1.2.3:info"
const n = 3
const depth = 4
namespaces, deepestNamespace := generateNamespaces(n, depth, zapcore.LevelOf(core).String())
err := setupFilter(namespaces)
if err != nil {
b.Fatal(err)
}
rootLogger := zap.New(core)
deepestLogger := rootLogger.Named(deepestNamespace)
b.ResetTimer()
for i := 0; i < b.N; i++ {
for level := zap.DebugLevel; level <= zap.ErrorLevel; level++ {
rootLogger.Check(level, "Benchmark message").Write(zap.Int("i", i))
deepestLogger.Check(level, "Benchmark message").Write(zap.Int("i", i))
}
}
}
func BenchmarkNamespacesFilteringCore(b *testing.B) {
core := zapcore.NewCore(
zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig()),
zapcore.Lock(zapcore.AddSync(bytes.NewBuffer(nil))),
zap.NewAtomicLevelAt(zap.InfoLevel),
)
filteringCore := newNamespaceFilteringCore(core)
benchmarkNamespaces(b, filteringCore, filteringCore.Rebuild)
}
func BenchmarkNamespacesZapCore(b *testing.B) {
core := zapcore.NewCore(
zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig()),
zapcore.Lock(zapcore.AddSync(bytes.NewBuffer(nil))),
zap.NewAtomicLevelAt(zap.InfoLevel),
)
benchmarkNamespaces(b, core, func(string) error { return nil })
}