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