diff --git a/example_test.go b/example_test.go index 327e3ef39..db03eec5b 100644 --- a/example_test.go +++ b/example_test.go @@ -358,3 +358,23 @@ func ExampleWrapCore_wrap() { // {"level":"info","msg":"doubled"} // {"level":"info","msg":"doubled"} } + +func ExampleDebugField() { + logger := zap.NewExample() + defer logger.Sync() + + logger.Info(".Info call filters out the debug field", + zap.Int("IntKey", 1), + zap.DebugField(zap.String("Key", "value")), + zap.Duration("DurationKey", time.Second), + ) + logger.Debug(".Debug call does NOT filter out the debug field", + zap.Int("IntKey", 1), + zap.DebugField(zap.String("Key", "value")), + zap.Duration("DurationKey", time.Second), + ) + + // Output: + // {"level":"info","msg":".Info call filters out the debug field","IntKey":1,"DurationKey":"1s"} + // {"level":"debug","msg":".Debug call does NOT filter out the debug field","IntKey":1,"Key":"value","DurationKey":"1s"} +} diff --git a/field.go b/field.go index bbb745db5..16ccc3527 100644 --- a/field.go +++ b/field.go @@ -35,8 +35,17 @@ type Field = zapcore.Field var ( _minTimeInt64 = time.Unix(0, math.MinInt64) _maxTimeInt64 = time.Unix(0, math.MaxInt64) + + debugLevelField = zapcore.DebugLevel ) +// DebugField wraps a field so that DebugLevel log displays it. +// See https://github.com/uber-go/zap/issues/1078 for motivation. +func DebugField(f Field) Field { + f.Level = &debugLevelField + return f +} + // Skip constructs a no-op field, which is often useful when handling invalid // inputs in other Field constructors. func Skip() Field { diff --git a/zapcore/entry.go b/zapcore/entry.go index 059844f92..ad90c93e2 100644 --- a/zapcore/entry.go +++ b/zapcore/entry.go @@ -249,8 +249,19 @@ func (ce *CheckedEntry) Write(fields ...Field) { ce.dirty = true var err error + + // https://github.com/golang/go/wiki/SliceTricks#filtering-without-allocating + filteredFields := fields[:0] + for _, field := range fields { + // include the field if the level is unset or + // the field level is higher than the entry's level. + if field.Level == nil || *field.Level >= ce.Entry.Level { + filteredFields = append(filteredFields, field) + } + } + for i := range ce.cores { - err = multierr.Append(err, ce.cores[i].Write(ce.Entry, fields)) + err = multierr.Append(err, ce.cores[i].Write(ce.Entry, filteredFields)) } if err != nil && ce.ErrorOutput != nil { fmt.Fprintf(ce.ErrorOutput, "%v write error: %v\n", ce.Time, err) @@ -259,7 +270,7 @@ func (ce *CheckedEntry) Write(fields ...Field) { hook := ce.after if hook != nil { - hook.OnWrite(ce, fields) + hook.OnWrite(ce, filteredFields) } putCheckedEntry(ce) } diff --git a/zapcore/field.go b/zapcore/field.go index 95bdb0a12..98f7955e6 100644 --- a/zapcore/field.go +++ b/zapcore/field.go @@ -106,6 +106,7 @@ type Field struct { Type FieldType Integer int64 String string + Level *Level Interface interface{} }