From 456073abc4aa67c38c360c840894a3b2acc9e289 Mon Sep 17 00:00:00 2001 From: alex arwine Date: Mon, 9 Oct 2023 10:06:15 -0700 Subject: [PATCH 1/7] Allow configurable stacktrace encoding This PR adds a `StacktraceEncoder` type to the `EncoderConfig` struct Users can override the `EncodeStacktrace` field to configure how stacktraces are outputed This PR aims to resolve https://github.com/uber-go/zap/issues/514 by mirroring the behavior of the `EncodeCaller` field The `EncodeStacktrace` field has been inserted as a required field, and has been added to `NewDevelopmentConfig` and `NewProductionConfig` as sane defaults however, it is currently a required field and can cause a panic if a user is manually building their config without these helpers. --- config.go | 50 +-- zapcore/console_encoder.go | 18 +- zapcore/encoder.go | 21 +- zapcore/encoder_test.go | 578 +++++++++++++++++++---------------- zapcore/json_encoder.go | 9 +- zapcore/json_encoder_test.go | 44 +-- 6 files changed, 402 insertions(+), 318 deletions(-) diff --git a/config.go b/config.go index e76e4e64f..edc8d5edd 100644 --- a/config.go +++ b/config.go @@ -123,18 +123,19 @@ type Config struct { // cfg.EncodeTime = zapcore.ISO8601TimeEncoder func NewProductionEncoderConfig() zapcore.EncoderConfig { return zapcore.EncoderConfig{ - TimeKey: "ts", - LevelKey: "level", - NameKey: "logger", - CallerKey: "caller", - FunctionKey: zapcore.OmitKey, - MessageKey: "msg", - StacktraceKey: "stacktrace", - LineEnding: zapcore.DefaultLineEnding, - EncodeLevel: zapcore.LowercaseLevelEncoder, - EncodeTime: zapcore.EpochTimeEncoder, - EncodeDuration: zapcore.SecondsDurationEncoder, - EncodeCaller: zapcore.ShortCallerEncoder, + TimeKey: "ts", + LevelKey: "level", + NameKey: "logger", + CallerKey: "caller", + FunctionKey: zapcore.OmitKey, + MessageKey: "msg", + StacktraceKey: "stacktrace", + LineEnding: zapcore.DefaultLineEnding, + EncodeLevel: zapcore.LowercaseLevelEncoder, + EncodeTime: zapcore.EpochTimeEncoder, + EncodeDuration: zapcore.SecondsDurationEncoder, + EncodeCaller: zapcore.ShortCallerEncoder, + EncodeStacktrace: zapcore.FullStacktraceEncoder, } } @@ -200,18 +201,19 @@ func NewProductionConfig() Config { func NewDevelopmentEncoderConfig() zapcore.EncoderConfig { return zapcore.EncoderConfig{ // Keys can be anything except the empty string. - TimeKey: "T", - LevelKey: "L", - NameKey: "N", - CallerKey: "C", - FunctionKey: zapcore.OmitKey, - MessageKey: "M", - StacktraceKey: "S", - LineEnding: zapcore.DefaultLineEnding, - EncodeLevel: zapcore.CapitalLevelEncoder, - EncodeTime: zapcore.ISO8601TimeEncoder, - EncodeDuration: zapcore.StringDurationEncoder, - EncodeCaller: zapcore.ShortCallerEncoder, + TimeKey: "T", + LevelKey: "L", + NameKey: "N", + CallerKey: "C", + FunctionKey: zapcore.OmitKey, + MessageKey: "M", + StacktraceKey: "S", + LineEnding: zapcore.DefaultLineEnding, + EncodeLevel: zapcore.CapitalLevelEncoder, + EncodeTime: zapcore.ISO8601TimeEncoder, + EncodeDuration: zapcore.StringDurationEncoder, + EncodeCaller: zapcore.ShortCallerEncoder, + EncodeStacktrace: zapcore.FullStacktraceEncoder, } } diff --git a/zapcore/console_encoder.go b/zapcore/console_encoder.go index cc2b4e07b..f2d130001 100644 --- a/zapcore/console_encoder.go +++ b/zapcore/console_encoder.go @@ -101,6 +101,7 @@ func (c consoleEncoder) EncodeEntry(ent Entry, fields []Field) (*buffer.Buffer, arr.AppendString(ent.Caller.Function) } } + for i := range arr.elems { if i > 0 { line.AppendString(c.ConsoleSeparator) @@ -119,10 +120,23 @@ func (c consoleEncoder) EncodeEntry(ent Entry, fields []Field) (*buffer.Buffer, c.writeContext(line, fields) // If there's no stacktrace key, honor that; this allows users to force - // single-line output. + // single-line output by avoiding printing the stacktrace. if ent.Stack != "" && c.StacktraceKey != "" { line.AppendByte('\n') - line.AppendString(ent.Stack) + + if c.EncodeStacktrace != nil { + arr = getSliceEncoder() + c.EncodeStacktrace(ent.Stack, arr) + for i := range arr.elems { + if i > 0 { + line.AppendString(c.ConsoleSeparator) + } + fmt.Fprint(line, arr.elems[i]) + } + putSliceEncoder(arr) + } else { + line.AppendString(ent.Stack) + } } line.AppendString(c.LineEnding) diff --git a/zapcore/encoder.go b/zapcore/encoder.go index 044625415..993eed9c4 100644 --- a/zapcore/encoder.go +++ b/zapcore/encoder.go @@ -270,6 +270,18 @@ func (e *DurationEncoder) UnmarshalText(text []byte) error { return nil } +// A StacktraceEncoder serializes a stacktrace +// +// This function must make exactly one call +// to a PrimitiveArrayEncoder's Append* method. +type StacktraceEncoder func(string, PrimitiveArrayEncoder) + +// FullStacktraceEncoder passes down the full stacktrace as a string to the enc +func FullStacktraceEncoder(stacktrace string, enc PrimitiveArrayEncoder) { + // TODO: consider using a byte-oriented API to save an allocation. + enc.AppendString(stacktrace) +} + // A CallerEncoder serializes an EntryCaller to a primitive type. // // This function must make exactly one call @@ -343,10 +355,11 @@ type EncoderConfig struct { // Configure the primitive representations of common complex types. For // example, some users may want all time.Times serialized as floating-point // seconds since epoch, while others may prefer ISO8601 strings. - EncodeLevel LevelEncoder `json:"levelEncoder" yaml:"levelEncoder"` - EncodeTime TimeEncoder `json:"timeEncoder" yaml:"timeEncoder"` - EncodeDuration DurationEncoder `json:"durationEncoder" yaml:"durationEncoder"` - EncodeCaller CallerEncoder `json:"callerEncoder" yaml:"callerEncoder"` + EncodeLevel LevelEncoder `json:"levelEncoder" yaml:"levelEncoder"` + EncodeTime TimeEncoder `json:"timeEncoder" yaml:"timeEncoder"` + EncodeDuration DurationEncoder `json:"durationEncoder" yaml:"durationEncoder"` + EncodeCaller CallerEncoder `json:"callerEncoder" yaml:"callerEncoder"` + EncodeStacktrace StacktraceEncoder `json:"stacktraceEncoder" yaml:"stacktraceEncoder"` // Unlike the other primitive type encoders, EncodeName is optional. The // zero value falls back to FullNameEncoder. EncodeName NameEncoder `json:"nameEncoder" yaml:"nameEncoder"` diff --git a/zapcore/encoder_test.go b/zapcore/encoder_test.go index 9b8142f5d..4618ca137 100644 --- a/zapcore/encoder_test.go +++ b/zapcore/encoder_test.go @@ -47,18 +47,19 @@ var ( func testEncoderConfig() EncoderConfig { return EncoderConfig{ - MessageKey: "msg", - LevelKey: "level", - NameKey: "name", - TimeKey: "ts", - CallerKey: "caller", - FunctionKey: "func", - StacktraceKey: "stacktrace", - LineEnding: "\n", - EncodeTime: EpochTimeEncoder, - EncodeLevel: LowercaseLevelEncoder, - EncodeDuration: SecondsDurationEncoder, - EncodeCaller: ShortCallerEncoder, + MessageKey: "msg", + LevelKey: "level", + NameKey: "name", + TimeKey: "ts", + CallerKey: "caller", + FunctionKey: "func", + StacktraceKey: "stacktrace", + LineEnding: "\n", + EncodeTime: EpochTimeEncoder, + EncodeLevel: LowercaseLevelEncoder, + EncodeDuration: SecondsDurationEncoder, + EncodeCaller: ShortCallerEncoder, + EncodeStacktrace: FullStacktraceEncoder, } } @@ -74,6 +75,10 @@ func capitalNameEncoder(loggerName string, enc PrimitiveArrayEncoder) { enc.AppendString(strings.ToUpper(loggerName)) } +func capitalStacktraceEncoder(stacktrace string, enc PrimitiveArrayEncoder) { + enc.AppendString(strings.ToUpper(stacktrace)) +} + func TestEncoderConfiguration(t *testing.T) { base := testEncoderConfig() @@ -98,18 +103,19 @@ func TestEncoderConfiguration(t *testing.T) { { desc: "use custom entry keys in JSON output and ignore them in console output", cfg: EncoderConfig{ - LevelKey: "L", - TimeKey: "T", - MessageKey: "M", - NameKey: "N", - CallerKey: "C", - FunctionKey: "F", - StacktraceKey: "S", - LineEnding: base.LineEnding, - EncodeTime: base.EncodeTime, - EncodeDuration: base.EncodeDuration, - EncodeLevel: base.EncodeLevel, - EncodeCaller: base.EncodeCaller, + LevelKey: "L", + TimeKey: "T", + MessageKey: "M", + NameKey: "N", + CallerKey: "C", + FunctionKey: "F", + StacktraceKey: "S", + LineEnding: base.LineEnding, + EncodeTime: base.EncodeTime, + EncodeDuration: base.EncodeDuration, + EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, + EncodeStacktrace: base.EncodeStacktrace, }, 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", @@ -117,19 +123,20 @@ func TestEncoderConfiguration(t *testing.T) { { desc: "skip line ending if SkipLineEnding is 'true'", cfg: EncoderConfig{ - LevelKey: "L", - TimeKey: "T", - MessageKey: "M", - NameKey: "N", - CallerKey: "C", - FunctionKey: "F", - StacktraceKey: "S", - LineEnding: base.LineEnding, - SkipLineEnding: true, - EncodeTime: base.EncodeTime, - EncodeDuration: base.EncodeDuration, - EncodeLevel: base.EncodeLevel, - EncodeCaller: base.EncodeCaller, + LevelKey: "L", + TimeKey: "T", + MessageKey: "M", + NameKey: "N", + CallerKey: "C", + FunctionKey: "F", + StacktraceKey: "S", + LineEnding: base.LineEnding, + SkipLineEnding: true, + EncodeTime: base.EncodeTime, + EncodeDuration: base.EncodeDuration, + EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, + EncodeStacktrace: base.EncodeStacktrace, }, expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","S":"fake-stack"}`, expectedConsole: "0\tinfo\tmain\tfoo.go:42\tfoo.Foo\thello\nfake-stack", @@ -137,18 +144,19 @@ func TestEncoderConfiguration(t *testing.T) { { desc: "skip level if LevelKey is omitted", cfg: EncoderConfig{ - LevelKey: OmitKey, - TimeKey: "T", - MessageKey: "M", - NameKey: "N", - CallerKey: "C", - FunctionKey: "F", - StacktraceKey: "S", - LineEnding: base.LineEnding, - EncodeTime: base.EncodeTime, - EncodeDuration: base.EncodeDuration, - EncodeLevel: base.EncodeLevel, - EncodeCaller: base.EncodeCaller, + LevelKey: OmitKey, + TimeKey: "T", + MessageKey: "M", + NameKey: "N", + CallerKey: "C", + FunctionKey: "F", + StacktraceKey: "S", + LineEnding: base.LineEnding, + EncodeTime: base.EncodeTime, + EncodeDuration: base.EncodeDuration, + EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, + EncodeStacktrace: base.EncodeStacktrace, }, 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", @@ -156,18 +164,19 @@ func TestEncoderConfiguration(t *testing.T) { { desc: "skip timestamp if TimeKey is omitted", cfg: EncoderConfig{ - LevelKey: "L", - TimeKey: OmitKey, - MessageKey: "M", - NameKey: "N", - CallerKey: "C", - FunctionKey: "F", - StacktraceKey: "S", - LineEnding: base.LineEnding, - EncodeTime: base.EncodeTime, - EncodeDuration: base.EncodeDuration, - EncodeLevel: base.EncodeLevel, - EncodeCaller: base.EncodeCaller, + LevelKey: "L", + TimeKey: OmitKey, + MessageKey: "M", + NameKey: "N", + CallerKey: "C", + FunctionKey: "F", + StacktraceKey: "S", + LineEnding: base.LineEnding, + EncodeTime: base.EncodeTime, + EncodeDuration: base.EncodeDuration, + EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, + EncodeStacktrace: base.EncodeStacktrace, }, 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", @@ -175,18 +184,19 @@ func TestEncoderConfiguration(t *testing.T) { { desc: "skip message if MessageKey is omitted", cfg: EncoderConfig{ - LevelKey: "L", - TimeKey: "T", - MessageKey: OmitKey, - NameKey: "N", - CallerKey: "C", - FunctionKey: "F", - StacktraceKey: "S", - LineEnding: base.LineEnding, - EncodeTime: base.EncodeTime, - EncodeDuration: base.EncodeDuration, - EncodeLevel: base.EncodeLevel, - EncodeCaller: base.EncodeCaller, + LevelKey: "L", + TimeKey: "T", + MessageKey: OmitKey, + NameKey: "N", + CallerKey: "C", + FunctionKey: "F", + StacktraceKey: "S", + LineEnding: base.LineEnding, + EncodeTime: base.EncodeTime, + EncodeDuration: base.EncodeDuration, + EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, + EncodeStacktrace: base.EncodeStacktrace, }, 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", @@ -194,18 +204,19 @@ func TestEncoderConfiguration(t *testing.T) { { desc: "skip name if NameKey is omitted", cfg: EncoderConfig{ - LevelKey: "L", - TimeKey: "T", - MessageKey: "M", - NameKey: OmitKey, - CallerKey: "C", - FunctionKey: "F", - StacktraceKey: "S", - LineEnding: base.LineEnding, - EncodeTime: base.EncodeTime, - EncodeDuration: base.EncodeDuration, - EncodeLevel: base.EncodeLevel, - EncodeCaller: base.EncodeCaller, + LevelKey: "L", + TimeKey: "T", + MessageKey: "M", + NameKey: OmitKey, + CallerKey: "C", + FunctionKey: "F", + StacktraceKey: "S", + LineEnding: base.LineEnding, + EncodeTime: base.EncodeTime, + EncodeDuration: base.EncodeDuration, + EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, + EncodeStacktrace: base.EncodeStacktrace, }, 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", @@ -213,18 +224,19 @@ func TestEncoderConfiguration(t *testing.T) { { desc: "skip caller if CallerKey is omitted", cfg: EncoderConfig{ - LevelKey: "L", - TimeKey: "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, + LevelKey: "L", + TimeKey: "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, + EncodeStacktrace: base.EncodeStacktrace, }, 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", @@ -232,18 +244,19 @@ func TestEncoderConfiguration(t *testing.T) { { 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, - EncodeDuration: base.EncodeDuration, - EncodeLevel: base.EncodeLevel, - EncodeCaller: base.EncodeCaller, + LevelKey: "L", + TimeKey: "T", + MessageKey: "M", + NameKey: "N", + CallerKey: "C", + FunctionKey: OmitKey, + StacktraceKey: "S", + LineEnding: base.LineEnding, + EncodeTime: base.EncodeTime, + EncodeDuration: base.EncodeDuration, + EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, + EncodeStacktrace: base.EncodeStacktrace, }, 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", @@ -251,18 +264,19 @@ func TestEncoderConfiguration(t *testing.T) { { desc: "skip stacktrace if StacktraceKey is omitted", cfg: EncoderConfig{ - LevelKey: "L", - TimeKey: "T", - MessageKey: "M", - NameKey: "N", - CallerKey: "C", - FunctionKey: "F", - StacktraceKey: OmitKey, - LineEnding: base.LineEnding, - EncodeTime: base.EncodeTime, - EncodeDuration: base.EncodeDuration, - EncodeLevel: base.EncodeLevel, - EncodeCaller: base.EncodeCaller, + LevelKey: "L", + TimeKey: "T", + MessageKey: "M", + NameKey: "N", + CallerKey: "C", + FunctionKey: "F", + StacktraceKey: OmitKey, + LineEnding: base.LineEnding, + EncodeTime: base.EncodeTime, + EncodeDuration: base.EncodeDuration, + EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, + EncodeStacktrace: base.EncodeStacktrace, }, 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", @@ -270,18 +284,19 @@ func TestEncoderConfiguration(t *testing.T) { { desc: "use the supplied EncodeTime, for both the entry and any times added", cfg: EncoderConfig{ - LevelKey: "L", - TimeKey: "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()) }, - EncodeDuration: base.EncodeDuration, - EncodeLevel: base.EncodeLevel, - EncodeCaller: base.EncodeCaller, + LevelKey: "L", + TimeKey: "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()) }, + EncodeDuration: base.EncodeDuration, + EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, + EncodeStacktrace: base.EncodeStacktrace, }, extra: func(enc Encoder) { enc.AddTime("extra", _epoch) @@ -299,18 +314,19 @@ func TestEncoderConfiguration(t *testing.T) { { desc: "use the supplied EncodeDuration for any durations added", cfg: EncoderConfig{ - LevelKey: "L", - TimeKey: "T", - MessageKey: "M", - NameKey: "N", - CallerKey: "C", - FunctionKey: "F", - StacktraceKey: "S", - LineEnding: base.LineEnding, - EncodeTime: base.EncodeTime, - EncodeDuration: StringDurationEncoder, - EncodeLevel: base.EncodeLevel, - EncodeCaller: base.EncodeCaller, + LevelKey: "L", + TimeKey: "T", + MessageKey: "M", + NameKey: "N", + CallerKey: "C", + FunctionKey: "F", + StacktraceKey: "S", + LineEnding: base.LineEnding, + EncodeTime: base.EncodeTime, + EncodeDuration: StringDurationEncoder, + EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, + EncodeStacktrace: base.EncodeStacktrace, }, extra: func(enc Encoder) { enc.AddDuration("extra", time.Second) @@ -328,18 +344,19 @@ func TestEncoderConfiguration(t *testing.T) { { desc: "use the supplied EncodeLevel", cfg: EncoderConfig{ - LevelKey: "L", - TimeKey: "T", - MessageKey: "M", - NameKey: "N", - CallerKey: "C", - FunctionKey: "F", - StacktraceKey: "S", - LineEnding: base.LineEnding, - EncodeTime: base.EncodeTime, - EncodeDuration: base.EncodeDuration, - EncodeLevel: CapitalLevelEncoder, - EncodeCaller: base.EncodeCaller, + LevelKey: "L", + TimeKey: "T", + MessageKey: "M", + NameKey: "N", + CallerKey: "C", + FunctionKey: "F", + StacktraceKey: "S", + LineEnding: base.LineEnding, + EncodeTime: base.EncodeTime, + EncodeDuration: base.EncodeDuration, + EncodeLevel: CapitalLevelEncoder, + EncodeCaller: base.EncodeCaller, + EncodeStacktrace: base.EncodeStacktrace, }, 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", @@ -347,38 +364,60 @@ func TestEncoderConfiguration(t *testing.T) { { desc: "use the supplied EncodeName", cfg: EncoderConfig{ - LevelKey: "L", - TimeKey: "T", - MessageKey: "M", - NameKey: "N", - CallerKey: "C", - FunctionKey: "F", - StacktraceKey: "S", - LineEnding: base.LineEnding, - EncodeTime: base.EncodeTime, - EncodeDuration: base.EncodeDuration, - EncodeLevel: base.EncodeLevel, - EncodeCaller: base.EncodeCaller, - EncodeName: capitalNameEncoder, + LevelKey: "L", + TimeKey: "T", + MessageKey: "M", + NameKey: "N", + CallerKey: "C", + FunctionKey: "F", + StacktraceKey: "S", + LineEnding: base.LineEnding, + EncodeTime: base.EncodeTime, + EncodeDuration: base.EncodeDuration, + EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, + EncodeStacktrace: base.EncodeStacktrace, + EncodeName: capitalNameEncoder, }, 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 EncodeStacktrace", + cfg: EncoderConfig{ + LevelKey: "L", + TimeKey: "T", + MessageKey: "M", + NameKey: "N", + CallerKey: "C", + FunctionKey: "F", + StacktraceKey: "S", + LineEnding: base.LineEnding, + EncodeTime: base.EncodeTime, + EncodeDuration: base.EncodeDuration, + EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, + EncodeStacktrace: capitalStacktraceEncoder, + }, + 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", cfg: EncoderConfig{ - LevelKey: "L", - TimeKey: "T", - MessageKey: "M", - NameKey: "N", - CallerKey: "C", - FunctionKey: "F", - StacktraceKey: "S", - LineEnding: base.LineEnding, - EncodeTime: base.EncodeTime, - EncodeDuration: base.EncodeDuration, - EncodeLevel: base.EncodeLevel, - EncodeCaller: base.EncodeCaller, + LevelKey: "L", + TimeKey: "T", + MessageKey: "M", + NameKey: "N", + CallerKey: "C", + FunctionKey: "F", + StacktraceKey: "S", + LineEnding: base.LineEnding, + EncodeTime: base.EncodeTime, + EncodeDuration: base.EncodeDuration, + EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, + EncodeStacktrace: base.EncodeStacktrace, }, extra: func(enc Encoder) { enc.OpenNamespace("outer") @@ -394,18 +433,19 @@ func TestEncoderConfiguration(t *testing.T) { { desc: "handle no-op EncodeTime", cfg: EncoderConfig{ - LevelKey: "L", - TimeKey: "T", - MessageKey: "M", - NameKey: "N", - CallerKey: "C", - FunctionKey: "F", - StacktraceKey: "S", - LineEnding: base.LineEnding, - EncodeTime: func(time.Time, PrimitiveArrayEncoder) {}, - EncodeDuration: base.EncodeDuration, - EncodeLevel: base.EncodeLevel, - EncodeCaller: base.EncodeCaller, + LevelKey: "L", + TimeKey: "T", + MessageKey: "M", + NameKey: "N", + CallerKey: "C", + FunctionKey: "F", + StacktraceKey: "S", + LineEnding: base.LineEnding, + EncodeTime: func(time.Time, PrimitiveArrayEncoder) {}, + EncodeDuration: base.EncodeDuration, + EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, + EncodeStacktrace: base.EncodeStacktrace, }, extra: func(enc Encoder) { enc.AddTime("sometime", time.Unix(0, 100)) }, expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","sometime":100,"S":"fake-stack"}` + "\n", @@ -414,18 +454,19 @@ func TestEncoderConfiguration(t *testing.T) { { desc: "handle no-op EncodeDuration", cfg: EncoderConfig{ - LevelKey: "L", - TimeKey: "T", - MessageKey: "M", - NameKey: "N", - CallerKey: "C", - FunctionKey: "F", - StacktraceKey: "S", - LineEnding: base.LineEnding, - EncodeTime: base.EncodeTime, - EncodeDuration: func(time.Duration, PrimitiveArrayEncoder) {}, - EncodeLevel: base.EncodeLevel, - EncodeCaller: base.EncodeCaller, + LevelKey: "L", + TimeKey: "T", + MessageKey: "M", + NameKey: "N", + CallerKey: "C", + FunctionKey: "F", + StacktraceKey: "S", + LineEnding: base.LineEnding, + EncodeTime: base.EncodeTime, + EncodeDuration: func(time.Duration, PrimitiveArrayEncoder) {}, + EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, + EncodeStacktrace: base.EncodeStacktrace, }, extra: func(enc Encoder) { enc.AddDuration("someduration", time.Microsecond) }, expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","someduration":1000,"S":"fake-stack"}` + "\n", @@ -434,18 +475,19 @@ func TestEncoderConfiguration(t *testing.T) { { desc: "handle no-op EncodeLevel", cfg: EncoderConfig{ - LevelKey: "L", - TimeKey: "T", - MessageKey: "M", - NameKey: "N", - CallerKey: "C", - FunctionKey: "F", - StacktraceKey: "S", - LineEnding: base.LineEnding, - EncodeTime: base.EncodeTime, - EncodeDuration: base.EncodeDuration, - EncodeLevel: func(Level, PrimitiveArrayEncoder) {}, - EncodeCaller: base.EncodeCaller, + LevelKey: "L", + TimeKey: "T", + MessageKey: "M", + NameKey: "N", + CallerKey: "C", + FunctionKey: "F", + StacktraceKey: "S", + LineEnding: base.LineEnding, + EncodeTime: base.EncodeTime, + EncodeDuration: base.EncodeDuration, + EncodeLevel: func(Level, PrimitiveArrayEncoder) {}, + EncodeCaller: base.EncodeCaller, + EncodeStacktrace: base.EncodeStacktrace, }, 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", @@ -453,18 +495,19 @@ func TestEncoderConfiguration(t *testing.T) { { desc: "handle no-op EncodeCaller", cfg: EncoderConfig{ - LevelKey: "L", - TimeKey: "T", - MessageKey: "M", - NameKey: "N", - CallerKey: "C", - FunctionKey: "F", - StacktraceKey: "S", - LineEnding: base.LineEnding, - EncodeTime: base.EncodeTime, - EncodeDuration: base.EncodeDuration, - EncodeLevel: base.EncodeLevel, - EncodeCaller: func(EntryCaller, PrimitiveArrayEncoder) {}, + LevelKey: "L", + TimeKey: "T", + MessageKey: "M", + NameKey: "N", + CallerKey: "C", + FunctionKey: "F", + StacktraceKey: "S", + LineEnding: base.LineEnding, + EncodeTime: base.EncodeTime, + EncodeDuration: base.EncodeDuration, + EncodeLevel: base.EncodeLevel, + EncodeCaller: func(EntryCaller, PrimitiveArrayEncoder) {}, + EncodeStacktrace: base.EncodeStacktrace, }, 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", @@ -472,19 +515,20 @@ func TestEncoderConfiguration(t *testing.T) { { desc: "handle no-op EncodeName", cfg: EncoderConfig{ - LevelKey: "L", - TimeKey: "T", - MessageKey: "M", - NameKey: "N", - CallerKey: "C", - FunctionKey: "F", - StacktraceKey: "S", - LineEnding: base.LineEnding, - EncodeTime: base.EncodeTime, - EncodeDuration: base.EncodeDuration, - EncodeLevel: base.EncodeLevel, - EncodeCaller: base.EncodeCaller, - EncodeName: func(string, PrimitiveArrayEncoder) {}, + LevelKey: "L", + TimeKey: "T", + MessageKey: "M", + NameKey: "N", + CallerKey: "C", + FunctionKey: "F", + StacktraceKey: "S", + LineEnding: base.LineEnding, + EncodeTime: base.EncodeTime, + EncodeDuration: base.EncodeDuration, + EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, + EncodeStacktrace: base.EncodeStacktrace, + EncodeName: func(string, PrimitiveArrayEncoder) {}, }, 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", @@ -492,18 +536,19 @@ func TestEncoderConfiguration(t *testing.T) { { desc: "use custom line separator", cfg: EncoderConfig{ - LevelKey: "L", - TimeKey: "T", - MessageKey: "M", - NameKey: "N", - CallerKey: "C", - FunctionKey: "F", - StacktraceKey: "S", - LineEnding: "\r\n", - EncodeTime: base.EncodeTime, - EncodeDuration: base.EncodeDuration, - EncodeLevel: base.EncodeLevel, - EncodeCaller: base.EncodeCaller, + LevelKey: "L", + TimeKey: "T", + MessageKey: "M", + NameKey: "N", + CallerKey: "C", + FunctionKey: "F", + StacktraceKey: "S", + LineEnding: "\r\n", + EncodeTime: base.EncodeTime, + EncodeDuration: base.EncodeDuration, + EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, + EncodeStacktrace: base.EncodeStacktrace, }, 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", @@ -511,17 +556,18 @@ func TestEncoderConfiguration(t *testing.T) { { desc: "omit line separator definition - fall back to default", cfg: EncoderConfig{ - LevelKey: "L", - TimeKey: "T", - MessageKey: "M", - NameKey: "N", - CallerKey: "C", - FunctionKey: "F", - StacktraceKey: "S", - EncodeTime: base.EncodeTime, - EncodeDuration: base.EncodeDuration, - EncodeLevel: base.EncodeLevel, - EncodeCaller: base.EncodeCaller, + LevelKey: "L", + TimeKey: "T", + MessageKey: "M", + NameKey: "N", + CallerKey: "C", + FunctionKey: "F", + StacktraceKey: "S", + EncodeTime: base.EncodeTime, + EncodeDuration: base.EncodeDuration, + EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, + EncodeStacktrace: base.EncodeStacktrace, }, 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/json_encoder.go b/zapcore/json_encoder.go index 9685169b2..4ea7ebf94 100644 --- a/zapcore/json_encoder.go +++ b/zapcore/json_encoder.go @@ -420,7 +420,14 @@ func (enc *jsonEncoder) EncodeEntry(ent Entry, fields []Field) (*buffer.Buffer, addFields(final, fields) final.closeOpenNamespaces() if ent.Stack != "" && final.StacktraceKey != "" { - final.AddString(final.StacktraceKey, ent.Stack) + final.addKey(final.StacktraceKey) + cur := final.buf.Len() + final.EncodeStacktrace(ent.Stack, final) + if cur == final.buf.Len() { + // User-supplied EncodeStacktrace was a no-op. Fall back to strings to + // keep output JSON valid. + final.AppendString(ent.Stack) + } } final.buf.AppendByte('}') final.buf.AppendString(final.LineEnding) diff --git a/zapcore/json_encoder_test.go b/zapcore/json_encoder_test.go index b2150256e..667df866f 100644 --- a/zapcore/json_encoder_test.go +++ b/zapcore/json_encoder_test.go @@ -126,17 +126,18 @@ func TestJSONEncodeEntry(t *testing.T) { } enc := zapcore.NewJSONEncoder(zapcore.EncoderConfig{ - MessageKey: "M", - LevelKey: "L", - TimeKey: "T", - NameKey: "N", - CallerKey: "C", - FunctionKey: "F", - StacktraceKey: "S", - EncodeLevel: zapcore.LowercaseLevelEncoder, - EncodeTime: zapcore.ISO8601TimeEncoder, - EncodeDuration: zapcore.SecondsDurationEncoder, - EncodeCaller: zapcore.ShortCallerEncoder, + MessageKey: "M", + LevelKey: "L", + TimeKey: "T", + NameKey: "N", + CallerKey: "C", + FunctionKey: "F", + StacktraceKey: "S", + EncodeLevel: zapcore.LowercaseLevelEncoder, + EncodeTime: zapcore.ISO8601TimeEncoder, + EncodeDuration: zapcore.SecondsDurationEncoder, + EncodeCaller: zapcore.ShortCallerEncoder, + EncodeStacktrace: zapcore.FullStacktraceEncoder, }) for _, tt := range tests { @@ -152,16 +153,17 @@ func TestJSONEncodeEntry(t *testing.T) { func TestNoEncodeLevelSupplied(t *testing.T) { enc := zapcore.NewJSONEncoder(zapcore.EncoderConfig{ - MessageKey: "M", - LevelKey: "L", - TimeKey: "T", - NameKey: "N", - CallerKey: "C", - FunctionKey: "F", - StacktraceKey: "S", - EncodeTime: zapcore.ISO8601TimeEncoder, - EncodeDuration: zapcore.SecondsDurationEncoder, - EncodeCaller: zapcore.ShortCallerEncoder, + MessageKey: "M", + LevelKey: "L", + TimeKey: "T", + NameKey: "N", + CallerKey: "C", + FunctionKey: "F", + StacktraceKey: "S", + EncodeTime: zapcore.ISO8601TimeEncoder, + EncodeDuration: zapcore.SecondsDurationEncoder, + EncodeCaller: zapcore.ShortCallerEncoder, + EncodeStacktrace: zapcore.FullStacktraceEncoder, }) ent := zapcore.Entry{ From ebc0c4f9cf415ae6f8fe3ff245cba9fdfd32c668 Mon Sep 17 00:00:00 2001 From: Alex Arwine Date: Tue, 10 Oct 2023 08:18:25 -0700 Subject: [PATCH 2/7] Prevent unconfigured stacktraceEncoder from panicing --- zapcore/json_encoder.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/zapcore/json_encoder.go b/zapcore/json_encoder.go index 4ea7ebf94..911915698 100644 --- a/zapcore/json_encoder.go +++ b/zapcore/json_encoder.go @@ -422,7 +422,14 @@ func (enc *jsonEncoder) EncodeEntry(ent Entry, fields []Field) (*buffer.Buffer, if ent.Stack != "" && final.StacktraceKey != "" { final.addKey(final.StacktraceKey) cur := final.buf.Len() - final.EncodeStacktrace(ent.Stack, final) + + // if no stacktrace encoder is provided, fall back to FullStacktraceEncoder to protect backwards compatibility + stacktraceEncoder := final.EncodeStacktrace + if stacktraceEncoder == nil { + stacktraceEncoder = FullStacktraceEncoder + } + + stacktraceEncoder(ent.Stack, final) if cur == final.buf.Len() { // User-supplied EncodeStacktrace was a no-op. Fall back to strings to // keep output JSON valid. From a0dbf50cfaeb62233d7e5acfbb3fb4a1bafb90c8 Mon Sep 17 00:00:00 2001 From: Alex Arwine Date: Tue, 10 Oct 2023 08:28:51 -0700 Subject: [PATCH 3/7] Adding ability to unmarshal text to a StacktraceEncoder for configs --- zapcore/encoder.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/zapcore/encoder.go b/zapcore/encoder.go index 993eed9c4..9fe4c7ca5 100644 --- a/zapcore/encoder.go +++ b/zapcore/encoder.go @@ -278,10 +278,20 @@ type StacktraceEncoder func(string, PrimitiveArrayEncoder) // FullStacktraceEncoder passes down the full stacktrace as a string to the enc func FullStacktraceEncoder(stacktrace string, enc PrimitiveArrayEncoder) { - // TODO: consider using a byte-oriented API to save an allocation. enc.AppendString(stacktrace) } +// UnmarshalText unmarshals text to a StacktraceEncoder. Currently, it will only default to FullStacktraceEncoder. +func (e *StacktraceEncoder) UnmarshalText(text []byte) error { + switch string(text) { + case "full": + *e = FullStacktraceEncoder + default: + *e = FullStacktraceEncoder + } + return nil +} + // A CallerEncoder serializes an EntryCaller to a primitive type. // // This function must make exactly one call From 57f339820edc62c30b8315085addafeeeffd8200 Mon Sep 17 00:00:00 2001 From: Alex Arwine Date: Tue, 10 Oct 2023 08:34:13 -0700 Subject: [PATCH 4/7] removing unneeded nil check This is now unneeded because `EncodeStacktrace` will not be nil --- zapcore/console_encoder.go | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/zapcore/console_encoder.go b/zapcore/console_encoder.go index f2d130001..aa1aae6ce 100644 --- a/zapcore/console_encoder.go +++ b/zapcore/console_encoder.go @@ -124,19 +124,15 @@ func (c consoleEncoder) EncodeEntry(ent Entry, fields []Field) (*buffer.Buffer, if ent.Stack != "" && c.StacktraceKey != "" { line.AppendByte('\n') - if c.EncodeStacktrace != nil { - arr = getSliceEncoder() - c.EncodeStacktrace(ent.Stack, arr) - for i := range arr.elems { - if i > 0 { - line.AppendString(c.ConsoleSeparator) - } - fmt.Fprint(line, arr.elems[i]) + arr = getSliceEncoder() + c.EncodeStacktrace(ent.Stack, arr) + for i := range arr.elems { + if i > 0 { + line.AppendString(c.ConsoleSeparator) } - putSliceEncoder(arr) - } else { - line.AppendString(ent.Stack) + fmt.Fprint(line, arr.elems[i]) } + putSliceEncoder(arr) } line.AppendString(c.LineEnding) From cad62a81a2cfb8dfa94e3d1bad05c18991f8626c Mon Sep 17 00:00:00 2001 From: Alex Arwine Date: Tue, 10 Oct 2023 09:56:40 -0700 Subject: [PATCH 5/7] Fix nil and noop EncodeStacktraces improve test cases --- zapcore/console_encoder.go | 6 ++++- zapcore/encoder_test.go | 46 ++++++++++++++++++++++++++++++++++++++ zapcore/json_encoder.go | 2 +- 3 files changed, 52 insertions(+), 2 deletions(-) diff --git a/zapcore/console_encoder.go b/zapcore/console_encoder.go index aa1aae6ce..9d119355b 100644 --- a/zapcore/console_encoder.go +++ b/zapcore/console_encoder.go @@ -125,7 +125,11 @@ func (c consoleEncoder) EncodeEntry(ent Entry, fields []Field) (*buffer.Buffer, line.AppendByte('\n') arr = getSliceEncoder() - c.EncodeStacktrace(ent.Stack, arr) + stacktraceEncoder := c.EncodeStacktrace + if stacktraceEncoder == nil { + stacktraceEncoder = FullStacktraceEncoder + } + stacktraceEncoder(ent.Stack, arr) for i := range arr.elems { if i > 0 { line.AppendString(c.ConsoleSeparator) diff --git a/zapcore/encoder_test.go b/zapcore/encoder_test.go index 4618ca137..be5086d9a 100644 --- a/zapcore/encoder_test.go +++ b/zapcore/encoder_test.go @@ -572,6 +572,48 @@ func TestEncoderConfiguration(t *testing.T) { 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, }, + { + desc: "ensure no error with noop EncodeStacktrace", + cfg: EncoderConfig{ + LevelKey: "L", + TimeKey: "T", + MessageKey: "M", + NameKey: "N", + CallerKey: "C", + FunctionKey: "F", + StacktraceKey: "S", + LineEnding: base.LineEnding, + EncodeTime: base.EncodeTime, + EncodeDuration: base.EncodeDuration, + EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, + EncodeStacktrace: func(string, PrimitiveArrayEncoder) {}, + }, + expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","S":""}` + "\n", + + expectedConsole: "0\tinfo\tmain\tfoo.go:42\tfoo.Foo\thello\n" + "\n", + }, + { + desc: "ensure no panic with nil EncodeStacktrace", + cfg: EncoderConfig{ + LevelKey: "L", + TimeKey: "T", + MessageKey: "M", + NameKey: "N", + CallerKey: "C", + FunctionKey: "F", + StacktraceKey: "S", + LineEnding: base.LineEnding, + EncodeTime: base.EncodeTime, + EncodeDuration: base.EncodeDuration, + EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, + EncodeStacktrace: nil, + }, + 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", + }, } for i, tt := range tests { @@ -744,6 +786,10 @@ func TestCallerEncoders(t *testing.T) { } } +func TestStacktraceEncoders(t *testing.T) { + +} + func TestNameEncoders(t *testing.T) { tests := []struct { name string diff --git a/zapcore/json_encoder.go b/zapcore/json_encoder.go index 911915698..dc9083a76 100644 --- a/zapcore/json_encoder.go +++ b/zapcore/json_encoder.go @@ -433,7 +433,7 @@ func (enc *jsonEncoder) EncodeEntry(ent Entry, fields []Field) (*buffer.Buffer, if cur == final.buf.Len() { // User-supplied EncodeStacktrace was a no-op. Fall back to strings to // keep output JSON valid. - final.AppendString(ent.Stack) + final.AppendString("") } } final.buf.AppendByte('}') From 449be6b49bf5a2a277e1957610dfb56fb9f85cf7 Mon Sep 17 00:00:00 2001 From: Alex Arwine Date: Wed, 11 Oct 2023 08:39:39 -0700 Subject: [PATCH 6/7] Update zapcore/encoder.go Co-authored-by: Abhinav Gupta --- zapcore/encoder.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zapcore/encoder.go b/zapcore/encoder.go index 9fe4c7ca5..cb6464bf7 100644 --- a/zapcore/encoder.go +++ b/zapcore/encoder.go @@ -276,7 +276,7 @@ func (e *DurationEncoder) UnmarshalText(text []byte) error { // to a PrimitiveArrayEncoder's Append* method. type StacktraceEncoder func(string, PrimitiveArrayEncoder) -// FullStacktraceEncoder passes down the full stacktrace as a string to the enc +// FullStacktraceEncoder passes down the full stacktrace as a string to the encoder. func FullStacktraceEncoder(stacktrace string, enc PrimitiveArrayEncoder) { enc.AppendString(stacktrace) } From d45920a2535d10dc22b32b89f9d3e4af84d2f512 Mon Sep 17 00:00:00 2001 From: Alex Arwine Date: Wed, 11 Oct 2023 08:40:11 -0700 Subject: [PATCH 7/7] Update zapcore/encoder.go Co-authored-by: Abhinav Gupta --- zapcore/encoder.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/zapcore/encoder.go b/zapcore/encoder.go index cb6464bf7..5ade814e8 100644 --- a/zapcore/encoder.go +++ b/zapcore/encoder.go @@ -281,7 +281,9 @@ func FullStacktraceEncoder(stacktrace string, enc PrimitiveArrayEncoder) { enc.AppendString(stacktrace) } -// UnmarshalText unmarshals text to a StacktraceEncoder. Currently, it will only default to FullStacktraceEncoder. +// UnmarshalText unmarshals a StacktraceEncoder from its name. +// The following names are supported: "full" +// Defaults to "full" for unknown names. func (e *StacktraceEncoder) UnmarshalText(text []byte) error { switch string(text) { case "full":