logrotator 교체
This commit is contained in:
parent
b1f49e40e7
commit
fba6751e05
|
@ -0,0 +1,92 @@
|
||||||
|
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||||
|
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
name = "github.com/NebulousLabs/fastrand"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "3cf7173006a0b7d2371fa1a220da7f9d48c7827c"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
name = "github.com/benburkert/dns"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "737ce0376169e5410fb2a4ae066d5aabbb71648c"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "github.com/lestrrat-go/file-rotatelogs"
|
||||||
|
packages = [
|
||||||
|
".",
|
||||||
|
"internal/option"
|
||||||
|
]
|
||||||
|
revision = "3b4f34a036f374c18d604e1b8006d9fbb9593c06"
|
||||||
|
version = "v2.2.0"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
name = "github.com/lestrrat-go/strftime"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "59966ecb6d84ec0010de6a5b8deae0299ce5b549"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "github.com/pkg/errors"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "645ef00459ed84a119197bfb8d8205042c6df63d"
|
||||||
|
version = "v0.8.0"
|
||||||
|
|
||||||
|
[[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 = "eeedf312bc6c57391d84767a4cd413f02a917974"
|
||||||
|
version = "v1.8.0"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
name = "golang.org/x/crypto"
|
||||||
|
packages = ["blake2b"]
|
||||||
|
revision = "8ac0e0d97ce45cd83d1d7243c060cb8461dda5e9"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
name = "golang.org/x/sys"
|
||||||
|
packages = ["cpu"]
|
||||||
|
revision = "9527bec2660bd847c050fda93a0f0c6dee0800bb"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "golang.org/x/text"
|
||||||
|
packages = [
|
||||||
|
"internal/gen",
|
||||||
|
"internal/triegen",
|
||||||
|
"internal/ucd",
|
||||||
|
"transform",
|
||||||
|
"unicode/cldr",
|
||||||
|
"width"
|
||||||
|
]
|
||||||
|
revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0"
|
||||||
|
version = "v0.3.0"
|
||||||
|
|
||||||
|
[solve-meta]
|
||||||
|
analyzer-name = "dep"
|
||||||
|
analyzer-version = 1
|
||||||
|
inputs-digest = "6efd7212bdd402a83d3371c761b44b92ba0a2dfb54708f3fe576e5fd1ed5bade"
|
||||||
|
solver-name = "gps-cdcl"
|
||||||
|
solver-version = 1
|
|
@ -0,0 +1,50 @@
|
||||||
|
# Gopkg.toml example
|
||||||
|
#
|
||||||
|
# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html
|
||||||
|
# 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"
|
||||||
|
#
|
||||||
|
# [prune]
|
||||||
|
# non-go = false
|
||||||
|
# go-tests = true
|
||||||
|
# unused-packages = true
|
||||||
|
|
||||||
|
|
||||||
|
[[constraint]]
|
||||||
|
branch = "master"
|
||||||
|
name = "github.com/NebulousLabs/fastrand"
|
||||||
|
|
||||||
|
[[constraint]]
|
||||||
|
branch = "master"
|
||||||
|
name = "github.com/benburkert/dns"
|
||||||
|
|
||||||
|
[[constraint]]
|
||||||
|
name = "go.uber.org/zap"
|
||||||
|
version = "1.8.0"
|
||||||
|
|
||||||
|
[[constraint]]
|
||||||
|
name = "golang.org/x/text"
|
||||||
|
version = "0.3.0"
|
||||||
|
|
||||||
|
[[constraint]]
|
||||||
|
name = "gopkg.in/natefinch/lumberjack.v2"
|
||||||
|
version = "2.1.0"
|
||||||
|
|
||||||
|
[prune]
|
||||||
|
go-tests = true
|
||||||
|
unused-packages = true
|
|
@ -9,29 +9,25 @@ import (
|
||||||
|
|
||||||
var loggers RotateSyncerSet
|
var loggers RotateSyncerSet
|
||||||
|
|
||||||
func NewLogWriter(FileName string, MaxSizeMb, MaxBackup, MaxDay int, logDir string) logger.RotateSyncer {
|
func NewLogWriter(FileName string, logDir string, options ...Option) (logger.RotateSyncer, error) {
|
||||||
switch FileName {
|
switch FileName {
|
||||||
case "Stdout":
|
case "Stdout":
|
||||||
return NewLocked(os.Stdout)
|
return NewLocked(os.Stdout), nil
|
||||||
case "Stderr":
|
case "Stderr":
|
||||||
return NewLocked(os.Stderr)
|
return NewLocked(os.Stderr), nil
|
||||||
default:
|
default:
|
||||||
logpath := FileName
|
logpath := FileName
|
||||||
if logDir != "" {
|
if logDir != "" {
|
||||||
logpath = path.Join(logDir, FileName)
|
logpath = path.Join(logDir, FileName)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Println(" Attention!! log writes to ", logpath)
|
log.Println(" Attention!! log writes to ", logpath)
|
||||||
|
if logWriter, err := NewRotater(logpath, options...); err != nil {
|
||||||
logWriter := NewRotater(
|
return nil, err
|
||||||
logpath,
|
} else {
|
||||||
MaxSizeMb, // megabytes
|
|
||||||
MaxBackup,
|
|
||||||
MaxDay, //days
|
|
||||||
)
|
|
||||||
loggers.Store(logWriter)
|
loggers.Store(logWriter)
|
||||||
logWriter.SetOnClose(func() { loggers.Delete(logWriter) })
|
logWriter.SetOnClose(func() { loggers.Delete(logWriter) })
|
||||||
return logWriter
|
return logWriter, nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,6 @@ func (s *LockedWriteSyncer) Sync() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LockedWriteSyncer) SetOnClose(closeFunc logger.CloseFunc) {}
|
func (r *LockedWriteSyncer) SetOnClose(closeFunc func()) {}
|
||||||
func (r *LockedWriteSyncer) Rotate() (err error) { return }
|
func (r *LockedWriteSyncer) Rotate() (err error) { return }
|
||||||
func (r *LockedWriteSyncer) Close() (err error) { return }
|
func (r *LockedWriteSyncer) Close() (err error) { return }
|
||||||
|
|
|
@ -3,26 +3,25 @@ package rotater
|
||||||
import (
|
import (
|
||||||
"sync"
|
"sync"
|
||||||
"amuz.es/src/infra/goutils/logger"
|
"amuz.es/src/infra/goutils/logger"
|
||||||
lumberjack "gopkg.in/natefinch/lumberjack.v2"
|
"github.com/lestrrat-go/file-rotatelogs"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Option = rotatelogs.Option
|
||||||
|
|
||||||
type rotateSyncer struct {
|
type rotateSyncer struct {
|
||||||
setOnceOnclose *sync.Once
|
setOnceOnclose *sync.Once
|
||||||
onClose func()
|
onClose func()
|
||||||
lumberjack.Logger
|
*rotatelogs.RotateLogs
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRotater(filename string, maxSize, maxBackup, maxDay int) logger.RotateSyncer {
|
func NewRotater(filename string, options ...Option) (logger.RotateSyncer, error) {
|
||||||
|
if rotateLogger, err := rotatelogs.New(filename, options...); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
return &rotateSyncer{
|
return &rotateSyncer{
|
||||||
setOnceOnclose: &sync.Once{},
|
setOnceOnclose: &sync.Once{},
|
||||||
Logger: lumberjack.Logger{
|
RotateLogs: rotateLogger,
|
||||||
Filename: filename,
|
}, nil
|
||||||
MaxSize: maxSize, // megabytes
|
|
||||||
MaxBackups: maxBackup,
|
|
||||||
MaxAge: maxDay, //days
|
|
||||||
LocalTime: false,
|
|
||||||
Compress: false,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
func (r *rotateSyncer) SetOnClose(closeFunc func()) {
|
func (r *rotateSyncer) SetOnClose(closeFunc func()) {
|
||||||
|
@ -32,7 +31,7 @@ func (r *rotateSyncer) SetOnClose(closeFunc func()) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *rotateSyncer) Rotate() error {
|
func (r *rotateSyncer) Rotate() error {
|
||||||
return r.Logger.Rotate()
|
return r.RotateLogs.Rotate()
|
||||||
}
|
}
|
||||||
func (r *rotateSyncer) Close() error {
|
func (r *rotateSyncer) Close() error {
|
||||||
defer func() {
|
defer func() {
|
||||||
|
@ -40,7 +39,7 @@ func (r *rotateSyncer) Close() error {
|
||||||
r.onClose()
|
r.onClose()
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
return r.Logger.Close()
|
return r.RotateLogs.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *rotateSyncer) Sync() error {
|
func (r *rotateSyncer) Sync() error {
|
||||||
|
@ -48,5 +47,5 @@ func (r *rotateSyncer) Sync() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *rotateSyncer) Write(bs []byte) (int, error) {
|
func (s *rotateSyncer) Write(bs []byte) (int, error) {
|
||||||
return s.Logger.Write(bs)
|
return s.RotateLogs.Write(bs)
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
|
|
||||||
"go.uber.org/zap/zapcore"
|
"go.uber.org/zap/zapcore"
|
||||||
"amuz.es/src/infra/goutils/logger"
|
"amuz.es/src/infra/goutils/logger"
|
||||||
|
"amuz.es/src/infra/goutils/logger/rotater"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -26,13 +27,14 @@ func Init(
|
||||||
verbose bool,
|
verbose bool,
|
||||||
formatter zapcore.Encoder,
|
formatter zapcore.Encoder,
|
||||||
mainLogName, logFilename, logDir string,
|
mainLogName, logFilename, logDir string,
|
||||||
maxSizeMb, maxBackup, maxDay int,
|
rotateOption []rotater.Option,
|
||||||
logLevel zapcore.Level,
|
logLevel zapcore.Level,
|
||||||
additionalOptions ...zap.Option,
|
additionalOptions ...zap.Option,
|
||||||
) *zap.SugaredLogger {
|
) (*zap.SugaredLogger, error) {
|
||||||
level := zap.NewAtomicLevelAt(logLevel)
|
level := zap.NewAtomicLevelAt(logLevel)
|
||||||
defaultWriter = rotater.NewLogWriter(logFilename, maxSizeMb, maxBackup, maxDay, logDir)
|
if defaultWriter, err := rotater.NewLogWriter(logFilename, logDir, rotateOption...); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
defaultErrorOutputOptions = []zap.Option{zap.ErrorOutput(defaultWriter)}
|
defaultErrorOutputOptions = []zap.Option{zap.ErrorOutput(defaultWriter)}
|
||||||
options := defaultErrorOutputOptions
|
options := defaultErrorOutputOptions
|
||||||
if verbose {
|
if verbose {
|
||||||
|
@ -44,7 +46,8 @@ func Init(
|
||||||
log := initLogger(defaultWriter, mainLogName, formatter, level, options...)
|
log := initLogger(defaultWriter, mainLogName, formatter, level, options...)
|
||||||
|
|
||||||
replaceGlobalLogger(log)
|
replaceGlobalLogger(log)
|
||||||
return log.Sugar()
|
return log.Sugar(), nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(parent *zap.SugaredLogger, moduleName string, options ...zap.Option) *zap.SugaredLogger {
|
func New(parent *zap.SugaredLogger, moduleName string, options ...zap.Option) *zap.SugaredLogger {
|
||||||
|
@ -63,12 +66,15 @@ func New(parent *zap.SugaredLogger, moduleName string, options ...zap.Option) *z
|
||||||
func NewOtherLogger(
|
func NewOtherLogger(
|
||||||
formatter zapcore.Encoder,
|
formatter zapcore.Encoder,
|
||||||
moduleName, logFilename, logDir string,
|
moduleName, logFilename, logDir string,
|
||||||
maxSizeMb, maxBackup, maxDay int,
|
rotateOption []rotater.Option,
|
||||||
logLevel zapcore.Level,
|
logLevel zapcore.Level,
|
||||||
fields ...zapcore.Field,
|
fields ...zapcore.Field,
|
||||||
) (logger *zap.SugaredLogger, closer func() error) {
|
) (logger *zap.SugaredLogger, closer func() error, err error) {
|
||||||
loglevel := zap.NewAtomicLevelAt(logLevel)
|
loglevel := zap.NewAtomicLevelAt(logLevel)
|
||||||
logWriter := rotater.NewLogWriter(logFilename, maxSizeMb, maxBackup, maxDay, logDir)
|
logWriter, err := rotater.NewLogWriter(logFilename, logDir, rotateOption...)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
core := zapcore.NewCore(formatter, logWriter, loglevel)
|
core := zapcore.NewCore(formatter, logWriter, loglevel)
|
||||||
closer = logWriter.Close
|
closer = logWriter.Close
|
||||||
logger = zap.New(core, defaultErrorOutputOptions...).
|
logger = zap.New(core, defaultErrorOutputOptions...).
|
||||||
|
@ -79,13 +85,16 @@ func NewOtherLogger(
|
||||||
func NewOtherLoggerWithOption(
|
func NewOtherLoggerWithOption(
|
||||||
formatter zapcore.Encoder,
|
formatter zapcore.Encoder,
|
||||||
moduleName, logFilename, logDir string,
|
moduleName, logFilename, logDir string,
|
||||||
maxSizeMb, maxBackup, maxDay int,
|
rotateOption []rotater.Option,
|
||||||
logLevel zapcore.Level,
|
logLevel zapcore.Level,
|
||||||
options []zap.Option,
|
options []zap.Option,
|
||||||
fields ...zapcore.Field,
|
fields ...zapcore.Field,
|
||||||
) (logger *zap.SugaredLogger, closer func() error) {
|
) (logger *zap.SugaredLogger, closer func() error, err error) {
|
||||||
loglevel := zap.NewAtomicLevelAt(logLevel)
|
loglevel := zap.NewAtomicLevelAt(logLevel)
|
||||||
logWriter := rotater.NewLogWriter(logFilename, maxSizeMb, maxBackup, maxDay, logDir)
|
logWriter, err := rotater.NewLogWriter(logFilename, logDir, rotateOption...)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
core := zapcore.NewCore(formatter, logWriter, loglevel)
|
core := zapcore.NewCore(formatter, logWriter, loglevel)
|
||||||
closer = logWriter.Close
|
closer = logWriter.Close
|
||||||
options = append(defaultErrorOutputOptions, options...)
|
options = append(defaultErrorOutputOptions, options...)
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2017 Nebulous 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,55 @@
|
||||||
|
fastrand
|
||||||
|
--------
|
||||||
|
|
||||||
|
[![GoDoc](https://godoc.org/github.com/NebulousLabs/fastrand?status.svg)](https://godoc.org/github.com/NebulousLabs/fastrand)
|
||||||
|
[![Go Report Card](http://goreportcard.com/badge/github.com/NebulousLabs/fastrand)](https://goreportcard.com/report/github.com/NebulousLabs/fastrand)
|
||||||
|
|
||||||
|
```
|
||||||
|
go get github.com/NebulousLabs/fastrand
|
||||||
|
```
|
||||||
|
|
||||||
|
`fastrand` implements a cryptographically secure pseudorandom number generator.
|
||||||
|
The generator is seeded using the system's default entropy source, and
|
||||||
|
thereafter produces random values via repeated hashing. As a result, `fastrand`
|
||||||
|
can generate randomness much faster than `crypto/rand`, and generation cannot
|
||||||
|
fail beyond a potential panic during `init()`.
|
||||||
|
|
||||||
|
`fastrand` also scales better than `crypto/rand` and `math/rand` when called in
|
||||||
|
parallel. In fact, `fastrand` can even outperform `math/rand` when using enough threads.
|
||||||
|
|
||||||
|
|
||||||
|
## Benchmarks ##
|
||||||
|
|
||||||
|
```
|
||||||
|
// 32 byte reads
|
||||||
|
BenchmarkRead32 10000000 175 ns/op 181.86 MB/s
|
||||||
|
BenchmarkReadCrypto32 500000 2733 ns/op 11.71 MB/s
|
||||||
|
|
||||||
|
// 512 kb reads
|
||||||
|
BenchmarkRead512kb 1000 1336217 ns/op 383.17 MB/s
|
||||||
|
BenchmarkReadCrypto512kb 50 33423693 ns/op 15.32 MB/s
|
||||||
|
|
||||||
|
// 32 byte reads using 4 threads
|
||||||
|
BenchmarkRead4Threads32 3000000 392 ns/op 326.46 MB/s
|
||||||
|
BenchmarkReadCrypto4Threads32 200000 7579 ns/op 16.89 MB/s
|
||||||
|
|
||||||
|
// 512 kb reads using 4 threads
|
||||||
|
BenchmarkRead4Threads512kb 1000 1899048 ns/op 1078.43 MB/s
|
||||||
|
BenchmarkReadCrypto4Threads512kb 20 97423380 ns/op 21.02 MB/s
|
||||||
|
```
|
||||||
|
|
||||||
|
## Security ##
|
||||||
|
|
||||||
|
`fastrand` uses an algorithm similar to Fortuna, which is the basis for the
|
||||||
|
`/dev/random` device in FreeBSD. However, although the techniques used by
|
||||||
|
`fastrand` are known to be secure, the specific implementation has not been
|
||||||
|
reviewed by a security professional. Use with caution.
|
||||||
|
|
||||||
|
The general strategy is to use `crypto/rand` at init to get 32 bytes of strong
|
||||||
|
entropy. From there, the entropy is concatenated to a counter and hashed
|
||||||
|
repeatedly, providing 64 bytes of random output each time the counter is
|
||||||
|
incremented. The counter is 16 bytes, which provides strong guarantees that a
|
||||||
|
cycle will not be seen throughout the lifetime of the program.
|
||||||
|
|
||||||
|
The `sync/atomic` package is used to ensure that multiple threads calling
|
||||||
|
`fastrand` concurrently are always guaranteed to end up with unique counters.
|
|
@ -0,0 +1,174 @@
|
||||||
|
// Package fastrand implements a cryptographically secure pseudorandom number
|
||||||
|
// generator. The generator is seeded using the system's default entropy source,
|
||||||
|
// and thereafter produces random values via repeated hashing. As a result,
|
||||||
|
// fastrand can generate randomness much faster than crypto/rand, and generation
|
||||||
|
// cannot fail beyond a potential panic at init.
|
||||||
|
//
|
||||||
|
// The method used in this package is similar to the Fortuna algorithm, which is
|
||||||
|
// used in used in FreeBSD for /dev/urandom. This package uses techniques that
|
||||||
|
// are known to be secure, however the exact implementation has not been heavily
|
||||||
|
// reviewed by cryptographers.
|
||||||
|
package fastrand
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
|
"math"
|
||||||
|
"math/big"
|
||||||
|
"strconv"
|
||||||
|
"sync/atomic"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/blake2b"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A randReader produces random values via repeated hashing. The entropy field
|
||||||
|
// is the concatenation of an initial seed and a 128-bit counter. Each time
|
||||||
|
// the entropy is hashed, the counter is incremented.
|
||||||
|
type randReader struct {
|
||||||
|
counter uint64 // First 64 bits of the counter.
|
||||||
|
counterExtra uint64 // Second 64 bits of the counter.
|
||||||
|
entropy [32]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reader is a global, shared instance of a cryptographically strong pseudo-
|
||||||
|
// random generator. It uses blake2b as its hashing function. Reader is safe
|
||||||
|
// for concurrent use by multiple goroutines.
|
||||||
|
var Reader io.Reader
|
||||||
|
|
||||||
|
// init provides the initial entropy for the reader that will seed all numbers
|
||||||
|
// coming out of fastrand.
|
||||||
|
func init() {
|
||||||
|
r := &randReader{}
|
||||||
|
n, err := rand.Read(r.entropy[:])
|
||||||
|
if err != nil || n != len(r.entropy) {
|
||||||
|
panic("not enough entropy to fill fastrand reader at startup")
|
||||||
|
}
|
||||||
|
Reader = r
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read fills b with random data. It always returns len(b), nil.
|
||||||
|
func (r *randReader) Read(b []byte) (int, error) {
|
||||||
|
if len(b) == 0 {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
// Grab a unique counter from the reader, while atomically updating the
|
||||||
|
// counter so that concurrent callers also end up with unique values.
|
||||||
|
counter := atomic.AddUint64(&r.counter, 1)
|
||||||
|
counterExtra := atomic.LoadUint64(&r.counterExtra)
|
||||||
|
|
||||||
|
// Increment counterExtra when counter is close to overflowing. We cannot
|
||||||
|
// wait until counter == math.MaxUint64 to increment counterExtra, because
|
||||||
|
// another goroutine could call Read, overflowing counter to 0 before the
|
||||||
|
// first goroutine increments counterExtra. The second goroutine would then
|
||||||
|
// be reusing the counter pair (0, 0). Instead, we increment at 1<<63 so
|
||||||
|
// that there is little risk of an overflow.
|
||||||
|
//
|
||||||
|
// There is still a potential overlap near 1<<63, though, because another
|
||||||
|
// goroutine could see counter == 1<<63+1 before the first goroutine
|
||||||
|
// increments counterExtra. The counter pair (1<<63+1, 1) would then be
|
||||||
|
// reused. To prevent this, we also increment at math.MaxUint64. This means
|
||||||
|
// that in order for an overlap to occur, 1<<63 goroutine would need to
|
||||||
|
// increment counter before the first goroutine increments counterExtra.
|
||||||
|
//
|
||||||
|
// This strategy means that many counters will be omitted, and that the
|
||||||
|
// total space cycle time is potentially as low as 2^126. This is fine
|
||||||
|
// however, as the security model merely mandates that no counter is ever
|
||||||
|
// used twice.
|
||||||
|
if counter == 1<<63 || counter == math.MaxUint64 {
|
||||||
|
atomic.AddUint64(&r.counterExtra, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy the counter and entropy into a separate slice, so that the result
|
||||||
|
// may be used in isolation of the other threads. The counter ensures that
|
||||||
|
// the result is unique to this thread.
|
||||||
|
seed := make([]byte, 64)
|
||||||
|
binary.LittleEndian.PutUint64(seed[0:8], counter)
|
||||||
|
binary.LittleEndian.PutUint64(seed[8:16], counterExtra)
|
||||||
|
// Leave 16 bytes for the inner counter.
|
||||||
|
copy(seed[32:], r.entropy[:])
|
||||||
|
|
||||||
|
// Set up an inner counter, that can be incremented to produce unique
|
||||||
|
// entropy within this thread.
|
||||||
|
n := 0
|
||||||
|
innerCounter := uint64(0)
|
||||||
|
innerCounterExtra := uint64(0)
|
||||||
|
for n < len(b) {
|
||||||
|
// Copy in the inner counter values.
|
||||||
|
binary.LittleEndian.PutUint64(seed[16:24], innerCounter)
|
||||||
|
binary.LittleEndian.PutUint64(seed[24:32], innerCounterExtra)
|
||||||
|
|
||||||
|
// Hash the seed to produce the next set of entropy.
|
||||||
|
result := blake2b.Sum512(seed)
|
||||||
|
n += copy(b[n:], result[:])
|
||||||
|
|
||||||
|
// Increment the inner counter. Because we are the only thread accessing
|
||||||
|
// the counter, we can wait until the first 64 bits have reached their
|
||||||
|
// maximum value before incrementing the next 64 bits.
|
||||||
|
innerCounter++
|
||||||
|
if innerCounter == math.MaxUint64 {
|
||||||
|
innerCounterExtra++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read is a helper function that calls Reader.Read on b. It always fills b
|
||||||
|
// completely.
|
||||||
|
func Read(b []byte) { Reader.Read(b) }
|
||||||
|
|
||||||
|
// Bytes is a helper function that returns n bytes of random data.
|
||||||
|
func Bytes(n int) []byte {
|
||||||
|
b := make([]byte, n)
|
||||||
|
Read(b)
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint64n returns a uniform random uint64 in [0,n). It panics if n == 0.
|
||||||
|
func Uint64n(n uint64) uint64 {
|
||||||
|
if n == 0 {
|
||||||
|
panic("fastrand: argument to Uint64n is 0")
|
||||||
|
}
|
||||||
|
// To eliminate modulo bias, keep selecting at random until we fall within
|
||||||
|
// a range that is evenly divisible by n.
|
||||||
|
// NOTE: since n is at most math.MaxUint64, max is minimized when:
|
||||||
|
// n = math.MaxUint64/2 + 1 -> max = math.MaxUint64 - math.MaxUint64/2
|
||||||
|
// This gives an expected 2 tries before choosing a value < max.
|
||||||
|
max := math.MaxUint64 - math.MaxUint64%n
|
||||||
|
b := Bytes(8)
|
||||||
|
r := *(*uint64)(unsafe.Pointer(&b[0]))
|
||||||
|
for r >= max {
|
||||||
|
Read(b)
|
||||||
|
r = *(*uint64)(unsafe.Pointer(&b[0]))
|
||||||
|
}
|
||||||
|
return r % n
|
||||||
|
}
|
||||||
|
|
||||||
|
// Intn returns a uniform random int in [0,n). It panics if n <= 0.
|
||||||
|
func Intn(n int) int {
|
||||||
|
if n <= 0 {
|
||||||
|
panic("fastrand: argument to Intn is <= 0: " + strconv.Itoa(n))
|
||||||
|
}
|
||||||
|
// NOTE: since n is at most math.MaxUint64/2, max is minimized when:
|
||||||
|
// n = math.MaxUint64/4 + 1 -> max = math.MaxUint64 - math.MaxUint64/4
|
||||||
|
// This gives an expected 1.333 tries before choosing a value < max.
|
||||||
|
return int(Uint64n(uint64(n)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// BigIntn returns a uniform random *big.Int in [0,n). It panics if n <= 0.
|
||||||
|
func BigIntn(n *big.Int) *big.Int {
|
||||||
|
i, _ := rand.Int(Reader, n)
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perm returns a random permutation of the integers [0,n).
|
||||||
|
func Perm(n int) []int {
|
||||||
|
m := make([]int, n)
|
||||||
|
for i := 1; i < n; i++ {
|
||||||
|
j := Intn(i + 1)
|
||||||
|
m[i] = m[j]
|
||||||
|
m[j] = i
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
language: go
|
||||||
|
sudo: false
|
||||||
|
go:
|
||||||
|
- 1.9
|
||||||
|
- tip
|
||||||
|
script: "go test -v -race ./..."
|
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2017 Ben Burkert
|
||||||
|
|
||||||
|
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,3 @@
|
||||||
|
# dns [![GoDoc](https://godoc.org/github.com/benburkert/dns?status.svg)](https://godoc.org/github.com/benburkert/dns) [![Build Status](https://travis-ci.org/benburkert/dns.svg)](https://travis-ci.org/benburkert/dns) [![Go Report Card](https://goreportcard.com/badge/github.com/benburkert/dns)](https://goreportcard.com/report/github.com/benburkert/dns)
|
||||||
|
|
||||||
|
DNS client and server package. [See godoc for details & examples.](https://godoc.org/github.com/benburkert/dns)
|
|
@ -0,0 +1,155 @@
|
||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"math/rand"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Cache is a DNS query cache handler.
|
||||||
|
type Cache struct {
|
||||||
|
mu sync.RWMutex
|
||||||
|
cache map[Question]*Message
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServeDNS answers query questions from a local cache, and forwards unanswered
|
||||||
|
// questions upstream, then caches the answers from the response.
|
||||||
|
func (c *Cache) ServeDNS(ctx context.Context, w MessageWriter, r *Query) {
|
||||||
|
var (
|
||||||
|
miss bool
|
||||||
|
|
||||||
|
now = time.Now()
|
||||||
|
)
|
||||||
|
|
||||||
|
c.mu.RLock()
|
||||||
|
for _, q := range r.Questions {
|
||||||
|
if hit := c.lookup(q, w, now); !hit {
|
||||||
|
miss = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.mu.RUnlock()
|
||||||
|
|
||||||
|
if !miss {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
msg, err := w.Recur(ctx)
|
||||||
|
if err != nil || msg == nil {
|
||||||
|
w.Status(ServFail)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if msg.RCode == NoError {
|
||||||
|
c.insert(msg, now)
|
||||||
|
}
|
||||||
|
writeMessage(w, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// c.mu.RLock held
|
||||||
|
func (c *Cache) lookup(q Question, w MessageWriter, now time.Time) bool {
|
||||||
|
msg, ok := c.cache[q]
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
var answers, authorities, additionals []Resource
|
||||||
|
|
||||||
|
for _, res := range msg.Answers {
|
||||||
|
if res.TTL = cacheTTL(res.TTL, now); res.TTL <= 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
answers = append(answers, res)
|
||||||
|
}
|
||||||
|
for _, res := range msg.Authorities {
|
||||||
|
if res.TTL = cacheTTL(res.TTL, now); res.TTL <= 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
authorities = append(authorities, res)
|
||||||
|
}
|
||||||
|
for _, res := range msg.Additionals {
|
||||||
|
if res.TTL = cacheTTL(res.TTL, now); res.TTL <= 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
additionals = append(additionals, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
randomize(answers)
|
||||||
|
for _, res := range answers {
|
||||||
|
w.Answer(res.Name, res.TTL, res.Record)
|
||||||
|
}
|
||||||
|
for _, res := range authorities {
|
||||||
|
w.Authority(res.Name, res.TTL, res.Record)
|
||||||
|
}
|
||||||
|
for _, res := range additionals {
|
||||||
|
w.Additional(res.Name, res.TTL, res.Record)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cache) insert(msg *Message, now time.Time) {
|
||||||
|
cache := make(map[Question]*Message, len(msg.Questions))
|
||||||
|
for _, q := range msg.Questions {
|
||||||
|
m := new(Message)
|
||||||
|
for _, res := range msg.Answers {
|
||||||
|
res.TTL = cacheEpoch(res.TTL, now)
|
||||||
|
m.Answers = append(m.Answers, res)
|
||||||
|
}
|
||||||
|
for _, res := range msg.Authorities {
|
||||||
|
res.TTL = cacheEpoch(res.TTL, now)
|
||||||
|
m.Authorities = append(m.Authorities, res)
|
||||||
|
}
|
||||||
|
for _, res := range msg.Additionals {
|
||||||
|
res.TTL = cacheEpoch(res.TTL, now)
|
||||||
|
m.Additionals = append(m.Additionals, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
cache[q] = m
|
||||||
|
}
|
||||||
|
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
|
||||||
|
if c.cache == nil {
|
||||||
|
c.cache = cache
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for q, m := range cache {
|
||||||
|
c.cache[q] = m
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func cacheEpoch(ttl time.Duration, now time.Time) time.Duration {
|
||||||
|
return time.Duration(now.Add(ttl).UnixNano())
|
||||||
|
}
|
||||||
|
|
||||||
|
func cacheTTL(epoch time.Duration, now time.Time) time.Duration {
|
||||||
|
return time.Unix(0, int64(epoch)).Sub(now)
|
||||||
|
}
|
||||||
|
|
||||||
|
// randomize shuffles contigous groups of resourcesfor the same name.
|
||||||
|
func randomize(s []Resource) {
|
||||||
|
var low, high int
|
||||||
|
for low = 0; low < len(s)-1; low++ {
|
||||||
|
for high = low + 1; high < len(s) && s[low].Name == s[high].Name; high++ {
|
||||||
|
}
|
||||||
|
|
||||||
|
shuffle(s[low:high])
|
||||||
|
low = high
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func shuffle(s []Resource) {
|
||||||
|
if len(s) < 2 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := len(s) - 1; i > 0; i-- {
|
||||||
|
j := rand.Intn(i + 1)
|
||||||
|
s[i], s[j] = s[j], s[i]
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,219 @@
|
||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"sync/atomic"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Client is a DNS client.
|
||||||
|
type Client struct {
|
||||||
|
// Transport manages connections to DNS servers.
|
||||||
|
Transport AddrDialer
|
||||||
|
|
||||||
|
// Resolver is a handler that may answer all or portions of a query.
|
||||||
|
// Any questions answered by the handler are not sent to the upstream
|
||||||
|
// server.
|
||||||
|
Resolver Handler
|
||||||
|
|
||||||
|
id uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dial dials a DNS server and returns a net Conn that reads and writes DNS
|
||||||
|
// messages.
|
||||||
|
func (c *Client) Dial(ctx context.Context, network, address string) (net.Conn, error) {
|
||||||
|
switch network {
|
||||||
|
case "tcp", "tcp4", "tcp6":
|
||||||
|
addr, err := net.ResolveTCPAddr(network, address)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := c.dial(ctx, addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &streamSession{
|
||||||
|
session: session{
|
||||||
|
Conn: conn,
|
||||||
|
addr: addr,
|
||||||
|
client: c,
|
||||||
|
msgerrc: make(chan msgerr),
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
case "udp", "udp4", "udp6":
|
||||||
|
addr, err := net.ResolveUDPAddr(network, address)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := c.dial(ctx, addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &packetSession{
|
||||||
|
session: session{
|
||||||
|
Conn: conn,
|
||||||
|
addr: addr,
|
||||||
|
client: c,
|
||||||
|
msgerrc: make(chan msgerr),
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
default:
|
||||||
|
return nil, ErrUnsupportedNetwork
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do sends a DNS query to a server and returns the response message.
|
||||||
|
func (c *Client) Do(ctx context.Context, query *Query) (*Message, error) {
|
||||||
|
conn, err := c.dial(ctx, query.RemoteAddr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if t, ok := ctx.Deadline(); ok {
|
||||||
|
if err := conn.SetDeadline(t); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.do(ctx, conn, query)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) dial(ctx context.Context, addr net.Addr) (Conn, error) {
|
||||||
|
tport := c.Transport
|
||||||
|
if tport == nil {
|
||||||
|
tport = new(Transport)
|
||||||
|
}
|
||||||
|
|
||||||
|
return tport.DialAddr(ctx, addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) do(ctx context.Context, conn Conn, query *Query) (*Message, error) {
|
||||||
|
if c.Resolver == nil {
|
||||||
|
return c.roundtrip(conn, query)
|
||||||
|
}
|
||||||
|
|
||||||
|
w := &clientWriter{
|
||||||
|
messageWriter: &messageWriter{
|
||||||
|
msg: response(query.Message),
|
||||||
|
},
|
||||||
|
|
||||||
|
req: request(query.Message),
|
||||||
|
addr: query.RemoteAddr,
|
||||||
|
conn: conn,
|
||||||
|
|
||||||
|
roundtrip: c.roundtrip,
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Resolver.ServeDNS(ctx, w, query)
|
||||||
|
if w.err != nil {
|
||||||
|
return nil, w.err
|
||||||
|
}
|
||||||
|
return response(w.msg), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) roundtrip(conn Conn, query *Query) (*Message, error) {
|
||||||
|
id := query.ID
|
||||||
|
|
||||||
|
msg := *query.Message
|
||||||
|
msg.ID = c.nextID()
|
||||||
|
|
||||||
|
if err := conn.Send(&msg); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := conn.Recv(&msg); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
msg.ID = id
|
||||||
|
|
||||||
|
return &msg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const idMask = (1 << 16) - 1
|
||||||
|
|
||||||
|
func (c *Client) nextID() int {
|
||||||
|
return int(atomic.AddUint32(&c.id, 1) & idMask)
|
||||||
|
}
|
||||||
|
|
||||||
|
type clientWriter struct {
|
||||||
|
*messageWriter
|
||||||
|
|
||||||
|
req *Message
|
||||||
|
err error
|
||||||
|
|
||||||
|
addr net.Addr
|
||||||
|
conn Conn
|
||||||
|
|
||||||
|
roundtrip func(Conn, *Query) (*Message, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *clientWriter) Recur(context.Context) (*Message, error) {
|
||||||
|
qs := make([]Question, 0, len(w.req.Questions))
|
||||||
|
for _, q := range w.req.Questions {
|
||||||
|
if !questionMatched(q, w.msg) {
|
||||||
|
qs = append(qs, q)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.req.Questions = qs
|
||||||
|
|
||||||
|
req := &Query{
|
||||||
|
Message: w.req,
|
||||||
|
RemoteAddr: w.addr,
|
||||||
|
}
|
||||||
|
|
||||||
|
msg, err := w.roundtrip(w.conn, req)
|
||||||
|
if err != nil {
|
||||||
|
w.err = err
|
||||||
|
}
|
||||||
|
|
||||||
|
return msg, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *clientWriter) Reply(context.Context) error {
|
||||||
|
return ErrUnsupportedOp
|
||||||
|
}
|
||||||
|
|
||||||
|
func request(msg *Message) *Message {
|
||||||
|
req := new(Message)
|
||||||
|
*req = *msg // shallow copy
|
||||||
|
|
||||||
|
return req
|
||||||
|
}
|
||||||
|
|
||||||
|
func questionMatched(q Question, msg *Message) bool {
|
||||||
|
mrs := [3][]Resource{
|
||||||
|
msg.Answers,
|
||||||
|
msg.Authorities,
|
||||||
|
msg.Additionals,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, rs := range mrs {
|
||||||
|
for _, res := range rs {
|
||||||
|
if res.Name == q.Name {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeMessage(w MessageWriter, msg *Message) {
|
||||||
|
w.Status(msg.RCode)
|
||||||
|
w.Authoritative(msg.Authoritative)
|
||||||
|
w.Recursion(msg.RecursionAvailable)
|
||||||
|
|
||||||
|
for _, res := range msg.Answers {
|
||||||
|
w.Answer(res.Name, res.TTL, res.Record)
|
||||||
|
}
|
||||||
|
for _, res := range msg.Authorities {
|
||||||
|
w.Authority(res.Name, res.TTL, res.Record)
|
||||||
|
}
|
||||||
|
for _, res := range msg.Additionals {
|
||||||
|
w.Additional(res.Name, res.TTL, res.Record)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,177 @@
|
||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Compressor encodes domain names.
|
||||||
|
type Compressor interface {
|
||||||
|
Length(...string) int
|
||||||
|
Pack([]byte, string) ([]byte, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decompressor decodes domain names.
|
||||||
|
type Decompressor interface {
|
||||||
|
Unpack([]byte) (string, []byte, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type compressor struct {
|
||||||
|
tbl map[string]int
|
||||||
|
offset int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c compressor) Length(names ...string) int {
|
||||||
|
var visited map[string]struct{}
|
||||||
|
if c.tbl != nil {
|
||||||
|
visited = make(map[string]struct{})
|
||||||
|
}
|
||||||
|
|
||||||
|
var n int
|
||||||
|
for _, name := range names {
|
||||||
|
n += c.length(name, visited)
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c compressor) length(name string, visited map[string]struct{}) int {
|
||||||
|
if name == "." || name == "" {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.tbl != nil {
|
||||||
|
if _, ok := c.tbl[name]; ok {
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
if _, ok := visited[name]; ok {
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
|
||||||
|
visited[name] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
pvt := strings.IndexByte(name, '.')
|
||||||
|
return pvt + 1 + c.length(name[pvt+1:], visited)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c compressor) Pack(b []byte, fqdn string) ([]byte, error) {
|
||||||
|
if fqdn == "." || fqdn == "" {
|
||||||
|
return append(b, 0x00), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.tbl != nil {
|
||||||
|
if idx, ok := c.tbl[fqdn]; ok {
|
||||||
|
ptr, err := pointerTo(idx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return append(b, ptr...), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pvt := strings.IndexByte(fqdn, '.')
|
||||||
|
if pvt == 0 {
|
||||||
|
return nil, errZeroSegLen
|
||||||
|
}
|
||||||
|
if pvt > 63 {
|
||||||
|
return nil, errSegTooLong
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.tbl != nil {
|
||||||
|
idx := len(b) - c.offset
|
||||||
|
if int(uint16(idx)) != idx {
|
||||||
|
return nil, errInvalidPtr
|
||||||
|
}
|
||||||
|
c.tbl[fqdn] = idx
|
||||||
|
}
|
||||||
|
|
||||||
|
b = append(b, byte(pvt))
|
||||||
|
b = append(b, fqdn[:pvt]...)
|
||||||
|
|
||||||
|
return c.Pack(b, fqdn[pvt+1:])
|
||||||
|
}
|
||||||
|
|
||||||
|
type decompressor []byte
|
||||||
|
|
||||||
|
func (d decompressor) Unpack(b []byte) (string, []byte, error) {
|
||||||
|
name, b, err := d.unpack(make([]byte, 0, 32), b, nil)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
return string(name), b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d decompressor) unpack(name, b []byte, visited []int) ([]byte, []byte, error) {
|
||||||
|
lenb := len(b)
|
||||||
|
if lenb == 0 {
|
||||||
|
return nil, nil, errBaseLen
|
||||||
|
}
|
||||||
|
if b[0] == 0x00 {
|
||||||
|
if len(name) == 0 {
|
||||||
|
return append(name, '.'), b[1:], nil
|
||||||
|
}
|
||||||
|
return name, b[1:], nil
|
||||||
|
}
|
||||||
|
if lenb < 2 {
|
||||||
|
return nil, nil, errBaseLen
|
||||||
|
}
|
||||||
|
|
||||||
|
if isPointer(b[0]) {
|
||||||
|
if d == nil {
|
||||||
|
return nil, nil, errBaseLen
|
||||||
|
}
|
||||||
|
|
||||||
|
ptr := nbo.Uint16(b[:2])
|
||||||
|
name, err := d.deref(name, ptr, visited)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return name, b[2:], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
lenl, b := int(b[0]), b[1:]
|
||||||
|
|
||||||
|
if len(b) < lenl {
|
||||||
|
return nil, nil, errCalcLen
|
||||||
|
}
|
||||||
|
|
||||||
|
name = append(name, b[:lenl]...)
|
||||||
|
name = append(name, '.')
|
||||||
|
|
||||||
|
return d.unpack(name, b[lenl:], visited)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d decompressor) deref(name []byte, ptr uint16, visited []int) ([]byte, error) {
|
||||||
|
idx := int(ptr & 0x3FFF)
|
||||||
|
if len(d) < idx {
|
||||||
|
return nil, errInvalidPtr
|
||||||
|
}
|
||||||
|
|
||||||
|
if isPointer(d[idx]) {
|
||||||
|
return nil, errInvalidPtr
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range visited {
|
||||||
|
if idx == v {
|
||||||
|
return nil, errPtrCycle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
name, _, err := d.unpack(name, d[idx:], append(visited, idx))
|
||||||
|
return name, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func isPointer(b byte) bool { return b&0xC0 > 0 }
|
||||||
|
|
||||||
|
func pointerTo(idx int) ([]byte, error) {
|
||||||
|
ptr := uint16(idx)
|
||||||
|
if int(ptr) != idx {
|
||||||
|
return nil, errInvalidPtr
|
||||||
|
}
|
||||||
|
ptr |= 0xC000
|
||||||
|
|
||||||
|
buf := [2]byte{}
|
||||||
|
nbo.PutUint16(buf[:], ptr)
|
||||||
|
return buf[:], nil
|
||||||
|
}
|
|
@ -0,0 +1,113 @@
|
||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Conn is a network connection to a DNS resolver.
|
||||||
|
type Conn interface {
|
||||||
|
net.Conn
|
||||||
|
|
||||||
|
// Recv reads a DNS message from the connection.
|
||||||
|
Recv(msg *Message) error
|
||||||
|
|
||||||
|
// Send writes a DNS message to the connection.
|
||||||
|
Send(msg *Message) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// PacketConn is a packet-oriented network connection to a DNS resolver that
|
||||||
|
// expects transmitted messages to adhere to RFC 1035 Section 4.2.1. "UDP
|
||||||
|
// usage".
|
||||||
|
type PacketConn struct {
|
||||||
|
net.Conn
|
||||||
|
|
||||||
|
rbuf, wbuf []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recv reads a DNS message from the underlying connection.
|
||||||
|
func (c *PacketConn) Recv(msg *Message) error {
|
||||||
|
if len(c.rbuf) != maxPacketLen {
|
||||||
|
c.rbuf = make([]byte, maxPacketLen)
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err := c.Read(c.rbuf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = msg.Unpack(c.rbuf[:n])
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send writes a DNS message to the underlying connection.
|
||||||
|
func (c *PacketConn) Send(msg *Message) error {
|
||||||
|
if len(c.wbuf) != maxPacketLen {
|
||||||
|
c.wbuf = make([]byte, maxPacketLen)
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
if c.wbuf, err = msg.Pack(c.wbuf[:0], true); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(c.wbuf) > maxPacketLen {
|
||||||
|
return ErrOversizedMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = c.Write(c.wbuf)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// StreamConn is a stream-oriented network connection to a DNS resolver that
|
||||||
|
// expects transmitted messages to adhere to RFC 1035 Section 4.2.2. "TCP
|
||||||
|
// usage".
|
||||||
|
type StreamConn struct {
|
||||||
|
net.Conn
|
||||||
|
|
||||||
|
rbuf, wbuf []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recv reads a DNS message from the underlying connection.
|
||||||
|
func (c *StreamConn) Recv(msg *Message) error {
|
||||||
|
if len(c.rbuf) < 2 {
|
||||||
|
c.rbuf = make([]byte, 1280)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := io.ReadFull(c, c.rbuf[:2]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
mlen := nbo.Uint16(c.rbuf[:2])
|
||||||
|
if len(c.rbuf) < int(mlen) {
|
||||||
|
c.rbuf = make([]byte, mlen)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := io.ReadFull(c, c.rbuf[:mlen]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := msg.Unpack(c.rbuf[:mlen])
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send writes a DNS message to the underlying connection.
|
||||||
|
func (c *StreamConn) Send(msg *Message) error {
|
||||||
|
if len(c.wbuf) < 2 {
|
||||||
|
c.wbuf = make([]byte, 1024)
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := msg.Pack(c.wbuf[2:2], true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
mlen := uint16(len(b))
|
||||||
|
if int(mlen) != len(b) {
|
||||||
|
return ErrOversizedMessage
|
||||||
|
}
|
||||||
|
nbo.PutUint16(c.wbuf[:2], mlen)
|
||||||
|
|
||||||
|
_, err = c.Write(c.wbuf[:len(b)+2])
|
||||||
|
return err
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrConflictingID is a pipelining error due to the same message ID being
|
||||||
|
// used for more than one inflight query.
|
||||||
|
ErrConflictingID = errors.New("conflicting message id")
|
||||||
|
|
||||||
|
// ErrOversizedMessage is an error returned when attempting to send a
|
||||||
|
// message that is longer than the maximum allowed number of bytes.
|
||||||
|
ErrOversizedMessage = errors.New("oversized message")
|
||||||
|
|
||||||
|
// ErrTruncatedMessage indicates the response message has been truncated.
|
||||||
|
ErrTruncatedMessage = errors.New("truncated message")
|
||||||
|
|
||||||
|
// ErrUnsupportedNetwork is returned when DialAddr is called with an
|
||||||
|
// unknown network.
|
||||||
|
ErrUnsupportedNetwork = errors.New("unsupported network")
|
||||||
|
|
||||||
|
// ErrUnsupportedOp indicates the operation is not supported by callee.
|
||||||
|
ErrUnsupportedOp = errors.New("unsupported operation")
|
||||||
|
)
|
||||||
|
|
||||||
|
// AddrDialer dials a net Addr.
|
||||||
|
type AddrDialer interface {
|
||||||
|
DialAddr(context.Context, net.Addr) (Conn, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query is a DNS request message bound for a DNS resolver.
|
||||||
|
type Query struct {
|
||||||
|
*Message
|
||||||
|
|
||||||
|
// RemoteAddr is the address of a DNS resolver.
|
||||||
|
RemoteAddr net.Addr
|
||||||
|
}
|
||||||
|
|
||||||
|
// OverTLSAddr indicates the remote DNS service implements DNS-over-TLS as
|
||||||
|
// defined in RFC 7858.
|
||||||
|
type OverTLSAddr struct {
|
||||||
|
net.Addr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Network returns the address's network name with a "-tls" suffix.
|
||||||
|
func (a OverTLSAddr) Network() string {
|
||||||
|
return a.Addr.Network() + "-tls"
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProxyFunc modifies the address of a DNS server.
|
||||||
|
type ProxyFunc func(context.Context, net.Addr) (net.Addr, error)
|
||||||
|
|
||||||
|
// RoundTripper is an interface representing the ability to execute a single
|
||||||
|
// DNS transaction, obtaining a response Message for a given Query.
|
||||||
|
type RoundTripper interface {
|
||||||
|
Do(context.Context, *Query) (*Message, error)
|
||||||
|
}
|
|
@ -0,0 +1,76 @@
|
||||||
|
/*
|
||||||
|
Package dns provides DNS client and server implementations.
|
||||||
|
|
||||||
|
A client can handle queries for a net.Dialer:
|
||||||
|
|
||||||
|
dialer := &net.Dialer{
|
||||||
|
Resolver: &net.Resolver{
|
||||||
|
PreferGo: true,
|
||||||
|
|
||||||
|
Dial: new(dns.Client).Dial,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := dialer.DialContext(ctx, "tcp", "example.com:80")
|
||||||
|
|
||||||
|
|
||||||
|
It can also query a remote DNS server directly:
|
||||||
|
|
||||||
|
client := new(dns.Client)
|
||||||
|
query := &dns.Query{
|
||||||
|
RemoteAddr: &net.TCPAddr{IP: net.IPv4(8, 8, 8, 8), Port: 53},
|
||||||
|
|
||||||
|
Message: &dns.Message{
|
||||||
|
Questions: []dns.Question{
|
||||||
|
{
|
||||||
|
Name: "example.com.",
|
||||||
|
Type: dns.TypeA,
|
||||||
|
Class: dns.ClassIN,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "example.com.",
|
||||||
|
Type: dns.TypeAAAA,
|
||||||
|
Class: dns.ClassIN,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
msg, err := client.Do(ctx, query)
|
||||||
|
|
||||||
|
A handler answers queries for a server or a local resolver for a client:
|
||||||
|
|
||||||
|
zone := &dns.Zone{
|
||||||
|
Origin: "localhost.",
|
||||||
|
TTL: 5 * time.Minute,
|
||||||
|
RRs: dns.RRSet{
|
||||||
|
"alpha": []dns.Record{
|
||||||
|
&dns.A{net.IPv4(127, 0, 0, 42).To4()},
|
||||||
|
&dns.AAAA{net.ParseIP("::42")},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
srv := &dns.Server{
|
||||||
|
Addr: ":53",
|
||||||
|
Handler: zone,
|
||||||
|
}
|
||||||
|
|
||||||
|
go srv.ListenAndServe(ctx)
|
||||||
|
|
||||||
|
mux := new(dns.ResolveMux)
|
||||||
|
mux.Handle(dns.TypeANY, zone.Origin, zone)
|
||||||
|
|
||||||
|
client := &dns.Client{
|
||||||
|
Resolver: mux,
|
||||||
|
}
|
||||||
|
|
||||||
|
net.DefaultResolver = &net.Resolver{
|
||||||
|
PreferGo: true,
|
||||||
|
Dial: client.Dial,
|
||||||
|
}
|
||||||
|
|
||||||
|
addrs, err := net.LookupHost("alpha.localhost")
|
||||||
|
|
||||||
|
*/
|
||||||
|
package dns
|
|
@ -0,0 +1,247 @@
|
||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Handler responds to a DNS query.
|
||||||
|
//
|
||||||
|
// ServeDNS should build the reply message using the MessageWriter, and may
|
||||||
|
// optionally call the Reply method. Returning signals that the request is
|
||||||
|
// finished and the response is ready to send.
|
||||||
|
//
|
||||||
|
// A recursive handler may call the Recur method of the MessageWriter to send
|
||||||
|
// an query upstream. Only unanswered questions are included in the upstream
|
||||||
|
// query.
|
||||||
|
type Handler interface {
|
||||||
|
ServeDNS(context.Context, MessageWriter, *Query)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The HandlerFunc type is an adapter to allow the use of ordinary functions as
|
||||||
|
// DNS handlers. If f is a function with the appropriate signature,
|
||||||
|
// HandlerFunc(f) is a Handler that calls f.
|
||||||
|
type HandlerFunc func(context.Context, MessageWriter, *Query)
|
||||||
|
|
||||||
|
// ServeDNS calls f(w, r).
|
||||||
|
func (f HandlerFunc) ServeDNS(ctx context.Context, w MessageWriter, r *Query) {
|
||||||
|
f(ctx, w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recursor forwards a query and copies the response.
|
||||||
|
func Recursor(ctx context.Context, w MessageWriter, r *Query) {
|
||||||
|
msg, err := w.Recur(ctx)
|
||||||
|
if err != nil {
|
||||||
|
w.Status(ServFail)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
writeMessage(w, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refuse responds to all queries with a "Query Refused" message.
|
||||||
|
func Refuse(ctx context.Context, w MessageWriter, r *Query) {
|
||||||
|
w.Status(Refused)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResolveMux is a DNS query multiplexer. It matches a question type and name
|
||||||
|
// suffix to a Handler.
|
||||||
|
type ResolveMux struct {
|
||||||
|
tbl []muxEntry
|
||||||
|
}
|
||||||
|
|
||||||
|
type muxEntry struct {
|
||||||
|
typ Type
|
||||||
|
suffix string
|
||||||
|
h Handler
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle registers the handler for the given question type and name suffix.
|
||||||
|
func (m *ResolveMux) Handle(typ Type, suffix string, h Handler) {
|
||||||
|
m.tbl = append(m.tbl, muxEntry{typ: typ, suffix: suffix, h: h})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServeDNS dispatches the query to the handler(s) whose pattern most closely
|
||||||
|
// matches each question.
|
||||||
|
func (m *ResolveMux) ServeDNS(ctx context.Context, w MessageWriter, r *Query) {
|
||||||
|
var muxw *muxWriter
|
||||||
|
for _, q := range r.Questions {
|
||||||
|
h := m.lookup(q)
|
||||||
|
|
||||||
|
muxm := new(Message)
|
||||||
|
*muxm = *r.Message
|
||||||
|
muxm.Questions = []Question{q}
|
||||||
|
|
||||||
|
muxr := new(Query)
|
||||||
|
*muxr = *r
|
||||||
|
muxr.Message = muxm
|
||||||
|
|
||||||
|
muxw = &muxWriter{
|
||||||
|
messageWriter: &messageWriter{
|
||||||
|
msg: response(muxr.Message),
|
||||||
|
},
|
||||||
|
|
||||||
|
recurc: make(chan msgerr),
|
||||||
|
replyc: make(chan msgerr),
|
||||||
|
|
||||||
|
next: muxw,
|
||||||
|
}
|
||||||
|
|
||||||
|
go m.serveMux(ctx, h, muxw, muxr)
|
||||||
|
}
|
||||||
|
|
||||||
|
if me, ok := <-muxw.recurc; ok {
|
||||||
|
writeMessage(w, me.msg)
|
||||||
|
msg, err := w.Recur(ctx)
|
||||||
|
muxw.recurc <- msgerr{msg, err}
|
||||||
|
}
|
||||||
|
|
||||||
|
me := <-muxw.replyc
|
||||||
|
writeMessage(w, me.msg)
|
||||||
|
|
||||||
|
if err := w.Reply(ctx); err != nil {
|
||||||
|
muxw.replyc <- msgerr{nil, err}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var recursiveHandler = HandlerFunc(func(ctx context.Context, w MessageWriter, r *Query) {
|
||||||
|
msg, err := w.Recur(ctx)
|
||||||
|
if err != nil {
|
||||||
|
w.Status(ServFail)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Status(msg.RCode)
|
||||||
|
w.Authoritative(msg.Authoritative)
|
||||||
|
w.Recursion(msg.RecursionAvailable)
|
||||||
|
|
||||||
|
for _, rec := range msg.Answers {
|
||||||
|
w.Answer(rec.Name, rec.TTL, rec.Record)
|
||||||
|
}
|
||||||
|
for _, rec := range msg.Authorities {
|
||||||
|
w.Authority(rec.Name, rec.TTL, rec.Record)
|
||||||
|
}
|
||||||
|
for _, rec := range msg.Additionals {
|
||||||
|
w.Additional(rec.Name, rec.TTL, rec.Record)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
func (m *ResolveMux) lookup(q Question) Handler {
|
||||||
|
for _, e := range m.tbl {
|
||||||
|
if e.typ != q.Type && e.typ != TypeANY {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strings.HasSuffix(q.Name, e.suffix) {
|
||||||
|
return e.h
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return recursiveHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *ResolveMux) serveMux(ctx context.Context, h Handler, w *muxWriter, r *Query) {
|
||||||
|
h.ServeDNS(ctx, w, r)
|
||||||
|
w.finish(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
type muxWriter struct {
|
||||||
|
*messageWriter
|
||||||
|
|
||||||
|
recurc, replyc chan msgerr
|
||||||
|
|
||||||
|
next *muxWriter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w muxWriter) Recur(ctx context.Context) (*Message, error) {
|
||||||
|
var (
|
||||||
|
nextOK bool
|
||||||
|
|
||||||
|
msg = request(w.msg)
|
||||||
|
)
|
||||||
|
|
||||||
|
if w.next != nil {
|
||||||
|
var me msgerr
|
||||||
|
if me, nextOK = <-w.next.recurc; nextOK {
|
||||||
|
mergeRequests(msg, me.msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.recurc <- msgerr{msg, nil}
|
||||||
|
|
||||||
|
me := <-w.recurc
|
||||||
|
if nextOK {
|
||||||
|
w.next.recurc <- me
|
||||||
|
}
|
||||||
|
if me.err != nil {
|
||||||
|
return nil, me.err
|
||||||
|
}
|
||||||
|
return responseFor(w.msg.Questions[0], me.msg), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w muxWriter) Reply(ctx context.Context) error {
|
||||||
|
msg := response(w.msg)
|
||||||
|
if w.next != nil {
|
||||||
|
if me, ok := <-w.next.recurc; ok {
|
||||||
|
w.recurc <- me
|
||||||
|
me = <-w.recurc
|
||||||
|
w.next.recurc <- me
|
||||||
|
}
|
||||||
|
|
||||||
|
me, ok := <-w.next.replyc
|
||||||
|
if !ok || me.err != nil {
|
||||||
|
panic("impossible")
|
||||||
|
}
|
||||||
|
mergeResponses(msg, me.msg)
|
||||||
|
}
|
||||||
|
close(w.recurc)
|
||||||
|
w.replyc <- msgerr{msg, nil}
|
||||||
|
|
||||||
|
me := <-w.replyc
|
||||||
|
if w.next != nil {
|
||||||
|
w.next.replyc <- me
|
||||||
|
}
|
||||||
|
|
||||||
|
close(w.replyc)
|
||||||
|
w.replyc = nil
|
||||||
|
|
||||||
|
return me.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w muxWriter) finish(ctx context.Context) {
|
||||||
|
if w.replyc != nil {
|
||||||
|
w.Reply(ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func mergeRequests(to, from *Message) {
|
||||||
|
if from.OpCode > to.OpCode {
|
||||||
|
to.OpCode = from.OpCode
|
||||||
|
}
|
||||||
|
to.RecursionDesired = to.RecursionDesired || from.RecursionDesired
|
||||||
|
to.Questions = append(from.Questions, to.Questions...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func mergeResponses(to, from *Message) {
|
||||||
|
to.Authoritative = to.Authoritative && from.Authoritative
|
||||||
|
to.RecursionAvailable = to.RecursionAvailable || from.RecursionAvailable
|
||||||
|
if from.RCode > to.RCode {
|
||||||
|
to.RCode = from.RCode
|
||||||
|
}
|
||||||
|
to.Questions = append(from.Questions, to.Questions...)
|
||||||
|
to.Answers = append(from.Answers, to.Answers...)
|
||||||
|
to.Authorities = append(from.Authorities, to.Authorities...)
|
||||||
|
to.Additionals = append(from.Additionals, to.Additionals...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func responseFor(q Question, res *Message) *Message {
|
||||||
|
msg := response(res)
|
||||||
|
|
||||||
|
var answers []Resource
|
||||||
|
for _, a := range res.Answers {
|
||||||
|
if a.Name == q.Name {
|
||||||
|
answers = append(answers, a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
msg.Answers = answers
|
||||||
|
|
||||||
|
return msg
|
||||||
|
}
|
|
@ -0,0 +1,819 @@
|
||||||
|
// Copyright 2017 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var nbo = binary.BigEndian
|
||||||
|
|
||||||
|
// A Type is a type of DNS request and response.
|
||||||
|
type Type uint16
|
||||||
|
|
||||||
|
// A Class is a type of network.
|
||||||
|
type Class uint16
|
||||||
|
|
||||||
|
// An OpCode is a DNS operation code.
|
||||||
|
type OpCode uint16
|
||||||
|
|
||||||
|
// An RCode is a DNS response status code.
|
||||||
|
type RCode uint16
|
||||||
|
|
||||||
|
// Domain Name System (DNS) Parameters.
|
||||||
|
//
|
||||||
|
// Taken from https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml
|
||||||
|
const (
|
||||||
|
// Resource Record (RR) TYPEs
|
||||||
|
TypeA Type = 1 // [RFC1035] a host address
|
||||||
|
TypeNS Type = 2 // [RFC1035] an authoritative name server
|
||||||
|
TypeCNAME Type = 5 // [RFC1035] the canonical name for an alias
|
||||||
|
TypeSOA Type = 6 // [RFC1035] marks the start of a zone of authority
|
||||||
|
TypeWKS Type = 11 // [RFC1035] a well known service description
|
||||||
|
TypePTR Type = 12 // [RFC1035] a domain name pointer
|
||||||
|
TypeHINFO Type = 13 // [RFC1035] host information
|
||||||
|
TypeMINFO Type = 14 // [RFC1035] mailbox or mail list information
|
||||||
|
TypeMX Type = 15 // [RFC1035] mail exchange
|
||||||
|
TypeTXT Type = 16 // [RFC1035] text strings
|
||||||
|
TypeAAAA Type = 28 // [RFC3596] IP6 Address
|
||||||
|
TypeSRV Type = 33 // [RFC2782] Server Selection
|
||||||
|
TypeAXFR Type = 252 // [RFC1035][RFC5936] transfer of an entire zone
|
||||||
|
TypeALL Type = 255 // [RFC1035][RFC6895] A request for all records the server/cache has available
|
||||||
|
|
||||||
|
TypeANY Type = 0
|
||||||
|
|
||||||
|
// DNS CLASSes
|
||||||
|
ClassIN Class = 1 // [RFC1035] Internet (IN)
|
||||||
|
ClassCH Class = 3 // [] Chaos (CH)
|
||||||
|
ClassHS Class = 4 // [] Hesiod (HS)
|
||||||
|
ClassANY Class = 255 // [RFC1035] QCLASS * (ANY)
|
||||||
|
|
||||||
|
// DNS RCODEs
|
||||||
|
NoError RCode = 0 // [RFC1035] No Error
|
||||||
|
FormErr RCode = 1 // [RFC1035] Format Error
|
||||||
|
ServFail RCode = 2 // [RFC1035] Server Failure
|
||||||
|
NXDomain RCode = 3 // [RFC1035] Non-Existent Domain
|
||||||
|
NotImp RCode = 4 // [RFC1035] Not Implemented
|
||||||
|
Refused RCode = 5 // [RFC1035] Query Refused
|
||||||
|
|
||||||
|
maxPacketLen = 512
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewRecordByType returns a new instance of a Record for a Type.
|
||||||
|
var NewRecordByType = map[Type]func() Record{
|
||||||
|
TypeA: func() Record { return new(A) },
|
||||||
|
TypeNS: func() Record { return new(NS) },
|
||||||
|
TypeCNAME: func() Record { return new(CNAME) },
|
||||||
|
TypeSOA: func() Record { return new(SOA) },
|
||||||
|
TypePTR: func() Record { return new(PTR) },
|
||||||
|
TypeMX: func() Record { return new(MX) },
|
||||||
|
TypeTXT: func() Record { return new(TXT) },
|
||||||
|
TypeAAAA: func() Record { return new(AAAA) },
|
||||||
|
TypeSRV: func() Record { return new(SRV) },
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrNotStarted indicates that the prerequisite information isn't
|
||||||
|
// available yet because the previous records haven't been appropriately
|
||||||
|
// parsed or skipped.
|
||||||
|
ErrNotStarted = errors.New("parsing of this type isn't available yet")
|
||||||
|
|
||||||
|
// ErrSectionDone indicated that all records in the section have been
|
||||||
|
// parsed.
|
||||||
|
ErrSectionDone = errors.New("parsing of this section has completed")
|
||||||
|
|
||||||
|
errBaseLen = errors.New("insufficient data for base length type")
|
||||||
|
errCalcLen = errors.New("insufficient data for calculated length type")
|
||||||
|
errReserved = errors.New("segment prefix is reserved")
|
||||||
|
errPtrCycle = errors.New("pointer cycle")
|
||||||
|
errInvalidPtr = errors.New("invalid pointer")
|
||||||
|
errResourceLen = errors.New("insufficient data for resource body length")
|
||||||
|
errSegTooLong = errors.New("segment length too long")
|
||||||
|
errZeroSegLen = errors.New("zero length segment")
|
||||||
|
errResTooLong = errors.New("resource length too long")
|
||||||
|
errTooManyQuestions = errors.New("too many Questions to pack (>65535)")
|
||||||
|
errTooManyAnswers = errors.New("too many Answers to pack (>65535)")
|
||||||
|
errTooManyAuthorities = errors.New("too many Authorities to pack (>65535)")
|
||||||
|
errTooManyAdditionals = errors.New("too many Additionals to pack (>65535)")
|
||||||
|
errFieldOverflow = errors.New("value too large for packed field")
|
||||||
|
errUnknownType = errors.New("unknown resource type")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Message is a DNS message.
|
||||||
|
type Message struct {
|
||||||
|
ID int
|
||||||
|
Response bool
|
||||||
|
OpCode OpCode
|
||||||
|
Authoritative bool
|
||||||
|
Truncated bool
|
||||||
|
RecursionDesired bool
|
||||||
|
RecursionAvailable bool
|
||||||
|
RCode RCode
|
||||||
|
|
||||||
|
Questions []Question
|
||||||
|
Answers []Resource
|
||||||
|
Authorities []Resource
|
||||||
|
Additionals []Resource
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pack encodes m as a byte slice. If b is not nil, m is appended into b.
|
||||||
|
// Domain name compression is enabled by setting compress.
|
||||||
|
func (m *Message) Pack(b []byte, compress bool) ([]byte, error) {
|
||||||
|
if b == nil {
|
||||||
|
b = make([]byte, 0, maxPacketLen)
|
||||||
|
}
|
||||||
|
|
||||||
|
var com Compressor
|
||||||
|
if compress {
|
||||||
|
com = compressor{tbl: make(map[string]int), offset: len(b)}
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
if b, err = m.packHeader(b); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, q := range m.Questions {
|
||||||
|
if b, err = q.Pack(b, com); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, rs := range [3][]Resource{m.Answers, m.Authorities, m.Additionals} {
|
||||||
|
for _, r := range rs {
|
||||||
|
if b, err = r.Pack(b, com); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unpack decodes m from b. Unused bytes are returned.
|
||||||
|
func (m *Message) Unpack(b []byte) ([]byte, error) {
|
||||||
|
dec := decompressor(b)
|
||||||
|
|
||||||
|
var err error
|
||||||
|
if b, err = m.unpackHeader(b); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < cap(m.Questions); i++ {
|
||||||
|
var q Question
|
||||||
|
if b, err = q.Unpack(b, dec); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
m.Questions = append(m.Questions, q)
|
||||||
|
}
|
||||||
|
for i := 0; i < cap(m.Answers); i++ {
|
||||||
|
var r Resource
|
||||||
|
if b, err = r.Unpack(b, dec); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
m.Answers = append(m.Answers, r)
|
||||||
|
}
|
||||||
|
for i := 0; i < cap(m.Authorities); i++ {
|
||||||
|
var r Resource
|
||||||
|
if b, err = r.Unpack(b, dec); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
m.Authorities = append(m.Authorities, r)
|
||||||
|
}
|
||||||
|
for i := 0; i < cap(m.Additionals); i++ {
|
||||||
|
var r Resource
|
||||||
|
if b, err = r.Unpack(b, dec); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
m.Additionals = append(m.Additionals, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
headerBitQR = 1 << 15 // query/response (response=1)
|
||||||
|
headerBitAA = 1 << 10 // authoritative
|
||||||
|
headerBitTC = 1 << 9 // truncated
|
||||||
|
headerBitRD = 1 << 8 // recursion desired
|
||||||
|
headerBitRA = 1 << 7 // recursion available
|
||||||
|
)
|
||||||
|
|
||||||
|
func (m *Message) packHeader(b []byte) ([]byte, error) {
|
||||||
|
id := uint16(m.ID)
|
||||||
|
if int(id) != m.ID {
|
||||||
|
return nil, errFieldOverflow
|
||||||
|
}
|
||||||
|
|
||||||
|
opcode := m.OpCode & 0x0F
|
||||||
|
if opcode != m.OpCode {
|
||||||
|
return nil, errFieldOverflow
|
||||||
|
}
|
||||||
|
|
||||||
|
rcode := m.RCode & 0x0F
|
||||||
|
if rcode != m.RCode {
|
||||||
|
return nil, errFieldOverflow
|
||||||
|
}
|
||||||
|
|
||||||
|
bits := uint16(opcode)<<11 | uint16(rcode)
|
||||||
|
if m.Response {
|
||||||
|
bits |= headerBitQR
|
||||||
|
}
|
||||||
|
if m.RecursionAvailable {
|
||||||
|
bits |= headerBitRA
|
||||||
|
}
|
||||||
|
if m.RecursionDesired {
|
||||||
|
bits |= headerBitRD
|
||||||
|
}
|
||||||
|
if m.Truncated {
|
||||||
|
bits |= headerBitTC
|
||||||
|
}
|
||||||
|
if m.Authoritative {
|
||||||
|
bits |= headerBitAA
|
||||||
|
}
|
||||||
|
|
||||||
|
qdcount := uint16(len(m.Questions))
|
||||||
|
if int(qdcount) != len(m.Questions) {
|
||||||
|
return nil, errTooManyQuestions
|
||||||
|
}
|
||||||
|
|
||||||
|
ancount := uint16(len(m.Answers))
|
||||||
|
if int(ancount) != len(m.Answers) {
|
||||||
|
return nil, errTooManyAnswers
|
||||||
|
}
|
||||||
|
|
||||||
|
nscount := uint16(len(m.Authorities))
|
||||||
|
if int(nscount) != len(m.Authorities) {
|
||||||
|
return nil, errTooManyAuthorities
|
||||||
|
}
|
||||||
|
|
||||||
|
arcount := uint16(len(m.Additionals))
|
||||||
|
if int(nscount) != len(m.Authorities) {
|
||||||
|
return nil, errTooManyAuthorities
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := [12]byte{}
|
||||||
|
nbo.PutUint16(buf[0:2], id)
|
||||||
|
nbo.PutUint16(buf[2:4], bits)
|
||||||
|
nbo.PutUint16(buf[4:6], qdcount)
|
||||||
|
nbo.PutUint16(buf[6:8], ancount)
|
||||||
|
nbo.PutUint16(buf[8:10], nscount)
|
||||||
|
nbo.PutUint16(buf[10:12], arcount)
|
||||||
|
return append(b, buf[:]...), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Message) unpackHeader(b []byte) ([]byte, error) {
|
||||||
|
if len(b) < 12 {
|
||||||
|
return nil, errResourceLen
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
id = int(nbo.Uint16(b))
|
||||||
|
bits = nbo.Uint16(b[2:])
|
||||||
|
qdcount = nbo.Uint16(b[4:])
|
||||||
|
ancount = nbo.Uint16(b[6:])
|
||||||
|
nscount = nbo.Uint16(b[8:])
|
||||||
|
arcount = nbo.Uint16(b[10:])
|
||||||
|
)
|
||||||
|
|
||||||
|
*m = Message{
|
||||||
|
ID: id,
|
||||||
|
Response: (bits & headerBitQR) > 0,
|
||||||
|
OpCode: OpCode(bits>>11) & 0xF,
|
||||||
|
Authoritative: (bits & headerBitAA) > 0,
|
||||||
|
Truncated: (bits & headerBitTC) > 0,
|
||||||
|
RecursionDesired: (bits & headerBitRD) > 0,
|
||||||
|
RecursionAvailable: (bits & headerBitRA) > 0,
|
||||||
|
RCode: RCode(bits) & 0xF,
|
||||||
|
}
|
||||||
|
|
||||||
|
if qdcount > 0 {
|
||||||
|
m.Questions = make([]Question, 0, qdcount)
|
||||||
|
}
|
||||||
|
if ancount > 0 {
|
||||||
|
m.Answers = make([]Resource, 0, ancount)
|
||||||
|
}
|
||||||
|
if nscount > 0 {
|
||||||
|
m.Authorities = make([]Resource, 0, nscount)
|
||||||
|
}
|
||||||
|
if arcount > 0 {
|
||||||
|
m.Additionals = make([]Resource, 0, arcount)
|
||||||
|
}
|
||||||
|
|
||||||
|
return b[12:], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Question is a DNS query.
|
||||||
|
type Question struct {
|
||||||
|
Name string
|
||||||
|
Type Type
|
||||||
|
Class Class
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pack encodes q as a byte slice. If b is not nil, m is appended into b.
|
||||||
|
func (q Question) Pack(b []byte, com Compressor) ([]byte, error) {
|
||||||
|
if com == nil {
|
||||||
|
com = compressor{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
if b, err = com.Pack(b, q.Name); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := [4]byte{}
|
||||||
|
nbo.PutUint16(buf[:2], uint16(q.Type))
|
||||||
|
nbo.PutUint16(buf[2:4], uint16(q.Class))
|
||||||
|
return append(b, buf[:]...), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unpack decodes q from b.
|
||||||
|
func (q *Question) Unpack(b []byte, dec Decompressor) ([]byte, error) {
|
||||||
|
if dec == nil {
|
||||||
|
dec = decompressor(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
if q.Name, b, err = dec.Unpack(b); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(b) < 4 {
|
||||||
|
return nil, errResourceLen
|
||||||
|
}
|
||||||
|
|
||||||
|
q.Type = Type(nbo.Uint16(b[:2]))
|
||||||
|
q.Class = Class(nbo.Uint16(b[2:4]))
|
||||||
|
|
||||||
|
return b[4:], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resource is a DNS resource record (RR).
|
||||||
|
type Resource struct {
|
||||||
|
Name string
|
||||||
|
Class Class
|
||||||
|
TTL time.Duration
|
||||||
|
|
||||||
|
Record
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pack encodes r onto b.
|
||||||
|
func (r Resource) Pack(b []byte, com Compressor) ([]byte, error) {
|
||||||
|
if com == nil {
|
||||||
|
com = compressor{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
if b, err = com.Pack(b, r.Name); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rtype := r.Record.Type()
|
||||||
|
|
||||||
|
ttl := uint32(r.TTL / time.Second)
|
||||||
|
if time.Duration(ttl) != r.TTL/time.Second {
|
||||||
|
return nil, errFieldOverflow
|
||||||
|
}
|
||||||
|
|
||||||
|
rlen := r.Record.Length(com)
|
||||||
|
rdatalen := uint16(rlen)
|
||||||
|
if int(rdatalen) != rlen {
|
||||||
|
return nil, errFieldOverflow
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := [10]byte{}
|
||||||
|
nbo.PutUint16(buf[:2], uint16(rtype))
|
||||||
|
nbo.PutUint16(buf[2:4], uint16(r.Class))
|
||||||
|
nbo.PutUint32(buf[4:8], ttl)
|
||||||
|
nbo.PutUint16(buf[8:10], rdatalen)
|
||||||
|
b = append(b, buf[:]...)
|
||||||
|
|
||||||
|
return r.Record.Pack(b, com)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unpack decodes r from b.
|
||||||
|
func (r *Resource) Unpack(b []byte, dec Decompressor) ([]byte, error) {
|
||||||
|
var err error
|
||||||
|
if r.Name, b, err = dec.Unpack(b); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(b) < 10 {
|
||||||
|
return nil, errResourceLen
|
||||||
|
}
|
||||||
|
|
||||||
|
rtype := Type(nbo.Uint16(b[:2]))
|
||||||
|
r.Class = Class(nbo.Uint16(b[2:4]))
|
||||||
|
r.TTL = time.Duration(nbo.Uint32(b[4:8])) * time.Second
|
||||||
|
|
||||||
|
rdlen, b := int(nbo.Uint16(b[8:10])), b[10:]
|
||||||
|
if len(b) < rdlen {
|
||||||
|
return nil, errResourceLen
|
||||||
|
}
|
||||||
|
|
||||||
|
newfn, ok := NewRecordByType[rtype]
|
||||||
|
if !ok {
|
||||||
|
return nil, errUnknownType
|
||||||
|
}
|
||||||
|
|
||||||
|
record := newfn()
|
||||||
|
buf, err := record.Unpack(b[:rdlen], dec)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(buf) > 0 {
|
||||||
|
return nil, errResTooLong
|
||||||
|
}
|
||||||
|
r.Record = record
|
||||||
|
|
||||||
|
return b[rdlen:], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Record is a DNS record.
|
||||||
|
type Record interface {
|
||||||
|
Type() Type
|
||||||
|
Length(Compressor) int
|
||||||
|
Pack([]byte, Compressor) ([]byte, error)
|
||||||
|
Unpack([]byte, Decompressor) ([]byte, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// A A is a DNS A record.
|
||||||
|
type A struct {
|
||||||
|
A net.IP
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type returns the RR type identifier.
|
||||||
|
func (A) Type() Type { return TypeA }
|
||||||
|
|
||||||
|
// Length returns the encoded RDATA size.
|
||||||
|
func (A) Length(Compressor) int { return 4 }
|
||||||
|
|
||||||
|
// Pack encodes a as RDATA.
|
||||||
|
func (a A) Pack(b []byte, _ Compressor) ([]byte, error) {
|
||||||
|
if len(a.A) < 4 {
|
||||||
|
return nil, errResourceLen
|
||||||
|
}
|
||||||
|
return append(b, a.A.To4()...), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unpack decodes a from RDATA in b.
|
||||||
|
func (a *A) Unpack(b []byte, _ Decompressor) ([]byte, error) {
|
||||||
|
if len(b) < 4 {
|
||||||
|
return nil, errResourceLen
|
||||||
|
}
|
||||||
|
if len(a.A) != 4 {
|
||||||
|
a.A = make([]byte, 4)
|
||||||
|
}
|
||||||
|
copy(a.A, b[:4])
|
||||||
|
|
||||||
|
return b[4:], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AAAA is a DNS AAAA record.
|
||||||
|
type AAAA struct {
|
||||||
|
AAAA net.IP
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type returns the RR type identifier.
|
||||||
|
func (AAAA) Type() Type { return TypeAAAA }
|
||||||
|
|
||||||
|
// Length returns the encoded RDATA size.
|
||||||
|
func (AAAA) Length(Compressor) int { return 16 }
|
||||||
|
|
||||||
|
// Pack encodes a as RDATA.
|
||||||
|
func (a AAAA) Pack(b []byte, _ Compressor) ([]byte, error) {
|
||||||
|
if len(a.AAAA) != 16 {
|
||||||
|
return nil, errResourceLen
|
||||||
|
}
|
||||||
|
return append(b, a.AAAA...), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unpack decodes a from RDATA in b.
|
||||||
|
func (a *AAAA) Unpack(b []byte, _ Decompressor) ([]byte, error) {
|
||||||
|
if len(b) < 16 {
|
||||||
|
return nil, errResourceLen
|
||||||
|
}
|
||||||
|
if len(a.AAAA) != 16 {
|
||||||
|
a.AAAA = make([]byte, 16)
|
||||||
|
}
|
||||||
|
copy(a.AAAA, b[:16])
|
||||||
|
|
||||||
|
return b[16:], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CNAME is a DNS CNAME record.
|
||||||
|
type CNAME struct {
|
||||||
|
CNAME string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type returns the RR type identifier.
|
||||||
|
func (CNAME) Type() Type { return TypeCNAME }
|
||||||
|
|
||||||
|
// Length returns the encoded RDATA size.
|
||||||
|
func (c CNAME) Length(com Compressor) int {
|
||||||
|
return com.Length(c.CNAME)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pack encodes c as RDATA.
|
||||||
|
func (c CNAME) Pack(b []byte, com Compressor) ([]byte, error) {
|
||||||
|
return com.Pack(b, c.CNAME)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unpack decodes c from RDATA in b.
|
||||||
|
func (c *CNAME) Unpack(b []byte, dec Decompressor) ([]byte, error) {
|
||||||
|
var err error
|
||||||
|
c.CNAME, b, err = dec.Unpack(b)
|
||||||
|
return b, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// SOA is a DNS SOA record.
|
||||||
|
type SOA struct {
|
||||||
|
NS string
|
||||||
|
MBox string
|
||||||
|
Serial int
|
||||||
|
Refresh time.Duration
|
||||||
|
Retry time.Duration
|
||||||
|
Expire time.Duration
|
||||||
|
MinTTL time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type returns the RR type identifier.
|
||||||
|
func (SOA) Type() Type { return TypeSOA }
|
||||||
|
|
||||||
|
// Length returns the encoded RDATA size.
|
||||||
|
func (s SOA) Length(com Compressor) int {
|
||||||
|
return com.Length(s.NS, s.MBox) + 20
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pack encodes s as RDATA.
|
||||||
|
func (s SOA) Pack(b []byte, com Compressor) ([]byte, error) {
|
||||||
|
var err error
|
||||||
|
if b, err = com.Pack(b, s.NS); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if b, err = com.Pack(b, s.MBox); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
serial = uint32(s.Serial)
|
||||||
|
refresh = int32(s.Refresh / time.Second)
|
||||||
|
retry = int32(s.Retry / time.Second)
|
||||||
|
expire = int32(s.Expire / time.Second)
|
||||||
|
minimum = uint32(s.MinTTL / time.Second)
|
||||||
|
)
|
||||||
|
|
||||||
|
if int(serial) != s.Serial {
|
||||||
|
return nil, errFieldOverflow
|
||||||
|
}
|
||||||
|
if time.Duration(refresh) != s.Refresh/time.Second {
|
||||||
|
return nil, errFieldOverflow
|
||||||
|
}
|
||||||
|
if time.Duration(retry) != s.Retry/time.Second {
|
||||||
|
return nil, errFieldOverflow
|
||||||
|
}
|
||||||
|
if time.Duration(expire) != s.Expire/time.Second {
|
||||||
|
return nil, errFieldOverflow
|
||||||
|
}
|
||||||
|
if time.Duration(minimum) != s.MinTTL/time.Second {
|
||||||
|
return nil, errFieldOverflow
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := [20]byte{}
|
||||||
|
nbo.PutUint32(buf[:4], serial)
|
||||||
|
nbo.PutUint32(buf[4:8], uint32(refresh))
|
||||||
|
nbo.PutUint32(buf[8:12], uint32(retry))
|
||||||
|
nbo.PutUint32(buf[12:16], uint32(expire))
|
||||||
|
nbo.PutUint32(buf[16:], minimum)
|
||||||
|
|
||||||
|
return append(b, buf[:]...), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unpack decodes s from RDATA in b.
|
||||||
|
func (s *SOA) Unpack(b []byte, dec Decompressor) ([]byte, error) {
|
||||||
|
var err error
|
||||||
|
if s.NS, b, err = dec.Unpack(b); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if s.MBox, b, err = dec.Unpack(b); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(b) < 20 {
|
||||||
|
return nil, errResourceLen
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
serial = nbo.Uint32(b[:4])
|
||||||
|
refresh = int32(nbo.Uint32(b[4:8]))
|
||||||
|
retry = int32(nbo.Uint32(b[8:12]))
|
||||||
|
expire = int32(nbo.Uint32(b[12:16]))
|
||||||
|
minimum = nbo.Uint32(b[16:20])
|
||||||
|
)
|
||||||
|
|
||||||
|
s.Serial = int(serial)
|
||||||
|
s.Refresh = time.Duration(refresh) * time.Second
|
||||||
|
s.Retry = time.Duration(retry) * time.Second
|
||||||
|
s.Expire = time.Duration(expire) * time.Second
|
||||||
|
s.MinTTL = time.Duration(minimum) * time.Second
|
||||||
|
|
||||||
|
return b[20:], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PTR is a DNS PTR record.
|
||||||
|
type PTR struct {
|
||||||
|
PTR string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type returns the RR type identifier.
|
||||||
|
func (PTR) Type() Type { return TypePTR }
|
||||||
|
|
||||||
|
// Length returns the encoded RDATA size.
|
||||||
|
func (p PTR) Length(com Compressor) int {
|
||||||
|
return com.Length(p.PTR)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pack encodes p as RDATA.
|
||||||
|
func (p PTR) Pack(b []byte, com Compressor) ([]byte, error) {
|
||||||
|
return com.Pack(b, p.PTR)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unpack decodes p from RDATA in b.
|
||||||
|
func (p *PTR) Unpack(b []byte, dec Decompressor) ([]byte, error) {
|
||||||
|
var err error
|
||||||
|
p.PTR, b, err = dec.Unpack(b)
|
||||||
|
return b, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// MX is a DNS MX record.
|
||||||
|
type MX struct {
|
||||||
|
Pref int
|
||||||
|
MX string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type returns the RR type identifier.
|
||||||
|
func (MX) Type() Type { return TypeMX }
|
||||||
|
|
||||||
|
// Length returns the encoded RDATA size.
|
||||||
|
func (m MX) Length(com Compressor) int {
|
||||||
|
return 2 + com.Length(m.MX)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pack encodes m as RDATA.
|
||||||
|
func (m MX) Pack(b []byte, com Compressor) ([]byte, error) {
|
||||||
|
pref := uint16(m.Pref)
|
||||||
|
if int(pref) != m.Pref {
|
||||||
|
return nil, errFieldOverflow
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := [2]byte{}
|
||||||
|
nbo.PutUint16(buf[:], pref)
|
||||||
|
|
||||||
|
return com.Pack(append(b, buf[:]...), m.MX)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unpack decodes m from RDATA in b.
|
||||||
|
func (m *MX) Unpack(b []byte, dec Decompressor) ([]byte, error) {
|
||||||
|
if len(b) < 2 {
|
||||||
|
return nil, errResourceLen
|
||||||
|
}
|
||||||
|
|
||||||
|
m.Pref = int(nbo.Uint16(b[:2]))
|
||||||
|
|
||||||
|
var err error
|
||||||
|
m.MX, b, err = dec.Unpack(b[2:])
|
||||||
|
return b, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// NS is a DNS MX record.
|
||||||
|
type NS struct {
|
||||||
|
NS string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type returns the RR type identifier.
|
||||||
|
func (NS) Type() Type { return TypeNS }
|
||||||
|
|
||||||
|
// Length returns the encoded RDATA size.
|
||||||
|
func (n NS) Length(com Compressor) int {
|
||||||
|
return com.Length(n.NS)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pack encodes n as RDATA.
|
||||||
|
func (n NS) Pack(b []byte, com Compressor) ([]byte, error) {
|
||||||
|
return com.Pack(b, n.NS)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unpack decodes n from RDATA in b.
|
||||||
|
func (n *NS) Unpack(b []byte, dec Decompressor) ([]byte, error) {
|
||||||
|
var err error
|
||||||
|
n.NS, b, err = dec.Unpack(b)
|
||||||
|
return b, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TXT is a DNS TXT record.
|
||||||
|
type TXT struct {
|
||||||
|
TXT []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type returns the RR type identifier.
|
||||||
|
func (TXT) Type() Type { return TypeTXT }
|
||||||
|
|
||||||
|
// Length returns the encoded RDATA size.
|
||||||
|
func (t TXT) Length(_ Compressor) int {
|
||||||
|
var n int
|
||||||
|
for _, s := range t.TXT {
|
||||||
|
n += 1 + len(s)
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pack encodes t as RDATA.
|
||||||
|
func (t TXT) Pack(b []byte, _ Compressor) ([]byte, error) {
|
||||||
|
for _, s := range t.TXT {
|
||||||
|
if len(s) > 255 {
|
||||||
|
return nil, errSegTooLong
|
||||||
|
}
|
||||||
|
|
||||||
|
b = append(append(b, byte(len(s))), []byte(s)...)
|
||||||
|
}
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unpack decodes t from RDATA in b.
|
||||||
|
func (t *TXT) Unpack(b []byte, _ Decompressor) ([]byte, error) {
|
||||||
|
var txts []string
|
||||||
|
for len(b) > 0 {
|
||||||
|
txtlen := int(b[0])
|
||||||
|
if len(b) < txtlen+1 {
|
||||||
|
return nil, errResourceLen
|
||||||
|
}
|
||||||
|
|
||||||
|
txts = append(txts, string(b[1:1+txtlen]))
|
||||||
|
b = b[1+txtlen:]
|
||||||
|
}
|
||||||
|
|
||||||
|
t.TXT = txts
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SRV is a DNS SRV record.
|
||||||
|
type SRV struct {
|
||||||
|
Priority int
|
||||||
|
Weight int
|
||||||
|
Port int
|
||||||
|
Target string // Not compressed as per RFC 2782.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type returns the RR type identifier.
|
||||||
|
func (SRV) Type() Type { return TypeSRV }
|
||||||
|
|
||||||
|
// Length returns the encoded RDATA size.
|
||||||
|
func (s SRV) Length(_ Compressor) int {
|
||||||
|
return 6 + compressor{}.Length(s.Target)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pack encodes s as RDATA.
|
||||||
|
func (s SRV) Pack(b []byte, _ Compressor) ([]byte, error) {
|
||||||
|
var (
|
||||||
|
priority = uint16(s.Priority)
|
||||||
|
weight = uint16(s.Weight)
|
||||||
|
port = uint16(s.Port)
|
||||||
|
)
|
||||||
|
|
||||||
|
if int(priority) != s.Priority {
|
||||||
|
return nil, errFieldOverflow
|
||||||
|
}
|
||||||
|
if int(weight) != s.Weight {
|
||||||
|
return nil, errFieldOverflow
|
||||||
|
}
|
||||||
|
if int(port) != s.Port {
|
||||||
|
return nil, errFieldOverflow
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := [6]byte{}
|
||||||
|
nbo.PutUint16(buf[:2], priority)
|
||||||
|
nbo.PutUint16(buf[2:4], weight)
|
||||||
|
nbo.PutUint16(buf[4:], port)
|
||||||
|
|
||||||
|
return compressor{}.Pack(append(b, buf[:]...), s.Target)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unpack decodes s from RDATA in b.
|
||||||
|
func (s *SRV) Unpack(b []byte, _ Decompressor) ([]byte, error) {
|
||||||
|
if len(b) < 6 {
|
||||||
|
return nil, errResourceLen
|
||||||
|
}
|
||||||
|
|
||||||
|
s.Priority = int(nbo.Uint16(b[:2]))
|
||||||
|
s.Weight = int(nbo.Uint16(b[2:4]))
|
||||||
|
s.Port = int(nbo.Uint16(b[4:6]))
|
||||||
|
|
||||||
|
var err error
|
||||||
|
s.Target, b, err = decompressor(nil).Unpack(b[6:])
|
||||||
|
return b, err
|
||||||
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MessageWriter is used by a DNS handler to serve a DNS query.
|
||||||
|
type MessageWriter interface {
|
||||||
|
// Authoritative sets the Authoritative Answer (AA) bit of the header.
|
||||||
|
Authoritative(bool)
|
||||||
|
// Recursion sets the Recursion Available (RA) bit of the header.
|
||||||
|
Recursion(bool)
|
||||||
|
// Status sets the Response code (RCODE) bits of the header.
|
||||||
|
Status(RCode)
|
||||||
|
|
||||||
|
// Answer adds a record to the answers section.
|
||||||
|
Answer(string, time.Duration, Record)
|
||||||
|
// Authority adds a record to the authority section.
|
||||||
|
Authority(string, time.Duration, Record)
|
||||||
|
// Additional adds a record to the additional section
|
||||||
|
Additional(string, time.Duration, Record)
|
||||||
|
|
||||||
|
// Recur forwards the request query upstream, and returns the response
|
||||||
|
// message or error.
|
||||||
|
Recur(context.Context) (*Message, error)
|
||||||
|
|
||||||
|
// Reply sends the response message.
|
||||||
|
//
|
||||||
|
// For large messages sent over a UDP connection, an ErrTruncatedMessage
|
||||||
|
// error is returned if the message was truncated.
|
||||||
|
Reply(context.Context) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type messageWriter struct {
|
||||||
|
msg *Message
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *messageWriter) Authoritative(aa bool) { w.msg.Authoritative = aa }
|
||||||
|
func (w *messageWriter) Recursion(ra bool) { w.msg.RecursionAvailable = ra }
|
||||||
|
func (w *messageWriter) Status(rc RCode) { w.msg.RCode = rc }
|
||||||
|
|
||||||
|
func (w *messageWriter) Answer(fqdn string, ttl time.Duration, rec Record) {
|
||||||
|
w.msg.Answers = append(w.msg.Answers, w.rr(fqdn, ttl, rec))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *messageWriter) Authority(fqdn string, ttl time.Duration, rec Record) {
|
||||||
|
w.msg.Authorities = append(w.msg.Authorities, w.rr(fqdn, ttl, rec))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *messageWriter) Additional(fqdn string, ttl time.Duration, rec Record) {
|
||||||
|
w.msg.Additionals = append(w.msg.Additionals, w.rr(fqdn, ttl, rec))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *messageWriter) rr(fqdn string, ttl time.Duration, rec Record) Resource {
|
||||||
|
return Resource{
|
||||||
|
Name: fqdn,
|
||||||
|
Class: ClassIN,
|
||||||
|
TTL: ttl,
|
||||||
|
Record: rec,
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
cryptorand "crypto/rand"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"math/big"
|
||||||
|
"net"
|
||||||
|
"sync/atomic"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NameServers is a slice of DNS nameserver addresses.
|
||||||
|
type NameServers []net.Addr
|
||||||
|
|
||||||
|
// Random picks a random Addr from s every time.
|
||||||
|
func (s NameServers) Random(rand io.Reader) ProxyFunc {
|
||||||
|
addrsByNet := s.netAddrsMap()
|
||||||
|
|
||||||
|
maxByNet := make(map[string]*big.Int, len(addrsByNet))
|
||||||
|
for network, addrs := range addrsByNet {
|
||||||
|
maxByNet[network] = big.NewInt(int64(len(addrs)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return func(_ context.Context, addr net.Addr) (net.Addr, error) {
|
||||||
|
network := addr.Network()
|
||||||
|
max, ok := maxByNet[network]
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("no nameservers for network: " + network)
|
||||||
|
}
|
||||||
|
|
||||||
|
addrs, ok := addrsByNet[network]
|
||||||
|
if !ok {
|
||||||
|
panic("impossible")
|
||||||
|
}
|
||||||
|
|
||||||
|
idx, err := cryptorand.Int(rand, max)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return addrs[idx.Uint64()], nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RoundRobin picks the next Addr of s by index of the last pick.
|
||||||
|
func (s NameServers) RoundRobin() ProxyFunc {
|
||||||
|
addrsByNet := s.netAddrsMap()
|
||||||
|
|
||||||
|
idxByNet := make(map[string]*uint32, len(s))
|
||||||
|
for network := range addrsByNet {
|
||||||
|
idxByNet[network] = new(uint32)
|
||||||
|
}
|
||||||
|
|
||||||
|
return func(_ context.Context, addr net.Addr) (net.Addr, error) {
|
||||||
|
network := addr.Network()
|
||||||
|
idx, ok := idxByNet[network]
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("no nameservers for network: " + network)
|
||||||
|
}
|
||||||
|
|
||||||
|
addrs, ok := addrsByNet[network]
|
||||||
|
if !ok {
|
||||||
|
panic("impossible")
|
||||||
|
}
|
||||||
|
|
||||||
|
return addrs[int(atomic.AddUint32(idx, 1)-1)%len(addrs)], nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s NameServers) netAddrsMap() map[string][]net.Addr {
|
||||||
|
addrsByNet := make(map[string][]net.Addr, len(s))
|
||||||
|
for _, addr := range s {
|
||||||
|
network := addr.Network()
|
||||||
|
addrsByNet[network] = append(addrsByNet[network], addr)
|
||||||
|
}
|
||||||
|
return addrsByNet
|
||||||
|
}
|
|
@ -0,0 +1,158 @@
|
||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type pipeline struct {
|
||||||
|
Conn
|
||||||
|
|
||||||
|
rmu, wmu sync.Mutex
|
||||||
|
|
||||||
|
mu sync.Mutex
|
||||||
|
inflight map[int]pipelineTx
|
||||||
|
readerr error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *pipeline) alive() bool {
|
||||||
|
p.mu.Lock()
|
||||||
|
defer p.mu.Unlock()
|
||||||
|
|
||||||
|
return p.readerr == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *pipeline) conn() Conn {
|
||||||
|
return &pipelineConn{
|
||||||
|
pipeline: p,
|
||||||
|
tx: pipelineTx{
|
||||||
|
msgerrc: make(chan msgerr),
|
||||||
|
abortc: make(chan struct{}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *pipeline) run() {
|
||||||
|
var err error
|
||||||
|
for {
|
||||||
|
var msg Message
|
||||||
|
|
||||||
|
p.rmu.Lock()
|
||||||
|
if err = p.Recv(&msg); err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
p.rmu.Unlock()
|
||||||
|
|
||||||
|
p.mu.Lock()
|
||||||
|
tx, ok := p.inflight[msg.ID]
|
||||||
|
delete(p.inflight, msg.ID)
|
||||||
|
p.mu.Unlock()
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
go tx.deliver(msgerr{msg: &msg})
|
||||||
|
}
|
||||||
|
p.rmu.Unlock()
|
||||||
|
|
||||||
|
p.mu.Lock()
|
||||||
|
p.readerr = err
|
||||||
|
txs := make([]pipelineTx, 0, len(p.inflight))
|
||||||
|
for _, tx := range p.inflight {
|
||||||
|
txs = append(txs, tx)
|
||||||
|
}
|
||||||
|
p.mu.Unlock()
|
||||||
|
|
||||||
|
for _, tx := range txs {
|
||||||
|
go tx.deliver(msgerr{err: err})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type pipelineConn struct {
|
||||||
|
*pipeline
|
||||||
|
|
||||||
|
aborto sync.Once
|
||||||
|
tx pipelineTx
|
||||||
|
|
||||||
|
readDeadline, writeDeadline time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *pipelineConn) Close() error {
|
||||||
|
c.aborto.Do(c.tx.abort)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *pipelineConn) Recv(msg *Message) error {
|
||||||
|
var me msgerr
|
||||||
|
select {
|
||||||
|
case me = <-c.tx.msgerrc:
|
||||||
|
case <-c.tx.abortc:
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := me.err; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
*msg = *me.msg // shallow copy
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *pipelineConn) Send(msg *Message) error {
|
||||||
|
if err := c.register(msg); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.wmu.Lock()
|
||||||
|
defer c.wmu.Unlock()
|
||||||
|
|
||||||
|
if err := c.Conn.SetWriteDeadline(c.writeDeadline); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Conn.Send(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *pipelineConn) SetDeadline(t time.Time) error {
|
||||||
|
c.SetReadDeadline(t)
|
||||||
|
c.SetWriteDeadline(t)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *pipelineConn) SetReadDeadline(t time.Time) error {
|
||||||
|
c.readDeadline = t
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *pipelineConn) SetWriteDeadline(t time.Time) error {
|
||||||
|
c.writeDeadline = t
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *pipelineConn) register(msg *Message) error {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
|
||||||
|
if _, ok := c.inflight[msg.ID]; ok {
|
||||||
|
return ErrConflictingID
|
||||||
|
}
|
||||||
|
|
||||||
|
c.inflight[msg.ID] = c.tx
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type pipelineTx struct {
|
||||||
|
msgerrc chan msgerr
|
||||||
|
abortc chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p pipelineTx) abort() { close(p.abortc) }
|
||||||
|
|
||||||
|
func (p pipelineTx) deliver(me msgerr) {
|
||||||
|
select {
|
||||||
|
case p.msgerrc <- me:
|
||||||
|
case <-p.abortc:
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,370 @@
|
||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A Server defines parameters for running a DNS server. The zero value for
|
||||||
|
// Server is a valid configuration.
|
||||||
|
type Server struct {
|
||||||
|
Addr string // TCP and UDP address to listen on, ":domain" if empty
|
||||||
|
Handler Handler // handler to invoke
|
||||||
|
TLSConfig *tls.Config // optional TLS config, used by ListenAndServeTLS
|
||||||
|
|
||||||
|
// Forwarder relays a recursive query. If nil, recursive queries are
|
||||||
|
// answered with a "Query Refused" message.
|
||||||
|
Forwarder RoundTripper
|
||||||
|
|
||||||
|
// ErrorLog specifies an optional logger for errors accepting connections,
|
||||||
|
// reading data, and unpacking messages.
|
||||||
|
// If nil, logging is done via the log package's standard logger.
|
||||||
|
ErrorLog *log.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListenAndServe listens on both the TCP and UDP network address s.Addr and
|
||||||
|
// then calls Serve or ServePacket to handle queries on incoming connections.
|
||||||
|
// If srv.Addr is blank, ":domain" is used. ListenAndServe always returns a
|
||||||
|
// non-nil error.
|
||||||
|
func (s *Server) ListenAndServe(ctx context.Context) error {
|
||||||
|
addr := s.Addr
|
||||||
|
if addr == "" {
|
||||||
|
addr = ":domain"
|
||||||
|
}
|
||||||
|
|
||||||
|
ln, err := net.Listen("tcp", addr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := net.ListenPacket("udp", addr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
errc := make(chan error, 1)
|
||||||
|
go func() { errc <- s.Serve(ctx, ln) }()
|
||||||
|
go func() { errc <- s.ServePacket(ctx, conn) }()
|
||||||
|
|
||||||
|
return <-errc
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListenAndServeTLS listens on the TCP network address s.Addr and then calls
|
||||||
|
// Serve to handle requests on incoming TLS connections.
|
||||||
|
//
|
||||||
|
// If s.Addr is blank, ":853" is used.
|
||||||
|
//
|
||||||
|
// ListenAndServeTLS always returns a non-nil error.
|
||||||
|
func (s *Server) ListenAndServeTLS(ctx context.Context) error {
|
||||||
|
addr := s.Addr
|
||||||
|
if addr == "" {
|
||||||
|
addr = ":domain"
|
||||||
|
}
|
||||||
|
|
||||||
|
ln, err := net.Listen("tcp", addr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.ServeTLS(ctx, ln)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serve accepts incoming connections on the Listener ln, creating a new
|
||||||
|
// service goroutine for each. The service goroutines read TCP encoded queries
|
||||||
|
// and then call s.Handler to reply to them.
|
||||||
|
//
|
||||||
|
// See RFC 1035, section 4.2.2 "TCP usage" for transport encoding of messages.
|
||||||
|
//
|
||||||
|
// Serve always returns a non-nil error.
|
||||||
|
func (s *Server) Serve(ctx context.Context, ln net.Listener) error {
|
||||||
|
defer ln.Close()
|
||||||
|
|
||||||
|
for {
|
||||||
|
conn, err := ln.Accept()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
go s.serveStream(ctx, conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServePacket reads UDP encoded queries from the PacketConn conn, creating a
|
||||||
|
// new service goroutine for each. The service goroutines call s.Handler to
|
||||||
|
// reply.
|
||||||
|
//
|
||||||
|
// See RFC 1035, section 4.2.1 "UDP usage" for transport encoding of messages.
|
||||||
|
//
|
||||||
|
// ServePacket always returns a non-nil error.
|
||||||
|
func (s *Server) ServePacket(ctx context.Context, conn net.PacketConn) error {
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
for {
|
||||||
|
buf := make([]byte, maxPacketLen)
|
||||||
|
n, addr, err := conn.ReadFrom(buf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
req := &Query{
|
||||||
|
Message: new(Message),
|
||||||
|
RemoteAddr: addr,
|
||||||
|
}
|
||||||
|
|
||||||
|
if buf, err = req.Message.Unpack(buf[:n]); err != nil {
|
||||||
|
s.logf("dns unpack: %s", err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(buf) != 0 {
|
||||||
|
s.logf("dns unpack: malformed packet, extra message bytes")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
pw := &packetWriter{
|
||||||
|
messageWriter: &messageWriter{
|
||||||
|
msg: response(req.Message),
|
||||||
|
},
|
||||||
|
|
||||||
|
addr: addr,
|
||||||
|
conn: conn,
|
||||||
|
}
|
||||||
|
|
||||||
|
go s.handle(ctx, pw, req)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServeTLS accepts incoming connections on the Listener ln, creating a new
|
||||||
|
// service goroutine for each. The service goroutines read TCP encoded queries
|
||||||
|
// over a TLS channel and then call s.Handler to reply to them, in another
|
||||||
|
// service goroutine.
|
||||||
|
//
|
||||||
|
// See RFC 7858, section 3.3 for transport encoding of messages.
|
||||||
|
//
|
||||||
|
// ServeTLS always returns a non-nil error.
|
||||||
|
func (s *Server) ServeTLS(ctx context.Context, ln net.Listener) error {
|
||||||
|
ln = tls.NewListener(ln, s.TLSConfig.Clone())
|
||||||
|
defer ln.Close()
|
||||||
|
|
||||||
|
for {
|
||||||
|
conn, err := ln.Accept()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
go func(conn net.Conn) {
|
||||||
|
if err := conn.(*tls.Conn).Handshake(); err != nil {
|
||||||
|
s.logf("dns handshake: %s", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
s.serveStream(ctx, conn)
|
||||||
|
}(conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) serveStream(ctx context.Context, conn net.Conn) {
|
||||||
|
var (
|
||||||
|
rbuf = bufio.NewReader(conn)
|
||||||
|
|
||||||
|
lbuf [2]byte
|
||||||
|
mu sync.Mutex
|
||||||
|
)
|
||||||
|
|
||||||
|
for {
|
||||||
|
if _, err := rbuf.Read(lbuf[:]); err != nil {
|
||||||
|
if err != io.EOF {
|
||||||
|
s.logf("dns read: %s", err.Error())
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := make([]byte, int(nbo.Uint16(lbuf[:])))
|
||||||
|
if _, err := io.ReadFull(rbuf, buf); err != nil {
|
||||||
|
s.logf("dns read: %s", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
req := &Query{
|
||||||
|
Message: new(Message),
|
||||||
|
RemoteAddr: conn.RemoteAddr(),
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
if buf, err = req.Message.Unpack(buf); err != nil {
|
||||||
|
s.logf("dns unpack: %s", err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(buf) != 0 {
|
||||||
|
s.logf("dns unpack: malformed packet, extra message bytes")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
sw := streamWriter{
|
||||||
|
messageWriter: &messageWriter{
|
||||||
|
msg: response(req.Message),
|
||||||
|
},
|
||||||
|
|
||||||
|
mu: &mu,
|
||||||
|
conn: conn,
|
||||||
|
}
|
||||||
|
|
||||||
|
go s.handle(ctx, sw, req)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) handle(ctx context.Context, w MessageWriter, r *Query) {
|
||||||
|
sw := &serverWriter{
|
||||||
|
MessageWriter: w,
|
||||||
|
forwarder: s.Forwarder,
|
||||||
|
query: r,
|
||||||
|
}
|
||||||
|
|
||||||
|
s.Handler.ServeDNS(ctx, sw, r)
|
||||||
|
|
||||||
|
if !sw.replied {
|
||||||
|
if err := sw.Reply(ctx); err != nil {
|
||||||
|
s.logf("dns: %s", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) logf(format string, args ...interface{}) {
|
||||||
|
printf := log.Printf
|
||||||
|
if s.ErrorLog != nil {
|
||||||
|
printf = s.ErrorLog.Printf
|
||||||
|
}
|
||||||
|
|
||||||
|
printf(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
type packetWriter struct {
|
||||||
|
*messageWriter
|
||||||
|
|
||||||
|
addr net.Addr
|
||||||
|
conn net.PacketConn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w packetWriter) Recur(ctx context.Context) (*Message, error) {
|
||||||
|
return nil, ErrUnsupportedOp
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w packetWriter) Reply(ctx context.Context) error {
|
||||||
|
buf, err := w.msg.Pack(nil, true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(buf) > maxPacketLen {
|
||||||
|
return w.truncate(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = w.conn.WriteTo(buf, w.addr)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w packetWriter) truncate(buf []byte) error {
|
||||||
|
var err error
|
||||||
|
if buf, err = truncate(buf, maxPacketLen); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := w.conn.WriteTo(buf, w.addr); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return ErrTruncatedMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
type streamWriter struct {
|
||||||
|
*messageWriter
|
||||||
|
|
||||||
|
mu *sync.Mutex
|
||||||
|
conn net.Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w streamWriter) Recur(ctx context.Context) (*Message, error) {
|
||||||
|
return nil, ErrUnsupportedOp
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w streamWriter) Reply(ctx context.Context) error {
|
||||||
|
buf, err := w.msg.Pack(make([]byte, 2), true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
blen := uint16(len(buf) - 2)
|
||||||
|
if int(blen) != len(buf)-2 {
|
||||||
|
return ErrOversizedMessage
|
||||||
|
}
|
||||||
|
nbo.PutUint16(buf[:2], blen)
|
||||||
|
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
|
||||||
|
_, err = w.conn.Write(buf)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
type serverWriter struct {
|
||||||
|
MessageWriter
|
||||||
|
|
||||||
|
forwarder RoundTripper
|
||||||
|
query *Query
|
||||||
|
|
||||||
|
replied bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w serverWriter) Recur(ctx context.Context) (*Message, error) {
|
||||||
|
query := &Query{
|
||||||
|
Message: request(w.query.Message),
|
||||||
|
RemoteAddr: w.query.RemoteAddr,
|
||||||
|
}
|
||||||
|
|
||||||
|
qs := make([]Question, 0, len(w.query.Questions))
|
||||||
|
for _, q := range w.query.Questions {
|
||||||
|
if !questionMatched(q, query.Message) {
|
||||||
|
qs = append(qs, q)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
query.Questions = qs
|
||||||
|
|
||||||
|
return w.forward(ctx, query)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w serverWriter) Reply(ctx context.Context) error {
|
||||||
|
w.replied = true
|
||||||
|
|
||||||
|
return w.MessageWriter.Reply(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func response(msg *Message) *Message {
|
||||||
|
res := new(Message)
|
||||||
|
*res = *msg // shallow copy
|
||||||
|
|
||||||
|
res.Response = true
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
var refuser = &Client{
|
||||||
|
Transport: nopDialer{},
|
||||||
|
Resolver: HandlerFunc(Refuse),
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w serverWriter) forward(ctx context.Context, query *Query) (*Message, error) {
|
||||||
|
if w.forwarder != nil {
|
||||||
|
return w.forwarder.Do(ctx, query)
|
||||||
|
}
|
||||||
|
|
||||||
|
return refuser.Do(ctx, query)
|
||||||
|
}
|
||||||
|
|
||||||
|
type nopDialer struct{}
|
||||||
|
|
||||||
|
func (nopDialer) DialAddr(ctx context.Context, addr net.Addr) (Conn, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
|
@ -0,0 +1,171 @@
|
||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
type packetSession struct {
|
||||||
|
session
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *packetSession) Read(b []byte) (int, error) {
|
||||||
|
msg, err := s.recv()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, err := msg.Pack(b[:0:len(b)], true)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if len(buf) > len(b) {
|
||||||
|
if buf, err = truncate(buf, len(b)); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
copy(b, buf)
|
||||||
|
return len(buf), nil
|
||||||
|
}
|
||||||
|
return len(buf), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *packetSession) ReadFrom(b []byte) (int, net.Addr, error) {
|
||||||
|
n, err := s.Read(b)
|
||||||
|
return n, s.addr, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *packetSession) Write(b []byte) (int, error) {
|
||||||
|
msg := new(Message)
|
||||||
|
if _, err := msg.Unpack(b); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
query := &Query{
|
||||||
|
RemoteAddr: s.addr,
|
||||||
|
Message: msg,
|
||||||
|
}
|
||||||
|
|
||||||
|
go s.do(query)
|
||||||
|
|
||||||
|
return len(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *packetSession) WriteTo(b []byte, addr net.Addr) (int, error) {
|
||||||
|
return s.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
type streamSession struct {
|
||||||
|
session
|
||||||
|
|
||||||
|
rbuf []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *streamSession) Read(b []byte) (int, error) {
|
||||||
|
if len(s.rbuf) > 0 {
|
||||||
|
return s.read(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg, err := s.recv()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.rbuf, err = msg.Pack(s.rbuf[:0], true); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
mlen := uint16(len(s.rbuf))
|
||||||
|
if int(mlen) != len(s.rbuf) {
|
||||||
|
return 0, ErrOversizedMessage
|
||||||
|
}
|
||||||
|
nbo.PutUint16(b, mlen)
|
||||||
|
|
||||||
|
if len(b) == 2 {
|
||||||
|
return 2, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err := s.read(b[2:])
|
||||||
|
return 2 + n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *streamSession) read(b []byte) (int, error) {
|
||||||
|
if len(s.rbuf) > len(b) {
|
||||||
|
copy(b, s.rbuf[:len(b)])
|
||||||
|
s.rbuf = s.rbuf[len(b):]
|
||||||
|
return len(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
n := len(s.rbuf)
|
||||||
|
copy(b, s.rbuf)
|
||||||
|
s.rbuf = s.rbuf[:0]
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s streamSession) Write(b []byte) (int, error) {
|
||||||
|
if len(b) < 2 {
|
||||||
|
return 0, io.ErrShortWrite
|
||||||
|
}
|
||||||
|
|
||||||
|
mlen := nbo.Uint16(b[:2])
|
||||||
|
buf := b[2:]
|
||||||
|
|
||||||
|
if int(mlen) != len(buf) {
|
||||||
|
return 0, io.ErrShortWrite
|
||||||
|
}
|
||||||
|
|
||||||
|
msg := new(Message)
|
||||||
|
if _, err := msg.Unpack(buf); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
query := &Query{
|
||||||
|
RemoteAddr: s.addr,
|
||||||
|
Message: msg,
|
||||||
|
}
|
||||||
|
|
||||||
|
go s.do(query)
|
||||||
|
|
||||||
|
return len(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type session struct {
|
||||||
|
Conn
|
||||||
|
|
||||||
|
addr net.Addr
|
||||||
|
|
||||||
|
client *Client
|
||||||
|
|
||||||
|
msgerrc chan msgerr
|
||||||
|
}
|
||||||
|
|
||||||
|
type msgerr struct {
|
||||||
|
msg *Message
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s session) do(query *Query) {
|
||||||
|
msg, err := s.client.do(context.Background(), s.Conn, query)
|
||||||
|
s.msgerrc <- msgerr{msg, err}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s session) recv() (*Message, error) {
|
||||||
|
me, ok := <-s.msgerrc
|
||||||
|
if !ok {
|
||||||
|
panic("impossible")
|
||||||
|
}
|
||||||
|
return me.msg, me.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func truncate(buf []byte, maxPacketLength int) ([]byte, error) {
|
||||||
|
msg := new(Message)
|
||||||
|
if _, err := msg.Unpack(buf[:maxPacketLen]); err != nil {
|
||||||
|
if err != errResourceLen && err != errBaseLen {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
msg.Truncated = true
|
||||||
|
|
||||||
|
return msg.Pack(buf[:0], true)
|
||||||
|
}
|
|
@ -0,0 +1,149 @@
|
||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Transport is an implementation of AddrDialer that manages connections to DNS
|
||||||
|
// servers. Transport may modify the sending and receiving of messages but does
|
||||||
|
// not modify messages.
|
||||||
|
type Transport struct {
|
||||||
|
TLSConfig *tls.Config // optional TLS config, used by DialAddr
|
||||||
|
|
||||||
|
// DialContext func creates the underlying net connection. The DialContext
|
||||||
|
// method of a new net.Dialer is used by default.
|
||||||
|
DialContext func(context.Context, string, string) (net.Conn, error)
|
||||||
|
|
||||||
|
// Proxy modifies the address of the DNS server to dial.
|
||||||
|
Proxy ProxyFunc
|
||||||
|
|
||||||
|
// DisablePipelining disables query pipelining for stream oriented
|
||||||
|
// connections as defined in RFC 7766, section 6.2.1.1.
|
||||||
|
DisablePipelining bool
|
||||||
|
|
||||||
|
plinemu sync.Mutex
|
||||||
|
plines map[net.Addr]*pipeline
|
||||||
|
}
|
||||||
|
|
||||||
|
// DialAddr dials a net Addr and returns a Conn.
|
||||||
|
func (t *Transport) DialAddr(ctx context.Context, addr net.Addr) (Conn, error) {
|
||||||
|
if !t.DisablePipelining {
|
||||||
|
if pline := t.getPipeline(addr); pline != nil && pline.alive() {
|
||||||
|
return pline.conn(), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := t.dialAddr(ctx, addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Transport) dialAddr(ctx context.Context, addr net.Addr) (Conn, error) {
|
||||||
|
conn, dnsOverTLS, err := t.dial(ctx, addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if conn, ok := conn.(Conn); ok {
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := conn.(*tls.Conn); dnsOverTLS && !ok {
|
||||||
|
ipaddr, _, err := net.SplitHostPort(addr.String())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg := &tls.Config{ServerName: ipaddr}
|
||||||
|
if t.TLSConfig != nil {
|
||||||
|
cfg = t.TLSConfig.Clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
conn = tls.Client(conn, cfg)
|
||||||
|
if err := conn.(*tls.Conn).Handshake(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := conn.(net.PacketConn); ok {
|
||||||
|
return &PacketConn{
|
||||||
|
Conn: conn,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
sconn := &StreamConn{
|
||||||
|
Conn: conn,
|
||||||
|
}
|
||||||
|
|
||||||
|
if !t.DisablePipelining {
|
||||||
|
pline := t.setPipeline(addr, sconn)
|
||||||
|
return pline.conn(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return sconn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var defaultDialer = &net.Dialer{
|
||||||
|
Resolver: &net.Resolver{},
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Transport) dial(ctx context.Context, addr net.Addr) (net.Conn, bool, error) {
|
||||||
|
if t.Proxy != nil {
|
||||||
|
var err error
|
||||||
|
if addr, err = t.Proxy(ctx, addr); err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
network, dnsOverTLS := addr.Network(), false
|
||||||
|
if strings.HasSuffix(network, "-tls") {
|
||||||
|
network, dnsOverTLS = network[:len(network)-4], true
|
||||||
|
}
|
||||||
|
|
||||||
|
dial := t.DialContext
|
||||||
|
if dial == nil {
|
||||||
|
dial = defaultDialer.DialContext
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := dial(ctx, network, addr.String())
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return conn, dnsOverTLS, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Transport) getPipeline(addr net.Addr) *pipeline {
|
||||||
|
t.plinemu.Lock()
|
||||||
|
defer t.plinemu.Unlock()
|
||||||
|
|
||||||
|
if t.plines == nil {
|
||||||
|
t.plines = make(map[net.Addr]*pipeline)
|
||||||
|
}
|
||||||
|
|
||||||
|
return t.plines[addr]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Transport) setPipeline(addr net.Addr, conn Conn) *pipeline {
|
||||||
|
pline := &pipeline{
|
||||||
|
Conn: conn,
|
||||||
|
inflight: make(map[int]pipelineTx),
|
||||||
|
}
|
||||||
|
go pline.run()
|
||||||
|
|
||||||
|
t.plinemu.Lock()
|
||||||
|
defer t.plinemu.Unlock()
|
||||||
|
|
||||||
|
if t.plines == nil {
|
||||||
|
t.plines = make(map[net.Addr]*pipeline)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.plines[addr] = pline
|
||||||
|
return pline
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RRSet is a set of resource records indexed by name and type.
|
||||||
|
type RRSet map[string]map[Type][]Record
|
||||||
|
|
||||||
|
// Zone is a contiguous set DNS records under an origin domain name.
|
||||||
|
type Zone struct {
|
||||||
|
Origin string
|
||||||
|
TTL time.Duration
|
||||||
|
|
||||||
|
SOA *SOA
|
||||||
|
|
||||||
|
RRs RRSet
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServeDNS answers DNS queries in zone z.
|
||||||
|
func (z *Zone) ServeDNS(ctx context.Context, w MessageWriter, r *Query) {
|
||||||
|
w.Authoritative(true)
|
||||||
|
|
||||||
|
var found bool
|
||||||
|
for _, q := range r.Questions {
|
||||||
|
if !strings.HasSuffix(q.Name, z.Origin) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
dn := q.Name[:len(q.Name)-len(z.Origin)-1]
|
||||||
|
|
||||||
|
rrs, ok := z.RRs[dn]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, rr := range rrs[q.Type] {
|
||||||
|
w.Answer(q.Name, z.TTL, rr)
|
||||||
|
found = true
|
||||||
|
|
||||||
|
if r.RecursionDesired && rr.Type() == TypeCNAME {
|
||||||
|
name := rr.(*CNAME).CNAME
|
||||||
|
dn := name[:len(name)-len(z.Origin)-1]
|
||||||
|
|
||||||
|
if rrs, ok := z.RRs[dn]; ok {
|
||||||
|
for _, rr := range rrs[q.Type] {
|
||||||
|
w.Answer(name, z.TTL, rr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
w.Status(NXDomain)
|
||||||
|
|
||||||
|
if z.SOA != nil {
|
||||||
|
w.Authority(z.Origin, z.TTL, z.SOA)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
# 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
|
|
@ -0,0 +1,5 @@
|
||||||
|
language: go
|
||||||
|
sudo: false
|
||||||
|
go:
|
||||||
|
- 1.7
|
||||||
|
- tip
|
|
@ -0,0 +1,20 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2014 lestrrat
|
||||||
|
|
||||||
|
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,190 @@
|
||||||
|
file-rotatelogs
|
||||||
|
==================
|
||||||
|
|
||||||
|
Periodically rotates log files from within the application. Port of [File::RotateLogs](https://metacpan.org/release/File-RotateLogs) from Perl to Go.
|
||||||
|
|
||||||
|
[![Build Status](https://travis-ci.org/lestrrat-go/file-rotatelogs.png?branch=master)](https://travis-ci.org/lestrrat-go/file-rotatelogs)
|
||||||
|
|
||||||
|
[![GoDoc](https://godoc.org/github.com/lestrrat-go/file-rotatelogs?status.svg)](https://godoc.org/github.com/lestrrat-go/file-rotatelogs)
|
||||||
|
|
||||||
|
|
||||||
|
# SYNOPSIS
|
||||||
|
|
||||||
|
```go
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
apachelog "github.com/lestrrat-go/apache-logformat"
|
||||||
|
rotatelogs "github.com/lestrrat-go/file-rotatelogs"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { ... })
|
||||||
|
|
||||||
|
logf, err := rotatelogs.New(
|
||||||
|
"/path/to/access_log.%Y%m%d%H%M",
|
||||||
|
rotatelogs.WithLinkName("/path/to/access_log"),
|
||||||
|
rotatelogs.WithMaxAge(24 * time.Hour),
|
||||||
|
rotatelogs.WithRotationTime(time.Hour),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("failed to create rotatelogs: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
http.ListenAndServe(":8080", apachelog.Wrap(mux, logf))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
# DESCRIPTION
|
||||||
|
|
||||||
|
When you integrate this to to you app, it automatically write to logs that
|
||||||
|
are rotated from within the app: No more disk-full alerts because you forgot
|
||||||
|
to setup logrotate!
|
||||||
|
|
||||||
|
To install, simply issue a `go get`:
|
||||||
|
|
||||||
|
```
|
||||||
|
go get github.com/lestrrat-go/file-rotatelogs
|
||||||
|
```
|
||||||
|
|
||||||
|
It's normally expected that this library is used with some other
|
||||||
|
logging service, such as the built-in `log` library, or loggers
|
||||||
|
such as `github.com/lestrrat-go/apache-logformat`.
|
||||||
|
|
||||||
|
```go
|
||||||
|
import(
|
||||||
|
"log"
|
||||||
|
"github.com/lestrrat-go/file-rotatelogs"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
rl, _ := rotatelogs.New("/path/to/access_log.%Y%m%d%H%M")
|
||||||
|
|
||||||
|
log.SetOutput(rl)
|
||||||
|
|
||||||
|
/* elsewhere ... */
|
||||||
|
log.Printf("Hello, World!")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
OPTIONS
|
||||||
|
====
|
||||||
|
|
||||||
|
## Pattern (Required)
|
||||||
|
|
||||||
|
The pattern used to generate actual log file names. You should use patterns
|
||||||
|
using the strftime (3) format. For example:
|
||||||
|
|
||||||
|
```go
|
||||||
|
rotatelogs.New("/var/log/myapp/log.%Y%m%d")
|
||||||
|
```
|
||||||
|
|
||||||
|
## Clock (default: rotatelogs.Local)
|
||||||
|
|
||||||
|
You may specify an object that implements the roatatelogs.Clock interface.
|
||||||
|
When this option is supplied, it's used to determine the current time to
|
||||||
|
base all of the calculations on. For example, if you want to base your
|
||||||
|
calculations in UTC, you may specify rotatelogs.UTC
|
||||||
|
|
||||||
|
```go
|
||||||
|
rotatelogs.New(
|
||||||
|
"/var/log/myapp/log.%Y%m%d",
|
||||||
|
rotatelogs.WithClock(rotatelogs.UTC),
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Location
|
||||||
|
|
||||||
|
This is an alternative to the `WithClock` option. Instead of providing an
|
||||||
|
explicit clock, you can provide a location for you times. We will create
|
||||||
|
a Clock object that produces times in your specified location, and configure
|
||||||
|
the rotatelog to respect it.
|
||||||
|
|
||||||
|
## LinkName (default: "")
|
||||||
|
|
||||||
|
Path where a symlink for the actual log file is placed. This allows you to
|
||||||
|
always check at the same location for log files even if the logs were rotated
|
||||||
|
|
||||||
|
```go
|
||||||
|
rotatelogs.New(
|
||||||
|
"/var/log/myapp/log.%Y%m%d",
|
||||||
|
rotatelogs.WithLinkName("/var/log/myapp/current"),
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
// Else where
|
||||||
|
$ tail -f /var/log/myapp/current
|
||||||
|
```
|
||||||
|
|
||||||
|
If not provided, no link will be written.
|
||||||
|
|
||||||
|
## RotationTime (default: 86400 sec)
|
||||||
|
|
||||||
|
Interval between file rotation. By default logs are rotated every 86400 seconds.
|
||||||
|
Note: Remember to use time.Duration values.
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Rotate every hour
|
||||||
|
rotatelogs.New(
|
||||||
|
"/var/log/myapp/log.%Y%m%d",
|
||||||
|
rotatelogs.WithRotationTime(time.Hour),
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
## MaxAge (default: 7 days)
|
||||||
|
|
||||||
|
Time to wait until old logs are purged. By default no logs are purged, which
|
||||||
|
certainly isn't what you want.
|
||||||
|
Note: Remember to use time.Duration values.
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Purge logs older than 1 hour
|
||||||
|
rotatelogs.New(
|
||||||
|
"/var/log/myapp/log.%Y%m%d",
|
||||||
|
rotatelogs.WithMaxAge(time.Hour),
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
## RotationCount (default: -1)
|
||||||
|
|
||||||
|
The number of files should be kept. By default, this option is disabled.
|
||||||
|
|
||||||
|
Note: MaxAge should be disabled by specifing `WithMaxAge(-1)` explicitly.
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Purge logs except latest 7 files
|
||||||
|
rotatelogs.New(
|
||||||
|
"/var/log/myapp/log.%Y%m%d",
|
||||||
|
rotatelogs.WithMaxAge(-1),
|
||||||
|
rotatelogs.WithRotationCount(7),
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
# Rotating files forcefully
|
||||||
|
|
||||||
|
If you want to rotate files forcefully before the actual rotation time has reached,
|
||||||
|
you may use the `Rotate()` method. This method forcefully rotates the logs, but
|
||||||
|
if the generated file name clashes, then a numeric suffix is added so that
|
||||||
|
the new file will forcefully appear on disk.
|
||||||
|
|
||||||
|
For example, suppose you had a pattern of '%Y.log' with a rotation time of
|
||||||
|
`86400` so that it only gets rotated every year, but for whatever reason you
|
||||||
|
wanted to rotate the logs now, you could install a signal handler to
|
||||||
|
trigger this rotation:
|
||||||
|
|
||||||
|
```go
|
||||||
|
rl := rotatelogs.New(...)
|
||||||
|
|
||||||
|
signal.Notify(ch, syscall.SIGHUP)
|
||||||
|
|
||||||
|
go func(ch chan os.Signal) {
|
||||||
|
<-ch
|
||||||
|
rl.Rotate()
|
||||||
|
}()
|
||||||
|
```
|
||||||
|
|
||||||
|
And you will get a log file name in like `2018.log.1`, `2018.log.2`, etc.
|
|
@ -0,0 +1,47 @@
|
||||||
|
package rotatelogs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
strftime "github.com/lestrrat-go/strftime"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RotateLogs represents a log file that gets
|
||||||
|
// automatically rotated as you write to it.
|
||||||
|
type RotateLogs struct {
|
||||||
|
clock Clock
|
||||||
|
curFn string
|
||||||
|
globPattern string
|
||||||
|
generation int
|
||||||
|
linkName string
|
||||||
|
maxAge time.Duration
|
||||||
|
mutex sync.RWMutex
|
||||||
|
outFh *os.File
|
||||||
|
pattern *strftime.Strftime
|
||||||
|
rotationTime time.Duration
|
||||||
|
rotationCount uint
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clock is the interface used by the RotateLogs
|
||||||
|
// object to determine the current time
|
||||||
|
type Clock interface {
|
||||||
|
Now() time.Time
|
||||||
|
}
|
||||||
|
type clockFn func() time.Time
|
||||||
|
|
||||||
|
// UTC is an object satisfying the Clock interface, which
|
||||||
|
// returns the current time in UTC
|
||||||
|
var UTC = clockFn(func() time.Time { return time.Now().UTC() })
|
||||||
|
|
||||||
|
// Local is an object satisfying the Clock interface, which
|
||||||
|
// returns the current time in the local timezone
|
||||||
|
var Local = clockFn(time.Now)
|
||||||
|
|
||||||
|
// Option is used to pass optional arguments to
|
||||||
|
// the RotateLogs constructor
|
||||||
|
type Option interface {
|
||||||
|
Name() string
|
||||||
|
Value() interface {}
|
||||||
|
}
|
25
vendor/github.com/lestrrat-go/file-rotatelogs/internal/option/option.go
generated
vendored
Normal file
25
vendor/github.com/lestrrat-go/file-rotatelogs/internal/option/option.go
generated
vendored
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
package option
|
||||||
|
|
||||||
|
type Interface interface {
|
||||||
|
Name() string
|
||||||
|
Value() interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Option struct {
|
||||||
|
name string
|
||||||
|
value interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(name string, value interface{}) *Option {
|
||||||
|
return &Option{
|
||||||
|
name: name,
|
||||||
|
value: value,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Option) Name() string {
|
||||||
|
return o.name
|
||||||
|
}
|
||||||
|
func (o *Option) Value() interface{} {
|
||||||
|
return o.value
|
||||||
|
}
|
|
@ -0,0 +1,66 @@
|
||||||
|
package rotatelogs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/lestrrat-go/file-rotatelogs/internal/option"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
optkeyClock = "clock"
|
||||||
|
optkeyLinkName = "link-name"
|
||||||
|
optkeyMaxAge = "max-age"
|
||||||
|
optkeyRotationTime = "rotation-time"
|
||||||
|
optkeyRotationCount = "rotation-count"
|
||||||
|
)
|
||||||
|
|
||||||
|
// WithClock creates a new Option that sets a clock
|
||||||
|
// that the RotateLogs object will use to determine
|
||||||
|
// the current time.
|
||||||
|
//
|
||||||
|
// By default rotatelogs.Local, which returns the
|
||||||
|
// current time in the local time zone, is used. If you
|
||||||
|
// would rather use UTC, use rotatelogs.UTC as the argument
|
||||||
|
// to this option, and pass it to the constructor.
|
||||||
|
func WithClock(c Clock) Option {
|
||||||
|
return option.New(optkeyClock, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithLocation creates a new Option that sets up a
|
||||||
|
// "Clock" interface that the RotateLogs object will use
|
||||||
|
// to determine the current time.
|
||||||
|
//
|
||||||
|
// This optin works by always returning the in the given
|
||||||
|
// location.
|
||||||
|
func WithLocation(loc *time.Location) Option {
|
||||||
|
return option.New(optkeyClock, clockFn(func() time.Time {
|
||||||
|
return time.Now().In(loc)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithLinkName creates a new Option that sets the
|
||||||
|
// symbolic link name that gets linked to the current
|
||||||
|
// file name being used.
|
||||||
|
func WithLinkName(s string) Option {
|
||||||
|
return option.New(optkeyLinkName, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithMaxAge creates a new Option that sets the
|
||||||
|
// max age of a log file before it gets purged from
|
||||||
|
// the file system.
|
||||||
|
func WithMaxAge(d time.Duration) Option {
|
||||||
|
return option.New(optkeyMaxAge, d)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithRotationTime creates a new Option that sets the
|
||||||
|
// time between rotation.
|
||||||
|
func WithRotationTime(d time.Duration) Option {
|
||||||
|
return option.New(optkeyRotationTime, d)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithRotationCount creates a new Option that sets the
|
||||||
|
// number of files should be kept before it gets
|
||||||
|
// purged from the file system.
|
||||||
|
func WithRotationCount(n uint) Option {
|
||||||
|
return option.New(optkeyRotationCount, n)
|
||||||
|
}
|
|
@ -0,0 +1,325 @@
|
||||||
|
// package rotatelogs is a port of File-RotateLogs from Perl
|
||||||
|
// (https://metacpan.org/release/File-RotateLogs), and it allows
|
||||||
|
// you to automatically rotate output files when you write to them
|
||||||
|
// according to the filename pattern that you can specify.
|
||||||
|
package rotatelogs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
strftime "github.com/lestrrat-go/strftime"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c clockFn) Now() time.Time {
|
||||||
|
return c()
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a new RotateLogs object. A log filename pattern
|
||||||
|
// must be passed. Optional `Option` parameters may be passed
|
||||||
|
func New(p string, options ...Option) (*RotateLogs, error) {
|
||||||
|
globPattern := p
|
||||||
|
for _, re := range patternConversionRegexps {
|
||||||
|
globPattern = re.ReplaceAllString(globPattern, "*")
|
||||||
|
}
|
||||||
|
|
||||||
|
pattern, err := strftime.New(p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, `invalid strftime pattern`)
|
||||||
|
}
|
||||||
|
|
||||||
|
var clock Clock = Local
|
||||||
|
rotationTime := 24 * time.Hour
|
||||||
|
var rotationCount uint
|
||||||
|
var linkName string
|
||||||
|
var maxAge time.Duration
|
||||||
|
|
||||||
|
for _, o := range options {
|
||||||
|
switch o.Name() {
|
||||||
|
case optkeyClock:
|
||||||
|
clock = o.Value().(Clock)
|
||||||
|
case optkeyLinkName:
|
||||||
|
linkName = o.Value().(string)
|
||||||
|
case optkeyMaxAge:
|
||||||
|
maxAge = o.Value().(time.Duration)
|
||||||
|
if maxAge < 0 {
|
||||||
|
maxAge = 0
|
||||||
|
}
|
||||||
|
case optkeyRotationTime:
|
||||||
|
rotationTime = o.Value().(time.Duration)
|
||||||
|
if rotationTime < 0 {
|
||||||
|
rotationTime = 0
|
||||||
|
}
|
||||||
|
case optkeyRotationCount:
|
||||||
|
rotationCount = o.Value().(uint)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if maxAge > 0 && rotationCount > 0 {
|
||||||
|
return nil, errors.New("options MaxAge and RotationCount cannot be both set")
|
||||||
|
}
|
||||||
|
|
||||||
|
if maxAge == 0 && rotationCount == 0 {
|
||||||
|
// if both are 0, give maxAge a sane default
|
||||||
|
maxAge = 7 * 24 * time.Hour
|
||||||
|
}
|
||||||
|
|
||||||
|
return &RotateLogs{
|
||||||
|
clock: clock,
|
||||||
|
globPattern: globPattern,
|
||||||
|
linkName: linkName,
|
||||||
|
maxAge: maxAge,
|
||||||
|
pattern: pattern,
|
||||||
|
rotationTime: rotationTime,
|
||||||
|
rotationCount: rotationCount,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rl *RotateLogs) genFilename() string {
|
||||||
|
now := rl.clock.Now()
|
||||||
|
|
||||||
|
// XXX HACK: Truncate only happens in UTC semantics, apparently.
|
||||||
|
// observed values for truncating given time with 86400 secs:
|
||||||
|
//
|
||||||
|
// before truncation: 2018/06/01 03:54:54 2018-06-01T03:18:00+09:00
|
||||||
|
// after truncation: 2018/06/01 03:54:54 2018-05-31T09:00:00+09:00
|
||||||
|
//
|
||||||
|
// This is really annoying when we want to truncate in local time
|
||||||
|
// so we hack: we take the apparent local time in the local zone,
|
||||||
|
// and pretend that it's in UTC. do our math, and put it back to
|
||||||
|
// the local zone
|
||||||
|
var base time.Time
|
||||||
|
if now.Location() != time.UTC {
|
||||||
|
base = time.Date(now.Year(), now.Month(), now.Day(), now.Hour(), now.Minute(), now.Second(), now.Nanosecond(), time.UTC)
|
||||||
|
base = base.Truncate(time.Duration(rl.rotationTime))
|
||||||
|
base = time.Date(base.Year(), base.Month(), base.Day(), base.Hour(), base.Minute(), base.Second(), base.Nanosecond(), base.Location())
|
||||||
|
} else {
|
||||||
|
base = now.Truncate(time.Duration(rl.rotationTime))
|
||||||
|
}
|
||||||
|
return rl.pattern.FormatString(base)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write satisfies the io.Writer interface. It writes to the
|
||||||
|
// appropriate file handle that is currently being used.
|
||||||
|
// If we have reached rotation time, the target file gets
|
||||||
|
// automatically rotated, and also purged if necessary.
|
||||||
|
func (rl *RotateLogs) Write(p []byte) (n int, err error) {
|
||||||
|
// Guard against concurrent writes
|
||||||
|
rl.mutex.Lock()
|
||||||
|
defer rl.mutex.Unlock()
|
||||||
|
|
||||||
|
out, err := rl.getWriter_nolock(false, false)
|
||||||
|
if err != nil {
|
||||||
|
return 0, errors.Wrap(err, `failed to acquite target io.Writer`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return out.Write(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// must be locked during this operation
|
||||||
|
func (rl *RotateLogs) getWriter_nolock(bailOnRotateFail, useGenerationalNames bool) (io.Writer, error) {
|
||||||
|
generation := rl.generation
|
||||||
|
|
||||||
|
// This filename contains the name of the "NEW" filename
|
||||||
|
// to log to, which may be newer than rl.currentFilename
|
||||||
|
filename := rl.genFilename()
|
||||||
|
if rl.curFn != filename {
|
||||||
|
generation = 0
|
||||||
|
} else {
|
||||||
|
if !useGenerationalNames {
|
||||||
|
// nothing to do
|
||||||
|
return rl.outFh, nil
|
||||||
|
}
|
||||||
|
// This is used when we *REALLY* want to rotate a log.
|
||||||
|
// instead of just using the regular strftime pattern, we
|
||||||
|
// create a new file name using generational names such as
|
||||||
|
// "foo.1", "foo.2", "foo.3", etc
|
||||||
|
for {
|
||||||
|
generation++
|
||||||
|
name := fmt.Sprintf("%s.%d", filename, generation)
|
||||||
|
if _, err := os.Stat(name); err != nil {
|
||||||
|
filename = name
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we got here, then we need to create a file
|
||||||
|
fh, err := os.OpenFile(filename, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Errorf("failed to open file %s: %s", rl.pattern, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := rl.rotate_nolock(filename); err != nil {
|
||||||
|
err = errors.Wrap(err, "failed to rotate")
|
||||||
|
if bailOnRotateFail {
|
||||||
|
// Failure to rotate is a problem, but it's really not a great
|
||||||
|
// idea to stop your application just because you couldn't rename
|
||||||
|
// your log.
|
||||||
|
// We only return this error when explicitly needed.
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
fmt.Fprintf(os.Stderr, "%s\n", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
rl.outFh.Close()
|
||||||
|
rl.outFh = fh
|
||||||
|
rl.curFn = filename
|
||||||
|
rl.generation = generation
|
||||||
|
|
||||||
|
return fh, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CurrentFileName returns the current file name that
|
||||||
|
// the RotateLogs object is writing to
|
||||||
|
func (rl *RotateLogs) CurrentFileName() string {
|
||||||
|
rl.mutex.RLock()
|
||||||
|
defer rl.mutex.RUnlock()
|
||||||
|
return rl.curFn
|
||||||
|
}
|
||||||
|
|
||||||
|
var patternConversionRegexps = []*regexp.Regexp{
|
||||||
|
regexp.MustCompile(`%[%+A-Za-z]`),
|
||||||
|
regexp.MustCompile(`\*+`),
|
||||||
|
}
|
||||||
|
|
||||||
|
type cleanupGuard struct {
|
||||||
|
enable bool
|
||||||
|
fn func()
|
||||||
|
mutex sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *cleanupGuard) Enable() {
|
||||||
|
g.mutex.Lock()
|
||||||
|
defer g.mutex.Unlock()
|
||||||
|
g.enable = true
|
||||||
|
}
|
||||||
|
func (g *cleanupGuard) Run() {
|
||||||
|
g.fn()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rotate forcefully rotates the log files. If the generated file name
|
||||||
|
// clash because file already exists, a numeric suffix of the form
|
||||||
|
// ".1", ".2", ".3" and so forth are appended to the end of the log file
|
||||||
|
//
|
||||||
|
// Thie method can be used in conjunction with a signal handler so to
|
||||||
|
// emulate servers that generate new log files when they receive a
|
||||||
|
// SIGHUP
|
||||||
|
func (rl *RotateLogs) Rotate() error {
|
||||||
|
rl.mutex.Lock()
|
||||||
|
defer rl.mutex.Unlock()
|
||||||
|
if _, err := rl.getWriter_nolock(true, true); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rl *RotateLogs) rotate_nolock(filename string) error {
|
||||||
|
lockfn := filename + `_lock`
|
||||||
|
fh, err := os.OpenFile(lockfn, os.O_CREATE|os.O_EXCL, 0644)
|
||||||
|
if err != nil {
|
||||||
|
// Can't lock, just return
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var guard cleanupGuard
|
||||||
|
guard.fn = func() {
|
||||||
|
fh.Close()
|
||||||
|
os.Remove(lockfn)
|
||||||
|
}
|
||||||
|
defer guard.Run()
|
||||||
|
|
||||||
|
if rl.linkName != "" {
|
||||||
|
tmpLinkName := filename + `_symlink`
|
||||||
|
if err := os.Symlink(filename, tmpLinkName); err != nil {
|
||||||
|
return errors.Wrap(err, `failed to create new symlink`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.Rename(tmpLinkName, rl.linkName); err != nil {
|
||||||
|
return errors.Wrap(err, `failed to rename new symlink`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if rl.maxAge <= 0 && rl.rotationCount <= 0 {
|
||||||
|
return errors.New("panic: maxAge and rotationCount are both set")
|
||||||
|
}
|
||||||
|
|
||||||
|
matches, err := filepath.Glob(rl.globPattern)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
cutoff := rl.clock.Now().Add(-1 * rl.maxAge)
|
||||||
|
var toUnlink []string
|
||||||
|
for _, path := range matches {
|
||||||
|
// Ignore lock files
|
||||||
|
if strings.HasSuffix(path, "_lock") || strings.HasSuffix(path, "_symlink") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
fi, err := os.Stat(path)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
fl, err := os.Lstat(path)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if rl.maxAge > 0 && fi.ModTime().After(cutoff) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if rl.rotationCount > 0 && fl.Mode()&os.ModeSymlink == os.ModeSymlink {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
toUnlink = append(toUnlink, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
if rl.rotationCount > 0 {
|
||||||
|
// Only delete if we have more than rotationCount
|
||||||
|
if rl.rotationCount >= uint(len(toUnlink)) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
toUnlink = toUnlink[:len(toUnlink)-int(rl.rotationCount)]
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(toUnlink) <= 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
guard.Enable()
|
||||||
|
go func() {
|
||||||
|
// unlink files on a separate goroutine
|
||||||
|
for _, path := range toUnlink {
|
||||||
|
os.Remove(path)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close satisfies the io.Closer interface. You must
|
||||||
|
// call this method if you performed any writes to
|
||||||
|
// the object.
|
||||||
|
func (rl *RotateLogs) Close() error {
|
||||||
|
rl.mutex.Lock()
|
||||||
|
defer rl.mutex.Unlock()
|
||||||
|
|
||||||
|
if rl.outFh == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
rl.outFh.Close()
|
||||||
|
rl.outFh = nil
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
# 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
|
|
@ -0,0 +1,5 @@
|
||||||
|
language: go
|
||||||
|
sudo: false
|
||||||
|
go:
|
||||||
|
- 1.7.x
|
||||||
|
- tip
|
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2016 lestrrat
|
||||||
|
|
||||||
|
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,151 @@
|
||||||
|
# strftime
|
||||||
|
|
||||||
|
Fast strftime for Go
|
||||||
|
|
||||||
|
[![Build Status](https://travis-ci.org/lestrrat-go/strftime.png?branch=master)](https://travis-ci.org/lestrrat-go/strftime)
|
||||||
|
|
||||||
|
[![GoDoc](https://godoc.org/github.com/lestrrat-go/strftime?status.svg)](https://godoc.org/github.com/lestrrat-go/strftime)
|
||||||
|
|
||||||
|
# SYNOPSIS
|
||||||
|
|
||||||
|
```go
|
||||||
|
f := strftime.New(`.... pattern ...`)
|
||||||
|
if err := f.Format(buf, time.Now()); err != nil {
|
||||||
|
log.Println(err.Error())
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
# DESCRIPTION
|
||||||
|
|
||||||
|
The goals for this library are
|
||||||
|
|
||||||
|
* Optimized for the same pattern being called repeatedly
|
||||||
|
* Be flexible about destination to write the results out
|
||||||
|
* Be as complete as possible in terms of conversion specifications
|
||||||
|
|
||||||
|
# API
|
||||||
|
|
||||||
|
## Format(string, time.Time) (string, error)
|
||||||
|
|
||||||
|
Takes the pattern and the time, and formats it. This function is a utility function that recompiles the pattern every time the function is called. If you know beforehand that you will be formatting the same pattern multiple times, consider using `New` to create a `Strftime` object and reuse it.
|
||||||
|
|
||||||
|
## New(string) (\*Strftime, error)
|
||||||
|
|
||||||
|
Takes the pattern and creates a new `Strftime` object.
|
||||||
|
|
||||||
|
## obj.Pattern() string
|
||||||
|
|
||||||
|
Returns the pattern string used to create this `Strftime` object
|
||||||
|
|
||||||
|
## obj.Format(io.Writer, time.Time) error
|
||||||
|
|
||||||
|
Formats the time according to the pre-compiled pattern, and writes the result to the specified `io.Writer`
|
||||||
|
|
||||||
|
## obj.FormatString(time.Time) string
|
||||||
|
|
||||||
|
Formats the time according to the pre-compiled pattern, and returns the result string.
|
||||||
|
|
||||||
|
# SUPPORTED CONVERSION SPECIFICATIONS
|
||||||
|
|
||||||
|
| pattern | description |
|
||||||
|
|:--------|:------------|
|
||||||
|
| %A | national representation of the full weekday name |
|
||||||
|
| %a | national representation of the abbreviated weekday |
|
||||||
|
| %B | national representation of the full month name |
|
||||||
|
| %b | national representation of the abbreviated month name |
|
||||||
|
| %C | (year / 100) as decimal number; single digits are preceded by a zero |
|
||||||
|
| %c | national representation of time and date |
|
||||||
|
| %D | equivalent to %m/%d/%y |
|
||||||
|
| %d | day of the month as a decimal number (01-31) |
|
||||||
|
| %e | the day of the month as a decimal number (1-31); single digits are preceded by a blank |
|
||||||
|
| %F | equivalent to %Y-%m-%d |
|
||||||
|
| %H | the hour (24-hour clock) as a decimal number (00-23) |
|
||||||
|
| %h | same as %b |
|
||||||
|
| %I | the hour (12-hour clock) as a decimal number (01-12) |
|
||||||
|
| %j | the day of the year as a decimal number (001-366) |
|
||||||
|
| %k | the hour (24-hour clock) as a decimal number (0-23); single digits are preceded by a blank |
|
||||||
|
| %l | the hour (12-hour clock) as a decimal number (1-12); single digits are preceded by a blank |
|
||||||
|
| %M | the minute as a decimal number (00-59) |
|
||||||
|
| %m | the month as a decimal number (01-12) |
|
||||||
|
| %n | a newline |
|
||||||
|
| %p | national representation of either "ante meridiem" (a.m.) or "post meridiem" (p.m.) as appropriate. |
|
||||||
|
| %R | equivalent to %H:%M |
|
||||||
|
| %r | equivalent to %I:%M:%S %p |
|
||||||
|
| %S | the second as a decimal number (00-60) |
|
||||||
|
| %T | equivalent to %H:%M:%S |
|
||||||
|
| %t | a tab |
|
||||||
|
| %U | the week number of the year (Sunday as the first day of the week) as a decimal number (00-53) |
|
||||||
|
| %u | the weekday (Monday as the first day of the week) as a decimal number (1-7) |
|
||||||
|
| %V | the week number of the year (Monday as the first day of the week) as a decimal number (01-53) |
|
||||||
|
| %v | equivalent to %e-%b-%Y |
|
||||||
|
| %W | the week number of the year (Monday as the first day of the week) as a decimal number (00-53) |
|
||||||
|
| %w | the weekday (Sunday as the first day of the week) as a decimal number (0-6) |
|
||||||
|
| %X | national representation of the time |
|
||||||
|
| %x | national representation of the date |
|
||||||
|
| %Y | the year with century as a decimal number |
|
||||||
|
| %y | the year without century as a decimal number (00-99) |
|
||||||
|
| %Z | the time zone name |
|
||||||
|
| %z | the time zone offset from UTC |
|
||||||
|
| %% | a '%' |
|
||||||
|
|
||||||
|
# PERFORMANCE / OTHER LIBRARIES
|
||||||
|
|
||||||
|
The following benchmarks were run separately because some libraries were using cgo on specific platforms (notabley, the fastly version)
|
||||||
|
|
||||||
|
```
|
||||||
|
// On my OS X 10.11.6, 2.9 GHz Intel Core i5, 16GB memory.
|
||||||
|
// go version go1.8rc1 darwin/amd64
|
||||||
|
hummingbird% go test -tags bench -benchmem -bench .
|
||||||
|
<snip>
|
||||||
|
BenchmarkTebeka-4 300000 4469 ns/op 288 B/op 21 allocs/op
|
||||||
|
BenchmarkJehiah-4 1000000 1931 ns/op 256 B/op 17 allocs/op
|
||||||
|
BenchmarkFastly-4 2000000 724 ns/op 80 B/op 5 allocs/op
|
||||||
|
BenchmarkLestrrat-4 1000000 1572 ns/op 240 B/op 3 allocs/op
|
||||||
|
BenchmarkLestrratCachedString-4 3000000 548 ns/op 128 B/op 2 allocs/op
|
||||||
|
BenchmarkLestrratCachedWriter-4 500000 2519 ns/op 192 B/op 3 allocs/op
|
||||||
|
PASS
|
||||||
|
ok github.com/lestrrat-go/strftime 22.900s
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
// On a host on Google Cloud Platform, machine-type: n1-standard-4 (vCPU x 4, memory: 15GB)
|
||||||
|
// Linux <snip> 3.16.0-4-amd64 #1 SMP Debian 3.16.36-1+deb8u2 (2016-10-19) x86_64 GNU/Linux
|
||||||
|
// go version go1.8rc1 linux/amd64
|
||||||
|
hummingbird% go test -tags bench -benchmem -bench .
|
||||||
|
<snip>
|
||||||
|
BenchmarkTebeka-4 500000 3904 ns/op 288 B/op 21 allocs/op
|
||||||
|
BenchmarkJehiah-4 1000000 1665 ns/op 256 B/op 17 allocs/op
|
||||||
|
BenchmarkFastly-4 1000000 2134 ns/op 192 B/op 13 allocs/op
|
||||||
|
BenchmarkLestrrat-4 1000000 1327 ns/op 240 B/op 3 allocs/op
|
||||||
|
BenchmarkLestrratCachedString-4 3000000 498 ns/op 128 B/op 2 allocs/op
|
||||||
|
BenchmarkLestrratCachedWriter-4 1000000 3390 ns/op 192 B/op 3 allocs/op
|
||||||
|
PASS
|
||||||
|
ok github.com/lestrrat-go/strftime 44.854s
|
||||||
|
```
|
||||||
|
|
||||||
|
This library is much faster than other libraries *IF* you can reuse the format pattern.
|
||||||
|
|
||||||
|
Here's the annotated list from the benchmark results. You can clearly see that (re)using a `Strftime` object
|
||||||
|
and producing a string is the fastest. Writing to an `io.Writer` seems a bit sluggish, but since
|
||||||
|
the one producing the string is doing almost exactly the same thing, we believe this is purely the overhead of
|
||||||
|
writing to an `io.Writer`
|
||||||
|
|
||||||
|
| Import Path | Score | Note |
|
||||||
|
|:------------------------------------|--------:|:--------------------------------|
|
||||||
|
| github.com/lestrrat-go/strftime | 3000000 | Using `FormatString()` (cached) |
|
||||||
|
| github.com/fastly/go-utils/strftime | 2000000 | Pure go version on OS X |
|
||||||
|
| github.com/lestrrat-go/strftime | 1000000 | Using `Format()` (NOT cached) |
|
||||||
|
| github.com/jehiah/go-strftime | 1000000 | |
|
||||||
|
| github.com/fastly/go-utils/strftime | 1000000 | cgo version on Linux |
|
||||||
|
| github.com/lestrrat-go/strftime | 500000 | Using `Format()` (cached) |
|
||||||
|
| github.com/tebeka/strftime | 300000 | |
|
||||||
|
|
||||||
|
However, depending on your pattern, this speed may vary. If you find a particular pattern that seems sluggish,
|
||||||
|
please send in patches or tests.
|
||||||
|
|
||||||
|
Please also note that this benchmark only uses the subset of conversion specifications that are supported by *ALL* of the libraries compared.
|
||||||
|
|
||||||
|
Somethings to consider when making performance comparisons in the future:
|
||||||
|
|
||||||
|
* Can it write to io.Writer?
|
||||||
|
* Which `%specification` does it handle?
|
|
@ -0,0 +1,219 @@
|
||||||
|
package strftime
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
var directives = map[byte]appender{
|
||||||
|
'A': timefmt("Monday"),
|
||||||
|
'a': timefmt("Mon"),
|
||||||
|
'B': timefmt("January"),
|
||||||
|
'b': timefmt("Jan"),
|
||||||
|
'C': ¢ury{},
|
||||||
|
'c': timefmt("Mon Jan _2 15:04:05 2006"),
|
||||||
|
'D': timefmt("01/02/06"),
|
||||||
|
'd': timefmt("02"),
|
||||||
|
'e': timefmt("_2"),
|
||||||
|
'F': timefmt("2006-01-02"),
|
||||||
|
'H': timefmt("15"),
|
||||||
|
'h': timefmt("Jan"), // same as 'b'
|
||||||
|
'I': timefmt("3"),
|
||||||
|
'j': &dayofyear{},
|
||||||
|
'k': hourwblank(false),
|
||||||
|
'l': hourwblank(true),
|
||||||
|
'M': timefmt("04"),
|
||||||
|
'm': timefmt("01"),
|
||||||
|
'n': verbatim("\n"),
|
||||||
|
'p': timefmt("PM"),
|
||||||
|
'R': timefmt("15:04"),
|
||||||
|
'r': timefmt("3:04:05 PM"),
|
||||||
|
'S': timefmt("05"),
|
||||||
|
'T': timefmt("15:04:05"),
|
||||||
|
't': verbatim("\t"),
|
||||||
|
'U': weeknumberOffset(0), // week number of the year, Sunday first
|
||||||
|
'u': weekday(1),
|
||||||
|
'V': &weeknumber{},
|
||||||
|
'v': timefmt("_2-Jan-2006"),
|
||||||
|
'W': weeknumberOffset(1), // week number of the year, Monday first
|
||||||
|
'w': weekday(0),
|
||||||
|
'X': timefmt("15:04:05"), // national representation of the time XXX is this correct?
|
||||||
|
'x': timefmt("01/02/06"), // national representation of the date XXX is this correct?
|
||||||
|
'Y': timefmt("2006"), // year with century
|
||||||
|
'y': timefmt("06"), // year w/o century
|
||||||
|
'Z': timefmt("MST"), // time zone name
|
||||||
|
'z': timefmt("-0700"), // time zone ofset from UTC
|
||||||
|
'%': verbatim("%"),
|
||||||
|
}
|
||||||
|
|
||||||
|
type combiningAppend struct {
|
||||||
|
list appenderList
|
||||||
|
prev appender
|
||||||
|
prevCanCombine bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ca *combiningAppend) Append(w appender) {
|
||||||
|
if ca.prevCanCombine {
|
||||||
|
if wc, ok := w.(combiner); ok && wc.canCombine() {
|
||||||
|
ca.prev = ca.prev.(combiner).combine(wc)
|
||||||
|
ca.list[len(ca.list)-1] = ca.prev
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ca.list = append(ca.list, w)
|
||||||
|
ca.prev = w
|
||||||
|
ca.prevCanCombine = false
|
||||||
|
if comb, ok := w.(combiner); ok {
|
||||||
|
if comb.canCombine() {
|
||||||
|
ca.prevCanCombine = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func compile(wl *appenderList, p string) error {
|
||||||
|
var ca combiningAppend
|
||||||
|
for l := len(p); l > 0; l = len(p) {
|
||||||
|
i := strings.IndexByte(p, '%')
|
||||||
|
if i < 0 {
|
||||||
|
ca.Append(verbatim(p))
|
||||||
|
// this is silly, but I don't trust break keywords when there's a
|
||||||
|
// possibility of this piece of code being rearranged
|
||||||
|
p = p[l:]
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if i == l-1 {
|
||||||
|
return errors.New(`stray % at the end of pattern`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// we found a '%'. we need the next byte to decide what to do next
|
||||||
|
// we already know that i < l - 1
|
||||||
|
// everything up to the i is verbatim
|
||||||
|
if i > 0 {
|
||||||
|
ca.Append(verbatim(p[:i]))
|
||||||
|
p = p[i:]
|
||||||
|
}
|
||||||
|
|
||||||
|
directive, ok := directives[p[1]]
|
||||||
|
if !ok {
|
||||||
|
return errors.Errorf(`unknown time format specification '%c'`, p[1])
|
||||||
|
}
|
||||||
|
ca.Append(directive)
|
||||||
|
p = p[2:]
|
||||||
|
}
|
||||||
|
|
||||||
|
*wl = ca.list
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format takes the format `s` and the time `t` to produce the
|
||||||
|
// format date/time. Note that this function re-compiles the
|
||||||
|
// pattern every time it is called.
|
||||||
|
//
|
||||||
|
// If you know beforehand that you will be reusing the pattern
|
||||||
|
// within your application, consider creating a `Strftime` object
|
||||||
|
// and reusing it.
|
||||||
|
func Format(p string, t time.Time) (string, error) {
|
||||||
|
var dst []byte
|
||||||
|
// TODO: optimize for 64 byte strings
|
||||||
|
dst = make([]byte, 0, len(p)+10)
|
||||||
|
// Compile, but execute as we go
|
||||||
|
for l := len(p); l > 0; l = len(p) {
|
||||||
|
i := strings.IndexByte(p, '%')
|
||||||
|
if i < 0 {
|
||||||
|
dst = append(dst, p...)
|
||||||
|
// this is silly, but I don't trust break keywords when there's a
|
||||||
|
// possibility of this piece of code being rearranged
|
||||||
|
p = p[l:]
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if i == l-1 {
|
||||||
|
return "", errors.New(`stray % at the end of pattern`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// we found a '%'. we need the next byte to decide what to do next
|
||||||
|
// we already know that i < l - 1
|
||||||
|
// everything up to the i is verbatim
|
||||||
|
if i > 0 {
|
||||||
|
dst = append(dst, p[:i]...)
|
||||||
|
p = p[i:]
|
||||||
|
}
|
||||||
|
|
||||||
|
directive, ok := directives[p[1]]
|
||||||
|
if !ok {
|
||||||
|
return "", errors.Errorf(`unknown time format specification '%c'`, p[1])
|
||||||
|
}
|
||||||
|
dst = directive.Append(dst, t)
|
||||||
|
p = p[2:]
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(dst), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strftime is the object that represents a compiled strftime pattern
|
||||||
|
type Strftime struct {
|
||||||
|
pattern string
|
||||||
|
compiled appenderList
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a new Strftime object. If the compilation fails, then
|
||||||
|
// an error is returned in the second argument.
|
||||||
|
func New(f string) (*Strftime, error) {
|
||||||
|
var wl appenderList
|
||||||
|
if err := compile(&wl, f); err != nil {
|
||||||
|
return nil, errors.Wrap(err, `failed to compile format`)
|
||||||
|
}
|
||||||
|
return &Strftime{
|
||||||
|
pattern: f,
|
||||||
|
compiled: wl,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pattern returns the original pattern string
|
||||||
|
func (f *Strftime) Pattern() string {
|
||||||
|
return f.pattern
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format takes the destination `dst` and time `t`. It formats the date/time
|
||||||
|
// using the pre-compiled pattern, and outputs the results to `dst`
|
||||||
|
func (f *Strftime) Format(dst io.Writer, t time.Time) error {
|
||||||
|
const bufSize = 64
|
||||||
|
var b []byte
|
||||||
|
max := len(f.pattern) + 10
|
||||||
|
if max < bufSize {
|
||||||
|
var buf [bufSize]byte
|
||||||
|
b = buf[:0]
|
||||||
|
} else {
|
||||||
|
b = make([]byte, 0, max)
|
||||||
|
}
|
||||||
|
if _, err := dst.Write(f.format(b, t)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Strftime) format(b []byte, t time.Time) []byte {
|
||||||
|
for _, w := range f.compiled {
|
||||||
|
b = w.Append(b, t)
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// FormatString takes the time `t` and formats it, returning the
|
||||||
|
// string containing the formated data.
|
||||||
|
func (f *Strftime) FormatString(t time.Time) string {
|
||||||
|
const bufSize = 64
|
||||||
|
var b []byte
|
||||||
|
max := len(f.pattern) + 10
|
||||||
|
if max < bufSize {
|
||||||
|
var buf [bufSize]byte
|
||||||
|
b = buf[:0]
|
||||||
|
} else {
|
||||||
|
b = make([]byte, 0, max)
|
||||||
|
}
|
||||||
|
return string(f.format(b, t))
|
||||||
|
}
|
|
@ -0,0 +1,168 @@
|
||||||
|
package strftime
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type appender interface {
|
||||||
|
Append([]byte, time.Time) []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type appenderList []appender
|
||||||
|
|
||||||
|
// does the time.Format thing
|
||||||
|
type timefmtw struct {
|
||||||
|
s string
|
||||||
|
}
|
||||||
|
|
||||||
|
func timefmt(s string) *timefmtw {
|
||||||
|
return &timefmtw{s: s}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v timefmtw) Append(b []byte, t time.Time) []byte {
|
||||||
|
return t.AppendFormat(b, v.s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v timefmtw) str() string {
|
||||||
|
return v.s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v timefmtw) canCombine() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v timefmtw) combine(w combiner) appender {
|
||||||
|
return timefmt(v.s + w.str())
|
||||||
|
}
|
||||||
|
|
||||||
|
type verbatimw struct {
|
||||||
|
s string
|
||||||
|
}
|
||||||
|
|
||||||
|
func verbatim(s string) *verbatimw {
|
||||||
|
return &verbatimw{s: s}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v verbatimw) Append(b []byte, _ time.Time) []byte {
|
||||||
|
return append(b, v.s...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v verbatimw) canCombine() bool {
|
||||||
|
return canCombine(v.s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v verbatimw) combine(w combiner) appender {
|
||||||
|
if _, ok := w.(*timefmtw); ok {
|
||||||
|
return timefmt(v.s + w.str())
|
||||||
|
}
|
||||||
|
return verbatim(v.s + w.str())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v verbatimw) str() string {
|
||||||
|
return v.s
|
||||||
|
}
|
||||||
|
|
||||||
|
// These words below, as well as any decimal character
|
||||||
|
var combineExclusion = []string{
|
||||||
|
"Mon",
|
||||||
|
"Monday",
|
||||||
|
"Jan",
|
||||||
|
"January",
|
||||||
|
"MST",
|
||||||
|
"PM",
|
||||||
|
}
|
||||||
|
|
||||||
|
func canCombine(s string) bool {
|
||||||
|
if strings.ContainsAny(s, "0123456789") {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, word := range combineExclusion {
|
||||||
|
if strings.Contains(s, word) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
type combiner interface {
|
||||||
|
canCombine() bool
|
||||||
|
combine(combiner) appender
|
||||||
|
str() string
|
||||||
|
}
|
||||||
|
|
||||||
|
type century struct{}
|
||||||
|
|
||||||
|
func (v century) Append(b []byte, t time.Time) []byte {
|
||||||
|
n := t.Year() / 100
|
||||||
|
if n < 10 {
|
||||||
|
b = append(b, '0')
|
||||||
|
}
|
||||||
|
return append(b, strconv.Itoa(n)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
type weekday int
|
||||||
|
|
||||||
|
func (v weekday) Append(b []byte, t time.Time) []byte {
|
||||||
|
n := int(t.Weekday())
|
||||||
|
if n < int(v) {
|
||||||
|
n += 7
|
||||||
|
}
|
||||||
|
return append(b, byte(n+48))
|
||||||
|
}
|
||||||
|
|
||||||
|
type weeknumberOffset int
|
||||||
|
|
||||||
|
func (v weeknumberOffset) Append(b []byte, t time.Time) []byte {
|
||||||
|
yd := t.YearDay()
|
||||||
|
offset := int(t.Weekday()) - int(v)
|
||||||
|
if offset < 0 {
|
||||||
|
offset += 7
|
||||||
|
}
|
||||||
|
|
||||||
|
if yd < offset {
|
||||||
|
return append(b, '0', '0')
|
||||||
|
}
|
||||||
|
|
||||||
|
n := ((yd - offset) / 7) + 1
|
||||||
|
if n < 10 {
|
||||||
|
b = append(b, '0')
|
||||||
|
}
|
||||||
|
return append(b, strconv.Itoa(n)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
type weeknumber struct{}
|
||||||
|
|
||||||
|
func (v weeknumber) Append(b []byte, t time.Time) []byte {
|
||||||
|
_, n := t.ISOWeek()
|
||||||
|
if n < 10 {
|
||||||
|
b = append(b, '0')
|
||||||
|
}
|
||||||
|
return append(b, strconv.Itoa(n)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
type dayofyear struct{}
|
||||||
|
|
||||||
|
func (v dayofyear) Append(b []byte, t time.Time) []byte {
|
||||||
|
n := t.YearDay()
|
||||||
|
if n < 10 {
|
||||||
|
b = append(b, '0', '0')
|
||||||
|
} else if n < 100 {
|
||||||
|
b = append(b, '0')
|
||||||
|
}
|
||||||
|
return append(b, strconv.Itoa(n)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
type hourwblank bool
|
||||||
|
|
||||||
|
func (v hourwblank) Append(b []byte, t time.Time) []byte {
|
||||||
|
h := t.Hour()
|
||||||
|
if bool(v) && h > 12 {
|
||||||
|
h = h - 12
|
||||||
|
}
|
||||||
|
if h < 10 {
|
||||||
|
b = append(b, ' ')
|
||||||
|
}
|
||||||
|
return append(b, strconv.Itoa(h)...)
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
# 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
|
|
@ -0,0 +1,11 @@
|
||||||
|
language: go
|
||||||
|
go_import_path: github.com/pkg/errors
|
||||||
|
go:
|
||||||
|
- 1.4.3
|
||||||
|
- 1.5.4
|
||||||
|
- 1.6.2
|
||||||
|
- 1.7.1
|
||||||
|
- tip
|
||||||
|
|
||||||
|
script:
|
||||||
|
- go test -v ./...
|
|
@ -0,0 +1,23 @@
|
||||||
|
Copyright (c) 2015, Dave Cheney <dave@cheney.net>
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
* Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1,52 @@
|
||||||
|
# errors [![Travis-CI](https://travis-ci.org/pkg/errors.svg)](https://travis-ci.org/pkg/errors) [![AppVeyor](https://ci.appveyor.com/api/projects/status/b98mptawhudj53ep/branch/master?svg=true)](https://ci.appveyor.com/project/davecheney/errors/branch/master) [![GoDoc](https://godoc.org/github.com/pkg/errors?status.svg)](http://godoc.org/github.com/pkg/errors) [![Report card](https://goreportcard.com/badge/github.com/pkg/errors)](https://goreportcard.com/report/github.com/pkg/errors)
|
||||||
|
|
||||||
|
Package errors provides simple error handling primitives.
|
||||||
|
|
||||||
|
`go get github.com/pkg/errors`
|
||||||
|
|
||||||
|
The traditional error handling idiom in Go is roughly akin to
|
||||||
|
```go
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
```
|
||||||
|
which applied recursively up the call stack results in error reports without context or debugging information. The errors package allows programmers to add context to the failure path in their code in a way that does not destroy the original value of the error.
|
||||||
|
|
||||||
|
## Adding context to an error
|
||||||
|
|
||||||
|
The errors.Wrap function returns a new error that adds context to the original error. For example
|
||||||
|
```go
|
||||||
|
_, err := ioutil.ReadAll(r)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "read failed")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
## Retrieving the cause of an error
|
||||||
|
|
||||||
|
Using `errors.Wrap` constructs a stack of errors, adding context to the preceding error. Depending on the nature of the error it may be necessary to reverse the operation of errors.Wrap to retrieve the original error for inspection. Any error value which implements this interface can be inspected by `errors.Cause`.
|
||||||
|
```go
|
||||||
|
type causer interface {
|
||||||
|
Cause() error
|
||||||
|
}
|
||||||
|
```
|
||||||
|
`errors.Cause` will recursively retrieve the topmost error which does not implement `causer`, which is assumed to be the original cause. For example:
|
||||||
|
```go
|
||||||
|
switch err := errors.Cause(err).(type) {
|
||||||
|
case *MyError:
|
||||||
|
// handle specifically
|
||||||
|
default:
|
||||||
|
// unknown error
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
[Read the package documentation for more information](https://godoc.org/github.com/pkg/errors).
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
We welcome pull requests, bug fixes and issue reports. With that said, the bar for adding new symbols to this package is intentionally set high.
|
||||||
|
|
||||||
|
Before proposing a change, please discuss your change by raising an issue.
|
||||||
|
|
||||||
|
## Licence
|
||||||
|
|
||||||
|
BSD-2-Clause
|
|
@ -0,0 +1,32 @@
|
||||||
|
version: build-{build}.{branch}
|
||||||
|
|
||||||
|
clone_folder: C:\gopath\src\github.com\pkg\errors
|
||||||
|
shallow_clone: true # for startup speed
|
||||||
|
|
||||||
|
environment:
|
||||||
|
GOPATH: C:\gopath
|
||||||
|
|
||||||
|
platform:
|
||||||
|
- x64
|
||||||
|
|
||||||
|
# http://www.appveyor.com/docs/installed-software
|
||||||
|
install:
|
||||||
|
# some helpful output for debugging builds
|
||||||
|
- go version
|
||||||
|
- go env
|
||||||
|
# pre-installed MinGW at C:\MinGW is 32bit only
|
||||||
|
# but MSYS2 at C:\msys64 has mingw64
|
||||||
|
- set PATH=C:\msys64\mingw64\bin;%PATH%
|
||||||
|
- gcc --version
|
||||||
|
- g++ --version
|
||||||
|
|
||||||
|
build_script:
|
||||||
|
- go install -v ./...
|
||||||
|
|
||||||
|
test_script:
|
||||||
|
- set PATH=C:\gopath\bin;%PATH%
|
||||||
|
- go test -v ./...
|
||||||
|
|
||||||
|
#artifacts:
|
||||||
|
# - path: '%GOPATH%\bin\*.exe'
|
||||||
|
deploy: off
|
|
@ -0,0 +1,269 @@
|
||||||
|
// Package errors provides simple error handling primitives.
|
||||||
|
//
|
||||||
|
// The traditional error handling idiom in Go is roughly akin to
|
||||||
|
//
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// which applied recursively up the call stack results in error reports
|
||||||
|
// without context or debugging information. The errors package allows
|
||||||
|
// programmers to add context to the failure path in their code in a way
|
||||||
|
// that does not destroy the original value of the error.
|
||||||
|
//
|
||||||
|
// Adding context to an error
|
||||||
|
//
|
||||||
|
// The errors.Wrap function returns a new error that adds context to the
|
||||||
|
// original error by recording a stack trace at the point Wrap is called,
|
||||||
|
// and the supplied message. For example
|
||||||
|
//
|
||||||
|
// _, err := ioutil.ReadAll(r)
|
||||||
|
// if err != nil {
|
||||||
|
// return errors.Wrap(err, "read failed")
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// If additional control is required the errors.WithStack and errors.WithMessage
|
||||||
|
// functions destructure errors.Wrap into its component operations of annotating
|
||||||
|
// an error with a stack trace and an a message, respectively.
|
||||||
|
//
|
||||||
|
// Retrieving the cause of an error
|
||||||
|
//
|
||||||
|
// Using errors.Wrap constructs a stack of errors, adding context to the
|
||||||
|
// preceding error. Depending on the nature of the error it may be necessary
|
||||||
|
// to reverse the operation of errors.Wrap to retrieve the original error
|
||||||
|
// for inspection. Any error value which implements this interface
|
||||||
|
//
|
||||||
|
// type causer interface {
|
||||||
|
// Cause() error
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// can be inspected by errors.Cause. errors.Cause will recursively retrieve
|
||||||
|
// the topmost error which does not implement causer, which is assumed to be
|
||||||
|
// the original cause. For example:
|
||||||
|
//
|
||||||
|
// switch err := errors.Cause(err).(type) {
|
||||||
|
// case *MyError:
|
||||||
|
// // handle specifically
|
||||||
|
// default:
|
||||||
|
// // unknown error
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// causer interface is not exported by this package, but is considered a part
|
||||||
|
// of stable public API.
|
||||||
|
//
|
||||||
|
// Formatted printing of errors
|
||||||
|
//
|
||||||
|
// All error values returned from this package implement fmt.Formatter and can
|
||||||
|
// be formatted by the fmt package. The following verbs are supported
|
||||||
|
//
|
||||||
|
// %s print the error. If the error has a Cause it will be
|
||||||
|
// printed recursively
|
||||||
|
// %v see %s
|
||||||
|
// %+v extended format. Each Frame of the error's StackTrace will
|
||||||
|
// be printed in detail.
|
||||||
|
//
|
||||||
|
// Retrieving the stack trace of an error or wrapper
|
||||||
|
//
|
||||||
|
// New, Errorf, Wrap, and Wrapf record a stack trace at the point they are
|
||||||
|
// invoked. This information can be retrieved with the following interface.
|
||||||
|
//
|
||||||
|
// type stackTracer interface {
|
||||||
|
// StackTrace() errors.StackTrace
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Where errors.StackTrace is defined as
|
||||||
|
//
|
||||||
|
// type StackTrace []Frame
|
||||||
|
//
|
||||||
|
// The Frame type represents a call site in the stack trace. Frame supports
|
||||||
|
// the fmt.Formatter interface that can be used for printing information about
|
||||||
|
// the stack trace of this error. For example:
|
||||||
|
//
|
||||||
|
// if err, ok := err.(stackTracer); ok {
|
||||||
|
// for _, f := range err.StackTrace() {
|
||||||
|
// fmt.Printf("%+s:%d", f)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// stackTracer interface is not exported by this package, but is considered a part
|
||||||
|
// of stable public API.
|
||||||
|
//
|
||||||
|
// See the documentation for Frame.Format for more details.
|
||||||
|
package errors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// New returns an error with the supplied message.
|
||||||
|
// New also records the stack trace at the point it was called.
|
||||||
|
func New(message string) error {
|
||||||
|
return &fundamental{
|
||||||
|
msg: message,
|
||||||
|
stack: callers(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Errorf formats according to a format specifier and returns the string
|
||||||
|
// as a value that satisfies error.
|
||||||
|
// Errorf also records the stack trace at the point it was called.
|
||||||
|
func Errorf(format string, args ...interface{}) error {
|
||||||
|
return &fundamental{
|
||||||
|
msg: fmt.Sprintf(format, args...),
|
||||||
|
stack: callers(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fundamental is an error that has a message and a stack, but no caller.
|
||||||
|
type fundamental struct {
|
||||||
|
msg string
|
||||||
|
*stack
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fundamental) Error() string { return f.msg }
|
||||||
|
|
||||||
|
func (f *fundamental) Format(s fmt.State, verb rune) {
|
||||||
|
switch verb {
|
||||||
|
case 'v':
|
||||||
|
if s.Flag('+') {
|
||||||
|
io.WriteString(s, f.msg)
|
||||||
|
f.stack.Format(s, verb)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fallthrough
|
||||||
|
case 's':
|
||||||
|
io.WriteString(s, f.msg)
|
||||||
|
case 'q':
|
||||||
|
fmt.Fprintf(s, "%q", f.msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithStack annotates err with a stack trace at the point WithStack was called.
|
||||||
|
// If err is nil, WithStack returns nil.
|
||||||
|
func WithStack(err error) error {
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &withStack{
|
||||||
|
err,
|
||||||
|
callers(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type withStack struct {
|
||||||
|
error
|
||||||
|
*stack
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *withStack) Cause() error { return w.error }
|
||||||
|
|
||||||
|
func (w *withStack) Format(s fmt.State, verb rune) {
|
||||||
|
switch verb {
|
||||||
|
case 'v':
|
||||||
|
if s.Flag('+') {
|
||||||
|
fmt.Fprintf(s, "%+v", w.Cause())
|
||||||
|
w.stack.Format(s, verb)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fallthrough
|
||||||
|
case 's':
|
||||||
|
io.WriteString(s, w.Error())
|
||||||
|
case 'q':
|
||||||
|
fmt.Fprintf(s, "%q", w.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrap returns an error annotating err with a stack trace
|
||||||
|
// at the point Wrap is called, and the supplied message.
|
||||||
|
// If err is nil, Wrap returns nil.
|
||||||
|
func Wrap(err error, message string) error {
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
err = &withMessage{
|
||||||
|
cause: err,
|
||||||
|
msg: message,
|
||||||
|
}
|
||||||
|
return &withStack{
|
||||||
|
err,
|
||||||
|
callers(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrapf returns an error annotating err with a stack trace
|
||||||
|
// at the point Wrapf is call, and the format specifier.
|
||||||
|
// If err is nil, Wrapf returns nil.
|
||||||
|
func Wrapf(err error, format string, args ...interface{}) error {
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
err = &withMessage{
|
||||||
|
cause: err,
|
||||||
|
msg: fmt.Sprintf(format, args...),
|
||||||
|
}
|
||||||
|
return &withStack{
|
||||||
|
err,
|
||||||
|
callers(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithMessage annotates err with a new message.
|
||||||
|
// If err is nil, WithMessage returns nil.
|
||||||
|
func WithMessage(err error, message string) error {
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &withMessage{
|
||||||
|
cause: err,
|
||||||
|
msg: message,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type withMessage struct {
|
||||||
|
cause error
|
||||||
|
msg string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *withMessage) Error() string { return w.msg + ": " + w.cause.Error() }
|
||||||
|
func (w *withMessage) Cause() error { return w.cause }
|
||||||
|
|
||||||
|
func (w *withMessage) Format(s fmt.State, verb rune) {
|
||||||
|
switch verb {
|
||||||
|
case 'v':
|
||||||
|
if s.Flag('+') {
|
||||||
|
fmt.Fprintf(s, "%+v\n", w.Cause())
|
||||||
|
io.WriteString(s, w.msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fallthrough
|
||||||
|
case 's', 'q':
|
||||||
|
io.WriteString(s, w.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cause returns the underlying cause of the error, if possible.
|
||||||
|
// An error value has a cause if it implements the following
|
||||||
|
// interface:
|
||||||
|
//
|
||||||
|
// type causer interface {
|
||||||
|
// Cause() error
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// If the error does not implement Cause, the original error will
|
||||||
|
// be returned. If the error is nil, nil will be returned without further
|
||||||
|
// investigation.
|
||||||
|
func Cause(err error) error {
|
||||||
|
type causer interface {
|
||||||
|
Cause() error
|
||||||
|
}
|
||||||
|
|
||||||
|
for err != nil {
|
||||||
|
cause, ok := err.(causer)
|
||||||
|
if !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
err = cause.Cause()
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
|
@ -0,0 +1,178 @@
|
||||||
|
package errors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"path"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Frame represents a program counter inside a stack frame.
|
||||||
|
type Frame uintptr
|
||||||
|
|
||||||
|
// pc returns the program counter for this frame;
|
||||||
|
// multiple frames may have the same PC value.
|
||||||
|
func (f Frame) pc() uintptr { return uintptr(f) - 1 }
|
||||||
|
|
||||||
|
// file returns the full path to the file that contains the
|
||||||
|
// function for this Frame's pc.
|
||||||
|
func (f Frame) file() string {
|
||||||
|
fn := runtime.FuncForPC(f.pc())
|
||||||
|
if fn == nil {
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
file, _ := fn.FileLine(f.pc())
|
||||||
|
return file
|
||||||
|
}
|
||||||
|
|
||||||
|
// line returns the line number of source code of the
|
||||||
|
// function for this Frame's pc.
|
||||||
|
func (f Frame) line() int {
|
||||||
|
fn := runtime.FuncForPC(f.pc())
|
||||||
|
if fn == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
_, line := fn.FileLine(f.pc())
|
||||||
|
return line
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format formats the frame according to the fmt.Formatter interface.
|
||||||
|
//
|
||||||
|
// %s source file
|
||||||
|
// %d source line
|
||||||
|
// %n function name
|
||||||
|
// %v equivalent to %s:%d
|
||||||
|
//
|
||||||
|
// Format accepts flags that alter the printing of some verbs, as follows:
|
||||||
|
//
|
||||||
|
// %+s path of source file relative to the compile time GOPATH
|
||||||
|
// %+v equivalent to %+s:%d
|
||||||
|
func (f Frame) Format(s fmt.State, verb rune) {
|
||||||
|
switch verb {
|
||||||
|
case 's':
|
||||||
|
switch {
|
||||||
|
case s.Flag('+'):
|
||||||
|
pc := f.pc()
|
||||||
|
fn := runtime.FuncForPC(pc)
|
||||||
|
if fn == nil {
|
||||||
|
io.WriteString(s, "unknown")
|
||||||
|
} else {
|
||||||
|
file, _ := fn.FileLine(pc)
|
||||||
|
fmt.Fprintf(s, "%s\n\t%s", fn.Name(), file)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
io.WriteString(s, path.Base(f.file()))
|
||||||
|
}
|
||||||
|
case 'd':
|
||||||
|
fmt.Fprintf(s, "%d", f.line())
|
||||||
|
case 'n':
|
||||||
|
name := runtime.FuncForPC(f.pc()).Name()
|
||||||
|
io.WriteString(s, funcname(name))
|
||||||
|
case 'v':
|
||||||
|
f.Format(s, 's')
|
||||||
|
io.WriteString(s, ":")
|
||||||
|
f.Format(s, 'd')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// StackTrace is stack of Frames from innermost (newest) to outermost (oldest).
|
||||||
|
type StackTrace []Frame
|
||||||
|
|
||||||
|
func (st StackTrace) Format(s fmt.State, verb rune) {
|
||||||
|
switch verb {
|
||||||
|
case 'v':
|
||||||
|
switch {
|
||||||
|
case s.Flag('+'):
|
||||||
|
for _, f := range st {
|
||||||
|
fmt.Fprintf(s, "\n%+v", f)
|
||||||
|
}
|
||||||
|
case s.Flag('#'):
|
||||||
|
fmt.Fprintf(s, "%#v", []Frame(st))
|
||||||
|
default:
|
||||||
|
fmt.Fprintf(s, "%v", []Frame(st))
|
||||||
|
}
|
||||||
|
case 's':
|
||||||
|
fmt.Fprintf(s, "%s", []Frame(st))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// stack represents a stack of program counters.
|
||||||
|
type stack []uintptr
|
||||||
|
|
||||||
|
func (s *stack) Format(st fmt.State, verb rune) {
|
||||||
|
switch verb {
|
||||||
|
case 'v':
|
||||||
|
switch {
|
||||||
|
case st.Flag('+'):
|
||||||
|
for _, pc := range *s {
|
||||||
|
f := Frame(pc)
|
||||||
|
fmt.Fprintf(st, "\n%+v", f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stack) StackTrace() StackTrace {
|
||||||
|
f := make([]Frame, len(*s))
|
||||||
|
for i := 0; i < len(f); i++ {
|
||||||
|
f[i] = Frame((*s)[i])
|
||||||
|
}
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
func callers() *stack {
|
||||||
|
const depth = 32
|
||||||
|
var pcs [depth]uintptr
|
||||||
|
n := runtime.Callers(3, pcs[:])
|
||||||
|
var st stack = pcs[0:n]
|
||||||
|
return &st
|
||||||
|
}
|
||||||
|
|
||||||
|
// funcname removes the path prefix component of a function's name reported by func.Name().
|
||||||
|
func funcname(name string) string {
|
||||||
|
i := strings.LastIndex(name, "/")
|
||||||
|
name = name[i+1:]
|
||||||
|
i = strings.Index(name, ".")
|
||||||
|
return name[i+1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
func trimGOPATH(name, file string) string {
|
||||||
|
// Here we want to get the source file path relative to the compile time
|
||||||
|
// GOPATH. As of Go 1.6.x there is no direct way to know the compiled
|
||||||
|
// GOPATH at runtime, but we can infer the number of path segments in the
|
||||||
|
// GOPATH. We note that fn.Name() returns the function name qualified by
|
||||||
|
// the import path, which does not include the GOPATH. Thus we can trim
|
||||||
|
// segments from the beginning of the file path until the number of path
|
||||||
|
// separators remaining is one more than the number of path separators in
|
||||||
|
// the function name. For example, given:
|
||||||
|
//
|
||||||
|
// GOPATH /home/user
|
||||||
|
// file /home/user/src/pkg/sub/file.go
|
||||||
|
// fn.Name() pkg/sub.Type.Method
|
||||||
|
//
|
||||||
|
// We want to produce:
|
||||||
|
//
|
||||||
|
// pkg/sub/file.go
|
||||||
|
//
|
||||||
|
// From this we can easily see that fn.Name() has one less path separator
|
||||||
|
// than our desired output. We count separators from the end of the file
|
||||||
|
// path until it finds two more than in the function name and then move
|
||||||
|
// one character forward to preserve the initial path segment without a
|
||||||
|
// leading separator.
|
||||||
|
const sep = "/"
|
||||||
|
goal := strings.Count(name, sep) + 2
|
||||||
|
i := len(file)
|
||||||
|
for n := 0; n < goal; n++ {
|
||||||
|
i = strings.LastIndex(file[:i], sep)
|
||||||
|
if i == -1 {
|
||||||
|
// not enough separators found, set i so that the slice expression
|
||||||
|
// below leaves file unmodified
|
||||||
|
i = -len(sep)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// get back to 0 or trim the leading separator
|
||||||
|
file = file[i+len(sep):]
|
||||||
|
return file
|
||||||
|
}
|
|
@ -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,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,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,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,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,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,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,21 @@
|
||||||
|
language: go
|
||||||
|
sudo: false
|
||||||
|
go:
|
||||||
|
- "1.9"
|
||||||
|
- "1.10"
|
||||||
|
go_import_path: go.uber.org/zap
|
||||||
|
env:
|
||||||
|
global:
|
||||||
|
- 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,286 @@
|
||||||
|
# Changelog
|
||||||
|
|
||||||
|
## v1.8.0 (13 Apr 2018)
|
||||||
|
|
||||||
|
Enhancements:
|
||||||
|
* [#508][]: Make log level configurable when redirecting the standard
|
||||||
|
library's logger.
|
||||||
|
* [#518][]: Add a logger that writes to a `*testing.TB`.
|
||||||
|
* [#577][]: Add a top-level alias for `zapcore.Field` to clean up GoDoc.
|
||||||
|
|
||||||
|
Bugfixes:
|
||||||
|
* [#574][]: Add a missing import comment to `go.uber.org/zap/buffer`.
|
||||||
|
|
||||||
|
Thanks to @DiSiqueira and @djui for their contributions to this release.
|
||||||
|
|
||||||
|
## 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
|
||||||
|
[#504]: https://github.com/uber-go/zap/pull/504
|
||||||
|
[#508]: https://github.com/uber-go/zap/pull/508
|
||||||
|
[#518]: https://github.com/uber-go/zap/pull/518
|
||||||
|
[#577]: https://github.com/uber-go/zap/pull/577
|
||||||
|
[#574]: https://github.com/uber-go/zap/pull/574
|
|
@ -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,154 @@
|
||||||
|
# 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)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Extensions
|
||||||
|
|
||||||
|
We'd love to support every logging need within zap itself, but we're only
|
||||||
|
familiar with a handful of log ingestion systems, flag-parsing packages, and
|
||||||
|
the like. Rather than merging code that we can't effectively debug and
|
||||||
|
support, we'd rather grow an ecosystem of zap extensions.
|
||||||
|
|
||||||
|
We're aware of the following extensions, but haven't used them ourselves:
|
||||||
|
|
||||||
|
| Package | Integration |
|
||||||
|
| --- | --- |
|
||||||
|
| `github.com/tchap/zapext` | Sentry, syslog |
|
||||||
|
| `github.com/fgrosse/zaptest` | Ginkgo |
|
||||||
|
|
||||||
|
[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 internal/ztest
|
||||||
|
|
||||||
|
# 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 := 10
|
||||||
|
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) Field {
|
||||||
|
return Field{Key: key, Type: zapcore.ArrayMarshalerType, Interface: val}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bools constructs a field that carries a slice of bools.
|
||||||
|
func Bools(key string, bs []bool) 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) Field {
|
||||||
|
return Array(key, byteStringsArray(bss))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Complex128s constructs a field that carries a slice of complex numbers.
|
||||||
|
func Complex128s(key string, nums []complex128) Field {
|
||||||
|
return Array(key, complex128s(nums))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Complex64s constructs a field that carries a slice of complex numbers.
|
||||||
|
func Complex64s(key string, nums []complex64) Field {
|
||||||
|
return Array(key, complex64s(nums))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Durations constructs a field that carries a slice of time.Durations.
|
||||||
|
func Durations(key string, ds []time.Duration) Field {
|
||||||
|
return Array(key, durations(ds))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float64s constructs a field that carries a slice of floats.
|
||||||
|
func Float64s(key string, nums []float64) Field {
|
||||||
|
return Array(key, float64s(nums))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float32s constructs a field that carries a slice of floats.
|
||||||
|
func Float32s(key string, nums []float32) Field {
|
||||||
|
return Array(key, float32s(nums))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ints constructs a field that carries a slice of integers.
|
||||||
|
func Ints(key string, nums []int) Field {
|
||||||
|
return Array(key, ints(nums))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int64s constructs a field that carries a slice of integers.
|
||||||
|
func Int64s(key string, nums []int64) Field {
|
||||||
|
return Array(key, int64s(nums))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int32s constructs a field that carries a slice of integers.
|
||||||
|
func Int32s(key string, nums []int32) Field {
|
||||||
|
return Array(key, int32s(nums))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int16s constructs a field that carries a slice of integers.
|
||||||
|
func Int16s(key string, nums []int16) Field {
|
||||||
|
return Array(key, int16s(nums))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int8s constructs a field that carries a slice of integers.
|
||||||
|
func Int8s(key string, nums []int8) Field {
|
||||||
|
return Array(key, int8s(nums))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strings constructs a field that carries a slice of strings.
|
||||||
|
func Strings(key string, ss []string) Field {
|
||||||
|
return Array(key, stringArray(ss))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Times constructs a field that carries a slice of time.Times.
|
||||||
|
func Times(key string, ts []time.Time) Field {
|
||||||
|
return Array(key, times(ts))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uints constructs a field that carries a slice of unsigned integers.
|
||||||
|
func Uints(key string, nums []uint) Field {
|
||||||
|
return Array(key, uints(nums))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint64s constructs a field that carries a slice of unsigned integers.
|
||||||
|
func Uint64s(key string, nums []uint64) Field {
|
||||||
|
return Array(key, uint64s(nums))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint32s constructs a field that carries a slice of unsigned integers.
|
||||||
|
func Uint32s(key string, nums []uint32) Field {
|
||||||
|
return Array(key, uint32s(nums))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint16s constructs a field that carries a slice of unsigned integers.
|
||||||
|
func Uint16s(key string, nums []uint16) Field {
|
||||||
|
return Array(key, uint16s(nums))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint8s constructs a field that carries a slice of unsigned integers.
|
||||||
|
func Uint8s(key string, nums []uint8) Field {
|
||||||
|
return Array(key, uint8s(nums))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uintptrs constructs a field that carries a slice of pointer addresses.
|
||||||
|
func Uintptrs(key string, us []uintptr) Field {
|
||||||
|
return Array(key, uintptrs(us))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Errors constructs a field that carries a slice of errors.
|
||||||
|
func Errors(key string, errs []error) 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,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 "go.uber.org/zap/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,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,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,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([]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,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,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) 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) Field {
|
||||||
|
if err == nil {
|
||||||
|
return Skip()
|
||||||
|
}
|
||||||
|
return 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,310 @@
|
||||||
|
// 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"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Field is an alias for Field. Aliasing this type dramatically
|
||||||
|
// improves the navigability of this package's API documentation.
|
||||||
|
type Field = zapcore.Field
|
||||||
|
|
||||||
|
// Skip constructs a no-op field, which is often useful when handling invalid
|
||||||
|
// inputs in other Field constructors.
|
||||||
|
func Skip() Field {
|
||||||
|
return 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) Field {
|
||||||
|
return Field{Key: key, Type: zapcore.BinaryType, Interface: val}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bool constructs a field that carries a bool.
|
||||||
|
func Bool(key string, val bool) Field {
|
||||||
|
var ival int64
|
||||||
|
if val {
|
||||||
|
ival = 1
|
||||||
|
}
|
||||||
|
return 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) Field {
|
||||||
|
return 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) Field {
|
||||||
|
return 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) Field {
|
||||||
|
return 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) Field {
|
||||||
|
return 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) Field {
|
||||||
|
return 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) Field {
|
||||||
|
return Int64(key, int64(val))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int64 constructs a field with the given key and value.
|
||||||
|
func Int64(key string, val int64) Field {
|
||||||
|
return Field{Key: key, Type: zapcore.Int64Type, Integer: val}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int32 constructs a field with the given key and value.
|
||||||
|
func Int32(key string, val int32) Field {
|
||||||
|
return 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) Field {
|
||||||
|
return 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) Field {
|
||||||
|
return 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) Field {
|
||||||
|
return Field{Key: key, Type: zapcore.StringType, String: val}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint constructs a field with the given key and value.
|
||||||
|
func Uint(key string, val uint) Field {
|
||||||
|
return Uint64(key, uint64(val))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint64 constructs a field with the given key and value.
|
||||||
|
func Uint64(key string, val uint64) Field {
|
||||||
|
return 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) Field {
|
||||||
|
return 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) Field {
|
||||||
|
return 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) Field {
|
||||||
|
return 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) Field {
|
||||||
|
return 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{}) Field {
|
||||||
|
return 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) Field {
|
||||||
|
return 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) Field {
|
||||||
|
return Field{Key: key, Type: zapcore.StringerType, Interface: val}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Time constructs a Field with the given key and value. The encoder
|
||||||
|
// controls how the time is serialized.
|
||||||
|
func Time(key string, val time.Time) Field {
|
||||||
|
return 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) 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) Field {
|
||||||
|
return 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) Field {
|
||||||
|
return 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 surprises, []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{}) 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,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,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,169 @@
|
||||||
|
// 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
|
||||||
|
_programmerErrorTemplate = "You've found a bug in zap! Please file a bug at " +
|
||||||
|
"https://github.com/uber-go/zap/issues/new and reference this error: %v"
|
||||||
|
)
|
||||||
|
|
||||||
|
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))
|
||||||
|
logFunc, err := levelToFunc(logger, level)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
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.Stderr.
|
||||||
|
func RedirectStdLog(l *Logger) func() {
|
||||||
|
f, err := redirectStdLogAt(l, InfoLevel)
|
||||||
|
if err != nil {
|
||||||
|
// Can't get here, since passing InfoLevel to redirectStdLogAt always
|
||||||
|
// works.
|
||||||
|
panic(fmt.Sprintf(_programmerErrorTemplate, err))
|
||||||
|
}
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
// RedirectStdLogAt redirects output from the standard library's package-global
|
||||||
|
// logger to the supplied logger at the specified level. 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.Stderr.
|
||||||
|
func RedirectStdLogAt(l *Logger, level zapcore.Level) (func(), error) {
|
||||||
|
return redirectStdLogAt(l, level)
|
||||||
|
}
|
||||||
|
|
||||||
|
func redirectStdLogAt(l *Logger, level zapcore.Level) (func(), error) {
|
||||||
|
flags := log.Flags()
|
||||||
|
prefix := log.Prefix()
|
||||||
|
log.SetFlags(0)
|
||||||
|
log.SetPrefix("")
|
||||||
|
logger := l.WithOptions(AddCallerSkip(_stdLogDefaultDepth + _loggerWriterDepth))
|
||||||
|
logFunc, err := levelToFunc(logger, level)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
log.SetOutput(&loggerWriter{logFunc})
|
||||||
|
return func() {
|
||||||
|
log.SetFlags(flags)
|
||||||
|
log.SetPrefix(prefix)
|
||||||
|
log.SetOutput(os.Stderr)
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func levelToFunc(logger *Logger, lvl zapcore.Level) (func(string, ...Field), error) {
|
||||||
|
switch lvl {
|
||||||
|
case DebugLevel:
|
||||||
|
return logger.Debug, nil
|
||||||
|
case InfoLevel:
|
||||||
|
return logger.Info, nil
|
||||||
|
case WarnLevel:
|
||||||
|
return logger.Warn, nil
|
||||||
|
case ErrorLevel:
|
||||||
|
return logger.Error, nil
|
||||||
|
case DPanicLevel:
|
||||||
|
return logger.DPanic, nil
|
||||||
|
case PanicLevel:
|
||||||
|
return logger.Panic, nil
|
||||||
|
case FatalLevel:
|
||||||
|
return logger.Fatal, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("unrecognized level: %q", lvl)
|
||||||
|
}
|
||||||
|
|
||||||
|
type loggerWriter struct {
|
||||||
|
logFunc func(msg string, fields ...Field)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *loggerWriter) Write(p []byte) (int, error) {
|
||||||
|
p = bytes.TrimSpace(p)
|
||||||
|
l.logFunc(string(p))
|
||||||
|
return len(p), nil
|
||||||
|
}
|
|
@ -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,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,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,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 convenience 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,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 ...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 ...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 ...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 ...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 ...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 ...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 ...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 ...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,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 ...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,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,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 zap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
|
|
||||||
|
"go.uber.org/multierr"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
_oddNumberErrMsg = "Ignored key without a value."
|
||||||
|
_nonStringKeyErrMsg = "Ignored key-value pairs with non-string keys."
|
||||||
|
)
|
||||||
|
|
||||||
|
// A SugaredLogger wraps the base Logger functionality in a slower, but less
|
||||||
|
// verbose, API. Any Logger can be converted to a SugaredLogger with its Sugar
|
||||||
|
// method.
|
||||||
|
//
|
||||||
|
// Unlike the Logger, the SugaredLogger doesn't insist on structured logging.
|
||||||
|
// For each log level, it exposes three methods: one for loosely-typed
|
||||||
|
// structured logging, one for println-style formatting, and one for
|
||||||
|
// printf-style formatting. For example, SugaredLoggers can produce InfoLevel
|
||||||
|
// output with Infow ("info with" structured context), Info, or Infof.
|
||||||
|
type SugaredLogger struct {
|
||||||
|
base *Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// Desugar unwraps a SugaredLogger, exposing the original Logger. Desugaring
|
||||||
|
// 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 (s *SugaredLogger) Desugar() *Logger {
|
||||||
|
base := s.base.clone()
|
||||||
|
base.callerSkip -= 2
|
||||||
|
return base
|
||||||
|
}
|
||||||
|
|
||||||
|
// Named adds a sub-scope to the logger's name. See Logger.Named for details.
|
||||||
|
func (s *SugaredLogger) Named(name string) *SugaredLogger {
|
||||||
|
return &SugaredLogger{base: s.base.Named(name)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// With adds a variadic number of fields to the logging context. It accepts a
|
||||||
|
// mix of strongly-typed Field objects and loosely-typed key-value pairs. When
|
||||||
|
// processing pairs, the first element of the pair is used as the field key
|
||||||
|
// and the second as the field value.
|
||||||
|
//
|
||||||
|
// For example,
|
||||||
|
// sugaredLogger.With(
|
||||||
|
// "hello", "world",
|
||||||
|
// "failure", errors.New("oh no"),
|
||||||
|
// Stack(),
|
||||||
|
// "count", 42,
|
||||||
|
// "user", User{Name: "alice"},
|
||||||
|
// )
|
||||||
|
// is the equivalent of
|
||||||
|
// unsugared.With(
|
||||||
|
// String("hello", "world"),
|
||||||
|
// String("failure", "oh no"),
|
||||||
|
// Stack(),
|
||||||
|
// Int("count", 42),
|
||||||
|
// Object("user", User{Name: "alice"}),
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// Note that the keys in key-value pairs should be strings. In development,
|
||||||
|
// passing a non-string key panics. In production, the logger is more
|
||||||
|
// forgiving: a separate error is logged, but the key-value pair is skipped
|
||||||
|
// and execution continues. Passing an orphaned key triggers similar behavior:
|
||||||
|
// panics in development and errors in production.
|
||||||
|
func (s *SugaredLogger) With(args ...interface{}) *SugaredLogger {
|
||||||
|
return &SugaredLogger{base: s.base.With(s.sweetenFields(args)...)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug uses fmt.Sprint to construct and log a message.
|
||||||
|
func (s *SugaredLogger) Debug(args ...interface{}) {
|
||||||
|
s.log(DebugLevel, "", args, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Info uses fmt.Sprint to construct and log a message.
|
||||||
|
func (s *SugaredLogger) Info(args ...interface{}) {
|
||||||
|
s.log(InfoLevel, "", args, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warn uses fmt.Sprint to construct and log a message.
|
||||||
|
func (s *SugaredLogger) Warn(args ...interface{}) {
|
||||||
|
s.log(WarnLevel, "", args, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error uses fmt.Sprint to construct and log a message.
|
||||||
|
func (s *SugaredLogger) Error(args ...interface{}) {
|
||||||
|
s.log(ErrorLevel, "", args, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DPanic uses fmt.Sprint to construct and log a message. In development, the
|
||||||
|
// logger then panics. (See DPanicLevel for details.)
|
||||||
|
func (s *SugaredLogger) DPanic(args ...interface{}) {
|
||||||
|
s.log(DPanicLevel, "", args, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Panic uses fmt.Sprint to construct and log a message, then panics.
|
||||||
|
func (s *SugaredLogger) Panic(args ...interface{}) {
|
||||||
|
s.log(PanicLevel, "", args, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fatal uses fmt.Sprint to construct and log a message, then calls os.Exit.
|
||||||
|
func (s *SugaredLogger) Fatal(args ...interface{}) {
|
||||||
|
s.log(FatalLevel, "", args, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debugf uses fmt.Sprintf to log a templated message.
|
||||||
|
func (s *SugaredLogger) Debugf(template string, args ...interface{}) {
|
||||||
|
s.log(DebugLevel, template, args, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Infof uses fmt.Sprintf to log a templated message.
|
||||||
|
func (s *SugaredLogger) Infof(template string, args ...interface{}) {
|
||||||
|
s.log(InfoLevel, template, args, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warnf uses fmt.Sprintf to log a templated message.
|
||||||
|
func (s *SugaredLogger) Warnf(template string, args ...interface{}) {
|
||||||
|
s.log(WarnLevel, template, args, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Errorf uses fmt.Sprintf to log a templated message.
|
||||||
|
func (s *SugaredLogger) Errorf(template string, args ...interface{}) {
|
||||||
|
s.log(ErrorLevel, template, args, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DPanicf uses fmt.Sprintf to log a templated message. In development, the
|
||||||
|
// logger then panics. (See DPanicLevel for details.)
|
||||||
|
func (s *SugaredLogger) DPanicf(template string, args ...interface{}) {
|
||||||
|
s.log(DPanicLevel, template, args, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Panicf uses fmt.Sprintf to log a templated message, then panics.
|
||||||
|
func (s *SugaredLogger) Panicf(template string, args ...interface{}) {
|
||||||
|
s.log(PanicLevel, template, args, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fatalf uses fmt.Sprintf to log a templated message, then calls os.Exit.
|
||||||
|
func (s *SugaredLogger) Fatalf(template string, args ...interface{}) {
|
||||||
|
s.log(FatalLevel, template, args, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debugw logs a message with some additional context. The variadic key-value
|
||||||
|
// pairs are treated as they are in With.
|
||||||
|
//
|
||||||
|
// When debug-level logging is disabled, this is much faster than
|
||||||
|
// s.With(keysAndValues).Debug(msg)
|
||||||
|
func (s *SugaredLogger) Debugw(msg string, keysAndValues ...interface{}) {
|
||||||
|
s.log(DebugLevel, msg, nil, keysAndValues)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Infow logs a message with some additional context. The variadic key-value
|
||||||
|
// pairs are treated as they are in With.
|
||||||
|
func (s *SugaredLogger) Infow(msg string, keysAndValues ...interface{}) {
|
||||||
|
s.log(InfoLevel, msg, nil, keysAndValues)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warnw logs a message with some additional context. The variadic key-value
|
||||||
|
// pairs are treated as they are in With.
|
||||||
|
func (s *SugaredLogger) Warnw(msg string, keysAndValues ...interface{}) {
|
||||||
|
s.log(WarnLevel, msg, nil, keysAndValues)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Errorw logs a message with some additional context. The variadic key-value
|
||||||
|
// pairs are treated as they are in With.
|
||||||
|
func (s *SugaredLogger) Errorw(msg string, keysAndValues ...interface{}) {
|
||||||
|
s.log(ErrorLevel, msg, nil, keysAndValues)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DPanicw logs a message with some additional context. In development, the
|
||||||
|
// logger then panics. (See DPanicLevel for details.) The variadic key-value
|
||||||
|
// pairs are treated as they are in With.
|
||||||
|
func (s *SugaredLogger) DPanicw(msg string, keysAndValues ...interface{}) {
|
||||||
|
s.log(DPanicLevel, msg, nil, keysAndValues)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Panicw logs a message with some additional context, then panics. The
|
||||||
|
// variadic key-value pairs are treated as they are in With.
|
||||||
|
func (s *SugaredLogger) Panicw(msg string, keysAndValues ...interface{}) {
|
||||||
|
s.log(PanicLevel, msg, nil, keysAndValues)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fatalw logs a message with some additional context, then calls os.Exit. The
|
||||||
|
// variadic key-value pairs are treated as they are in With.
|
||||||
|
func (s *SugaredLogger) Fatalw(msg string, keysAndValues ...interface{}) {
|
||||||
|
s.log(FatalLevel, msg, nil, keysAndValues)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sync flushes any buffered log entries.
|
||||||
|
func (s *SugaredLogger) Sync() error {
|
||||||
|
return s.base.Sync()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SugaredLogger) log(lvl zapcore.Level, template string, fmtArgs []interface{}, context []interface{}) {
|
||||||
|
// If logging at this level is completely disabled, skip the overhead of
|
||||||
|
// string formatting.
|
||||||
|
if lvl < DPanicLevel && !s.base.Core().Enabled(lvl) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format with Sprint, Sprintf, or neither.
|
||||||
|
msg := template
|
||||||
|
if msg == "" && len(fmtArgs) > 0 {
|
||||||
|
msg = fmt.Sprint(fmtArgs...)
|
||||||
|
} else if msg != "" && len(fmtArgs) > 0 {
|
||||||
|
msg = fmt.Sprintf(template, fmtArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ce := s.base.Check(lvl, msg); ce != nil {
|
||||||
|
ce.Write(s.sweetenFields(context)...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SugaredLogger) sweetenFields(args []interface{}) []Field {
|
||||||
|
if len(args) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate enough space for the worst case; if users pass only structured
|
||||||
|
// fields, we shouldn't penalize them with extra allocations.
|
||||||
|
fields := make([]Field, 0, len(args))
|
||||||
|
var invalid invalidPairs
|
||||||
|
|
||||||
|
for i := 0; i < len(args); {
|
||||||
|
// This is a strongly-typed field. Consume it and move on.
|
||||||
|
if f, ok := args[i].(Field); ok {
|
||||||
|
fields = append(fields, f)
|
||||||
|
i++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure this element isn't a dangling key.
|
||||||
|
if i == len(args)-1 {
|
||||||
|
s.base.DPanic(_oddNumberErrMsg, Any("ignored", args[i]))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Consume this value and the next, treating them as a key-value pair. If the
|
||||||
|
// key isn't a string, add this pair to the slice of invalid pairs.
|
||||||
|
key, val := args[i], args[i+1]
|
||||||
|
if keyStr, ok := key.(string); !ok {
|
||||||
|
// Subsequent errors are likely, so allocate once up front.
|
||||||
|
if cap(invalid) == 0 {
|
||||||
|
invalid = make(invalidPairs, 0, len(args)/2)
|
||||||
|
}
|
||||||
|
invalid = append(invalid, invalidPair{i, key, val})
|
||||||
|
} else {
|
||||||
|
fields = append(fields, Any(keyStr, val))
|
||||||
|
}
|
||||||
|
i += 2
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we encountered any invalid key-value pairs, log an error.
|
||||||
|
if len(invalid) > 0 {
|
||||||
|
s.base.DPanic(_nonStringKeyErrMsg, Array("invalid", invalid))
|
||||||
|
}
|
||||||
|
return fields
|
||||||
|
}
|
||||||
|
|
||||||
|
type invalidPair struct {
|
||||||
|
position int
|
||||||
|
key, value interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p invalidPair) MarshalLogObject(enc zapcore.ObjectEncoder) error {
|
||||||
|
enc.AddInt64("position", int64(p.position))
|
||||||
|
Any("key", p.key).AddTo(enc)
|
||||||
|
Any("value", p.value).AddTo(enc)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type invalidPairs []invalidPair
|
||||||
|
|
||||||
|
func (ps invalidPairs) MarshalLogArray(enc zapcore.ArrayEncoder) error {
|
||||||
|
var err error
|
||||||
|
for i := range ps {
|
||||||
|
err = multierr.Append(err, enc.AppendObject(ps[i]))
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue