Skip to content

Commit

Permalink
CallerEncoder (#319)
Browse files Browse the repository at this point in the history
CallerEncoder similar to other encoders for complex types. It allows to configure caller presentation in log.
  • Loading branch information
skipor authored and Akshay Shah committed Mar 13, 2017
1 parent 6314854 commit 5bb2590
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 5 deletions.
2 changes: 2 additions & 0 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ func NewProductionEncoderConfig() zapcore.EncoderConfig {
EncodeLevel: zapcore.LowercaseLevelEncoder,
EncodeTime: zapcore.EpochTimeEncoder,
EncodeDuration: zapcore.SecondsDurationEncoder,
EncodeCaller: zapcore.FullPathCallerEncoder,
}
}

Expand Down Expand Up @@ -126,6 +127,7 @@ func NewDevelopmentEncoderConfig() zapcore.EncoderConfig {
EncodeLevel: zapcore.CapitalLevelEncoder,
EncodeTime: zapcore.ISO8601TimeEncoder,
EncodeDuration: zapcore.StringDurationEncoder,
EncodeCaller: zapcore.FullPathCallerEncoder,
}
}

Expand Down
31 changes: 27 additions & 4 deletions zapcore/encoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,14 +162,36 @@ func (e *DurationEncoder) UnmarshalText(text []byte) error {
return nil
}

// A CallerEncoder serializes a EntryCaller to a primitive type.
type CallerEncoder func(EntryCaller, PrimitiveArrayEncoder)

// FullPathCallerEncoder serializes caller in /full/path/file:line format.
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
Expand All @@ -178,6 +200,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
Expand Down
37 changes: 37 additions & 0 deletions zapcore/encoder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ func testEncoderConfig() EncoderConfig {
EncodeTime: EpochTimeEncoder,
EncodeLevel: LowercaseLevelEncoder,
EncodeDuration: SecondsDurationEncoder,
EncodeCaller: FullPathCallerEncoder,
}
}

Expand Down Expand Up @@ -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",
Expand All @@ -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",
Expand All @@ -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",
Expand All @@ -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",
Expand All @@ -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",
Expand All @@ -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",
Expand All @@ -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",
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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",
Expand All @@ -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")
Expand All @@ -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"}`,
Expand All @@ -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"}`,
Expand All @@ -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",
Expand Down Expand Up @@ -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 file name as %v with %q.", tt.expected, tt.name,
)
}
}

func assertAppended(t testing.TB, expected interface{}, f func(ArrayEncoder), msgAndArgs ...interface{}) {
mem := NewMapObjectEncoder()
mem.AddArray("k", ArrayMarshalerFunc(func(arr ArrayEncoder) error {
Expand Down
3 changes: 2 additions & 1 deletion zapcore/json_encoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,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)
Expand Down

0 comments on commit 5bb2590

Please sign in to comment.