diff --git a/log/zap/filter_core.go b/log/zap/filter_core.go new file mode 100644 index 0000000000000000000000000000000000000000..1c6099e6baebacfed5d6bc21e844673d42d42534 --- /dev/null +++ b/log/zap/filter_core.go @@ -0,0 +1,84 @@ +package zap + +import ( + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +type FilterFunc func(entry zapcore.Entry, fields []zapcore.Field) bool + +func HasField(field zapcore.Field) FilterFunc { + return func(_ zapcore.Entry, fields []zapcore.Field) bool { + for _, f := range fields { + if f.Equals(field) { + return true + } + } + return false + } +} + +func NotHasField(field zapcore.Field) FilterFunc { + return func(entry zapcore.Entry, fields []zapcore.Field) bool { + return !HasField(field)(entry, fields) + } +} + +type filterCore struct { + zapcore.Core + + filters []FilterFunc + + // fields хранит контекст записей ядра, передаваемых при вызове With. + // В методе Write передаются только поля конкретной записи, но мы также хотим учитывать поля контекста ядра. + fields []zap.Field +} + +// RegisterFilters - добавить фильтры, которые будут применяться при записи лога (вызове `core.Write`) +// Метод `core.Write` будет вызван только в случае, когда результат всех фильтров `true` +// +// Обратить внимание, фильтр не применяется к полям, которые были добавлены в `core` через вызов `core.With` +// до вызова RegisterFilters. Пример: +// +// l, _ := zap.NewDevelopment() +// core := l.Core().With([]zapcore.Field{zap.Int("a", 5)}) +// core = RegisterFilters(core, HasField(zap.Int("a", 5))) +// +// logger := zap.New(core) +// logger.Info("Test log") // НЕ будет записан +// logger.Info("Test log", zap.Int("a", 5)) // будет записан +func RegisterFilters(core zapcore.Core, filters ...FilterFunc) zapcore.Core { + return &filterCore{ + Core: core, + filters: filters, + } +} + +func (core *filterCore) With(fields []zapcore.Field) zapcore.Core { + return &filterCore{ + Core: core.Core.With(fields), + filters: core.filters, + fields: append(core.fields, fields...), + } +} + +func (core *filterCore) Check(entry zapcore.Entry, checkedEntry *zapcore.CheckedEntry) *zapcore.CheckedEntry { + if core.Core.Enabled(entry.Level) { + return checkedEntry.AddCore(entry, core) + } + return checkedEntry +} + +func (core *filterCore) Write(entry zapcore.Entry, fields []zapcore.Field) error { + if len(core.fields) > 0 { + fields = append(core.fields, fields...) + } + + for _, filter := range core.filters { + if !filter(entry, fields) { + return nil + } + } + + return core.Core.Write(entry, fields) +} diff --git a/log/zap/filter_core_test.go b/log/zap/filter_core_test.go new file mode 100644 index 0000000000000000000000000000000000000000..2cf2cf01561ce7ee84a99ed3a420bc6a71026275 --- /dev/null +++ b/log/zap/filter_core_test.go @@ -0,0 +1,48 @@ +package zap + +import ( + "testing" + + "github.com/stretchr/testify/require" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + "go.uber.org/zap/zaptest/observer" +) + +func TestFilterCore_Write(t *testing.T) { + core, logs := observer.New(zapcore.InfoLevel) + core = RegisterFilters(core, HasField(zap.Bool("check", true))) + + err := core.With([]zapcore.Field{zap.Bool("check", true)}).Write(zapcore.Entry{Message: "msg"}, nil) + require.NoError(t, err) + + err = core.Write(zapcore.Entry{Message: "msg"}, []zapcore.Field{zap.Bool("check", true)}) + require.NoError(t, err) + + err = core.Write(zapcore.Entry{Message: "msg"}, nil) + require.NoError(t, err) + + require.Equal(t, 2, logs.Len()) +} + +func TestNotHasField(t *testing.T) { + core, logs := observer.New(zapcore.InfoLevel) + core = RegisterFilters(core, NotHasField(zap.Int("b", 2))) + + err := core.Write(zapcore.Entry{Message: "msg"}, []zapcore.Field{ + zap.Int("a", 1), + zap.Int("b", 2), + }) + require.NoError(t, err) + + err = core.Write(zapcore.Entry{Message: "msg"}, []zapcore.Field{ + zap.Int("a", 1), + zap.Int("b", 3), + }) + require.NoError(t, err) + + err = core.Write(zapcore.Entry{Message: "msg"}, []zapcore.Field{}) + require.NoError(t, err) + + require.Equal(t, 2, logs.Len()) +}