Skip to content

Commit

Permalink
Make caller serialization configurable (#327)
Browse files Browse the repository at this point in the history
Make the serialization of log callers configurable, and default to a more terse representation.
  • Loading branch information
skipor authored and akshayjshah committed Mar 14, 2017
1 parent 78c612c commit 640110d
Show file tree
Hide file tree
Showing 9 changed files with 183 additions and 61 deletions.
3 changes: 1 addition & 2 deletions benchmarks/zap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,7 @@ var _jane = user{

func newZapLogger(lvl zapcore.Level) *zap.Logger {
// use the canned production encoder configuration
cfg := zap.NewProductionConfig()
enc := zapcore.NewJSONEncoder(cfg.EncoderConfig)
enc := zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())
return zap.New(zapcore.NewCore(
enc,
&zaptest.Discarder{},
Expand Down
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.ShortCallerEncoder,
}
}

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

Expand Down
10 changes: 5 additions & 5 deletions config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,16 +40,16 @@ func TestConfig(t *testing.T) {
desc: "production",
cfg: NewProductionConfig(),
expectN: 2 + 100 + 1, // 2 from initial logs, 100 initial sampled logs, 1 from off-by-one in sampler
expectRe: `{"level":"info","caller":".*/go.uber.org/zap/config_test.go:\d+","msg":"info","k":"v","z":"zz"}` + "\n" +
`{"level":"warn","caller":".*/go.uber.org/zap/config_test.go:\d+","msg":"warn","k":"v","z":"zz"}` + "\n",
expectRe: `{"level":"info","caller":"zap/config_test.go:\d+","msg":"info","k":"v","z":"zz"}` + "\n" +
`{"level":"warn","caller":"zap/config_test.go:\d+","msg":"warn","k":"v","z":"zz"}` + "\n",
},
{
desc: "development",
cfg: NewDevelopmentConfig(),
expectN: 3 + 200, // 3 initial logs, all 200 subsequent logs
expectRe: "DEBUG\t.*go.uber.org/zap/config_test.go:" + `\d+` + "\tdebug\t" + `{"k": "v", "z": "zz"}` + "\n" +
"INFO\t.*go.uber.org/zap/config_test.go:" + `\d+` + "\tinfo\t" + `{"k": "v", "z": "zz"}` + "\n" +
"WARN\t.*go.uber.org/zap/config_test.go:" + `\d+` + "\twarn\t" + `{"k": "v", "z": "zz"}` + "\n" +
expectRe: "DEBUG\tzap/config_test.go:" + `\d+` + "\tdebug\t" + `{"k": "v", "z": "zz"}` + "\n" +
"INFO\tzap/config_test.go:" + `\d+` + "\tinfo\t" + `{"k": "v", "z": "zz"}` + "\n" +
"WARN\tzap/config_test.go:" + `\d+` + "\twarn\t" + `{"k": "v", "z": "zz"}` + "\n" +
`go.uber.org/zap.Stack`,
},
}
Expand Down
35 changes: 9 additions & 26 deletions zapcore/console_encoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,19 +66,25 @@ func (c consoleEncoder) Clone() Encoder {
func (c consoleEncoder) EncodeEntry(ent Entry, fields []Field) (*buffer.Buffer, error) {
line := bufferpool.Get()

// We don't want the date and level to be quoted and escaped (if they're
// We don't want the entry's metadata to be quoted and escaped (if it's
// encoded as strings), which means that we can't use the JSON encoder. The
// simplest option is to use the memory encoder and fmt.Fprint.
//
// If this ever becomes a performance bottleneck, we can implement
// ArrayEncoder for our plain-text format.
arr := getSliceEncoder()
if c.TimeKey != "" {
if c.TimeKey != "" && c.EncodeTime != nil {
c.EncodeTime(ent.Time, arr)
}
if c.LevelKey != "" {
if c.LevelKey != "" && c.EncodeLevel != nil {
c.EncodeLevel(ent.Level, arr)
}
if ent.LoggerName != "" && c.NameKey != "" {
arr.AppendString(ent.LoggerName)
}
if ent.Caller.Defined && c.CallerKey != "" && c.EncodeCaller != nil {
c.EncodeCaller(ent.Caller, arr)
}
for i := range arr.elems {
if i > 0 {
line.AppendByte('\t')
Expand All @@ -87,9 +93,6 @@ func (c consoleEncoder) EncodeEntry(ent Entry, fields []Field) (*buffer.Buffer,
}
putSliceEncoder(arr)

// Compose the logger name and caller info into a call site and add it.
c.writeCallSite(line, ent.LoggerName, ent.Caller)

// Add the message itself.
if c.MessageKey != "" {
c.addTabIfNecessary(line)
Expand All @@ -110,26 +113,6 @@ func (c consoleEncoder) EncodeEntry(ent Entry, fields []Field) (*buffer.Buffer,
return line, nil
}

func (c consoleEncoder) writeCallSite(line *buffer.Buffer, name string, caller EntryCaller) {
shouldWriteName := name != "" && c.NameKey != ""
shouldWriteCaller := caller.Defined && c.CallerKey != ""
if !shouldWriteName && !shouldWriteCaller {
return
}
c.addTabIfNecessary(line)
if shouldWriteName {
line.AppendString(name)
if shouldWriteCaller {
line.AppendByte('@')
}
}
if shouldWriteCaller {
line.AppendString(caller.File)
line.AppendByte(':')
line.AppendInt(int64(caller.Line))
}
}

func (c consoleEncoder) writeContext(line *buffer.Buffer, extra []Field) {
context := c.jsonEncoder.Clone().(*jsonEncoder)
defer bufferpool.Put(context.buf)
Expand Down
33 changes: 32 additions & 1 deletion zapcore/encoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,10 +162,40 @@ func (e *DurationEncoder) UnmarshalText(text []byte) error {
return nil
}

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

// FullCallerEncoder serializes a caller in /full/path/to/package/file:line
// format.
func FullCallerEncoder(caller EntryCaller, enc PrimitiveArrayEncoder) {
// TODO: consider using a byte-oriented API to save an allocation.
enc.AppendString(caller.String())
}

// ShortCallerEncoder serializes a caller in package/file:line format, trimming
// all but the final directory from the full path.
func ShortCallerEncoder(caller EntryCaller, enc PrimitiveArrayEncoder) {
// TODO: consider using a byte-oriented API to save an allocation.
enc.AppendString(caller.TrimmedPath())
}

// UnmarshalText unmarshals text to a CallerEncoder. "full" is unmarshaled to
// FullCallerEncoder and anything else is unmarshaled to ShortCallerEncoder.
func (e *CallerEncoder) UnmarshalText(text []byte) error {
switch string(text) {
case "full":
*e = FullCallerEncoder
default:
*e = ShortCallerEncoder
}
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.
// Set the keys used for each log entry. If any key is empty, that portion
// of the entry is omitted.
MessageKey string `json:"messageKey" yaml:"messageKey"`
LevelKey string `json:"levelKey" yaml:"levelKey"`
TimeKey string `json:"timeKey" yaml:"timeKey"`
Expand All @@ -178,6 +208,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
Loading

0 comments on commit 640110d

Please sign in to comment.