feat(logging)_: introduce custom zap.Core enabling runtime changes
Geth logger allows overriding the log level, format and writer at runtime. To make it interchangeable with zap.Logger, a custom zap.Core has been introduced to enable these features as well. closes: #6023
This commit is contained in:
parent
11cf42bedd
commit
f596a4dabf
|
@ -0,0 +1,118 @@
|
|||
package logutils
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
)
|
||||
|
||||
// encoderWrapper holds any zapcore.Encoder and ensures a consistent type for atomic.Value
|
||||
type encoderWrapper struct {
|
||||
zapcore.Encoder
|
||||
}
|
||||
|
||||
// writeSyncerWrapper holds any zapcore.WriteSyncer and ensures a consistent type for atomic.Value
|
||||
type writeSyncerWrapper struct {
|
||||
zapcore.WriteSyncer
|
||||
}
|
||||
|
||||
// Core wraps a zapcore.Core that can update its syncer and encoder at runtime
|
||||
type Core struct {
|
||||
encoder atomic.Value // encoderWrapper
|
||||
syncer *atomic.Value // writeSyncerWrapper
|
||||
level zap.AtomicLevel
|
||||
|
||||
next *Core
|
||||
nextFields []zapcore.Field
|
||||
}
|
||||
|
||||
var (
|
||||
_ zapcore.Core = (*Core)(nil)
|
||||
)
|
||||
|
||||
func NewCore(encoder zapcore.Encoder, syncer zapcore.WriteSyncer, atomicLevel zap.AtomicLevel) *Core {
|
||||
core := &Core{
|
||||
syncer: &atomic.Value{},
|
||||
level: atomicLevel,
|
||||
}
|
||||
core.encoder.Store(encoderWrapper{Encoder: encoder})
|
||||
core.syncer.Store(writeSyncerWrapper{WriteSyncer: syncer})
|
||||
return core
|
||||
}
|
||||
|
||||
func (core *Core) getEncoder() zapcore.Encoder {
|
||||
return core.encoder.Load().(zapcore.Encoder)
|
||||
}
|
||||
|
||||
func (core *Core) getSyncer() zapcore.WriteSyncer {
|
||||
return core.syncer.Load().(zapcore.WriteSyncer)
|
||||
}
|
||||
|
||||
func (core *Core) Enabled(lvl zapcore.Level) bool {
|
||||
return core.level.Enabled(lvl)
|
||||
}
|
||||
|
||||
func (core *Core) With(fields []zapcore.Field) zapcore.Core {
|
||||
clonedEncoder := encoderWrapper{Encoder: core.getEncoder().Clone()}
|
||||
for i := range fields {
|
||||
fields[i].AddTo(clonedEncoder)
|
||||
}
|
||||
|
||||
clone := *core
|
||||
clone.encoder.Store(clonedEncoder)
|
||||
|
||||
core.next = &clone
|
||||
core.nextFields = fields
|
||||
|
||||
return &clone
|
||||
}
|
||||
|
||||
func (core *Core) Check(ent zapcore.Entry, ce *zapcore.CheckedEntry) *zapcore.CheckedEntry {
|
||||
if core.Enabled(ent.Level) {
|
||||
return ce.AddCore(ent, core)
|
||||
}
|
||||
return ce
|
||||
}
|
||||
|
||||
func (core *Core) Write(ent zapcore.Entry, fields []zapcore.Field) error {
|
||||
buf, err := core.getEncoder().EncodeEntry(ent, fields)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = core.getSyncer().Write(buf.Bytes())
|
||||
buf.Free()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if ent.Level > zapcore.ErrorLevel {
|
||||
// Since we may be crashing the program, sync the output.
|
||||
_ = core.Sync()
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (core *Core) Sync() error {
|
||||
return core.getSyncer().Sync()
|
||||
}
|
||||
|
||||
func (core *Core) UpdateSyncer(newSyncer zapcore.WriteSyncer) {
|
||||
core.syncer.Store(writeSyncerWrapper{WriteSyncer: newSyncer})
|
||||
}
|
||||
|
||||
func (core *Core) UpdateEncoder(newEncoder zapcore.Encoder) {
|
||||
core.encoder.Store(encoderWrapper{Encoder: newEncoder})
|
||||
|
||||
// Update next Cores with newEncoder
|
||||
current := core
|
||||
for current.next != nil {
|
||||
clonedEncoder := encoderWrapper{Encoder: core.getEncoder().Clone()}
|
||||
for i := range core.nextFields {
|
||||
current.nextFields[i].AddTo(clonedEncoder)
|
||||
}
|
||||
current.next.encoder.Store(clonedEncoder)
|
||||
current = current.next
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
package logutils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
)
|
||||
|
||||
func TestCore(t *testing.T) {
|
||||
level := zap.NewAtomicLevelAt(zap.DebugLevel)
|
||||
|
||||
buffer1 := bytes.NewBuffer(nil)
|
||||
buffer2 := bytes.NewBuffer(nil)
|
||||
|
||||
core := NewCore(
|
||||
zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig()),
|
||||
zapcore.AddSync(buffer1),
|
||||
level,
|
||||
)
|
||||
|
||||
parent := zap.New(core)
|
||||
child := parent.Named("child")
|
||||
childWithContext := child.With(zap.String("key1", "value1"))
|
||||
childWithMoreContext := childWithContext.With(zap.String("key2", "value2"))
|
||||
grandChild := childWithMoreContext.Named("grandChild")
|
||||
|
||||
parent.Debug("Status")
|
||||
child.Debug("Super")
|
||||
childWithContext.Debug("App")
|
||||
childWithMoreContext.Debug("The")
|
||||
grandChild.Debug("Best")
|
||||
|
||||
core.UpdateSyncer(zapcore.AddSync(buffer2))
|
||||
core.UpdateEncoder(zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()))
|
||||
|
||||
parent.Debug("Status")
|
||||
child.Debug("Super")
|
||||
childWithContext.Debug("App")
|
||||
childWithMoreContext.Debug("The")
|
||||
grandChild.Debug("Best")
|
||||
|
||||
fmt.Println(buffer1.String())
|
||||
fmt.Println(buffer2.String())
|
||||
|
||||
// Ensure that the first buffer has the console encoder output
|
||||
buffer1Lines := strings.Split(buffer1.String(), "\n")
|
||||
require.Len(t, buffer1Lines, 5+1)
|
||||
require.Regexp(t, `\s+child\s+`, buffer1Lines[1])
|
||||
require.Regexp(t, `\s+child\.grandChild\s+`, buffer1Lines[4])
|
||||
|
||||
// Ensure that the second buffer has the JSON encoder output
|
||||
buffer2Lines := strings.Split(buffer2.String(), "\n")
|
||||
require.Len(t, buffer2Lines, 5+1)
|
||||
require.Regexp(t, `"logger"\s*:\s*"child"`, buffer2Lines[1])
|
||||
require.Regexp(t, `"logger"\s*:\s*"child\.grandChild"`, buffer2Lines[4])
|
||||
}
|
Loading…
Reference in New Issue