Commit 7f7e59c0 authored by Alexandre Viau's avatar Alexandre Viau

New upstream version 2.13

parents
Aaron L <aaron@bettercoder.net>
Alan Shreve <alan@inconshreveable.com>
Andy Walker <walkeraj@gmail.com>
Andy Watson <andrewmoorewatson@gmail.com>
Chris Hines <github@cs-guy.com> Chris Hines <chines@comscore.com>
Ciaran Downey <me@ciarand.me>
Dmitry Chestnykh <dmitry@codingrobots.com>
Evan Shaw <edsrzf@gmail.com>
Gonzalo Serrano <boikot@gmail.com>
Jeremy <jrbudnack@starkandwayne.com>
Jonathan Rudenberg <jonathan@titanous.com>
Kevin Burke <kev@inburke.com>
Marc Abramowitz <marc@marc-abramowitz.com>
Nathan Baulch <nathan.baulch@gmail.com>
NotZippy <notzippy@gmail.com>
Péter Szilágyi <peterke@gmail.com>
Robert Egorov <robert.egorov@gmail.com>
Robert Starbuck <robstarbuck@gmail.com>
Robert Zaremba <robert.zaremba@scale-it.pl>
Spencer Nelson <s@spenczar.com>
Tomasz Grodzki <tg@users.noreply.github.com>
Trevor Gattis <github@trevorgattis.com>
Vincent Vanackere <vincent.vanackere@gmail.com>
Will McGovern <will@brkt.com>
Carl Veazey <Carl_Veazey@cable.comcast.com>
Kang Seong-Min <kang.seongmin@gmail.com>
language: go
go_import_path: github.com/inconshreveable/log15
sudo: false
go:
- 1.3
- 1.4
- 1.5
- 1.6
- 1.7
- 1.8
- 1.9
- tip
Aaron L <aaron@bettercoder.net>
Alan Shreve <alan@inconshreveable.com>
Andy Walker <walkeraj@gmail.com>
Andy Watson <andrewmoorewatson@gmail.com>
Carl Veazey <Carl_Veazey@cable.comcast.com>
Chris Hines <github@cs-guy.com>
Christoph Hack <christoph@tux21b.org>
Ciaran Downey <me@ciarand.me>
Dmitry Chestnykh <dmitry@codingrobots.com>
Evan Shaw <edsrzf@gmail.com>
Gonzalo Serrano <boikot@gmail.com>
Jeremy <jrbudnack@starkandwayne.com>
Jonathan Rudenberg <jonathan@titanous.com>
Kang Seong-Min <kang.seongmin@gmail.com>
Kevin Burke <kev@inburke.com>
Marc Abramowitz <marc@marc-abramowitz.com>
Nathan Baulch <nathan.baulch@gmail.com>
NotZippy <notzippy@gmail.com>
Péter Szilágyi <peterke@gmail.com>
Robert Egorov <robert.egorov@gmail.com>
Robert Starbuck <robstarbuck@gmail.com>
Robert Zaremba <robert.zaremba@scale-it.pl>
Sean Chittenden <sean@chittenden.org>
Spencer Nelson <s@spenczar.com>
Tomasz Grodzki <tg@users.noreply.github.com>
Trevor Gattis <github@trevorgattis.com>
Vincent Vanackere <vincent.vanackere@gmail.com>
Will McGovern <will@brkt.com>
Copyright 2014 Alan Shreve
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
![obligatory xkcd](http://imgs.xkcd.com/comics/standards.png)
# log15 [![godoc reference](https://godoc.org/github.com/inconshreveable/log15?status.png)](https://godoc.org/github.com/inconshreveable/log15) [![Build Status](https://travis-ci.org/inconshreveable/log15.svg?branch=master)](https://travis-ci.org/inconshreveable/log15)
Package log15 provides an opinionated, simple toolkit for best-practice logging in Go (golang) that is both human and machine readable. It is modeled after the Go standard library's [`io`](http://golang.org/pkg/io/) and [`net/http`](http://golang.org/pkg/net/http/) packages and is an alternative to the standard library's [`log`](http://golang.org/pkg/log/) package.
## Features
- A simple, easy-to-understand API
- Promotes structured logging by encouraging use of key/value pairs
- Child loggers which inherit and add their own private context
- Lazy evaluation of expensive operations
- Simple Handler interface allowing for construction of flexible, custom logging configurations with a tiny API.
- Color terminal support
- Built-in support for logging to files, streams, syslog, and the network
- Support for forking records to multiple handlers, buffering records for output, failing over from failed handler writes, + more
## Versioning
The API of the master branch of log15 should always be considered unstable. If you want to rely on a stable API,
you must vendor the library.
## Importing
```go
import log "github.com/inconshreveable/log15"
```
## Examples
```go
// all loggers can have key/value context
srvlog := log.New("module", "app/server")
// all log messages can have key/value context
srvlog.Warn("abnormal conn rate", "rate", curRate, "low", lowRate, "high", highRate)
// child loggers with inherited context
connlog := srvlog.New("raddr", c.RemoteAddr())
connlog.Info("connection open")
// lazy evaluation
connlog.Debug("ping remote", "latency", log.Lazy{pingRemote})
// flexible configuration
srvlog.SetHandler(log.MultiHandler(
log.StreamHandler(os.Stderr, log.LogfmtFormat()),
log.LvlFilterHandler(
log.LvlError,
log.Must.FileHandler("errors.json", log.JsonFormat()))))
```
Will result in output that looks like this:
```
WARN[06-17|21:58:10] abnormal conn rate module=app/server rate=0.500 low=0.100 high=0.800
INFO[06-17|21:58:10] connection open module=app/server raddr=10.0.0.1
```
## Breaking API Changes
The following commits broke API stability. This reference is intended to help you understand the consequences of updating to a newer version
of log15.
- 57a084d014d4150152b19e4e531399a7145d1540 - Added a `Get()` method to the `Logger` interface to retrieve the current handler
- 93404652ee366648fa622b64d1e2b67d75a3094a - `Record` field `Call` changed to `stack.Call` with switch to `github.com/go-stack/stack`
- a5e7613673c73281f58e15a87d2cf0cf111e8152 - Restored `syslog.Priority` argument to the `SyslogXxx` handler constructors
## FAQ
### The varargs style is brittle and error prone! Can I have type safety please?
Yes. Use `log.Ctx`:
```go
srvlog := log.New(log.Ctx{"module": "app/server"})
srvlog.Warn("abnormal conn rate", log.Ctx{"rate": curRate, "low": lowRate, "high": highRate})
```
### Regenerating the CONTRIBUTORS file
```
go get -u github.com/kevinburke/write_mailmap
write_mailmap > CONTRIBUTORS
```
## License
Apache
// +build go1.3
package log15
import (
"bytes"
"errors"
"io/ioutil"
"testing"
"time"
)
func BenchmarkStreamNoCtx(b *testing.B) {
lg := New()
buf := bytes.Buffer{}
lg.SetHandler(StreamHandler(&buf, LogfmtFormat()))
for i := 0; i < b.N; i++ {
lg.Info("test message")
buf.Reset()
}
}
func BenchmarkDiscard(b *testing.B) {
lg := New()
lg.SetHandler(DiscardHandler())
for i := 0; i < b.N; i++ {
lg.Info("test message")
}
}
func BenchmarkCallerFileHandler(b *testing.B) {
lg := New()
lg.SetHandler(CallerFileHandler(DiscardHandler()))
for i := 0; i < b.N; i++ {
lg.Info("test message")
}
}
func BenchmarkCallerFuncHandler(b *testing.B) {
lg := New()
lg.SetHandler(CallerFuncHandler(DiscardHandler()))
for i := 0; i < b.N; i++ {
lg.Info("test message")
}
}
func BenchmarkLogfmtNoCtx(b *testing.B) {
r := Record{
Time: time.Now(),
Lvl: LvlInfo,
Msg: "test message",
Ctx: []interface{}{},
}
logfmt := LogfmtFormat()
for i := 0; i < b.N; i++ {
logfmt.Format(&r)
}
}
func BenchmarkJsonNoCtx(b *testing.B) {
r := Record{
Time: time.Now(),
Lvl: LvlInfo,
Msg: "test message",
Ctx: []interface{}{},
}
jsonfmt := JsonFormat()
for i := 0; i < b.N; i++ {
jsonfmt.Format(&r)
}
}
func BenchmarkMultiLevelFilter(b *testing.B) {
handler := MultiHandler(
LvlFilterHandler(LvlDebug, DiscardHandler()),
LvlFilterHandler(LvlError, DiscardHandler()),
)
lg := New()
lg.SetHandler(handler)
for i := 0; i < b.N; i++ {
lg.Info("test message")
}
}
func BenchmarkDescendant1(b *testing.B) {
lg := New()
lg.SetHandler(DiscardHandler())
lg = lg.New()
for i := 0; i < b.N; i++ {
lg.Info("test message")
}
}
func BenchmarkDescendant2(b *testing.B) {
lg := New()
lg.SetHandler(DiscardHandler())
for i := 0; i < 2; i++ {
lg = lg.New()
}
for i := 0; i < b.N; i++ {
lg.Info("test message")
}
}
func BenchmarkDescendant4(b *testing.B) {
lg := New()
lg.SetHandler(DiscardHandler())
for i := 0; i < 4; i++ {
lg = lg.New()
}
for i := 0; i < b.N; i++ {
lg.Info("test message")
}
}
func BenchmarkDescendant8(b *testing.B) {
lg := New()
lg.SetHandler(DiscardHandler())
for i := 0; i < 8; i++ {
lg = lg.New()
}
for i := 0; i < b.N; i++ {
lg.Info("test message")
}
}
// Copied from https://github.com/uber-go/zap/blob/master/benchmarks/log15_bench_test.go
// (MIT License)
func newLog15() Logger {
logger := New()
logger.SetHandler(StreamHandler(ioutil.Discard, JsonFormat()))
return logger
}
var errExample = errors.New("fail")
type user struct {
Name string `json:"name"`
Email string `json:"email"`
CreatedAt time.Time `json:"created_at"`
}
var _jane = user{
Name: "Jane Doe",
Email: "jane@test.com",
CreatedAt: time.Date(1980, 1, 1, 12, 0, 0, 0, time.UTC),
}
func BenchmarkLog15AddingFields(b *testing.B) {
logger := newLog15()
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
logger.Info("Go fast.",
"int", 1,
"int64", int64(1),
"float", 3.0,
"string", "four!",
"bool", true,
"time", time.Unix(0, 0),
"error", errExample.Error(),
"duration", time.Second,
"user-defined type", _jane,
"another string", "done!",
)
}
})
}
func BenchmarkLog15WithAccumulatedContext(b *testing.B) {
logger := newLog15().New(
"int", 1,
"int64", int64(1),
"float", 3.0,
"string", "four!",
"bool", true,
"time", time.Unix(0, 0),
"error", errExample.Error(),
"duration", time.Second,
"user-defined type", _jane,
"another string", "done!",
)
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
logger.Info("Go really fast.")
}
})
}
func BenchmarkLog15WithoutFields(b *testing.B) {
logger := newLog15()
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
logger.Info("Go fast.")
}
})
}
This diff is collapsed.
package ext
import (
"errors"
log "github.com/inconshreveable/log15"
"math"
"testing"
)
func testHandler() (log.Handler, *log.Record) {
rec := new(log.Record)
return log.FuncHandler(func(r *log.Record) error {
*rec = *r
return nil
}), rec
}
func TestHotSwapHandler(t *testing.T) {
t.Parallel()
h1, r1 := testHandler()
l := log.New()
h := HotSwapHandler(h1)
l.SetHandler(h)
l.Info("to h1")
if r1.Msg != "to h1" {
t.Fatalf("didn't get expected message to h1")
}
h2, r2 := testHandler()
h.Swap(h2)
l.Info("to h2")
if r2.Msg != "to h2" {
t.Fatalf("didn't get expected message to h2")
}
}
func TestSpeculativeHandler(t *testing.T) {
t.Parallel()
// test with an even multiple of the buffer size, less than full buffer size
// and not a multiple of the buffer size
for _, count := range []int{10000, 50, 432} {
recs := make(chan *log.Record)
done := make(chan int)
spec := SpeculativeHandler(100, log.ChannelHandler(recs))
go func() {
defer close(done)
expectedCount := int(math.Min(float64(count), float64(100)))
expectedIdx := count - expectedCount
for r := range recs {
if r.Ctx[1] != expectedIdx {
t.Errorf("Bad ctx 'i', got %d expected %d", r.Ctx[1], expectedIdx)
return
}
expectedIdx++
expectedCount--
if expectedCount == 0 {
// got everything we expected
break
}
}
select {
case <-recs:
t.Errorf("got an extra record we shouldn't have!")
default:
}
}()
lg := log.New()
lg.SetHandler(spec)
for i := 0; i < count; i++ {
lg.Debug("test speculative", "i", i)
}
go spec.Flush()
// wait for the go routine to finish
<-done
}
}
func TestErrorHandler(t *testing.T) {
t.Parallel()
h, r := testHandler()
lg := log.New()
lg.SetHandler(EscalateErrHandler(
log.LvlFilterHandler(log.LvlError, h)))
lg.Debug("some function result", "err", nil)
if r.Msg != "" {
t.Fatalf("Expected debug level message to be filtered")
}
lg.Debug("some function result", "err", errors.New("failed operation"))
if r.Msg != "some function result" {
t.Fatalf("Expected debug level message to be escalated and pass lvlfilter")
}
if r.Lvl != log.LvlError {
t.Fatalf("Expected debug level message to be escalated to LvlError")
}
}
package ext
import (
"os"
"sync"
"sync/atomic"
"unsafe"
log "github.com/inconshreveable/log15"
)
// EscalateErrHandler wraps another handler and passes all records through
// unchanged except if the logged context contains a non-nil error
// value in its context. In that case, the record's level is raised
// to LvlError unless it was already more serious (LvlCrit).
//
// This allows you to log the result of all functions for debugging
// and still capture error conditions when in production with a single
// log line. As an example, the following the log record will be written
// out only if there was an error writing a value to redis:
//
// logger := logext.EscalateErrHandler(
// log.LvlFilterHandler(log.LvlInfo, log.StdoutHandler))
//
// reply, err := redisConn.Do("SET", "foo", "bar")
// logger.Debug("Wrote value to redis", "reply", reply, "err", err)
// if err != nil {
// return err
// }
//
func EscalateErrHandler(h log.Handler) log.Handler {
return log.FuncHandler(func(r *log.Record) error {
if r.Lvl > log.LvlError {
for i := 1; i < len(r.Ctx); i++ {
if v, ok := r.Ctx[i].(error); ok && v != nil {
r.Lvl = log.LvlError
break
}
}
}
return h.Log(r)
})
}
// SpeculativeHandler is a handler for speculative logging. It
// keeps a ring buffer of the given size full of the last events
// logged into it. When Flush is called, all buffered log records
// are written to the wrapped handler. This is extremely for
// continuosly capturing debug level output, but only flushing those
// log records if an exceptional condition is encountered.
func SpeculativeHandler(size int, h log.Handler) *Speculative {
return &Speculative{
handler: h,
recs: make([]*log.Record, size),
}
}
// Speculative is the Log15.Handler. Read `SpeculativeHandler` for more information.
type Speculative struct {
mu sync.Mutex
idx int
recs []*log.Record
handler log.Handler
full bool
}
// Log implements log15.Handler interface
func (h *Speculative) Log(r *log.Record) error {
h.mu.Lock()
defer h.mu.Unlock()
h.recs[h.idx] = r
h.idx = (h.idx + 1) % len(h.recs)
h.full = h.full || h.idx == 0
return nil
}
// Flush logs all records on the handler.
func (h *Speculative) Flush() {
recs := make([]*log.Record, 0)
func() {
h.mu.Lock()
defer h.mu.Unlock()
if h.full {
recs = append(recs, h.recs[h.idx:]...)
}
recs = append(recs, h.recs[:h.idx]...)
// reset state
h.full = false
h.idx = 0
}()
// don't hold the lock while we flush to the wrapped handler
for _, r := range recs {
h.handler.Log(r)
}
}
// HotSwapHandler wraps another handler that may swapped out
// dynamically at runtime in a thread-safe fashion.
// HotSwapHandler is the same functionality
// used to implement the SetHandler method for the default
// implementation of Logger.
func HotSwapHandler(h log.Handler) *HotSwap {
hs := new(HotSwap)
hs.Swap(h)
return hs
}
// HotSwap is the Log15.Handler. Read `HotSwapHandler` for more information.
type HotSwap struct {
handler unsafe.Pointer
}
// Log implements log15.Handler interface.
func (h *HotSwap) Log(r *log.Record) error {
return (*(*log.Handler)(atomic.LoadPointer(&h.handler))).Log(r)
}
// Swap atomically the logger handler.
func (h *HotSwap) Swap(newHandler log.Handler) {
atomic.StorePointer(&h.handler, unsafe.Pointer(&newHandler))
}
// FatalHandler makes critical errors exit the program
// immediately, much like the log.Fatal* methods from the
// standard log package
func FatalHandler(h log.Handler) log.Handler {
return log.FuncHandler(func(r *log.Record) error {
err := h.Log(r)
if r.Lvl == log.LvlCrit {
os.Exit(1)
}
return err
})
}
package ext
import (
"fmt"
"math/rand"
"sync"
"time"
)
var r = rand.New(&lockedSource{src: rand.NewSource(time.Now().Unix())})
// RandId creates a random identifier of the requested length.
// Useful for assigning mostly-unique identifiers for logging
// and identification that are unlikely to collide because of
// short lifespan or low set cardinality
func RandId(idlen int) string {
b := make([]byte, idlen)
var randVal uint32
for i := 0; i < idlen; i++ {
byteIdx := i % 4
if byteIdx == 0 {
randVal = r.Uint32()
}
b[i] = byte((randVal >> (8 * uint(byteIdx))) & 0xFF)
}
return fmt.Sprintf("%x", b)
}
// lockedSource is a wrapper to allow a rand.Source to be used
// concurrently (same type as the one used internally in math/rand).
type lockedSource struct {
lk sync.Mutex
src rand.Source
}
func (r *lockedSource) Int63() (n int64) {
r.lk.Lock()
n = r.src.Int63()
r.lk.Unlock()
return
}
func (r *lockedSource) Seed(seed int64) {
r.lk.Lock()
r.src.Seed(seed)
r.lk.Unlock()
}
package log15
import (
"bytes"
"encoding/json"
"fmt"
"reflect"
"strconv"
"strings"
"sync"
"time"
)
const (
timeFormat = "2006-01-02T15:04:05-0700"
termTimeFormat = "01-02|15:04:05"
floatFormat = 'f'
termMsgJust = 40
)
// Format is the interface implemented by StreamHandler formatters.
type Format interface {
Format(r *Record) []byte
}
// FormatFunc returns a new Format object which uses
// the given function to perform record formatting.
func FormatFunc(f func(*Record) []byte) Format {
return formatFunc(f)
}
type formatFunc func(*Record) []byte
func (f formatFunc) Format(r *Record) []byte {
return f(r)
}
// TerminalFormat formats log records optimized for human readability on
// a terminal with color-coded level output and terser human friendly timestamp.
// This format should only be used for interactive programs or while developing.
//
// [TIME] [LEVEL] MESAGE key=value key=value ...
//
// Example:
//
// [May 16 20:58:45] [DBUG] remove route ns=haproxy addr=127.0.0.1:50002
//
func TerminalFormat() Format {
return FormatFunc(func(r *Record) []byte {
var color = 0
switch r.Lvl {
case LvlCrit:
color = 35
case LvlError:
color = 31
case LvlWarn:
color = 33
case LvlInfo:
color = 32
case LvlDebug:
color = 36
}
b := &bytes.Buffer{}
lvl := strings.ToUpper(r.Lvl.String())
if color > 0 {
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s] %s ", color, lvl, r.Time.Format(termTimeFormat), r.Msg)
} else {
fmt.Fprintf(b, "[%s] [%s] %s ", lvl, r.Time.Format(termTimeFormat), r.Msg)
}
// try to justify the log output for short messages
if len(r.Ctx) > 0 && len(r.Msg) < termMsgJust {