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