diff --git a/log/zap/channel_core.go b/log/zap/channel_core.go new file mode 100644 index 0000000000000000000000000000000000000000..4eda68780e6460cb20201f07dfde4e2f222d9d2e --- /dev/null +++ b/log/zap/channel_core.go @@ -0,0 +1,43 @@ +package zap + +import ( + "strings" + + "go.uber.org/zap/zapcore" +) + +const channelKey = "channel" + +func HasChannel(channel string) FilterFunc { + return func(entry zapcore.Entry, fields []zapcore.Field) bool { + for _, f := range fields { + if f.Key == channelKey && f.Type == zapcore.StringType { + list := strings.Split(f.String, ",") + for _, v := range list { + if v == channel { + return true + } + } + } + } + return false + } +} + +// NewDefaultChannelCore аналогичен NewChannelCore, но также устанавливает переданный канал в качестве канала по умолчанию. +// Это означает, что если поле Channel в записи не указано, запись все равно будет передана в zapcore.Core. +func NewDefaultChannelCore(core zapcore.Core, channel string) zapcore.Core { + return RegisterFilters( + core, + Or( + HasChannel(channel), + Not(HasKey(channelKey)), + ), + ) +} + +// NewChannelCore добавляет к переданному Core фильтрацию записей по каналам. +// Это означает, что если запись содержит поле Channel и значение соответствует переданному каналу, то запись будет передана в zapcore.Core. +func NewChannelCore(core zapcore.Core, channel string) zapcore.Core { + return RegisterFilters(core, HasChannel(channel)) +} diff --git a/log/zap/channel_core_test.go b/log/zap/channel_core_test.go new file mode 100644 index 0000000000000000000000000000000000000000..51fa290785a258dcb9c5faa372afa5ec1c228072 --- /dev/null +++ b/log/zap/channel_core_test.go @@ -0,0 +1,53 @@ +package zap + +import ( + "testing" + + "github.com/stretchr/testify/require" + "go.uber.org/zap/zapcore" + "go.uber.org/zap/zaptest/observer" +) + +func TestNewChannelCore_WriteSingleChannel(t *testing.T) { + core, logs := observer.New(zapcore.InfoLevel) + core = NewChannelCore(core, "test") + + err := core.Write(zapcore.Entry{Message: "msg"}, []zapcore.Field{Channel("test")}) + require.NoError(t, err) + + err = core.Write(zapcore.Entry{Message: "msg"}, []zapcore.Field{Channel("empty")}) // запись не попадет в лог + require.NoError(t, err) + + require.Equal(t, 1, logs.Len()) +} + +func TestNewChannelCore_WriteMultiplyChannels(t *testing.T) { + core, logs := observer.New(zapcore.InfoLevel) + + core = zapcore.NewTee( + NewChannelCore(core, "test1"), + NewChannelCore(core, "test2"), + ) + + err := core.Write(zapcore.Entry{Message: "msg"}, []zapcore.Field{Channel("test1", "test2")}) // запись попадет сразу в 2 core + require.NoError(t, err) + + require.Equal(t, 2, logs.Len()) +} + +func TestNewDefaultChannelCore(t *testing.T) { + core, logs := observer.New(zapcore.InfoLevel) + + core = NewDefaultChannelCore(core, "test1") + + err := core.Write(zapcore.Entry{Message: "msg"}, []zapcore.Field{Channel("test1")}) + require.NoError(t, err) + + err = core.Write(zapcore.Entry{Message: "msg"}, []zapcore.Field{Channel("test2")}) // эта запись не попадет в лог + require.NoError(t, err) + + err = core.Write(zapcore.Entry{Message: "msg"}, []zapcore.Field{}) + require.NoError(t, err) + + require.Equal(t, 2, logs.Len()) +} diff --git a/log/zap/field.go b/log/zap/field.go index acc6932d5fab8b2975d09998557e6ed41197010d..b439f51b05736b77ff0a991e9b828a2116cfa0e3 100644 --- a/log/zap/field.go +++ b/log/zap/field.go @@ -2,6 +2,7 @@ package zap import ( "context" + "strings" "git.perx.ru/perxis/perxis-go/id" _ "git.perx.ru/perxis/perxis-go/id/system" // регистрируем обработчики для системных объектов @@ -9,6 +10,13 @@ import ( "go.uber.org/zap" ) +func Channel(channels ...string) zap.Field { + if len(channels) == 0 { + return zap.Skip() + } + return zap.String(channelKey, strings.Join(channels, ",")) +} + func Category(category string) zap.Field { if category == "" { return zap.Skip() diff --git a/log/zap/filter_core.go b/log/zap/filter_core.go index 1c6099e6baebacfed5d6bc21e844673d42d42534..f60ba37d7c95b153858e8f2f2c95a3ee68e5f829 100644 --- a/log/zap/filter_core.go +++ b/log/zap/filter_core.go @@ -24,6 +24,34 @@ func NotHasField(field zapcore.Field) FilterFunc { } } +func HasKey(key string) FilterFunc { + return func(entry zapcore.Entry, fields []zapcore.Field) bool { + for _, f := range fields { + if f.Key == key { + return true + } + } + return false + } +} + +func Or(filters ...FilterFunc) FilterFunc { + return func(entry zapcore.Entry, fields []zapcore.Field) bool { + for _, f := range filters { + if f(entry, fields) { + return true + } + } + return false + } +} + +func Not(filter FilterFunc) FilterFunc { + return func(entry zapcore.Entry, fields []zapcore.Field) bool { + return !filter(entry, fields) + } +} + type filterCore struct { zapcore.Core