From b6b7b16b16262fe7337944a60d673455cff48ace Mon Sep 17 00:00:00 2001 From: Vladimir Skipor Date: Sun, 19 Feb 2017 20:45:32 +0300 Subject: [PATCH] CallerEncoder (#319) CallerEncoder similar to other encoders for complex types. It allows to configure caller presentation in log. --- config.go | 2 ++ zapcore/encoder.go | 30 ++++++++++++++++++++++++++---- zapcore/encoder_test.go | 37 +++++++++++++++++++++++++++++++++++++ zapcore/json_encoder.go | 3 ++- 4 files changed, 67 insertions(+), 5 deletions(-) diff --git a/config.go b/config.go index 3231caf3c..0a63e99dc 100644 --- a/config.go +++ b/config.go @@ -101,6 +101,7 @@ func NewProductionConfig() Config { EncodeLevel: zapcore.LowercaseLevelEncoder, EncodeTime: zapcore.EpochTimeEncoder, EncodeDuration: zapcore.SecondsDurationEncoder, + EncodeCaller: zapcore.FullPathCallerEncoder, }, OutputPaths: []string{"stderr"}, ErrorOutputPaths: []string{"stderr"}, @@ -132,6 +133,7 @@ func NewDevelopmentConfig() Config { EncodeLevel: zapcore.CapitalLevelEncoder, EncodeTime: zapcore.ISO8601TimeEncoder, EncodeDuration: zapcore.StringDurationEncoder, + EncodeCaller: zapcore.FullPathCallerEncoder, }, OutputPaths: []string{"stderr"}, ErrorOutputPaths: []string{"stderr"}, diff --git a/zapcore/encoder.go b/zapcore/encoder.go index 86cbd194d..ffaef94b4 100644 --- a/zapcore/encoder.go +++ b/zapcore/encoder.go @@ -137,14 +137,35 @@ func (e *DurationEncoder) UnmarshalText(text []byte) error { return nil } +// A CallerEncoder serializes a EntryCaller to a primitive type. +type CallerEncoder func(EntryCaller, PrimitiveArrayEncoder) + +func FullPathCallerEncoder(caller EntryCaller, enc PrimitiveArrayEncoder) { + // OPTIMIZE: after adding AppendBytes to PrimitiveArrayEncoder just copy bytes + // from buffer to not allocate string. + enc.AppendString(caller.String()) +} + +// UnmarshalText unmarshals text to a CallerEncoder. +// Anything is unmarshaled to FullPathCallerEncoder at that moment. +func (e *CallerEncoder) UnmarshalText(text []byte) error { + switch string(text) { + //case "gopath": // TODO + default: + *e = FullPathCallerEncoder + } + return nil +} + // An EncoderConfig allows users to configure the concrete encoders supplied by // zapcore. type EncoderConfig struct { // Set the keys used for each log entry. - MessageKey string `json:"messageKey" yaml:"messageKey"` - LevelKey string `json:"levelKey" yaml:"levelKey"` - TimeKey string `json:"timeKey" yaml:"timeKey"` - NameKey string `json:"nameKey" yaml:"nameKey"` + MessageKey string `json:"messageKey" yaml:"messageKey"` + LevelKey string `json:"levelKey" yaml:"levelKey"` + TimeKey string `json:"timeKey" yaml:"timeKey"` + NameKey string `json:"nameKey" yaml:"nameKey"` + // CallerKey sets key for caller. If empty, caller is not logged. CallerKey string `json:"callerKey" yaml:"callerKey"` StacktraceKey string `json:"stacktraceKey" yaml:"stacktraceKey"` // Configure the primitive representations of common complex types. For @@ -153,6 +174,7 @@ type EncoderConfig struct { EncodeLevel LevelEncoder `json:"levelEncoder" yaml:"levelEncoder"` EncodeTime TimeEncoder `json:"timeEncoder" yaml:"timeEncoder"` EncodeDuration DurationEncoder `json:"durationEncoder" yaml:"durationEncoder"` + EncodeCaller CallerEncoder `json:"callerEncoder" yaml:"callerEncoder"` } // ObjectEncoder is a strongly-typed, encoding-agnostic interface for adding a diff --git a/zapcore/encoder_test.go b/zapcore/encoder_test.go index 3f802d78c..5c8c320b9 100644 --- a/zapcore/encoder_test.go +++ b/zapcore/encoder_test.go @@ -53,6 +53,7 @@ func testEncoderConfig() EncoderConfig { EncodeTime: EpochTimeEncoder, EncodeLevel: LowercaseLevelEncoder, EncodeDuration: SecondsDurationEncoder, + EncodeCaller: FullPathCallerEncoder, } } @@ -105,6 +106,7 @@ func TestEncoderConfiguration(t *testing.T) { EncodeTime: base.EncodeTime, EncodeDuration: base.EncodeDuration, EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, }, expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","M":"hello","S":"fake-stack"}`, expectedConsole: "0\tinfo\tmain@foo.go:42\thello\nfake-stack", @@ -121,6 +123,7 @@ func TestEncoderConfiguration(t *testing.T) { EncodeTime: base.EncodeTime, EncodeDuration: base.EncodeDuration, EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, }, expectedJSON: `{"T":0,"N":"main","C":"foo.go:42","M":"hello","S":"fake-stack"}`, expectedConsole: "0\tmain@foo.go:42\thello\nfake-stack", @@ -137,6 +140,7 @@ func TestEncoderConfiguration(t *testing.T) { EncodeTime: base.EncodeTime, EncodeDuration: base.EncodeDuration, EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, }, expectedJSON: `{"L":"info","N":"main","C":"foo.go:42","M":"hello","S":"fake-stack"}`, expectedConsole: "info\tmain@foo.go:42\thello\nfake-stack", @@ -153,6 +157,7 @@ func TestEncoderConfiguration(t *testing.T) { EncodeTime: base.EncodeTime, EncodeDuration: base.EncodeDuration, EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, }, expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","S":"fake-stack"}`, expectedConsole: "0\tinfo\tmain@foo.go:42\nfake-stack", @@ -169,6 +174,7 @@ func TestEncoderConfiguration(t *testing.T) { EncodeTime: base.EncodeTime, EncodeDuration: base.EncodeDuration, EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, }, expectedJSON: `{"L":"info","T":0,"C":"foo.go:42","M":"hello","S":"fake-stack"}`, expectedConsole: "0\tinfo\tfoo.go:42\thello\nfake-stack", @@ -185,6 +191,7 @@ func TestEncoderConfiguration(t *testing.T) { EncodeTime: base.EncodeTime, EncodeDuration: base.EncodeDuration, EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, }, expectedJSON: `{"L":"info","T":0,"N":"main","M":"hello","S":"fake-stack"}`, expectedConsole: "0\tinfo\tmain\thello\nfake-stack", @@ -201,6 +208,7 @@ func TestEncoderConfiguration(t *testing.T) { EncodeTime: base.EncodeTime, EncodeDuration: base.EncodeDuration, EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, }, expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","M":"hello"}`, expectedConsole: "0\tinfo\tmain@foo.go:42\thello", @@ -217,6 +225,7 @@ func TestEncoderConfiguration(t *testing.T) { EncodeTime: func(t time.Time, enc PrimitiveArrayEncoder) { enc.AppendString(t.String()) }, EncodeDuration: base.EncodeDuration, EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, }, extra: func(enc Encoder) { enc.AddTime("extra", _epoch) @@ -242,6 +251,7 @@ func TestEncoderConfiguration(t *testing.T) { EncodeTime: base.EncodeTime, EncodeDuration: StringDurationEncoder, EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, }, extra: func(enc Encoder) { enc.AddDuration("extra", time.Second) @@ -267,6 +277,7 @@ func TestEncoderConfiguration(t *testing.T) { EncodeTime: base.EncodeTime, EncodeDuration: base.EncodeDuration, EncodeLevel: CapitalLevelEncoder, + EncodeCaller: base.EncodeCaller, }, expectedJSON: `{"L":"INFO","T":0,"N":"main","C":"foo.go:42","M":"hello","S":"fake-stack"}`, expectedConsole: "0\tINFO\tmain@foo.go:42\thello\nfake-stack", @@ -283,6 +294,7 @@ func TestEncoderConfiguration(t *testing.T) { EncodeTime: base.EncodeTime, EncodeDuration: base.EncodeDuration, EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, }, extra: func(enc Encoder) { enc.OpenNamespace("outer") @@ -307,6 +319,7 @@ func TestEncoderConfiguration(t *testing.T) { EncodeTime: func(time.Time, PrimitiveArrayEncoder) {}, EncodeDuration: base.EncodeDuration, EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, }, extra: func(enc Encoder) { enc.AddTime("sometime", time.Unix(0, 100)) }, expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","M":"hello","sometime":100,"S":"fake-stack"}`, @@ -324,6 +337,7 @@ func TestEncoderConfiguration(t *testing.T) { EncodeTime: base.EncodeTime, EncodeDuration: func(time.Duration, PrimitiveArrayEncoder) {}, EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, }, extra: func(enc Encoder) { enc.AddDuration("someduration", time.Microsecond) }, expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","M":"hello","someduration":1000,"S":"fake-stack"}`, @@ -341,6 +355,7 @@ func TestEncoderConfiguration(t *testing.T) { EncodeTime: base.EncodeTime, EncodeDuration: base.EncodeDuration, EncodeLevel: func(Level, PrimitiveArrayEncoder) {}, + EncodeCaller: base.EncodeCaller, }, expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","M":"hello","S":"fake-stack"}`, expectedConsole: "0\tmain@foo.go:42\thello\nfake-stack", @@ -452,6 +467,28 @@ func TestDurationEncoders(t *testing.T) { } } +func TestCallerEncoders(t *testing.T) { + caller := _testEntry.Caller + tests := []struct { + name string + expected interface{} // output of serializing caller + }{ + {"", "foo.go:42"}, + {"something-random", "foo.go:42"}, + } + + for _, tt := range tests { + var ce CallerEncoder + require.NoError(t, ce.UnmarshalText([]byte(tt.name)), "Unexpected error unmarshaling %q.", tt.name) + assertAppended( + t, + tt.expected, + func(arr ArrayEncoder) { ce(caller, arr) }, + "Unexpected output serializing InfoLevel with %q.", tt.name, + ) + } +} + func assertAppended(t testing.TB, expected interface{}, f func(ArrayEncoder), msgAndArgs ...interface{}) { mem := NewMapObjectEncoder() mem.AddArray("k", ArrayMarshalerFunc(func(arr ArrayEncoder) error { diff --git a/zapcore/json_encoder.go b/zapcore/json_encoder.go index 1dfc6a968..a9972a540 100644 --- a/zapcore/json_encoder.go +++ b/zapcore/json_encoder.go @@ -299,7 +299,8 @@ func (enc *jsonEncoder) EncodeEntry(ent Entry, fields []Field) (*buffer.Buffer, if ent.Caller.Defined && final.CallerKey != "" { // NOTE: we add the field here for parity compromise with text // prepending, while not actually mutating the message string. - final.AddString(final.CallerKey, ent.Caller.String()) + final.addKey(final.CallerKey) + final.EncodeCaller(ent.Caller, final) } if final.MessageKey != "" { final.addKey(enc.MessageKey)