initial commit
This commit is contained in:
commit
0d92295142
|
@ -0,0 +1,120 @@
|
||||||
|
|
||||||
|
# Created by https://www.gitignore.io/api/intellij,go,linux,osx,windows
|
||||||
|
|
||||||
|
### Intellij ###
|
||||||
|
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
|
||||||
|
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||||
|
|
||||||
|
.idea
|
||||||
|
*.iml
|
||||||
|
|
||||||
|
## File-based project format:
|
||||||
|
*.ipr
|
||||||
|
*.iws
|
||||||
|
|
||||||
|
## Plugin-specific files:
|
||||||
|
|
||||||
|
# IntelliJ
|
||||||
|
/out/
|
||||||
|
|
||||||
|
# mpeltonen/sbt-idea plugin
|
||||||
|
.idea_modules/
|
||||||
|
|
||||||
|
# JIRA plugin
|
||||||
|
atlassian-ide-plugin.xml
|
||||||
|
|
||||||
|
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||||
|
com_crashlytics_export_strings.xml
|
||||||
|
crashlytics.properties
|
||||||
|
crashlytics-build.properties
|
||||||
|
fabric.properties
|
||||||
|
|
||||||
|
|
||||||
|
### Go ###
|
||||||
|
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||||
|
*.o
|
||||||
|
*.a
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Folders
|
||||||
|
_obj
|
||||||
|
_test
|
||||||
|
|
||||||
|
# Architecture specific extensions/prefixes
|
||||||
|
*.[568vq]
|
||||||
|
[568vq].out
|
||||||
|
|
||||||
|
*.cgo1.go
|
||||||
|
*.cgo2.c
|
||||||
|
_cgo_defun.c
|
||||||
|
_cgo_gotypes.go
|
||||||
|
_cgo_export.*
|
||||||
|
|
||||||
|
_testmain.go
|
||||||
|
|
||||||
|
*.exe
|
||||||
|
*.test
|
||||||
|
*.prof
|
||||||
|
|
||||||
|
|
||||||
|
### Linux ###
|
||||||
|
*~
|
||||||
|
*.swp
|
||||||
|
|
||||||
|
# temporary files which can be created if a process still has a handle open of a deleted file
|
||||||
|
.fuse_hidden*
|
||||||
|
|
||||||
|
# KDE directory preferences
|
||||||
|
.directory
|
||||||
|
|
||||||
|
# Linux trash folder which might appear on any partition or disk
|
||||||
|
.Trash-*
|
||||||
|
|
||||||
|
|
||||||
|
### OSX ###
|
||||||
|
.DS_Store
|
||||||
|
.AppleDouble
|
||||||
|
.LSOverride
|
||||||
|
|
||||||
|
# Icon must end with two \r
|
||||||
|
Icon
|
||||||
|
|
||||||
|
|
||||||
|
# Thumbnails
|
||||||
|
._*
|
||||||
|
|
||||||
|
# Files that might appear in the root of a volume
|
||||||
|
.DocumentRevisions-V100
|
||||||
|
.fseventsd
|
||||||
|
.Spotlight-V100
|
||||||
|
.TemporaryItems
|
||||||
|
.Trashes
|
||||||
|
.VolumeIcon.icns
|
||||||
|
|
||||||
|
# Directories potentially created on remote AFP share
|
||||||
|
.AppleDB
|
||||||
|
.AppleDesktop
|
||||||
|
Network Trash Folder
|
||||||
|
Temporary Items
|
||||||
|
.apdisk
|
||||||
|
|
||||||
|
|
||||||
|
### Windows ###
|
||||||
|
# Windows image file caches
|
||||||
|
Thumbs.db
|
||||||
|
ehthumbs.db
|
||||||
|
|
||||||
|
# Folder config file
|
||||||
|
Desktop.ini
|
||||||
|
|
||||||
|
# Recycle Bin used on file shares
|
||||||
|
$RECYCLE.BIN/
|
||||||
|
|
||||||
|
# Windows Installer files
|
||||||
|
*.cab
|
||||||
|
*.msi
|
||||||
|
*.msm
|
||||||
|
*.msp
|
||||||
|
|
||||||
|
# Windows shortcuts
|
||||||
|
*.lnk
|
|
@ -0,0 +1,33 @@
|
||||||
|
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||||
|
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "go.uber.org/atomic"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "4e336646b2ef9fc6e47be8e21594178f98e5ebcf"
|
||||||
|
version = "v1.2.0"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "go.uber.org/multierr"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "3c4937480c32f4c13a875a1829af76c98ca3d40a"
|
||||||
|
version = "v1.1.0"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "go.uber.org/zap"
|
||||||
|
packages = [".","buffer","internal/bufferpool","internal/color","internal/exit","zapcore"]
|
||||||
|
revision = "35aad584952c3e7020db7b839f6b102de6271f89"
|
||||||
|
version = "v1.7.1"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "gopkg.in/natefinch/lumberjack.v2"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "a96e63847dc3c67d17befa69c303767e2f84e54f"
|
||||||
|
version = "v2.1"
|
||||||
|
|
||||||
|
[solve-meta]
|
||||||
|
analyzer-name = "dep"
|
||||||
|
analyzer-version = 1
|
||||||
|
inputs-digest = "4e56bae68db45690431363f15d1dcc7b8a412c4522bab27b460e3f0797d4b1f1"
|
||||||
|
solver-name = "gps-cdcl"
|
||||||
|
solver-version = 1
|
|
@ -0,0 +1,30 @@
|
||||||
|
|
||||||
|
# Gopkg.toml example
|
||||||
|
#
|
||||||
|
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
|
||||||
|
# for detailed Gopkg.toml documentation.
|
||||||
|
#
|
||||||
|
# required = ["github.com/user/thing/cmd/thing"]
|
||||||
|
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
|
||||||
|
#
|
||||||
|
# [[constraint]]
|
||||||
|
# name = "github.com/user/project"
|
||||||
|
# version = "1.0.0"
|
||||||
|
#
|
||||||
|
# [[constraint]]
|
||||||
|
# name = "github.com/user/project2"
|
||||||
|
# branch = "dev"
|
||||||
|
# source = "github.com/myfork/project2"
|
||||||
|
#
|
||||||
|
# [[override]]
|
||||||
|
# name = "github.com/x/y"
|
||||||
|
# version = "2.4.0"
|
||||||
|
|
||||||
|
|
||||||
|
[[constraint]]
|
||||||
|
name = "go.uber.org/zap"
|
||||||
|
version = "1.7.1"
|
||||||
|
|
||||||
|
[[constraint]]
|
||||||
|
name = "gopkg.in/natefinch/lumberjack.v2"
|
||||||
|
version = "2.1.0"
|
|
@ -0,0 +1,6 @@
|
||||||
|
# Builds the project
|
||||||
|
setup:
|
||||||
|
go get -u github.com/golang/dep/cmd/dep
|
||||||
|
${GOPATH}/bin/dep ensure -v
|
||||||
|
|
||||||
|
.PHONY: clean install
|
|
@ -0,0 +1,51 @@
|
||||||
|
package rotater
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
var loggers RotateSyncerSet
|
||||||
|
|
||||||
|
func NewLogWriter(FileName string, MaxSizeMb, MaxBackup, MaxDay int, logDir string) RotateSyncer {
|
||||||
|
switch FileName {
|
||||||
|
case "Stdout":
|
||||||
|
return newLocked(os.Stdout)
|
||||||
|
case "Stderr":
|
||||||
|
return newLocked(os.Stderr)
|
||||||
|
default:
|
||||||
|
logpath := FileName
|
||||||
|
if logDir != "" {
|
||||||
|
logpath = path.Join(logDir, FileName)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println(" Attention!! log writes to ", logpath)
|
||||||
|
|
||||||
|
logWriter := newRotater(
|
||||||
|
logpath,
|
||||||
|
MaxSizeMb, // megabytes
|
||||||
|
MaxBackup,
|
||||||
|
MaxDay, //days
|
||||||
|
)
|
||||||
|
loggers.Store(logWriter)
|
||||||
|
logWriter.SetOnClose(func() { loggers.Delete(logWriter) })
|
||||||
|
return logWriter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Rotate() {
|
||||||
|
loggers.Range(func(rotater RotateSyncer) {
|
||||||
|
rotater.Sync()
|
||||||
|
rotater.Rotate()
|
||||||
|
})
|
||||||
|
log.Println("rotated")
|
||||||
|
}
|
||||||
|
|
||||||
|
func Close() {
|
||||||
|
loggers.Range(func(rotater RotateSyncer) {
|
||||||
|
rotater.Sync()
|
||||||
|
rotater.Close()
|
||||||
|
})
|
||||||
|
log.Println("end of log")
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
package rotater
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
logger set
|
||||||
|
*/
|
||||||
|
type RotateSyncerSet struct {
|
||||||
|
storage sync.Map
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *RotateSyncerSet) Delete(key RotateSyncer) {
|
||||||
|
s.storage.Delete(key)
|
||||||
|
}
|
||||||
|
func (s *RotateSyncerSet) Exist(key RotateSyncer) (ok bool) {
|
||||||
|
_, ok = s.storage.Load(key)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (s *RotateSyncerSet) SetNx(key RotateSyncer) (bool) {
|
||||||
|
_, exist := s.storage.LoadOrStore(key, 0)
|
||||||
|
return !exist
|
||||||
|
}
|
||||||
|
func (s *RotateSyncerSet) Range(f func(key RotateSyncer)) {
|
||||||
|
s.storage.Range(s.rangeWrap(f))
|
||||||
|
}
|
||||||
|
func (s *RotateSyncerSet) Store(key RotateSyncer) {
|
||||||
|
s.storage.Store(key, 0)
|
||||||
|
}
|
||||||
|
func (s *RotateSyncerSet) rangeWrap(f func(key RotateSyncer)) func(key, value interface{}) bool {
|
||||||
|
ok := true
|
||||||
|
return func(key, value interface{}) bool {
|
||||||
|
f(key.(RotateSyncer))
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *RotateSyncerSet) Len() int {
|
||||||
|
var count uint64
|
||||||
|
s.Range(func(conn RotateSyncer) {
|
||||||
|
atomic.AddUint64(&count, 1)
|
||||||
|
})
|
||||||
|
return int(count)
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
package rotater
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LockedWriteSyncer struct {
|
||||||
|
sync.Mutex
|
||||||
|
ws WriteSyncer
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lock wraps a WriteSyncer in a mutex to make it safe for concurrent use. In
|
||||||
|
// particular, *os.Files must be locked before use.
|
||||||
|
func newLocked(ws WriteSyncer) RotateSyncer {
|
||||||
|
if lws, ok := ws.(*LockedWriteSyncer); ok {
|
||||||
|
// no need to layer on another lock
|
||||||
|
return lws
|
||||||
|
}
|
||||||
|
return &LockedWriteSyncer{ws: ws}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *LockedWriteSyncer) Write(bs []byte) (int, error) {
|
||||||
|
s.Lock()
|
||||||
|
n, err := s.ws.Write(bs)
|
||||||
|
s.Unlock()
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *LockedWriteSyncer) Sync() error {
|
||||||
|
s.Lock()
|
||||||
|
err := s.ws.Sync()
|
||||||
|
s.Unlock()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LockedWriteSyncer) SetOnClose(closeFunc CloseFunc) {}
|
||||||
|
func (r *LockedWriteSyncer) Rotate() (err error) { return }
|
||||||
|
func (r *LockedWriteSyncer) Close() (err error) { return }
|
|
@ -0,0 +1,66 @@
|
||||||
|
package rotater
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gopkg.in/natefinch/lumberjack.v2"
|
||||||
|
"io"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CloseFunc func()
|
||||||
|
|
||||||
|
type WriteSyncer interface {
|
||||||
|
io.Writer
|
||||||
|
Sync() error
|
||||||
|
}
|
||||||
|
|
||||||
|
type RotateSyncer interface {
|
||||||
|
io.WriteCloser
|
||||||
|
SetOnClose(CloseFunc)
|
||||||
|
Rotate() error
|
||||||
|
Sync() error
|
||||||
|
}
|
||||||
|
|
||||||
|
type rotateSyncer struct {
|
||||||
|
setOnceOnclose *sync.Once
|
||||||
|
onClose CloseFunc
|
||||||
|
lumberjack.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRotater(filename string, maxSize, maxBackup, maxDay int) RotateSyncer {
|
||||||
|
return &rotateSyncer{
|
||||||
|
setOnceOnclose: &sync.Once{},
|
||||||
|
Logger: lumberjack.Logger{
|
||||||
|
Filename: filename,
|
||||||
|
MaxSize: maxSize, // megabytes
|
||||||
|
MaxBackups: maxBackup,
|
||||||
|
MaxAge: maxDay, //days
|
||||||
|
LocalTime: false,
|
||||||
|
Compress: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func (r *rotateSyncer) SetOnClose(closeFunc CloseFunc) {
|
||||||
|
r.setOnceOnclose.Do(func() {
|
||||||
|
r.onClose = closeFunc
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *rotateSyncer) Rotate() error {
|
||||||
|
return r.Logger.Rotate()
|
||||||
|
}
|
||||||
|
func (r *rotateSyncer) Close() error {
|
||||||
|
defer func() {
|
||||||
|
if r.onClose != nil {
|
||||||
|
r.onClose()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return r.Logger.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *rotateSyncer) Sync() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *rotateSyncer) Write(bs []byte) (int, error) {
|
||||||
|
return s.Logger.Write(bs)
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
Before opening your pull request, please make sure that you've:
|
||||||
|
|
||||||
|
- [ ] [signed Uber's Contributor License Agreement](https://docs.google.com/a/uber.com/forms/d/1pAwS_-dA1KhPlfxzYLBqK6rsSWwRwH95OCCZrcsY5rk/viewform);
|
||||||
|
- [ ] added tests to cover your changes;
|
||||||
|
- [ ] run the test suite locally (`make test`); and finally,
|
||||||
|
- [ ] run the linters locally (`make lint`).
|
||||||
|
|
||||||
|
Thanks for your contribution!
|
|
@ -0,0 +1,11 @@
|
||||||
|
.DS_Store
|
||||||
|
/vendor
|
||||||
|
/cover
|
||||||
|
cover.out
|
||||||
|
lint.log
|
||||||
|
|
||||||
|
# Binaries
|
||||||
|
*.test
|
||||||
|
|
||||||
|
# Profiling output
|
||||||
|
*.prof
|
|
@ -0,0 +1,21 @@
|
||||||
|
sudo: false
|
||||||
|
language: go
|
||||||
|
go_import_path: go.uber.org/atomic
|
||||||
|
|
||||||
|
go:
|
||||||
|
- 1.5
|
||||||
|
- 1.6
|
||||||
|
- tip
|
||||||
|
|
||||||
|
cache:
|
||||||
|
directories:
|
||||||
|
- vendor
|
||||||
|
|
||||||
|
install:
|
||||||
|
- make install_ci
|
||||||
|
|
||||||
|
script:
|
||||||
|
- make test_ci
|
||||||
|
- scripts/test-ubergo.sh
|
||||||
|
- make lint
|
||||||
|
- travis_retry goveralls -coverprofile=cover.out -service=travis-ci
|
|
@ -0,0 +1,19 @@
|
||||||
|
Copyright (c) 2016 Uber Technologies, Inc.
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
|
@ -0,0 +1,64 @@
|
||||||
|
PACKAGES := $(shell glide nv)
|
||||||
|
# Many Go tools take file globs or directories as arguments instead of packages.
|
||||||
|
PACKAGE_FILES ?= *.go
|
||||||
|
|
||||||
|
|
||||||
|
# The linting tools evolve with each Go version, so run them only on the latest
|
||||||
|
# stable release.
|
||||||
|
GO_VERSION := $(shell go version | cut -d " " -f 3)
|
||||||
|
GO_MINOR_VERSION := $(word 2,$(subst ., ,$(GO_VERSION)))
|
||||||
|
LINTABLE_MINOR_VERSIONS := 7 8
|
||||||
|
ifneq ($(filter $(LINTABLE_MINOR_VERSIONS),$(GO_MINOR_VERSION)),)
|
||||||
|
SHOULD_LINT := true
|
||||||
|
endif
|
||||||
|
|
||||||
|
|
||||||
|
export GO15VENDOREXPERIMENT=1
|
||||||
|
|
||||||
|
|
||||||
|
.PHONY: build
|
||||||
|
build:
|
||||||
|
go build -i $(PACKAGES)
|
||||||
|
|
||||||
|
|
||||||
|
.PHONY: install
|
||||||
|
install:
|
||||||
|
glide --version || go get github.com/Masterminds/glide
|
||||||
|
glide install
|
||||||
|
|
||||||
|
|
||||||
|
.PHONY: test
|
||||||
|
test:
|
||||||
|
go test -cover -race $(PACKAGES)
|
||||||
|
|
||||||
|
|
||||||
|
.PHONY: install_ci
|
||||||
|
install_ci: install
|
||||||
|
go get github.com/wadey/gocovmerge
|
||||||
|
go get github.com/mattn/goveralls
|
||||||
|
go get golang.org/x/tools/cmd/cover
|
||||||
|
ifdef SHOULD_LINT
|
||||||
|
go get github.com/golang/lint/golint
|
||||||
|
endif
|
||||||
|
|
||||||
|
.PHONY: lint
|
||||||
|
lint:
|
||||||
|
ifdef SHOULD_LINT
|
||||||
|
@rm -rf lint.log
|
||||||
|
@echo "Checking formatting..."
|
||||||
|
@gofmt -d -s $(PACKAGE_FILES) 2>&1 | tee lint.log
|
||||||
|
@echo "Checking vet..."
|
||||||
|
@$(foreach dir,$(PACKAGE_FILES),go tool vet $(dir) 2>&1 | tee -a lint.log;)
|
||||||
|
@echo "Checking lint..."
|
||||||
|
@$(foreach dir,$(PKGS),golint $(dir) 2>&1 | tee -a lint.log;)
|
||||||
|
@echo "Checking for unresolved FIXMEs..."
|
||||||
|
@git grep -i fixme | grep -v -e vendor -e Makefile | tee -a lint.log
|
||||||
|
@[ ! -s lint.log ]
|
||||||
|
else
|
||||||
|
@echo "Skipping linters on" $(GO_VERSION)
|
||||||
|
endif
|
||||||
|
|
||||||
|
|
||||||
|
.PHONY: test_ci
|
||||||
|
test_ci: install_ci build
|
||||||
|
./scripts/cover.sh $(shell go list $(PACKAGES))
|
|
@ -0,0 +1,34 @@
|
||||||
|
# atomic [![GoDoc][doc-img]][doc] [![Build Status][ci-img]][ci] [![Coverage Status][cov-img]][cov]
|
||||||
|
|
||||||
|
Simple wrappers for primitive types to enforce atomic access.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
`go get -u go.uber.org/atomic`
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
The standard library's `sync/atomic` is powerful, but it's easy to forget which
|
||||||
|
variables must be accessed atomically. `go.uber.org/atomic` preserves all the
|
||||||
|
functionality of the standard library, but wraps the primitive types to
|
||||||
|
provide a safer, more convenient API.
|
||||||
|
|
||||||
|
```go
|
||||||
|
var atom atomic.Uint32
|
||||||
|
atom.Store(42)
|
||||||
|
atom.Sub(2)
|
||||||
|
atom.CAS(40, 11)
|
||||||
|
```
|
||||||
|
|
||||||
|
See the [documentation][doc] for a complete API specification.
|
||||||
|
|
||||||
|
## Development Status
|
||||||
|
Stable.
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
Released under the [MIT License](LICENSE.txt).
|
||||||
|
|
||||||
|
[doc-img]: https://godoc.org/github.com/uber-go/atomic?status.svg
|
||||||
|
[doc]: https://godoc.org/go.uber.org/atomic
|
||||||
|
[ci-img]: https://travis-ci.org/uber-go/atomic.svg?branch=master
|
||||||
|
[ci]: https://travis-ci.org/uber-go/atomic
|
||||||
|
[cov-img]: https://coveralls.io/repos/github/uber-go/atomic/badge.svg?branch=master
|
||||||
|
[cov]: https://coveralls.io/github/uber-go/atomic?branch=master
|
|
@ -0,0 +1,304 @@
|
||||||
|
// Copyright (c) 2016 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
// Package atomic provides simple wrappers around numerics to enforce atomic
|
||||||
|
// access.
|
||||||
|
package atomic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"sync/atomic"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Int32 is an atomic wrapper around an int32.
|
||||||
|
type Int32 struct{ v int32 }
|
||||||
|
|
||||||
|
// NewInt32 creates an Int32.
|
||||||
|
func NewInt32(i int32) *Int32 {
|
||||||
|
return &Int32{i}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load atomically loads the wrapped value.
|
||||||
|
func (i *Int32) Load() int32 {
|
||||||
|
return atomic.LoadInt32(&i.v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add atomically adds to the wrapped int32 and returns the new value.
|
||||||
|
func (i *Int32) Add(n int32) int32 {
|
||||||
|
return atomic.AddInt32(&i.v, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sub atomically subtracts from the wrapped int32 and returns the new value.
|
||||||
|
func (i *Int32) Sub(n int32) int32 {
|
||||||
|
return atomic.AddInt32(&i.v, -n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inc atomically increments the wrapped int32 and returns the new value.
|
||||||
|
func (i *Int32) Inc() int32 {
|
||||||
|
return i.Add(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dec atomically decrements the wrapped int32 and returns the new value.
|
||||||
|
func (i *Int32) Dec() int32 {
|
||||||
|
return i.Sub(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CAS is an atomic compare-and-swap.
|
||||||
|
func (i *Int32) CAS(old, new int32) bool {
|
||||||
|
return atomic.CompareAndSwapInt32(&i.v, old, new)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store atomically stores the passed value.
|
||||||
|
func (i *Int32) Store(n int32) {
|
||||||
|
atomic.StoreInt32(&i.v, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Swap atomically swaps the wrapped int32 and returns the old value.
|
||||||
|
func (i *Int32) Swap(n int32) int32 {
|
||||||
|
return atomic.SwapInt32(&i.v, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int64 is an atomic wrapper around an int64.
|
||||||
|
type Int64 struct{ v int64 }
|
||||||
|
|
||||||
|
// NewInt64 creates an Int64.
|
||||||
|
func NewInt64(i int64) *Int64 {
|
||||||
|
return &Int64{i}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load atomically loads the wrapped value.
|
||||||
|
func (i *Int64) Load() int64 {
|
||||||
|
return atomic.LoadInt64(&i.v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add atomically adds to the wrapped int64 and returns the new value.
|
||||||
|
func (i *Int64) Add(n int64) int64 {
|
||||||
|
return atomic.AddInt64(&i.v, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sub atomically subtracts from the wrapped int64 and returns the new value.
|
||||||
|
func (i *Int64) Sub(n int64) int64 {
|
||||||
|
return atomic.AddInt64(&i.v, -n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inc atomically increments the wrapped int64 and returns the new value.
|
||||||
|
func (i *Int64) Inc() int64 {
|
||||||
|
return i.Add(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dec atomically decrements the wrapped int64 and returns the new value.
|
||||||
|
func (i *Int64) Dec() int64 {
|
||||||
|
return i.Sub(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CAS is an atomic compare-and-swap.
|
||||||
|
func (i *Int64) CAS(old, new int64) bool {
|
||||||
|
return atomic.CompareAndSwapInt64(&i.v, old, new)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store atomically stores the passed value.
|
||||||
|
func (i *Int64) Store(n int64) {
|
||||||
|
atomic.StoreInt64(&i.v, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Swap atomically swaps the wrapped int64 and returns the old value.
|
||||||
|
func (i *Int64) Swap(n int64) int64 {
|
||||||
|
return atomic.SwapInt64(&i.v, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint32 is an atomic wrapper around an uint32.
|
||||||
|
type Uint32 struct{ v uint32 }
|
||||||
|
|
||||||
|
// NewUint32 creates a Uint32.
|
||||||
|
func NewUint32(i uint32) *Uint32 {
|
||||||
|
return &Uint32{i}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load atomically loads the wrapped value.
|
||||||
|
func (i *Uint32) Load() uint32 {
|
||||||
|
return atomic.LoadUint32(&i.v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add atomically adds to the wrapped uint32 and returns the new value.
|
||||||
|
func (i *Uint32) Add(n uint32) uint32 {
|
||||||
|
return atomic.AddUint32(&i.v, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sub atomically subtracts from the wrapped uint32 and returns the new value.
|
||||||
|
func (i *Uint32) Sub(n uint32) uint32 {
|
||||||
|
return atomic.AddUint32(&i.v, ^(n - 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inc atomically increments the wrapped uint32 and returns the new value.
|
||||||
|
func (i *Uint32) Inc() uint32 {
|
||||||
|
return i.Add(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dec atomically decrements the wrapped int32 and returns the new value.
|
||||||
|
func (i *Uint32) Dec() uint32 {
|
||||||
|
return i.Sub(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CAS is an atomic compare-and-swap.
|
||||||
|
func (i *Uint32) CAS(old, new uint32) bool {
|
||||||
|
return atomic.CompareAndSwapUint32(&i.v, old, new)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store atomically stores the passed value.
|
||||||
|
func (i *Uint32) Store(n uint32) {
|
||||||
|
atomic.StoreUint32(&i.v, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Swap atomically swaps the wrapped uint32 and returns the old value.
|
||||||
|
func (i *Uint32) Swap(n uint32) uint32 {
|
||||||
|
return atomic.SwapUint32(&i.v, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint64 is an atomic wrapper around a uint64.
|
||||||
|
type Uint64 struct{ v uint64 }
|
||||||
|
|
||||||
|
// NewUint64 creates a Uint64.
|
||||||
|
func NewUint64(i uint64) *Uint64 {
|
||||||
|
return &Uint64{i}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load atomically loads the wrapped value.
|
||||||
|
func (i *Uint64) Load() uint64 {
|
||||||
|
return atomic.LoadUint64(&i.v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add atomically adds to the wrapped uint64 and returns the new value.
|
||||||
|
func (i *Uint64) Add(n uint64) uint64 {
|
||||||
|
return atomic.AddUint64(&i.v, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sub atomically subtracts from the wrapped uint64 and returns the new value.
|
||||||
|
func (i *Uint64) Sub(n uint64) uint64 {
|
||||||
|
return atomic.AddUint64(&i.v, ^(n - 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inc atomically increments the wrapped uint64 and returns the new value.
|
||||||
|
func (i *Uint64) Inc() uint64 {
|
||||||
|
return i.Add(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dec atomically decrements the wrapped uint64 and returns the new value.
|
||||||
|
func (i *Uint64) Dec() uint64 {
|
||||||
|
return i.Sub(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CAS is an atomic compare-and-swap.
|
||||||
|
func (i *Uint64) CAS(old, new uint64) bool {
|
||||||
|
return atomic.CompareAndSwapUint64(&i.v, old, new)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store atomically stores the passed value.
|
||||||
|
func (i *Uint64) Store(n uint64) {
|
||||||
|
atomic.StoreUint64(&i.v, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Swap atomically swaps the wrapped uint64 and returns the old value.
|
||||||
|
func (i *Uint64) Swap(n uint64) uint64 {
|
||||||
|
return atomic.SwapUint64(&i.v, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bool is an atomic Boolean.
|
||||||
|
type Bool struct{ v uint32 }
|
||||||
|
|
||||||
|
// NewBool creates a Bool.
|
||||||
|
func NewBool(initial bool) *Bool {
|
||||||
|
return &Bool{boolToInt(initial)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load atomically loads the Boolean.
|
||||||
|
func (b *Bool) Load() bool {
|
||||||
|
return truthy(atomic.LoadUint32(&b.v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store atomically stores the passed value.
|
||||||
|
func (b *Bool) Store(new bool) {
|
||||||
|
atomic.StoreUint32(&b.v, boolToInt(new))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Swap sets the given value and returns the previous value.
|
||||||
|
func (b *Bool) Swap(new bool) bool {
|
||||||
|
return truthy(atomic.SwapUint32(&b.v, boolToInt(new)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toggle atomically negates the Boolean and returns the previous value.
|
||||||
|
func (b *Bool) Toggle() bool {
|
||||||
|
return truthy(atomic.AddUint32(&b.v, 1) - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func truthy(n uint32) bool {
|
||||||
|
return n&1 == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func boolToInt(b bool) uint32 {
|
||||||
|
if b {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float64 is an atomic wrapper around float64.
|
||||||
|
type Float64 struct {
|
||||||
|
v uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFloat64 creates a Float64.
|
||||||
|
func NewFloat64(f float64) *Float64 {
|
||||||
|
return &Float64{math.Float64bits(f)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load atomically loads the wrapped value.
|
||||||
|
func (f *Float64) Load() float64 {
|
||||||
|
return math.Float64frombits(atomic.LoadUint64(&f.v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store atomically stores the passed value.
|
||||||
|
func (f *Float64) Store(s float64) {
|
||||||
|
atomic.StoreUint64(&f.v, math.Float64bits(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add atomically adds to the wrapped float64 and returns the new value.
|
||||||
|
func (f *Float64) Add(s float64) float64 {
|
||||||
|
for {
|
||||||
|
old := f.Load()
|
||||||
|
new := old + s
|
||||||
|
if f.CAS(old, new) {
|
||||||
|
return new
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sub atomically subtracts from the wrapped float64 and returns the new value.
|
||||||
|
func (f *Float64) Sub(s float64) float64 {
|
||||||
|
return f.Add(-s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CAS is an atomic compare-and-swap.
|
||||||
|
func (f *Float64) CAS(old, new float64) bool {
|
||||||
|
return atomic.CompareAndSwapUint64(&f.v, math.Float64bits(old), math.Float64bits(new))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value shadows the type of the same name from sync/atomic
|
||||||
|
// https://godoc.org/sync/atomic#Value
|
||||||
|
type Value struct{ atomic.Value }
|
|
@ -0,0 +1,147 @@
|
||||||
|
// Copyright (c) 2016 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
package atomic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestInt32(t *testing.T) {
|
||||||
|
atom := NewInt32(42)
|
||||||
|
|
||||||
|
require.Equal(t, int32(42), atom.Load(), "Load didn't work.")
|
||||||
|
require.Equal(t, int32(46), atom.Add(4), "Add didn't work.")
|
||||||
|
require.Equal(t, int32(44), atom.Sub(2), "Sub didn't work.")
|
||||||
|
require.Equal(t, int32(45), atom.Inc(), "Inc didn't work.")
|
||||||
|
require.Equal(t, int32(44), atom.Dec(), "Dec didn't work.")
|
||||||
|
|
||||||
|
require.True(t, atom.CAS(44, 0), "CAS didn't report a swap.")
|
||||||
|
require.Equal(t, int32(0), atom.Load(), "CAS didn't set the correct value.")
|
||||||
|
|
||||||
|
require.Equal(t, int32(0), atom.Swap(1), "Swap didn't return the old value.")
|
||||||
|
require.Equal(t, int32(1), atom.Load(), "Swap didn't set the correct value.")
|
||||||
|
|
||||||
|
atom.Store(42)
|
||||||
|
require.Equal(t, int32(42), atom.Load(), "Store didn't set the correct value.")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInt64(t *testing.T) {
|
||||||
|
atom := NewInt64(42)
|
||||||
|
|
||||||
|
require.Equal(t, int64(42), atom.Load(), "Load didn't work.")
|
||||||
|
require.Equal(t, int64(46), atom.Add(4), "Add didn't work.")
|
||||||
|
require.Equal(t, int64(44), atom.Sub(2), "Sub didn't work.")
|
||||||
|
require.Equal(t, int64(45), atom.Inc(), "Inc didn't work.")
|
||||||
|
require.Equal(t, int64(44), atom.Dec(), "Dec didn't work.")
|
||||||
|
|
||||||
|
require.True(t, atom.CAS(44, 0), "CAS didn't report a swap.")
|
||||||
|
require.Equal(t, int64(0), atom.Load(), "CAS didn't set the correct value.")
|
||||||
|
|
||||||
|
require.Equal(t, int64(0), atom.Swap(1), "Swap didn't return the old value.")
|
||||||
|
require.Equal(t, int64(1), atom.Load(), "Swap didn't set the correct value.")
|
||||||
|
|
||||||
|
atom.Store(42)
|
||||||
|
require.Equal(t, int64(42), atom.Load(), "Store didn't set the correct value.")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUint32(t *testing.T) {
|
||||||
|
atom := NewUint32(42)
|
||||||
|
|
||||||
|
require.Equal(t, uint32(42), atom.Load(), "Load didn't work.")
|
||||||
|
require.Equal(t, uint32(46), atom.Add(4), "Add didn't work.")
|
||||||
|
require.Equal(t, uint32(44), atom.Sub(2), "Sub didn't work.")
|
||||||
|
require.Equal(t, uint32(45), atom.Inc(), "Inc didn't work.")
|
||||||
|
require.Equal(t, uint32(44), atom.Dec(), "Dec didn't work.")
|
||||||
|
|
||||||
|
require.True(t, atom.CAS(44, 0), "CAS didn't report a swap.")
|
||||||
|
require.Equal(t, uint32(0), atom.Load(), "CAS didn't set the correct value.")
|
||||||
|
|
||||||
|
require.Equal(t, uint32(0), atom.Swap(1), "Swap didn't return the old value.")
|
||||||
|
require.Equal(t, uint32(1), atom.Load(), "Swap didn't set the correct value.")
|
||||||
|
|
||||||
|
atom.Store(42)
|
||||||
|
require.Equal(t, uint32(42), atom.Load(), "Store didn't set the correct value.")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUint64(t *testing.T) {
|
||||||
|
atom := NewUint64(42)
|
||||||
|
|
||||||
|
require.Equal(t, uint64(42), atom.Load(), "Load didn't work.")
|
||||||
|
require.Equal(t, uint64(46), atom.Add(4), "Add didn't work.")
|
||||||
|
require.Equal(t, uint64(44), atom.Sub(2), "Sub didn't work.")
|
||||||
|
require.Equal(t, uint64(45), atom.Inc(), "Inc didn't work.")
|
||||||
|
require.Equal(t, uint64(44), atom.Dec(), "Dec didn't work.")
|
||||||
|
|
||||||
|
require.True(t, atom.CAS(44, 0), "CAS didn't report a swap.")
|
||||||
|
require.Equal(t, uint64(0), atom.Load(), "CAS didn't set the correct value.")
|
||||||
|
|
||||||
|
require.Equal(t, uint64(0), atom.Swap(1), "Swap didn't return the old value.")
|
||||||
|
require.Equal(t, uint64(1), atom.Load(), "Swap didn't set the correct value.")
|
||||||
|
|
||||||
|
atom.Store(42)
|
||||||
|
require.Equal(t, uint64(42), atom.Load(), "Store didn't set the correct value.")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBool(t *testing.T) {
|
||||||
|
atom := NewBool(false)
|
||||||
|
require.False(t, atom.Toggle(), "Expected swap to return previous value.")
|
||||||
|
require.True(t, atom.Load(), "Unexpected state after swap.")
|
||||||
|
|
||||||
|
atom.Store(false)
|
||||||
|
require.False(t, atom.Load(), "Unexpected state after store.")
|
||||||
|
|
||||||
|
prev := atom.Swap(false)
|
||||||
|
require.False(t, prev, "Expected Swap to return previous value.")
|
||||||
|
|
||||||
|
prev = atom.Swap(true)
|
||||||
|
require.False(t, prev, "Expected Swap to return previous value.")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFloat64(t *testing.T) {
|
||||||
|
atom := NewFloat64(4.2)
|
||||||
|
|
||||||
|
require.Equal(t, float64(4.2), atom.Load(), "Load didn't work.")
|
||||||
|
|
||||||
|
require.True(t, atom.CAS(4.2, 0.5), "CAS didn't report a swap.")
|
||||||
|
require.Equal(t, float64(0.5), atom.Load(), "CAS didn't set the correct value.")
|
||||||
|
require.False(t, atom.CAS(0.0, 1.5), "CAS reported a swap.")
|
||||||
|
|
||||||
|
atom.Store(42.0)
|
||||||
|
require.Equal(t, float64(42.0), atom.Load(), "Store didn't set the correct value.")
|
||||||
|
require.Equal(t, float64(42.5), atom.Add(0.5), "Add didn't work.")
|
||||||
|
require.Equal(t, float64(42.0), atom.Sub(0.5), "Sub didn't work.")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValue(t *testing.T) {
|
||||||
|
var v Value
|
||||||
|
assert.Nil(t, v.Load(), "initial Value is not nil")
|
||||||
|
|
||||||
|
v.Store(42)
|
||||||
|
assert.Equal(t, 42, v.Load())
|
||||||
|
|
||||||
|
v.Store(84)
|
||||||
|
assert.Equal(t, 84, v.Load())
|
||||||
|
|
||||||
|
assert.Panics(t, func() { v.Store("foo") })
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
// Copyright (c) 2016 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
package atomic_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"go.uber.org/atomic"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Example() {
|
||||||
|
// Uint32 is a thin wrapper around the primitive uint32 type.
|
||||||
|
var atom atomic.Uint32
|
||||||
|
|
||||||
|
// The wrapper ensures that all operations are atomic.
|
||||||
|
atom.Store(42)
|
||||||
|
fmt.Println(atom.Inc())
|
||||||
|
fmt.Println(atom.CAS(43, 0))
|
||||||
|
fmt.Println(atom.Load())
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// 43
|
||||||
|
// true
|
||||||
|
// 0
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
hash: f14d51408e3e0e4f73b34e4039484c78059cd7fc5f4996fdd73db20dc8d24f53
|
||||||
|
updated: 2016-10-27T00:10:51.16960137-07:00
|
||||||
|
imports: []
|
||||||
|
testImports:
|
||||||
|
- name: github.com/davecgh/go-spew
|
||||||
|
version: 5215b55f46b2b919f50a1df0eaa5886afe4e3b3d
|
||||||
|
subpackages:
|
||||||
|
- spew
|
||||||
|
- name: github.com/pmezard/go-difflib
|
||||||
|
version: d8ed2627bdf02c080bf22230dbb337003b7aba2d
|
||||||
|
subpackages:
|
||||||
|
- difflib
|
||||||
|
- name: github.com/stretchr/testify
|
||||||
|
version: d77da356e56a7428ad25149ca77381849a6a5232
|
||||||
|
subpackages:
|
||||||
|
- assert
|
||||||
|
- require
|
|
@ -0,0 +1,6 @@
|
||||||
|
package: go.uber.org/atomic
|
||||||
|
testImport:
|
||||||
|
- package: github.com/stretchr/testify
|
||||||
|
subpackages:
|
||||||
|
- assert
|
||||||
|
- require
|
|
@ -0,0 +1,40 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
COVER=cover
|
||||||
|
ROOT_PKG=go.uber.org/atomic
|
||||||
|
|
||||||
|
if [[ -d "$COVER" ]]; then
|
||||||
|
rm -rf "$COVER"
|
||||||
|
fi
|
||||||
|
mkdir -p "$COVER"
|
||||||
|
|
||||||
|
i=0
|
||||||
|
for pkg in "$@"; do
|
||||||
|
i=$((i + 1))
|
||||||
|
|
||||||
|
extracoverpkg=""
|
||||||
|
if [[ -f "$GOPATH/src/$pkg/.extra-coverpkg" ]]; then
|
||||||
|
extracoverpkg=$( \
|
||||||
|
sed -e "s|^|$pkg/|g" < "$GOPATH/src/$pkg/.extra-coverpkg" \
|
||||||
|
| tr '\n' ',')
|
||||||
|
fi
|
||||||
|
|
||||||
|
coverpkg=$(go list -json "$pkg" | jq -r '
|
||||||
|
.Deps
|
||||||
|
| map(select(startswith("'"$ROOT_PKG"'")))
|
||||||
|
| map(select(contains("/vendor/") | not))
|
||||||
|
| . + ["'"$pkg"'"]
|
||||||
|
| join(",")
|
||||||
|
')
|
||||||
|
if [[ -n "$extracoverpkg" ]]; then
|
||||||
|
coverpkg="$extracoverpkg$coverpkg"
|
||||||
|
fi
|
||||||
|
|
||||||
|
go test \
|
||||||
|
-coverprofile "$COVER/cover.${i}.out" -coverpkg "$coverpkg" \
|
||||||
|
-v "$pkg"
|
||||||
|
done
|
||||||
|
|
||||||
|
gocovmerge "$COVER"/*.out > cover.out
|
|
@ -0,0 +1,26 @@
|
||||||
|
#!/bin/bash
|
||||||
|
set -euox pipefail
|
||||||
|
IFS=$'\n\t'
|
||||||
|
|
||||||
|
# This script creates a fake GOPATH, symlinks in the current
|
||||||
|
# directory as uber-go/atomic and verifies that tests still pass.
|
||||||
|
|
||||||
|
WORK_DIR=`mktemp -d`
|
||||||
|
function cleanup {
|
||||||
|
rm -rf "$WORK_DIR"
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
|
||||||
|
export GOPATH="$WORK_DIR"
|
||||||
|
PKG_PARENT="$WORK_DIR/src/github.com/uber-go"
|
||||||
|
PKG_DIR="$PKG_PARENT/atomic"
|
||||||
|
|
||||||
|
mkdir -p "$PKG_PARENT"
|
||||||
|
cp -R `pwd` "$PKG_DIR"
|
||||||
|
cd "$PKG_DIR"
|
||||||
|
|
||||||
|
# The example imports go.uber.org, fix the import.
|
||||||
|
sed -e 's/go.uber.org\/atomic/github.com\/uber-go\/atomic/' -i="" example_test.go
|
||||||
|
|
||||||
|
make test
|
|
@ -0,0 +1,140 @@
|
||||||
|
// Copyright (c) 2016 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
package atomic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"runtime"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
_parallelism = 4
|
||||||
|
_iterations = 1000
|
||||||
|
)
|
||||||
|
|
||||||
|
func runStress(f func()) {
|
||||||
|
defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(_parallelism))
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(_parallelism)
|
||||||
|
for i := 0; i < _parallelism; i++ {
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
for j := 0; j < _iterations; j++ {
|
||||||
|
f()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStressInt32(t *testing.T) {
|
||||||
|
var atom Int32
|
||||||
|
runStress(func() {
|
||||||
|
atom.Load()
|
||||||
|
atom.Add(1)
|
||||||
|
atom.Sub(2)
|
||||||
|
atom.Inc()
|
||||||
|
atom.Dec()
|
||||||
|
atom.CAS(1, 0)
|
||||||
|
atom.Swap(5)
|
||||||
|
atom.Store(1)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStressInt64(t *testing.T) {
|
||||||
|
var atom Int64
|
||||||
|
runStress(func() {
|
||||||
|
atom.Load()
|
||||||
|
atom.Add(1)
|
||||||
|
atom.Sub(2)
|
||||||
|
atom.Inc()
|
||||||
|
atom.Dec()
|
||||||
|
atom.CAS(1, 0)
|
||||||
|
atom.Swap(5)
|
||||||
|
atom.Store(1)
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStressUint32(t *testing.T) {
|
||||||
|
var atom Uint32
|
||||||
|
runStress(func() {
|
||||||
|
atom.Load()
|
||||||
|
atom.Add(1)
|
||||||
|
atom.Sub(2)
|
||||||
|
atom.Inc()
|
||||||
|
atom.Dec()
|
||||||
|
atom.CAS(1, 0)
|
||||||
|
atom.Swap(5)
|
||||||
|
atom.Store(1)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStressUint64(t *testing.T) {
|
||||||
|
var atom Uint64
|
||||||
|
runStress(func() {
|
||||||
|
atom.Load()
|
||||||
|
atom.Add(1)
|
||||||
|
atom.Sub(2)
|
||||||
|
atom.Inc()
|
||||||
|
atom.Dec()
|
||||||
|
atom.CAS(1, 0)
|
||||||
|
atom.Swap(5)
|
||||||
|
atom.Store(1)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStressFloat64(t *testing.T) {
|
||||||
|
var atom Float64
|
||||||
|
runStress(func() {
|
||||||
|
atom.Load()
|
||||||
|
atom.CAS(1.0, 0.1)
|
||||||
|
atom.Add(1.1)
|
||||||
|
atom.Sub(0.2)
|
||||||
|
atom.Store(1.0)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStressBool(t *testing.T) {
|
||||||
|
var atom Bool
|
||||||
|
runStress(func() {
|
||||||
|
atom.Load()
|
||||||
|
atom.Store(false)
|
||||||
|
atom.Swap(true)
|
||||||
|
atom.Load()
|
||||||
|
atom.Toggle()
|
||||||
|
atom.Toggle()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStressString(t *testing.T) {
|
||||||
|
var atom String
|
||||||
|
runStress(func() {
|
||||||
|
atom.Load()
|
||||||
|
atom.Store("abc")
|
||||||
|
atom.Load()
|
||||||
|
atom.Store("def")
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
// Copyright (c) 2016 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
package atomic
|
||||||
|
|
||||||
|
import "sync/atomic"
|
||||||
|
|
||||||
|
// String is an atomic type-safe wrapper around atomic.Value for strings.
|
||||||
|
type String struct{ v atomic.Value }
|
||||||
|
|
||||||
|
// NewString creates a String.
|
||||||
|
func NewString(str string) *String {
|
||||||
|
s := &String{}
|
||||||
|
if str != "" {
|
||||||
|
s.Store(str)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load atomically loads the wrapped string.
|
||||||
|
func (s *String) Load() string {
|
||||||
|
v := s.v.Load()
|
||||||
|
if v == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return v.(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store atomically stores the passed string.
|
||||||
|
// Note: Converting the string to an interface{} to store in the atomic.Value
|
||||||
|
// requires an allocation.
|
||||||
|
func (s *String) Store(str string) {
|
||||||
|
s.v.Store(str)
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
// Copyright (c) 2016 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
package atomic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestStringNoInitialValue(t *testing.T) {
|
||||||
|
atom := &String{}
|
||||||
|
require.Equal(t, "", atom.Load(), "Initial value should be blank string")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestString(t *testing.T) {
|
||||||
|
atom := NewString("")
|
||||||
|
require.Equal(t, "", atom.Load(), "Expected Load to return initialized value")
|
||||||
|
|
||||||
|
atom.Store("abc")
|
||||||
|
require.Equal(t, "abc", atom.Load(), "Unexpected value after Store")
|
||||||
|
|
||||||
|
atom = NewString("bcd")
|
||||||
|
require.Equal(t, "bcd", atom.Load(), "Expected Load to return initialized value")
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
coverage:
|
||||||
|
range: 80..100
|
||||||
|
round: down
|
||||||
|
precision: 2
|
||||||
|
|
||||||
|
status:
|
||||||
|
project: # measuring the overall project coverage
|
||||||
|
default: # context, you can create multiple ones with custom titles
|
||||||
|
enabled: yes # must be yes|true to enable this status
|
||||||
|
target: 100 # specify the target coverage for each commit status
|
||||||
|
# option: "auto" (must increase from parent commit or pull request base)
|
||||||
|
# option: "X%" a static target percentage to hit
|
||||||
|
if_not_found: success # if parent is not found report status as success, error, or failure
|
||||||
|
if_ci_failed: error # if ci fails report status as success, error, or failure
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
/vendor
|
|
@ -0,0 +1,33 @@
|
||||||
|
sudo: false
|
||||||
|
language: go
|
||||||
|
go_import_path: go.uber.org/multierr
|
||||||
|
|
||||||
|
env:
|
||||||
|
global:
|
||||||
|
- GO15VENDOREXPERIMENT=1
|
||||||
|
|
||||||
|
go:
|
||||||
|
- 1.7
|
||||||
|
- 1.8
|
||||||
|
- tip
|
||||||
|
|
||||||
|
cache:
|
||||||
|
directories:
|
||||||
|
- vendor
|
||||||
|
|
||||||
|
before_install:
|
||||||
|
- go version
|
||||||
|
|
||||||
|
install:
|
||||||
|
- |
|
||||||
|
set -e
|
||||||
|
make install_ci
|
||||||
|
|
||||||
|
script:
|
||||||
|
- |
|
||||||
|
set -e
|
||||||
|
make lint
|
||||||
|
make test_ci
|
||||||
|
|
||||||
|
after_success:
|
||||||
|
- bash <(curl -s https://codecov.io/bash)
|
|
@ -0,0 +1,28 @@
|
||||||
|
Releases
|
||||||
|
========
|
||||||
|
|
||||||
|
v1.1.0 (2017-06-30)
|
||||||
|
===================
|
||||||
|
|
||||||
|
- Added an `Errors(error) []error` function to extract the underlying list of
|
||||||
|
errors for a multierr error.
|
||||||
|
|
||||||
|
|
||||||
|
v1.0.0 (2017-05-31)
|
||||||
|
===================
|
||||||
|
|
||||||
|
No changes since v0.2.0. This release is committing to making no breaking
|
||||||
|
changes to the current API in the 1.X series.
|
||||||
|
|
||||||
|
|
||||||
|
v0.2.0 (2017-04-11)
|
||||||
|
===================
|
||||||
|
|
||||||
|
- Repeatedly appending to the same error is now faster due to fewer
|
||||||
|
allocations.
|
||||||
|
|
||||||
|
|
||||||
|
v0.1.0 (2017-31-03)
|
||||||
|
===================
|
||||||
|
|
||||||
|
- Initial release
|
|
@ -0,0 +1,19 @@
|
||||||
|
Copyright (c) 2017 Uber Technologies, Inc.
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
|
@ -0,0 +1,74 @@
|
||||||
|
export GO15VENDOREXPERIMENT=1
|
||||||
|
|
||||||
|
PACKAGES := $(shell glide nv)
|
||||||
|
|
||||||
|
GO_FILES := $(shell \
|
||||||
|
find . '(' -path '*/.*' -o -path './vendor' ')' -prune \
|
||||||
|
-o -name '*.go' -print | cut -b3-)
|
||||||
|
|
||||||
|
.PHONY: install
|
||||||
|
install:
|
||||||
|
glide --version || go get github.com/Masterminds/glide
|
||||||
|
glide install
|
||||||
|
|
||||||
|
.PHONY: build
|
||||||
|
build:
|
||||||
|
go build -i $(PACKAGES)
|
||||||
|
|
||||||
|
.PHONY: test
|
||||||
|
test:
|
||||||
|
go test -cover -race $(PACKAGES)
|
||||||
|
|
||||||
|
.PHONY: gofmt
|
||||||
|
gofmt:
|
||||||
|
$(eval FMT_LOG := $(shell mktemp -t gofmt.XXXXX))
|
||||||
|
@gofmt -e -s -l $(GO_FILES) > $(FMT_LOG) || true
|
||||||
|
@[ ! -s "$(FMT_LOG)" ] || (echo "gofmt failed:" | cat - $(FMT_LOG) && false)
|
||||||
|
|
||||||
|
.PHONY: govet
|
||||||
|
govet:
|
||||||
|
$(eval VET_LOG := $(shell mktemp -t govet.XXXXX))
|
||||||
|
@go vet $(PACKAGES) 2>&1 \
|
||||||
|
| grep -v '^exit status' > $(VET_LOG) || true
|
||||||
|
@[ ! -s "$(VET_LOG)" ] || (echo "govet failed:" | cat - $(VET_LOG) && false)
|
||||||
|
|
||||||
|
.PHONY: golint
|
||||||
|
golint:
|
||||||
|
@go get github.com/golang/lint/golint
|
||||||
|
$(eval LINT_LOG := $(shell mktemp -t golint.XXXXX))
|
||||||
|
@cat /dev/null > $(LINT_LOG)
|
||||||
|
@$(foreach pkg, $(PACKAGES), golint $(pkg) >> $(LINT_LOG) || true;)
|
||||||
|
@[ ! -s "$(LINT_LOG)" ] || (echo "golint failed:" | cat - $(LINT_LOG) && false)
|
||||||
|
|
||||||
|
.PHONY: staticcheck
|
||||||
|
staticcheck:
|
||||||
|
@go get honnef.co/go/tools/cmd/staticcheck
|
||||||
|
$(eval STATICCHECK_LOG := $(shell mktemp -t staticcheck.XXXXX))
|
||||||
|
@staticcheck $(PACKAGES) 2>&1 > $(STATICCHECK_LOG) || true
|
||||||
|
@[ ! -s "$(STATICCHECK_LOG)" ] || (echo "staticcheck failed:" | cat - $(STATICCHECK_LOG) && false)
|
||||||
|
|
||||||
|
.PHONY: lint
|
||||||
|
lint: gofmt govet golint staticcheck
|
||||||
|
|
||||||
|
.PHONY: cover
|
||||||
|
cover:
|
||||||
|
./scripts/cover.sh $(shell go list $(PACKAGES))
|
||||||
|
go tool cover -html=cover.out -o cover.html
|
||||||
|
|
||||||
|
update-license:
|
||||||
|
@go get go.uber.org/tools/update-license
|
||||||
|
@update-license \
|
||||||
|
$(shell go list -json $(PACKAGES) | \
|
||||||
|
jq -r '.Dir + "/" + (.GoFiles | .[])')
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
.PHONY: install_ci
|
||||||
|
install_ci: install
|
||||||
|
go get github.com/wadey/gocovmerge
|
||||||
|
go get github.com/mattn/goveralls
|
||||||
|
go get golang.org/x/tools/cmd/cover
|
||||||
|
|
||||||
|
.PHONY: test_ci
|
||||||
|
test_ci: install_ci
|
||||||
|
./scripts/cover.sh $(shell go list $(PACKAGES))
|
|
@ -0,0 +1,23 @@
|
||||||
|
# multierr [![GoDoc][doc-img]][doc] [![Build Status][ci-img]][ci] [![Coverage Status][cov-img]][cov]
|
||||||
|
|
||||||
|
`multierr` allows combining one or more Go `error`s together.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
go get -u go.uber.org/multierr
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
Stable: No breaking changes will be made before 2.0.
|
||||||
|
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
Released under the [MIT License].
|
||||||
|
|
||||||
|
[MIT License]: LICENSE.txt
|
||||||
|
[doc-img]: https://godoc.org/go.uber.org/multierr?status.svg
|
||||||
|
[doc]: https://godoc.org/go.uber.org/multierr
|
||||||
|
[ci-img]: https://travis-ci.org/uber-go/multierr.svg?branch=master
|
||||||
|
[cov-img]: https://codecov.io/gh/uber-go/multierr/branch/master/graph/badge.svg
|
||||||
|
[ci]: https://travis-ci.org/uber-go/multierr
|
||||||
|
[cov]: https://codecov.io/gh/uber-go/multierr
|
|
@ -0,0 +1,62 @@
|
||||||
|
// Copyright (c) 2017 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
package multierr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func BenchmarkAppend(b *testing.B) {
|
||||||
|
errorTypes := []struct {
|
||||||
|
name string
|
||||||
|
err error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "nil",
|
||||||
|
err: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "single error",
|
||||||
|
err: errors.New("test"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "multiple errors",
|
||||||
|
err: appendN(nil, errors.New("err"), 10),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, initial := range errorTypes {
|
||||||
|
for _, v := range errorTypes {
|
||||||
|
msg := fmt.Sprintf("append %v to %v", v.name, initial.name)
|
||||||
|
b.Run(msg, func(b *testing.B) {
|
||||||
|
for _, appends := range []int{1, 2, 10} {
|
||||||
|
b.Run(fmt.Sprint(appends), func(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
appendN(initial.err, v.err, appends)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,401 @@
|
||||||
|
// Copyright (c) 2017 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
// Package multierr allows combining one or more errors together.
|
||||||
|
//
|
||||||
|
// Overview
|
||||||
|
//
|
||||||
|
// Errors can be combined with the use of the Combine function.
|
||||||
|
//
|
||||||
|
// multierr.Combine(
|
||||||
|
// reader.Close(),
|
||||||
|
// writer.Close(),
|
||||||
|
// conn.Close(),
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// If only two errors are being combined, the Append function may be used
|
||||||
|
// instead.
|
||||||
|
//
|
||||||
|
// err = multierr.Combine(reader.Close(), writer.Close())
|
||||||
|
//
|
||||||
|
// This makes it possible to record resource cleanup failures from deferred
|
||||||
|
// blocks with the help of named return values.
|
||||||
|
//
|
||||||
|
// func sendRequest(req Request) (err error) {
|
||||||
|
// conn, err := openConnection()
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
// defer func() {
|
||||||
|
// err = multierr.Append(err, conn.Close())
|
||||||
|
// }()
|
||||||
|
// // ...
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// The underlying list of errors for a returned error object may be retrieved
|
||||||
|
// with the Errors function.
|
||||||
|
//
|
||||||
|
// errors := multierr.Errors(err)
|
||||||
|
// if len(errors) > 0 {
|
||||||
|
// fmt.Println("The following errors occurred:")
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Advanced Usage
|
||||||
|
//
|
||||||
|
// Errors returned by Combine and Append MAY implement the following
|
||||||
|
// interface.
|
||||||
|
//
|
||||||
|
// type errorGroup interface {
|
||||||
|
// // Returns a slice containing the underlying list of errors.
|
||||||
|
// //
|
||||||
|
// // This slice MUST NOT be modified by the caller.
|
||||||
|
// Errors() []error
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Note that if you need access to list of errors behind a multierr error, you
|
||||||
|
// should prefer using the Errors function. That said, if you need cheap
|
||||||
|
// read-only access to the underlying errors slice, you can attempt to cast
|
||||||
|
// the error to this interface. You MUST handle the failure case gracefully
|
||||||
|
// because errors returned by Combine and Append are not guaranteed to
|
||||||
|
// implement this interface.
|
||||||
|
//
|
||||||
|
// var errors []error
|
||||||
|
// group, ok := err.(errorGroup)
|
||||||
|
// if ok {
|
||||||
|
// errors = group.Errors()
|
||||||
|
// } else {
|
||||||
|
// errors = []error{err}
|
||||||
|
// }
|
||||||
|
package multierr // import "go.uber.org/multierr"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"go.uber.org/atomic"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// Separator for single-line error messages.
|
||||||
|
_singlelineSeparator = []byte("; ")
|
||||||
|
|
||||||
|
_newline = []byte("\n")
|
||||||
|
|
||||||
|
// Prefix for multi-line messages
|
||||||
|
_multilinePrefix = []byte("the following errors occurred:")
|
||||||
|
|
||||||
|
// Prefix for the first and following lines of an item in a list of
|
||||||
|
// multi-line error messages.
|
||||||
|
//
|
||||||
|
// For example, if a single item is:
|
||||||
|
//
|
||||||
|
// foo
|
||||||
|
// bar
|
||||||
|
//
|
||||||
|
// It will become,
|
||||||
|
//
|
||||||
|
// - foo
|
||||||
|
// bar
|
||||||
|
_multilineSeparator = []byte("\n - ")
|
||||||
|
_multilineIndent = []byte(" ")
|
||||||
|
)
|
||||||
|
|
||||||
|
// _bufferPool is a pool of bytes.Buffers.
|
||||||
|
var _bufferPool = sync.Pool{
|
||||||
|
New: func() interface{} {
|
||||||
|
return &bytes.Buffer{}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
type errorGroup interface {
|
||||||
|
Errors() []error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Errors returns a slice containing zero or more errors that the supplied
|
||||||
|
// error is composed of. If the error is nil, the returned slice is empty.
|
||||||
|
//
|
||||||
|
// err := multierr.Append(r.Close(), w.Close())
|
||||||
|
// errors := multierr.Errors(err)
|
||||||
|
//
|
||||||
|
// If the error is not composed of other errors, the returned slice contains
|
||||||
|
// just the error that was passed in.
|
||||||
|
//
|
||||||
|
// Callers of this function are free to modify the returned slice.
|
||||||
|
func Errors(err error) []error {
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note that we're casting to multiError, not errorGroup. Our contract is
|
||||||
|
// that returned errors MAY implement errorGroup. Errors, however, only
|
||||||
|
// has special behavior for multierr-specific error objects.
|
||||||
|
//
|
||||||
|
// This behavior can be expanded in the future but I think it's prudent to
|
||||||
|
// start with as little as possible in terms of contract and possibility
|
||||||
|
// of misuse.
|
||||||
|
eg, ok := err.(*multiError)
|
||||||
|
if !ok {
|
||||||
|
return []error{err}
|
||||||
|
}
|
||||||
|
|
||||||
|
errors := eg.Errors()
|
||||||
|
result := make([]error, len(errors))
|
||||||
|
copy(result, errors)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// multiError is an error that holds one or more errors.
|
||||||
|
//
|
||||||
|
// An instance of this is guaranteed to be non-empty and flattened. That is,
|
||||||
|
// none of the errors inside multiError are other multiErrors.
|
||||||
|
//
|
||||||
|
// multiError formats to a semi-colon delimited list of error messages with
|
||||||
|
// %v and with a more readable multi-line format with %+v.
|
||||||
|
type multiError struct {
|
||||||
|
copyNeeded atomic.Bool
|
||||||
|
errors []error
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ errorGroup = (*multiError)(nil)
|
||||||
|
|
||||||
|
// Errors returns the list of underlying errors.
|
||||||
|
//
|
||||||
|
// This slice MUST NOT be modified.
|
||||||
|
func (merr *multiError) Errors() []error {
|
||||||
|
if merr == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return merr.errors
|
||||||
|
}
|
||||||
|
|
||||||
|
func (merr *multiError) Error() string {
|
||||||
|
if merr == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
buff := _bufferPool.Get().(*bytes.Buffer)
|
||||||
|
buff.Reset()
|
||||||
|
|
||||||
|
merr.writeSingleline(buff)
|
||||||
|
|
||||||
|
result := buff.String()
|
||||||
|
_bufferPool.Put(buff)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func (merr *multiError) Format(f fmt.State, c rune) {
|
||||||
|
if c == 'v' && f.Flag('+') {
|
||||||
|
merr.writeMultiline(f)
|
||||||
|
} else {
|
||||||
|
merr.writeSingleline(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (merr *multiError) writeSingleline(w io.Writer) {
|
||||||
|
first := true
|
||||||
|
for _, item := range merr.errors {
|
||||||
|
if first {
|
||||||
|
first = false
|
||||||
|
} else {
|
||||||
|
w.Write(_singlelineSeparator)
|
||||||
|
}
|
||||||
|
io.WriteString(w, item.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (merr *multiError) writeMultiline(w io.Writer) {
|
||||||
|
w.Write(_multilinePrefix)
|
||||||
|
for _, item := range merr.errors {
|
||||||
|
w.Write(_multilineSeparator)
|
||||||
|
writePrefixLine(w, _multilineIndent, fmt.Sprintf("%+v", item))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Writes s to the writer with the given prefix added before each line after
|
||||||
|
// the first.
|
||||||
|
func writePrefixLine(w io.Writer, prefix []byte, s string) {
|
||||||
|
first := true
|
||||||
|
for len(s) > 0 {
|
||||||
|
if first {
|
||||||
|
first = false
|
||||||
|
} else {
|
||||||
|
w.Write(prefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
idx := strings.IndexByte(s, '\n')
|
||||||
|
if idx < 0 {
|
||||||
|
idx = len(s) - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
io.WriteString(w, s[:idx+1])
|
||||||
|
s = s[idx+1:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type inspectResult struct {
|
||||||
|
// Number of top-level non-nil errors
|
||||||
|
Count int
|
||||||
|
|
||||||
|
// Total number of errors including multiErrors
|
||||||
|
Capacity int
|
||||||
|
|
||||||
|
// Index of the first non-nil error in the list. Value is meaningless if
|
||||||
|
// Count is zero.
|
||||||
|
FirstErrorIdx int
|
||||||
|
|
||||||
|
// Whether the list contains at least one multiError
|
||||||
|
ContainsMultiError bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inspects the given slice of errors so that we can efficiently allocate
|
||||||
|
// space for it.
|
||||||
|
func inspect(errors []error) (res inspectResult) {
|
||||||
|
first := true
|
||||||
|
for i, err := range errors {
|
||||||
|
if err == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
res.Count++
|
||||||
|
if first {
|
||||||
|
first = false
|
||||||
|
res.FirstErrorIdx = i
|
||||||
|
}
|
||||||
|
|
||||||
|
if merr, ok := err.(*multiError); ok {
|
||||||
|
res.Capacity += len(merr.errors)
|
||||||
|
res.ContainsMultiError = true
|
||||||
|
} else {
|
||||||
|
res.Capacity++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// fromSlice converts the given list of errors into a single error.
|
||||||
|
func fromSlice(errors []error) error {
|
||||||
|
res := inspect(errors)
|
||||||
|
switch res.Count {
|
||||||
|
case 0:
|
||||||
|
return nil
|
||||||
|
case 1:
|
||||||
|
// only one non-nil entry
|
||||||
|
return errors[res.FirstErrorIdx]
|
||||||
|
case len(errors):
|
||||||
|
if !res.ContainsMultiError {
|
||||||
|
// already flat
|
||||||
|
return &multiError{errors: errors}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nonNilErrs := make([]error, 0, res.Capacity)
|
||||||
|
for _, err := range errors[res.FirstErrorIdx:] {
|
||||||
|
if err == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if nested, ok := err.(*multiError); ok {
|
||||||
|
nonNilErrs = append(nonNilErrs, nested.errors...)
|
||||||
|
} else {
|
||||||
|
nonNilErrs = append(nonNilErrs, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &multiError{errors: nonNilErrs}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Combine combines the passed errors into a single error.
|
||||||
|
//
|
||||||
|
// If zero arguments were passed or if all items are nil, a nil error is
|
||||||
|
// returned.
|
||||||
|
//
|
||||||
|
// Combine(nil, nil) // == nil
|
||||||
|
//
|
||||||
|
// If only a single error was passed, it is returned as-is.
|
||||||
|
//
|
||||||
|
// Combine(err) // == err
|
||||||
|
//
|
||||||
|
// Combine skips over nil arguments so this function may be used to combine
|
||||||
|
// together errors from operations that fail independently of each other.
|
||||||
|
//
|
||||||
|
// multierr.Combine(
|
||||||
|
// reader.Close(),
|
||||||
|
// writer.Close(),
|
||||||
|
// pipe.Close(),
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// If any of the passed errors is a multierr error, it will be flattened along
|
||||||
|
// with the other errors.
|
||||||
|
//
|
||||||
|
// multierr.Combine(multierr.Combine(err1, err2), err3)
|
||||||
|
// // is the same as
|
||||||
|
// multierr.Combine(err1, err2, err3)
|
||||||
|
//
|
||||||
|
// The returned error formats into a readable multi-line error message if
|
||||||
|
// formatted with %+v.
|
||||||
|
//
|
||||||
|
// fmt.Sprintf("%+v", multierr.Combine(err1, err2))
|
||||||
|
func Combine(errors ...error) error {
|
||||||
|
return fromSlice(errors)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append appends the given errors together. Either value may be nil.
|
||||||
|
//
|
||||||
|
// This function is a specialization of Combine for the common case where
|
||||||
|
// there are only two errors.
|
||||||
|
//
|
||||||
|
// err = multierr.Append(reader.Close(), writer.Close())
|
||||||
|
//
|
||||||
|
// The following pattern may also be used to record failure of deferred
|
||||||
|
// operations without losing information about the original error.
|
||||||
|
//
|
||||||
|
// func doSomething(..) (err error) {
|
||||||
|
// f := acquireResource()
|
||||||
|
// defer func() {
|
||||||
|
// err = multierr.Append(err, f.Close())
|
||||||
|
// }()
|
||||||
|
func Append(left error, right error) error {
|
||||||
|
switch {
|
||||||
|
case left == nil:
|
||||||
|
return right
|
||||||
|
case right == nil:
|
||||||
|
return left
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := right.(*multiError); !ok {
|
||||||
|
if l, ok := left.(*multiError); ok && !l.copyNeeded.Swap(true) {
|
||||||
|
// Common case where the error on the left is constantly being
|
||||||
|
// appended to.
|
||||||
|
errs := append(l.errors, right)
|
||||||
|
return &multiError{errors: errs}
|
||||||
|
} else if !ok {
|
||||||
|
// Both errors are single errors.
|
||||||
|
return &multiError{errors: []error{left, right}}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Either right or both, left and right, are multiErrors. Rely on usual
|
||||||
|
// expensive logic.
|
||||||
|
errors := [2]error{left, right}
|
||||||
|
return fromSlice(errors[0:])
|
||||||
|
}
|
|
@ -0,0 +1,511 @@
|
||||||
|
// Copyright (c) 2017 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
package multierr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
// richFormatError is an error that prints a different output depending on
|
||||||
|
// whether %v or %+v was used.
|
||||||
|
type richFormatError struct{}
|
||||||
|
|
||||||
|
func (r richFormatError) Error() string {
|
||||||
|
return fmt.Sprint(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (richFormatError) Format(f fmt.State, c rune) {
|
||||||
|
if c == 'v' && f.Flag('+') {
|
||||||
|
io.WriteString(f, "multiline\nmessage\nwith plus")
|
||||||
|
} else {
|
||||||
|
io.WriteString(f, "without plus")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func appendN(initial, err error, n int) error {
|
||||||
|
errs := initial
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
errs = Append(errs, err)
|
||||||
|
}
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMultiErr(errors ...error) error {
|
||||||
|
return &multiError{errors: errors}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCombine(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
// Input
|
||||||
|
giveErrors []error
|
||||||
|
|
||||||
|
// Resulting error
|
||||||
|
wantError error
|
||||||
|
|
||||||
|
// %+v and %v string representations
|
||||||
|
wantMultiline string
|
||||||
|
wantSingleline string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
giveErrors: nil,
|
||||||
|
wantError: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
giveErrors: []error{},
|
||||||
|
wantError: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
giveErrors: []error{
|
||||||
|
errors.New("foo"),
|
||||||
|
nil,
|
||||||
|
newMultiErr(
|
||||||
|
errors.New("bar"),
|
||||||
|
),
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
wantError: newMultiErr(
|
||||||
|
errors.New("foo"),
|
||||||
|
errors.New("bar"),
|
||||||
|
),
|
||||||
|
wantMultiline: "the following errors occurred:\n" +
|
||||||
|
" - foo\n" +
|
||||||
|
" - bar",
|
||||||
|
wantSingleline: "foo; bar",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
giveErrors: []error{
|
||||||
|
errors.New("foo"),
|
||||||
|
newMultiErr(
|
||||||
|
errors.New("bar"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
wantError: newMultiErr(
|
||||||
|
errors.New("foo"),
|
||||||
|
errors.New("bar"),
|
||||||
|
),
|
||||||
|
wantMultiline: "the following errors occurred:\n" +
|
||||||
|
" - foo\n" +
|
||||||
|
" - bar",
|
||||||
|
wantSingleline: "foo; bar",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
giveErrors: []error{errors.New("great sadness")},
|
||||||
|
wantError: errors.New("great sadness"),
|
||||||
|
wantMultiline: "great sadness",
|
||||||
|
wantSingleline: "great sadness",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
giveErrors: []error{
|
||||||
|
errors.New("foo"),
|
||||||
|
errors.New("bar"),
|
||||||
|
},
|
||||||
|
wantError: newMultiErr(
|
||||||
|
errors.New("foo"),
|
||||||
|
errors.New("bar"),
|
||||||
|
),
|
||||||
|
wantMultiline: "the following errors occurred:\n" +
|
||||||
|
" - foo\n" +
|
||||||
|
" - bar",
|
||||||
|
wantSingleline: "foo; bar",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
giveErrors: []error{
|
||||||
|
errors.New("great sadness"),
|
||||||
|
errors.New("multi\n line\nerror message"),
|
||||||
|
errors.New("single line error message"),
|
||||||
|
},
|
||||||
|
wantError: newMultiErr(
|
||||||
|
errors.New("great sadness"),
|
||||||
|
errors.New("multi\n line\nerror message"),
|
||||||
|
errors.New("single line error message"),
|
||||||
|
),
|
||||||
|
wantMultiline: "the following errors occurred:\n" +
|
||||||
|
" - great sadness\n" +
|
||||||
|
" - multi\n" +
|
||||||
|
" line\n" +
|
||||||
|
" error message\n" +
|
||||||
|
" - single line error message",
|
||||||
|
wantSingleline: "great sadness; " +
|
||||||
|
"multi\n line\nerror message; " +
|
||||||
|
"single line error message",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
giveErrors: []error{
|
||||||
|
errors.New("foo"),
|
||||||
|
newMultiErr(
|
||||||
|
errors.New("bar"),
|
||||||
|
errors.New("baz"),
|
||||||
|
),
|
||||||
|
errors.New("qux"),
|
||||||
|
},
|
||||||
|
wantError: newMultiErr(
|
||||||
|
errors.New("foo"),
|
||||||
|
errors.New("bar"),
|
||||||
|
errors.New("baz"),
|
||||||
|
errors.New("qux"),
|
||||||
|
),
|
||||||
|
wantMultiline: "the following errors occurred:\n" +
|
||||||
|
" - foo\n" +
|
||||||
|
" - bar\n" +
|
||||||
|
" - baz\n" +
|
||||||
|
" - qux",
|
||||||
|
wantSingleline: "foo; bar; baz; qux",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
giveErrors: []error{
|
||||||
|
errors.New("foo"),
|
||||||
|
nil,
|
||||||
|
newMultiErr(
|
||||||
|
errors.New("bar"),
|
||||||
|
),
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
wantError: newMultiErr(
|
||||||
|
errors.New("foo"),
|
||||||
|
errors.New("bar"),
|
||||||
|
),
|
||||||
|
wantMultiline: "the following errors occurred:\n" +
|
||||||
|
" - foo\n" +
|
||||||
|
" - bar",
|
||||||
|
wantSingleline: "foo; bar",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
giveErrors: []error{
|
||||||
|
errors.New("foo"),
|
||||||
|
newMultiErr(
|
||||||
|
errors.New("bar"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
wantError: newMultiErr(
|
||||||
|
errors.New("foo"),
|
||||||
|
errors.New("bar"),
|
||||||
|
),
|
||||||
|
wantMultiline: "the following errors occurred:\n" +
|
||||||
|
" - foo\n" +
|
||||||
|
" - bar",
|
||||||
|
wantSingleline: "foo; bar",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
giveErrors: []error{
|
||||||
|
errors.New("foo"),
|
||||||
|
richFormatError{},
|
||||||
|
errors.New("bar"),
|
||||||
|
},
|
||||||
|
wantError: newMultiErr(
|
||||||
|
errors.New("foo"),
|
||||||
|
richFormatError{},
|
||||||
|
errors.New("bar"),
|
||||||
|
),
|
||||||
|
wantMultiline: "the following errors occurred:\n" +
|
||||||
|
" - foo\n" +
|
||||||
|
" - multiline\n" +
|
||||||
|
" message\n" +
|
||||||
|
" with plus\n" +
|
||||||
|
" - bar",
|
||||||
|
wantSingleline: "foo; without plus; bar",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range tests {
|
||||||
|
t.Run(fmt.Sprint(i), func(t *testing.T) {
|
||||||
|
err := Combine(tt.giveErrors...)
|
||||||
|
require.Equal(t, tt.wantError, err)
|
||||||
|
|
||||||
|
if tt.wantMultiline != "" {
|
||||||
|
t.Run("Sprintf/multiline", func(t *testing.T) {
|
||||||
|
assert.Equal(t, tt.wantMultiline, fmt.Sprintf("%+v", err))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if tt.wantSingleline != "" {
|
||||||
|
t.Run("Sprintf/singleline", func(t *testing.T) {
|
||||||
|
assert.Equal(t, tt.wantSingleline, fmt.Sprintf("%v", err))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Error()", func(t *testing.T) {
|
||||||
|
assert.Equal(t, tt.wantSingleline, err.Error())
|
||||||
|
})
|
||||||
|
|
||||||
|
if s, ok := err.(fmt.Stringer); ok {
|
||||||
|
t.Run("String()", func(t *testing.T) {
|
||||||
|
assert.Equal(t, tt.wantSingleline, s.String())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCombineDoesNotModifySlice(t *testing.T) {
|
||||||
|
errors := []error{
|
||||||
|
errors.New("foo"),
|
||||||
|
nil,
|
||||||
|
errors.New("bar"),
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.NotNil(t, Combine(errors...))
|
||||||
|
assert.Len(t, errors, 3)
|
||||||
|
assert.Nil(t, errors[1], 3)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAppend(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
left error
|
||||||
|
right error
|
||||||
|
want error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
left: nil,
|
||||||
|
right: nil,
|
||||||
|
want: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
left: nil,
|
||||||
|
right: errors.New("great sadness"),
|
||||||
|
want: errors.New("great sadness"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
left: errors.New("great sadness"),
|
||||||
|
right: nil,
|
||||||
|
want: errors.New("great sadness"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
left: errors.New("foo"),
|
||||||
|
right: errors.New("bar"),
|
||||||
|
want: newMultiErr(
|
||||||
|
errors.New("foo"),
|
||||||
|
errors.New("bar"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
left: newMultiErr(
|
||||||
|
errors.New("foo"),
|
||||||
|
errors.New("bar"),
|
||||||
|
),
|
||||||
|
right: errors.New("baz"),
|
||||||
|
want: newMultiErr(
|
||||||
|
errors.New("foo"),
|
||||||
|
errors.New("bar"),
|
||||||
|
errors.New("baz"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
left: errors.New("baz"),
|
||||||
|
right: newMultiErr(
|
||||||
|
errors.New("foo"),
|
||||||
|
errors.New("bar"),
|
||||||
|
),
|
||||||
|
want: newMultiErr(
|
||||||
|
errors.New("baz"),
|
||||||
|
errors.New("foo"),
|
||||||
|
errors.New("bar"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
left: newMultiErr(
|
||||||
|
errors.New("foo"),
|
||||||
|
),
|
||||||
|
right: newMultiErr(
|
||||||
|
errors.New("bar"),
|
||||||
|
),
|
||||||
|
want: newMultiErr(
|
||||||
|
errors.New("foo"),
|
||||||
|
errors.New("bar"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
assert.Equal(t, tt.want, Append(tt.left, tt.right))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type notMultiErr struct{}
|
||||||
|
|
||||||
|
var _ errorGroup = notMultiErr{}
|
||||||
|
|
||||||
|
func (notMultiErr) Error() string {
|
||||||
|
return "great sadness"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (notMultiErr) Errors() []error {
|
||||||
|
return []error{errors.New("great sadness")}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestErrors(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
give error
|
||||||
|
want []error
|
||||||
|
|
||||||
|
// Don't attempt to cast to errorGroup or *multiError
|
||||||
|
dontCast bool
|
||||||
|
}{
|
||||||
|
{dontCast: true}, // nil
|
||||||
|
{
|
||||||
|
give: errors.New("hi"),
|
||||||
|
want: []error{errors.New("hi")},
|
||||||
|
dontCast: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// We don't yet support non-multierr errors.
|
||||||
|
give: notMultiErr{},
|
||||||
|
want: []error{notMultiErr{}},
|
||||||
|
dontCast: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
give: Combine(
|
||||||
|
errors.New("foo"),
|
||||||
|
errors.New("bar"),
|
||||||
|
),
|
||||||
|
want: []error{
|
||||||
|
errors.New("foo"),
|
||||||
|
errors.New("bar"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
give: Append(
|
||||||
|
errors.New("foo"),
|
||||||
|
errors.New("bar"),
|
||||||
|
),
|
||||||
|
want: []error{
|
||||||
|
errors.New("foo"),
|
||||||
|
errors.New("bar"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
give: Append(
|
||||||
|
errors.New("foo"),
|
||||||
|
Combine(
|
||||||
|
errors.New("bar"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
want: []error{
|
||||||
|
errors.New("foo"),
|
||||||
|
errors.New("bar"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
give: Combine(
|
||||||
|
errors.New("foo"),
|
||||||
|
Append(
|
||||||
|
errors.New("bar"),
|
||||||
|
errors.New("baz"),
|
||||||
|
),
|
||||||
|
errors.New("qux"),
|
||||||
|
),
|
||||||
|
want: []error{
|
||||||
|
errors.New("foo"),
|
||||||
|
errors.New("bar"),
|
||||||
|
errors.New("baz"),
|
||||||
|
errors.New("qux"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range tests {
|
||||||
|
t.Run(fmt.Sprint(i), func(t *testing.T) {
|
||||||
|
t.Run("Errors()", func(t *testing.T) {
|
||||||
|
require.Equal(t, tt.want, Errors(tt.give))
|
||||||
|
})
|
||||||
|
|
||||||
|
if tt.dontCast {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("multiError", func(t *testing.T) {
|
||||||
|
require.Equal(t, tt.want, tt.give.(*multiError).Errors())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("errorGroup", func(t *testing.T) {
|
||||||
|
require.Equal(t, tt.want, tt.give.(errorGroup).Errors())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func createMultiErrWithCapacity() error {
|
||||||
|
// Create a multiError that has capacity for more errors so Append will
|
||||||
|
// modify the underlying array that may be shared.
|
||||||
|
return appendN(nil, errors.New("append"), 50)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAppendDoesNotModify(t *testing.T) {
|
||||||
|
initial := createMultiErrWithCapacity()
|
||||||
|
err1 := Append(initial, errors.New("err1"))
|
||||||
|
err2 := Append(initial, errors.New("err2"))
|
||||||
|
|
||||||
|
// Make sure the error messages match, since we do modify the copyNeeded
|
||||||
|
// atomic, the values cannot be compared.
|
||||||
|
assert.EqualError(t, initial, createMultiErrWithCapacity().Error(), "Initial should not be modified")
|
||||||
|
|
||||||
|
assert.EqualError(t, err1, Append(createMultiErrWithCapacity(), errors.New("err1")).Error())
|
||||||
|
assert.EqualError(t, err2, Append(createMultiErrWithCapacity(), errors.New("err2")).Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAppendRace(t *testing.T) {
|
||||||
|
initial := createMultiErrWithCapacity()
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
err := initial
|
||||||
|
for j := 0; j < 10; j++ {
|
||||||
|
err = Append(err, errors.New("err"))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestErrorsSliceIsImmutable(t *testing.T) {
|
||||||
|
err1 := errors.New("err1")
|
||||||
|
err2 := errors.New("err2")
|
||||||
|
|
||||||
|
err := Append(err1, err2)
|
||||||
|
gotErrors := Errors(err)
|
||||||
|
require.Equal(t, []error{err1, err2}, gotErrors, "errors must match")
|
||||||
|
|
||||||
|
gotErrors[0] = nil
|
||||||
|
gotErrors[1] = errors.New("err3")
|
||||||
|
|
||||||
|
require.Equal(t, []error{err1, err2}, Errors(err),
|
||||||
|
"errors must match after modification")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNilMultierror(t *testing.T) {
|
||||||
|
// For safety, all operations on multiError should be safe even if it is
|
||||||
|
// nil.
|
||||||
|
var err *multiError
|
||||||
|
|
||||||
|
require.Empty(t, err.Error())
|
||||||
|
require.Empty(t, err.Errors())
|
||||||
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
// Copyright (c) 2017 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
package multierr_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"go.uber.org/multierr"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ExampleCombine() {
|
||||||
|
err := multierr.Combine(
|
||||||
|
errors.New("call 1 failed"),
|
||||||
|
nil, // successful request
|
||||||
|
errors.New("call 3 failed"),
|
||||||
|
nil, // successful request
|
||||||
|
errors.New("call 5 failed"),
|
||||||
|
)
|
||||||
|
fmt.Printf("%+v", err)
|
||||||
|
// Output:
|
||||||
|
// the following errors occurred:
|
||||||
|
// - call 1 failed
|
||||||
|
// - call 3 failed
|
||||||
|
// - call 5 failed
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleAppend() {
|
||||||
|
var err error
|
||||||
|
err = multierr.Append(err, errors.New("call 1 failed"))
|
||||||
|
err = multierr.Append(err, errors.New("call 2 failed"))
|
||||||
|
fmt.Println(err)
|
||||||
|
// Output:
|
||||||
|
// call 1 failed; call 2 failed
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleErrors() {
|
||||||
|
err := multierr.Combine(
|
||||||
|
nil, // successful request
|
||||||
|
errors.New("call 2 failed"),
|
||||||
|
errors.New("call 3 failed"),
|
||||||
|
)
|
||||||
|
err = multierr.Append(err, nil) // successful request
|
||||||
|
err = multierr.Append(err, errors.New("call 5 failed"))
|
||||||
|
|
||||||
|
errors := multierr.Errors(err)
|
||||||
|
for _, err := range errors {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
// Output:
|
||||||
|
// call 2 failed
|
||||||
|
// call 3 failed
|
||||||
|
// call 5 failed
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
hash: b53b5e9a84b9cb3cc4b2d0499e23da2feca1eec318ce9bb717ecf35bf24bf221
|
||||||
|
updated: 2017-04-10T13:34:45.671678062-07:00
|
||||||
|
imports:
|
||||||
|
- name: go.uber.org/atomic
|
||||||
|
version: 3b8db5e93c4c02efbc313e17b2e796b0914a01fb
|
||||||
|
testImports:
|
||||||
|
- name: github.com/davecgh/go-spew
|
||||||
|
version: 6d212800a42e8ab5c146b8ace3490ee17e5225f9
|
||||||
|
subpackages:
|
||||||
|
- spew
|
||||||
|
- name: github.com/pmezard/go-difflib
|
||||||
|
version: d8ed2627bdf02c080bf22230dbb337003b7aba2d
|
||||||
|
subpackages:
|
||||||
|
- difflib
|
||||||
|
- name: github.com/stretchr/testify
|
||||||
|
version: 69483b4bd14f5845b5a1e55bca19e954e827f1d0
|
||||||
|
subpackages:
|
||||||
|
- assert
|
||||||
|
- require
|
|
@ -0,0 +1,8 @@
|
||||||
|
package: go.uber.org/multierr
|
||||||
|
import:
|
||||||
|
- package: go.uber.org/atomic
|
||||||
|
version: ^1
|
||||||
|
testImport:
|
||||||
|
- package: github.com/stretchr/testify
|
||||||
|
subpackages:
|
||||||
|
- assert
|
|
@ -0,0 +1,40 @@
|
||||||
|
#!/bin/bash -e
|
||||||
|
|
||||||
|
COVER=cover
|
||||||
|
ROOT_PKG=go.uber.org/multierr
|
||||||
|
|
||||||
|
if [[ -d "$COVER" ]]; then
|
||||||
|
rm -rf "$COVER"
|
||||||
|
fi
|
||||||
|
mkdir -p "$COVER"
|
||||||
|
|
||||||
|
i=0
|
||||||
|
for pkg in "$@"; do
|
||||||
|
i=$((i + 1))
|
||||||
|
|
||||||
|
extracoverpkg=""
|
||||||
|
if [[ -f "$GOPATH/src/$pkg/.extra-coverpkg" ]]; then
|
||||||
|
extracoverpkg=$( \
|
||||||
|
sed -e "s|^|$pkg/|g" < "$GOPATH/src/$pkg/.extra-coverpkg" \
|
||||||
|
| tr '\n' ',')
|
||||||
|
fi
|
||||||
|
|
||||||
|
coverpkg=$(go list -json "$pkg" | jq -r '
|
||||||
|
.Deps
|
||||||
|
| . + ["'"$pkg"'"]
|
||||||
|
| map
|
||||||
|
( select(startswith("'"$ROOT_PKG"'"))
|
||||||
|
| select(contains("/vendor/") | not)
|
||||||
|
)
|
||||||
|
| join(",")
|
||||||
|
')
|
||||||
|
if [[ -n "$extracoverpkg" ]]; then
|
||||||
|
coverpkg="$extracoverpkg$coverpkg"
|
||||||
|
fi
|
||||||
|
|
||||||
|
go test \
|
||||||
|
-coverprofile "$COVER/cover.${i}.out" -coverpkg "$coverpkg" \
|
||||||
|
-v "$pkg"
|
||||||
|
done
|
||||||
|
|
||||||
|
gocovmerge "$COVER"/*.out > cover.out
|
|
@ -0,0 +1,17 @@
|
||||||
|
coverage:
|
||||||
|
range: 80..100
|
||||||
|
round: down
|
||||||
|
precision: 2
|
||||||
|
|
||||||
|
status:
|
||||||
|
project: # measuring the overall project coverage
|
||||||
|
default: # context, you can create multiple ones with custom titles
|
||||||
|
enabled: yes # must be yes|true to enable this status
|
||||||
|
target: 95% # specify the target coverage for each commit status
|
||||||
|
# option: "auto" (must increase from parent commit or pull request base)
|
||||||
|
# option: "X%" a static target percentage to hit
|
||||||
|
if_not_found: success # if parent is not found report status as success, error, or failure
|
||||||
|
if_ci_failed: error # if ci fails report status as success, error, or failure
|
||||||
|
ignore:
|
||||||
|
- internal/readme/readme.go
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||||
|
*.o
|
||||||
|
*.a
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Folders
|
||||||
|
_obj
|
||||||
|
_test
|
||||||
|
vendor
|
||||||
|
|
||||||
|
# Architecture specific extensions/prefixes
|
||||||
|
*.[568vq]
|
||||||
|
[568vq].out
|
||||||
|
|
||||||
|
*.cgo1.go
|
||||||
|
*.cgo2.c
|
||||||
|
_cgo_defun.c
|
||||||
|
_cgo_gotypes.go
|
||||||
|
_cgo_export.*
|
||||||
|
|
||||||
|
_testmain.go
|
||||||
|
|
||||||
|
*.exe
|
||||||
|
*.test
|
||||||
|
*.prof
|
||||||
|
*.pprof
|
||||||
|
*.out
|
||||||
|
*.log
|
|
@ -0,0 +1,108 @@
|
||||||
|
# :zap: zap [![GoDoc][doc-img]][doc] [![Build Status][ci-img]][ci] [![Coverage Status][cov-img]][cov]
|
||||||
|
|
||||||
|
Blazing fast, structured, leveled logging in Go.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
`go get -u go.uber.org/zap`
|
||||||
|
|
||||||
|
Note that zap only supports the two most recent minor versions of Go.
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
In contexts where performance is nice, but not critical, use the
|
||||||
|
`SugaredLogger`. It's 4-10x faster than than other structured logging
|
||||||
|
packages and includes both structured and `printf`-style APIs.
|
||||||
|
|
||||||
|
```go
|
||||||
|
logger, _ := zap.NewProduction()
|
||||||
|
defer logger.Sync() // flushes buffer, if any
|
||||||
|
sugar := logger.Sugar()
|
||||||
|
sugar.Infow("failed to fetch URL",
|
||||||
|
// Structured context as loosely typed key-value pairs.
|
||||||
|
"url", url,
|
||||||
|
"attempt", 3,
|
||||||
|
"backoff", time.Second,
|
||||||
|
)
|
||||||
|
sugar.Infof("Failed to fetch URL: %s", url)
|
||||||
|
```
|
||||||
|
|
||||||
|
When performance and type safety are critical, use the `Logger`. It's even
|
||||||
|
faster than the `SugaredLogger` and allocates far less, but it only supports
|
||||||
|
structured logging.
|
||||||
|
|
||||||
|
```go
|
||||||
|
logger, _ := zap.NewProduction()
|
||||||
|
defer logger.Sync()
|
||||||
|
logger.Info("failed to fetch URL",
|
||||||
|
// Structured context as strongly typed Field values.
|
||||||
|
zap.String("url", url),
|
||||||
|
zap.Int("attempt", 3),
|
||||||
|
zap.Duration("backoff", time.Second),
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
See the [documentation][doc] and [FAQ](FAQ.md) for more details.
|
||||||
|
|
||||||
|
## Performance
|
||||||
|
|
||||||
|
For applications that log in the hot path, reflection-based serialization and
|
||||||
|
string formatting are prohibitively expensive — they're CPU-intensive
|
||||||
|
and make many small allocations. Put differently, using `encoding/json` and
|
||||||
|
`fmt.Fprintf` to log tons of `interface{}`s makes your application slow.
|
||||||
|
|
||||||
|
Zap takes a different approach. It includes a reflection-free, zero-allocation
|
||||||
|
JSON encoder, and the base `Logger` strives to avoid serialization overhead
|
||||||
|
and allocations wherever possible. By building the high-level `SugaredLogger`
|
||||||
|
on that foundation, zap lets users *choose* when they need to count every
|
||||||
|
allocation and when they'd prefer a more familiar, loosely typed API.
|
||||||
|
|
||||||
|
As measured by its own [benchmarking suite][], not only is zap more performant
|
||||||
|
than comparable structured logging packages — it's also faster than the
|
||||||
|
standard library. Like all benchmarks, take these with a grain of salt.<sup
|
||||||
|
id="anchor-versions">[1](#footnote-versions)</sup>
|
||||||
|
|
||||||
|
Log a message and 10 fields:
|
||||||
|
|
||||||
|
{{.BenchmarkAddingFields}}
|
||||||
|
|
||||||
|
Log a message with a logger that already has 10 fields of context:
|
||||||
|
|
||||||
|
{{.BenchmarkAccumulatedContext}}
|
||||||
|
|
||||||
|
Log a static string, without any context or `printf`-style templating:
|
||||||
|
|
||||||
|
{{.BenchmarkWithoutFields}}
|
||||||
|
|
||||||
|
## Development Status: Stable
|
||||||
|
|
||||||
|
All APIs are finalized, and no breaking changes will be made in the 1.x series
|
||||||
|
of releases. Users of semver-aware dependency management systems should pin
|
||||||
|
zap to `^1`.
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
We encourage and support an active, healthy community of contributors —
|
||||||
|
including you! Details are in the [contribution guide](CONTRIBUTING.md) and
|
||||||
|
the [code of conduct](CODE_OF_CONDUCT.md). The zap maintainers keep an eye on
|
||||||
|
issues and pull requests, but you can also report any negative conduct to
|
||||||
|
oss-conduct@uber.com. That email list is a private, safe space; even the zap
|
||||||
|
maintainers don't have access, so don't hesitate to hold us to a high
|
||||||
|
standard.
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
Released under the [MIT License](LICENSE.txt).
|
||||||
|
|
||||||
|
<sup id="footnote-versions">1</sup> In particular, keep in mind that we may be
|
||||||
|
benchmarking against slightly older versions of other packages. Versions are
|
||||||
|
pinned in zap's [glide.lock][] file. [↩](#anchor-versions)
|
||||||
|
|
||||||
|
[doc-img]: https://godoc.org/go.uber.org/zap?status.svg
|
||||||
|
[doc]: https://godoc.org/go.uber.org/zap
|
||||||
|
[ci-img]: https://travis-ci.org/uber-go/zap.svg?branch=master
|
||||||
|
[ci]: https://travis-ci.org/uber-go/zap
|
||||||
|
[cov-img]: https://codecov.io/gh/uber-go/zap/branch/master/graph/badge.svg
|
||||||
|
[cov]: https://codecov.io/gh/uber-go/zap
|
||||||
|
[benchmarking suite]: https://github.com/uber-go/zap/tree/master/benchmarks
|
||||||
|
[glide.lock]: https://github.com/uber-go/zap/blob/master/glide.lock
|
|
@ -0,0 +1,23 @@
|
||||||
|
language: go
|
||||||
|
sudo: false
|
||||||
|
go:
|
||||||
|
- 1.7
|
||||||
|
- 1.8
|
||||||
|
- 1.9
|
||||||
|
go_import_path: go.uber.org/zap
|
||||||
|
env:
|
||||||
|
global:
|
||||||
|
- GO15VENDOREXPERIMENT=1
|
||||||
|
- TEST_TIMEOUT_SCALE=10
|
||||||
|
cache:
|
||||||
|
directories:
|
||||||
|
- vendor
|
||||||
|
install:
|
||||||
|
- make dependencies
|
||||||
|
script:
|
||||||
|
- make lint
|
||||||
|
- make test
|
||||||
|
- make bench
|
||||||
|
after_success:
|
||||||
|
- make cover
|
||||||
|
- bash <(curl -s https://codecov.io/bash)
|
|
@ -0,0 +1,270 @@
|
||||||
|
# Changelog
|
||||||
|
|
||||||
|
## v1.7.1 (25 Sep 2017)
|
||||||
|
|
||||||
|
Bugfixes:
|
||||||
|
* [#504][]: Store strings when using AddByteString with the map encoder.
|
||||||
|
|
||||||
|
## v1.7.0 (21 Sep 2017)
|
||||||
|
|
||||||
|
Enhancements:
|
||||||
|
|
||||||
|
* [#487][]: Add `NewStdLogAt`, which extends `NewStdLog` by allowing the user
|
||||||
|
to specify the level of the logged messages.
|
||||||
|
|
||||||
|
## v1.6.0 (30 Aug 2017)
|
||||||
|
|
||||||
|
Enhancements:
|
||||||
|
|
||||||
|
* [#491][]: Omit zap stack frames from stacktraces.
|
||||||
|
* [#490][]: Add a `ContextMap` method to observer logs for simpler
|
||||||
|
field validation in tests.
|
||||||
|
|
||||||
|
## v1.5.0 (22 Jul 2017)
|
||||||
|
|
||||||
|
Enhancements:
|
||||||
|
|
||||||
|
* [#460][] and [#470][]: Support errors produced by `go.uber.org/multierr`.
|
||||||
|
* [#465][]: Support user-supplied encoders for logger names.
|
||||||
|
|
||||||
|
Bugfixes:
|
||||||
|
|
||||||
|
* [#477][]: Fix a bug that incorrectly truncated deep stacktraces.
|
||||||
|
|
||||||
|
Thanks to @richard-tunein and @pavius for their contributions to this release.
|
||||||
|
|
||||||
|
## v1.4.1 (08 Jun 2017)
|
||||||
|
|
||||||
|
This release fixes two bugs.
|
||||||
|
|
||||||
|
Bugfixes:
|
||||||
|
|
||||||
|
* [#435][]: Support a variety of case conventions when unmarshaling levels.
|
||||||
|
* [#444][]: Fix a panic in the observer.
|
||||||
|
|
||||||
|
## v1.4.0 (12 May 2017)
|
||||||
|
|
||||||
|
This release adds a few small features and is fully backward-compatible.
|
||||||
|
|
||||||
|
Enhancements:
|
||||||
|
|
||||||
|
* [#424][]: Add a `LineEnding` field to `EncoderConfig`, allowing users to
|
||||||
|
override the Unix-style default.
|
||||||
|
* [#425][]: Preserve time zones when logging times.
|
||||||
|
* [#431][]: Make `zap.AtomicLevel` implement `fmt.Stringer`, which makes a
|
||||||
|
variety of operations a bit simpler.
|
||||||
|
|
||||||
|
## v1.3.0 (25 Apr 2017)
|
||||||
|
|
||||||
|
This release adds an enhancement to zap's testing helpers as well as the
|
||||||
|
ability to marshal an AtomicLevel. It is fully backward-compatible.
|
||||||
|
|
||||||
|
Enhancements:
|
||||||
|
|
||||||
|
* [#415][]: Add a substring-filtering helper to zap's observer. This is
|
||||||
|
particularly useful when testing the `SugaredLogger`.
|
||||||
|
* [#416][]: Make `AtomicLevel` implement `encoding.TextMarshaler`.
|
||||||
|
|
||||||
|
## v1.2.0 (13 Apr 2017)
|
||||||
|
|
||||||
|
This release adds a gRPC compatibility wrapper. It is fully backward-compatible.
|
||||||
|
|
||||||
|
Enhancements:
|
||||||
|
|
||||||
|
* [#402][]: Add a `zapgrpc` package that wraps zap's Logger and implements
|
||||||
|
`grpclog.Logger`.
|
||||||
|
|
||||||
|
## v1.1.0 (31 Mar 2017)
|
||||||
|
|
||||||
|
This release fixes two bugs and adds some enhancements to zap's testing helpers.
|
||||||
|
It is fully backward-compatible.
|
||||||
|
|
||||||
|
Bugfixes:
|
||||||
|
|
||||||
|
* [#385][]: Fix caller path trimming on Windows.
|
||||||
|
* [#396][]: Fix a panic when attempting to use non-existent directories with
|
||||||
|
zap's configuration struct.
|
||||||
|
|
||||||
|
Enhancements:
|
||||||
|
|
||||||
|
* [#386][]: Add filtering helpers to zaptest's observing logger.
|
||||||
|
|
||||||
|
Thanks to @moitias for contributing to this release.
|
||||||
|
|
||||||
|
## v1.0.0 (14 Mar 2017)
|
||||||
|
|
||||||
|
This is zap's first stable release. All exported APIs are now final, and no
|
||||||
|
further breaking changes will be made in the 1.x release series. Anyone using a
|
||||||
|
semver-aware dependency manager should now pin to `^1`.
|
||||||
|
|
||||||
|
Breaking changes:
|
||||||
|
|
||||||
|
* [#366][]: Add byte-oriented APIs to encoders to log UTF-8 encoded text without
|
||||||
|
casting from `[]byte` to `string`.
|
||||||
|
* [#364][]: To support buffering outputs, add `Sync` methods to `zapcore.Core`,
|
||||||
|
`zap.Logger`, and `zap.SugaredLogger`.
|
||||||
|
* [#371][]: Rename the `testutils` package to `zaptest`, which is less likely to
|
||||||
|
clash with other testing helpers.
|
||||||
|
|
||||||
|
Bugfixes:
|
||||||
|
|
||||||
|
* [#362][]: Make the ISO8601 time formatters fixed-width, which is friendlier
|
||||||
|
for tab-separated console output.
|
||||||
|
* [#369][]: Remove the automatic locks in `zapcore.NewCore`, which allows zap to
|
||||||
|
work with concurrency-safe `WriteSyncer` implementations.
|
||||||
|
* [#347][]: Stop reporting errors when trying to `fsync` standard out on Linux
|
||||||
|
systems.
|
||||||
|
* [#373][]: Report the correct caller from zap's standard library
|
||||||
|
interoperability wrappers.
|
||||||
|
|
||||||
|
Enhancements:
|
||||||
|
|
||||||
|
* [#348][]: Add a registry allowing third-party encodings to work with zap's
|
||||||
|
built-in `Config`.
|
||||||
|
* [#327][]: Make the representation of logger callers configurable (like times,
|
||||||
|
levels, and durations).
|
||||||
|
* [#376][]: Allow third-party encoders to use their own buffer pools, which
|
||||||
|
removes the last performance advantage that zap's encoders have over plugins.
|
||||||
|
* [#346][]: Add `CombineWriteSyncers`, a convenience function to tee multiple
|
||||||
|
`WriteSyncer`s and lock the result.
|
||||||
|
* [#365][]: Make zap's stacktraces compatible with mid-stack inlining (coming in
|
||||||
|
Go 1.9).
|
||||||
|
* [#372][]: Export zap's observing logger as `zaptest/observer`. This makes it
|
||||||
|
easier for particularly punctilious users to unit test their application's
|
||||||
|
logging.
|
||||||
|
|
||||||
|
Thanks to @suyash, @htrendev, @flisky, @Ulexus, and @skipor for their
|
||||||
|
contributions to this release.
|
||||||
|
|
||||||
|
## v1.0.0-rc.3 (7 Mar 2017)
|
||||||
|
|
||||||
|
This is the third release candidate for zap's stable release. There are no
|
||||||
|
breaking changes.
|
||||||
|
|
||||||
|
Bugfixes:
|
||||||
|
|
||||||
|
* [#339][]: Byte slices passed to `zap.Any` are now correctly treated as binary blobs
|
||||||
|
rather than `[]uint8`.
|
||||||
|
|
||||||
|
Enhancements:
|
||||||
|
|
||||||
|
* [#307][]: Users can opt into colored output for log levels.
|
||||||
|
* [#353][]: In addition to hijacking the output of the standard library's
|
||||||
|
package-global logging functions, users can now construct a zap-backed
|
||||||
|
`log.Logger` instance.
|
||||||
|
* [#311][]: Frames from common runtime functions and some of zap's internal
|
||||||
|
machinery are now omitted from stacktraces.
|
||||||
|
|
||||||
|
Thanks to @ansel1 and @suyash for their contributions to this release.
|
||||||
|
|
||||||
|
## v1.0.0-rc.2 (21 Feb 2017)
|
||||||
|
|
||||||
|
This is the second release candidate for zap's stable release. It includes two
|
||||||
|
breaking changes.
|
||||||
|
|
||||||
|
Breaking changes:
|
||||||
|
|
||||||
|
* [#316][]: Zap's global loggers are now fully concurrency-safe
|
||||||
|
(previously, users had to ensure that `ReplaceGlobals` was called before the
|
||||||
|
loggers were in use). However, they must now be accessed via the `L()` and
|
||||||
|
`S()` functions. Users can update their projects with
|
||||||
|
|
||||||
|
```
|
||||||
|
gofmt -r "zap.L -> zap.L()" -w .
|
||||||
|
gofmt -r "zap.S -> zap.S()" -w .
|
||||||
|
```
|
||||||
|
* [#309][] and [#317][]: RC1 was mistakenly shipped with invalid
|
||||||
|
JSON and YAML struct tags on all config structs. This release fixes the tags
|
||||||
|
and adds static analysis to prevent similar bugs in the future.
|
||||||
|
|
||||||
|
Bugfixes:
|
||||||
|
|
||||||
|
* [#321][]: Redirecting the standard library's `log` output now
|
||||||
|
correctly reports the logger's caller.
|
||||||
|
|
||||||
|
Enhancements:
|
||||||
|
|
||||||
|
* [#325][] and [#333][]: Zap now transparently supports non-standard, rich
|
||||||
|
errors like those produced by `github.com/pkg/errors`.
|
||||||
|
* [#326][]: Though `New(nil)` continues to return a no-op logger, `NewNop()` is
|
||||||
|
now preferred. Users can update their projects with `gofmt -r 'zap.New(nil) ->
|
||||||
|
zap.NewNop()' -w .`.
|
||||||
|
* [#300][]: Incorrectly importing zap as `github.com/uber-go/zap` now returns a
|
||||||
|
more informative error.
|
||||||
|
|
||||||
|
Thanks to @skipor and @chapsuk for their contributions to this release.
|
||||||
|
|
||||||
|
## v1.0.0-rc.1 (14 Feb 2017)
|
||||||
|
|
||||||
|
This is the first release candidate for zap's stable release. There are multiple
|
||||||
|
breaking changes and improvements from the pre-release version. Most notably:
|
||||||
|
|
||||||
|
* **Zap's import path is now "go.uber.org/zap"** — all users will
|
||||||
|
need to update their code.
|
||||||
|
* User-facing types and functions remain in the `zap` package. Code relevant
|
||||||
|
largely to extension authors is now in the `zapcore` package.
|
||||||
|
* The `zapcore.Core` type makes it easy for third-party packages to use zap's
|
||||||
|
internals but provide a different user-facing API.
|
||||||
|
* `Logger` is now a concrete type instead of an interface.
|
||||||
|
* A less verbose (though slower) logging API is included by default.
|
||||||
|
* Package-global loggers `L` and `S` are included.
|
||||||
|
* A human-friendly console encoder is included.
|
||||||
|
* A declarative config struct allows common logger configurations to be managed
|
||||||
|
as configuration instead of code.
|
||||||
|
* Sampling is more accurate, and doesn't depend on the standard library's shared
|
||||||
|
timer heap.
|
||||||
|
|
||||||
|
## v0.1.0-beta.1 (6 Feb 2017)
|
||||||
|
|
||||||
|
This is a minor version, tagged to allow users to pin to the pre-1.0 APIs and
|
||||||
|
upgrade at their leisure. Since this is the first tagged release, there are no
|
||||||
|
backward compatibility concerns and all functionality is new.
|
||||||
|
|
||||||
|
Early zap adopters should pin to the 0.1.x minor version until they're ready to
|
||||||
|
upgrade to the upcoming stable release.
|
||||||
|
|
||||||
|
[#316]: https://github.com/uber-go/zap/pull/316
|
||||||
|
[#309]: https://github.com/uber-go/zap/pull/309
|
||||||
|
[#317]: https://github.com/uber-go/zap/pull/317
|
||||||
|
[#321]: https://github.com/uber-go/zap/pull/321
|
||||||
|
[#325]: https://github.com/uber-go/zap/pull/325
|
||||||
|
[#333]: https://github.com/uber-go/zap/pull/333
|
||||||
|
[#326]: https://github.com/uber-go/zap/pull/326
|
||||||
|
[#300]: https://github.com/uber-go/zap/pull/300
|
||||||
|
[#339]: https://github.com/uber-go/zap/pull/339
|
||||||
|
[#307]: https://github.com/uber-go/zap/pull/307
|
||||||
|
[#353]: https://github.com/uber-go/zap/pull/353
|
||||||
|
[#311]: https://github.com/uber-go/zap/pull/311
|
||||||
|
[#366]: https://github.com/uber-go/zap/pull/366
|
||||||
|
[#364]: https://github.com/uber-go/zap/pull/364
|
||||||
|
[#371]: https://github.com/uber-go/zap/pull/371
|
||||||
|
[#362]: https://github.com/uber-go/zap/pull/362
|
||||||
|
[#369]: https://github.com/uber-go/zap/pull/369
|
||||||
|
[#347]: https://github.com/uber-go/zap/pull/347
|
||||||
|
[#373]: https://github.com/uber-go/zap/pull/373
|
||||||
|
[#348]: https://github.com/uber-go/zap/pull/348
|
||||||
|
[#327]: https://github.com/uber-go/zap/pull/327
|
||||||
|
[#376]: https://github.com/uber-go/zap/pull/376
|
||||||
|
[#346]: https://github.com/uber-go/zap/pull/346
|
||||||
|
[#365]: https://github.com/uber-go/zap/pull/365
|
||||||
|
[#372]: https://github.com/uber-go/zap/pull/372
|
||||||
|
[#385]: https://github.com/uber-go/zap/pull/385
|
||||||
|
[#396]: https://github.com/uber-go/zap/pull/396
|
||||||
|
[#386]: https://github.com/uber-go/zap/pull/386
|
||||||
|
[#402]: https://github.com/uber-go/zap/pull/402
|
||||||
|
[#415]: https://github.com/uber-go/zap/pull/415
|
||||||
|
[#416]: https://github.com/uber-go/zap/pull/416
|
||||||
|
[#424]: https://github.com/uber-go/zap/pull/424
|
||||||
|
[#425]: https://github.com/uber-go/zap/pull/425
|
||||||
|
[#431]: https://github.com/uber-go/zap/pull/431
|
||||||
|
[#435]: https://github.com/uber-go/zap/pull/435
|
||||||
|
[#444]: https://github.com/uber-go/zap/pull/444
|
||||||
|
[#477]: https://github.com/uber-go/zap/pull/477
|
||||||
|
[#465]: https://github.com/uber-go/zap/pull/465
|
||||||
|
[#460]: https://github.com/uber-go/zap/pull/460
|
||||||
|
[#470]: https://github.com/uber-go/zap/pull/470
|
||||||
|
[#487]: https://github.com/uber-go/zap/pull/487
|
||||||
|
[#490]: https://github.com/uber-go/zap/pull/490
|
||||||
|
[#491]: https://github.com/uber-go/zap/pull/491
|
||||||
|
[#491]: https://github.com/uber-go/zap/pull/439
|
||||||
|
[#504]: https://github.com/uber-go/zap/pull/504
|
|
@ -0,0 +1,75 @@
|
||||||
|
# Contributor Covenant Code of Conduct
|
||||||
|
|
||||||
|
## Our Pledge
|
||||||
|
|
||||||
|
In the interest of fostering an open and welcoming environment, we as
|
||||||
|
contributors and maintainers pledge to making participation in our project and
|
||||||
|
our community a harassment-free experience for everyone, regardless of age,
|
||||||
|
body size, disability, ethnicity, gender identity and expression, level of
|
||||||
|
experience, nationality, personal appearance, race, religion, or sexual
|
||||||
|
identity and orientation.
|
||||||
|
|
||||||
|
## Our Standards
|
||||||
|
|
||||||
|
Examples of behavior that contributes to creating a positive environment
|
||||||
|
include:
|
||||||
|
|
||||||
|
* Using welcoming and inclusive language
|
||||||
|
* Being respectful of differing viewpoints and experiences
|
||||||
|
* Gracefully accepting constructive criticism
|
||||||
|
* Focusing on what is best for the community
|
||||||
|
* Showing empathy towards other community members
|
||||||
|
|
||||||
|
Examples of unacceptable behavior by participants include:
|
||||||
|
|
||||||
|
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||||
|
advances
|
||||||
|
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||||
|
* Public or private harassment
|
||||||
|
* Publishing others' private information, such as a physical or electronic
|
||||||
|
address, without explicit permission
|
||||||
|
* Other conduct which could reasonably be considered inappropriate in a
|
||||||
|
professional setting
|
||||||
|
|
||||||
|
## Our Responsibilities
|
||||||
|
|
||||||
|
Project maintainers are responsible for clarifying the standards of acceptable
|
||||||
|
behavior and are expected to take appropriate and fair corrective action in
|
||||||
|
response to any instances of unacceptable behavior.
|
||||||
|
|
||||||
|
Project maintainers have the right and responsibility to remove, edit, or
|
||||||
|
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||||
|
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||||
|
permanently any contributor for other behaviors that they deem inappropriate,
|
||||||
|
threatening, offensive, or harmful.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
This Code of Conduct applies both within project spaces and in public spaces
|
||||||
|
when an individual is representing the project or its community. Examples of
|
||||||
|
representing a project or community include using an official project e-mail
|
||||||
|
address, posting via an official social media account, or acting as an
|
||||||
|
appointed representative at an online or offline event. Representation of a
|
||||||
|
project may be further defined and clarified by project maintainers.
|
||||||
|
|
||||||
|
## Enforcement
|
||||||
|
|
||||||
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||||
|
reported by contacting the project team at oss-conduct@uber.com. The project
|
||||||
|
team will review and investigate all complaints, and will respond in a way
|
||||||
|
that it deems appropriate to the circumstances. The project team is obligated
|
||||||
|
to maintain confidentiality with regard to the reporter of an incident.
|
||||||
|
Further details of specific enforcement policies may be posted separately.
|
||||||
|
|
||||||
|
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||||
|
faith may face temporary or permanent repercussions as determined by other
|
||||||
|
members of the project's leadership.
|
||||||
|
|
||||||
|
## Attribution
|
||||||
|
|
||||||
|
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||||
|
version 1.4, available at
|
||||||
|
[http://contributor-covenant.org/version/1/4][version].
|
||||||
|
|
||||||
|
[homepage]: http://contributor-covenant.org
|
||||||
|
[version]: http://contributor-covenant.org/version/1/4/
|
|
@ -0,0 +1,81 @@
|
||||||
|
# Contributing
|
||||||
|
|
||||||
|
We'd love your help making zap the very best structured logging library in Go!
|
||||||
|
|
||||||
|
If you'd like to add new exported APIs, please [open an issue][open-issue]
|
||||||
|
describing your proposal — discussing API changes ahead of time makes
|
||||||
|
pull request review much smoother. In your issue, pull request, and any other
|
||||||
|
communications, please remember to treat your fellow contributors with
|
||||||
|
respect! We take our [code of conduct](CODE_OF_CONDUCT.md) seriously.
|
||||||
|
|
||||||
|
Note that you'll need to sign [Uber's Contributor License Agreement][cla]
|
||||||
|
before we can accept any of your contributions. If necessary, a bot will remind
|
||||||
|
you to accept the CLA when you open your pull request.
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
[Fork][fork], then clone the repository:
|
||||||
|
|
||||||
|
```
|
||||||
|
mkdir -p $GOPATH/src/go.uber.org
|
||||||
|
cd $GOPATH/src/go.uber.org
|
||||||
|
git clone git@github.com:your_github_username/zap.git
|
||||||
|
cd zap
|
||||||
|
git remote add upstream https://github.com/uber-go/zap.git
|
||||||
|
git fetch upstream
|
||||||
|
```
|
||||||
|
|
||||||
|
Install zap's dependencies:
|
||||||
|
|
||||||
|
```
|
||||||
|
make dependencies
|
||||||
|
```
|
||||||
|
|
||||||
|
Make sure that the tests and the linters pass:
|
||||||
|
|
||||||
|
```
|
||||||
|
make test
|
||||||
|
make lint
|
||||||
|
```
|
||||||
|
|
||||||
|
If you're not using the minor version of Go specified in the Makefile's
|
||||||
|
`LINTABLE_MINOR_VERSIONS` variable, `make lint` doesn't do anything. This is
|
||||||
|
fine, but it means that you'll only discover lint failures after you open your
|
||||||
|
pull request.
|
||||||
|
|
||||||
|
## Making Changes
|
||||||
|
|
||||||
|
Start by creating a new branch for your changes:
|
||||||
|
|
||||||
|
```
|
||||||
|
cd $GOPATH/src/go.uber.org/zap
|
||||||
|
git checkout master
|
||||||
|
git fetch upstream
|
||||||
|
git rebase upstream/master
|
||||||
|
git checkout -b cool_new_feature
|
||||||
|
```
|
||||||
|
|
||||||
|
Make your changes, then ensure that `make lint` and `make test` still pass. If
|
||||||
|
you're satisfied with your changes, push them to your fork.
|
||||||
|
|
||||||
|
```
|
||||||
|
git push origin cool_new_feature
|
||||||
|
```
|
||||||
|
|
||||||
|
Then use the GitHub UI to open a pull request.
|
||||||
|
|
||||||
|
At this point, you're waiting on us to review your changes. We *try* to respond
|
||||||
|
to issues and pull requests within a few business days, and we may suggest some
|
||||||
|
improvements or alternatives. Once your changes are approved, one of the
|
||||||
|
project maintainers will merge them.
|
||||||
|
|
||||||
|
We're much more likely to approve your changes if you:
|
||||||
|
|
||||||
|
* Add tests for new functionality.
|
||||||
|
* Write a [good commit message][commit-message].
|
||||||
|
* Maintain backward compatibility.
|
||||||
|
|
||||||
|
[fork]: https://github.com/uber-go/zap/fork
|
||||||
|
[open-issue]: https://github.com/uber-go/zap/issues/new
|
||||||
|
[cla]: https://cla-assistant.io/uber-go/zap
|
||||||
|
[commit-message]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html
|
|
@ -0,0 +1,140 @@
|
||||||
|
# Frequently Asked Questions
|
||||||
|
|
||||||
|
## Design
|
||||||
|
|
||||||
|
### Why spend so much effort on logger performance?
|
||||||
|
|
||||||
|
Of course, most applications won't notice the impact of a slow logger: they
|
||||||
|
already take tens or hundreds of milliseconds for each operation, so an extra
|
||||||
|
millisecond doesn't matter.
|
||||||
|
|
||||||
|
On the other hand, why *not* make structured logging fast? The `SugaredLogger`
|
||||||
|
isn't any harder to use than other logging packages, and the `Logger` makes
|
||||||
|
structured logging possible in performance-sensitive contexts. Across a fleet
|
||||||
|
of Go microservices, making each application even slightly more efficient adds
|
||||||
|
up quickly.
|
||||||
|
|
||||||
|
### Why aren't `Logger` and `SugaredLogger` interfaces?
|
||||||
|
|
||||||
|
Unlike the familiar `io.Writer` and `http.Handler`, `Logger` and
|
||||||
|
`SugaredLogger` interfaces would include *many* methods. As [Rob Pike points
|
||||||
|
out][go-proverbs], "The bigger the interface, the weaker the abstraction."
|
||||||
|
Interfaces are also rigid — *any* change requires releasing a new major
|
||||||
|
version, since it breaks all third-party implementations.
|
||||||
|
|
||||||
|
Making the `Logger` and `SugaredLogger` concrete types doesn't sacrifice much
|
||||||
|
abstraction, and it lets us add methods without introducing breaking changes.
|
||||||
|
Your applications should define and depend upon an interface that includes
|
||||||
|
just the methods you use.
|
||||||
|
|
||||||
|
### Why sample application logs?
|
||||||
|
|
||||||
|
Applications often experience runs of errors, either because of a bug or
|
||||||
|
because of a misbehaving user. Logging errors is usually a good idea, but it
|
||||||
|
can easily make this bad situation worse: not only is your application coping
|
||||||
|
with a flood of errors, it's also spending extra CPU cycles and I/O logging
|
||||||
|
those errors. Since writes are typically serialized, logging limits throughput
|
||||||
|
when you need it most.
|
||||||
|
|
||||||
|
Sampling fixes this problem by dropping repetitive log entries. Under normal
|
||||||
|
conditions, your application writes out every entry. When similar entries are
|
||||||
|
logged hundreds or thousands of times each second, though, zap begins dropping
|
||||||
|
duplicates to preserve throughput.
|
||||||
|
|
||||||
|
### Why do the structured logging APIs take a message in addition to fields?
|
||||||
|
|
||||||
|
Subjectively, we find it helpful to accompany structured context with a brief
|
||||||
|
description. This isn't critical during development, but it makes debugging
|
||||||
|
and operating unfamiliar systems much easier.
|
||||||
|
|
||||||
|
More concretely, zap's sampling algorithm uses the message to identify
|
||||||
|
duplicate entries. In our experience, this is a practical middle ground
|
||||||
|
between random sampling (which often drops the exact entry that you need while
|
||||||
|
debugging) and hashing the complete entry (which is prohibitively expensive).
|
||||||
|
|
||||||
|
### Why include package-global loggers?
|
||||||
|
|
||||||
|
Since so many other logging packages include a global logger, many
|
||||||
|
applications aren't designed to accept loggers as explicit parameters.
|
||||||
|
Changing function signatures is often a breaking change, so zap includes
|
||||||
|
global loggers to simplify migration.
|
||||||
|
|
||||||
|
Avoid them where possible.
|
||||||
|
|
||||||
|
### Why include dedicated Panic and Fatal log levels?
|
||||||
|
|
||||||
|
In general, application code should handle errors gracefully instead of using
|
||||||
|
`panic` or `os.Exit`. However, every rule has exceptions, and it's common to
|
||||||
|
crash when an error is truly unrecoverable. To avoid losing any information
|
||||||
|
— especially the reason for the crash — the logger must flush any
|
||||||
|
buffered entries before the process exits.
|
||||||
|
|
||||||
|
Zap makes this easy by offering `Panic` and `Fatal` logging methods that
|
||||||
|
automatically flush before exiting. Of course, this doesn't guarantee that
|
||||||
|
logs will never be lost, but it eliminates a common error.
|
||||||
|
|
||||||
|
See the discussion in uber-go/zap#207 for more details.
|
||||||
|
|
||||||
|
### What's `DPanic`?
|
||||||
|
|
||||||
|
`DPanic` stands for "panic in development." In development, it logs at
|
||||||
|
`PanicLevel`; otherwise, it logs at `ErrorLevel`. `DPanic` makes it easier to
|
||||||
|
catch errors that are theoretically possible, but shouldn't actually happen,
|
||||||
|
*without* crashing in production.
|
||||||
|
|
||||||
|
If you've ever written code like this, you need `DPanic`:
|
||||||
|
|
||||||
|
```go
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("shouldn't ever get here: %v", err))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
### What does the error `expects import "go.uber.org/zap"` mean?
|
||||||
|
|
||||||
|
Either zap was installed incorrectly or you're referencing the wrong package
|
||||||
|
name in your code.
|
||||||
|
|
||||||
|
Zap's source code happens to be hosted on GitHub, but the [import
|
||||||
|
path][import-path] is `go.uber.org/zap`. This gives us, the project
|
||||||
|
maintainers, the freedom to move the source code if necessary. However, it
|
||||||
|
means that you need to take a little care when installing and using the
|
||||||
|
package.
|
||||||
|
|
||||||
|
If you follow two simple rules, everything should work: install zap with `go
|
||||||
|
get -u go.uber.org/zap`, and always import it in your code with `import
|
||||||
|
"go.uber.org/zap"`. Your code shouldn't contain *any* references to
|
||||||
|
`github.com/uber-go/zap`.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Does zap support log rotation?
|
||||||
|
|
||||||
|
Zap doesn't natively support rotating log files, since we prefer to leave this
|
||||||
|
to an external program like `logrotate`.
|
||||||
|
|
||||||
|
However, it's easy to integrate a log rotation package like
|
||||||
|
[`gopkg.in/natefinch/lumberjack.v2`][lumberjack] as a `zapcore.WriteSyncer`.
|
||||||
|
|
||||||
|
```go
|
||||||
|
// lumberjack.Logger is already safe for concurrent use, so we don't need to
|
||||||
|
// lock it.
|
||||||
|
w := zapcore.AddSync(&lumberjack.Logger{
|
||||||
|
Filename: "/var/log/myapp/foo.log",
|
||||||
|
MaxSize: 500, // megabytes
|
||||||
|
MaxBackups: 3,
|
||||||
|
MaxAge: 28, // days
|
||||||
|
})
|
||||||
|
core := zapcore.NewCore(
|
||||||
|
zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()),
|
||||||
|
w,
|
||||||
|
zap.InfoLevel,
|
||||||
|
)
|
||||||
|
logger := zap.New(core)
|
||||||
|
```
|
||||||
|
|
||||||
|
[go-proverbs]: https://go-proverbs.github.io/
|
||||||
|
[import-path]: https://golang.org/cmd/go/#hdr-Remote_import_paths
|
||||||
|
[lumberjack]: https://godoc.org/gopkg.in/natefinch/lumberjack.v2
|
|
@ -0,0 +1,19 @@
|
||||||
|
Copyright (c) 2016-2017 Uber Technologies, Inc.
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
|
@ -0,0 +1,76 @@
|
||||||
|
export GO15VENDOREXPERIMENT=1
|
||||||
|
|
||||||
|
BENCH_FLAGS ?= -cpuprofile=cpu.pprof -memprofile=mem.pprof -benchmem
|
||||||
|
PKGS ?= $(shell glide novendor)
|
||||||
|
# Many Go tools take file globs or directories as arguments instead of packages.
|
||||||
|
PKG_FILES ?= *.go zapcore benchmarks buffer zapgrpc zaptest zaptest/observer internal/bufferpool internal/exit internal/color
|
||||||
|
|
||||||
|
# The linting tools evolve with each Go version, so run them only on the latest
|
||||||
|
# stable release.
|
||||||
|
GO_VERSION := $(shell go version | cut -d " " -f 3)
|
||||||
|
GO_MINOR_VERSION := $(word 2,$(subst ., ,$(GO_VERSION)))
|
||||||
|
LINTABLE_MINOR_VERSIONS := 8
|
||||||
|
ifneq ($(filter $(LINTABLE_MINOR_VERSIONS),$(GO_MINOR_VERSION)),)
|
||||||
|
SHOULD_LINT := true
|
||||||
|
endif
|
||||||
|
|
||||||
|
|
||||||
|
.PHONY: all
|
||||||
|
all: lint test
|
||||||
|
|
||||||
|
.PHONY: dependencies
|
||||||
|
dependencies:
|
||||||
|
@echo "Installing Glide and locked dependencies..."
|
||||||
|
glide --version || go get -u -f github.com/Masterminds/glide
|
||||||
|
glide install
|
||||||
|
@echo "Installing test dependencies..."
|
||||||
|
go install ./vendor/github.com/axw/gocov/gocov
|
||||||
|
go install ./vendor/github.com/mattn/goveralls
|
||||||
|
ifdef SHOULD_LINT
|
||||||
|
@echo "Installing golint..."
|
||||||
|
go install ./vendor/github.com/golang/lint/golint
|
||||||
|
else
|
||||||
|
@echo "Not installing golint, since we don't expect to lint on" $(GO_VERSION)
|
||||||
|
endif
|
||||||
|
|
||||||
|
# Disable printf-like invocation checking due to testify.assert.Error()
|
||||||
|
VET_RULES := -printf=false
|
||||||
|
|
||||||
|
.PHONY: lint
|
||||||
|
lint:
|
||||||
|
ifdef SHOULD_LINT
|
||||||
|
@rm -rf lint.log
|
||||||
|
@echo "Checking formatting..."
|
||||||
|
@gofmt -d -s $(PKG_FILES) 2>&1 | tee lint.log
|
||||||
|
@echo "Installing test dependencies for vet..."
|
||||||
|
@go test -i $(PKGS)
|
||||||
|
@echo "Checking vet..."
|
||||||
|
@$(foreach dir,$(PKG_FILES),go tool vet $(VET_RULES) $(dir) 2>&1 | tee -a lint.log;)
|
||||||
|
@echo "Checking lint..."
|
||||||
|
@$(foreach dir,$(PKGS),golint $(dir) 2>&1 | tee -a lint.log;)
|
||||||
|
@echo "Checking for unresolved FIXMEs..."
|
||||||
|
@git grep -i fixme | grep -v -e vendor -e Makefile | tee -a lint.log
|
||||||
|
@echo "Checking for license headers..."
|
||||||
|
@./check_license.sh | tee -a lint.log
|
||||||
|
@[ ! -s lint.log ]
|
||||||
|
else
|
||||||
|
@echo "Skipping linters on" $(GO_VERSION)
|
||||||
|
endif
|
||||||
|
|
||||||
|
.PHONY: test
|
||||||
|
test:
|
||||||
|
go test -race $(PKGS)
|
||||||
|
|
||||||
|
.PHONY: cover
|
||||||
|
cover:
|
||||||
|
./scripts/cover.sh $(PKGS)
|
||||||
|
|
||||||
|
.PHONY: bench
|
||||||
|
BENCH ?= .
|
||||||
|
bench:
|
||||||
|
@$(foreach pkg,$(PKGS),go test -bench=$(BENCH) -run="^$$" $(BENCH_FLAGS) $(pkg);)
|
||||||
|
|
||||||
|
.PHONY: updatereadme
|
||||||
|
updatereadme:
|
||||||
|
rm -f README.md
|
||||||
|
cat .readme.tmpl | go run internal/readme/readme.go > README.md
|
|
@ -0,0 +1,136 @@
|
||||||
|
# :zap: zap [![GoDoc][doc-img]][doc] [![Build Status][ci-img]][ci] [![Coverage Status][cov-img]][cov]
|
||||||
|
|
||||||
|
Blazing fast, structured, leveled logging in Go.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
`go get -u go.uber.org/zap`
|
||||||
|
|
||||||
|
Note that zap only supports the two most recent minor versions of Go.
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
In contexts where performance is nice, but not critical, use the
|
||||||
|
`SugaredLogger`. It's 4-10x faster than than other structured logging
|
||||||
|
packages and includes both structured and `printf`-style APIs.
|
||||||
|
|
||||||
|
```go
|
||||||
|
logger, _ := zap.NewProduction()
|
||||||
|
defer logger.Sync() // flushes buffer, if any
|
||||||
|
sugar := logger.Sugar()
|
||||||
|
sugar.Infow("failed to fetch URL",
|
||||||
|
// Structured context as loosely typed key-value pairs.
|
||||||
|
"url", url,
|
||||||
|
"attempt", 3,
|
||||||
|
"backoff", time.Second,
|
||||||
|
)
|
||||||
|
sugar.Infof("Failed to fetch URL: %s", url)
|
||||||
|
```
|
||||||
|
|
||||||
|
When performance and type safety are critical, use the `Logger`. It's even
|
||||||
|
faster than the `SugaredLogger` and allocates far less, but it only supports
|
||||||
|
structured logging.
|
||||||
|
|
||||||
|
```go
|
||||||
|
logger, _ := zap.NewProduction()
|
||||||
|
defer logger.Sync()
|
||||||
|
logger.Info("failed to fetch URL",
|
||||||
|
// Structured context as strongly typed Field values.
|
||||||
|
zap.String("url", url),
|
||||||
|
zap.Int("attempt", 3),
|
||||||
|
zap.Duration("backoff", time.Second),
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
See the [documentation][doc] and [FAQ](FAQ.md) for more details.
|
||||||
|
|
||||||
|
## Performance
|
||||||
|
|
||||||
|
For applications that log in the hot path, reflection-based serialization and
|
||||||
|
string formatting are prohibitively expensive — they're CPU-intensive
|
||||||
|
and make many small allocations. Put differently, using `encoding/json` and
|
||||||
|
`fmt.Fprintf` to log tons of `interface{}`s makes your application slow.
|
||||||
|
|
||||||
|
Zap takes a different approach. It includes a reflection-free, zero-allocation
|
||||||
|
JSON encoder, and the base `Logger` strives to avoid serialization overhead
|
||||||
|
and allocations wherever possible. By building the high-level `SugaredLogger`
|
||||||
|
on that foundation, zap lets users *choose* when they need to count every
|
||||||
|
allocation and when they'd prefer a more familiar, loosely typed API.
|
||||||
|
|
||||||
|
As measured by its own [benchmarking suite][], not only is zap more performant
|
||||||
|
than comparable structured logging packages — it's also faster than the
|
||||||
|
standard library. Like all benchmarks, take these with a grain of salt.<sup
|
||||||
|
id="anchor-versions">[1](#footnote-versions)</sup>
|
||||||
|
|
||||||
|
Log a message and 10 fields:
|
||||||
|
|
||||||
|
| Package | Time | Objects Allocated |
|
||||||
|
| :--- | :---: | :---: |
|
||||||
|
| :zap: zap | 3131 ns/op | 5 allocs/op |
|
||||||
|
| :zap: zap (sugared) | 4173 ns/op | 21 allocs/op |
|
||||||
|
| zerolog | 16154 ns/op | 90 allocs/op |
|
||||||
|
| lion | 16341 ns/op | 111 allocs/op |
|
||||||
|
| go-kit | 17049 ns/op | 126 allocs/op |
|
||||||
|
| logrus | 23662 ns/op | 142 allocs/op |
|
||||||
|
| log15 | 36351 ns/op | 149 allocs/op |
|
||||||
|
| apex/log | 42530 ns/op | 126 allocs/op |
|
||||||
|
|
||||||
|
Log a message with a logger that already has 10 fields of context:
|
||||||
|
|
||||||
|
| Package | Time | Objects Allocated |
|
||||||
|
| :--- | :---: | :---: |
|
||||||
|
| :zap: zap | 380 ns/op | 0 allocs/op |
|
||||||
|
| :zap: zap (sugared) | 564 ns/op | 2 allocs/op |
|
||||||
|
| zerolog | 321 ns/op | 0 allocs/op |
|
||||||
|
| lion | 7092 ns/op | 39 allocs/op |
|
||||||
|
| go-kit | 20226 ns/op | 115 allocs/op |
|
||||||
|
| logrus | 22312 ns/op | 130 allocs/op |
|
||||||
|
| log15 | 28788 ns/op | 79 allocs/op |
|
||||||
|
| apex/log | 42063 ns/op | 115 allocs/op |
|
||||||
|
|
||||||
|
Log a static string, without any context or `printf`-style templating:
|
||||||
|
|
||||||
|
| Package | Time | Objects Allocated |
|
||||||
|
| :--- | :---: | :---: |
|
||||||
|
| :zap: zap | 361 ns/op | 0 allocs/op |
|
||||||
|
| :zap: zap (sugared) | 534 ns/op | 2 allocs/op |
|
||||||
|
| zerolog | 323 ns/op | 0 allocs/op |
|
||||||
|
| standard library | 575 ns/op | 2 allocs/op |
|
||||||
|
| go-kit | 922 ns/op | 13 allocs/op |
|
||||||
|
| lion | 1413 ns/op | 10 allocs/op |
|
||||||
|
| logrus | 2291 ns/op | 27 allocs/op |
|
||||||
|
| apex/log | 3690 ns/op | 11 allocs/op |
|
||||||
|
| log15 | 5954 ns/op | 26 allocs/op |
|
||||||
|
|
||||||
|
## Development Status: Stable
|
||||||
|
|
||||||
|
All APIs are finalized, and no breaking changes will be made in the 1.x series
|
||||||
|
of releases. Users of semver-aware dependency management systems should pin
|
||||||
|
zap to `^1`.
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
We encourage and support an active, healthy community of contributors —
|
||||||
|
including you! Details are in the [contribution guide](CONTRIBUTING.md) and
|
||||||
|
the [code of conduct](CODE_OF_CONDUCT.md). The zap maintainers keep an eye on
|
||||||
|
issues and pull requests, but you can also report any negative conduct to
|
||||||
|
oss-conduct@uber.com. That email list is a private, safe space; even the zap
|
||||||
|
maintainers don't have access, so don't hesitate to hold us to a high
|
||||||
|
standard.
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
Released under the [MIT License](LICENSE.txt).
|
||||||
|
|
||||||
|
<sup id="footnote-versions">1</sup> In particular, keep in mind that we may be
|
||||||
|
benchmarking against slightly older versions of other packages. Versions are
|
||||||
|
pinned in zap's [glide.lock][] file. [↩](#anchor-versions)
|
||||||
|
|
||||||
|
[doc-img]: https://godoc.org/go.uber.org/zap?status.svg
|
||||||
|
[doc]: https://godoc.org/go.uber.org/zap
|
||||||
|
[ci-img]: https://travis-ci.org/uber-go/zap.svg?branch=master
|
||||||
|
[ci]: https://travis-ci.org/uber-go/zap
|
||||||
|
[cov-img]: https://codecov.io/gh/uber-go/zap/branch/master/graph/badge.svg
|
||||||
|
[cov]: https://codecov.io/gh/uber-go/zap
|
||||||
|
[benchmarking suite]: https://github.com/uber-go/zap/tree/master/benchmarks
|
||||||
|
[glide.lock]: https://github.com/uber-go/zap/blob/master/glide.lock
|
|
@ -0,0 +1,320 @@
|
||||||
|
// Copyright (c) 2016 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
package zap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Array constructs a field with the given key and ArrayMarshaler. It provides
|
||||||
|
// a flexible, but still type-safe and efficient, way to add array-like types
|
||||||
|
// to the logging context. The struct's MarshalLogArray method is called lazily.
|
||||||
|
func Array(key string, val zapcore.ArrayMarshaler) zapcore.Field {
|
||||||
|
return zapcore.Field{Key: key, Type: zapcore.ArrayMarshalerType, Interface: val}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bools constructs a field that carries a slice of bools.
|
||||||
|
func Bools(key string, bs []bool) zapcore.Field {
|
||||||
|
return Array(key, bools(bs))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByteStrings constructs a field that carries a slice of []byte, each of which
|
||||||
|
// must be UTF-8 encoded text.
|
||||||
|
func ByteStrings(key string, bss [][]byte) zapcore.Field {
|
||||||
|
return Array(key, byteStringsArray(bss))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Complex128s constructs a field that carries a slice of complex numbers.
|
||||||
|
func Complex128s(key string, nums []complex128) zapcore.Field {
|
||||||
|
return Array(key, complex128s(nums))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Complex64s constructs a field that carries a slice of complex numbers.
|
||||||
|
func Complex64s(key string, nums []complex64) zapcore.Field {
|
||||||
|
return Array(key, complex64s(nums))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Durations constructs a field that carries a slice of time.Durations.
|
||||||
|
func Durations(key string, ds []time.Duration) zapcore.Field {
|
||||||
|
return Array(key, durations(ds))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float64s constructs a field that carries a slice of floats.
|
||||||
|
func Float64s(key string, nums []float64) zapcore.Field {
|
||||||
|
return Array(key, float64s(nums))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float32s constructs a field that carries a slice of floats.
|
||||||
|
func Float32s(key string, nums []float32) zapcore.Field {
|
||||||
|
return Array(key, float32s(nums))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ints constructs a field that carries a slice of integers.
|
||||||
|
func Ints(key string, nums []int) zapcore.Field {
|
||||||
|
return Array(key, ints(nums))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int64s constructs a field that carries a slice of integers.
|
||||||
|
func Int64s(key string, nums []int64) zapcore.Field {
|
||||||
|
return Array(key, int64s(nums))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int32s constructs a field that carries a slice of integers.
|
||||||
|
func Int32s(key string, nums []int32) zapcore.Field {
|
||||||
|
return Array(key, int32s(nums))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int16s constructs a field that carries a slice of integers.
|
||||||
|
func Int16s(key string, nums []int16) zapcore.Field {
|
||||||
|
return Array(key, int16s(nums))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int8s constructs a field that carries a slice of integers.
|
||||||
|
func Int8s(key string, nums []int8) zapcore.Field {
|
||||||
|
return Array(key, int8s(nums))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strings constructs a field that carries a slice of strings.
|
||||||
|
func Strings(key string, ss []string) zapcore.Field {
|
||||||
|
return Array(key, stringArray(ss))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Times constructs a field that carries a slice of time.Times.
|
||||||
|
func Times(key string, ts []time.Time) zapcore.Field {
|
||||||
|
return Array(key, times(ts))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uints constructs a field that carries a slice of unsigned integers.
|
||||||
|
func Uints(key string, nums []uint) zapcore.Field {
|
||||||
|
return Array(key, uints(nums))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint64s constructs a field that carries a slice of unsigned integers.
|
||||||
|
func Uint64s(key string, nums []uint64) zapcore.Field {
|
||||||
|
return Array(key, uint64s(nums))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint32s constructs a field that carries a slice of unsigned integers.
|
||||||
|
func Uint32s(key string, nums []uint32) zapcore.Field {
|
||||||
|
return Array(key, uint32s(nums))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint16s constructs a field that carries a slice of unsigned integers.
|
||||||
|
func Uint16s(key string, nums []uint16) zapcore.Field {
|
||||||
|
return Array(key, uint16s(nums))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint8s constructs a field that carries a slice of unsigned integers.
|
||||||
|
func Uint8s(key string, nums []uint8) zapcore.Field {
|
||||||
|
return Array(key, uint8s(nums))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uintptrs constructs a field that carries a slice of pointer addresses.
|
||||||
|
func Uintptrs(key string, us []uintptr) zapcore.Field {
|
||||||
|
return Array(key, uintptrs(us))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Errors constructs a field that carries a slice of errors.
|
||||||
|
func Errors(key string, errs []error) zapcore.Field {
|
||||||
|
return Array(key, errArray(errs))
|
||||||
|
}
|
||||||
|
|
||||||
|
type bools []bool
|
||||||
|
|
||||||
|
func (bs bools) MarshalLogArray(arr zapcore.ArrayEncoder) error {
|
||||||
|
for i := range bs {
|
||||||
|
arr.AppendBool(bs[i])
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type byteStringsArray [][]byte
|
||||||
|
|
||||||
|
func (bss byteStringsArray) MarshalLogArray(arr zapcore.ArrayEncoder) error {
|
||||||
|
for i := range bss {
|
||||||
|
arr.AppendByteString(bss[i])
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type complex128s []complex128
|
||||||
|
|
||||||
|
func (nums complex128s) MarshalLogArray(arr zapcore.ArrayEncoder) error {
|
||||||
|
for i := range nums {
|
||||||
|
arr.AppendComplex128(nums[i])
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type complex64s []complex64
|
||||||
|
|
||||||
|
func (nums complex64s) MarshalLogArray(arr zapcore.ArrayEncoder) error {
|
||||||
|
for i := range nums {
|
||||||
|
arr.AppendComplex64(nums[i])
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type durations []time.Duration
|
||||||
|
|
||||||
|
func (ds durations) MarshalLogArray(arr zapcore.ArrayEncoder) error {
|
||||||
|
for i := range ds {
|
||||||
|
arr.AppendDuration(ds[i])
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type float64s []float64
|
||||||
|
|
||||||
|
func (nums float64s) MarshalLogArray(arr zapcore.ArrayEncoder) error {
|
||||||
|
for i := range nums {
|
||||||
|
arr.AppendFloat64(nums[i])
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type float32s []float32
|
||||||
|
|
||||||
|
func (nums float32s) MarshalLogArray(arr zapcore.ArrayEncoder) error {
|
||||||
|
for i := range nums {
|
||||||
|
arr.AppendFloat32(nums[i])
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type ints []int
|
||||||
|
|
||||||
|
func (nums ints) MarshalLogArray(arr zapcore.ArrayEncoder) error {
|
||||||
|
for i := range nums {
|
||||||
|
arr.AppendInt(nums[i])
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type int64s []int64
|
||||||
|
|
||||||
|
func (nums int64s) MarshalLogArray(arr zapcore.ArrayEncoder) error {
|
||||||
|
for i := range nums {
|
||||||
|
arr.AppendInt64(nums[i])
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type int32s []int32
|
||||||
|
|
||||||
|
func (nums int32s) MarshalLogArray(arr zapcore.ArrayEncoder) error {
|
||||||
|
for i := range nums {
|
||||||
|
arr.AppendInt32(nums[i])
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type int16s []int16
|
||||||
|
|
||||||
|
func (nums int16s) MarshalLogArray(arr zapcore.ArrayEncoder) error {
|
||||||
|
for i := range nums {
|
||||||
|
arr.AppendInt16(nums[i])
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type int8s []int8
|
||||||
|
|
||||||
|
func (nums int8s) MarshalLogArray(arr zapcore.ArrayEncoder) error {
|
||||||
|
for i := range nums {
|
||||||
|
arr.AppendInt8(nums[i])
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type stringArray []string
|
||||||
|
|
||||||
|
func (ss stringArray) MarshalLogArray(arr zapcore.ArrayEncoder) error {
|
||||||
|
for i := range ss {
|
||||||
|
arr.AppendString(ss[i])
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type times []time.Time
|
||||||
|
|
||||||
|
func (ts times) MarshalLogArray(arr zapcore.ArrayEncoder) error {
|
||||||
|
for i := range ts {
|
||||||
|
arr.AppendTime(ts[i])
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type uints []uint
|
||||||
|
|
||||||
|
func (nums uints) MarshalLogArray(arr zapcore.ArrayEncoder) error {
|
||||||
|
for i := range nums {
|
||||||
|
arr.AppendUint(nums[i])
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type uint64s []uint64
|
||||||
|
|
||||||
|
func (nums uint64s) MarshalLogArray(arr zapcore.ArrayEncoder) error {
|
||||||
|
for i := range nums {
|
||||||
|
arr.AppendUint64(nums[i])
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type uint32s []uint32
|
||||||
|
|
||||||
|
func (nums uint32s) MarshalLogArray(arr zapcore.ArrayEncoder) error {
|
||||||
|
for i := range nums {
|
||||||
|
arr.AppendUint32(nums[i])
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type uint16s []uint16
|
||||||
|
|
||||||
|
func (nums uint16s) MarshalLogArray(arr zapcore.ArrayEncoder) error {
|
||||||
|
for i := range nums {
|
||||||
|
arr.AppendUint16(nums[i])
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type uint8s []uint8
|
||||||
|
|
||||||
|
func (nums uint8s) MarshalLogArray(arr zapcore.ArrayEncoder) error {
|
||||||
|
for i := range nums {
|
||||||
|
arr.AppendUint8(nums[i])
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type uintptrs []uintptr
|
||||||
|
|
||||||
|
func (nums uintptrs) MarshalLogArray(arr zapcore.ArrayEncoder) error {
|
||||||
|
for i := range nums {
|
||||||
|
arr.AppendUintptr(nums[i])
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,107 @@
|
||||||
|
// Copyright (c) 2016 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
package zap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func BenchmarkBoolsArrayMarshaler(b *testing.B) {
|
||||||
|
// Keep this benchmark here to capture the overhead of the ArrayMarshaler
|
||||||
|
// wrapper.
|
||||||
|
bs := make([]bool, 50)
|
||||||
|
enc := zapcore.NewJSONEncoder(zapcore.EncoderConfig{})
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
Bools("array", bs).AddTo(enc.Clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkBoolsReflect(b *testing.B) {
|
||||||
|
bs := make([]bool, 50)
|
||||||
|
enc := zapcore.NewJSONEncoder(zapcore.EncoderConfig{})
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
Reflect("array", bs).AddTo(enc.Clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArrayWrappers(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
desc string
|
||||||
|
field zapcore.Field
|
||||||
|
expected []interface{}
|
||||||
|
}{
|
||||||
|
{"empty bools", Bools("", []bool{}), []interface{}(nil)},
|
||||||
|
{"empty byte strings", ByteStrings("", [][]byte{}), []interface{}(nil)},
|
||||||
|
{"empty complex128s", Complex128s("", []complex128{}), []interface{}(nil)},
|
||||||
|
{"empty complex64s", Complex64s("", []complex64{}), []interface{}(nil)},
|
||||||
|
{"empty durations", Durations("", []time.Duration{}), []interface{}(nil)},
|
||||||
|
{"empty float64s", Float64s("", []float64{}), []interface{}(nil)},
|
||||||
|
{"empty float32s", Float32s("", []float32{}), []interface{}(nil)},
|
||||||
|
{"empty ints", Ints("", []int{}), []interface{}(nil)},
|
||||||
|
{"empty int64s", Int64s("", []int64{}), []interface{}(nil)},
|
||||||
|
{"empty int32s", Int32s("", []int32{}), []interface{}(nil)},
|
||||||
|
{"empty int16s", Int16s("", []int16{}), []interface{}(nil)},
|
||||||
|
{"empty int8s", Int8s("", []int8{}), []interface{}(nil)},
|
||||||
|
{"empty strings", Strings("", []string{}), []interface{}(nil)},
|
||||||
|
{"empty times", Times("", []time.Time{}), []interface{}(nil)},
|
||||||
|
{"empty uints", Uints("", []uint{}), []interface{}(nil)},
|
||||||
|
{"empty uint64s", Uint64s("", []uint64{}), []interface{}(nil)},
|
||||||
|
{"empty uint32s", Uint32s("", []uint32{}), []interface{}(nil)},
|
||||||
|
{"empty uint16s", Uint16s("", []uint16{}), []interface{}(nil)},
|
||||||
|
{"empty uint8s", Uint8s("", []uint8{}), []interface{}(nil)},
|
||||||
|
{"empty uintptrs", Uintptrs("", []uintptr{}), []interface{}(nil)},
|
||||||
|
{"bools", Bools("", []bool{true, false}), []interface{}{true, false}},
|
||||||
|
{"byte strings", ByteStrings("", [][]byte{{1, 2}, {3, 4}}), []interface{}{[]byte{1, 2}, []byte{3, 4}}},
|
||||||
|
{"complex128s", Complex128s("", []complex128{1 + 2i, 3 + 4i}), []interface{}{1 + 2i, 3 + 4i}},
|
||||||
|
{"complex64s", Complex64s("", []complex64{1 + 2i, 3 + 4i}), []interface{}{complex64(1 + 2i), complex64(3 + 4i)}},
|
||||||
|
{"durations", Durations("", []time.Duration{1, 2}), []interface{}{time.Nanosecond, 2 * time.Nanosecond}},
|
||||||
|
{"float64s", Float64s("", []float64{1.2, 3.4}), []interface{}{1.2, 3.4}},
|
||||||
|
{"float32s", Float32s("", []float32{1.2, 3.4}), []interface{}{float32(1.2), float32(3.4)}},
|
||||||
|
{"ints", Ints("", []int{1, 2}), []interface{}{1, 2}},
|
||||||
|
{"int64s", Int64s("", []int64{1, 2}), []interface{}{int64(1), int64(2)}},
|
||||||
|
{"int32s", Int32s("", []int32{1, 2}), []interface{}{int32(1), int32(2)}},
|
||||||
|
{"int16s", Int16s("", []int16{1, 2}), []interface{}{int16(1), int16(2)}},
|
||||||
|
{"int8s", Int8s("", []int8{1, 2}), []interface{}{int8(1), int8(2)}},
|
||||||
|
{"strings", Strings("", []string{"foo", "bar"}), []interface{}{"foo", "bar"}},
|
||||||
|
{"times", Times("", []time.Time{time.Unix(0, 0), time.Unix(0, 0)}), []interface{}{time.Unix(0, 0), time.Unix(0, 0)}},
|
||||||
|
{"uints", Uints("", []uint{1, 2}), []interface{}{uint(1), uint(2)}},
|
||||||
|
{"uint64s", Uint64s("", []uint64{1, 2}), []interface{}{uint64(1), uint64(2)}},
|
||||||
|
{"uint32s", Uint32s("", []uint32{1, 2}), []interface{}{uint32(1), uint32(2)}},
|
||||||
|
{"uint16s", Uint16s("", []uint16{1, 2}), []interface{}{uint16(1), uint16(2)}},
|
||||||
|
{"uint8s", Uint8s("", []uint8{1, 2}), []interface{}{uint8(1), uint8(2)}},
|
||||||
|
{"uintptrs", Uintptrs("", []uintptr{1, 2}), []interface{}{uintptr(1), uintptr(2)}},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
enc := zapcore.NewMapObjectEncoder()
|
||||||
|
tt.field.Key = "k"
|
||||||
|
tt.field.AddTo(enc)
|
||||||
|
assert.Equal(t, tt.expected, enc.Fields["k"], "%s: unexpected map contents.", tt.desc)
|
||||||
|
assert.Equal(t, 1, len(enc.Fields), "%s: found extra keys in map: %v", tt.desc, enc.Fields)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
// Copyright (c) 2016 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
package benchmarks
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
|
||||||
|
"github.com/apex/log"
|
||||||
|
"github.com/apex/log/handlers/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newDisabledApexLog() *log.Logger {
|
||||||
|
return &log.Logger{
|
||||||
|
Handler: json.New(ioutil.Discard),
|
||||||
|
Level: log.ErrorLevel,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newApexLog() *log.Logger {
|
||||||
|
return &log.Logger{
|
||||||
|
Handler: json.New(ioutil.Discard),
|
||||||
|
Level: log.DebugLevel,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fakeApexFields() log.Fields {
|
||||||
|
return log.Fields{
|
||||||
|
"int": _tenInts[0],
|
||||||
|
"ints": _tenInts,
|
||||||
|
"string": _tenStrings[0],
|
||||||
|
"strings": _tenStrings,
|
||||||
|
"time": _tenTimes[0],
|
||||||
|
"times": _tenTimes,
|
||||||
|
"user1": _oneUser,
|
||||||
|
"user2": _oneUser,
|
||||||
|
"users": _tenUsers,
|
||||||
|
"error": errExample,
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
// Copyright (c) 2016 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
// Package benchmarks contains only benchmarks comparing zap to other
|
||||||
|
// structured logging libraries.
|
||||||
|
package benchmarks
|
|
@ -0,0 +1,31 @@
|
||||||
|
// Copyright (c) 2016 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
package benchmarks
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
|
||||||
|
"github.com/go-kit/kit/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newKitLog(fields ...interface{}) log.Logger {
|
||||||
|
return log.With(log.NewJSONLogger(ioutil.Discard), fields...)
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
// Copyright (c) 2016 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
package benchmarks
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
|
||||||
|
"go.pedge.io/lion"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newLion() lion.Logger {
|
||||||
|
return lion.NewLogger(lion.NewJSONWritePusher(ioutil.Discard))
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
// Copyright (c) 2016 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
package benchmarks
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
|
||||||
|
"gopkg.in/inconshreveable/log15.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newLog15() log15.Logger {
|
||||||
|
logger := log15.New()
|
||||||
|
logger.SetHandler(log15.StreamHandler(ioutil.Discard, log15.JsonFormat()))
|
||||||
|
return logger
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
// Copyright (c) 2016 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
package benchmarks
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newDisabledLogrus() *logrus.Logger {
|
||||||
|
logger := newLogrus()
|
||||||
|
logger.Level = logrus.ErrorLevel
|
||||||
|
return logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func newLogrus() *logrus.Logger {
|
||||||
|
return &logrus.Logger{
|
||||||
|
Out: ioutil.Discard,
|
||||||
|
Formatter: new(logrus.JSONFormatter),
|
||||||
|
Hooks: make(logrus.LevelHooks),
|
||||||
|
Level: logrus.DebugLevel,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fakeLogrusFields() logrus.Fields {
|
||||||
|
return logrus.Fields{
|
||||||
|
"int": _tenInts[0],
|
||||||
|
"ints": _tenInts,
|
||||||
|
"string": _tenStrings[0],
|
||||||
|
"strings": _tenStrings,
|
||||||
|
"time": _tenTimes[0],
|
||||||
|
"times": _tenTimes,
|
||||||
|
"user1": _oneUser,
|
||||||
|
"user2": _oneUser,
|
||||||
|
"users": _tenUsers,
|
||||||
|
"error": errExample,
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,614 @@
|
||||||
|
// Copyright (c) 2016 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
package benchmarks
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
func BenchmarkDisabledWithoutFields(b *testing.B) {
|
||||||
|
b.Logf("Logging at a disabled level without any structured context.")
|
||||||
|
b.Run("Zap", func(b *testing.B) {
|
||||||
|
logger := newZapLogger(zap.ErrorLevel)
|
||||||
|
b.ResetTimer()
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
logger.Info(getMessage(0))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
b.Run("Zap.Check", func(b *testing.B) {
|
||||||
|
logger := newZapLogger(zap.ErrorLevel)
|
||||||
|
b.ResetTimer()
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
if m := logger.Check(zap.InfoLevel, getMessage(0)); m != nil {
|
||||||
|
m.Write()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
b.Run("Zap.Sugar", func(b *testing.B) {
|
||||||
|
logger := newZapLogger(zap.ErrorLevel).Sugar()
|
||||||
|
b.ResetTimer()
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
logger.Info(getMessage(0))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
b.Run("Zap.SugarFormatting", func(b *testing.B) {
|
||||||
|
logger := newZapLogger(zap.ErrorLevel).Sugar()
|
||||||
|
b.ResetTimer()
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
logger.Infof("%v %v %v %s %v %v %v %v %v %s\n", fakeFmtArgs()...)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
b.Run("apex/log", func(b *testing.B) {
|
||||||
|
logger := newDisabledApexLog()
|
||||||
|
b.ResetTimer()
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
logger.Info(getMessage(0))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
b.Run("sirupsen/logrus", func(b *testing.B) {
|
||||||
|
logger := newDisabledLogrus()
|
||||||
|
b.ResetTimer()
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
logger.Info(getMessage(0))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
b.Run("rs/zerolog", func(b *testing.B) {
|
||||||
|
logger := newDisabledZerolog()
|
||||||
|
b.ResetTimer()
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
logger.Info().Msg(getMessage(0))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkDisabledAccumulatedContext(b *testing.B) {
|
||||||
|
b.Logf("Logging at a disabled level with some accumulated context.")
|
||||||
|
b.Run("Zap", func(b *testing.B) {
|
||||||
|
logger := newZapLogger(zap.ErrorLevel).With(fakeFields()...)
|
||||||
|
b.ResetTimer()
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
logger.Info(getMessage(0))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
b.Run("Zap.Check", func(b *testing.B) {
|
||||||
|
logger := newZapLogger(zap.ErrorLevel).With(fakeFields()...)
|
||||||
|
b.ResetTimer()
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
if m := logger.Check(zap.InfoLevel, getMessage(0)); m != nil {
|
||||||
|
m.Write()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
b.Run("Zap.Sugar", func(b *testing.B) {
|
||||||
|
logger := newZapLogger(zap.ErrorLevel).With(fakeFields()...).Sugar()
|
||||||
|
b.ResetTimer()
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
logger.Info(getMessage(0))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
b.Run("Zap.SugarFormatting", func(b *testing.B) {
|
||||||
|
logger := newZapLogger(zap.ErrorLevel).With(fakeFields()...).Sugar()
|
||||||
|
b.ResetTimer()
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
logger.Infof("%v %v %v %s %v %v %v %v %v %s\n", fakeFmtArgs()...)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
b.Run("apex/log", func(b *testing.B) {
|
||||||
|
logger := newDisabledApexLog().WithFields(fakeApexFields())
|
||||||
|
b.ResetTimer()
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
logger.Info(getMessage(0))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
b.Run("sirupsen/logrus", func(b *testing.B) {
|
||||||
|
logger := newDisabledLogrus().WithFields(fakeLogrusFields())
|
||||||
|
b.ResetTimer()
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
logger.Info(getMessage(0))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
b.Run("rs/zerolog", func(b *testing.B) {
|
||||||
|
logger := fakeZerologContext(newDisabledZerolog().With()).Logger()
|
||||||
|
b.ResetTimer()
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
logger.Info().Msg(getMessage(0))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkDisabledAddingFields(b *testing.B) {
|
||||||
|
b.Logf("Logging at a disabled level, adding context at each log site.")
|
||||||
|
b.Run("Zap", func(b *testing.B) {
|
||||||
|
logger := newZapLogger(zap.ErrorLevel)
|
||||||
|
b.ResetTimer()
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
logger.Info(getMessage(0), fakeFields()...)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
b.Run("Zap.Check", func(b *testing.B) {
|
||||||
|
logger := newZapLogger(zap.ErrorLevel)
|
||||||
|
b.ResetTimer()
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
if m := logger.Check(zap.InfoLevel, getMessage(0)); m != nil {
|
||||||
|
m.Write(fakeFields()...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
b.Run("Zap.Sugar", func(b *testing.B) {
|
||||||
|
logger := newZapLogger(zap.ErrorLevel).Sugar()
|
||||||
|
b.ResetTimer()
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
logger.Infow(getMessage(0), fakeSugarFields()...)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
b.Run("apex/log", func(b *testing.B) {
|
||||||
|
logger := newDisabledApexLog()
|
||||||
|
b.ResetTimer()
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
logger.WithFields(fakeApexFields()).Info(getMessage(0))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
b.Run("sirupsen/logrus", func(b *testing.B) {
|
||||||
|
logger := newDisabledLogrus()
|
||||||
|
b.ResetTimer()
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
logger.WithFields(fakeLogrusFields()).Info(getMessage(0))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
b.Run("rs/zerolog", func(b *testing.B) {
|
||||||
|
logger := newDisabledZerolog()
|
||||||
|
b.ResetTimer()
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
fakeZerologFields(logger.Info()).Msg(getMessage(0))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkWithoutFields(b *testing.B) {
|
||||||
|
b.Logf("Logging without any structured context.")
|
||||||
|
b.Run("Zap", func(b *testing.B) {
|
||||||
|
logger := newZapLogger(zap.DebugLevel)
|
||||||
|
b.ResetTimer()
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
logger.Info(getMessage(0))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
b.Run("Zap.Check", func(b *testing.B) {
|
||||||
|
logger := newZapLogger(zap.DebugLevel)
|
||||||
|
b.ResetTimer()
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
if ce := logger.Check(zap.InfoLevel, getMessage(0)); ce != nil {
|
||||||
|
ce.Write()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
b.Run("Zap.CheckSampled", func(b *testing.B) {
|
||||||
|
logger := newSampledLogger(zap.DebugLevel)
|
||||||
|
b.ResetTimer()
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
i := 0
|
||||||
|
for pb.Next() {
|
||||||
|
i++
|
||||||
|
if ce := logger.Check(zap.InfoLevel, getMessage(i)); ce != nil {
|
||||||
|
ce.Write()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
b.Run("Zap.Sugar", func(b *testing.B) {
|
||||||
|
logger := newZapLogger(zap.DebugLevel).Sugar()
|
||||||
|
b.ResetTimer()
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
logger.Info(getMessage(0))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
b.Run("Zap.SugarFormatting", func(b *testing.B) {
|
||||||
|
logger := newZapLogger(zap.DebugLevel).Sugar()
|
||||||
|
b.ResetTimer()
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
logger.Infof("%v %v %v %s %v %v %v %v %v %s\n", fakeFmtArgs()...)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
b.Run("apex/log", func(b *testing.B) {
|
||||||
|
logger := newApexLog()
|
||||||
|
b.ResetTimer()
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
logger.Info(getMessage(0))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
b.Run("go-kit/kit/log", func(b *testing.B) {
|
||||||
|
logger := newKitLog()
|
||||||
|
b.ResetTimer()
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
logger.Log(getMessage(0), getMessage(1))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
b.Run("inconshreveable/log15", func(b *testing.B) {
|
||||||
|
logger := newLog15()
|
||||||
|
b.ResetTimer()
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
logger.Info(getMessage(0))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
b.Run("sirupsen/logrus", func(b *testing.B) {
|
||||||
|
logger := newLogrus()
|
||||||
|
b.ResetTimer()
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
logger.Info(getMessage(0))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
b.Run("go.pedge.io/lion", func(b *testing.B) {
|
||||||
|
logger := newLion()
|
||||||
|
b.ResetTimer()
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
logger.Printf(getMessage(0))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
b.Run("stdlib.Println", func(b *testing.B) {
|
||||||
|
logger := log.New(ioutil.Discard, "", log.LstdFlags)
|
||||||
|
b.ResetTimer()
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
logger.Println(getMessage(0))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
b.Run("stdlib.Printf", func(b *testing.B) {
|
||||||
|
logger := log.New(ioutil.Discard, "", log.LstdFlags)
|
||||||
|
b.ResetTimer()
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
logger.Printf("%v %v %v %s %v %v %v %v %v %s\n", fakeFmtArgs()...)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
b.Run("rs/zerolog", func(b *testing.B) {
|
||||||
|
logger := newZerolog()
|
||||||
|
b.ResetTimer()
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
logger.Info().Msg(getMessage(0))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
b.Run("rs/zerolog.Formatting", func(b *testing.B) {
|
||||||
|
logger := newZerolog()
|
||||||
|
b.ResetTimer()
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
logger.Info().Msgf("%v %v %v %s %v %v %v %v %v %s\n", fakeFmtArgs()...)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
b.Run("rs/zerolog.Check", func(b *testing.B) {
|
||||||
|
logger := newZerolog()
|
||||||
|
b.ResetTimer()
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
if e := logger.Info(); e.Enabled() {
|
||||||
|
e.Msg(getMessage(0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkAccumulatedContext(b *testing.B) {
|
||||||
|
b.Logf("Logging with some accumulated context.")
|
||||||
|
b.Run("Zap", func(b *testing.B) {
|
||||||
|
logger := newZapLogger(zap.DebugLevel).With(fakeFields()...)
|
||||||
|
b.ResetTimer()
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
logger.Info(getMessage(0))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
b.Run("Zap.Check", func(b *testing.B) {
|
||||||
|
logger := newZapLogger(zap.DebugLevel).With(fakeFields()...)
|
||||||
|
b.ResetTimer()
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
if ce := logger.Check(zap.InfoLevel, getMessage(0)); ce != nil {
|
||||||
|
ce.Write()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
b.Run("Zap.CheckSampled", func(b *testing.B) {
|
||||||
|
logger := newSampledLogger(zap.DebugLevel).With(fakeFields()...)
|
||||||
|
b.ResetTimer()
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
i := 0
|
||||||
|
for pb.Next() {
|
||||||
|
i++
|
||||||
|
if ce := logger.Check(zap.InfoLevel, getMessage(i)); ce != nil {
|
||||||
|
ce.Write()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
b.Run("Zap.Sugar", func(b *testing.B) {
|
||||||
|
logger := newZapLogger(zap.DebugLevel).With(fakeFields()...).Sugar()
|
||||||
|
b.ResetTimer()
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
logger.Info(getMessage(0))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
b.Run("Zap.SugarFormatting", func(b *testing.B) {
|
||||||
|
logger := newZapLogger(zap.DebugLevel).With(fakeFields()...).Sugar()
|
||||||
|
b.ResetTimer()
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
logger.Infof("%v %v %v %s %v %v %v %v %v %s\n", fakeFmtArgs()...)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
b.Run("apex/log", func(b *testing.B) {
|
||||||
|
logger := newApexLog().WithFields(fakeApexFields())
|
||||||
|
b.ResetTimer()
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
logger.Info(getMessage(0))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
b.Run("go-kit/kit/log", func(b *testing.B) {
|
||||||
|
logger := newKitLog(fakeSugarFields()...)
|
||||||
|
b.ResetTimer()
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
logger.Log(getMessage(0), getMessage(1))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
b.Run("inconshreveable/log15", func(b *testing.B) {
|
||||||
|
logger := newLog15().New(fakeSugarFields())
|
||||||
|
b.ResetTimer()
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
logger.Info(getMessage(0))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
b.Run("sirupsen/logrus", func(b *testing.B) {
|
||||||
|
logger := newLogrus().WithFields(fakeLogrusFields())
|
||||||
|
b.ResetTimer()
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
logger.Info(getMessage(0))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
b.Run("go.pedge.io/lion", func(b *testing.B) {
|
||||||
|
logger := newLion().WithFields(fakeLogrusFields())
|
||||||
|
b.ResetTimer()
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
logger.Infof(getMessage(0))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
b.Run("rs/zerolog", func(b *testing.B) {
|
||||||
|
logger := fakeZerologContext(newZerolog().With()).Logger()
|
||||||
|
b.ResetTimer()
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
logger.Info().Msg(getMessage(0))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
b.Run("rs/zerolog.Check", func(b *testing.B) {
|
||||||
|
logger := fakeZerologContext(newZerolog().With()).Logger()
|
||||||
|
b.ResetTimer()
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
if e := logger.Info(); e.Enabled() {
|
||||||
|
e.Msg(getMessage(0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
b.Run("rs/zerolog.Formatting", func(b *testing.B) {
|
||||||
|
logger := fakeZerologContext(newZerolog().With()).Logger()
|
||||||
|
b.ResetTimer()
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
logger.Info().Msgf("%v %v %v %s %v %v %v %v %v %s\n", fakeFmtArgs()...)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkAddingFields(b *testing.B) {
|
||||||
|
b.Logf("Logging with additional context at each log site.")
|
||||||
|
b.Run("Zap", func(b *testing.B) {
|
||||||
|
logger := newZapLogger(zap.DebugLevel)
|
||||||
|
b.ResetTimer()
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
logger.Info(getMessage(0), fakeFields()...)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
b.Run("Zap.Check", func(b *testing.B) {
|
||||||
|
logger := newZapLogger(zap.DebugLevel)
|
||||||
|
b.ResetTimer()
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
if ce := logger.Check(zap.InfoLevel, getMessage(0)); ce != nil {
|
||||||
|
ce.Write(fakeFields()...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
b.Run("Zap.CheckSampled", func(b *testing.B) {
|
||||||
|
logger := newSampledLogger(zap.DebugLevel)
|
||||||
|
b.ResetTimer()
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
i := 0
|
||||||
|
for pb.Next() {
|
||||||
|
i++
|
||||||
|
if ce := logger.Check(zap.InfoLevel, getMessage(i)); ce != nil {
|
||||||
|
ce.Write(fakeFields()...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
b.Run("Zap.Sugar", func(b *testing.B) {
|
||||||
|
logger := newZapLogger(zap.DebugLevel).Sugar()
|
||||||
|
b.ResetTimer()
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
logger.Infow(getMessage(0), fakeSugarFields()...)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
b.Run("apex/log", func(b *testing.B) {
|
||||||
|
logger := newApexLog()
|
||||||
|
b.ResetTimer()
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
logger.WithFields(fakeApexFields()).Info(getMessage(0))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
b.Run("go-kit/kit/log", func(b *testing.B) {
|
||||||
|
logger := newKitLog()
|
||||||
|
b.ResetTimer()
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
logger.Log(fakeSugarFields()...)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
b.Run("inconshreveable/log15", func(b *testing.B) {
|
||||||
|
logger := newLog15()
|
||||||
|
b.ResetTimer()
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
logger.Info(getMessage(0), fakeSugarFields()...)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
b.Run("sirupsen/logrus", func(b *testing.B) {
|
||||||
|
logger := newLogrus()
|
||||||
|
b.ResetTimer()
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
logger.WithFields(fakeLogrusFields()).Info(getMessage(0))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
b.Run("go.pedge.io/lion", func(b *testing.B) {
|
||||||
|
logger := newLion()
|
||||||
|
b.ResetTimer()
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
logger.WithFields(fakeLogrusFields()).Infof(getMessage(0))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
b.Run("rs/zerolog", func(b *testing.B) {
|
||||||
|
logger := newZerolog()
|
||||||
|
b.ResetTimer()
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
fakeZerologFields(logger.Info()).Msg(getMessage(0))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
b.Run("rs/zerolog.Check", func(b *testing.B) {
|
||||||
|
logger := newZerolog()
|
||||||
|
b.ResetTimer()
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
if e := logger.Info(); e.Enabled() {
|
||||||
|
fakeZerologFields(e).Msg(getMessage(0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,172 @@
|
||||||
|
// Copyright (c) 2016 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
package benchmarks
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"go.uber.org/multierr"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
|
"go.uber.org/zap/zaptest"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errExample = errors.New("fail")
|
||||||
|
|
||||||
|
_messages = fakeMessages(1000)
|
||||||
|
_tenInts = []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}
|
||||||
|
_tenStrings = []string{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j"}
|
||||||
|
_tenTimes = []time.Time{
|
||||||
|
time.Unix(0, 0),
|
||||||
|
time.Unix(1, 0),
|
||||||
|
time.Unix(2, 0),
|
||||||
|
time.Unix(3, 0),
|
||||||
|
time.Unix(4, 0),
|
||||||
|
time.Unix(5, 0),
|
||||||
|
time.Unix(6, 0),
|
||||||
|
time.Unix(7, 0),
|
||||||
|
time.Unix(8, 0),
|
||||||
|
time.Unix(9, 0),
|
||||||
|
}
|
||||||
|
_oneUser = &user{
|
||||||
|
Name: "Jane Doe",
|
||||||
|
Email: "jane@test.com",
|
||||||
|
CreatedAt: time.Date(1980, 1, 1, 12, 0, 0, 0, time.UTC),
|
||||||
|
}
|
||||||
|
_tenUsers = users{
|
||||||
|
_oneUser,
|
||||||
|
_oneUser,
|
||||||
|
_oneUser,
|
||||||
|
_oneUser,
|
||||||
|
_oneUser,
|
||||||
|
_oneUser,
|
||||||
|
_oneUser,
|
||||||
|
_oneUser,
|
||||||
|
_oneUser,
|
||||||
|
_oneUser,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func fakeMessages(n int) []string {
|
||||||
|
messages := make([]string, n)
|
||||||
|
for i := range messages {
|
||||||
|
messages[i] = fmt.Sprintf("Test logging, but use a somewhat realistic message length. (#%v)", i)
|
||||||
|
}
|
||||||
|
return messages
|
||||||
|
}
|
||||||
|
|
||||||
|
func getMessage(iter int) string {
|
||||||
|
return _messages[iter%1000]
|
||||||
|
}
|
||||||
|
|
||||||
|
type users []*user
|
||||||
|
|
||||||
|
func (uu users) MarshalLogArray(arr zapcore.ArrayEncoder) error {
|
||||||
|
var err error
|
||||||
|
for i := range uu {
|
||||||
|
err = multierr.Append(err, arr.AppendObject(uu[i]))
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
type user struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *user) MarshalLogObject(enc zapcore.ObjectEncoder) error {
|
||||||
|
enc.AddString("name", u.Name)
|
||||||
|
enc.AddString("email", u.Email)
|
||||||
|
enc.AddInt64("createdAt", u.CreatedAt.UnixNano())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newZapLogger(lvl zapcore.Level) *zap.Logger {
|
||||||
|
ec := zap.NewProductionEncoderConfig()
|
||||||
|
ec.EncodeDuration = zapcore.NanosDurationEncoder
|
||||||
|
ec.EncodeTime = zapcore.EpochNanosTimeEncoder
|
||||||
|
enc := zapcore.NewJSONEncoder(ec)
|
||||||
|
return zap.New(zapcore.NewCore(
|
||||||
|
enc,
|
||||||
|
&zaptest.Discarder{},
|
||||||
|
lvl,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
func newSampledLogger(lvl zapcore.Level) *zap.Logger {
|
||||||
|
return zap.New(zapcore.NewSampler(
|
||||||
|
newZapLogger(zap.DebugLevel).Core(),
|
||||||
|
100*time.Millisecond,
|
||||||
|
10, // first
|
||||||
|
10, // thereafter
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
func fakeFields() []zapcore.Field {
|
||||||
|
return []zapcore.Field{
|
||||||
|
zap.Int("int", _tenInts[0]),
|
||||||
|
zap.Ints("ints", _tenInts),
|
||||||
|
zap.String("string", _tenStrings[0]),
|
||||||
|
zap.Strings("strings", _tenStrings),
|
||||||
|
zap.Time("time", _tenTimes[0]),
|
||||||
|
zap.Times("times", _tenTimes),
|
||||||
|
zap.Object("user1", _oneUser),
|
||||||
|
zap.Object("user2", _oneUser),
|
||||||
|
zap.Array("users", _tenUsers),
|
||||||
|
zap.Error(errExample),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fakeSugarFields() []interface{} {
|
||||||
|
return []interface{}{
|
||||||
|
"int", _tenInts[0],
|
||||||
|
"ints", _tenInts,
|
||||||
|
"string", _tenStrings[0],
|
||||||
|
"strings", _tenStrings,
|
||||||
|
"time", _tenTimes[0],
|
||||||
|
"times", _tenTimes,
|
||||||
|
"user1", _oneUser,
|
||||||
|
"user2", _oneUser,
|
||||||
|
"users", _tenUsers,
|
||||||
|
"error", errExample,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fakeFmtArgs() []interface{} {
|
||||||
|
// Need to keep this a function instead of a package-global var so that we
|
||||||
|
// pay the cast-to-interface{} penalty on each call.
|
||||||
|
return []interface{}{
|
||||||
|
_tenInts[0],
|
||||||
|
_tenInts,
|
||||||
|
_tenStrings[0],
|
||||||
|
_tenStrings,
|
||||||
|
_tenTimes[0],
|
||||||
|
_tenTimes,
|
||||||
|
_oneUser,
|
||||||
|
_oneUser,
|
||||||
|
_tenUsers,
|
||||||
|
errExample,
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
// Copyright (c) 2016 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
package benchmarks
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newZerolog() zerolog.Logger {
|
||||||
|
return zerolog.New(ioutil.Discard).With().Timestamp().Logger()
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDisabledZerolog() zerolog.Logger {
|
||||||
|
return newZerolog().Level(zerolog.Disabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
func fakeZerologFields(e *zerolog.Event) *zerolog.Event {
|
||||||
|
return e.
|
||||||
|
Int("int", _tenInts[0]).
|
||||||
|
Interface("ints", _tenInts).
|
||||||
|
Str("string", _tenStrings[0]).
|
||||||
|
Interface("strings", _tenStrings).
|
||||||
|
Time("time", _tenTimes[0]).
|
||||||
|
Interface("times", _tenTimes).
|
||||||
|
Interface("user1", _oneUser).
|
||||||
|
Interface("user2", _oneUser).
|
||||||
|
Interface("users", _tenUsers).
|
||||||
|
Err(errExample)
|
||||||
|
}
|
||||||
|
|
||||||
|
func fakeZerologContext(c zerolog.Context) zerolog.Context {
|
||||||
|
return c.
|
||||||
|
Int("int", _tenInts[0]).
|
||||||
|
Interface("ints", _tenInts).
|
||||||
|
Str("string", _tenStrings[0]).
|
||||||
|
Interface("strings", _tenStrings).
|
||||||
|
Time("time", _tenTimes[0]).
|
||||||
|
Interface("times", _tenTimes).
|
||||||
|
Interface("user1", _oneUser).
|
||||||
|
Interface("user2", _oneUser).
|
||||||
|
Interface("users", _tenUsers).
|
||||||
|
Err(errExample)
|
||||||
|
}
|
|
@ -0,0 +1,106 @@
|
||||||
|
// Copyright (c) 2016 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
// Package buffer provides a thin wrapper around a byte slice. Unlike the
|
||||||
|
// standard library's bytes.Buffer, it supports a portion of the strconv
|
||||||
|
// package's zero-allocation formatters.
|
||||||
|
package buffer
|
||||||
|
|
||||||
|
import "strconv"
|
||||||
|
|
||||||
|
const _size = 1024 // by default, create 1 KiB buffers
|
||||||
|
|
||||||
|
// Buffer is a thin wrapper around a byte slice. It's intended to be pooled, so
|
||||||
|
// the only way to construct one is via a Pool.
|
||||||
|
type Buffer struct {
|
||||||
|
bs []byte
|
||||||
|
pool Pool
|
||||||
|
}
|
||||||
|
|
||||||
|
// AppendByte writes a single byte to the Buffer.
|
||||||
|
func (b *Buffer) AppendByte(v byte) {
|
||||||
|
b.bs = append(b.bs, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AppendString writes a string to the Buffer.
|
||||||
|
func (b *Buffer) AppendString(s string) {
|
||||||
|
b.bs = append(b.bs, s...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AppendInt appends an integer to the underlying buffer (assuming base 10).
|
||||||
|
func (b *Buffer) AppendInt(i int64) {
|
||||||
|
b.bs = strconv.AppendInt(b.bs, i, 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AppendUint appends an unsigned integer to the underlying buffer (assuming
|
||||||
|
// base 10).
|
||||||
|
func (b *Buffer) AppendUint(i uint64) {
|
||||||
|
b.bs = strconv.AppendUint(b.bs, i, 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AppendBool appends a bool to the underlying buffer.
|
||||||
|
func (b *Buffer) AppendBool(v bool) {
|
||||||
|
b.bs = strconv.AppendBool(b.bs, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AppendFloat appends a float to the underlying buffer. It doesn't quote NaN
|
||||||
|
// or +/- Inf.
|
||||||
|
func (b *Buffer) AppendFloat(f float64, bitSize int) {
|
||||||
|
b.bs = strconv.AppendFloat(b.bs, f, 'f', -1, bitSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len returns the length of the underlying byte slice.
|
||||||
|
func (b *Buffer) Len() int {
|
||||||
|
return len(b.bs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cap returns the capacity of the underlying byte slice.
|
||||||
|
func (b *Buffer) Cap() int {
|
||||||
|
return cap(b.bs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bytes returns a mutable reference to the underlying byte slice.
|
||||||
|
func (b *Buffer) Bytes() []byte {
|
||||||
|
return b.bs
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a string copy of the underlying byte slice.
|
||||||
|
func (b *Buffer) String() string {
|
||||||
|
return string(b.bs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset resets the underlying byte slice. Subsequent writes re-use the slice's
|
||||||
|
// backing array.
|
||||||
|
func (b *Buffer) Reset() {
|
||||||
|
b.bs = b.bs[:0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write implements io.Writer.
|
||||||
|
func (b *Buffer) Write(bs []byte) (int, error) {
|
||||||
|
b.bs = append(b.bs, bs...)
|
||||||
|
return len(bs), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Free returns the Buffer to its Pool.
|
||||||
|
//
|
||||||
|
// Callers must not retain references to the Buffer after calling Free.
|
||||||
|
func (b *Buffer) Free() {
|
||||||
|
b.pool.put(b)
|
||||||
|
}
|
|
@ -0,0 +1,91 @@
|
||||||
|
// Copyright (c) 2016 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
package buffer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBufferWrites(t *testing.T) {
|
||||||
|
buf := NewPool().Get()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
desc string
|
||||||
|
f func()
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{"AppendByte", func() { buf.AppendByte('v') }, "v"},
|
||||||
|
{"AppendString", func() { buf.AppendString("foo") }, "foo"},
|
||||||
|
{"AppendIntPositive", func() { buf.AppendInt(42) }, "42"},
|
||||||
|
{"AppendIntNegative", func() { buf.AppendInt(-42) }, "-42"},
|
||||||
|
{"AppendUint", func() { buf.AppendUint(42) }, "42"},
|
||||||
|
{"AppendBool", func() { buf.AppendBool(true) }, "true"},
|
||||||
|
{"AppendFloat64", func() { buf.AppendFloat(3.14, 64) }, "3.14"},
|
||||||
|
// Intenationally introduce some floating-point error.
|
||||||
|
{"AppendFloat32", func() { buf.AppendFloat(float64(float32(3.14)), 32) }, "3.14"},
|
||||||
|
{"AppendWrite", func() { buf.Write([]byte("foo")) }, "foo"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.desc, func(t *testing.T) {
|
||||||
|
buf.Reset()
|
||||||
|
tt.f()
|
||||||
|
assert.Equal(t, tt.want, buf.String(), "Unexpected buffer.String().")
|
||||||
|
assert.Equal(t, tt.want, string(buf.Bytes()), "Unexpected string(buffer.Bytes()).")
|
||||||
|
assert.Equal(t, len(tt.want), buf.Len(), "Unexpected buffer length.")
|
||||||
|
// We're not writing more than a kibibyte in tests.
|
||||||
|
assert.Equal(t, _size, buf.Cap(), "Expected buffer capacity to remain constant.")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkBuffers(b *testing.B) {
|
||||||
|
// Because we use the strconv.AppendFoo functions so liberally, we can't
|
||||||
|
// use the standard library's bytes.Buffer anyways (without incurring a
|
||||||
|
// bunch of extra allocations). Nevertheless, let's make sure that we're
|
||||||
|
// not losing any precious nanoseconds.
|
||||||
|
str := strings.Repeat("a", 1024)
|
||||||
|
slice := make([]byte, 1024)
|
||||||
|
buf := bytes.NewBuffer(slice)
|
||||||
|
custom := NewPool().Get()
|
||||||
|
b.Run("ByteSlice", func(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
slice = append(slice, str...)
|
||||||
|
slice = slice[:0]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
b.Run("BytesBuffer", func(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
buf.WriteString(str)
|
||||||
|
buf.Reset()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
b.Run("CustomBuffer", func(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
custom.AppendString(str)
|
||||||
|
custom.Reset()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
// Copyright (c) 2016 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
package buffer
|
||||||
|
|
||||||
|
import "sync"
|
||||||
|
|
||||||
|
// A Pool is a type-safe wrapper around a sync.Pool.
|
||||||
|
type Pool struct {
|
||||||
|
p *sync.Pool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPool constructs a new Pool.
|
||||||
|
func NewPool() Pool {
|
||||||
|
return Pool{p: &sync.Pool{
|
||||||
|
New: func() interface{} {
|
||||||
|
return &Buffer{bs: make([]byte, 0, _size)}
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get retrieves a Buffer from the pool, creating one if necessary.
|
||||||
|
func (p Pool) Get() *Buffer {
|
||||||
|
buf := p.p.Get().(*Buffer)
|
||||||
|
buf.Reset()
|
||||||
|
buf.pool = p
|
||||||
|
return buf
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Pool) put(buf *Buffer) {
|
||||||
|
p.p.Put(buf)
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
// Copyright (c) 2016 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
package buffer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBuffers(t *testing.T) {
|
||||||
|
const dummyData = "dummy data"
|
||||||
|
p := NewPool()
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
for g := 0; g < 10; g++ {
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
for i := 0; i < 100; i++ {
|
||||||
|
buf := p.Get()
|
||||||
|
assert.Zero(t, buf.Len(), "Expected truncated buffer")
|
||||||
|
assert.NotZero(t, buf.Cap(), "Expected non-zero capacity")
|
||||||
|
|
||||||
|
buf.AppendString(dummyData)
|
||||||
|
assert.Equal(t, buf.Len(), len(dummyData), "Expected buffer to contain dummy data")
|
||||||
|
|
||||||
|
buf.Free()
|
||||||
|
}
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
#!/bin/bash -e
|
||||||
|
|
||||||
|
ERROR_COUNT=0
|
||||||
|
while read -r file
|
||||||
|
do
|
||||||
|
case "$(head -1 "${file}")" in
|
||||||
|
*"Copyright (c) "*" Uber Technologies, Inc.")
|
||||||
|
# everything's cool
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "$file is missing license header."
|
||||||
|
(( ERROR_COUNT++ ))
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done < <(git ls-files "*\.go")
|
||||||
|
|
||||||
|
exit $ERROR_COUNT
|
|
@ -0,0 +1,57 @@
|
||||||
|
// Copyright (c) 2016 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
package zap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
|
"go.uber.org/zap/zaptest/observer"
|
||||||
|
)
|
||||||
|
|
||||||
|
func opts(opts ...Option) []Option {
|
||||||
|
return opts
|
||||||
|
}
|
||||||
|
|
||||||
|
// Here specifically to introduce an easily-identifiable filename for testing
|
||||||
|
// stacktraces and caller skips.
|
||||||
|
func withLogger(t testing.TB, e zapcore.LevelEnabler, opts []Option, f func(*Logger, *observer.ObservedLogs)) {
|
||||||
|
fac, logs := observer.New(e)
|
||||||
|
log := New(fac, opts...)
|
||||||
|
f(log, logs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func withSugar(t testing.TB, e zapcore.LevelEnabler, opts []Option, f func(*SugaredLogger, *observer.ObservedLogs)) {
|
||||||
|
withLogger(t, e, opts, func(logger *Logger, logs *observer.ObservedLogs) { f(logger.Sugar(), logs) })
|
||||||
|
}
|
||||||
|
|
||||||
|
func runConcurrently(goroutines, iterations int, wg *sync.WaitGroup, f func()) {
|
||||||
|
wg.Add(goroutines)
|
||||||
|
for g := 0; g < goroutines; g++ {
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
for i := 0; i < iterations; i++ {
|
||||||
|
f()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,243 @@
|
||||||
|
// Copyright (c) 2016 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
package zap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sort"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SamplingConfig sets a sampling strategy for the logger. Sampling caps the
|
||||||
|
// global CPU and I/O load that logging puts on your process while attempting
|
||||||
|
// to preserve a representative subset of your logs.
|
||||||
|
//
|
||||||
|
// Values configured here are per-second. See zapcore.NewSampler for details.
|
||||||
|
type SamplingConfig struct {
|
||||||
|
Initial int `json:"initial" yaml:"initial"`
|
||||||
|
Thereafter int `json:"thereafter" yaml:"thereafter"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Config offers a declarative way to construct a logger. It doesn't do
|
||||||
|
// anything that can't be done with New, Options, and the various
|
||||||
|
// zapcore.WriteSyncer and zapcore.Core wrappers, but it's a simpler way to
|
||||||
|
// toggle common options.
|
||||||
|
//
|
||||||
|
// Note that Config intentionally supports only the most common options. More
|
||||||
|
// unusual logging setups (logging to network connections or message queues,
|
||||||
|
// splitting output between multiple files, etc.) are possible, but require
|
||||||
|
// direct use of the zapcore package. For sample code, see the package-level
|
||||||
|
// BasicConfiguration and AdvancedConfiguration examples.
|
||||||
|
//
|
||||||
|
// For an example showing runtime log level changes, see the documentation for
|
||||||
|
// AtomicLevel.
|
||||||
|
type Config struct {
|
||||||
|
// Level is the minimum enabled logging level. Note that this is a dynamic
|
||||||
|
// level, so calling Config.Level.SetLevel will atomically change the log
|
||||||
|
// level of all loggers descended from this config.
|
||||||
|
Level AtomicLevel `json:"level" yaml:"level"`
|
||||||
|
// Development puts the logger in development mode, which changes the
|
||||||
|
// behavior of DPanicLevel and takes stacktraces more liberally.
|
||||||
|
Development bool `json:"development" yaml:"development"`
|
||||||
|
// DisableCaller stops annotating logs with the calling function's file
|
||||||
|
// name and line number. By default, all logs are annotated.
|
||||||
|
DisableCaller bool `json:"disableCaller" yaml:"disableCaller"`
|
||||||
|
// DisableStacktrace completely disables automatic stacktrace capturing. By
|
||||||
|
// default, stacktraces are captured for WarnLevel and above logs in
|
||||||
|
// development and ErrorLevel and above in production.
|
||||||
|
DisableStacktrace bool `json:"disableStacktrace" yaml:"disableStacktrace"`
|
||||||
|
// Sampling sets a sampling policy. A nil SamplingConfig disables sampling.
|
||||||
|
Sampling *SamplingConfig `json:"sampling" yaml:"sampling"`
|
||||||
|
// Encoding sets the logger's encoding. Valid values are "json" and
|
||||||
|
// "console", as well as any third-party encodings registered via
|
||||||
|
// RegisterEncoder.
|
||||||
|
Encoding string `json:"encoding" yaml:"encoding"`
|
||||||
|
// EncoderConfig sets options for the chosen encoder. See
|
||||||
|
// zapcore.EncoderConfig for details.
|
||||||
|
EncoderConfig zapcore.EncoderConfig `json:"encoderConfig" yaml:"encoderConfig"`
|
||||||
|
// OutputPaths is a list of paths to write logging output to. See Open for
|
||||||
|
// details.
|
||||||
|
OutputPaths []string `json:"outputPaths" yaml:"outputPaths"`
|
||||||
|
// ErrorOutputPaths is a list of paths to write internal logger errors to.
|
||||||
|
// The default is standard error.
|
||||||
|
//
|
||||||
|
// Note that this setting only affects internal errors; for sample code that
|
||||||
|
// sends error-level logs to a different location from info- and debug-level
|
||||||
|
// logs, see the package-level AdvancedConfiguration example.
|
||||||
|
ErrorOutputPaths []string `json:"errorOutputPaths" yaml:"errorOutputPaths"`
|
||||||
|
// InitialFields is a collection of fields to add to the root logger.
|
||||||
|
InitialFields map[string]interface{} `json:"initialFields" yaml:"initialFields"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewProductionEncoderConfig returns an opinionated EncoderConfig for
|
||||||
|
// production environments.
|
||||||
|
func NewProductionEncoderConfig() zapcore.EncoderConfig {
|
||||||
|
return zapcore.EncoderConfig{
|
||||||
|
TimeKey: "ts",
|
||||||
|
LevelKey: "level",
|
||||||
|
NameKey: "logger",
|
||||||
|
CallerKey: "caller",
|
||||||
|
MessageKey: "msg",
|
||||||
|
StacktraceKey: "stacktrace",
|
||||||
|
LineEnding: zapcore.DefaultLineEnding,
|
||||||
|
EncodeLevel: zapcore.LowercaseLevelEncoder,
|
||||||
|
EncodeTime: zapcore.EpochTimeEncoder,
|
||||||
|
EncodeDuration: zapcore.SecondsDurationEncoder,
|
||||||
|
EncodeCaller: zapcore.ShortCallerEncoder,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewProductionConfig is a reasonable production logging configuration.
|
||||||
|
// Logging is enabled at InfoLevel and above.
|
||||||
|
//
|
||||||
|
// It uses a JSON encoder, writes to standard error, and enables sampling.
|
||||||
|
// Stacktraces are automatically included on logs of ErrorLevel and above.
|
||||||
|
func NewProductionConfig() Config {
|
||||||
|
return Config{
|
||||||
|
Level: NewAtomicLevelAt(InfoLevel),
|
||||||
|
Development: false,
|
||||||
|
Sampling: &SamplingConfig{
|
||||||
|
Initial: 100,
|
||||||
|
Thereafter: 100,
|
||||||
|
},
|
||||||
|
Encoding: "json",
|
||||||
|
EncoderConfig: NewProductionEncoderConfig(),
|
||||||
|
OutputPaths: []string{"stderr"},
|
||||||
|
ErrorOutputPaths: []string{"stderr"},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDevelopmentEncoderConfig returns an opinionated EncoderConfig for
|
||||||
|
// development environments.
|
||||||
|
func NewDevelopmentEncoderConfig() zapcore.EncoderConfig {
|
||||||
|
return zapcore.EncoderConfig{
|
||||||
|
// Keys can be anything except the empty string.
|
||||||
|
TimeKey: "T",
|
||||||
|
LevelKey: "L",
|
||||||
|
NameKey: "N",
|
||||||
|
CallerKey: "C",
|
||||||
|
MessageKey: "M",
|
||||||
|
StacktraceKey: "S",
|
||||||
|
LineEnding: zapcore.DefaultLineEnding,
|
||||||
|
EncodeLevel: zapcore.CapitalLevelEncoder,
|
||||||
|
EncodeTime: zapcore.ISO8601TimeEncoder,
|
||||||
|
EncodeDuration: zapcore.StringDurationEncoder,
|
||||||
|
EncodeCaller: zapcore.ShortCallerEncoder,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDevelopmentConfig is a reasonable development logging configuration.
|
||||||
|
// Logging is enabled at DebugLevel and above.
|
||||||
|
//
|
||||||
|
// It enables development mode (which makes DPanicLevel logs panic), uses a
|
||||||
|
// console encoder, writes to standard error, and disables sampling.
|
||||||
|
// Stacktraces are automatically included on logs of WarnLevel and above.
|
||||||
|
func NewDevelopmentConfig() Config {
|
||||||
|
return Config{
|
||||||
|
Level: NewAtomicLevelAt(DebugLevel),
|
||||||
|
Development: true,
|
||||||
|
Encoding: "console",
|
||||||
|
EncoderConfig: NewDevelopmentEncoderConfig(),
|
||||||
|
OutputPaths: []string{"stderr"},
|
||||||
|
ErrorOutputPaths: []string{"stderr"},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build constructs a logger from the Config and Options.
|
||||||
|
func (cfg Config) Build(opts ...Option) (*Logger, error) {
|
||||||
|
enc, err := cfg.buildEncoder()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
sink, errSink, err := cfg.openSinks()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
log := New(
|
||||||
|
zapcore.NewCore(enc, sink, cfg.Level),
|
||||||
|
cfg.buildOptions(errSink)...,
|
||||||
|
)
|
||||||
|
if len(opts) > 0 {
|
||||||
|
log = log.WithOptions(opts...)
|
||||||
|
}
|
||||||
|
return log, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg Config) buildOptions(errSink zapcore.WriteSyncer) []Option {
|
||||||
|
opts := []Option{ErrorOutput(errSink)}
|
||||||
|
|
||||||
|
if cfg.Development {
|
||||||
|
opts = append(opts, Development())
|
||||||
|
}
|
||||||
|
|
||||||
|
if !cfg.DisableCaller {
|
||||||
|
opts = append(opts, AddCaller())
|
||||||
|
}
|
||||||
|
|
||||||
|
stackLevel := ErrorLevel
|
||||||
|
if cfg.Development {
|
||||||
|
stackLevel = WarnLevel
|
||||||
|
}
|
||||||
|
if !cfg.DisableStacktrace {
|
||||||
|
opts = append(opts, AddStacktrace(stackLevel))
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.Sampling != nil {
|
||||||
|
opts = append(opts, WrapCore(func(core zapcore.Core) zapcore.Core {
|
||||||
|
return zapcore.NewSampler(core, time.Second, int(cfg.Sampling.Initial), int(cfg.Sampling.Thereafter))
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(cfg.InitialFields) > 0 {
|
||||||
|
fs := make([]zapcore.Field, 0, len(cfg.InitialFields))
|
||||||
|
keys := make([]string, 0, len(cfg.InitialFields))
|
||||||
|
for k := range cfg.InitialFields {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
for _, k := range keys {
|
||||||
|
fs = append(fs, Any(k, cfg.InitialFields[k]))
|
||||||
|
}
|
||||||
|
opts = append(opts, Fields(fs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
return opts
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg Config) openSinks() (zapcore.WriteSyncer, zapcore.WriteSyncer, error) {
|
||||||
|
sink, closeOut, err := Open(cfg.OutputPaths...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
errSink, _, err := Open(cfg.ErrorOutputPaths...)
|
||||||
|
if err != nil {
|
||||||
|
closeOut()
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return sink, errSink, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg Config) buildEncoder() (zapcore.Encoder, error) {
|
||||||
|
return newEncoder(cfg.Encoding, cfg.EncoderConfig)
|
||||||
|
}
|
|
@ -0,0 +1,108 @@
|
||||||
|
// Copyright (c) 2016 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
package zap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestConfig(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
desc string
|
||||||
|
cfg Config
|
||||||
|
expectN int64
|
||||||
|
expectRe string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
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":"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\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+`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.desc, func(t *testing.T) {
|
||||||
|
temp, err := ioutil.TempFile("", "zap-prod-config-test")
|
||||||
|
require.NoError(t, err, "Failed to create temp file.")
|
||||||
|
defer os.Remove(temp.Name())
|
||||||
|
|
||||||
|
tt.cfg.OutputPaths = []string{temp.Name()}
|
||||||
|
tt.cfg.EncoderConfig.TimeKey = "" // no timestamps in tests
|
||||||
|
tt.cfg.InitialFields = map[string]interface{}{"z": "zz", "k": "v"}
|
||||||
|
|
||||||
|
hook, count := makeCountingHook()
|
||||||
|
logger, err := tt.cfg.Build(Hooks(hook))
|
||||||
|
require.NoError(t, err, "Unexpected error constructing logger.")
|
||||||
|
|
||||||
|
logger.Debug("debug")
|
||||||
|
logger.Info("info")
|
||||||
|
logger.Warn("warn")
|
||||||
|
|
||||||
|
byteContents, err := ioutil.ReadAll(temp)
|
||||||
|
require.NoError(t, err, "Couldn't read log contents from temp file.")
|
||||||
|
logs := string(byteContents)
|
||||||
|
assert.Regexp(t, tt.expectRe, logs, "Unexpected log output.")
|
||||||
|
|
||||||
|
for i := 0; i < 200; i++ {
|
||||||
|
logger.Info("sampling")
|
||||||
|
}
|
||||||
|
assert.Equal(t, tt.expectN, count.Load(), "Hook called an unexpected number of times.")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfigWithInvalidPaths(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
desc string
|
||||||
|
output string
|
||||||
|
errOutput string
|
||||||
|
}{
|
||||||
|
{"output directory doesn't exist", "/tmp/not-there/foo.log", "stderr"},
|
||||||
|
{"error output directory doesn't exist", "stdout", "/tmp/not-there/foo-errors.log"},
|
||||||
|
{"neither output directory exists", "/tmp/not-there/foo.log", "/tmp/not-there/foo-errors.log"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.desc, func(t *testing.T) {
|
||||||
|
cfg := NewProductionConfig()
|
||||||
|
cfg.OutputPaths = []string{tt.output}
|
||||||
|
cfg.ErrorOutputPaths = []string{tt.errOutput}
|
||||||
|
_, err := cfg.Build()
|
||||||
|
assert.Error(t, err, "Expected an error opening a non-existent directory.")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,113 @@
|
||||||
|
// Copyright (c) 2016 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
// Package zap provides fast, structured, leveled logging.
|
||||||
|
//
|
||||||
|
// For applications that log in the hot path, reflection-based serialization
|
||||||
|
// and string formatting are prohibitively expensive - they're CPU-intensive
|
||||||
|
// and make many small allocations. Put differently, using json.Marshal and
|
||||||
|
// fmt.Fprintf to log tons of interface{} makes your application slow.
|
||||||
|
//
|
||||||
|
// Zap takes a different approach. It includes a reflection-free,
|
||||||
|
// zero-allocation JSON encoder, and the base Logger strives to avoid
|
||||||
|
// serialization overhead and allocations wherever possible. By building the
|
||||||
|
// high-level SugaredLogger on that foundation, zap lets users choose when
|
||||||
|
// they need to count every allocation and when they'd prefer a more familiar,
|
||||||
|
// loosely typed API.
|
||||||
|
//
|
||||||
|
// Choosing a Logger
|
||||||
|
//
|
||||||
|
// In contexts where performance is nice, but not critical, use the
|
||||||
|
// SugaredLogger. It's 4-10x faster than other structured logging packages and
|
||||||
|
// supports both structured and printf-style logging. Like log15 and go-kit,
|
||||||
|
// the SugaredLogger's structured logging APIs are loosely typed and accept a
|
||||||
|
// variadic number of key-value pairs. (For more advanced use cases, they also
|
||||||
|
// accept strongly typed fields - see the SugaredLogger.With documentation for
|
||||||
|
// details.)
|
||||||
|
// sugar := zap.NewExample().Sugar()
|
||||||
|
// defer sugar.Sync()
|
||||||
|
// sugar.Infow("failed to fetch URL",
|
||||||
|
// "url", "http://example.com",
|
||||||
|
// "attempt", 3,
|
||||||
|
// "backoff", time.Second,
|
||||||
|
// )
|
||||||
|
// sugar.Printf("failed to fetch URL: %s", "http://example.com")
|
||||||
|
//
|
||||||
|
// By default, loggers are unbuffered. However, since zap's low-level APIs
|
||||||
|
// allow buffering, calling Sync before letting your process exit is a good
|
||||||
|
// habit.
|
||||||
|
//
|
||||||
|
// In the rare contexts where every microsecond and every allocation matter,
|
||||||
|
// use the Logger. It's even faster than the SugaredLogger and allocates far
|
||||||
|
// less, but it only supports strongly-typed, structured logging.
|
||||||
|
// logger := zap.NewExample()
|
||||||
|
// defer logger.Sync()
|
||||||
|
// logger.Info("failed to fetch URL",
|
||||||
|
// zap.String("url", "http://example.com"),
|
||||||
|
// zap.Int("attempt", 3),
|
||||||
|
// zap.Duration("backoff", time.Second),
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// Choosing between the Logger and SugaredLogger doesn't need to be an
|
||||||
|
// application-wide decision: converting between the two is simple and
|
||||||
|
// inexpensive.
|
||||||
|
// logger := zap.NewExample()
|
||||||
|
// defer logger.Sync()
|
||||||
|
// sugar := logger.Sugar()
|
||||||
|
// plain := sugar.Desugar()
|
||||||
|
//
|
||||||
|
// Configuring Zap
|
||||||
|
//
|
||||||
|
// The simplest way to build a Logger is to use zap's opinionated presets:
|
||||||
|
// NewExample, NewProduction, and NewDevelopment. These presets build a logger
|
||||||
|
// with a single function call:
|
||||||
|
// logger, err := zap.NewProduction()
|
||||||
|
// if err != nil {
|
||||||
|
// log.Fatalf("can't initialize zap logger: %v", err)
|
||||||
|
// }
|
||||||
|
// defer logger.Sync()
|
||||||
|
//
|
||||||
|
// Presets are fine for small projects, but larger projects and organizations
|
||||||
|
// naturally require a bit more customization. For most users, zap's Config
|
||||||
|
// struct strikes the right balance between flexibility and convenience. See
|
||||||
|
// the package-level BasicConfiguration example for sample code.
|
||||||
|
//
|
||||||
|
// More unusual configurations (splitting output between files, sending logs
|
||||||
|
// to a message queue, etc.) are possible, but require direct use of
|
||||||
|
// go.uber.org/zap/zapcore. See the package-level AdvancedConfiguration
|
||||||
|
// example for sample code.
|
||||||
|
//
|
||||||
|
// Extending Zap
|
||||||
|
//
|
||||||
|
// The zap package itself is a relatively thin wrapper around the interfaces
|
||||||
|
// in go.uber.org/zap/zapcore. Extending zap to support a new encoding (e.g.,
|
||||||
|
// BSON), a new log sink (e.g., Kafka), or something more exotic (perhaps an
|
||||||
|
// exception aggregation service, like Sentry or Rollbar) typically requires
|
||||||
|
// implementing the zapcore.Encoder, zapcore.WriteSyncer, or zapcore.Core
|
||||||
|
// interfaces. See the zapcore documentation for details.
|
||||||
|
//
|
||||||
|
// Similarly, package authors can use the high-performance Encoder and Core
|
||||||
|
// implementations in the zapcore package to build their own loggers.
|
||||||
|
//
|
||||||
|
// Frequently Asked Questions
|
||||||
|
//
|
||||||
|
// An FAQ covering everything from installation errors to design decisions is
|
||||||
|
// available at https://github.com/uber-go/zap/blob/master/FAQ.md.
|
||||||
|
package zap // import "go.uber.org/zap"
|
|
@ -0,0 +1,75 @@
|
||||||
|
// Copyright (c) 2016 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
package zap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errNoEncoderNameSpecified = errors.New("no encoder name specified")
|
||||||
|
|
||||||
|
_encoderNameToConstructor = map[string]func(zapcore.EncoderConfig) (zapcore.Encoder, error){
|
||||||
|
"console": func(encoderConfig zapcore.EncoderConfig) (zapcore.Encoder, error) {
|
||||||
|
return zapcore.NewConsoleEncoder(encoderConfig), nil
|
||||||
|
},
|
||||||
|
"json": func(encoderConfig zapcore.EncoderConfig) (zapcore.Encoder, error) {
|
||||||
|
return zapcore.NewJSONEncoder(encoderConfig), nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
_encoderMutex sync.RWMutex
|
||||||
|
)
|
||||||
|
|
||||||
|
// RegisterEncoder registers an encoder constructor, which the Config struct
|
||||||
|
// can then reference. By default, the "json" and "console" encoders are
|
||||||
|
// registered.
|
||||||
|
//
|
||||||
|
// Attempting to register an encoder whose name is already taken returns an
|
||||||
|
// error.
|
||||||
|
func RegisterEncoder(name string, constructor func(zapcore.EncoderConfig) (zapcore.Encoder, error)) error {
|
||||||
|
_encoderMutex.Lock()
|
||||||
|
defer _encoderMutex.Unlock()
|
||||||
|
if name == "" {
|
||||||
|
return errNoEncoderNameSpecified
|
||||||
|
}
|
||||||
|
if _, ok := _encoderNameToConstructor[name]; ok {
|
||||||
|
return fmt.Errorf("encoder already registered for name %q", name)
|
||||||
|
}
|
||||||
|
_encoderNameToConstructor[name] = constructor
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newEncoder(name string, encoderConfig zapcore.EncoderConfig) (zapcore.Encoder, error) {
|
||||||
|
_encoderMutex.RLock()
|
||||||
|
defer _encoderMutex.RUnlock()
|
||||||
|
if name == "" {
|
||||||
|
return nil, errNoEncoderNameSpecified
|
||||||
|
}
|
||||||
|
constructor, ok := _encoderNameToConstructor[name]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("no encoder registered for name %q", name)
|
||||||
|
}
|
||||||
|
return constructor(encoderConfig)
|
||||||
|
}
|
|
@ -0,0 +1,88 @@
|
||||||
|
// Copyright (c) 2016 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
package zap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRegisterDefaultEncoders(t *testing.T) {
|
||||||
|
testEncodersRegistered(t, "console", "json")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRegisterEncoder(t *testing.T) {
|
||||||
|
testEncoders(func() {
|
||||||
|
assert.NoError(t, RegisterEncoder("foo", newNilEncoder), "expected to be able to register the encoder foo")
|
||||||
|
testEncodersRegistered(t, "foo")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDuplicateRegisterEncoder(t *testing.T) {
|
||||||
|
testEncoders(func() {
|
||||||
|
RegisterEncoder("foo", newNilEncoder)
|
||||||
|
assert.Error(t, RegisterEncoder("foo", newNilEncoder), "expected an error when registering an encoder with the same name twice")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRegisterEncoderNoName(t *testing.T) {
|
||||||
|
assert.Equal(t, errNoEncoderNameSpecified, RegisterEncoder("", newNilEncoder), "expected an error when registering an encoder with no name")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewEncoder(t *testing.T) {
|
||||||
|
testEncoders(func() {
|
||||||
|
RegisterEncoder("foo", newNilEncoder)
|
||||||
|
encoder, err := newEncoder("foo", zapcore.EncoderConfig{})
|
||||||
|
assert.NoError(t, err, "could not create an encoder for the registered name foo")
|
||||||
|
assert.Nil(t, encoder, "the encoder from newNilEncoder is not nil")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewEncoderNotRegistered(t *testing.T) {
|
||||||
|
_, err := newEncoder("foo", zapcore.EncoderConfig{})
|
||||||
|
assert.Error(t, err, "expected an error when trying to create an encoder of an unregistered name")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewEncoderNoName(t *testing.T) {
|
||||||
|
_, err := newEncoder("", zapcore.EncoderConfig{})
|
||||||
|
assert.Equal(t, errNoEncoderNameSpecified, err, "expected an error when creating an encoder with no name")
|
||||||
|
}
|
||||||
|
|
||||||
|
func testEncoders(f func()) {
|
||||||
|
existing := _encoderNameToConstructor
|
||||||
|
_encoderNameToConstructor = make(map[string]func(zapcore.EncoderConfig) (zapcore.Encoder, error))
|
||||||
|
defer func() { _encoderNameToConstructor = existing }()
|
||||||
|
f()
|
||||||
|
}
|
||||||
|
|
||||||
|
func testEncodersRegistered(t *testing.T, names ...string) {
|
||||||
|
assert.Len(t, _encoderNameToConstructor, len(names), "the expected number of registered encoders does not match the actual number")
|
||||||
|
for _, name := range names {
|
||||||
|
assert.NotNil(t, _encoderNameToConstructor[name], "no encoder is registered for name %s", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newNilEncoder(_ zapcore.EncoderConfig) (zapcore.Encoder, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
|
@ -0,0 +1,80 @@
|
||||||
|
// Copyright (c) 2017 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
package zap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _errArrayElemPool = sync.Pool{New: func() interface{} {
|
||||||
|
return &errArrayElem{}
|
||||||
|
}}
|
||||||
|
|
||||||
|
// Error is shorthand for the common idiom NamedError("error", err).
|
||||||
|
func Error(err error) zapcore.Field {
|
||||||
|
return NamedError("error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NamedError constructs a field that lazily stores err.Error() under the
|
||||||
|
// provided key. Errors which also implement fmt.Formatter (like those produced
|
||||||
|
// by github.com/pkg/errors) will also have their verbose representation stored
|
||||||
|
// under key+"Verbose". If passed a nil error, the field is a no-op.
|
||||||
|
//
|
||||||
|
// For the common case in which the key is simply "error", the Error function
|
||||||
|
// is shorter and less repetitive.
|
||||||
|
func NamedError(key string, err error) zapcore.Field {
|
||||||
|
if err == nil {
|
||||||
|
return Skip()
|
||||||
|
}
|
||||||
|
return zapcore.Field{Key: key, Type: zapcore.ErrorType, Interface: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
type errArray []error
|
||||||
|
|
||||||
|
func (errs errArray) MarshalLogArray(arr zapcore.ArrayEncoder) error {
|
||||||
|
for i := range errs {
|
||||||
|
if errs[i] == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// To represent each error as an object with an "error" attribute and
|
||||||
|
// potentially an "errorVerbose" attribute, we need to wrap it in a
|
||||||
|
// type that implements LogObjectMarshaler. To prevent this from
|
||||||
|
// allocating, pool the wrapper type.
|
||||||
|
elem := _errArrayElemPool.Get().(*errArrayElem)
|
||||||
|
elem.error = errs[i]
|
||||||
|
arr.AppendObject(elem)
|
||||||
|
elem.error = nil
|
||||||
|
_errArrayElemPool.Put(elem)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type errArrayElem struct {
|
||||||
|
error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *errArrayElem) MarshalLogObject(enc zapcore.ObjectEncoder) error {
|
||||||
|
// Re-use the error field's logic, which supports non-standard error types.
|
||||||
|
Error(e.error).AddTo(enc)
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,99 @@
|
||||||
|
// Copyright (c) 2017 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
package zap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
|
|
||||||
|
richErrors "github.com/pkg/errors"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestErrorConstructors(t *testing.T) {
|
||||||
|
fail := errors.New("fail")
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
field zapcore.Field
|
||||||
|
expect zapcore.Field
|
||||||
|
}{
|
||||||
|
{"Error", Skip(), Error(nil)},
|
||||||
|
{"Error", zapcore.Field{Key: "error", Type: zapcore.ErrorType, Interface: fail}, Error(fail)},
|
||||||
|
{"NamedError", Skip(), NamedError("foo", nil)},
|
||||||
|
{"NamedError", zapcore.Field{Key: "foo", Type: zapcore.ErrorType, Interface: fail}, NamedError("foo", fail)},
|
||||||
|
{"Any:Error", Any("k", errors.New("v")), NamedError("k", errors.New("v"))},
|
||||||
|
{"Any:Errors", Any("k", []error{errors.New("v")}), Errors("k", []error{errors.New("v")})},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
if !assert.Equal(t, tt.expect, tt.field, "Unexpected output from convenience field constructor %s.", tt.name) {
|
||||||
|
t.Logf("type expected: %T\nGot: %T", tt.expect.Interface, tt.field.Interface)
|
||||||
|
}
|
||||||
|
assertCanBeReused(t, tt.field)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestErrorArrayConstructor(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
desc string
|
||||||
|
field zapcore.Field
|
||||||
|
expected []interface{}
|
||||||
|
}{
|
||||||
|
{"empty errors", Errors("", []error{}), []interface{}(nil)},
|
||||||
|
{
|
||||||
|
"errors",
|
||||||
|
Errors("", []error{nil, errors.New("foo"), nil, errors.New("bar")}),
|
||||||
|
[]interface{}{map[string]interface{}{"error": "foo"}, map[string]interface{}{"error": "bar"}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
enc := zapcore.NewMapObjectEncoder()
|
||||||
|
tt.field.Key = "k"
|
||||||
|
tt.field.AddTo(enc)
|
||||||
|
assert.Equal(t, tt.expected, enc.Fields["k"], "%s: unexpected map contents.", tt.desc)
|
||||||
|
assert.Equal(t, 1, len(enc.Fields), "%s: found extra keys in map: %v", tt.desc, enc.Fields)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestErrorsArraysHandleRichErrors(t *testing.T) {
|
||||||
|
errs := []error{richErrors.New("egad")}
|
||||||
|
|
||||||
|
enc := zapcore.NewMapObjectEncoder()
|
||||||
|
Errors("k", errs).AddTo(enc)
|
||||||
|
assert.Equal(t, 1, len(enc.Fields), "Expected only top-level field.")
|
||||||
|
|
||||||
|
val := enc.Fields["k"]
|
||||||
|
arr, ok := val.([]interface{})
|
||||||
|
require.True(t, ok, "Expected top-level field to be an array.")
|
||||||
|
require.Equal(t, 1, len(arr), "Expected only one error object in array.")
|
||||||
|
|
||||||
|
serialized := arr[0]
|
||||||
|
errMap, ok := serialized.(map[string]interface{})
|
||||||
|
require.True(t, ok, "Expected serialized error to be a map, got %T.", serialized)
|
||||||
|
assert.Equal(t, "egad", errMap["error"], "Unexpected standard error string.")
|
||||||
|
assert.Contains(t, errMap["errorVerbose"], "egad", "Verbose error string should be a superset of standard error.")
|
||||||
|
assert.Contains(t, errMap["errorVerbose"], "TestErrorsArraysHandleRichErrors", "Verbose error string should contain a stacktrace.")
|
||||||
|
}
|
|
@ -0,0 +1,327 @@
|
||||||
|
// Copyright (c) 2016 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
package zap_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Example_presets() {
|
||||||
|
// Using zap's preset constructors is the simplest way to get a feel for the
|
||||||
|
// package, but they don't allow much customization.
|
||||||
|
logger := zap.NewExample() // or NewProduction, or NewDevelopment
|
||||||
|
defer logger.Sync()
|
||||||
|
|
||||||
|
const url = "http://example.com"
|
||||||
|
|
||||||
|
// In most circumstances, use the SugaredLogger. It's 4-10x faster than most
|
||||||
|
// other structured logging packages and has a familiar, loosely-typed API.
|
||||||
|
sugar := logger.Sugar()
|
||||||
|
sugar.Infow("Failed to fetch URL.",
|
||||||
|
// Structured context as loosely typed key-value pairs.
|
||||||
|
"url", url,
|
||||||
|
"attempt", 3,
|
||||||
|
"backoff", time.Second,
|
||||||
|
)
|
||||||
|
sugar.Infof("Failed to fetch URL: %s", url)
|
||||||
|
|
||||||
|
// In the unusual situations where every microsecond matters, use the
|
||||||
|
// Logger. It's even faster than the SugaredLogger, but only supports
|
||||||
|
// structured logging.
|
||||||
|
logger.Info("Failed to fetch URL.",
|
||||||
|
// Structured context as strongly typed fields.
|
||||||
|
zap.String("url", url),
|
||||||
|
zap.Int("attempt", 3),
|
||||||
|
zap.Duration("backoff", time.Second),
|
||||||
|
)
|
||||||
|
// Output:
|
||||||
|
// {"level":"info","msg":"Failed to fetch URL.","url":"http://example.com","attempt":3,"backoff":"1s"}
|
||||||
|
// {"level":"info","msg":"Failed to fetch URL: http://example.com"}
|
||||||
|
// {"level":"info","msg":"Failed to fetch URL.","url":"http://example.com","attempt":3,"backoff":"1s"}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Example_basicConfiguration() {
|
||||||
|
// For some users, the presets offered by the NewProduction, NewDevelopment,
|
||||||
|
// and NewExample constructors won't be appropriate. For most of those
|
||||||
|
// users, the bundled Config struct offers the right balance of flexibility
|
||||||
|
// and convenience. (For more complex needs, see the AdvancedConfiguration
|
||||||
|
// example.)
|
||||||
|
//
|
||||||
|
// See the documentation for Config and zapcore.EncoderConfig for all the
|
||||||
|
// available options.
|
||||||
|
rawJSON := []byte(`{
|
||||||
|
"level": "debug",
|
||||||
|
"encoding": "json",
|
||||||
|
"outputPaths": ["stdout", "/tmp/logs"],
|
||||||
|
"errorOutputPaths": ["stderr"],
|
||||||
|
"initialFields": {"foo": "bar"},
|
||||||
|
"encoderConfig": {
|
||||||
|
"messageKey": "message",
|
||||||
|
"levelKey": "level",
|
||||||
|
"levelEncoder": "lowercase"
|
||||||
|
}
|
||||||
|
}`)
|
||||||
|
|
||||||
|
var cfg zap.Config
|
||||||
|
if err := json.Unmarshal(rawJSON, &cfg); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
logger, err := cfg.Build()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer logger.Sync()
|
||||||
|
|
||||||
|
logger.Info("logger construction succeeded")
|
||||||
|
// Output:
|
||||||
|
// {"level":"info","message":"logger construction succeeded","foo":"bar"}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Example_advancedConfiguration() {
|
||||||
|
// The bundled Config struct only supports the most common configuration
|
||||||
|
// options. More complex needs, like splitting logs between multiple files
|
||||||
|
// or writing to non-file outputs, require use of the zapcore package.
|
||||||
|
//
|
||||||
|
// In this example, imagine we're both sending our logs to Kafka and writing
|
||||||
|
// them to the console. We'd like to encode the console output and the Kafka
|
||||||
|
// topics differently, and we'd also like special treatment for
|
||||||
|
// high-priority logs.
|
||||||
|
|
||||||
|
// First, define our level-handling logic.
|
||||||
|
highPriority := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
|
||||||
|
return lvl >= zapcore.ErrorLevel
|
||||||
|
})
|
||||||
|
lowPriority := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
|
||||||
|
return lvl < zapcore.ErrorLevel
|
||||||
|
})
|
||||||
|
|
||||||
|
// Assume that we have clients for two Kafka topics. The clients implement
|
||||||
|
// zapcore.WriteSyncer and are safe for concurrent use. (If they only
|
||||||
|
// implement io.Writer, we can use zapcore.AddSync to add a no-op Sync
|
||||||
|
// method. If they're not safe for concurrent use, we can add a protecting
|
||||||
|
// mutex with zapcore.Lock.)
|
||||||
|
topicDebugging := zapcore.AddSync(ioutil.Discard)
|
||||||
|
topicErrors := zapcore.AddSync(ioutil.Discard)
|
||||||
|
|
||||||
|
// High-priority output should also go to standard error, and low-priority
|
||||||
|
// output should also go to standard out.
|
||||||
|
consoleDebugging := zapcore.Lock(os.Stdout)
|
||||||
|
consoleErrors := zapcore.Lock(os.Stderr)
|
||||||
|
|
||||||
|
// Optimize the Kafka output for machine consumption and the console output
|
||||||
|
// for human operators.
|
||||||
|
kafkaEncoder := zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())
|
||||||
|
consoleEncoder := zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig())
|
||||||
|
|
||||||
|
// Join the outputs, encoders, and level-handling functions into
|
||||||
|
// zapcore.Cores, then tee the four cores together.
|
||||||
|
core := zapcore.NewTee(
|
||||||
|
zapcore.NewCore(kafkaEncoder, topicErrors, highPriority),
|
||||||
|
zapcore.NewCore(consoleEncoder, consoleErrors, highPriority),
|
||||||
|
zapcore.NewCore(kafkaEncoder, topicDebugging, lowPriority),
|
||||||
|
zapcore.NewCore(consoleEncoder, consoleDebugging, lowPriority),
|
||||||
|
)
|
||||||
|
|
||||||
|
// From a zapcore.Core, it's easy to construct a Logger.
|
||||||
|
logger := zap.New(core)
|
||||||
|
defer logger.Sync()
|
||||||
|
logger.Info("constructed a logger")
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleNamespace() {
|
||||||
|
logger := zap.NewExample()
|
||||||
|
defer logger.Sync()
|
||||||
|
|
||||||
|
logger.With(
|
||||||
|
zap.Namespace("metrics"),
|
||||||
|
zap.Int("counter", 1),
|
||||||
|
).Info("tracked some metrics")
|
||||||
|
// Output:
|
||||||
|
// {"level":"info","msg":"tracked some metrics","metrics":{"counter":1}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleNewStdLog() {
|
||||||
|
logger := zap.NewExample()
|
||||||
|
defer logger.Sync()
|
||||||
|
|
||||||
|
std := zap.NewStdLog(logger)
|
||||||
|
std.Print("standard logger wrapper")
|
||||||
|
// Output:
|
||||||
|
// {"level":"info","msg":"standard logger wrapper"}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleRedirectStdLog() {
|
||||||
|
logger := zap.NewExample()
|
||||||
|
defer logger.Sync()
|
||||||
|
|
||||||
|
undo := zap.RedirectStdLog(logger)
|
||||||
|
defer undo()
|
||||||
|
|
||||||
|
log.Print("redirected standard library")
|
||||||
|
// Output:
|
||||||
|
// {"level":"info","msg":"redirected standard library"}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleReplaceGlobals() {
|
||||||
|
logger := zap.NewExample()
|
||||||
|
defer logger.Sync()
|
||||||
|
|
||||||
|
undo := zap.ReplaceGlobals(logger)
|
||||||
|
defer undo()
|
||||||
|
|
||||||
|
zap.L().Info("replaced zap's global loggers")
|
||||||
|
// Output:
|
||||||
|
// {"level":"info","msg":"replaced zap's global loggers"}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleAtomicLevel() {
|
||||||
|
atom := zap.NewAtomicLevel()
|
||||||
|
|
||||||
|
// To keep the example deterministic, disable timestamps in the output.
|
||||||
|
encoderCfg := zap.NewProductionEncoderConfig()
|
||||||
|
encoderCfg.TimeKey = ""
|
||||||
|
|
||||||
|
logger := zap.New(zapcore.NewCore(
|
||||||
|
zapcore.NewJSONEncoder(encoderCfg),
|
||||||
|
zapcore.Lock(os.Stdout),
|
||||||
|
atom,
|
||||||
|
))
|
||||||
|
defer logger.Sync()
|
||||||
|
|
||||||
|
logger.Info("info logging enabled")
|
||||||
|
|
||||||
|
atom.SetLevel(zap.ErrorLevel)
|
||||||
|
logger.Info("info logging disabled")
|
||||||
|
// Output:
|
||||||
|
// {"level":"info","msg":"info logging enabled"}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleAtomicLevel_config() {
|
||||||
|
// The zap.Config struct includes an AtomicLevel. To use it, keep a
|
||||||
|
// reference to the Config.
|
||||||
|
rawJSON := []byte(`{
|
||||||
|
"level": "info",
|
||||||
|
"outputPaths": ["stdout"],
|
||||||
|
"errorOutputPaths": ["stderr"],
|
||||||
|
"encoding": "json",
|
||||||
|
"encoderConfig": {
|
||||||
|
"messageKey": "message",
|
||||||
|
"levelKey": "level",
|
||||||
|
"levelEncoder": "lowercase"
|
||||||
|
}
|
||||||
|
}`)
|
||||||
|
var cfg zap.Config
|
||||||
|
if err := json.Unmarshal(rawJSON, &cfg); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
logger, err := cfg.Build()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer logger.Sync()
|
||||||
|
|
||||||
|
logger.Info("info logging enabled")
|
||||||
|
|
||||||
|
cfg.Level.SetLevel(zap.ErrorLevel)
|
||||||
|
logger.Info("info logging disabled")
|
||||||
|
// Output:
|
||||||
|
// {"level":"info","message":"info logging enabled"}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleLogger_Check() {
|
||||||
|
logger := zap.NewExample()
|
||||||
|
defer logger.Sync()
|
||||||
|
|
||||||
|
if ce := logger.Check(zap.DebugLevel, "debugging"); ce != nil {
|
||||||
|
// If debug-level log output isn't enabled or if zap's sampling would have
|
||||||
|
// dropped this log entry, we don't allocate the slice that holds these
|
||||||
|
// fields.
|
||||||
|
ce.Write(
|
||||||
|
zap.String("foo", "bar"),
|
||||||
|
zap.String("baz", "quux"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// {"level":"debug","msg":"debugging","foo":"bar","baz":"quux"}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleLogger_Named() {
|
||||||
|
logger := zap.NewExample()
|
||||||
|
defer logger.Sync()
|
||||||
|
|
||||||
|
// By default, Loggers are unnamed.
|
||||||
|
logger.Info("no name")
|
||||||
|
|
||||||
|
// The first call to Named sets the Logger name.
|
||||||
|
main := logger.Named("main")
|
||||||
|
main.Info("main logger")
|
||||||
|
|
||||||
|
// Additional calls to Named create a period-separated path.
|
||||||
|
main.Named("subpackage").Info("sub-logger")
|
||||||
|
// Output:
|
||||||
|
// {"level":"info","msg":"no name"}
|
||||||
|
// {"level":"info","logger":"main","msg":"main logger"}
|
||||||
|
// {"level":"info","logger":"main.subpackage","msg":"sub-logger"}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleWrapCore_replace() {
|
||||||
|
// Replacing a Logger's core can alter fundamental behaviors. For example,
|
||||||
|
// example, it can convert a Logger to a no-op.
|
||||||
|
nop := zap.WrapCore(func(zapcore.Core) zapcore.Core {
|
||||||
|
return zapcore.NewNopCore()
|
||||||
|
})
|
||||||
|
|
||||||
|
logger := zap.NewExample()
|
||||||
|
defer logger.Sync()
|
||||||
|
|
||||||
|
logger.Info("working")
|
||||||
|
logger.WithOptions(nop).Info("no-op")
|
||||||
|
logger.Info("original logger still works")
|
||||||
|
// Output:
|
||||||
|
// {"level":"info","msg":"working"}
|
||||||
|
// {"level":"info","msg":"original logger still works"}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleWrapCore_wrap() {
|
||||||
|
// Wrapping a Logger's core can extend its functionality. As a trivial
|
||||||
|
// example, it can double-write all logs.
|
||||||
|
doubled := zap.WrapCore(func(c zapcore.Core) zapcore.Core {
|
||||||
|
return zapcore.NewTee(c, c)
|
||||||
|
})
|
||||||
|
|
||||||
|
logger := zap.NewExample()
|
||||||
|
defer logger.Sync()
|
||||||
|
|
||||||
|
logger.Info("single")
|
||||||
|
logger.WithOptions(doubled).Info("doubled")
|
||||||
|
// Output:
|
||||||
|
// {"level":"info","msg":"single"}
|
||||||
|
// {"level":"info","msg":"doubled"}
|
||||||
|
// {"level":"info","msg":"doubled"}
|
||||||
|
}
|
|
@ -0,0 +1,306 @@
|
||||||
|
// Copyright (c) 2016 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
package zap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Skip constructs a no-op field, which is often useful when handling invalid
|
||||||
|
// inputs in other Field constructors.
|
||||||
|
func Skip() zapcore.Field {
|
||||||
|
return zapcore.Field{Type: zapcore.SkipType}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Binary constructs a field that carries an opaque binary blob.
|
||||||
|
//
|
||||||
|
// Binary data is serialized in an encoding-appropriate format. For example,
|
||||||
|
// zap's JSON encoder base64-encodes binary blobs. To log UTF-8 encoded text,
|
||||||
|
// use ByteString.
|
||||||
|
func Binary(key string, val []byte) zapcore.Field {
|
||||||
|
return zapcore.Field{Key: key, Type: zapcore.BinaryType, Interface: val}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bool constructs a field that carries a bool.
|
||||||
|
func Bool(key string, val bool) zapcore.Field {
|
||||||
|
var ival int64
|
||||||
|
if val {
|
||||||
|
ival = 1
|
||||||
|
}
|
||||||
|
return zapcore.Field{Key: key, Type: zapcore.BoolType, Integer: ival}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByteString constructs a field that carries UTF-8 encoded text as a []byte.
|
||||||
|
// To log opaque binary blobs (which aren't necessarily valid UTF-8), use
|
||||||
|
// Binary.
|
||||||
|
func ByteString(key string, val []byte) zapcore.Field {
|
||||||
|
return zapcore.Field{Key: key, Type: zapcore.ByteStringType, Interface: val}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Complex128 constructs a field that carries a complex number. Unlike most
|
||||||
|
// numeric fields, this costs an allocation (to convert the complex128 to
|
||||||
|
// interface{}).
|
||||||
|
func Complex128(key string, val complex128) zapcore.Field {
|
||||||
|
return zapcore.Field{Key: key, Type: zapcore.Complex128Type, Interface: val}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Complex64 constructs a field that carries a complex number. Unlike most
|
||||||
|
// numeric fields, this costs an allocation (to convert the complex64 to
|
||||||
|
// interface{}).
|
||||||
|
func Complex64(key string, val complex64) zapcore.Field {
|
||||||
|
return zapcore.Field{Key: key, Type: zapcore.Complex64Type, Interface: val}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float64 constructs a field that carries a float64. The way the
|
||||||
|
// floating-point value is represented is encoder-dependent, so marshaling is
|
||||||
|
// necessarily lazy.
|
||||||
|
func Float64(key string, val float64) zapcore.Field {
|
||||||
|
return zapcore.Field{Key: key, Type: zapcore.Float64Type, Integer: int64(math.Float64bits(val))}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float32 constructs a field that carries a float32. The way the
|
||||||
|
// floating-point value is represented is encoder-dependent, so marshaling is
|
||||||
|
// necessarily lazy.
|
||||||
|
func Float32(key string, val float32) zapcore.Field {
|
||||||
|
return zapcore.Field{Key: key, Type: zapcore.Float32Type, Integer: int64(math.Float32bits(val))}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int constructs a field with the given key and value.
|
||||||
|
func Int(key string, val int) zapcore.Field {
|
||||||
|
return Int64(key, int64(val))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int64 constructs a field with the given key and value.
|
||||||
|
func Int64(key string, val int64) zapcore.Field {
|
||||||
|
return zapcore.Field{Key: key, Type: zapcore.Int64Type, Integer: val}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int32 constructs a field with the given key and value.
|
||||||
|
func Int32(key string, val int32) zapcore.Field {
|
||||||
|
return zapcore.Field{Key: key, Type: zapcore.Int32Type, Integer: int64(val)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int16 constructs a field with the given key and value.
|
||||||
|
func Int16(key string, val int16) zapcore.Field {
|
||||||
|
return zapcore.Field{Key: key, Type: zapcore.Int16Type, Integer: int64(val)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int8 constructs a field with the given key and value.
|
||||||
|
func Int8(key string, val int8) zapcore.Field {
|
||||||
|
return zapcore.Field{Key: key, Type: zapcore.Int8Type, Integer: int64(val)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// String constructs a field with the given key and value.
|
||||||
|
func String(key string, val string) zapcore.Field {
|
||||||
|
return zapcore.Field{Key: key, Type: zapcore.StringType, String: val}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint constructs a field with the given key and value.
|
||||||
|
func Uint(key string, val uint) zapcore.Field {
|
||||||
|
return Uint64(key, uint64(val))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint64 constructs a field with the given key and value.
|
||||||
|
func Uint64(key string, val uint64) zapcore.Field {
|
||||||
|
return zapcore.Field{Key: key, Type: zapcore.Uint64Type, Integer: int64(val)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint32 constructs a field with the given key and value.
|
||||||
|
func Uint32(key string, val uint32) zapcore.Field {
|
||||||
|
return zapcore.Field{Key: key, Type: zapcore.Uint32Type, Integer: int64(val)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint16 constructs a field with the given key and value.
|
||||||
|
func Uint16(key string, val uint16) zapcore.Field {
|
||||||
|
return zapcore.Field{Key: key, Type: zapcore.Uint16Type, Integer: int64(val)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint8 constructs a field with the given key and value.
|
||||||
|
func Uint8(key string, val uint8) zapcore.Field {
|
||||||
|
return zapcore.Field{Key: key, Type: zapcore.Uint8Type, Integer: int64(val)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uintptr constructs a field with the given key and value.
|
||||||
|
func Uintptr(key string, val uintptr) zapcore.Field {
|
||||||
|
return zapcore.Field{Key: key, Type: zapcore.UintptrType, Integer: int64(val)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reflect constructs a field with the given key and an arbitrary object. It uses
|
||||||
|
// an encoding-appropriate, reflection-based function to lazily serialize nearly
|
||||||
|
// any object into the logging context, but it's relatively slow and
|
||||||
|
// allocation-heavy. Outside tests, Any is always a better choice.
|
||||||
|
//
|
||||||
|
// If encoding fails (e.g., trying to serialize a map[int]string to JSON), Reflect
|
||||||
|
// includes the error message in the final log output.
|
||||||
|
func Reflect(key string, val interface{}) zapcore.Field {
|
||||||
|
return zapcore.Field{Key: key, Type: zapcore.ReflectType, Interface: val}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Namespace creates a named, isolated scope within the logger's context. All
|
||||||
|
// subsequent fields will be added to the new namespace.
|
||||||
|
//
|
||||||
|
// This helps prevent key collisions when injecting loggers into sub-components
|
||||||
|
// or third-party libraries.
|
||||||
|
func Namespace(key string) zapcore.Field {
|
||||||
|
return zapcore.Field{Key: key, Type: zapcore.NamespaceType}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stringer constructs a field with the given key and the output of the value's
|
||||||
|
// String method. The Stringer's String method is called lazily.
|
||||||
|
func Stringer(key string, val fmt.Stringer) zapcore.Field {
|
||||||
|
return zapcore.Field{Key: key, Type: zapcore.StringerType, Interface: val}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Time constructs a zapcore.Field with the given key and value. The encoder
|
||||||
|
// controls how the time is serialized.
|
||||||
|
func Time(key string, val time.Time) zapcore.Field {
|
||||||
|
return zapcore.Field{Key: key, Type: zapcore.TimeType, Integer: val.UnixNano(), Interface: val.Location()}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stack constructs a field that stores a stacktrace of the current goroutine
|
||||||
|
// under provided key. Keep in mind that taking a stacktrace is eager and
|
||||||
|
// expensive (relatively speaking); this function both makes an allocation and
|
||||||
|
// takes about two microseconds.
|
||||||
|
func Stack(key string) zapcore.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())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Duration constructs a field with the given key and value. The encoder
|
||||||
|
// controls how the duration is serialized.
|
||||||
|
func Duration(key string, val time.Duration) zapcore.Field {
|
||||||
|
return zapcore.Field{Key: key, Type: zapcore.DurationType, Integer: int64(val)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Object constructs a field with the given key and ObjectMarshaler. It
|
||||||
|
// provides a flexible, but still type-safe and efficient, way to add map- or
|
||||||
|
// struct-like user-defined types to the logging context. The struct's
|
||||||
|
// MarshalLogObject method is called lazily.
|
||||||
|
func Object(key string, val zapcore.ObjectMarshaler) zapcore.Field {
|
||||||
|
return zapcore.Field{Key: key, Type: zapcore.ObjectMarshalerType, Interface: val}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Any takes a key and an arbitrary value and chooses the best way to represent
|
||||||
|
// them as a field, falling back to a reflection-based approach only if
|
||||||
|
// necessary.
|
||||||
|
//
|
||||||
|
// Since byte/uint8 and rune/int32 are aliases, Any can't differentiate between
|
||||||
|
// them. To minimize suprise, []byte values are treated as binary blobs, byte
|
||||||
|
// values are treated as uint8, and runes are always treated as integers.
|
||||||
|
func Any(key string, value interface{}) zapcore.Field {
|
||||||
|
switch val := value.(type) {
|
||||||
|
case zapcore.ObjectMarshaler:
|
||||||
|
return Object(key, val)
|
||||||
|
case zapcore.ArrayMarshaler:
|
||||||
|
return Array(key, val)
|
||||||
|
case bool:
|
||||||
|
return Bool(key, val)
|
||||||
|
case []bool:
|
||||||
|
return Bools(key, val)
|
||||||
|
case complex128:
|
||||||
|
return Complex128(key, val)
|
||||||
|
case []complex128:
|
||||||
|
return Complex128s(key, val)
|
||||||
|
case complex64:
|
||||||
|
return Complex64(key, val)
|
||||||
|
case []complex64:
|
||||||
|
return Complex64s(key, val)
|
||||||
|
case float64:
|
||||||
|
return Float64(key, val)
|
||||||
|
case []float64:
|
||||||
|
return Float64s(key, val)
|
||||||
|
case float32:
|
||||||
|
return Float32(key, val)
|
||||||
|
case []float32:
|
||||||
|
return Float32s(key, val)
|
||||||
|
case int:
|
||||||
|
return Int(key, val)
|
||||||
|
case []int:
|
||||||
|
return Ints(key, val)
|
||||||
|
case int64:
|
||||||
|
return Int64(key, val)
|
||||||
|
case []int64:
|
||||||
|
return Int64s(key, val)
|
||||||
|
case int32:
|
||||||
|
return Int32(key, val)
|
||||||
|
case []int32:
|
||||||
|
return Int32s(key, val)
|
||||||
|
case int16:
|
||||||
|
return Int16(key, val)
|
||||||
|
case []int16:
|
||||||
|
return Int16s(key, val)
|
||||||
|
case int8:
|
||||||
|
return Int8(key, val)
|
||||||
|
case []int8:
|
||||||
|
return Int8s(key, val)
|
||||||
|
case string:
|
||||||
|
return String(key, val)
|
||||||
|
case []string:
|
||||||
|
return Strings(key, val)
|
||||||
|
case uint:
|
||||||
|
return Uint(key, val)
|
||||||
|
case []uint:
|
||||||
|
return Uints(key, val)
|
||||||
|
case uint64:
|
||||||
|
return Uint64(key, val)
|
||||||
|
case []uint64:
|
||||||
|
return Uint64s(key, val)
|
||||||
|
case uint32:
|
||||||
|
return Uint32(key, val)
|
||||||
|
case []uint32:
|
||||||
|
return Uint32s(key, val)
|
||||||
|
case uint16:
|
||||||
|
return Uint16(key, val)
|
||||||
|
case []uint16:
|
||||||
|
return Uint16s(key, val)
|
||||||
|
case uint8:
|
||||||
|
return Uint8(key, val)
|
||||||
|
case []byte:
|
||||||
|
return Binary(key, val)
|
||||||
|
case uintptr:
|
||||||
|
return Uintptr(key, val)
|
||||||
|
case []uintptr:
|
||||||
|
return Uintptrs(key, val)
|
||||||
|
case time.Time:
|
||||||
|
return Time(key, val)
|
||||||
|
case []time.Time:
|
||||||
|
return Times(key, val)
|
||||||
|
case time.Duration:
|
||||||
|
return Duration(key, val)
|
||||||
|
case []time.Duration:
|
||||||
|
return Durations(key, val)
|
||||||
|
case error:
|
||||||
|
return NamedError(key, val)
|
||||||
|
case []error:
|
||||||
|
return Errors(key, val)
|
||||||
|
case fmt.Stringer:
|
||||||
|
return Stringer(key, val)
|
||||||
|
default:
|
||||||
|
return Reflect(key, val)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,159 @@
|
||||||
|
// Copyright (c) 2016 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
package zap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
|
)
|
||||||
|
|
||||||
|
type username string
|
||||||
|
|
||||||
|
func (n username) MarshalLogObject(enc zapcore.ObjectEncoder) error {
|
||||||
|
enc.AddString("username", string(n))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertCanBeReused(t testing.TB, field zapcore.Field) {
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
|
for i := 0; i < 100; i++ {
|
||||||
|
enc := zapcore.NewMapObjectEncoder()
|
||||||
|
|
||||||
|
// Ensure using the field in multiple encoders in separate goroutines
|
||||||
|
// does not cause any races or panics.
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
assert.NotPanics(t, func() {
|
||||||
|
field.AddTo(enc)
|
||||||
|
}, "Reusing a field should not cause issues")
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFieldConstructors(t *testing.T) {
|
||||||
|
// Interface types.
|
||||||
|
addr := net.ParseIP("1.2.3.4")
|
||||||
|
name := username("phil")
|
||||||
|
ints := []int{5, 6}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
field zapcore.Field
|
||||||
|
expect zapcore.Field
|
||||||
|
}{
|
||||||
|
{"Skip", zapcore.Field{Type: zapcore.SkipType}, Skip()},
|
||||||
|
{"Binary", zapcore.Field{Key: "k", Type: zapcore.BinaryType, Interface: []byte("ab12")}, Binary("k", []byte("ab12"))},
|
||||||
|
{"Bool", zapcore.Field{Key: "k", Type: zapcore.BoolType, Integer: 1}, Bool("k", true)},
|
||||||
|
{"Bool", zapcore.Field{Key: "k", Type: zapcore.BoolType, Integer: 1}, Bool("k", true)},
|
||||||
|
{"ByteString", zapcore.Field{Key: "k", Type: zapcore.ByteStringType, Interface: []byte("ab12")}, ByteString("k", []byte("ab12"))},
|
||||||
|
{"Complex128", zapcore.Field{Key: "k", Type: zapcore.Complex128Type, Interface: 1 + 2i}, Complex128("k", 1+2i)},
|
||||||
|
{"Complex64", zapcore.Field{Key: "k", Type: zapcore.Complex64Type, Interface: complex64(1 + 2i)}, Complex64("k", 1+2i)},
|
||||||
|
{"Duration", zapcore.Field{Key: "k", Type: zapcore.DurationType, Integer: 1}, Duration("k", 1)},
|
||||||
|
{"Int", zapcore.Field{Key: "k", Type: zapcore.Int64Type, Integer: 1}, Int("k", 1)},
|
||||||
|
{"Int64", zapcore.Field{Key: "k", Type: zapcore.Int64Type, Integer: 1}, Int64("k", 1)},
|
||||||
|
{"Int32", zapcore.Field{Key: "k", Type: zapcore.Int32Type, Integer: 1}, Int32("k", 1)},
|
||||||
|
{"Int16", zapcore.Field{Key: "k", Type: zapcore.Int16Type, Integer: 1}, Int16("k", 1)},
|
||||||
|
{"Int8", zapcore.Field{Key: "k", Type: zapcore.Int8Type, Integer: 1}, Int8("k", 1)},
|
||||||
|
{"String", zapcore.Field{Key: "k", Type: zapcore.StringType, String: "foo"}, String("k", "foo")},
|
||||||
|
{"Time", zapcore.Field{Key: "k", Type: zapcore.TimeType, Integer: 0, Interface: time.UTC}, Time("k", time.Unix(0, 0).In(time.UTC))},
|
||||||
|
{"Time", zapcore.Field{Key: "k", Type: zapcore.TimeType, Integer: 1000, Interface: time.UTC}, Time("k", time.Unix(0, 1000).In(time.UTC))},
|
||||||
|
{"Uint", zapcore.Field{Key: "k", Type: zapcore.Uint64Type, Integer: 1}, Uint("k", 1)},
|
||||||
|
{"Uint64", zapcore.Field{Key: "k", Type: zapcore.Uint64Type, Integer: 1}, Uint64("k", 1)},
|
||||||
|
{"Uint32", zapcore.Field{Key: "k", Type: zapcore.Uint32Type, Integer: 1}, Uint32("k", 1)},
|
||||||
|
{"Uint16", zapcore.Field{Key: "k", Type: zapcore.Uint16Type, Integer: 1}, Uint16("k", 1)},
|
||||||
|
{"Uint8", zapcore.Field{Key: "k", Type: zapcore.Uint8Type, Integer: 1}, Uint8("k", 1)},
|
||||||
|
{"Uintptr", zapcore.Field{Key: "k", Type: zapcore.UintptrType, Integer: 10}, Uintptr("k", 0xa)},
|
||||||
|
{"Reflect", zapcore.Field{Key: "k", Type: zapcore.ReflectType, Interface: ints}, Reflect("k", ints)},
|
||||||
|
{"Stringer", zapcore.Field{Key: "k", Type: zapcore.StringerType, Interface: addr}, Stringer("k", addr)},
|
||||||
|
{"Object", zapcore.Field{Key: "k", Type: zapcore.ObjectMarshalerType, Interface: name}, Object("k", name)},
|
||||||
|
{"Any:ObjectMarshaler", Any("k", name), Object("k", name)},
|
||||||
|
{"Any:ArrayMarshaler", Any("k", bools([]bool{true})), Array("k", bools([]bool{true}))},
|
||||||
|
{"Any:Stringer", Any("k", addr), Stringer("k", addr)},
|
||||||
|
{"Any:Bool", Any("k", true), Bool("k", true)},
|
||||||
|
{"Any:Bools", Any("k", []bool{true}), Bools("k", []bool{true})},
|
||||||
|
{"Any:Byte", Any("k", byte(1)), Uint8("k", 1)},
|
||||||
|
{"Any:Bytes", Any("k", []byte{1}), Binary("k", []byte{1})},
|
||||||
|
{"Any:Complex128", Any("k", 1+2i), Complex128("k", 1+2i)},
|
||||||
|
{"Any:Complex128s", Any("k", []complex128{1 + 2i}), Complex128s("k", []complex128{1 + 2i})},
|
||||||
|
{"Any:Complex64", Any("k", complex64(1+2i)), Complex64("k", 1+2i)},
|
||||||
|
{"Any:Complex64s", Any("k", []complex64{1 + 2i}), Complex64s("k", []complex64{1 + 2i})},
|
||||||
|
{"Any:Float64", Any("k", 3.14), Float64("k", 3.14)},
|
||||||
|
{"Any:Float64s", Any("k", []float64{3.14}), Float64s("k", []float64{3.14})},
|
||||||
|
{"Any:Float32", Any("k", float32(3.14)), Float32("k", 3.14)},
|
||||||
|
{"Any:Float32s", Any("k", []float32{3.14}), Float32s("k", []float32{3.14})},
|
||||||
|
{"Any:Int", Any("k", 1), Int("k", 1)},
|
||||||
|
{"Any:Ints", Any("k", []int{1}), Ints("k", []int{1})},
|
||||||
|
{"Any:Int64", Any("k", int64(1)), Int64("k", 1)},
|
||||||
|
{"Any:Int64s", Any("k", []int64{1}), Int64s("k", []int64{1})},
|
||||||
|
{"Any:Int32", Any("k", int32(1)), Int32("k", 1)},
|
||||||
|
{"Any:Int32s", Any("k", []int32{1}), Int32s("k", []int32{1})},
|
||||||
|
{"Any:Int16", Any("k", int16(1)), Int16("k", 1)},
|
||||||
|
{"Any:Int16s", Any("k", []int16{1}), Int16s("k", []int16{1})},
|
||||||
|
{"Any:Int8", Any("k", int8(1)), Int8("k", 1)},
|
||||||
|
{"Any:Int8s", Any("k", []int8{1}), Int8s("k", []int8{1})},
|
||||||
|
{"Any:Rune", Any("k", rune(1)), Int32("k", 1)},
|
||||||
|
{"Any:Runes", Any("k", []rune{1}), Int32s("k", []int32{1})},
|
||||||
|
{"Any:String", Any("k", "v"), String("k", "v")},
|
||||||
|
{"Any:Strings", Any("k", []string{"v"}), Strings("k", []string{"v"})},
|
||||||
|
{"Any:Uint", Any("k", uint(1)), Uint("k", 1)},
|
||||||
|
{"Any:Uints", Any("k", []uint{1}), Uints("k", []uint{1})},
|
||||||
|
{"Any:Uint64", Any("k", uint64(1)), Uint64("k", 1)},
|
||||||
|
{"Any:Uint64s", Any("k", []uint64{1}), Uint64s("k", []uint64{1})},
|
||||||
|
{"Any:Uint32", Any("k", uint32(1)), Uint32("k", 1)},
|
||||||
|
{"Any:Uint32s", Any("k", []uint32{1}), Uint32s("k", []uint32{1})},
|
||||||
|
{"Any:Uint16", Any("k", uint16(1)), Uint16("k", 1)},
|
||||||
|
{"Any:Uint16s", Any("k", []uint16{1}), Uint16s("k", []uint16{1})},
|
||||||
|
{"Any:Uint8", Any("k", uint8(1)), Uint8("k", 1)},
|
||||||
|
{"Any:Uint8s", Any("k", []uint8{1}), Binary("k", []uint8{1})},
|
||||||
|
{"Any:Uintptr", Any("k", uintptr(1)), Uintptr("k", 1)},
|
||||||
|
{"Any:Uintptrs", Any("k", []uintptr{1}), Uintptrs("k", []uintptr{1})},
|
||||||
|
{"Any:Time", Any("k", time.Unix(0, 0)), Time("k", time.Unix(0, 0))},
|
||||||
|
{"Any:Times", Any("k", []time.Time{time.Unix(0, 0)}), Times("k", []time.Time{time.Unix(0, 0)})},
|
||||||
|
{"Any:Duration", Any("k", time.Second), Duration("k", time.Second)},
|
||||||
|
{"Any:Durations", Any("k", []time.Duration{time.Second}), Durations("k", []time.Duration{time.Second})},
|
||||||
|
{"Any:Fallback", Any("k", struct{}{}), Reflect("k", struct{}{})},
|
||||||
|
{"Namespace", Namespace("k"), zapcore.Field{Key: "k", Type: zapcore.NamespaceType}},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
if !assert.Equal(t, tt.expect, tt.field, "Unexpected output from convenience field constructor %s.", tt.name) {
|
||||||
|
t.Logf("type expected: %T\nGot: %T", tt.expect.Interface, tt.field.Interface)
|
||||||
|
}
|
||||||
|
assertCanBeReused(t, tt.field)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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")
|
||||||
|
assertCanBeReused(t, f)
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
// Copyright (c) 2016 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
package zap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LevelFlag uses the standard library's flag.Var to declare a global flag
|
||||||
|
// with the specified name, default, and usage guidance. The returned value is
|
||||||
|
// a pointer to the value of the flag.
|
||||||
|
//
|
||||||
|
// If you don't want to use the flag package's global state, you can use any
|
||||||
|
// non-nil *Level as a flag.Value with your own *flag.FlagSet.
|
||||||
|
func LevelFlag(name string, defaultLevel zapcore.Level, usage string) *zapcore.Level {
|
||||||
|
lvl := defaultLevel
|
||||||
|
flag.Var(&lvl, name, usage)
|
||||||
|
return &lvl
|
||||||
|
}
|
|
@ -0,0 +1,102 @@
|
||||||
|
// Copyright (c) 2016 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
package zap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"io/ioutil"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
type flagTestCase struct {
|
||||||
|
args []string
|
||||||
|
wantLevel zapcore.Level
|
||||||
|
wantErr bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tc flagTestCase) runImplicitSet(t testing.TB) {
|
||||||
|
origCommandLine := flag.CommandLine
|
||||||
|
flag.CommandLine = flag.NewFlagSet("test", flag.ContinueOnError)
|
||||||
|
flag.CommandLine.SetOutput(ioutil.Discard)
|
||||||
|
defer func() { flag.CommandLine = origCommandLine }()
|
||||||
|
|
||||||
|
level := LevelFlag("level", InfoLevel, "")
|
||||||
|
tc.run(t, flag.CommandLine, level)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tc flagTestCase) runExplicitSet(t testing.TB) {
|
||||||
|
var lvl zapcore.Level
|
||||||
|
set := flag.NewFlagSet("test", flag.ContinueOnError)
|
||||||
|
set.Var(&lvl, "level", "minimum enabled logging level")
|
||||||
|
tc.run(t, set, &lvl)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tc flagTestCase) run(t testing.TB, set *flag.FlagSet, actual *zapcore.Level) {
|
||||||
|
err := set.Parse(tc.args)
|
||||||
|
if tc.wantErr {
|
||||||
|
assert.Error(t, err, "Parse(%v) should fail.", tc.args)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if assert.NoError(t, err, "Parse(%v) should succeed.", tc.args) {
|
||||||
|
assert.Equal(t, tc.wantLevel, *actual, "Level mismatch.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLevelFlag(t *testing.T) {
|
||||||
|
tests := []flagTestCase{
|
||||||
|
{
|
||||||
|
args: nil,
|
||||||
|
wantLevel: zapcore.InfoLevel,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: []string{"--level", "unknown"},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: []string{"--level", "error"},
|
||||||
|
wantLevel: zapcore.ErrorLevel,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
tt.runExplicitSet(t)
|
||||||
|
tt.runImplicitSet(t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLevelFlagsAreIndependent(t *testing.T) {
|
||||||
|
origCommandLine := flag.CommandLine
|
||||||
|
flag.CommandLine = flag.NewFlagSet("test", flag.ContinueOnError)
|
||||||
|
flag.CommandLine.SetOutput(ioutil.Discard)
|
||||||
|
defer func() { flag.CommandLine = origCommandLine }()
|
||||||
|
|
||||||
|
// Make sure that these two flags are independent.
|
||||||
|
fileLevel := LevelFlag("file-level", InfoLevel, "")
|
||||||
|
consoleLevel := LevelFlag("console-level", InfoLevel, "")
|
||||||
|
|
||||||
|
assert.NoError(t, flag.CommandLine.Parse([]string{"-file-level", "debug"}), "Unexpected flag-parsing error.")
|
||||||
|
assert.Equal(t, InfoLevel, *consoleLevel, "Expected file logging level to remain unchanged.")
|
||||||
|
assert.Equal(t, DebugLevel, *fileLevel, "Expected console logging level to have changed.")
|
||||||
|
}
|
|
@ -0,0 +1,76 @@
|
||||||
|
hash: f073ba522c06c88ea3075bde32a8aaf0969a840a66cab6318a0897d141ffee92
|
||||||
|
updated: 2017-07-22T18:06:49.598185334-07:00
|
||||||
|
imports:
|
||||||
|
- name: go.uber.org/atomic
|
||||||
|
version: 4e336646b2ef9fc6e47be8e21594178f98e5ebcf
|
||||||
|
- name: go.uber.org/multierr
|
||||||
|
version: 3c4937480c32f4c13a875a1829af76c98ca3d40a
|
||||||
|
testImports:
|
||||||
|
- name: github.com/apex/log
|
||||||
|
version: d9b960447bfa720077b2da653cc79e533455b499
|
||||||
|
subpackages:
|
||||||
|
- handlers/json
|
||||||
|
- name: github.com/axw/gocov
|
||||||
|
version: 3a69a0d2a4ef1f263e2d92b041a69593d6964fe8
|
||||||
|
subpackages:
|
||||||
|
- gocov
|
||||||
|
- name: github.com/davecgh/go-spew
|
||||||
|
version: 04cdfd42973bb9c8589fd6a731800cf222fde1a9
|
||||||
|
subpackages:
|
||||||
|
- spew
|
||||||
|
- name: github.com/fatih/color
|
||||||
|
version: 62e9147c64a1ed519147b62a56a14e83e2be02c1
|
||||||
|
- name: github.com/go-kit/kit
|
||||||
|
version: e10f5bf035be9af21fd5b2fb4469d5716c6ab07d
|
||||||
|
subpackages:
|
||||||
|
- log
|
||||||
|
- name: github.com/go-logfmt/logfmt
|
||||||
|
version: 390ab7935ee28ec6b286364bba9b4dd6410cb3d5
|
||||||
|
- name: github.com/go-stack/stack
|
||||||
|
version: 54be5f394ed2c3e19dac9134a40a95ba5a017f7b
|
||||||
|
- name: github.com/golang/lint
|
||||||
|
version: c5fb716d6688a859aae56d26d3e6070808df29f7
|
||||||
|
subpackages:
|
||||||
|
- golint
|
||||||
|
- name: github.com/kr/logfmt
|
||||||
|
version: b84e30acd515aadc4b783ad4ff83aff3299bdfe0
|
||||||
|
- name: github.com/mattn/go-colorable
|
||||||
|
version: 3fa8c76f9daed4067e4a806fb7e4dc86455c6d6a
|
||||||
|
- name: github.com/mattn/go-isatty
|
||||||
|
version: fc9e8d8ef48496124e79ae0df75490096eccf6fe
|
||||||
|
- name: github.com/mattn/goveralls
|
||||||
|
version: 6efce81852ad1b7567c17ad71b03aeccc9dd9ae0
|
||||||
|
- name: github.com/pborman/uuid
|
||||||
|
version: e790cca94e6cc75c7064b1332e63811d4aae1a53
|
||||||
|
- name: github.com/pkg/errors
|
||||||
|
version: 645ef00459ed84a119197bfb8d8205042c6df63d
|
||||||
|
- name: github.com/pmezard/go-difflib
|
||||||
|
version: d8ed2627bdf02c080bf22230dbb337003b7aba2d
|
||||||
|
subpackages:
|
||||||
|
- difflib
|
||||||
|
- name: github.com/rs/zerolog
|
||||||
|
version: eed4c2b94d945e0b2456ad6aa518a443986b5f22
|
||||||
|
- name: github.com/satori/go.uuid
|
||||||
|
version: 5bf94b69c6b68ee1b541973bb8e1144db23a194b
|
||||||
|
- name: github.com/sirupsen/logrus
|
||||||
|
version: 7dd06bf38e1e13df288d471a57d5adbac106be9e
|
||||||
|
- name: github.com/stretchr/testify
|
||||||
|
version: f6abca593680b2315d2075e0f5e2a9751e3f431a
|
||||||
|
subpackages:
|
||||||
|
- assert
|
||||||
|
- require
|
||||||
|
- name: go.pedge.io/lion
|
||||||
|
version: 87958e8713f1fa138d993087133b97e976642159
|
||||||
|
- name: golang.org/x/sys
|
||||||
|
version: c4489faa6e5ab84c0ef40d6ee878f7a030281f0f
|
||||||
|
subpackages:
|
||||||
|
- unix
|
||||||
|
- name: golang.org/x/tools
|
||||||
|
version: 496819729719f9d07692195e0a94d6edd2251389
|
||||||
|
subpackages:
|
||||||
|
- cover
|
||||||
|
- name: gopkg.in/inconshreveable/log15.v2
|
||||||
|
version: b105bd37f74e5d9dc7b6ad7806715c7a2b83fd3f
|
||||||
|
subpackages:
|
||||||
|
- stack
|
||||||
|
- term
|
|
@ -0,0 +1,35 @@
|
||||||
|
package: go.uber.org/zap
|
||||||
|
license: MIT
|
||||||
|
import:
|
||||||
|
- package: go.uber.org/atomic
|
||||||
|
version: ^1
|
||||||
|
- package: go.uber.org/multierr
|
||||||
|
version: ^1
|
||||||
|
testImport:
|
||||||
|
- package: github.com/satori/go.uuid
|
||||||
|
- package: github.com/sirupsen/logrus
|
||||||
|
- package: github.com/apex/log
|
||||||
|
subpackages:
|
||||||
|
- handlers/json
|
||||||
|
- package: github.com/go-kit/kit
|
||||||
|
subpackages:
|
||||||
|
- log
|
||||||
|
- package: github.com/stretchr/testify
|
||||||
|
subpackages:
|
||||||
|
- assert
|
||||||
|
- require
|
||||||
|
- package: gopkg.in/inconshreveable/log15.v2
|
||||||
|
- package: github.com/mattn/goveralls
|
||||||
|
- package: github.com/pborman/uuid
|
||||||
|
- package: github.com/pkg/errors
|
||||||
|
- package: go.pedge.io/lion
|
||||||
|
- package: github.com/rs/zerolog
|
||||||
|
- package: golang.org/x/tools
|
||||||
|
subpackages:
|
||||||
|
- cover
|
||||||
|
- package: github.com/golang/lint
|
||||||
|
subpackages:
|
||||||
|
- golint
|
||||||
|
- package: github.com/axw/gocov
|
||||||
|
subpackages:
|
||||||
|
- gocov
|
|
@ -0,0 +1,139 @@
|
||||||
|
// Copyright (c) 2016 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
package zap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
_stdLogDefaultDepth = 2
|
||||||
|
_loggerWriterDepth = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
_globalMu sync.RWMutex
|
||||||
|
_globalL = NewNop()
|
||||||
|
_globalS = _globalL.Sugar()
|
||||||
|
)
|
||||||
|
|
||||||
|
// L returns the global Logger, which can be reconfigured with ReplaceGlobals.
|
||||||
|
// It's safe for concurrent use.
|
||||||
|
func L() *Logger {
|
||||||
|
_globalMu.RLock()
|
||||||
|
l := _globalL
|
||||||
|
_globalMu.RUnlock()
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
// S returns the global SugaredLogger, which can be reconfigured with
|
||||||
|
// ReplaceGlobals. It's safe for concurrent use.
|
||||||
|
func S() *SugaredLogger {
|
||||||
|
_globalMu.RLock()
|
||||||
|
s := _globalS
|
||||||
|
_globalMu.RUnlock()
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReplaceGlobals replaces the global Logger and SugaredLogger, and returns a
|
||||||
|
// function to restore the original values. It's safe for concurrent use.
|
||||||
|
func ReplaceGlobals(logger *Logger) func() {
|
||||||
|
_globalMu.Lock()
|
||||||
|
prev := _globalL
|
||||||
|
_globalL = logger
|
||||||
|
_globalS = logger.Sugar()
|
||||||
|
_globalMu.Unlock()
|
||||||
|
return func() { ReplaceGlobals(prev) }
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewStdLog returns a *log.Logger which writes to the supplied zap Logger at
|
||||||
|
// InfoLevel. To redirect the standard library's package-global logging
|
||||||
|
// functions, use RedirectStdLog instead.
|
||||||
|
func NewStdLog(l *Logger) *log.Logger {
|
||||||
|
logger := l.WithOptions(AddCallerSkip(_stdLogDefaultDepth + _loggerWriterDepth))
|
||||||
|
f := logger.Info
|
||||||
|
return log.New(&loggerWriter{f}, "" /* prefix */, 0 /* flags */)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewStdLogAt returns *log.Logger which writes to supplied zap logger at
|
||||||
|
// required level.
|
||||||
|
func NewStdLogAt(l *Logger, level zapcore.Level) (*log.Logger, error) {
|
||||||
|
logger := l.WithOptions(AddCallerSkip(_stdLogDefaultDepth + _loggerWriterDepth))
|
||||||
|
var logFunc func(string, ...zapcore.Field)
|
||||||
|
switch level {
|
||||||
|
case DebugLevel:
|
||||||
|
logFunc = logger.Debug
|
||||||
|
case InfoLevel:
|
||||||
|
logFunc = logger.Info
|
||||||
|
case WarnLevel:
|
||||||
|
logFunc = logger.Warn
|
||||||
|
case ErrorLevel:
|
||||||
|
logFunc = logger.Error
|
||||||
|
case DPanicLevel:
|
||||||
|
logFunc = logger.DPanic
|
||||||
|
case PanicLevel:
|
||||||
|
logFunc = logger.Panic
|
||||||
|
case FatalLevel:
|
||||||
|
logFunc = logger.Fatal
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unrecognized level: %q", level)
|
||||||
|
}
|
||||||
|
return log.New(&loggerWriter{logFunc}, "" /* prefix */, 0 /* flags */), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RedirectStdLog redirects output from the standard library's package-global
|
||||||
|
// logger to the supplied logger at InfoLevel. Since zap already handles caller
|
||||||
|
// annotations, timestamps, etc., it automatically disables the standard
|
||||||
|
// library's annotations and prefixing.
|
||||||
|
//
|
||||||
|
// It returns a function to restore the original prefix and flags and reset the
|
||||||
|
// standard library's output to os.Stdout.
|
||||||
|
func RedirectStdLog(l *Logger) func() {
|
||||||
|
flags := log.Flags()
|
||||||
|
prefix := log.Prefix()
|
||||||
|
log.SetFlags(0)
|
||||||
|
log.SetPrefix("")
|
||||||
|
logFunc := l.WithOptions(
|
||||||
|
AddCallerSkip(_stdLogDefaultDepth + _loggerWriterDepth),
|
||||||
|
).Info
|
||||||
|
log.SetOutput(&loggerWriter{logFunc})
|
||||||
|
return func() {
|
||||||
|
log.SetFlags(flags)
|
||||||
|
log.SetPrefix(prefix)
|
||||||
|
log.SetOutput(os.Stderr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type loggerWriter struct {
|
||||||
|
logFunc func(msg string, fields ...zapcore.Field)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *loggerWriter) Write(p []byte) (int, error) {
|
||||||
|
p = bytes.TrimSpace(p)
|
||||||
|
l.logFunc(string(p))
|
||||||
|
return len(p), nil
|
||||||
|
}
|
|
@ -0,0 +1,189 @@
|
||||||
|
// Copyright (c) 2016 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
package zap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"go.uber.org/zap/internal/exit"
|
||||||
|
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
|
"go.uber.org/zap/zaptest"
|
||||||
|
"go.uber.org/zap/zaptest/observer"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"go.uber.org/atomic"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestReplaceGlobals(t *testing.T) {
|
||||||
|
initialL := *L()
|
||||||
|
initialS := *S()
|
||||||
|
|
||||||
|
withLogger(t, DebugLevel, nil, func(l *Logger, logs *observer.ObservedLogs) {
|
||||||
|
L().Info("no-op")
|
||||||
|
S().Info("no-op")
|
||||||
|
assert.Equal(t, 0, logs.Len(), "Expected initial logs to go to default no-op global.")
|
||||||
|
|
||||||
|
defer ReplaceGlobals(l)()
|
||||||
|
|
||||||
|
L().Info("captured")
|
||||||
|
S().Info("captured")
|
||||||
|
expected := observer.LoggedEntry{
|
||||||
|
Entry: zapcore.Entry{Message: "captured"},
|
||||||
|
Context: []zapcore.Field{},
|
||||||
|
}
|
||||||
|
assert.Equal(
|
||||||
|
t,
|
||||||
|
[]observer.LoggedEntry{expected, expected},
|
||||||
|
logs.AllUntimed(),
|
||||||
|
"Unexpected global log output.",
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.Equal(t, initialL, *L(), "Expected func returned from ReplaceGlobals to restore initial L.")
|
||||||
|
assert.Equal(t, initialS, *S(), "Expected func returned from ReplaceGlobals to restore initial S.")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGlobalsConcurrentUse(t *testing.T) {
|
||||||
|
var (
|
||||||
|
stop atomic.Bool
|
||||||
|
wg sync.WaitGroup
|
||||||
|
)
|
||||||
|
|
||||||
|
for i := 0; i < 100; i++ {
|
||||||
|
wg.Add(2)
|
||||||
|
go func() {
|
||||||
|
for !stop.Load() {
|
||||||
|
ReplaceGlobals(NewNop())
|
||||||
|
}
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
go func() {
|
||||||
|
for !stop.Load() {
|
||||||
|
L().With(Int("foo", 42)).Named("main").WithOptions(Development()).Info("")
|
||||||
|
S().Info("")
|
||||||
|
}
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
zaptest.Sleep(100 * time.Millisecond)
|
||||||
|
stop.Toggle()
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewStdLog(t *testing.T) {
|
||||||
|
withLogger(t, DebugLevel, []Option{AddCaller()}, func(l *Logger, logs *observer.ObservedLogs) {
|
||||||
|
std := NewStdLog(l)
|
||||||
|
std.Print("redirected")
|
||||||
|
checkStdLogMessage(t, "redirected", logs)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewStdLogAt(t *testing.T) {
|
||||||
|
// include DPanicLevel here, but do not include Development in options
|
||||||
|
levels := []zapcore.Level{DebugLevel, InfoLevel, WarnLevel, ErrorLevel, DPanicLevel}
|
||||||
|
for _, level := range levels {
|
||||||
|
withLogger(t, DebugLevel, []Option{AddCaller()}, func(l *Logger, logs *observer.ObservedLogs) {
|
||||||
|
std, err := NewStdLogAt(l, level)
|
||||||
|
require.NoError(t, err, "Unexpected error.")
|
||||||
|
std.Print("redirected")
|
||||||
|
checkStdLogMessage(t, "redirected", logs)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewStdLogAtPanics(t *testing.T) {
|
||||||
|
// include DPanicLevel here and enable Development in options
|
||||||
|
levels := []zapcore.Level{DPanicLevel, PanicLevel}
|
||||||
|
for _, level := range levels {
|
||||||
|
withLogger(t, DebugLevel, []Option{AddCaller(), Development()}, func(l *Logger, logs *observer.ObservedLogs) {
|
||||||
|
std, err := NewStdLogAt(l, level)
|
||||||
|
require.NoError(t, err, "Unexpected error")
|
||||||
|
assert.Panics(t, func() { std.Print("redirected") }, "Expected log to panic.")
|
||||||
|
checkStdLogMessage(t, "redirected", logs)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewStdLogAtFatal(t *testing.T) {
|
||||||
|
withLogger(t, DebugLevel, []Option{AddCaller()}, func(l *Logger, logs *observer.ObservedLogs) {
|
||||||
|
stub := exit.WithStub(func() {
|
||||||
|
std, err := NewStdLogAt(l, FatalLevel)
|
||||||
|
require.NoError(t, err, "Unexpected error.")
|
||||||
|
std.Print("redirected")
|
||||||
|
checkStdLogMessage(t, "redirected", logs)
|
||||||
|
})
|
||||||
|
assert.True(t, true, stub.Exited, "Expected Fatal logger call to terminate process.")
|
||||||
|
stub.Unstub()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewStdLogAtInvalid(t *testing.T) {
|
||||||
|
_, err := NewStdLogAt(NewNop(), zapcore.Level(99))
|
||||||
|
assert.Error(t, err, "Expected to get error.")
|
||||||
|
assert.Contains(t, err.Error(), "99", "Expected level code in error message")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRedirectStdLog(t *testing.T) {
|
||||||
|
initialFlags := log.Flags()
|
||||||
|
initialPrefix := log.Prefix()
|
||||||
|
|
||||||
|
withLogger(t, DebugLevel, nil, func(l *Logger, logs *observer.ObservedLogs) {
|
||||||
|
defer RedirectStdLog(l)()
|
||||||
|
log.Print("redirected")
|
||||||
|
|
||||||
|
assert.Equal(t, []observer.LoggedEntry{{
|
||||||
|
Entry: zapcore.Entry{Message: "redirected"},
|
||||||
|
Context: []zapcore.Field{},
|
||||||
|
}}, logs.AllUntimed(), "Unexpected global log output.")
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.Equal(t, initialFlags, log.Flags(), "Expected to reset initial flags.")
|
||||||
|
assert.Equal(t, initialPrefix, log.Prefix(), "Expected to reset initial prefix.")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRedirectStdLogCaller(t *testing.T) {
|
||||||
|
withLogger(t, DebugLevel, []Option{AddCaller()}, func(l *Logger, logs *observer.ObservedLogs) {
|
||||||
|
defer RedirectStdLog(l)()
|
||||||
|
log.Print("redirected")
|
||||||
|
entries := logs.All()
|
||||||
|
require.Len(t, entries, 1, "Unexpected number of logs.")
|
||||||
|
assert.Contains(t, entries[0].Entry.Caller.File, "global_test.go", "Unexpected caller annotation.")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkStdLogMessage(t *testing.T, msg string, logs *observer.ObservedLogs) {
|
||||||
|
require.Equal(t, 1, logs.Len(), "Expected exactly one entry to be logged")
|
||||||
|
entry := logs.AllUntimed()[0]
|
||||||
|
assert.Equal(t, []zapcore.Field{}, entry.Context, "Unexpected entry context.")
|
||||||
|
assert.Equal(t, "redirected", entry.Entry.Message, "Unexpected entry message.")
|
||||||
|
assert.Regexp(
|
||||||
|
t,
|
||||||
|
`go.uber.org/zap/global_test.go:\d+$`,
|
||||||
|
entry.Entry.Caller.String(),
|
||||||
|
"Unexpected caller annotation.",
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,81 @@
|
||||||
|
// Copyright (c) 2016 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
package zap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ServeHTTP is a simple JSON endpoint that can report on or change the current
|
||||||
|
// logging level.
|
||||||
|
//
|
||||||
|
// GET requests return a JSON description of the current logging level. PUT
|
||||||
|
// requests change the logging level and expect a payload like:
|
||||||
|
// {"level":"info"}
|
||||||
|
//
|
||||||
|
// It's perfectly safe to change the logging level while a program is running.
|
||||||
|
func (lvl AtomicLevel) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
type errorResponse struct {
|
||||||
|
Error string `json:"error"`
|
||||||
|
}
|
||||||
|
type payload struct {
|
||||||
|
Level *zapcore.Level `json:"level"`
|
||||||
|
}
|
||||||
|
|
||||||
|
enc := json.NewEncoder(w)
|
||||||
|
|
||||||
|
switch r.Method {
|
||||||
|
|
||||||
|
case "GET":
|
||||||
|
current := lvl.Level()
|
||||||
|
enc.Encode(payload{Level: ¤t})
|
||||||
|
|
||||||
|
case "PUT":
|
||||||
|
var req payload
|
||||||
|
|
||||||
|
if errmess := func() string {
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||||
|
return fmt.Sprintf("Request body must be well-formed JSON: %v", err)
|
||||||
|
}
|
||||||
|
if req.Level == nil {
|
||||||
|
return "Must specify a logging level."
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}(); errmess != "" {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
enc.Encode(errorResponse{Error: errmess})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
lvl.SetLevel(*req.Level)
|
||||||
|
enc.Encode(req)
|
||||||
|
|
||||||
|
default:
|
||||||
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||||
|
enc.Encode(errorResponse{
|
||||||
|
Error: "Only GET and PUT are supported.",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,131 @@
|
||||||
|
// Copyright (c) 2016 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
package zap_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
. "go.uber.org/zap"
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newHandler() (AtomicLevel, *Logger) {
|
||||||
|
lvl := NewAtomicLevel()
|
||||||
|
logger := New(zapcore.NewNopCore())
|
||||||
|
return lvl, logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertCodeOK(t testing.TB, code int) {
|
||||||
|
assert.Equal(t, http.StatusOK, code, "Unexpected response status code.")
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertCodeBadRequest(t testing.TB, code int) {
|
||||||
|
assert.Equal(t, http.StatusBadRequest, code, "Unexpected response status code.")
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertCodeMethodNotAllowed(t testing.TB, code int) {
|
||||||
|
assert.Equal(t, http.StatusMethodNotAllowed, code, "Unexpected response status code.")
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertResponse(t testing.TB, expectedLevel zapcore.Level, actualBody string) {
|
||||||
|
assert.Equal(t, fmt.Sprintf(`{"level":"%s"}`, expectedLevel)+"\n", actualBody, "Unexpected response body.")
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertJSONError(t testing.TB, body string) {
|
||||||
|
// Don't need to test exact error message, but one should be present.
|
||||||
|
var payload map[string]interface{}
|
||||||
|
require.NoError(t, json.Unmarshal([]byte(body), &payload), "Expected error response to be JSON.")
|
||||||
|
|
||||||
|
msg, ok := payload["error"]
|
||||||
|
require.True(t, ok, "Error message is an unexpected type.")
|
||||||
|
assert.NotEqual(t, "", msg, "Expected an error message in response.")
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeRequest(t testing.TB, method string, handler http.Handler, reader io.Reader) (int, string) {
|
||||||
|
ts := httptest.NewServer(handler)
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
req, err := http.NewRequest(method, ts.URL, reader)
|
||||||
|
require.NoError(t, err, "Error constructing %s request.", method)
|
||||||
|
|
||||||
|
res, err := http.DefaultClient.Do(req)
|
||||||
|
require.NoError(t, err, "Error making %s request.", method)
|
||||||
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(res.Body)
|
||||||
|
require.NoError(t, err, "Error reading request body.")
|
||||||
|
|
||||||
|
return res.StatusCode, string(body)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHTTPHandlerGetLevel(t *testing.T) {
|
||||||
|
lvl, _ := newHandler()
|
||||||
|
code, body := makeRequest(t, "GET", lvl, nil)
|
||||||
|
assertCodeOK(t, code)
|
||||||
|
assertResponse(t, lvl.Level(), body)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHTTPHandlerPutLevel(t *testing.T) {
|
||||||
|
lvl, _ := newHandler()
|
||||||
|
|
||||||
|
code, body := makeRequest(t, "PUT", lvl, strings.NewReader(`{"level":"warn"}`))
|
||||||
|
|
||||||
|
assertCodeOK(t, code)
|
||||||
|
assertResponse(t, lvl.Level(), body)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHTTPHandlerPutUnrecognizedLevel(t *testing.T) {
|
||||||
|
lvl, _ := newHandler()
|
||||||
|
code, body := makeRequest(t, "PUT", lvl, strings.NewReader(`{"level":"unrecognized-level"}`))
|
||||||
|
assertCodeBadRequest(t, code)
|
||||||
|
assertJSONError(t, body)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHTTPHandlerNotJSON(t *testing.T) {
|
||||||
|
lvl, _ := newHandler()
|
||||||
|
code, body := makeRequest(t, "PUT", lvl, strings.NewReader(`{`))
|
||||||
|
assertCodeBadRequest(t, code)
|
||||||
|
assertJSONError(t, body)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHTTPHandlerNoLevelSpecified(t *testing.T) {
|
||||||
|
lvl, _ := newHandler()
|
||||||
|
code, body := makeRequest(t, "PUT", lvl, strings.NewReader(`{}`))
|
||||||
|
assertCodeBadRequest(t, code)
|
||||||
|
assertJSONError(t, body)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHTTPHandlerMethodNotAllowed(t *testing.T) {
|
||||||
|
lvl, _ := newHandler()
|
||||||
|
code, body := makeRequest(t, "POST", lvl, strings.NewReader(`{`))
|
||||||
|
assertCodeMethodNotAllowed(t, code)
|
||||||
|
assertJSONError(t, body)
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
// Copyright (c) 2016 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
// Package bufferpool houses zap's shared internal buffer pool. Third-party
|
||||||
|
// packages can recreate the same functionality with buffers.NewPool.
|
||||||
|
package bufferpool
|
||||||
|
|
||||||
|
import "go.uber.org/zap/buffer"
|
||||||
|
|
||||||
|
var (
|
||||||
|
_pool = buffer.NewPool()
|
||||||
|
// Get retrieves a buffer from the pool, creating one if necessary.
|
||||||
|
Get = _pool.Get
|
||||||
|
)
|
|
@ -0,0 +1,44 @@
|
||||||
|
// Copyright (c) 2016 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
// Package color adds coloring functionality for TTY output.
|
||||||
|
package color
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// Foreground colors.
|
||||||
|
const (
|
||||||
|
Black Color = iota + 30
|
||||||
|
Red
|
||||||
|
Green
|
||||||
|
Yellow
|
||||||
|
Blue
|
||||||
|
Magenta
|
||||||
|
Cyan
|
||||||
|
White
|
||||||
|
)
|
||||||
|
|
||||||
|
// Color represents a text color.
|
||||||
|
type Color uint8
|
||||||
|
|
||||||
|
// Add adds the coloring to the given string.
|
||||||
|
func (c Color) Add(s string) string {
|
||||||
|
return fmt.Sprintf("\x1b[%dm%s\x1b[0m", uint8(c), s)
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
// Copyright (c) 2016 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
package color
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestColorFormatting(t *testing.T) {
|
||||||
|
assert.Equal(
|
||||||
|
t,
|
||||||
|
"\x1b[31mfoo\x1b[0m",
|
||||||
|
Red.Add("foo"),
|
||||||
|
"Unexpected colored output.",
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,64 @@
|
||||||
|
// Copyright (c) 2016 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
// Package exit provides stubs so that unit tests can exercise code that calls
|
||||||
|
// os.Exit(1).
|
||||||
|
package exit
|
||||||
|
|
||||||
|
import "os"
|
||||||
|
|
||||||
|
var real = func() { os.Exit(1) }
|
||||||
|
|
||||||
|
// Exit normally terminates the process by calling os.Exit(1). If the package
|
||||||
|
// is stubbed, it instead records a call in the testing spy.
|
||||||
|
func Exit() {
|
||||||
|
real()
|
||||||
|
}
|
||||||
|
|
||||||
|
// A StubbedExit is a testing fake for os.Exit.
|
||||||
|
type StubbedExit struct {
|
||||||
|
Exited bool
|
||||||
|
prev func()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stub substitutes a fake for the call to os.Exit(1).
|
||||||
|
func Stub() *StubbedExit {
|
||||||
|
s := &StubbedExit{prev: real}
|
||||||
|
real = s.exit
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithStub runs the supplied function with Exit stubbed. It returns the stub
|
||||||
|
// used, so that users can test whether the process would have crashed.
|
||||||
|
func WithStub(f func()) *StubbedExit {
|
||||||
|
s := Stub()
|
||||||
|
defer s.Unstub()
|
||||||
|
f()
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unstub restores the previous exit function.
|
||||||
|
func (se *StubbedExit) Unstub() {
|
||||||
|
real = se.prev
|
||||||
|
}
|
||||||
|
|
||||||
|
func (se *StubbedExit) exit() {
|
||||||
|
se.Exited = true
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
// Copyright (c) 2016 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
package exit
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestStub(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
f func()
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{Exit, true},
|
||||||
|
{func() {}, false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
s := WithStub(tt.f)
|
||||||
|
assert.Equal(t, tt.want, s.Exited, "Stub captured unexpected exit value.")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,212 @@
|
||||||
|
// Copyright (c) 2016 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
libraryNameToMarkdownName = map[string]string{
|
||||||
|
"Zap": ":zap: zap",
|
||||||
|
"Zap.Sugar": ":zap: zap (sugared)",
|
||||||
|
"stdlib.Println": "standard library",
|
||||||
|
"sirupsen/logrus": "logrus",
|
||||||
|
"go-kit/kit/log": "go-kit",
|
||||||
|
"inconshreveable/log15": "log15",
|
||||||
|
"apex/log": "apex/log",
|
||||||
|
"go.pedge.io/lion": "lion",
|
||||||
|
"rs/zerolog": "zerolog",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
flag.Parse()
|
||||||
|
if err := do(); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func do() error {
|
||||||
|
tmplData, err := getTmplData()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
data, err := ioutil.ReadAll(os.Stdin)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
t, err := template.New("tmpl").Parse(string(data))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := t.Execute(os.Stdout, tmplData); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTmplData() (*tmplData, error) {
|
||||||
|
tmplData := &tmplData{}
|
||||||
|
rows, err := getBenchmarkRows("BenchmarkAddingFields")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
tmplData.BenchmarkAddingFields = rows
|
||||||
|
rows, err = getBenchmarkRows("BenchmarkAccumulatedContext")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
tmplData.BenchmarkAccumulatedContext = rows
|
||||||
|
rows, err = getBenchmarkRows("BenchmarkWithoutFields")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
tmplData.BenchmarkWithoutFields = rows
|
||||||
|
return tmplData, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getBenchmarkRows(benchmarkName string) (string, error) {
|
||||||
|
benchmarkOutput, err := getBenchmarkOutput(benchmarkName)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
var benchmarkRows []*benchmarkRow
|
||||||
|
for libraryName := range libraryNameToMarkdownName {
|
||||||
|
benchmarkRow, err := getBenchmarkRow(benchmarkOutput, benchmarkName, libraryName)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if benchmarkRow == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
benchmarkRows = append(benchmarkRows, benchmarkRow)
|
||||||
|
}
|
||||||
|
sort.Sort(benchmarkRowsByTime(benchmarkRows))
|
||||||
|
rows := []string{
|
||||||
|
"| Package | Time | Objects Allocated |",
|
||||||
|
"| :--- | :---: | :---: |",
|
||||||
|
}
|
||||||
|
for _, benchmarkRow := range benchmarkRows {
|
||||||
|
rows = append(rows, benchmarkRow.String())
|
||||||
|
}
|
||||||
|
return strings.Join(rows, "\n"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getBenchmarkRow(input []string, benchmarkName string, libraryName string) (*benchmarkRow, error) {
|
||||||
|
line, err := findUniqueSubstring(input, fmt.Sprintf("%s/%s-", benchmarkName, libraryName))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if line == "" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
split := strings.Split(line, "\t")
|
||||||
|
if len(split) < 5 {
|
||||||
|
return nil, fmt.Errorf("unknown benchmark line: %s", line)
|
||||||
|
}
|
||||||
|
duration, err := time.ParseDuration(strings.Replace(strings.TrimSuffix(strings.TrimSpace(split[2]), "/op"), " ", "", -1))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
allocatedBytes, err := strconv.Atoi(strings.TrimSuffix(strings.TrimSpace(split[3]), " B/op"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
allocatedObjects, err := strconv.Atoi(strings.TrimSuffix(strings.TrimSpace(split[4]), " allocs/op"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &benchmarkRow{
|
||||||
|
libraryNameToMarkdownName[libraryName],
|
||||||
|
duration,
|
||||||
|
allocatedBytes,
|
||||||
|
allocatedObjects,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func findUniqueSubstring(input []string, substring string) (string, error) {
|
||||||
|
var output string
|
||||||
|
for _, line := range input {
|
||||||
|
if strings.Contains(line, substring) {
|
||||||
|
if output != "" {
|
||||||
|
return "", fmt.Errorf("input has duplicate substring %s", substring)
|
||||||
|
}
|
||||||
|
output = line
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return output, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getBenchmarkOutput(benchmarkName string) ([]string, error) {
|
||||||
|
return getOutput("go", "test", fmt.Sprintf("-bench=%s", benchmarkName), "-benchmem", "./benchmarks")
|
||||||
|
}
|
||||||
|
|
||||||
|
func getOutput(name string, arg ...string) ([]string, error) {
|
||||||
|
output, err := exec.Command(name, arg...).CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error running %s %s: %v\n%s", name, strings.Join(arg, " "), err, string(output))
|
||||||
|
}
|
||||||
|
return strings.Split(string(output), "\n"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type tmplData struct {
|
||||||
|
BenchmarkAddingFields string
|
||||||
|
BenchmarkAccumulatedContext string
|
||||||
|
BenchmarkWithoutFields string
|
||||||
|
}
|
||||||
|
|
||||||
|
type benchmarkRow struct {
|
||||||
|
Name string
|
||||||
|
Time time.Duration
|
||||||
|
AllocatedBytes int
|
||||||
|
AllocatedObjects int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *benchmarkRow) String() string {
|
||||||
|
return fmt.Sprintf("| %s | %d ns/op | %d allocs/op |", b.Name, b.Time.Nanoseconds(), b.AllocatedObjects)
|
||||||
|
}
|
||||||
|
|
||||||
|
type benchmarkRowsByTime []*benchmarkRow
|
||||||
|
|
||||||
|
func (b benchmarkRowsByTime) Len() int { return len(b) }
|
||||||
|
func (b benchmarkRowsByTime) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
|
||||||
|
func (b benchmarkRowsByTime) Less(i, j int) bool {
|
||||||
|
left, right := b[i], b[j]
|
||||||
|
leftZap, rightZap := strings.Contains(left.Name, "zap"), strings.Contains(right.Name, "zap")
|
||||||
|
|
||||||
|
// If neither benchmark is for zap or both are, sort by time.
|
||||||
|
if !(leftZap || rightZap) || (leftZap && rightZap) {
|
||||||
|
return left.Time.Nanoseconds() < right.Time.Nanoseconds()
|
||||||
|
}
|
||||||
|
// Sort zap benchmark first.
|
||||||
|
return leftZap
|
||||||
|
}
|
|
@ -0,0 +1,132 @@
|
||||||
|
// Copyright (c) 2016 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
package zap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go.uber.org/atomic"
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// DebugLevel logs are typically voluminous, and are usually disabled in
|
||||||
|
// production.
|
||||||
|
DebugLevel = zapcore.DebugLevel
|
||||||
|
// InfoLevel is the default logging priority.
|
||||||
|
InfoLevel = zapcore.InfoLevel
|
||||||
|
// WarnLevel logs are more important than Info, but don't need individual
|
||||||
|
// human review.
|
||||||
|
WarnLevel = zapcore.WarnLevel
|
||||||
|
// ErrorLevel logs are high-priority. If an application is running smoothly,
|
||||||
|
// it shouldn't generate any error-level logs.
|
||||||
|
ErrorLevel = zapcore.ErrorLevel
|
||||||
|
// DPanicLevel logs are particularly important errors. In development the
|
||||||
|
// logger panics after writing the message.
|
||||||
|
DPanicLevel = zapcore.DPanicLevel
|
||||||
|
// PanicLevel logs a message, then panics.
|
||||||
|
PanicLevel = zapcore.PanicLevel
|
||||||
|
// FatalLevel logs a message, then calls os.Exit(1).
|
||||||
|
FatalLevel = zapcore.FatalLevel
|
||||||
|
)
|
||||||
|
|
||||||
|
// LevelEnablerFunc is a convenient way to implement zapcore.LevelEnabler with
|
||||||
|
// an anonymous function.
|
||||||
|
//
|
||||||
|
// It's particularly useful when splitting log output between different
|
||||||
|
// outputs (e.g., standard error and standard out). For sample code, see the
|
||||||
|
// package-level AdvancedConfiguration example.
|
||||||
|
type LevelEnablerFunc func(zapcore.Level) bool
|
||||||
|
|
||||||
|
// Enabled calls the wrapped function.
|
||||||
|
func (f LevelEnablerFunc) Enabled(lvl zapcore.Level) bool { return f(lvl) }
|
||||||
|
|
||||||
|
// An AtomicLevel is an atomically changeable, dynamic logging level. It lets
|
||||||
|
// you safely change the log level of a tree of loggers (the root logger and
|
||||||
|
// any children created by adding context) at runtime.
|
||||||
|
//
|
||||||
|
// The AtomicLevel itself is an http.Handler that serves a JSON endpoint to
|
||||||
|
// alter its level.
|
||||||
|
//
|
||||||
|
// AtomicLevels must be created with the NewAtomicLevel constructor to allocate
|
||||||
|
// their internal atomic pointer.
|
||||||
|
type AtomicLevel struct {
|
||||||
|
l *atomic.Int32
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAtomicLevel creates an AtomicLevel with InfoLevel and above logging
|
||||||
|
// enabled.
|
||||||
|
func NewAtomicLevel() AtomicLevel {
|
||||||
|
return AtomicLevel{
|
||||||
|
l: atomic.NewInt32(int32(InfoLevel)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAtomicLevelAt is a convienence function that creates an AtomicLevel
|
||||||
|
// and then calls SetLevel with the given level.
|
||||||
|
func NewAtomicLevelAt(l zapcore.Level) AtomicLevel {
|
||||||
|
a := NewAtomicLevel()
|
||||||
|
a.SetLevel(l)
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enabled implements the zapcore.LevelEnabler interface, which allows the
|
||||||
|
// AtomicLevel to be used in place of traditional static levels.
|
||||||
|
func (lvl AtomicLevel) Enabled(l zapcore.Level) bool {
|
||||||
|
return lvl.Level().Enabled(l)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Level returns the minimum enabled log level.
|
||||||
|
func (lvl AtomicLevel) Level() zapcore.Level {
|
||||||
|
return zapcore.Level(int8(lvl.l.Load()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLevel alters the logging level.
|
||||||
|
func (lvl AtomicLevel) SetLevel(l zapcore.Level) {
|
||||||
|
lvl.l.Store(int32(l))
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the string representation of the underlying Level.
|
||||||
|
func (lvl AtomicLevel) String() string {
|
||||||
|
return lvl.Level().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalText unmarshals the text to an AtomicLevel. It uses the same text
|
||||||
|
// representations as the static zapcore.Levels ("debug", "info", "warn",
|
||||||
|
// "error", "dpanic", "panic", and "fatal").
|
||||||
|
func (lvl *AtomicLevel) UnmarshalText(text []byte) error {
|
||||||
|
if lvl.l == nil {
|
||||||
|
lvl.l = &atomic.Int32{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var l zapcore.Level
|
||||||
|
if err := l.UnmarshalText(text); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
lvl.SetLevel(l)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalText marshals the AtomicLevel to a byte slice. It uses the same
|
||||||
|
// text representation as the static zapcore.Levels ("debug", "info", "warn",
|
||||||
|
// "error", "dpanic", "panic", and "fatal").
|
||||||
|
func (lvl AtomicLevel) MarshalText() (text []byte, err error) {
|
||||||
|
return lvl.Level().MarshalText()
|
||||||
|
}
|
|
@ -0,0 +1,117 @@
|
||||||
|
// Copyright (c) 2016 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
package zap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLevelEnablerFunc(t *testing.T) {
|
||||||
|
enab := LevelEnablerFunc(func(l zapcore.Level) bool { return l == zapcore.InfoLevel })
|
||||||
|
tests := []struct {
|
||||||
|
level zapcore.Level
|
||||||
|
enabled bool
|
||||||
|
}{
|
||||||
|
{DebugLevel, false},
|
||||||
|
{InfoLevel, true},
|
||||||
|
{WarnLevel, false},
|
||||||
|
{ErrorLevel, false},
|
||||||
|
{DPanicLevel, false},
|
||||||
|
{PanicLevel, false},
|
||||||
|
{FatalLevel, false},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
assert.Equal(t, tt.enabled, enab.Enabled(tt.level), "Unexpected result applying LevelEnablerFunc to %s", tt.level)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewAtomicLevel(t *testing.T) {
|
||||||
|
lvl := NewAtomicLevel()
|
||||||
|
assert.Equal(t, InfoLevel, lvl.Level(), "Unexpected initial level.")
|
||||||
|
lvl.SetLevel(ErrorLevel)
|
||||||
|
assert.Equal(t, ErrorLevel, lvl.Level(), "Unexpected level after SetLevel.")
|
||||||
|
lvl = NewAtomicLevelAt(WarnLevel)
|
||||||
|
assert.Equal(t, WarnLevel, lvl.Level(), "Unexpected level after SetLevel.")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAtomicLevelMutation(t *testing.T) {
|
||||||
|
lvl := NewAtomicLevel()
|
||||||
|
lvl.SetLevel(WarnLevel)
|
||||||
|
// Trigger races for non-atomic level mutations.
|
||||||
|
proceed := make(chan struct{})
|
||||||
|
wg := &sync.WaitGroup{}
|
||||||
|
runConcurrently(10, 100, wg, func() {
|
||||||
|
<-proceed
|
||||||
|
assert.Equal(t, WarnLevel, lvl.Level())
|
||||||
|
})
|
||||||
|
runConcurrently(10, 100, wg, func() {
|
||||||
|
<-proceed
|
||||||
|
lvl.SetLevel(WarnLevel)
|
||||||
|
})
|
||||||
|
close(proceed)
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAtomicLevelText(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
text string
|
||||||
|
expect zapcore.Level
|
||||||
|
err bool
|
||||||
|
}{
|
||||||
|
{"debug", DebugLevel, false},
|
||||||
|
{"info", InfoLevel, false},
|
||||||
|
{"", InfoLevel, false},
|
||||||
|
{"warn", WarnLevel, false},
|
||||||
|
{"error", ErrorLevel, false},
|
||||||
|
{"dpanic", DPanicLevel, false},
|
||||||
|
{"panic", PanicLevel, false},
|
||||||
|
{"fatal", FatalLevel, false},
|
||||||
|
{"foobar", InfoLevel, true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
var lvl AtomicLevel
|
||||||
|
// Test both initial unmarshaling and overwriting existing value.
|
||||||
|
for i := 0; i < 2; i++ {
|
||||||
|
if tt.err {
|
||||||
|
assert.Error(t, lvl.UnmarshalText([]byte(tt.text)), "Expected unmarshaling %q to fail.", tt.text)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, lvl.UnmarshalText([]byte(tt.text)), "Expected unmarshaling %q to succeed.", tt.text)
|
||||||
|
}
|
||||||
|
assert.Equal(t, tt.expect, lvl.Level(), "Unexpected level after unmarshaling.")
|
||||||
|
lvl.SetLevel(InfoLevel)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test marshalling
|
||||||
|
if tt.text != "" && !tt.err {
|
||||||
|
lvl.SetLevel(tt.expect)
|
||||||
|
marshaled, err := lvl.MarshalText()
|
||||||
|
assert.NoError(t, err, `Unexpected error marshalling level "%v" to text.`, tt.expect)
|
||||||
|
assert.Equal(t, tt.text, string(marshaled), "Expected marshaled text to match")
|
||||||
|
assert.Equal(t, tt.text, lvl.String(), "Expected Stringer call to match")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,305 @@
|
||||||
|
// Copyright (c) 2016 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
package zap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A Logger provides fast, leveled, structured logging. All methods are safe
|
||||||
|
// for concurrent use.
|
||||||
|
//
|
||||||
|
// The Logger is designed for contexts in which every microsecond and every
|
||||||
|
// allocation matters, so its API intentionally favors performance and type
|
||||||
|
// safety over brevity. For most applications, the SugaredLogger strikes a
|
||||||
|
// better balance between performance and ergonomics.
|
||||||
|
type Logger struct {
|
||||||
|
core zapcore.Core
|
||||||
|
|
||||||
|
development bool
|
||||||
|
name string
|
||||||
|
errorOutput zapcore.WriteSyncer
|
||||||
|
|
||||||
|
addCaller bool
|
||||||
|
addStack zapcore.LevelEnabler
|
||||||
|
|
||||||
|
callerSkip int
|
||||||
|
}
|
||||||
|
|
||||||
|
// New constructs a new Logger from the provided zapcore.Core and Options. If
|
||||||
|
// the passed zapcore.Core is nil, it falls back to using a no-op
|
||||||
|
// implementation.
|
||||||
|
//
|
||||||
|
// This is the most flexible way to construct a Logger, but also the most
|
||||||
|
// verbose. For typical use cases, the highly-opinionated presets
|
||||||
|
// (NewProduction, NewDevelopment, and NewExample) or the Config struct are
|
||||||
|
// more convenient.
|
||||||
|
//
|
||||||
|
// For sample code, see the package-level AdvancedConfiguration example.
|
||||||
|
func New(core zapcore.Core, options ...Option) *Logger {
|
||||||
|
if core == nil {
|
||||||
|
return NewNop()
|
||||||
|
}
|
||||||
|
log := &Logger{
|
||||||
|
core: core,
|
||||||
|
errorOutput: zapcore.Lock(os.Stderr),
|
||||||
|
addStack: zapcore.FatalLevel + 1,
|
||||||
|
}
|
||||||
|
return log.WithOptions(options...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewNop returns a no-op Logger. It never writes out logs or internal errors,
|
||||||
|
// and it never runs user-defined hooks.
|
||||||
|
//
|
||||||
|
// Using WithOptions to replace the Core or error output of a no-op Logger can
|
||||||
|
// re-enable logging.
|
||||||
|
func NewNop() *Logger {
|
||||||
|
return &Logger{
|
||||||
|
core: zapcore.NewNopCore(),
|
||||||
|
errorOutput: zapcore.AddSync(ioutil.Discard),
|
||||||
|
addStack: zapcore.FatalLevel + 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewProduction builds a sensible production Logger that writes InfoLevel and
|
||||||
|
// above logs to standard error as JSON.
|
||||||
|
//
|
||||||
|
// It's a shortcut for NewProductionConfig().Build(...Option).
|
||||||
|
func NewProduction(options ...Option) (*Logger, error) {
|
||||||
|
return NewProductionConfig().Build(options...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDevelopment builds a development Logger that writes DebugLevel and above
|
||||||
|
// logs to standard error in a human-friendly format.
|
||||||
|
//
|
||||||
|
// It's a shortcut for NewDevelopmentConfig().Build(...Option).
|
||||||
|
func NewDevelopment(options ...Option) (*Logger, error) {
|
||||||
|
return NewDevelopmentConfig().Build(options...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewExample builds a Logger that's designed for use in zap's testable
|
||||||
|
// examples. It writes DebugLevel and above logs to standard out as JSON, but
|
||||||
|
// omits the timestamp and calling function to keep example output
|
||||||
|
// short and deterministic.
|
||||||
|
func NewExample(options ...Option) *Logger {
|
||||||
|
encoderCfg := zapcore.EncoderConfig{
|
||||||
|
MessageKey: "msg",
|
||||||
|
LevelKey: "level",
|
||||||
|
NameKey: "logger",
|
||||||
|
EncodeLevel: zapcore.LowercaseLevelEncoder,
|
||||||
|
EncodeTime: zapcore.ISO8601TimeEncoder,
|
||||||
|
EncodeDuration: zapcore.StringDurationEncoder,
|
||||||
|
}
|
||||||
|
core := zapcore.NewCore(zapcore.NewJSONEncoder(encoderCfg), os.Stdout, DebugLevel)
|
||||||
|
return New(core).WithOptions(options...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sugar wraps the Logger to provide a more ergonomic, but slightly slower,
|
||||||
|
// API. Sugaring a Logger is quite inexpensive, so it's reasonable for a
|
||||||
|
// single application to use both Loggers and SugaredLoggers, converting
|
||||||
|
// between them on the boundaries of performance-sensitive code.
|
||||||
|
func (log *Logger) Sugar() *SugaredLogger {
|
||||||
|
core := log.clone()
|
||||||
|
core.callerSkip += 2
|
||||||
|
return &SugaredLogger{core}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Named adds a new path segment to the logger's name. Segments are joined by
|
||||||
|
// periods. By default, Loggers are unnamed.
|
||||||
|
func (log *Logger) Named(s string) *Logger {
|
||||||
|
if s == "" {
|
||||||
|
return log
|
||||||
|
}
|
||||||
|
l := log.clone()
|
||||||
|
if log.name == "" {
|
||||||
|
l.name = s
|
||||||
|
} else {
|
||||||
|
l.name = strings.Join([]string{l.name, s}, ".")
|
||||||
|
}
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithOptions clones the current Logger, applies the supplied Options, and
|
||||||
|
// returns the resulting Logger. It's safe to use concurrently.
|
||||||
|
func (log *Logger) WithOptions(opts ...Option) *Logger {
|
||||||
|
c := log.clone()
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt.apply(c)
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// With creates a child logger and adds structured context to it. Fields added
|
||||||
|
// to the child don't affect the parent, and vice versa.
|
||||||
|
func (log *Logger) With(fields ...zapcore.Field) *Logger {
|
||||||
|
if len(fields) == 0 {
|
||||||
|
return log
|
||||||
|
}
|
||||||
|
l := log.clone()
|
||||||
|
l.core = l.core.With(fields)
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check returns a CheckedEntry if logging a message at the specified level
|
||||||
|
// is enabled. It's a completely optional optimization; in high-performance
|
||||||
|
// applications, Check can help avoid allocating a slice to hold fields.
|
||||||
|
func (log *Logger) Check(lvl zapcore.Level, msg string) *zapcore.CheckedEntry {
|
||||||
|
return log.check(lvl, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug logs a message at DebugLevel. The message includes any fields passed
|
||||||
|
// at the log site, as well as any fields accumulated on the logger.
|
||||||
|
func (log *Logger) Debug(msg string, fields ...zapcore.Field) {
|
||||||
|
if ce := log.check(DebugLevel, msg); ce != nil {
|
||||||
|
ce.Write(fields...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Info logs a message at InfoLevel. The message includes any fields passed
|
||||||
|
// at the log site, as well as any fields accumulated on the logger.
|
||||||
|
func (log *Logger) Info(msg string, fields ...zapcore.Field) {
|
||||||
|
if ce := log.check(InfoLevel, msg); ce != nil {
|
||||||
|
ce.Write(fields...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warn logs a message at WarnLevel. The message includes any fields passed
|
||||||
|
// at the log site, as well as any fields accumulated on the logger.
|
||||||
|
func (log *Logger) Warn(msg string, fields ...zapcore.Field) {
|
||||||
|
if ce := log.check(WarnLevel, msg); ce != nil {
|
||||||
|
ce.Write(fields...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error logs a message at ErrorLevel. The message includes any fields passed
|
||||||
|
// at the log site, as well as any fields accumulated on the logger.
|
||||||
|
func (log *Logger) Error(msg string, fields ...zapcore.Field) {
|
||||||
|
if ce := log.check(ErrorLevel, msg); ce != nil {
|
||||||
|
ce.Write(fields...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DPanic logs a message at DPanicLevel. The message includes any fields
|
||||||
|
// passed at the log site, as well as any fields accumulated on the logger.
|
||||||
|
//
|
||||||
|
// If the logger is in development mode, it then panics (DPanic means
|
||||||
|
// "development panic"). This is useful for catching errors that are
|
||||||
|
// recoverable, but shouldn't ever happen.
|
||||||
|
func (log *Logger) DPanic(msg string, fields ...zapcore.Field) {
|
||||||
|
if ce := log.check(DPanicLevel, msg); ce != nil {
|
||||||
|
ce.Write(fields...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Panic logs a message at PanicLevel. The message includes any fields passed
|
||||||
|
// at the log site, as well as any fields accumulated on the logger.
|
||||||
|
//
|
||||||
|
// The logger then panics, even if logging at PanicLevel is disabled.
|
||||||
|
func (log *Logger) Panic(msg string, fields ...zapcore.Field) {
|
||||||
|
if ce := log.check(PanicLevel, msg); ce != nil {
|
||||||
|
ce.Write(fields...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fatal logs a message at FatalLevel. The message includes any fields passed
|
||||||
|
// at the log site, as well as any fields accumulated on the logger.
|
||||||
|
//
|
||||||
|
// The logger then calls os.Exit(1), even if logging at FatalLevel is
|
||||||
|
// disabled.
|
||||||
|
func (log *Logger) Fatal(msg string, fields ...zapcore.Field) {
|
||||||
|
if ce := log.check(FatalLevel, msg); ce != nil {
|
||||||
|
ce.Write(fields...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sync calls the underlying Core's Sync method, flushing any buffered log
|
||||||
|
// entries. Applications should take care to call Sync before exiting.
|
||||||
|
func (log *Logger) Sync() error {
|
||||||
|
return log.core.Sync()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Core returns the Logger's underlying zapcore.Core.
|
||||||
|
func (log *Logger) Core() zapcore.Core {
|
||||||
|
return log.core
|
||||||
|
}
|
||||||
|
|
||||||
|
func (log *Logger) clone() *Logger {
|
||||||
|
copy := *log
|
||||||
|
return ©
|
||||||
|
}
|
||||||
|
|
||||||
|
func (log *Logger) check(lvl zapcore.Level, msg string) *zapcore.CheckedEntry {
|
||||||
|
// check must always be called directly by a method in the Logger interface
|
||||||
|
// (e.g., Check, Info, Fatal).
|
||||||
|
const callerSkipOffset = 2
|
||||||
|
|
||||||
|
// Create basic checked entry thru the core; this will be non-nil if the
|
||||||
|
// log message will actually be written somewhere.
|
||||||
|
ent := zapcore.Entry{
|
||||||
|
LoggerName: log.name,
|
||||||
|
Time: time.Now(),
|
||||||
|
Level: lvl,
|
||||||
|
Message: msg,
|
||||||
|
}
|
||||||
|
ce := log.core.Check(ent, nil)
|
||||||
|
willWrite := ce != nil
|
||||||
|
|
||||||
|
// Set up any required terminal behavior.
|
||||||
|
switch ent.Level {
|
||||||
|
case zapcore.PanicLevel:
|
||||||
|
ce = ce.Should(ent, zapcore.WriteThenPanic)
|
||||||
|
case zapcore.FatalLevel:
|
||||||
|
ce = ce.Should(ent, zapcore.WriteThenFatal)
|
||||||
|
case zapcore.DPanicLevel:
|
||||||
|
if log.development {
|
||||||
|
ce = ce.Should(ent, zapcore.WriteThenPanic)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only do further annotation if we're going to write this message; checked
|
||||||
|
// entries that exist only for terminal behavior don't benefit from
|
||||||
|
// annotation.
|
||||||
|
if !willWrite {
|
||||||
|
return ce
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
fmt.Fprintf(log.errorOutput, "%v Logger.check error: failed to get caller\n", time.Now().UTC())
|
||||||
|
log.errorOutput.Sync()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if log.addStack.Enabled(ce.Entry.Level) {
|
||||||
|
ce.Entry.Stack = Stack("").String
|
||||||
|
}
|
||||||
|
|
||||||
|
return ce
|
||||||
|
}
|
|
@ -0,0 +1,222 @@
|
||||||
|
// Copyright (c) 2016 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
package zap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
|
"go.uber.org/zap/zaptest"
|
||||||
|
)
|
||||||
|
|
||||||
|
type user struct {
|
||||||
|
Name string
|
||||||
|
Email string
|
||||||
|
CreatedAt time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *user) MarshalLogObject(enc zapcore.ObjectEncoder) error {
|
||||||
|
enc.AddString("name", u.Name)
|
||||||
|
enc.AddString("email", u.Email)
|
||||||
|
enc.AddInt64("created_at", u.CreatedAt.UnixNano())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var _jane = &user{
|
||||||
|
Name: "Jane Doe",
|
||||||
|
Email: "jane@test.com",
|
||||||
|
CreatedAt: time.Date(1980, 1, 1, 12, 0, 0, 0, time.UTC),
|
||||||
|
}
|
||||||
|
|
||||||
|
func withBenchedLogger(b *testing.B, f func(*Logger)) {
|
||||||
|
logger := New(
|
||||||
|
zapcore.NewCore(
|
||||||
|
zapcore.NewJSONEncoder(NewProductionConfig().EncoderConfig),
|
||||||
|
&zaptest.Discarder{},
|
||||||
|
DebugLevel,
|
||||||
|
))
|
||||||
|
b.ResetTimer()
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
f(logger)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkNoContext(b *testing.B) {
|
||||||
|
withBenchedLogger(b, func(log *Logger) {
|
||||||
|
log.Info("No context.")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkBoolField(b *testing.B) {
|
||||||
|
withBenchedLogger(b, func(log *Logger) {
|
||||||
|
log.Info("Boolean.", Bool("foo", true))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkByteStringField(b *testing.B) {
|
||||||
|
val := []byte("bar")
|
||||||
|
withBenchedLogger(b, func(log *Logger) {
|
||||||
|
log.Info("ByteString.", ByteString("foo", val))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkFloat64Field(b *testing.B) {
|
||||||
|
withBenchedLogger(b, func(log *Logger) {
|
||||||
|
log.Info("Floating point.", Float64("foo", 3.14))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkIntField(b *testing.B) {
|
||||||
|
withBenchedLogger(b, func(log *Logger) {
|
||||||
|
log.Info("Integer.", Int("foo", 42))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkInt64Field(b *testing.B) {
|
||||||
|
withBenchedLogger(b, func(log *Logger) {
|
||||||
|
log.Info("64-bit integer.", Int64("foo", 42))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkStringField(b *testing.B) {
|
||||||
|
withBenchedLogger(b, func(log *Logger) {
|
||||||
|
log.Info("Strings.", String("foo", "bar"))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkStringerField(b *testing.B) {
|
||||||
|
withBenchedLogger(b, func(log *Logger) {
|
||||||
|
log.Info("Level.", Stringer("foo", InfoLevel))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkTimeField(b *testing.B) {
|
||||||
|
t := time.Unix(0, 0)
|
||||||
|
withBenchedLogger(b, func(log *Logger) {
|
||||||
|
log.Info("Time.", Time("foo", t))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkDurationField(b *testing.B) {
|
||||||
|
withBenchedLogger(b, func(log *Logger) {
|
||||||
|
log.Info("Duration", Duration("foo", time.Second))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkErrorField(b *testing.B) {
|
||||||
|
err := errors.New("egad")
|
||||||
|
withBenchedLogger(b, func(log *Logger) {
|
||||||
|
log.Info("Error.", Error(err))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkErrorsField(b *testing.B) {
|
||||||
|
errs := []error{
|
||||||
|
errors.New("egad"),
|
||||||
|
errors.New("oh no"),
|
||||||
|
errors.New("dear me"),
|
||||||
|
errors.New("such fail"),
|
||||||
|
}
|
||||||
|
withBenchedLogger(b, func(log *Logger) {
|
||||||
|
log.Info("Errors.", Errors("errors", errs))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkStackField(b *testing.B) {
|
||||||
|
withBenchedLogger(b, func(log *Logger) {
|
||||||
|
log.Info("Error.", Stack("stacktrace"))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkObjectField(b *testing.B) {
|
||||||
|
withBenchedLogger(b, func(log *Logger) {
|
||||||
|
log.Info("Arbitrary ObjectMarshaler.", Object("user", _jane))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkReflectField(b *testing.B) {
|
||||||
|
withBenchedLogger(b, func(log *Logger) {
|
||||||
|
log.Info("Reflection-based serialization.", Reflect("user", _jane))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkAddCallerHook(b *testing.B) {
|
||||||
|
logger := New(
|
||||||
|
zapcore.NewCore(
|
||||||
|
zapcore.NewJSONEncoder(NewProductionConfig().EncoderConfig),
|
||||||
|
&zaptest.Discarder{},
|
||||||
|
InfoLevel,
|
||||||
|
),
|
||||||
|
AddCaller(),
|
||||||
|
)
|
||||||
|
b.ResetTimer()
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
logger.Info("Caller.")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Benchmark10Fields(b *testing.B) {
|
||||||
|
withBenchedLogger(b, func(log *Logger) {
|
||||||
|
log.Info("Ten fields, passed at the log site.",
|
||||||
|
Int("one", 1),
|
||||||
|
Int("two", 2),
|
||||||
|
Int("three", 3),
|
||||||
|
Int("four", 4),
|
||||||
|
Int("five", 5),
|
||||||
|
Int("six", 6),
|
||||||
|
Int("seven", 7),
|
||||||
|
Int("eight", 8),
|
||||||
|
Int("nine", 9),
|
||||||
|
Int("ten", 10),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Benchmark100Fields(b *testing.B) {
|
||||||
|
const batchSize = 50
|
||||||
|
logger := New(zapcore.NewCore(
|
||||||
|
zapcore.NewJSONEncoder(NewProductionConfig().EncoderConfig),
|
||||||
|
&zaptest.Discarder{},
|
||||||
|
DebugLevel,
|
||||||
|
))
|
||||||
|
|
||||||
|
// Don't include allocating these helper slices in the benchmark. Since
|
||||||
|
// access to them isn't synchronized, we can't run the benchmark in
|
||||||
|
// parallel.
|
||||||
|
first := make([]zapcore.Field, batchSize)
|
||||||
|
second := make([]zapcore.Field, batchSize)
|
||||||
|
b.ResetTimer()
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
for i := 0; i < batchSize; i++ {
|
||||||
|
// We're duplicating keys, but that doesn't affect performance.
|
||||||
|
first[i] = Int("foo", i)
|
||||||
|
second[i] = Int("foo", i+batchSize)
|
||||||
|
}
|
||||||
|
logger.With(first...).Info("Child loggers with lots of context.", second...)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,432 @@
|
||||||
|
// Copyright (c) 2016 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
package zap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"go.uber.org/zap/internal/exit"
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
|
"go.uber.org/zap/zaptest"
|
||||||
|
"go.uber.org/zap/zaptest/observer"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"go.uber.org/atomic"
|
||||||
|
)
|
||||||
|
|
||||||
|
func makeCountingHook() (func(zapcore.Entry) error, *atomic.Int64) {
|
||||||
|
count := &atomic.Int64{}
|
||||||
|
h := func(zapcore.Entry) error {
|
||||||
|
count.Inc()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return h, count
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoggerAtomicLevel(t *testing.T) {
|
||||||
|
// Test that the dynamic level applies to all ancestors and descendants.
|
||||||
|
dl := NewAtomicLevel()
|
||||||
|
|
||||||
|
withLogger(t, dl, nil, func(grandparent *Logger, _ *observer.ObservedLogs) {
|
||||||
|
parent := grandparent.With(Int("generation", 1))
|
||||||
|
child := parent.With(Int("generation", 2))
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
setLevel zapcore.Level
|
||||||
|
testLevel zapcore.Level
|
||||||
|
enabled bool
|
||||||
|
}{
|
||||||
|
{DebugLevel, DebugLevel, true},
|
||||||
|
{InfoLevel, DebugLevel, false},
|
||||||
|
{WarnLevel, PanicLevel, true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
dl.SetLevel(tt.setLevel)
|
||||||
|
for _, logger := range []*Logger{grandparent, parent, child} {
|
||||||
|
if tt.enabled {
|
||||||
|
assert.NotNil(
|
||||||
|
t,
|
||||||
|
logger.Check(tt.testLevel, ""),
|
||||||
|
"Expected level %s to be enabled after setting level %s.", tt.testLevel, tt.setLevel,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
assert.Nil(
|
||||||
|
t,
|
||||||
|
logger.Check(tt.testLevel, ""),
|
||||||
|
"Expected level %s to be enabled after setting level %s.", tt.testLevel, tt.setLevel,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoggerInitialFields(t *testing.T) {
|
||||||
|
fieldOpts := opts(Fields(Int("foo", 42), String("bar", "baz")))
|
||||||
|
withLogger(t, DebugLevel, fieldOpts, func(logger *Logger, logs *observer.ObservedLogs) {
|
||||||
|
logger.Info("")
|
||||||
|
assert.Equal(
|
||||||
|
t,
|
||||||
|
observer.LoggedEntry{Context: []zapcore.Field{Int("foo", 42), String("bar", "baz")}},
|
||||||
|
logs.AllUntimed()[0],
|
||||||
|
"Unexpected output with initial fields set.",
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoggerWith(t *testing.T) {
|
||||||
|
fieldOpts := opts(Fields(Int("foo", 42)))
|
||||||
|
withLogger(t, DebugLevel, fieldOpts, func(logger *Logger, logs *observer.ObservedLogs) {
|
||||||
|
// Child loggers should have copy-on-write semantics, so two children
|
||||||
|
// shouldn't stomp on each other's fields or affect the parent's fields.
|
||||||
|
logger.With(String("one", "two")).Info("")
|
||||||
|
logger.With(String("three", "four")).Info("")
|
||||||
|
logger.Info("")
|
||||||
|
|
||||||
|
assert.Equal(t, []observer.LoggedEntry{
|
||||||
|
{Context: []zapcore.Field{Int("foo", 42), String("one", "two")}},
|
||||||
|
{Context: []zapcore.Field{Int("foo", 42), String("three", "four")}},
|
||||||
|
{Context: []zapcore.Field{Int("foo", 42)}},
|
||||||
|
}, logs.AllUntimed(), "Unexpected cross-talk between child loggers.")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoggerLogPanic(t *testing.T) {
|
||||||
|
for _, tt := range []struct {
|
||||||
|
do func(*Logger)
|
||||||
|
should bool
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{func(logger *Logger) { logger.Check(PanicLevel, "bar").Write() }, true, "bar"},
|
||||||
|
{func(logger *Logger) { logger.Panic("baz") }, true, "baz"},
|
||||||
|
} {
|
||||||
|
withLogger(t, DebugLevel, nil, func(logger *Logger, logs *observer.ObservedLogs) {
|
||||||
|
if tt.should {
|
||||||
|
assert.Panics(t, func() { tt.do(logger) }, "Expected panic")
|
||||||
|
} else {
|
||||||
|
assert.NotPanics(t, func() { tt.do(logger) }, "Expected no panic")
|
||||||
|
}
|
||||||
|
|
||||||
|
output := logs.AllUntimed()
|
||||||
|
assert.Equal(t, 1, len(output), "Unexpected number of logs.")
|
||||||
|
assert.Equal(t, 0, len(output[0].Context), "Unexpected context on first log.")
|
||||||
|
assert.Equal(
|
||||||
|
t,
|
||||||
|
zapcore.Entry{Message: tt.expected, Level: PanicLevel},
|
||||||
|
output[0].Entry,
|
||||||
|
"Unexpected output from panic-level Log.",
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoggerLogFatal(t *testing.T) {
|
||||||
|
for _, tt := range []struct {
|
||||||
|
do func(*Logger)
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{func(logger *Logger) { logger.Check(FatalLevel, "bar").Write() }, "bar"},
|
||||||
|
{func(logger *Logger) { logger.Fatal("baz") }, "baz"},
|
||||||
|
} {
|
||||||
|
withLogger(t, DebugLevel, nil, func(logger *Logger, logs *observer.ObservedLogs) {
|
||||||
|
stub := exit.WithStub(func() {
|
||||||
|
tt.do(logger)
|
||||||
|
})
|
||||||
|
assert.True(t, stub.Exited, "Expected Fatal logger call to terminate process.")
|
||||||
|
output := logs.AllUntimed()
|
||||||
|
assert.Equal(t, 1, len(output), "Unexpected number of logs.")
|
||||||
|
assert.Equal(t, 0, len(output[0].Context), "Unexpected context on first log.")
|
||||||
|
assert.Equal(
|
||||||
|
t,
|
||||||
|
zapcore.Entry{Message: tt.expected, Level: FatalLevel},
|
||||||
|
output[0].Entry,
|
||||||
|
"Unexpected output from fatal-level Log.",
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoggerLeveledMethods(t *testing.T) {
|
||||||
|
withLogger(t, DebugLevel, nil, func(logger *Logger, logs *observer.ObservedLogs) {
|
||||||
|
tests := []struct {
|
||||||
|
method func(string, ...zapcore.Field)
|
||||||
|
expectedLevel zapcore.Level
|
||||||
|
}{
|
||||||
|
{logger.Debug, DebugLevel},
|
||||||
|
{logger.Info, InfoLevel},
|
||||||
|
{logger.Warn, WarnLevel},
|
||||||
|
{logger.Error, ErrorLevel},
|
||||||
|
{logger.DPanic, DPanicLevel},
|
||||||
|
}
|
||||||
|
for i, tt := range tests {
|
||||||
|
tt.method("")
|
||||||
|
output := logs.AllUntimed()
|
||||||
|
assert.Equal(t, i+1, len(output), "Unexpected number of logs.")
|
||||||
|
assert.Equal(t, 0, len(output[i].Context), "Unexpected context on first log.")
|
||||||
|
assert.Equal(
|
||||||
|
t,
|
||||||
|
zapcore.Entry{Level: tt.expectedLevel},
|
||||||
|
output[i].Entry,
|
||||||
|
"Unexpected output from %s-level logger method.", tt.expectedLevel)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoggerAlwaysPanics(t *testing.T) {
|
||||||
|
// Users can disable writing out panic-level logs, but calls to logger.Panic()
|
||||||
|
// should still call panic().
|
||||||
|
withLogger(t, FatalLevel, nil, func(logger *Logger, logs *observer.ObservedLogs) {
|
||||||
|
msg := "Even if output is disabled, logger.Panic should always panic."
|
||||||
|
assert.Panics(t, func() { logger.Panic("foo") }, msg)
|
||||||
|
assert.Panics(t, func() {
|
||||||
|
if ce := logger.Check(PanicLevel, "foo"); ce != nil {
|
||||||
|
ce.Write()
|
||||||
|
}
|
||||||
|
}, msg)
|
||||||
|
assert.Equal(t, 0, logs.Len(), "Panics shouldn't be written out if PanicLevel is disabled.")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoggerAlwaysFatals(t *testing.T) {
|
||||||
|
// Users can disable writing out fatal-level logs, but calls to logger.Fatal()
|
||||||
|
// should still terminate the process.
|
||||||
|
withLogger(t, FatalLevel+1, nil, func(logger *Logger, logs *observer.ObservedLogs) {
|
||||||
|
stub := exit.WithStub(func() { logger.Fatal("") })
|
||||||
|
assert.True(t, stub.Exited, "Expected calls to logger.Fatal to terminate process.")
|
||||||
|
|
||||||
|
stub = exit.WithStub(func() {
|
||||||
|
if ce := logger.Check(FatalLevel, ""); ce != nil {
|
||||||
|
ce.Write()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
assert.True(t, stub.Exited, "Expected calls to logger.Check(FatalLevel, ...) to terminate process.")
|
||||||
|
|
||||||
|
assert.Equal(t, 0, logs.Len(), "Shouldn't write out logs when fatal-level logging is disabled.")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoggerDPanic(t *testing.T) {
|
||||||
|
withLogger(t, DebugLevel, nil, func(logger *Logger, logs *observer.ObservedLogs) {
|
||||||
|
assert.NotPanics(t, func() { logger.DPanic("") })
|
||||||
|
assert.Equal(
|
||||||
|
t,
|
||||||
|
[]observer.LoggedEntry{{Entry: zapcore.Entry{Level: DPanicLevel}, Context: []zapcore.Field{}}},
|
||||||
|
logs.AllUntimed(),
|
||||||
|
"Unexpected log output from DPanic in production mode.",
|
||||||
|
)
|
||||||
|
})
|
||||||
|
withLogger(t, DebugLevel, opts(Development()), func(logger *Logger, logs *observer.ObservedLogs) {
|
||||||
|
assert.Panics(t, func() { logger.DPanic("") })
|
||||||
|
assert.Equal(
|
||||||
|
t,
|
||||||
|
[]observer.LoggedEntry{{Entry: zapcore.Entry{Level: DPanicLevel}, Context: []zapcore.Field{}}},
|
||||||
|
logs.AllUntimed(),
|
||||||
|
"Unexpected log output from DPanic in development mode.",
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoggerNoOpsDisabledLevels(t *testing.T) {
|
||||||
|
withLogger(t, WarnLevel, nil, func(logger *Logger, logs *observer.ObservedLogs) {
|
||||||
|
logger.Info("silence!")
|
||||||
|
assert.Equal(
|
||||||
|
t,
|
||||||
|
[]observer.LoggedEntry{},
|
||||||
|
logs.AllUntimed(),
|
||||||
|
"Expected logging at a disabled level to produce no output.",
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoggerNames(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
names []string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{nil, ""},
|
||||||
|
{[]string{""}, ""},
|
||||||
|
{[]string{"foo"}, "foo"},
|
||||||
|
{[]string{"foo", ""}, "foo"},
|
||||||
|
{[]string{"foo", "bar"}, "foo.bar"},
|
||||||
|
{[]string{"foo.bar", "baz"}, "foo.bar.baz"},
|
||||||
|
// Garbage in, garbage out.
|
||||||
|
{[]string{"foo.", "bar"}, "foo..bar"},
|
||||||
|
{[]string{"foo", ".bar"}, "foo..bar"},
|
||||||
|
{[]string{"foo.", ".bar"}, "foo...bar"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
withLogger(t, DebugLevel, nil, func(log *Logger, logs *observer.ObservedLogs) {
|
||||||
|
for _, n := range tt.names {
|
||||||
|
log = log.Named(n)
|
||||||
|
}
|
||||||
|
log.Info("")
|
||||||
|
require.Equal(t, 1, logs.Len(), "Expected only one log entry to be written.")
|
||||||
|
assert.Equal(t, tt.expected, logs.AllUntimed()[0].Entry.LoggerName, "Unexpected logger name.")
|
||||||
|
})
|
||||||
|
withSugar(t, DebugLevel, nil, func(log *SugaredLogger, logs *observer.ObservedLogs) {
|
||||||
|
for _, n := range tt.names {
|
||||||
|
log = log.Named(n)
|
||||||
|
}
|
||||||
|
log.Infow("")
|
||||||
|
require.Equal(t, 1, logs.Len(), "Expected only one log entry to be written.")
|
||||||
|
assert.Equal(t, tt.expected, logs.AllUntimed()[0].Entry.LoggerName, "Unexpected logger name.")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoggerWriteFailure(t *testing.T) {
|
||||||
|
errSink := &zaptest.Buffer{}
|
||||||
|
logger := New(
|
||||||
|
zapcore.NewCore(
|
||||||
|
zapcore.NewJSONEncoder(NewProductionConfig().EncoderConfig),
|
||||||
|
zapcore.Lock(zapcore.AddSync(zaptest.FailWriter{})),
|
||||||
|
DebugLevel,
|
||||||
|
),
|
||||||
|
ErrorOutput(errSink),
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.Info("foo")
|
||||||
|
// Should log the error.
|
||||||
|
assert.Regexp(t, `write error: failed`, errSink.Stripped(), "Expected to log the error to the error output.")
|
||||||
|
assert.True(t, errSink.Called(), "Expected logging an internal error to call Sync the error sink.")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoggerSync(t *testing.T) {
|
||||||
|
withLogger(t, DebugLevel, nil, func(logger *Logger, _ *observer.ObservedLogs) {
|
||||||
|
assert.NoError(t, logger.Sync(), "Expected syncing a test logger to succeed.")
|
||||||
|
assert.NoError(t, logger.Sugar().Sync(), "Expected syncing a sugared logger to succeed.")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoggerSyncFail(t *testing.T) {
|
||||||
|
noSync := &zaptest.Buffer{}
|
||||||
|
err := errors.New("fail")
|
||||||
|
noSync.SetError(err)
|
||||||
|
logger := New(zapcore.NewCore(
|
||||||
|
zapcore.NewJSONEncoder(zapcore.EncoderConfig{}),
|
||||||
|
noSync,
|
||||||
|
DebugLevel,
|
||||||
|
))
|
||||||
|
assert.Equal(t, err, logger.Sync(), "Expected Logger.Sync to propagate errors.")
|
||||||
|
assert.Equal(t, err, logger.Sugar().Sync(), "Expected SugaredLogger.Sync to propagate errors.")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoggerAddCaller(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
options []Option
|
||||||
|
pat string
|
||||||
|
}{
|
||||||
|
{opts(AddCaller()), `.+/logger_test.go:[\d]+$`},
|
||||||
|
{opts(AddCaller(), AddCallerSkip(1), AddCallerSkip(-1)), `.+/zap/logger_test.go:[\d]+$`},
|
||||||
|
{opts(AddCaller(), AddCallerSkip(1)), `.+/zap/common_test.go:[\d]+$`},
|
||||||
|
{opts(AddCaller(), AddCallerSkip(1), AddCallerSkip(3)), `.+/src/runtime/.*:[\d]+$`},
|
||||||
|
}
|
||||||
|
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()
|
||||||
|
logger.Info("")
|
||||||
|
output := logs.AllUntimed()
|
||||||
|
assert.Equal(t, 1, len(output), "Unexpected number of logs written out.")
|
||||||
|
assert.Regexp(
|
||||||
|
t,
|
||||||
|
tt.pat,
|
||||||
|
output[0].Entry.Caller,
|
||||||
|
"Expected to find package name and file name in output.",
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoggerAddCallerFail(t *testing.T) {
|
||||||
|
errBuf := &zaptest.Buffer{}
|
||||||
|
withLogger(t, DebugLevel, opts(AddCaller(), ErrorOutput(errBuf)), func(log *Logger, logs *observer.ObservedLogs) {
|
||||||
|
log.callerSkip = 1e3
|
||||||
|
log.Info("Failure.")
|
||||||
|
assert.Regexp(
|
||||||
|
t,
|
||||||
|
`Logger.check error: failed to get caller`,
|
||||||
|
errBuf.String(),
|
||||||
|
"Didn't find expected failure message.",
|
||||||
|
)
|
||||||
|
assert.Equal(
|
||||||
|
t,
|
||||||
|
logs.AllUntimed()[0].Entry.Message,
|
||||||
|
"Failure.",
|
||||||
|
"Expected original message to survive failures in runtime.Caller.")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoggerReplaceCore(t *testing.T) {
|
||||||
|
replace := WrapCore(func(zapcore.Core) zapcore.Core {
|
||||||
|
return zapcore.NewNopCore()
|
||||||
|
})
|
||||||
|
withLogger(t, DebugLevel, opts(replace), func(logger *Logger, logs *observer.ObservedLogs) {
|
||||||
|
logger.Debug("")
|
||||||
|
logger.Info("")
|
||||||
|
logger.Warn("")
|
||||||
|
assert.Equal(t, 0, logs.Len(), "Expected no-op core to write no logs.")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoggerHooks(t *testing.T) {
|
||||||
|
hook, seen := makeCountingHook()
|
||||||
|
withLogger(t, DebugLevel, opts(Hooks(hook)), func(logger *Logger, logs *observer.ObservedLogs) {
|
||||||
|
logger.Debug("")
|
||||||
|
logger.Info("")
|
||||||
|
})
|
||||||
|
assert.Equal(t, int64(2), seen.Load(), "Hook saw an unexpected number of logs.")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoggerConcurrent(t *testing.T) {
|
||||||
|
withLogger(t, DebugLevel, nil, func(logger *Logger, logs *observer.ObservedLogs) {
|
||||||
|
child := logger.With(String("foo", "bar"))
|
||||||
|
|
||||||
|
wg := &sync.WaitGroup{}
|
||||||
|
runConcurrently(5, 10, wg, func() {
|
||||||
|
logger.Info("", String("foo", "bar"))
|
||||||
|
})
|
||||||
|
runConcurrently(5, 10, wg, func() {
|
||||||
|
child.Info("")
|
||||||
|
})
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
// Make sure the output doesn't contain interspersed entries.
|
||||||
|
assert.Equal(t, 100, logs.Len(), "Unexpected number of logs written out.")
|
||||||
|
for _, obs := range logs.AllUntimed() {
|
||||||
|
assert.Equal(
|
||||||
|
t,
|
||||||
|
observer.LoggedEntry{
|
||||||
|
Entry: zapcore.Entry{Level: InfoLevel},
|
||||||
|
Context: []zapcore.Field{String("foo", "bar")},
|
||||||
|
},
|
||||||
|
obs,
|
||||||
|
"Unexpected log output.",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,109 @@
|
||||||
|
// Copyright (c) 2016 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
package zap
|
||||||
|
|
||||||
|
import "go.uber.org/zap/zapcore"
|
||||||
|
|
||||||
|
// An Option configures a Logger.
|
||||||
|
type Option interface {
|
||||||
|
apply(*Logger)
|
||||||
|
}
|
||||||
|
|
||||||
|
// optionFunc wraps a func so it satisfies the Option interface.
|
||||||
|
type optionFunc func(*Logger)
|
||||||
|
|
||||||
|
func (f optionFunc) apply(log *Logger) {
|
||||||
|
f(log)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WrapCore wraps or replaces the Logger's underlying zapcore.Core.
|
||||||
|
func WrapCore(f func(zapcore.Core) zapcore.Core) Option {
|
||||||
|
return optionFunc(func(log *Logger) {
|
||||||
|
log.core = f(log.core)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hooks registers functions which will be called each time the Logger writes
|
||||||
|
// out an Entry. Repeated use of Hooks is additive.
|
||||||
|
//
|
||||||
|
// Hooks are useful for simple side effects, like capturing metrics for the
|
||||||
|
// number of emitted logs. More complex side effects, including anything that
|
||||||
|
// requires access to the Entry's structured fields, should be implemented as
|
||||||
|
// a zapcore.Core instead. See zapcore.RegisterHooks for details.
|
||||||
|
func Hooks(hooks ...func(zapcore.Entry) error) Option {
|
||||||
|
return optionFunc(func(log *Logger) {
|
||||||
|
log.core = zapcore.RegisterHooks(log.core, hooks...)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fields adds fields to the Logger.
|
||||||
|
func Fields(fs ...zapcore.Field) Option {
|
||||||
|
return optionFunc(func(log *Logger) {
|
||||||
|
log.core = log.core.With(fs)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorOutput sets the destination for errors generated by the Logger. Note
|
||||||
|
// that this option only affects internal errors; for sample code that sends
|
||||||
|
// error-level logs to a different location from info- and debug-level logs,
|
||||||
|
// see the package-level AdvancedConfiguration example.
|
||||||
|
//
|
||||||
|
// The supplied WriteSyncer must be safe for concurrent use. The Open and
|
||||||
|
// zapcore.Lock functions are the simplest ways to protect files with a mutex.
|
||||||
|
func ErrorOutput(w zapcore.WriteSyncer) Option {
|
||||||
|
return optionFunc(func(log *Logger) {
|
||||||
|
log.errorOutput = w
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Development puts the logger in development mode, which makes DPanic-level
|
||||||
|
// logs panic instead of simply logging an error.
|
||||||
|
func Development() Option {
|
||||||
|
return optionFunc(func(log *Logger) {
|
||||||
|
log.development = true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddCaller configures the Logger to annotate each message with the filename
|
||||||
|
// and line number of zap's caller.
|
||||||
|
func AddCaller() Option {
|
||||||
|
return optionFunc(func(log *Logger) {
|
||||||
|
log.addCaller = true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddCallerSkip increases the number of callers skipped by caller annotation
|
||||||
|
// (as enabled by the AddCaller option). When building wrappers around the
|
||||||
|
// Logger and SugaredLogger, supplying this Option prevents zap from always
|
||||||
|
// reporting the wrapper code as the caller.
|
||||||
|
func AddCallerSkip(skip int) Option {
|
||||||
|
return optionFunc(func(log *Logger) {
|
||||||
|
log.callerSkip += skip
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddStacktrace configures the Logger to record a stack trace for all messages at
|
||||||
|
// or above a given level.
|
||||||
|
func AddStacktrace(lvl zapcore.LevelEnabler) Option {
|
||||||
|
return optionFunc(func(log *Logger) {
|
||||||
|
log.addStack = lvl
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
echo "" > cover.out
|
||||||
|
|
||||||
|
for d in $(go list $@); do
|
||||||
|
go test -race -coverprofile=profile.out $d
|
||||||
|
if [ -f profile.out ]; then
|
||||||
|
cat profile.out >> cover.out
|
||||||
|
rm profile.out
|
||||||
|
fi
|
||||||
|
done
|
|
@ -0,0 +1,126 @@
|
||||||
|
// Copyright (c) 2016 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
package zap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"go.uber.org/zap/internal/bufferpool"
|
||||||
|
)
|
||||||
|
|
||||||
|
const _zapPackage = "go.uber.org/zap"
|
||||||
|
|
||||||
|
var (
|
||||||
|
_stacktracePool = sync.Pool{
|
||||||
|
New: func() interface{} {
|
||||||
|
return newProgramCounters(64)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// We add "." and "/" suffixes to the package name to ensure we only match
|
||||||
|
// the exact package and not any package with the same prefix.
|
||||||
|
_zapStacktracePrefixes = addPrefix(_zapPackage, ".", "/")
|
||||||
|
_zapStacktraceVendorContains = addPrefix("/vendor/", _zapStacktracePrefixes...)
|
||||||
|
)
|
||||||
|
|
||||||
|
func takeStacktrace() string {
|
||||||
|
buffer := bufferpool.Get()
|
||||||
|
defer buffer.Free()
|
||||||
|
programCounters := _stacktracePool.Get().(*programCounters)
|
||||||
|
defer _stacktracePool.Put(programCounters)
|
||||||
|
|
||||||
|
var numFrames int
|
||||||
|
for {
|
||||||
|
// Skip the call to runtime.Counters and takeStacktrace so that the
|
||||||
|
// program counters start at the caller of takeStacktrace.
|
||||||
|
numFrames = runtime.Callers(2, programCounters.pcs)
|
||||||
|
if numFrames < len(programCounters.pcs) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// Don't put the too-short counter slice back into the pool; this lets
|
||||||
|
// the pool adjust if we consistently take deep stacktraces.
|
||||||
|
programCounters = newProgramCounters(len(programCounters.pcs) * 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
i := 0
|
||||||
|
skipZapFrames := true // skip all consecutive zap frames at the beginning.
|
||||||
|
frames := runtime.CallersFrames(programCounters.pcs[:numFrames])
|
||||||
|
|
||||||
|
// Note: On the last iteration, frames.Next() returns false, with a valid
|
||||||
|
// frame, but we ignore this frame. The last frame is a a runtime frame which
|
||||||
|
// adds noise, since it's only either runtime.main or runtime.goexit.
|
||||||
|
for frame, more := frames.Next(); more; frame, more = frames.Next() {
|
||||||
|
if skipZapFrames && isZapFrame(frame.Function) {
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
skipZapFrames = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if i != 0 {
|
||||||
|
buffer.AppendByte('\n')
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
buffer.AppendString(frame.Function)
|
||||||
|
buffer.AppendByte('\n')
|
||||||
|
buffer.AppendByte('\t')
|
||||||
|
buffer.AppendString(frame.File)
|
||||||
|
buffer.AppendByte(':')
|
||||||
|
buffer.AppendInt(int64(frame.Line))
|
||||||
|
}
|
||||||
|
|
||||||
|
return buffer.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func isZapFrame(function string) bool {
|
||||||
|
for _, prefix := range _zapStacktracePrefixes {
|
||||||
|
if strings.HasPrefix(function, prefix) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We can't use a prefix match here since the location of the vendor
|
||||||
|
// directory affects the prefix. Instead we do a contains match.
|
||||||
|
for _, contains := range _zapStacktraceVendorContains {
|
||||||
|
if strings.Contains(function, contains) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
type programCounters struct {
|
||||||
|
pcs []uintptr
|
||||||
|
}
|
||||||
|
|
||||||
|
func newProgramCounters(size int) *programCounters {
|
||||||
|
return &programCounters{make([]uintptr, size)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func addPrefix(prefix string, ss ...string) []string {
|
||||||
|
withPrefix := make([]string, len(ss))
|
||||||
|
for i, s := range ss {
|
||||||
|
withPrefix[i] = prefix + s
|
||||||
|
}
|
||||||
|
return withPrefix
|
||||||
|
}
|
|
@ -0,0 +1,161 @@
|
||||||
|
// Copyright (c) 2016, 2017 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
package zap_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
// _zapPackages are packages that we search for in the logging output to match a
|
||||||
|
// zap stack frame. It is different from _zapStacktracePrefixes which is only
|
||||||
|
// intended to match on the function name, while this is on the full output
|
||||||
|
// which includes filenames.
|
||||||
|
var _zapPackages = []string{
|
||||||
|
"go.uber.org/zap.",
|
||||||
|
"go.uber.org/zap/zapcore.",
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStacktraceFiltersZapLog(t *testing.T) {
|
||||||
|
withLogger(t, func(logger *zap.Logger, out *bytes.Buffer) {
|
||||||
|
logger.Error("test log")
|
||||||
|
logger.Sugar().Error("sugar test log")
|
||||||
|
|
||||||
|
require.Contains(t, out.String(), "TestStacktraceFiltersZapLog", "Should not strip out non-zap import")
|
||||||
|
verifyNoZap(t, out.String())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStacktraceFiltersZapMarshal(t *testing.T) {
|
||||||
|
withLogger(t, func(logger *zap.Logger, out *bytes.Buffer) {
|
||||||
|
marshal := func(enc zapcore.ObjectEncoder) error {
|
||||||
|
logger.Warn("marshal caused warn")
|
||||||
|
enc.AddString("f", "v")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
logger.Error("test log", zap.Object("obj", zapcore.ObjectMarshalerFunc(marshal)))
|
||||||
|
|
||||||
|
logs := out.String()
|
||||||
|
|
||||||
|
// The marshal function (which will be under the test function) should not be stripped.
|
||||||
|
const marshalFnPrefix = "TestStacktraceFiltersZapMarshal."
|
||||||
|
require.Contains(t, logs, marshalFnPrefix, "Should not strip out marshal call")
|
||||||
|
|
||||||
|
// There should be no zap stack traces before that point.
|
||||||
|
marshalIndex := strings.Index(logs, marshalFnPrefix)
|
||||||
|
verifyNoZap(t, logs[:marshalIndex])
|
||||||
|
|
||||||
|
// After that point, there should be zap stack traces - we don't want to strip out
|
||||||
|
// the Marshal caller information.
|
||||||
|
for _, fnPrefix := range _zapPackages {
|
||||||
|
require.Contains(t, logs[marshalIndex:], fnPrefix, "Missing zap caller stack for Marshal")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStacktraceFiltersVendorZap(t *testing.T) {
|
||||||
|
// We need to simulate a zap as a vendor library, so we're going to create a fake GOPATH
|
||||||
|
// and run the above test which will contain zap in the vendor directory.
|
||||||
|
withGoPath(t, func(goPath string) {
|
||||||
|
curDir, err := os.Getwd()
|
||||||
|
require.NoError(t, err, "Failed to get current directory")
|
||||||
|
|
||||||
|
testDir := filepath.Join(goPath, "src/go.uber.org/zap_test/")
|
||||||
|
vendorDir := filepath.Join(testDir, "vendor")
|
||||||
|
require.NoError(t, os.MkdirAll(testDir, 0777), "Failed to create source director")
|
||||||
|
|
||||||
|
curFile := getSelfFilename(t)
|
||||||
|
//copyFile(t, curFile, filepath.Join(testDir, curFile))
|
||||||
|
setupSymlink(t, curFile, filepath.Join(testDir, curFile))
|
||||||
|
|
||||||
|
// Set up symlinks for zap, and for any test dependencies.
|
||||||
|
setupSymlink(t, curDir, filepath.Join(vendorDir, "go.uber.org/zap"))
|
||||||
|
for _, testDep := range []string{"github.com/stretchr/testify"} {
|
||||||
|
setupSymlink(t, filepath.Join(curDir, "vendor", testDep), filepath.Join(vendorDir, testDep))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now run the above test which ensures we filter out zap stacktraces, but this time
|
||||||
|
// zap is in a vendor
|
||||||
|
cmd := exec.Command("go", "test", "-v", "-run", "TestStacktraceFiltersZap")
|
||||||
|
cmd.Dir = testDir
|
||||||
|
out, err := cmd.CombinedOutput()
|
||||||
|
require.NoError(t, err, "Failed to run test in vendor directory, output: %s", out)
|
||||||
|
assert.Contains(t, string(out), "PASS")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// withLogger sets up a logger with a real encoder set up, so that any marshal functions are called.
|
||||||
|
// The inbuilt observer does not call Marshal for objects/arrays, which we need for some tests.
|
||||||
|
func withLogger(t *testing.T, fn func(logger *zap.Logger, out *bytes.Buffer)) {
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
encoder := zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig())
|
||||||
|
core := zapcore.NewCore(encoder, zapcore.AddSync(buf), zapcore.DebugLevel)
|
||||||
|
logger := zap.New(core, zap.AddStacktrace(zap.DebugLevel))
|
||||||
|
fn(logger, buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func verifyNoZap(t *testing.T, logs string) {
|
||||||
|
for _, fnPrefix := range _zapPackages {
|
||||||
|
require.NotContains(t, logs, fnPrefix, "Should not strip out marshal call")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func withGoPath(t *testing.T, f func(goPath string)) {
|
||||||
|
goPath, err := ioutil.TempDir("", "gopath")
|
||||||
|
require.NoError(t, err, "Failed to create temporary directory for GOPATH")
|
||||||
|
//defer os.RemoveAll(goPath)
|
||||||
|
|
||||||
|
os.Setenv("GOPATH", goPath)
|
||||||
|
defer os.Setenv("GOPATH", os.Getenv("GOPATH"))
|
||||||
|
|
||||||
|
f(goPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSelfFilename(t *testing.T) string {
|
||||||
|
_, file, _, ok := runtime.Caller(0)
|
||||||
|
require.True(t, ok, "Failed to get caller information to identify local file")
|
||||||
|
|
||||||
|
return filepath.Base(file)
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupSymlink(t *testing.T, src, dst string) {
|
||||||
|
// Make sure the destination directory exists.
|
||||||
|
os.MkdirAll(filepath.Dir(dst), 0777)
|
||||||
|
|
||||||
|
// Get absolute path of the source for the symlink, otherwise we can create a symlink
|
||||||
|
// that uses relative paths.
|
||||||
|
srcAbs, err := filepath.Abs(src)
|
||||||
|
require.NoError(t, err, "Failed to get absolute path")
|
||||||
|
|
||||||
|
require.NoError(t, os.Symlink(srcAbs, dst), "Failed to set up symlink")
|
||||||
|
}
|
|
@ -0,0 +1,75 @@
|
||||||
|
// Copyright (c) 2016 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
package zap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTakeStacktrace(t *testing.T) {
|
||||||
|
trace := takeStacktrace()
|
||||||
|
lines := strings.Split(trace, "\n")
|
||||||
|
require.True(t, len(lines) > 0, "Expected stacktrace to have at least one frame.")
|
||||||
|
assert.Contains(
|
||||||
|
t,
|
||||||
|
lines[0],
|
||||||
|
"testing.",
|
||||||
|
"Expected stacktrace to start with the test runner (zap frames are filtered out) %s.", lines[0],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsZapFrame(t *testing.T) {
|
||||||
|
zapFrames := []string{
|
||||||
|
"go.uber.org/zap.Stack",
|
||||||
|
"go.uber.org/zap.(*SugaredLogger).log",
|
||||||
|
"go.uber.org/zap/zapcore.(ArrayMarshalerFunc).MarshalLogArray",
|
||||||
|
"github.com/uber/tchannel-go/vendor/go.uber.org/zap.Stack",
|
||||||
|
"github.com/uber/tchannel-go/vendor/go.uber.org/zap.(*SugaredLogger).log",
|
||||||
|
"github.com/uber/tchannel-go/vendor/go.uber.org/zap/zapcore.(ArrayMarshalerFunc).MarshalLogArray",
|
||||||
|
}
|
||||||
|
nonZapFrames := []string{
|
||||||
|
"github.com/uber/tchannel-go.NewChannel",
|
||||||
|
"go.uber.org/not-zap.New",
|
||||||
|
"go.uber.org/zapext.ctx",
|
||||||
|
"go.uber.org/zap_ext/ctx.New",
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("zap frames", func(t *testing.T) {
|
||||||
|
for _, f := range zapFrames {
|
||||||
|
require.True(t, isZapFrame(f), f)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("non-zap frames", func(t *testing.T) {
|
||||||
|
for _, f := range nonZapFrames {
|
||||||
|
require.False(t, isZapFrame(f), f)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkTakeStacktrace(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
takeStacktrace()
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue