Skip to content

Commit

Permalink
Adding possibility to configure line separator for log lines by addin…
Browse files Browse the repository at this point in the history
…g LineSeparator to the encoder configuration
  • Loading branch information
alaczi authored and Andras Laczi committed May 4, 2017
1 parent 7641ffe commit 06bf851
Show file tree
Hide file tree
Showing 5 changed files with 102 additions and 36 deletions.
2 changes: 2 additions & 0 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ func NewProductionEncoderConfig() zapcore.EncoderConfig {
CallerKey: "caller",
MessageKey: "msg",
StacktraceKey: "stacktrace",
LineSeparator: zapcore.DefaultLineSeparator,
EncodeLevel: zapcore.LowercaseLevelEncoder,
EncodeTime: zapcore.EpochTimeEncoder,
EncodeDuration: zapcore.SecondsDurationEncoder,
Expand Down Expand Up @@ -124,6 +125,7 @@ func NewDevelopmentEncoderConfig() zapcore.EncoderConfig {
CallerKey: "C",
MessageKey: "M",
StacktraceKey: "S",
LineSeparator: zapcore.DefaultLineSeparator,
EncodeLevel: zapcore.CapitalLevelEncoder,
EncodeTime: zapcore.ISO8601TimeEncoder,
EncodeDuration: zapcore.StringDurationEncoder,
Expand Down
6 changes: 5 additions & 1 deletion zapcore/console_encoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,11 @@ func (c consoleEncoder) EncodeEntry(ent Entry, fields []Field) (*buffer.Buffer,
line.AppendString(ent.Stack)
}

line.AppendByte('\n')
if c.LineSeparator != "" {
line.AppendString(c.LineSeparator)
} else {
line.AppendString(DefaultLineSeparator)
}
return line, nil
}

Expand Down
5 changes: 5 additions & 0 deletions zapcore/encoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,10 @@ func (e *CallerEncoder) UnmarshalText(text []byte) error {
return nil
}

// DefaultLineSeparator defines the default line ending when writing logs
// Can be overwritten defining the LineSeparator in the EncoderConfig
const DefaultLineSeparator = "\n"

// An EncoderConfig allows users to configure the concrete encoders supplied by
// zapcore.
type EncoderConfig struct {
Expand All @@ -202,6 +206,7 @@ type EncoderConfig struct {
NameKey string `json:"nameKey" yaml:"nameKey"`
CallerKey string `json:"callerKey" yaml:"callerKey"`
StacktraceKey string `json:"stacktraceKey" yaml:"stacktraceKey"`
LineSeparator string `json:"lineSeparator" yaml:"lineSeparator"`
// 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.
Expand Down
119 changes: 85 additions & 34 deletions zapcore/encoder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ func testEncoderConfig() EncoderConfig {
TimeKey: "ts",
CallerKey: "caller",
StacktraceKey: "stacktrace",
LineSeparator: "\n",
EncodeTime: EpochTimeEncoder,
EncodeLevel: LowercaseLevelEncoder,
EncodeDuration: SecondsDurationEncoder,
Expand Down Expand Up @@ -91,8 +92,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"}`,
expectedConsole: "0\tinfo\tmain\tfoo.go:42\thello\\\nfake-stack",
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",
},
{
desc: "use custom entry keys in JSON output and ignore them in console output",
Expand All @@ -103,13 +104,14 @@ func TestEncoderConfiguration(t *testing.T) {
NameKey: "N",
CallerKey: "C",
StacktraceKey: "S",
LineSeparator: base.LineSeparator,
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\tfoo.go:42\thello\nfake-stack",
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 level if LevelKey is omitted",
Expand All @@ -120,13 +122,14 @@ func TestEncoderConfiguration(t *testing.T) {
NameKey: "N",
CallerKey: "C",
StacktraceKey: "S",
LineSeparator: base.LineSeparator,
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\tfoo.go:42\thello\nfake-stack",
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",
},
{
desc: "skip timestamp if TimeKey is omitted",
Expand All @@ -137,13 +140,14 @@ func TestEncoderConfiguration(t *testing.T) {
NameKey: "N",
CallerKey: "C",
StacktraceKey: "S",
LineSeparator: base.LineSeparator,
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\tfoo.go:42\thello\nfake-stack",
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",
},
{
desc: "skip message if MessageKey is omitted",
Expand All @@ -154,13 +158,14 @@ func TestEncoderConfiguration(t *testing.T) {
NameKey: "N",
CallerKey: "C",
StacktraceKey: "S",
LineSeparator: base.LineSeparator,
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\tfoo.go:42\nfake-stack",
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",
},
{
desc: "skip name if NameKey is omitted",
Expand All @@ -171,13 +176,14 @@ func TestEncoderConfiguration(t *testing.T) {
NameKey: "",
CallerKey: "C",
StacktraceKey: "S",
LineSeparator: base.LineSeparator,
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",
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",
},
{
desc: "skip caller if CallerKey is omitted",
Expand All @@ -188,13 +194,14 @@ func TestEncoderConfiguration(t *testing.T) {
NameKey: "N",
CallerKey: "",
StacktraceKey: "S",
LineSeparator: base.LineSeparator,
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",
expectedJSON: `{"L":"info","T":0,"N":"main","M":"hello","S":"fake-stack"}` + "\n",
expectedConsole: "0\tinfo\tmain\thello\nfake-stack\n",
},
{
desc: "skip stacktrace if StacktraceKey is omitted",
Expand All @@ -205,13 +212,14 @@ func TestEncoderConfiguration(t *testing.T) {
NameKey: "N",
CallerKey: "C",
StacktraceKey: "",
LineSeparator: base.LineSeparator,
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\tfoo.go:42\thello",
expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","M":"hello"}` + "\n",
expectedConsole: "0\tinfo\tmain\tfoo.go:42\thello\n",
},
{
desc: "use the supplied EncodeTime, for both the entry and any times added",
Expand All @@ -222,6 +230,7 @@ func TestEncoderConfiguration(t *testing.T) {
NameKey: "N",
CallerKey: "C",
StacktraceKey: "S",
LineSeparator: base.LineSeparator,
EncodeTime: func(t time.Time, enc PrimitiveArrayEncoder) { enc.AppendString(t.String()) },
EncodeDuration: base.EncodeDuration,
EncodeLevel: base.EncodeLevel,
Expand All @@ -234,10 +243,10 @@ 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"}`,
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
`{"extra": "1970-01-01 00:00:00 +0000 UTC", "extras": ["1970-01-01 00:00:00 +0000 UTC"]}` + // JSON context
"\nfake-stack", // stacktrace after newline
"\nfake-stack\n", // stacktrace after newline
},
{
desc: "use the supplied EncodeDuration for any durations added",
Expand All @@ -248,6 +257,7 @@ func TestEncoderConfiguration(t *testing.T) {
NameKey: "N",
CallerKey: "C",
StacktraceKey: "S",
LineSeparator: base.LineSeparator,
EncodeTime: base.EncodeTime,
EncodeDuration: StringDurationEncoder,
EncodeLevel: base.EncodeLevel,
Expand All @@ -260,10 +270,10 @@ 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"}`,
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
`{"extra": "1s", "extras": ["1m0s"]}` + // context
"\nfake-stack", // stacktrace
"\nfake-stack\n", // stacktrace
},
{
desc: "use the supplied EncodeLevel",
Expand All @@ -274,13 +284,14 @@ func TestEncoderConfiguration(t *testing.T) {
NameKey: "N",
CallerKey: "C",
StacktraceKey: "S",
LineSeparator: base.LineSeparator,
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\tfoo.go:42\thello\nfake-stack",
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: "close all open namespaces",
Expand All @@ -291,6 +302,7 @@ func TestEncoderConfiguration(t *testing.T) {
NameKey: "N",
CallerKey: "C",
StacktraceKey: "S",
LineSeparator: base.LineSeparator,
EncodeTime: base.EncodeTime,
EncodeDuration: base.EncodeDuration,
EncodeLevel: base.EncodeLevel,
Expand All @@ -302,10 +314,10 @@ 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"}`,
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" +
`{"outer": {"inner": {"foo": "bar", "innermost": {}}}}` +
"\nfake-stack",
"\nfake-stack\n",
},
{
desc: "handle no-op EncodeTime",
Expand All @@ -316,14 +328,15 @@ func TestEncoderConfiguration(t *testing.T) {
NameKey: "N",
CallerKey: "C",
StacktraceKey: "S",
LineSeparator: base.LineSeparator,
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"}`,
expectedConsole: "info\tmain\tfoo.go:42\thello\t" + `{"sometime": 100}` + "\nfake-stack",
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",
},
{
desc: "handle no-op EncodeDuration",
Expand All @@ -334,14 +347,15 @@ func TestEncoderConfiguration(t *testing.T) {
NameKey: "N",
CallerKey: "C",
StacktraceKey: "S",
LineSeparator: base.LineSeparator,
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"}`,
expectedConsole: "0\tinfo\tmain\tfoo.go:42\thello\t" + `{"someduration": 1000}` + "\nfake-stack",
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",
},
{
desc: "handle no-op EncodeLevel",
Expand All @@ -352,13 +366,14 @@ func TestEncoderConfiguration(t *testing.T) {
NameKey: "N",
CallerKey: "C",
StacktraceKey: "S",
LineSeparator: base.LineSeparator,
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\tfoo.go:42\thello\nfake-stack",
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",
},
{
desc: "handle no-op EncodeCaller",
Expand All @@ -369,13 +384,49 @@ func TestEncoderConfiguration(t *testing.T) {
NameKey: "N",
CallerKey: "C",
StacktraceKey: "S",
LineSeparator: base.LineSeparator,
EncodeTime: base.EncodeTime,
EncodeDuration: base.EncodeDuration,
EncodeLevel: base.EncodeLevel,
EncodeCaller: func(EntryCaller, PrimitiveArrayEncoder) {},
},
expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","M":"hello","S":"fake-stack"}`,
expectedConsole: "0\tinfo\tmain\thello\nfake-stack",
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",
},
{
desc: "use custom line separator",
cfg: EncoderConfig{
LevelKey: "L",
TimeKey: "T",
MessageKey: "M",
NameKey: "N",
CallerKey: "C",
StacktraceKey: "S",
LineSeparator: "\r\n",
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"}` + "\r\n",
expectedConsole: "0\tinfo\tmain\tfoo.go:42\thello\nfake-stack\r\n",
},
{
desc: "omit line separator definition - fall back to default",
cfg: EncoderConfig{
LevelKey: "L",
TimeKey: "T",
MessageKey: "M",
NameKey: "N",
CallerKey: "C",
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"}` + DefaultLineSeparator,
expectedConsole: "0\tinfo\tmain\tfoo.go:42\thello\nfake-stack" + DefaultLineSeparator,
},
}

Expand All @@ -394,7 +445,7 @@ func TestEncoderConfiguration(t *testing.T) {
if assert.NoError(t, jsonErr, "Unexpected error JSON-encoding entry in case #%d.", i) {
assert.Equal(
t,
tt.expectedJSON+"\n",
tt.expectedJSON,
jsonOut.String(),
"Unexpected JSON output: expected to %v.", tt.desc,
)
Expand All @@ -403,7 +454,7 @@ func TestEncoderConfiguration(t *testing.T) {
if assert.NoError(t, consoleErr, "Unexpected error console-encoding entry in case #%d.", i) {
assert.Equal(
t,
tt.expectedConsole+"\n",
tt.expectedConsole,
consoleOut.String(),
"Unexpected console output: expected to %v.", tt.desc,
)
Expand Down
Loading

0 comments on commit 06bf851

Please sign in to comment.