From 942a29c91ce589aca186545285d152d1769eefbe Mon Sep 17 00:00:00 2001 From: wijayaerick Date: Thu, 25 Jun 2020 16:28:35 +0700 Subject: [PATCH] Add New Function Entry --- config.go | 2 + logger.go | 21 ++++-- logger_test.go | 51 +++++++++++++++ options.go | 24 +++++-- zapcore/console_encoder.go | 3 + zapcore/encoder.go | 1 + zapcore/encoder_test.go | 120 +++++++++++++++++++++++------------ zapcore/entry.go | 1 + zapcore/json_encoder.go | 4 ++ zapcore/json_encoder_test.go | 1 + 10 files changed, 178 insertions(+), 50 deletions(-) diff --git a/config.go b/config.go index 192fd1a94..55637fb0b 100644 --- a/config.go +++ b/config.go @@ -101,6 +101,7 @@ func NewProductionEncoderConfig() zapcore.EncoderConfig { LevelKey: "level", NameKey: "logger", CallerKey: "caller", + FunctionKey: zapcore.OmitKey, MessageKey: "msg", StacktraceKey: "stacktrace", LineEnding: zapcore.DefaultLineEnding, @@ -140,6 +141,7 @@ func NewDevelopmentEncoderConfig() zapcore.EncoderConfig { LevelKey: "L", NameKey: "N", CallerKey: "C", + FunctionKey: zapcore.OmitKey, MessageKey: "M", StacktraceKey: "S", LineEnding: zapcore.DefaultLineEnding, diff --git a/logger.go b/logger.go index cd6e19551..4d8711cea 100644 --- a/logger.go +++ b/logger.go @@ -45,8 +45,9 @@ type Logger struct { name string errorOutput zapcore.WriteSyncer - addCaller bool - addStack zapcore.LevelEnabler + addCaller bool + addFunction bool + addStack zapcore.LevelEnabler callerSkip int } @@ -296,12 +297,22 @@ func (log *Logger) check(lvl zapcore.Level, msg string) *zapcore.CheckedEntry { // Thread the error output through to the CheckedEntry. ce.ErrorOutput = log.errorOutput - if log.addCaller { - ce.Entry.Caller = zapcore.NewEntryCaller(runtime.Caller(log.callerSkip + callerSkipOffset)) - if !ce.Entry.Caller.Defined { + if log.addCaller || log.addFunction { + pc := make([]uintptr, 1) + numFrames := runtime.Callers(log.callerSkip+callerSkipOffset+1, pc) + frame, _ := runtime.CallersFrames(pc[:numFrames]).Next() + defined := frame.PC != 0 + if !defined { fmt.Fprintf(log.errorOutput, "%v Logger.check error: failed to get caller\n", time.Now().UTC()) log.errorOutput.Sync() } + + if log.addCaller { + ce.Entry.Caller = zapcore.NewEntryCaller(frame.PC, frame.File, frame.Line, defined) + } + if log.addFunction { + ce.Entry.Function = frame.Function + } } if log.addStack.Enabled(ce.Entry.Level) { ce.Entry.Stack = Stack("").String diff --git a/logger_test.go b/logger_test.go index ed122e455..85e8d5f33 100644 --- a/logger_test.go +++ b/logger_test.go @@ -385,6 +385,57 @@ func TestLoggerAddCallerFail(t *testing.T) { }) } +func TestLoggerAddFunction(t *testing.T) { + tests := []struct { + options []Option + pat string + }{ + {opts(), `^$`}, + {opts(WithFunction(false)), `^$`}, + {opts(AddFunction()), `zap.TestLoggerAddFunction.func1$`}, + {opts(AddFunction(), WithFunction(false)), `^$`}, + {opts(WithFunction(true)), `zap.TestLoggerAddFunction.func1$`}, + {opts(WithFunction(true), WithFunction(false)), `^$`}, + {opts(AddFunction(), AddCallerSkip(1), AddCallerSkip(-1)), `zap.TestLoggerAddFunction.func1$`}, + {opts(AddFunction(), AddCallerSkip(1)), `zap.withLogger$`}, + {opts(AddFunction(), AddCallerSkip(1), AddCallerSkip(3)), `runtime.goexit$`}, + } + for _, tt := range tests { + withLogger(t, DebugLevel, tt.options, func(logger *Logger, logs *observer.ObservedLogs) { + // Make sure that sugaring and desugaring resets caller skip properly. + logger = logger.Sugar().Desugar() + logger.Info("") + output := logs.AllUntimed() + assert.Equal(t, 1, len(output), "Unexpected number of logs written out.") + assert.Regexp( + t, + tt.pat, + output[0].Entry.Function, + "Expected to find function name in output.", + ) + }) + } +} + +func TestLoggerAddFunctionFail(t *testing.T) { + errBuf := &ztest.Buffer{} + withLogger(t, DebugLevel, opts(AddFunction(), ErrorOutput(errBuf)), func(log *Logger, logs *observer.ObservedLogs) { + log.callerSkip = 1e3 + log.Info("Failure.") + assert.Regexp( + t, + `Logger.check error: failed to get caller`, + errBuf.String(), + "Didn't find expected failure message.", + ) + assert.Equal( + t, + logs.AllUntimed()[0].Entry.Message, + "Failure.", + "Expected original message to survive failures in runtime.Caller.") + }) +} + func TestLoggerReplaceCore(t *testing.T) { replace := WrapCore(func(zapcore.Core) zapcore.Core { return zapcore.NewNopCore() diff --git a/options.go b/options.go index c05b38117..5c607bc8e 100644 --- a/options.go +++ b/options.go @@ -87,7 +87,7 @@ func Development() Option { } // AddCaller configures the Logger to annotate each message with the filename -// and line number of zap's caller. See also WithCaller. +// and line number of zap's caller. See also WithCaller. func AddCaller() Option { return WithCaller(true) } @@ -101,10 +101,24 @@ func WithCaller(enabled bool) Option { }) } -// AddCallerSkip increases the number of callers skipped by caller annotation -// (as enabled by the AddCaller option). When building wrappers around the -// Logger and SugaredLogger, supplying this Option prevents zap from always -// reporting the wrapper code as the caller. +// AddFunction configures the Logger to annotate each message with the function +// name of zap's caller. See also WithFunction. +func AddFunction() Option { + return WithFunction(true) +} + +// WithFunction configures the Logger to annotate each message with the function +// name of zap's caller, or not, depending on the value of enabled. +func WithFunction(enabled bool) Option { + return optionFunc(func(log *Logger) { + log.addFunction = enabled + }) +} + +// AddCallerSkip increases the number of callers skipped by caller and function +// annotations (as enabled by the AddCaller and AddFunction options). When +// building wrappers around the Logger and SugaredLogger, supplying this Option +// prevents zap from always reporting the wrapper code as the caller. func AddCallerSkip(skip int) Option { return optionFunc(func(log *Logger) { log.callerSkip += skip diff --git a/zapcore/console_encoder.go b/zapcore/console_encoder.go index b7875966f..9ac89c4e0 100644 --- a/zapcore/console_encoder.go +++ b/zapcore/console_encoder.go @@ -92,6 +92,9 @@ func (c consoleEncoder) EncodeEntry(ent Entry, fields []Field) (*buffer.Buffer, if ent.Caller.Defined && c.CallerKey != "" && c.EncodeCaller != nil { c.EncodeCaller(ent.Caller, arr) } + if ent.Function != "" && c.FunctionKey != "" { + arr.AppendString(ent.Function) + } for i := range arr.elems { if i > 0 { line.AppendByte('\t') diff --git a/zapcore/encoder.go b/zapcore/encoder.go index 6c78f7e49..ff925936e 100644 --- a/zapcore/encoder.go +++ b/zapcore/encoder.go @@ -279,6 +279,7 @@ type EncoderConfig struct { TimeKey string `json:"timeKey" yaml:"timeKey"` NameKey string `json:"nameKey" yaml:"nameKey"` CallerKey string `json:"callerKey" yaml:"callerKey"` + FunctionKey string `json:"functionKey" yaml:"functionKey"` StacktraceKey string `json:"stacktraceKey" yaml:"stacktraceKey"` LineEnding string `json:"lineEnding" yaml:"lineEnding"` // Configure the primitive representations of common complex types. For diff --git a/zapcore/encoder_test.go b/zapcore/encoder_test.go index 54556b8d4..549a04cec 100644 --- a/zapcore/encoder_test.go +++ b/zapcore/encoder_test.go @@ -40,6 +40,7 @@ var ( Time: _epoch, Stack: "fake-stack", Caller: EntryCaller{Defined: true, File: "foo.go", Line: 42}, + Function: "foo.Foo", } ) @@ -50,6 +51,7 @@ func testEncoderConfig() EncoderConfig { NameKey: "name", TimeKey: "ts", CallerKey: "caller", + FunctionKey: "func", StacktraceKey: "stacktrace", LineEnding: "\n", EncodeTime: EpochTimeEncoder, @@ -89,8 +91,8 @@ func TestEncoderConfiguration(t *testing.T) { ent.Message = `hello\` return ent }, - expectedJSON: `{"level":"info","ts":0,"name":"main","caller":"foo.go:42","msg":"hello\\","stacktrace":"fake-stack"}` + "\n", - expectedConsole: "0\tinfo\tmain\tfoo.go:42\thello\\\nfake-stack\n", + expectedJSON: `{"level":"info","ts":0,"name":"main","caller":"foo.go:42","func":"foo.Foo","msg":"hello\\","stacktrace":"fake-stack"}` + "\n", + expectedConsole: "0\tinfo\tmain\tfoo.go:42\tfoo.Foo\thello\\\nfake-stack\n", }, { desc: "use custom entry keys in JSON output and ignore them in console output", @@ -100,6 +102,7 @@ func TestEncoderConfiguration(t *testing.T) { MessageKey: "M", NameKey: "N", CallerKey: "C", + FunctionKey: "F", StacktraceKey: "S", LineEnding: base.LineEnding, EncodeTime: base.EncodeTime, @@ -107,8 +110,8 @@ func TestEncoderConfiguration(t *testing.T) { EncodeLevel: base.EncodeLevel, EncodeCaller: base.EncodeCaller, }, - expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","M":"hello","S":"fake-stack"}` + "\n", - expectedConsole: "0\tinfo\tmain\tfoo.go:42\thello\nfake-stack\n", + expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","S":"fake-stack"}` + "\n", + expectedConsole: "0\tinfo\tmain\tfoo.go:42\tfoo.Foo\thello\nfake-stack\n", }, { desc: "skip level if LevelKey is omitted", @@ -118,6 +121,7 @@ func TestEncoderConfiguration(t *testing.T) { MessageKey: "M", NameKey: "N", CallerKey: "C", + FunctionKey: "F", StacktraceKey: "S", LineEnding: base.LineEnding, EncodeTime: base.EncodeTime, @@ -125,8 +129,8 @@ func TestEncoderConfiguration(t *testing.T) { EncodeLevel: base.EncodeLevel, EncodeCaller: base.EncodeCaller, }, - expectedJSON: `{"T":0,"N":"main","C":"foo.go:42","M":"hello","S":"fake-stack"}` + "\n", - expectedConsole: "0\tmain\tfoo.go:42\thello\nfake-stack\n", + expectedJSON: `{"T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","S":"fake-stack"}` + "\n", + expectedConsole: "0\tmain\tfoo.go:42\tfoo.Foo\thello\nfake-stack\n", }, { desc: "skip timestamp if TimeKey is omitted", @@ -136,6 +140,7 @@ func TestEncoderConfiguration(t *testing.T) { MessageKey: "M", NameKey: "N", CallerKey: "C", + FunctionKey: "F", StacktraceKey: "S", LineEnding: base.LineEnding, EncodeTime: base.EncodeTime, @@ -143,8 +148,8 @@ func TestEncoderConfiguration(t *testing.T) { EncodeLevel: base.EncodeLevel, EncodeCaller: base.EncodeCaller, }, - expectedJSON: `{"L":"info","N":"main","C":"foo.go:42","M":"hello","S":"fake-stack"}` + "\n", - expectedConsole: "info\tmain\tfoo.go:42\thello\nfake-stack\n", + expectedJSON: `{"L":"info","N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","S":"fake-stack"}` + "\n", + expectedConsole: "info\tmain\tfoo.go:42\tfoo.Foo\thello\nfake-stack\n", }, { desc: "skip message if MessageKey is omitted", @@ -154,6 +159,7 @@ func TestEncoderConfiguration(t *testing.T) { MessageKey: OmitKey, NameKey: "N", CallerKey: "C", + FunctionKey: "F", StacktraceKey: "S", LineEnding: base.LineEnding, EncodeTime: base.EncodeTime, @@ -161,8 +167,8 @@ func TestEncoderConfiguration(t *testing.T) { EncodeLevel: base.EncodeLevel, EncodeCaller: base.EncodeCaller, }, - expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","S":"fake-stack"}` + "\n", - expectedConsole: "0\tinfo\tmain\tfoo.go:42\nfake-stack\n", + expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","S":"fake-stack"}` + "\n", + expectedConsole: "0\tinfo\tmain\tfoo.go:42\tfoo.Foo\nfake-stack\n", }, { desc: "skip name if NameKey is omitted", @@ -172,6 +178,7 @@ func TestEncoderConfiguration(t *testing.T) { MessageKey: "M", NameKey: OmitKey, CallerKey: "C", + FunctionKey: "F", StacktraceKey: "S", LineEnding: base.LineEnding, EncodeTime: base.EncodeTime, @@ -179,8 +186,8 @@ func TestEncoderConfiguration(t *testing.T) { EncodeLevel: base.EncodeLevel, EncodeCaller: base.EncodeCaller, }, - expectedJSON: `{"L":"info","T":0,"C":"foo.go:42","M":"hello","S":"fake-stack"}` + "\n", - expectedConsole: "0\tinfo\tfoo.go:42\thello\nfake-stack\n", + expectedJSON: `{"L":"info","T":0,"C":"foo.go:42","F":"foo.Foo","M":"hello","S":"fake-stack"}` + "\n", + expectedConsole: "0\tinfo\tfoo.go:42\tfoo.Foo\thello\nfake-stack\n", }, { desc: "skip caller if CallerKey is omitted", @@ -190,6 +197,26 @@ func TestEncoderConfiguration(t *testing.T) { MessageKey: "M", NameKey: "N", CallerKey: OmitKey, + FunctionKey: "F", + StacktraceKey: "S", + LineEnding: base.LineEnding, + EncodeTime: base.EncodeTime, + EncodeDuration: base.EncodeDuration, + EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, + }, + expectedJSON: `{"L":"info","T":0,"N":"main","F":"foo.Foo","M":"hello","S":"fake-stack"}` + "\n", + expectedConsole: "0\tinfo\tmain\tfoo.Foo\thello\nfake-stack\n", + }, + { + desc: "skip function if FunctionKey is omitted", + cfg: EncoderConfig{ + LevelKey: "L", + TimeKey: "T", + MessageKey: "M", + NameKey: "N", + CallerKey: "C", + FunctionKey: OmitKey, StacktraceKey: "S", LineEnding: base.LineEnding, EncodeTime: base.EncodeTime, @@ -197,8 +224,8 @@ func TestEncoderConfiguration(t *testing.T) { EncodeLevel: base.EncodeLevel, EncodeCaller: base.EncodeCaller, }, - expectedJSON: `{"L":"info","T":0,"N":"main","M":"hello","S":"fake-stack"}` + "\n", - expectedConsole: "0\tinfo\tmain\thello\nfake-stack\n", + expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","M":"hello","S":"fake-stack"}` + "\n", + expectedConsole: "0\tinfo\tmain\tfoo.go:42\thello\nfake-stack\n", }, { desc: "skip stacktrace if StacktraceKey is omitted", @@ -208,6 +235,7 @@ func TestEncoderConfiguration(t *testing.T) { MessageKey: "M", NameKey: "N", CallerKey: "C", + FunctionKey: "F", StacktraceKey: OmitKey, LineEnding: base.LineEnding, EncodeTime: base.EncodeTime, @@ -215,8 +243,8 @@ func TestEncoderConfiguration(t *testing.T) { EncodeLevel: base.EncodeLevel, EncodeCaller: base.EncodeCaller, }, - expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","M":"hello"}` + "\n", - expectedConsole: "0\tinfo\tmain\tfoo.go:42\thello\n", + expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello"}` + "\n", + expectedConsole: "0\tinfo\tmain\tfoo.go:42\tfoo.Foo\thello\n", }, { desc: "use the supplied EncodeTime, for both the entry and any times added", @@ -226,6 +254,7 @@ func TestEncoderConfiguration(t *testing.T) { MessageKey: "M", NameKey: "N", CallerKey: "C", + FunctionKey: "F", StacktraceKey: "S", LineEnding: base.LineEnding, EncodeTime: func(t time.Time, enc PrimitiveArrayEncoder) { enc.AppendString(t.String()) }, @@ -240,8 +269,8 @@ func TestEncoderConfiguration(t *testing.T) { return nil })) }, - expectedJSON: `{"L":"info","T":"1970-01-01 00:00:00 +0000 UTC","N":"main","C":"foo.go:42","M":"hello","extra":"1970-01-01 00:00:00 +0000 UTC","extras":["1970-01-01 00:00:00 +0000 UTC"],"S":"fake-stack"}` + "\n", - expectedConsole: "1970-01-01 00:00:00 +0000 UTC\tinfo\tmain\tfoo.go:42\thello\t" + // plain-text preamble + expectedJSON: `{"L":"info","T":"1970-01-01 00:00:00 +0000 UTC","N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","extra":"1970-01-01 00:00:00 +0000 UTC","extras":["1970-01-01 00:00:00 +0000 UTC"],"S":"fake-stack"}` + "\n", + expectedConsole: "1970-01-01 00:00:00 +0000 UTC\tinfo\tmain\tfoo.go:42\tfoo.Foo\thello\t" + // plain-text preamble `{"extra": "1970-01-01 00:00:00 +0000 UTC", "extras": ["1970-01-01 00:00:00 +0000 UTC"]}` + // JSON context "\nfake-stack\n", // stacktrace after newline }, @@ -253,6 +282,7 @@ func TestEncoderConfiguration(t *testing.T) { MessageKey: "M", NameKey: "N", CallerKey: "C", + FunctionKey: "F", StacktraceKey: "S", LineEnding: base.LineEnding, EncodeTime: base.EncodeTime, @@ -267,8 +297,8 @@ func TestEncoderConfiguration(t *testing.T) { return nil })) }, - expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","M":"hello","extra":"1s","extras":["1m0s"],"S":"fake-stack"}` + "\n", - expectedConsole: "0\tinfo\tmain\tfoo.go:42\thello\t" + // preamble + expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","extra":"1s","extras":["1m0s"],"S":"fake-stack"}` + "\n", + expectedConsole: "0\tinfo\tmain\tfoo.go:42\tfoo.Foo\thello\t" + // preamble `{"extra": "1s", "extras": ["1m0s"]}` + // context "\nfake-stack\n", // stacktrace }, @@ -280,6 +310,7 @@ func TestEncoderConfiguration(t *testing.T) { MessageKey: "M", NameKey: "N", CallerKey: "C", + FunctionKey: "F", StacktraceKey: "S", LineEnding: base.LineEnding, EncodeTime: base.EncodeTime, @@ -287,8 +318,8 @@ func TestEncoderConfiguration(t *testing.T) { EncodeLevel: CapitalLevelEncoder, EncodeCaller: base.EncodeCaller, }, - expectedJSON: `{"L":"INFO","T":0,"N":"main","C":"foo.go:42","M":"hello","S":"fake-stack"}` + "\n", - expectedConsole: "0\tINFO\tmain\tfoo.go:42\thello\nfake-stack\n", + expectedJSON: `{"L":"INFO","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","S":"fake-stack"}` + "\n", + expectedConsole: "0\tINFO\tmain\tfoo.go:42\tfoo.Foo\thello\nfake-stack\n", }, { desc: "use the supplied EncodeName", @@ -298,6 +329,7 @@ func TestEncoderConfiguration(t *testing.T) { MessageKey: "M", NameKey: "N", CallerKey: "C", + FunctionKey: "F", StacktraceKey: "S", LineEnding: base.LineEnding, EncodeTime: base.EncodeTime, @@ -306,8 +338,8 @@ func TestEncoderConfiguration(t *testing.T) { EncodeCaller: base.EncodeCaller, EncodeName: capitalNameEncoder, }, - expectedJSON: `{"L":"info","T":0,"N":"MAIN","C":"foo.go:42","M":"hello","S":"fake-stack"}` + "\n", - expectedConsole: "0\tinfo\tMAIN\tfoo.go:42\thello\nfake-stack\n", + expectedJSON: `{"L":"info","T":0,"N":"MAIN","C":"foo.go:42","F":"foo.Foo","M":"hello","S":"fake-stack"}` + "\n", + expectedConsole: "0\tinfo\tMAIN\tfoo.go:42\tfoo.Foo\thello\nfake-stack\n", }, { desc: "close all open namespaces", @@ -317,6 +349,7 @@ func TestEncoderConfiguration(t *testing.T) { MessageKey: "M", NameKey: "N", CallerKey: "C", + FunctionKey: "F", StacktraceKey: "S", LineEnding: base.LineEnding, EncodeTime: base.EncodeTime, @@ -330,8 +363,8 @@ func TestEncoderConfiguration(t *testing.T) { enc.AddString("foo", "bar") enc.OpenNamespace("innermost") }, - expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","M":"hello","outer":{"inner":{"foo":"bar","innermost":{}}},"S":"fake-stack"}` + "\n", - expectedConsole: "0\tinfo\tmain\tfoo.go:42\thello\t" + + expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","outer":{"inner":{"foo":"bar","innermost":{}}},"S":"fake-stack"}` + "\n", + expectedConsole: "0\tinfo\tmain\tfoo.go:42\tfoo.Foo\thello\t" + `{"outer": {"inner": {"foo": "bar", "innermost": {}}}}` + "\nfake-stack\n", }, @@ -343,6 +376,7 @@ func TestEncoderConfiguration(t *testing.T) { MessageKey: "M", NameKey: "N", CallerKey: "C", + FunctionKey: "F", StacktraceKey: "S", LineEnding: base.LineEnding, EncodeTime: func(time.Time, PrimitiveArrayEncoder) {}, @@ -351,8 +385,8 @@ func TestEncoderConfiguration(t *testing.T) { 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"}` + "\n", - expectedConsole: "info\tmain\tfoo.go:42\thello\t" + `{"sometime": 100}` + "\nfake-stack\n", + expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","sometime":100,"S":"fake-stack"}` + "\n", + expectedConsole: "info\tmain\tfoo.go:42\tfoo.Foo\thello\t" + `{"sometime": 100}` + "\nfake-stack\n", }, { desc: "handle no-op EncodeDuration", @@ -362,6 +396,7 @@ func TestEncoderConfiguration(t *testing.T) { MessageKey: "M", NameKey: "N", CallerKey: "C", + FunctionKey: "F", StacktraceKey: "S", LineEnding: base.LineEnding, EncodeTime: base.EncodeTime, @@ -370,8 +405,8 @@ func TestEncoderConfiguration(t *testing.T) { 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"}` + "\n", - expectedConsole: "0\tinfo\tmain\tfoo.go:42\thello\t" + `{"someduration": 1000}` + "\nfake-stack\n", + expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","someduration":1000,"S":"fake-stack"}` + "\n", + expectedConsole: "0\tinfo\tmain\tfoo.go:42\tfoo.Foo\thello\t" + `{"someduration": 1000}` + "\nfake-stack\n", }, { desc: "handle no-op EncodeLevel", @@ -381,6 +416,7 @@ func TestEncoderConfiguration(t *testing.T) { MessageKey: "M", NameKey: "N", CallerKey: "C", + FunctionKey: "F", StacktraceKey: "S", LineEnding: base.LineEnding, EncodeTime: base.EncodeTime, @@ -388,8 +424,8 @@ func TestEncoderConfiguration(t *testing.T) { EncodeLevel: func(Level, PrimitiveArrayEncoder) {}, EncodeCaller: base.EncodeCaller, }, - expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","M":"hello","S":"fake-stack"}` + "\n", - expectedConsole: "0\tmain\tfoo.go:42\thello\nfake-stack\n", + expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","S":"fake-stack"}` + "\n", + expectedConsole: "0\tmain\tfoo.go:42\tfoo.Foo\thello\nfake-stack\n", }, { desc: "handle no-op EncodeCaller", @@ -399,6 +435,7 @@ func TestEncoderConfiguration(t *testing.T) { MessageKey: "M", NameKey: "N", CallerKey: "C", + FunctionKey: "F", StacktraceKey: "S", LineEnding: base.LineEnding, EncodeTime: base.EncodeTime, @@ -406,8 +443,8 @@ func TestEncoderConfiguration(t *testing.T) { EncodeLevel: base.EncodeLevel, EncodeCaller: func(EntryCaller, PrimitiveArrayEncoder) {}, }, - expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","M":"hello","S":"fake-stack"}` + "\n", - expectedConsole: "0\tinfo\tmain\thello\nfake-stack\n", + expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","S":"fake-stack"}` + "\n", + expectedConsole: "0\tinfo\tmain\tfoo.Foo\thello\nfake-stack\n", }, { desc: "handle no-op EncodeName", @@ -417,6 +454,7 @@ func TestEncoderConfiguration(t *testing.T) { MessageKey: "M", NameKey: "N", CallerKey: "C", + FunctionKey: "F", StacktraceKey: "S", LineEnding: base.LineEnding, EncodeTime: base.EncodeTime, @@ -425,8 +463,8 @@ func TestEncoderConfiguration(t *testing.T) { EncodeCaller: base.EncodeCaller, EncodeName: func(string, PrimitiveArrayEncoder) {}, }, - expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","M":"hello","S":"fake-stack"}` + "\n", - expectedConsole: "0\tinfo\tfoo.go:42\thello\nfake-stack\n", + expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","S":"fake-stack"}` + "\n", + expectedConsole: "0\tinfo\tfoo.go:42\tfoo.Foo\thello\nfake-stack\n", }, { desc: "use custom line separator", @@ -436,6 +474,7 @@ func TestEncoderConfiguration(t *testing.T) { MessageKey: "M", NameKey: "N", CallerKey: "C", + FunctionKey: "F", StacktraceKey: "S", LineEnding: "\r\n", EncodeTime: base.EncodeTime, @@ -443,8 +482,8 @@ func TestEncoderConfiguration(t *testing.T) { EncodeLevel: base.EncodeLevel, EncodeCaller: base.EncodeCaller, }, - expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","M":"hello","S":"fake-stack"}` + "\r\n", - expectedConsole: "0\tinfo\tmain\tfoo.go:42\thello\nfake-stack\r\n", + expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","S":"fake-stack"}` + "\r\n", + expectedConsole: "0\tinfo\tmain\tfoo.go:42\tfoo.Foo\thello\nfake-stack\r\n", }, { desc: "omit line separator definition - fall back to default", @@ -454,14 +493,15 @@ func TestEncoderConfiguration(t *testing.T) { MessageKey: "M", NameKey: "N", CallerKey: "C", + FunctionKey: "F", StacktraceKey: "S", 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"}` + DefaultLineEnding, - expectedConsole: "0\tinfo\tmain\tfoo.go:42\thello\nfake-stack" + DefaultLineEnding, + expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","S":"fake-stack"}` + DefaultLineEnding, + expectedConsole: "0\tinfo\tmain\tfoo.go:42\tfoo.Foo\thello\nfake-stack" + DefaultLineEnding, }, } diff --git a/zapcore/entry.go b/zapcore/entry.go index 8273abdf0..6753c3967 100644 --- a/zapcore/entry.go +++ b/zapcore/entry.go @@ -147,6 +147,7 @@ type Entry struct { LoggerName string Message string Caller EntryCaller + Function string Stack string } diff --git a/zapcore/json_encoder.go b/zapcore/json_encoder.go index 9ede4a9ec..e3286fdd6 100644 --- a/zapcore/json_encoder.go +++ b/zapcore/json_encoder.go @@ -376,6 +376,10 @@ func (enc *jsonEncoder) EncodeEntry(ent Entry, fields []Field) (*buffer.Buffer, final.AppendString(ent.Caller.String()) } } + if ent.Function != "" && final.FunctionKey != "" { + final.addKey(final.FunctionKey) + final.AppendString(ent.Function) + } if final.MessageKey != "" { final.addKey(enc.MessageKey) final.AppendString(ent.Message) diff --git a/zapcore/json_encoder_test.go b/zapcore/json_encoder_test.go index 5ae34d7f7..4baa04549 100644 --- a/zapcore/json_encoder_test.go +++ b/zapcore/json_encoder_test.go @@ -112,6 +112,7 @@ func TestJSONEncodeEntry(t *testing.T) { TimeKey: "T", NameKey: "N", CallerKey: "C", + FunctionKey: "F", StacktraceKey: "S", EncodeLevel: zapcore.LowercaseLevelEncoder, EncodeTime: zapcore.ISO8601TimeEncoder,