diff --git a/zapcore/core.go b/zapcore/core.go index 776e93f6f..ea7b8ae30 100644 --- a/zapcore/core.go +++ b/zapcore/core.go @@ -42,6 +42,8 @@ type Core interface { Write(Entry, []Field) error // Sync flushes buffered logs (if any). Sync() error + // Fields returns any added structured context from the Core. + Fields() []Field } type nopCore struct{} @@ -50,6 +52,7 @@ type nopCore struct{} func NewNopCore() Core { return nopCore{} } func (nopCore) Enabled(Level) bool { return false } func (n nopCore) With([]Field) Core { return n } +func (n nopCore) Fields() []Field { return nil } func (nopCore) Check(_ Entry, ce *CheckedEntry) *CheckedEntry { return ce } func (nopCore) Write(Entry, []Field) error { return nil } func (nopCore) Sync() error { return nil } @@ -65,8 +68,9 @@ func NewCore(enc Encoder, ws WriteSyncer, enab LevelEnabler) Core { type ioCore struct { LevelEnabler - enc Encoder - out WriteSyncer + enc Encoder + out WriteSyncer + fields []Field } var ( @@ -80,10 +84,15 @@ func (c *ioCore) Level() Level { func (c *ioCore) With(fields []Field) Core { clone := c.clone() + clone.fields = append(clone.fields, fields...) addFields(clone.enc, fields) return clone } +func (c *ioCore) Fields() []Field { + return c.fields +} + func (c *ioCore) Check(ent Entry, ce *CheckedEntry) *CheckedEntry { if c.Enabled(ent.Level) { return ce.AddCore(ent, c) @@ -118,5 +127,6 @@ func (c *ioCore) clone() *ioCore { LevelEnabler: c.LevelEnabler, enc: c.enc.Clone(), out: c.out, + fields: c.fields, } } diff --git a/zapcore/core_test.go b/zapcore/core_test.go index 3b23d2dea..2f7389217 100644 --- a/zapcore/core_test.go +++ b/zapcore/core_test.go @@ -164,3 +164,21 @@ func TestIOCoreWriteFailure(t *testing.T) { // Should log the error. assert.Error(t, err, "Expected writing Entry to fail.") } + +func TestIOCoreFields(t *testing.T) { + fields := []Field{makeInt64Field("k", 1)} + + core := NewCore( + NewJSONEncoder(testEncoderConfig()), + Lock(os.Stderr), + DebugLevel, + ).With(fields) + + expectedFields := core.Fields() + assert.Len(t, expectedFields, 1, "Expected one field.") + assert.Equal(t, fields, expectedFields, "Unexpected Fields.") + + core = core.With([]Field{makeInt64Field("w", 2)}) + expectedFields = core.Fields() + assert.Len(t, expectedFields, 2, "Expected two fields.") +} diff --git a/zapcore/increase_level.go b/zapcore/increase_level.go index 7a11237ae..23ffd365a 100644 --- a/zapcore/increase_level.go +++ b/zapcore/increase_level.go @@ -58,6 +58,10 @@ func (c *levelFilterCore) With(fields []Field) Core { return &levelFilterCore{c.core.With(fields), c.level} } +func (c *levelFilterCore) Fields() []Field { + return c.core.Fields() +} + func (c *levelFilterCore) Check(ent Entry, ce *CheckedEntry) *CheckedEntry { if !c.Enabled(ent.Level) { return ce diff --git a/zapcore/sampler_test.go b/zapcore/sampler_test.go index 55b4afabf..fb72be8e1 100644 --- a/zapcore/sampler_test.go +++ b/zapcore/sampler_test.go @@ -164,6 +164,7 @@ func (c *countingCore) Write(Entry, []Field) error { } func (c *countingCore) With([]Field) Core { return c } +func (c *countingCore) Fields() []Field { return nil } func (*countingCore) Enabled(Level) bool { return true } func (*countingCore) Sync() error { return nil } diff --git a/zapcore/tee.go b/zapcore/tee.go index 9bb32f055..fa4527a9a 100644 --- a/zapcore/tee.go +++ b/zapcore/tee.go @@ -53,6 +53,16 @@ func (mc multiCore) With(fields []Field) Core { return clone } +func (mc multiCore) Fields() []Field { + var fields []Field + + if len(mc) == 0 { + return fields + } + + return mc[0].Fields() +} + func (mc multiCore) Level() Level { minLvl := _maxLevel // mc is never empty for i := range mc { diff --git a/zapcore/tee_test.go b/zapcore/tee_test.go index f6b14eb44..b9048bf23 100644 --- a/zapcore/tee_test.go +++ b/zapcore/tee_test.go @@ -150,6 +150,17 @@ func TestTeeWith(t *testing.T) { }) } +func TestTeeFields(t *testing.T) { + withTee(func(tee Core, debugLogs, warnLogs *observer.ObservedLogs) { + fields := []Field{makeInt64Field("k", 42)} + tee = tee.With(fields) + + expectedFields := tee.Fields() + assert.Greater(t, len(expectedFields), 0, "Expected non-empty fields.") + assert.Equal(t, fields, expectedFields, "Unexpected fields.") + }) +} + func TestTeeEnabled(t *testing.T) { infoLogger, _ := observer.New(InfoLevel) warnLogger, _ := observer.New(WarnLevel) diff --git a/zaptest/observer/observer.go b/zaptest/observer/observer.go index f77f1308b..b90bbabb2 100644 --- a/zaptest/observer/observer.go +++ b/zaptest/observer/observer.go @@ -183,6 +183,10 @@ func (co *contextObserver) With(fields []zapcore.Field) zapcore.Core { } } +func (co *contextObserver) Fields() []zapcore.Field { + return co.context +} + func (co *contextObserver) Write(ent zapcore.Entry, fields []zapcore.Field) error { all := make([]zapcore.Field, 0, len(fields)+len(co.context)) all = append(all, co.context...)