Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for providing custom logger name encoders #465

Merged
merged 5 commits into from
Jul 22, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion zapcore/console_encoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,14 @@ func (c consoleEncoder) EncodeEntry(ent Entry, fields []Field) (*buffer.Buffer,
c.EncodeLevel(ent.Level, arr)
}
if ent.LoggerName != "" && c.NameKey != "" {
arr.AppendString(ent.LoggerName)
nameEncoder := c.EncodeName

if nameEncoder == nil {
// Fall back to FullNameEncoder for backward compatibility.
nameEncoder = FullNameEncoder
}

nameEncoder(ent.LoggerName, arr)
}
if ent.Caller.Defined && c.CallerKey != "" && c.EncodeCaller != nil {
c.EncodeCaller(ent.Caller, arr)
Expand Down
24 changes: 24 additions & 0 deletions zapcore/encoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,27 @@ func (e *CallerEncoder) UnmarshalText(text []byte) error {
return nil
}

// A NameEncoder serializes a period-separated logger name to a primitive
// type.
type NameEncoder func(string, PrimitiveArrayEncoder)

// FullNameEncoder serializes the logger name as-is.
func FullNameEncoder(loggerName string, enc PrimitiveArrayEncoder) {
enc.AppendString(loggerName)
}

// UnmarshalText unmarshals text to a NameEncoder. Currently, everything is
// unmarshaled to FullNameEncoder.
func (e *NameEncoder) UnmarshalText(text []byte) error {
switch string(text) {
case "full":
*e = FullNameEncoder
default:
*e = FullNameEncoder
}
return nil
}

// An EncoderConfig allows users to configure the concrete encoders supplied by
// zapcore.
type EncoderConfig struct {
Expand All @@ -215,6 +236,9 @@ type EncoderConfig struct {
EncodeTime TimeEncoder `json:"timeEncoder" yaml:"timeEncoder"`
EncodeDuration DurationEncoder `json:"durationEncoder" yaml:"durationEncoder"`
EncodeCaller CallerEncoder `json:"callerEncoder" yaml:"callerEncoder"`
// Unlike the other primitive type encoders, EncodeName is optional. The
// zero value falls back to FullNameEncoder.
EncodeName NameEncoder `json:"nameEncoder" yaml:"nameEncoder"`
}

// ObjectEncoder is a strongly-typed, encoding-agnostic interface for adding a
Expand Down
65 changes: 65 additions & 0 deletions zapcore/encoder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
package zapcore_test

import (
"strings"
"testing"
"time"

Expand Down Expand Up @@ -74,6 +75,10 @@ func withConsoleEncoder(f func(Encoder)) {
f(NewConsoleEncoder(humanEncoderConfig()))
}

func capitalNameEncoder(loggerName string, enc PrimitiveArrayEncoder) {
enc.AppendString(strings.ToUpper(loggerName))
}

func TestEncoderConfiguration(t *testing.T) {
base := testEncoderConfig()

Expand Down Expand Up @@ -293,6 +298,25 @@ func TestEncoderConfiguration(t *testing.T) {
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: "use the supplied EncodeName",
cfg: EncoderConfig{
LevelKey: "L",
TimeKey: "T",
MessageKey: "M",
NameKey: "N",
CallerKey: "C",
StacktraceKey: "S",
LineEnding: base.LineEnding,
EncodeTime: base.EncodeTime,
EncodeDuration: base.EncodeDuration,
EncodeLevel: base.EncodeLevel,
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",
},
{
desc: "close all open namespaces",
cfg: EncoderConfig{
Expand Down Expand Up @@ -393,6 +417,25 @@ func TestEncoderConfiguration(t *testing.T) {
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: "handle no-op EncodeName",
cfg: EncoderConfig{
LevelKey: "L",
TimeKey: "T",
MessageKey: "M",
NameKey: "N",
CallerKey: "C",
StacktraceKey: "S",
LineEnding: base.LineEnding,
EncodeTime: base.EncodeTime,
EncodeDuration: base.EncodeDuration,
EncodeLevel: base.EncodeLevel,
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",
},
{
desc: "use custom line separator",
cfg: EncoderConfig{
Expand Down Expand Up @@ -559,6 +602,28 @@ func TestCallerEncoders(t *testing.T) {
}
}

func TestNameEncoders(t *testing.T) {
tests := []struct {
name string
expected interface{} // output of encoding InfoLevel
}{
{"", "main"},
{"full", "main"},
{"something-random", "main"},
}

for _, tt := range tests {
var ne NameEncoder
require.NoError(t, ne.UnmarshalText([]byte(tt.name)), "Unexpected error unmarshaling %q.", tt.name)
assertAppended(
t,
tt.expected,
func(arr ArrayEncoder) { ne("main", arr) },
"Unexpected output serializing logger name 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 {
Expand Down
16 changes: 15 additions & 1 deletion zapcore/json_encoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,21 @@ func (enc *jsonEncoder) EncodeEntry(ent Entry, fields []Field) (*buffer.Buffer,
}
if ent.LoggerName != "" && final.NameKey != "" {
final.addKey(final.NameKey)
final.AppendString(ent.LoggerName)
cur := final.buf.Len()
nameEncoder := final.EncodeName

// if no name encoder provided, fall back to FullNameEncoder for backwards
// compatibility
if nameEncoder == nil {
nameEncoder = FullNameEncoder
}

nameEncoder(ent.LoggerName, final)
if cur == final.buf.Len() {
// User-supplied EncodeName was a no-op. Fall back to strings to
// keep output JSON valid.
final.AppendString(ent.LoggerName)
}
}
if ent.Caller.Defined && final.CallerKey != "" {
final.addKey(final.CallerKey)
Expand Down