Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: uber-go/zap
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v1.15.0
Choose a base ref
...
head repository: uber-go/zap
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v1.16.0
Choose a head ref
  • 14 commits
  • 26 files changed
  • 11 contributors

Commits on May 7, 2020

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    c9ca999 View commit details

Commits on May 26, 2020

  1. Add zapgorm (#833)

    As discussed in #660
    moul authored May 26, 2020

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    e931a6b View commit details

Commits on Jun 10, 2020

  1. json: Don't panic for nil Encode{Time, Duration} (#835)

    Fixes #834
    
    The JSON encoder assumes that encoders for `time.Time` and
    `time.Duration` are always specified, which causes nil pointer
    dereference panics.
    
    Fix this by treating nil encoders for time and duration as no-ops. This
    will fall back to existing logic in the JSON encoder that handles no-op
    time and duration encoders.
    abhinav authored Jun 10, 2020

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    3640f92 View commit details

Commits on Jun 11, 2020

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    39aa3a1 View commit details

Commits on Jul 7, 2020

  1. Added zapcore.TimeEncoderOfLayout (#629)

    I added `zapcore.TimeEncoderOfFormat` to make it easy to build `TimeEncoder` of custom format.
    
    You can use it in code:
    
    ```go
    config := zap.NewDevelopmentConfig()
    config.EncoderConfig.EncodeTime = zapcore.TimeEncoderOfLayout("06/01/02 03:04pm")
    ```
    
    or, with JSON:
    
    ```json
    {
      "encoderConfig": {
        "timeEncoder": {
           "layout": "06/01/02 03:04pm"
         }
      }
    }
    ```
    
    or, with YAML:
    ```yaml
    encoderConfig:
      timeEncoder:
        layout: '06/01/02 03:04pm'
    ```
    tmshn authored Jul 7, 2020

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    275c926 View commit details

Commits on Jul 17, 2020

  1. Support configurable delimiter for console encoder (#697)

    It would be nice to have a configurable delimiter for console encoder, tab by default.
    
    For our cases, we prefer to have space as element delimiter.
    
    A custom console delimiter can be set through the `consoleSeparator` encoder configuration.
    lixingwang authored Jul 17, 2020

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    53a3870 View commit details

Commits on Aug 5, 2020

  1. consoleEncoder: put cloned jsonEncoder back to pool (#852)

    consoleEncoder clone a jsonEncoder in `writeContext`, but never put back to pool after use.
    This make zap do more memory allocations, and may increase gc time.
    wyxloading authored Aug 5, 2020

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    639461d View commit details

Commits on Aug 14, 2020

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    2314926 View commit details

Commits on Aug 18, 2020

  1. Honor CallerSkip when taking stack traces & add the StackSkip field (#…

    …843)
    
    * Honor `CallerSkip` when taking a stack trace. Both the caller and stack trace will now point to the same frame.
    * Add `StackSkip` which is similar to `Stack` but allows skipping frames from the top of the stack.
    
    This removes the internal behavior of skipping zap specific stack frames when taking a stack trace, and instead relies on the same behavior used to calculate the number of frames to skip for getting the caller to also skip frames in the stack trace.
    
    Fixes #512, fixes #727
    segevfiner authored Aug 18, 2020

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    2657839 View commit details

Commits on Aug 28, 2020

  1. Show "<nil>" for nil Stringer. (#854)

    In encodeStringer, instead of returning an error when a panic
    occurs when calling String() on a nil pointer, use the string value
    "<nil>" like the fmt package does.
    
    It is not always possible to handle this case by fixing the
    implementation of String to not panic. This requires implementing
    String with a pointer receiver, which doesn't work if you need to
    be able to call String on non-addressable values.
    andy-retailnext authored Aug 28, 2020

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    663c590 View commit details
  2. Update Stringer panic check to look like stdlib (#857)

    There's no behaviour changes, but there are a couple of refactorings:
     * Name the named return error `retErr`, and use explicit return
       values. The only purpose of the named return is for the panic
       handling.
     * Make the panic handling look more similar to the standard library
       and add a reference to the stdlib code in fmt that does the
       same checks.
    prashantv authored Aug 28, 2020

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    be2be86 View commit details

Commits on Sep 1, 2020

  1. Add options to customize Fatal behaviour for better testability (#861)

    Fixes #846.
    
    There's currently no easy way to test Fatal from outside of zap, as it
    triggers an os.Exit(1). Add options to customize the behaviour (allowing
    for panic, or Goexit), which can be used with `recover()` in tests.
    prashantv authored Sep 1, 2020

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    217b2cb View commit details
  2. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    d0a4b9e View commit details
  3. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    404189c View commit details
Showing with 732 additions and 191 deletions.
  1. +33 −2 CHANGELOG.md
  2. +1 −0 FAQ.md
  3. +2 −0 config.go
  4. +1 −1 config_test.go
  5. +7 −1 field.go
  6. +20 −1 field_test.go
  7. +1 −0 go.mod
  8. +1 −1 increase_level_test.go
  9. +37 −4 logger.go
  10. +139 −2 logger_test.go
  11. +13 −6 options.go
  12. +1 −1 sink.go
  13. +3 −44 stacktrace.go
  14. +24 −0 stacktrace_ext_test.go
  15. +29 −30 stacktrace_test.go
  16. +22 −8 zapcore/console_encoder.go
  17. +91 −0 zapcore/console_encoder_test.go
  18. +42 −0 zapcore/encoder.go
  19. +133 −56 zapcore/encoder_test.go
  20. +10 −4 zapcore/entry.go
  21. +40 −15 zapcore/entry_test.go
  22. +14 −4 zapcore/field.go
  23. +2 −1 zapcore/field_test.go
  24. +20 −10 zapcore/json_encoder.go
  25. +38 −0 zapcore/json_encoder_test.go
  26. +8 −0 zapcore/marshaler.go
35 changes: 33 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,37 @@
# Changelog

## 1.16.0 (1 Sep 2020)

Bugfixes:
* [#828][]: Fix missing newline in IncreaseLevel error messages.
* [#835][]: Fix panic in JSON encoder when encoding times or durations
without specifying a time or duration encoder.
* [#843][]: Honor CallerSkip when taking stack traces.
* [#862][]: Fix the default file permissions to use `0666` and rely on the umask instead.
* [#854][]: Encode `<nil>` for nil `Stringer` instead of a panic error log.

Enhancements:
* [#629][]: Added `zapcore.TimeEncoderOfLayout` to easily create time encoders
for custom layouts.
* [#697][]: Added support for a configurable delimiter in the console encoder.
* [#852][]: Optimize console encoder by pooling the underlying JSON encoder.
* [#844][]: Add ability to include the calling function as part of logs.
* [#843][]: Add `StackSkip` for including truncated stacks as a field.
* [#861][]: Add options to customize Fatal behaviour for better testability.

Thanks to @SteelPhase, @tmshn, @lixingwang, @wyxloading, @moul, @segevfiner, @andy-retailnext and @jcorbin for their contributions to this release.

## 1.15.0 (23 Apr 2020)

Bugfixes:
* [#804][]: Fix handling of `Time` values out of `UnixNano` range.
* [#812][]: Fix `IncreaseLevel` being reset after a call to `With`.
* [#812][]: Fix `IncreaseLevel` being reset after a call to `With`.

Enhancements:
* [#806][]: Add `WithCaller` option to supersede the `AddCaller` option. This
allows disabling annotation of log entries with caller information if
previously enabled with `AddCaller`.
* [#813][]: Deprecate `NewSampler` constructor in favor of
* [#813][]: Deprecate `NewSampler` constructor in favor of
`NewSamplerWithOptions` which supports a `SamplerHook` option. This option
adds support for monitoring sampling decisions through a hook.

@@ -399,3 +420,13 @@ upgrade to the upcoming stable release.
[#812]: https://github.com/uber-go/zap/pull/812
[#806]: https://github.com/uber-go/zap/pull/806
[#813]: https://github.com/uber-go/zap/pull/813
[#629]: https://github.com/uber-go/zap/pull/629
[#697]: https://github.com/uber-go/zap/pull/697
[#828]: https://github.com/uber-go/zap/pull/828
[#835]: https://github.com/uber-go/zap/pull/835
[#843]: https://github.com/uber-go/zap/pull/843
[#844]: https://github.com/uber-go/zap/pull/844
[#852]: https://github.com/uber-go/zap/pull/852
[#854]: https://github.com/uber-go/zap/pull/854
[#861]: https://github.com/uber-go/zap/pull/861
[#862]: https://github.com/uber-go/zap/pull/862
1 change: 1 addition & 0 deletions FAQ.md
Original file line number Diff line number Diff line change
@@ -149,6 +149,7 @@ We're aware of the following extensions, but haven't used them ourselves:
| `github.com/tchap/zapext` | Sentry, syslog |
| `github.com/fgrosse/zaptest` | Ginkgo |
| `github.com/blendle/zapdriver` | Stackdriver |
| `github.com/moul/zapgorm` | Gorm |

[go-proverbs]: https://go-proverbs.github.io/
[import-path]: https://golang.org/cmd/go/#hdr-Remote_import_paths
2 changes: 2 additions & 0 deletions config.go
Original file line number Diff line number Diff line change
@@ -101,6 +101,7 @@ func NewProductionEncoderConfig() zapcore.EncoderConfig {
LevelKey: "level",
NameKey: "logger",
CallerKey: "caller",
FunctionKey: zapcore.OmitKey,
MessageKey: "msg",
StacktraceKey: "stacktrace",
LineEnding: zapcore.DefaultLineEnding,
@@ -140,6 +141,7 @@ func NewDevelopmentEncoderConfig() zapcore.EncoderConfig {
LevelKey: "L",
NameKey: "N",
CallerKey: "C",
FunctionKey: zapcore.OmitKey,
MessageKey: "M",
StacktraceKey: "S",
LineEnding: zapcore.DefaultLineEnding,
2 changes: 1 addition & 1 deletion config_test.go
Original file line number Diff line number Diff line change
@@ -52,7 +52,7 @@ func TestConfig(t *testing.T) {
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" +
`testing.\w+`,
`go.uber.org/zap.TestConfig.\w+`,
},
}

8 changes: 7 additions & 1 deletion field.go
Original file line number Diff line number Diff line change
@@ -364,11 +364,17 @@ func Timep(key string, val *time.Time) Field {
// expensive (relatively speaking); this function both makes an allocation and
// takes about two microseconds.
func Stack(key string) Field {
return StackSkip(key, 1) // skip Stack
}

// StackSkip constructs a field similarly to Stack, but also skips the given
// number of frames from the top of the stacktrace.
func StackSkip(key string, skip int) Field {
// Returning the stacktrace as a string costs an allocation, but saves us
// from expanding the zapcore.Field union struct to include a byte slice. Since
// taking a stacktrace is already so expensive (~10us), the extra allocation
// is okay.
return String(key, takeStacktrace())
return String(key, takeStacktrace(skip+1)) // skip StackSkip
}

// Duration constructs a field with the given key and value. The encoder
21 changes: 20 additions & 1 deletion field_test.go
Original file line number Diff line number Diff line change
@@ -23,6 +23,7 @@ package zap
import (
"math"
"net"
"regexp"
"sync"
"testing"
"time"
@@ -259,6 +260,24 @@ func TestStackField(t *testing.T) {
f := Stack("stacktrace")
assert.Equal(t, "stacktrace", f.Key, "Unexpected field key.")
assert.Equal(t, zapcore.StringType, f.Type, "Unexpected field type.")
assert.Equal(t, takeStacktrace(), f.String, "Unexpected stack trace")
r := regexp.MustCompile(`field_test.go:(\d+)`)
assert.Equal(t, r.ReplaceAllString(takeStacktrace(0), "field_test.go"), r.ReplaceAllString(f.String, "field_test.go"), "Unexpected stack trace")
assertCanBeReused(t, f)
}

func TestStackSkipField(t *testing.T) {
f := StackSkip("stacktrace", 0)
assert.Equal(t, "stacktrace", f.Key, "Unexpected field key.")
assert.Equal(t, zapcore.StringType, f.Type, "Unexpected field type.")
r := regexp.MustCompile(`field_test.go:(\d+)`)
assert.Equal(t, r.ReplaceAllString(takeStacktrace(0), "field_test.go"), r.ReplaceAllString(f.String, "field_test.go"), f.String, "Unexpected stack trace")
assertCanBeReused(t, f)
}

func TestStackSkipFieldWithSkip(t *testing.T) {
f := StackSkip("stacktrace", 1)
assert.Equal(t, "stacktrace", f.Key, "Unexpected field key.")
assert.Equal(t, zapcore.StringType, f.Type, "Unexpected field type.")
assert.Equal(t, takeStacktrace(1), f.String, "Unexpected stack trace")
assertCanBeReused(t, f)
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -8,5 +8,6 @@ require (
go.uber.org/atomic v1.6.0
go.uber.org/multierr v1.5.0
golang.org/x/lint v0.0.0-20190930215403-16217165b5de
gopkg.in/yaml.v2 v2.2.2
honnef.co/go/tools v0.0.1-2019.2.3
)
2 changes: 1 addition & 1 deletion increase_level_test.go
Original file line number Diff line number Diff line change
@@ -58,7 +58,7 @@ func TestIncreaseLevelTryDecrease(t *testing.T) {
newLoggedEntry(ErrorLevel, "increase level error log"),
}, logs.AllUntimed(), "unexpected logs")
assert.Equal(t,
`failed to IncreaseLevel: invalid increase level, as level "info" is allowed by increased level, but not by existing core`,
"failed to IncreaseLevel: invalid increase level, as level \"info\" is allowed by increased level, but not by existing core\n",
errorOut.String(),
"unexpected error output",
)
41 changes: 37 additions & 4 deletions logger.go
Original file line number Diff line number Diff line change
@@ -49,6 +49,7 @@ type Logger struct {
addStack zapcore.LevelEnabler

callerSkip int
onFatal zapcore.CheckWriteAction // default is WriteThenFatal
}

// New constructs a new Logger from the provided zapcore.Core and Options. If
@@ -280,7 +281,13 @@ func (log *Logger) check(lvl zapcore.Level, msg string) *zapcore.CheckedEntry {
case zapcore.PanicLevel:
ce = ce.Should(ent, zapcore.WriteThenPanic)
case zapcore.FatalLevel:
ce = ce.Should(ent, zapcore.WriteThenFatal)
onFatal := log.onFatal
// Noop is the default value for CheckWriteAction, and it leads to
// continued execution after a Fatal which is unexpected.
if onFatal == zapcore.WriteThenNoop {
onFatal = zapcore.WriteThenFatal
}
ce = ce.Should(ent, onFatal)
case zapcore.DPanicLevel:
if log.development {
ce = ce.Should(ent, zapcore.WriteThenPanic)
@@ -297,15 +304,41 @@ func (log *Logger) check(lvl zapcore.Level, msg string) *zapcore.CheckedEntry {
// Thread the error output through to the CheckedEntry.
ce.ErrorOutput = log.errorOutput
if log.addCaller {
ce.Entry.Caller = zapcore.NewEntryCaller(runtime.Caller(log.callerSkip + callerSkipOffset))
if !ce.Entry.Caller.Defined {
frame, defined := getCallerFrame(log.callerSkip + callerSkipOffset)
if !defined {
fmt.Fprintf(log.errorOutput, "%v Logger.check error: failed to get caller\n", time.Now().UTC())
log.errorOutput.Sync()
}

ce.Entry.Caller = zapcore.EntryCaller{
Defined: defined,
PC: frame.PC,
File: frame.File,
Line: frame.Line,
Function: frame.Function,
}
}
if log.addStack.Enabled(ce.Entry.Level) {
ce.Entry.Stack = Stack("").String
ce.Entry.Stack = StackSkip("", log.callerSkip+callerSkipOffset).String
}

return ce
}

// getCallerFrame gets caller frame. The argument skip is the number of stack
// frames to ascend, with 0 identifying the caller of getCallerFrame. The
// boolean ok is false if it was not possible to recover the information.
//
// Note: This implementation is similar to runtime.Caller, but it returns the whole frame.
func getCallerFrame(skip int) (frame runtime.Frame, ok bool) {
const skipOffset = 2 // skip getCallerFrame and Callers

pc := make([]uintptr, 1)
numFrames := runtime.Callers(skip+skipOffset, pc[:])
if numFrames < 1 {
return
}

frame, _ = runtime.CallersFrames(pc).Next()
return frame, frame.PC != 0
}
141 changes: 139 additions & 2 deletions logger_test.go
Original file line number Diff line number Diff line change
@@ -366,10 +366,89 @@ func TestLoggerAddCaller(t *testing.T) {
}
}

func TestLoggerAddCallerFunction(t *testing.T) {
tests := []struct {
options []Option
loggerFunction string
sugaredFunction string
}{
{
options: opts(),
loggerFunction: "",
sugaredFunction: "",
},
{
options: opts(WithCaller(false)),
loggerFunction: "",
sugaredFunction: "",
},
{
options: opts(AddCaller()),
loggerFunction: "go.uber.org/zap.infoLog",
sugaredFunction: "go.uber.org/zap.infoLogSugared",
},
{
options: opts(AddCaller(), WithCaller(false)),
loggerFunction: "",
sugaredFunction: "",
},
{
options: opts(WithCaller(true)),
loggerFunction: "go.uber.org/zap.infoLog",
sugaredFunction: "go.uber.org/zap.infoLogSugared",
},
{
options: opts(WithCaller(true), WithCaller(false)),
loggerFunction: "",
sugaredFunction: "",
},
{
options: opts(AddCaller(), AddCallerSkip(1), AddCallerSkip(-1)),
loggerFunction: "go.uber.org/zap.infoLog",
sugaredFunction: "go.uber.org/zap.infoLogSugared",
},
{
options: opts(AddCaller(), AddCallerSkip(2)),
loggerFunction: "go.uber.org/zap.withLogger",
sugaredFunction: "go.uber.org/zap.withLogger",
},
{
options: opts(AddCaller(), AddCallerSkip(2), AddCallerSkip(3)),
loggerFunction: "runtime.goexit",
sugaredFunction: "runtime.goexit",
},
}
for _, tt := range tests {
withLogger(t, DebugLevel, tt.options, func(logger *Logger, logs *observer.ObservedLogs) {
// Make sure that sugaring and desugaring resets caller skip properly.
logger = logger.Sugar().Desugar()
infoLog(logger, "")
infoLogSugared(logger.Sugar(), "")
infoLog(logger.Sugar().Desugar(), "")

entries := logs.AllUntimed()
assert.Equal(t, 3, len(entries), "Unexpected number of logs written out.")
for _, entry := range []observer.LoggedEntry{entries[0], entries[2]} {
assert.Regexp(
t,
tt.loggerFunction,
entry.Entry.Caller.Function,
"Expected to find function name in output.",
)
}
assert.Regexp(
t,
tt.sugaredFunction,
entries[1].Entry.Caller.Function,
"Expected to find function name in output.",
)
})
}
}

func TestLoggerAddCallerFail(t *testing.T) {
errBuf := &ztest.Buffer{}
withLogger(t, DebugLevel, opts(AddCaller(), ErrorOutput(errBuf)), func(log *Logger, logs *observer.ObservedLogs) {
log.callerSkip = 1e3
withLogger(t, DebugLevel, opts(AddCaller(), AddCallerSkip(1e3), ErrorOutput(errBuf)), func(log *Logger, logs *observer.ObservedLogs) {
log.Info("Failure.")
assert.Regexp(
t,
@@ -382,6 +461,11 @@ func TestLoggerAddCallerFail(t *testing.T) {
logs.AllUntimed()[0].Entry.Message,
"Failure.",
"Expected original message to survive failures in runtime.Caller.")
assert.Equal(
t,
logs.AllUntimed()[0].Entry.Caller.Function,
"",
"Expected function name to be empty string.")
})
}

@@ -450,3 +534,56 @@ func TestLoggerConcurrent(t *testing.T) {
}
})
}

func TestLoggerCustomOnFatal(t *testing.T) {
tests := []struct {
msg string
onFatal zapcore.CheckWriteAction
recoverValue interface{}
}{
{
msg: "panic",
onFatal: zapcore.WriteThenPanic,
recoverValue: "fatal",
},
{
msg: "goexit",
onFatal: zapcore.WriteThenGoexit,
recoverValue: nil,
},
}

for _, tt := range tests {
t.Run(tt.msg, func(t *testing.T) {
withLogger(t, InfoLevel, opts(OnFatal(tt.onFatal)), func(logger *Logger, logs *observer.ObservedLogs) {

var finished bool
recovered := make(chan interface{})
go func() {
defer func() {
recovered <- recover()
}()

logger.Fatal("fatal")
finished = true
}()

assert.Equal(t, tt.recoverValue, <-recovered, "unexpected value from recover()")
assert.False(t, finished, "expect goroutine to not finish after Fatal")

assert.Equal(t, []observer.LoggedEntry{{
Entry: zapcore.Entry{Level: FatalLevel, Message: "fatal"},
Context: []Field{},
}}, logs.AllUntimed(), "unexpected logs")
})
})
}
}

func infoLog(logger *Logger, msg string, fields ...Field) {
logger.Info(msg, fields...)
}

func infoLogSugared(logger *SugaredLogger, args ...interface{}) {
logger.Info(args...)
}
Loading