From 045b966ecb7fd706e41c6c1b5429e55dbb715486 Mon Sep 17 00:00:00 2001 From: Alex Vanin Date: Tue, 23 Jan 2024 19:35:40 +0300 Subject: [PATCH] [#15] Add partial encoder Partial encoder is an encoder wrapper which ignores specific fields. This is useful with zapjournald core to ignore syslog fields in human-readable console output. Encoder adds almost none overhead and even improves logging speed for entities with syslog fields. Signed-off-by: Alex Vanin --- partial_encoder.go | 214 ++++++++++++++++++++++++++++++++++ partial_encoder_bench_test.go | 63 ++++++++++ 2 files changed, 277 insertions(+) create mode 100644 partial_encoder.go create mode 100644 partial_encoder_bench_test.go diff --git a/partial_encoder.go b/partial_encoder.go new file mode 100644 index 0000000..a712cf4 --- /dev/null +++ b/partial_encoder.go @@ -0,0 +1,214 @@ +package zapjournald + +import ( + "time" + + "go.uber.org/zap/buffer" + "go.uber.org/zap/zapcore" + "golang.org/x/exp/maps" +) + +type partialEncoder struct { + wrap zapcore.Encoder + ignore map[string]struct{} +} + +// NewPartialEncoder wraps existing encoder to avoid output of some provided +// fields. The main use case is to ignore SyslogFields that leak into +// ConsoleEncoder and provide no additional info for the human. +func NewPartialEncoder(enc zapcore.Encoder, ignore []string) zapcore.Encoder { + m := make(map[string]struct{}, len(ignore)) + for _, i := range ignore { + m[i] = struct{}{} + } + return partialEncoder{ + wrap: enc, + ignore: m, + } +} + +func (enc partialEncoder) AddArray(key string, marshaler zapcore.ArrayMarshaler) error { + if _, ok := enc.ignore[key]; ok { + return nil + } + return enc.wrap.AddArray(key, marshaler) +} + +func (enc partialEncoder) AddObject(key string, marshaler zapcore.ObjectMarshaler) error { + if _, ok := enc.ignore[key]; ok { + return nil + } + return enc.wrap.AddObject(key, marshaler) +} + +func (enc partialEncoder) AddBinary(key string, value []byte) { + if _, ok := enc.ignore[key]; ok { + return + } + enc.wrap.AddBinary(key, value) +} + +func (enc partialEncoder) AddByteString(key string, value []byte) { + if _, ok := enc.ignore[key]; ok { + return + } + enc.wrap.AddByteString(key, value) +} + +func (enc partialEncoder) AddBool(key string, value bool) { + if _, ok := enc.ignore[key]; ok { + return + } + enc.wrap.AddBool(key, value) +} + +func (enc partialEncoder) AddComplex128(key string, value complex128) { + if _, ok := enc.ignore[key]; ok { + return + } + enc.wrap.AddComplex128(key, value) +} + +func (enc partialEncoder) AddComplex64(key string, value complex64) { + if _, ok := enc.ignore[key]; ok { + return + } + enc.wrap.AddComplex64(key, value) +} + +func (enc partialEncoder) AddDuration(key string, value time.Duration) { + if _, ok := enc.ignore[key]; ok { + return + } + enc.wrap.AddDuration(key, value) +} + +func (enc partialEncoder) AddFloat64(key string, value float64) { + if _, ok := enc.ignore[key]; ok { + return + } + enc.wrap.AddFloat64(key, value) +} + +func (enc partialEncoder) AddFloat32(key string, value float32) { + if _, ok := enc.ignore[key]; ok { + return + } + enc.wrap.AddFloat32(key, value) +} + +func (enc partialEncoder) AddInt(key string, value int) { + if _, ok := enc.ignore[key]; ok { + return + } + enc.wrap.AddInt(key, value) +} + +func (enc partialEncoder) AddInt64(key string, value int64) { + if _, ok := enc.ignore[key]; ok { + return + } + enc.wrap.AddInt64(key, value) +} + +func (enc partialEncoder) AddInt32(key string, value int32) { + if _, ok := enc.ignore[key]; ok { + return + } + enc.wrap.AddInt32(key, value) +} + +func (enc partialEncoder) AddInt16(key string, value int16) { + if _, ok := enc.ignore[key]; ok { + return + } + enc.wrap.AddInt16(key, value) +} + +func (enc partialEncoder) AddInt8(key string, value int8) { + if _, ok := enc.ignore[key]; ok { + return + } + enc.wrap.AddInt8(key, value) +} + +func (enc partialEncoder) AddString(key, value string) { + if _, ok := enc.ignore[key]; ok { + return + } + enc.wrap.AddString(key, value) +} + +func (enc partialEncoder) AddTime(key string, value time.Time) { + if _, ok := enc.ignore[key]; ok { + return + } + enc.wrap.AddTime(key, value) +} + +func (enc partialEncoder) AddUint(key string, value uint) { + if _, ok := enc.ignore[key]; ok { + return + } + enc.wrap.AddUint(key, value) +} + +func (enc partialEncoder) AddUint64(key string, value uint64) { + if _, ok := enc.ignore[key]; ok { + return + } + enc.wrap.AddUint64(key, value) +} + +func (enc partialEncoder) AddUint32(key string, value uint32) { + if _, ok := enc.ignore[key]; ok { + return + } + enc.wrap.AddUint32(key, value) +} + +func (enc partialEncoder) AddUint16(key string, value uint16) { + if _, ok := enc.ignore[key]; ok { + return + } + enc.wrap.AddUint16(key, value) +} + +func (enc partialEncoder) AddUint8(key string, value uint8) { + if _, ok := enc.ignore[key]; ok { + return + } + enc.wrap.AddUint8(key, value) +} + +func (enc partialEncoder) AddUintptr(key string, value uintptr) { + if _, ok := enc.ignore[key]; ok { + return + } + enc.wrap.AddUintptr(key, value) +} + +func (enc partialEncoder) AddReflected(key string, value interface{}) error { + if _, ok := enc.ignore[key]; ok { + return nil + } + return enc.wrap.AddReflected(key, value) +} + +func (enc partialEncoder) OpenNamespace(key string) { + if _, ok := enc.ignore[key]; ok { + return + } + enc.wrap.OpenNamespace(key) +} + +func (enc partialEncoder) Clone() zapcore.Encoder { + return partialEncoder{ + wrap: enc.wrap.Clone(), + ignore: maps.Clone(enc.ignore), + } +} + +func (enc partialEncoder) EncodeEntry(entry zapcore.Entry, fields []zapcore.Field) (*buffer.Buffer, error) { + return enc.wrap.EncodeEntry(entry, fields) +} diff --git a/partial_encoder_bench_test.go b/partial_encoder_bench_test.go new file mode 100644 index 0000000..480189b --- /dev/null +++ b/partial_encoder_bench_test.go @@ -0,0 +1,63 @@ +package zapjournald + +import ( + "testing" + + "github.com/ssgreg/journald" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +func BenchmarkEncoder(b *testing.B) { + zc := zap.NewProductionConfig() + zc.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder + zc.Level = zap.NewAtomicLevelAt(zap.InfoLevel) + + b.Run("console", func(b *testing.B) { + encoder := zapcore.NewConsoleEncoder(zc.EncoderConfig) + core := NewCore(zc.Level, encoder, &journald.Journal{}, SyslogFields) + core.j.TestModeEnabled = true // Disable actual writing to the journal. + + coreWithContext := core.With([]zapcore.Field{ + SyslogFacility(LogDaemon), + SyslogIdentifier(), + SyslogPid(), + }) + l := zap.New(coreWithContext) + benchmarkLog(b, l) + }) + b.Run("partial", func(b *testing.B) { + encoder := NewPartialEncoder(zapcore.NewConsoleEncoder(zc.EncoderConfig), SyslogFields) + core := NewCore(zc.Level, encoder, &journald.Journal{}, SyslogFields) + core.j.TestModeEnabled = true // Disable actual writing to the journal. + + coreWithContext := core.With([]zapcore.Field{ + SyslogFacility(LogDaemon), + SyslogIdentifier(), + SyslogPid(), + }) + l := zap.New(coreWithContext) + benchmarkLog(b, l) + }) +} + +func benchmarkEncoder(b *testing.B, l *zap.Logger) { + b.Run("no fields", func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + l.Info("Simple log message") + } + }) + b.Run("application fields", func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + l.Info("Simple log message", zap.Uint32("count", 123), zap.String("details", "nothing")) + } + }) + b.Run("journald fields", func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + l.Info("Simple log message", SyslogIdentifier()) + } + }) +}