mirror of
https://github.com/rs/zerolog
synced 2026-06-08 17:13:30 +00:00
Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b53826c57a | |||
| 1cc67e6325 | |||
| c2fc1c63dc | |||
| c3d02683c7 | |||
| 1251b38a89 | |||
| c8e50a6043 | |||
| 9a65e7ccd2 | |||
| 89e128fdc1 | |||
| 9d194eb6f5 | |||
| 3ac71fc58d | |||
| 26094019c8 | |||
| 8c682b3b12 | |||
| 96c2125038 | |||
| d76a89fffc | |||
| e26050b2a3 | |||
| 560e8848f1 | |||
| 9e5c06cf0e | |||
| 46339da83a | |||
| 2ed2f2c974 | |||
| 90fdb63d84 | |||
| a83efb6080 | |||
| 89ff8dbc5f | |||
| 614d88bbf8 | |||
| 6cdd9977c4 | |||
| fdbdb09630 | |||
| f220d89e1f | |||
| 87aceba511 | |||
| 2aa3c3ae4f | |||
| eed4c2b94d | |||
| 7af653895b | |||
| c964fc4812 | |||
| 72d41dedeb | |||
| 1b37e7fcd9 | |||
| 447d0fc7f5 | |||
| 9c5f03507d | |||
| 15fc33fe89 |
+2
-1
@@ -2,9 +2,10 @@ language: go
|
||||
go:
|
||||
- 1.7
|
||||
- 1.8
|
||||
- 1.9
|
||||
- tip
|
||||
matrix:
|
||||
allow_failures:
|
||||
- go: tip
|
||||
script:
|
||||
go test -v -race -cpu=1,2,4 ./...
|
||||
go test -v -race -cpu=1,2,4 -bench . -benchmem ./...
|
||||
|
||||
@@ -4,20 +4,25 @@
|
||||
|
||||
The zerolog package provides a fast and simple logger dedicated to JSON output.
|
||||
|
||||
Zerolog's API is designed to provide both a great developer experience and stunning [performance](#performance). Its unique chaining API allows zerolog to write JSON log events by avoiding allocations and reflection.
|
||||
Zerolog's API is designed to provide both a great developer experience and stunning [performance](#benchmarks). Its unique chaining API allows zerolog to write JSON log events by avoiding allocations and reflection.
|
||||
|
||||
The uber's [zap](https://godoc.org/go.uber.org/zap) library pioneered this approach. Zerolog is taking this concept to the next level with simpler to use API and even better performance.
|
||||
|
||||
To keep the code base and the API simple, zerolog focuses on JSON logging only. As [suggested on reddit](https://www.reddit.com/r/golang/comments/6c9k7n/zerolog_is_now_faster_than_zap/), you may use tools like [humanlog](https://github.com/aybabtme/humanlog) to pretty print JSON on the console during development.
|
||||
To keep the code base and the API simple, zerolog focuses on JSON logging only. Pretty logging on the console is made possible using the provided (but inefficient) `zerolog.ConsoleWriter`.
|
||||
|
||||

|
||||
|
||||
## Features
|
||||
|
||||
* Blazing fast
|
||||
* Low to zero allocation
|
||||
* Level logging
|
||||
* Sampling
|
||||
* Hooks
|
||||
* Contextual fields
|
||||
* `context.Context` integration
|
||||
* `net/http` helpers
|
||||
* Pretty logging for development
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -27,6 +32,13 @@ import "github.com/rs/zerolog/log"
|
||||
|
||||
### A global logger can be use for simple logging
|
||||
|
||||
```go
|
||||
log.Print("hello world")
|
||||
|
||||
// Output: {"level":"debug","time":1494567715,"message":"hello world"}
|
||||
```
|
||||
|
||||
|
||||
```go
|
||||
log.Info().Msg("hello world")
|
||||
|
||||
@@ -96,6 +108,18 @@ if e := log.Debug(); e.Enabled() {
|
||||
// Output: {"level":"info","time":1494567715,"message":"routed message"}
|
||||
```
|
||||
|
||||
### Pretty logging
|
||||
|
||||
```go
|
||||
if isConsole {
|
||||
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
|
||||
}
|
||||
|
||||
log.Info().Str("foo", "bar").Msg("Hello world")
|
||||
|
||||
// Output: 1494567715 |INFO| Hello world foo=bar
|
||||
```
|
||||
|
||||
### Sub dictionary
|
||||
|
||||
```go
|
||||
@@ -138,10 +162,45 @@ log.Logger = log.With().Str("foo", "bar").Logger()
|
||||
### Log Sampling
|
||||
|
||||
```go
|
||||
sampled := log.Sample(10)
|
||||
sampled := log.Sample(&zerolog.BasicSampler{N: 10})
|
||||
sampled.Info().Msg("will be logged every 10 messages")
|
||||
|
||||
// Output: {"time":1494567715,"sample":10,"message":"will be logged every 10 messages"}
|
||||
// Output: {"time":1494567715,"level":"info","message":"will be logged every 10 messages"}
|
||||
```
|
||||
|
||||
More advanced sampling:
|
||||
|
||||
```go
|
||||
// Will let 5 debug messages per period of 1 second.
|
||||
// Over 5 debug message, 1 every 100 debug messages are logged.
|
||||
// Other levels are not sampled.
|
||||
sampled := log.Sample(zerolog.LevelSampler{
|
||||
DebugSampler: &zerolog.BurstSampler{
|
||||
Burst: 5,
|
||||
Period: 1*time.Second,
|
||||
NextSampler: &zerolog.BasicSampler{N: 100},
|
||||
},
|
||||
})
|
||||
sampled.Debug().Msg("hello world")
|
||||
|
||||
// Output: {"time":1494567715,"level":"debug","message":"hello world"}
|
||||
```
|
||||
|
||||
### Hooks
|
||||
|
||||
```go
|
||||
type SeverityHook struct{}
|
||||
|
||||
func (h SeverityHook) Run(e *zerolog.Event, level zerolog.Level, msg string) {
|
||||
if level != zerolog.NoLevel {
|
||||
e.Str("severity", level.String())
|
||||
}
|
||||
}
|
||||
|
||||
hooked := log.Hook(SeverityHook{})
|
||||
hooked.Warn().Msg("")
|
||||
|
||||
// Output: {"level":"warn","severity":"warn"}
|
||||
```
|
||||
|
||||
### Pass a sub-logger by context
|
||||
@@ -189,6 +248,15 @@ c = c.Append(hlog.NewHandler(log))
|
||||
|
||||
// Install some provided extra handler to set some request's context fields.
|
||||
// Thanks to those handler, all our logs will come with some pre-populated fields.
|
||||
c = c.Append(hlog.AccessHandler(func(r *http.Request, status, size int, duration time.Duration) {
|
||||
hlog.FromRequest(r).Info().
|
||||
Str("method", r.Method).
|
||||
Str("url", r.URL.String()).
|
||||
Int("status", status).
|
||||
Int("size", size).
|
||||
Dur("duration", duration).
|
||||
Msg("")
|
||||
}))
|
||||
c = c.Append(hlog.RemoteAddrHandler("ip"))
|
||||
c = c.Append(hlog.UserAgentHandler("user_agent"))
|
||||
c = c.Append(hlog.RefererHandler("referer"))
|
||||
@@ -224,7 +292,6 @@ Some settings can be changed and will by applied to all loggers:
|
||||
* `zerolog.LevelFieldName`: Can be set to customize level field name.
|
||||
* `zerolog.MessageFieldName`: Can be set to customize message field name.
|
||||
* `zerolog.ErrorFieldName`: Can be set to customize `Err` field name.
|
||||
* `zerolog.SampleFieldName`: Can be set to customize the field name added when sampling is enabled.
|
||||
* `zerolog.TimeFieldFormat`: Can be set to customize `Time` field value formatting. If set with an empty string, times are formated as UNIX timestamp.
|
||||
// DurationFieldUnit defines the unit for time.Duration type fields added
|
||||
// using the Dur method.
|
||||
@@ -250,7 +317,7 @@ Some settings can be changed and will by applied to all loggers:
|
||||
* `Dict`: Adds a sub-key/value as a field of the event.
|
||||
* `Interface`: Uses reflection to marshal the type.
|
||||
|
||||
## Performance
|
||||
## Benchmarks
|
||||
|
||||
All operations are allocation free (those numbers *include* JSON encoding):
|
||||
|
||||
@@ -262,7 +329,12 @@ BenchmarkContextFields-8 30000000 44.9 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkLogFields-8 10000000 184 ns/op 0 B/op 0 allocs/op
|
||||
```
|
||||
|
||||
Using Uber's zap [comparison benchmark](https://github.com/uber-go/zap#performance):
|
||||
There are a few Go logging benchmarks and comparisons that include zerolog.
|
||||
|
||||
- [imkira/go-loggers-bench](https://github.com/imkira/go-loggers-bench)
|
||||
- [uber-common/zap](https://github.com/uber-go/zap#performance)
|
||||
|
||||
Using Uber's zap comparison benchmark:
|
||||
|
||||
Log a message and 10 fields:
|
||||
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
package zerolog
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog/internal/json"
|
||||
)
|
||||
|
||||
var arrayPool = &sync.Pool{
|
||||
New: func() interface{} {
|
||||
return &Array{
|
||||
buf: make([]byte, 0, 500),
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
type Array struct {
|
||||
buf []byte
|
||||
}
|
||||
|
||||
// Arr creates an array to be added to an Event or Context.
|
||||
func Arr() *Array {
|
||||
a := arrayPool.Get().(*Array)
|
||||
a.buf = a.buf[:0]
|
||||
return a
|
||||
}
|
||||
|
||||
func (*Array) MarshalZerologArray(*Array) {
|
||||
}
|
||||
|
||||
func (a *Array) write(dst []byte) []byte {
|
||||
if len(a.buf) == 0 {
|
||||
dst = append(dst, `[]`...)
|
||||
} else {
|
||||
a.buf[0] = '['
|
||||
dst = append(append(dst, a.buf...), ']')
|
||||
}
|
||||
arrayPool.Put(a)
|
||||
return dst
|
||||
}
|
||||
|
||||
// Object marshals an object that implement the LogObjectMarshaler
|
||||
// interface and append it to the array.
|
||||
func (a *Array) Object(obj LogObjectMarshaler) *Array {
|
||||
a.buf = append(a.buf, ',')
|
||||
e := Dict()
|
||||
obj.MarshalZerologObject(e)
|
||||
e.buf = append(e.buf, '}')
|
||||
a.buf = append(a.buf, e.buf...)
|
||||
return a
|
||||
}
|
||||
|
||||
// Str append the val as a string to the array.
|
||||
func (a *Array) Str(val string) *Array {
|
||||
a.buf = json.AppendString(append(a.buf, ','), val)
|
||||
return a
|
||||
}
|
||||
|
||||
// Bytes append the val as a string to the array.
|
||||
func (a *Array) Bytes(val []byte) *Array {
|
||||
a.buf = json.AppendBytes(append(a.buf, ','), val)
|
||||
return a
|
||||
}
|
||||
|
||||
// Err append the err as a string to the array.
|
||||
func (a *Array) Err(err error) *Array {
|
||||
a.buf = json.AppendError(append(a.buf, ','), err)
|
||||
return a
|
||||
}
|
||||
|
||||
// Bool append the val as a bool to the array.
|
||||
func (a *Array) Bool(b bool) *Array {
|
||||
a.buf = json.AppendBool(append(a.buf, ','), b)
|
||||
return a
|
||||
}
|
||||
|
||||
// Int append i as a int to the array.
|
||||
func (a *Array) Int(i int) *Array {
|
||||
a.buf = json.AppendInt(append(a.buf, ','), i)
|
||||
return a
|
||||
}
|
||||
|
||||
// Int8 append i as a int8 to the array.
|
||||
func (a *Array) Int8(i int8) *Array {
|
||||
a.buf = json.AppendInt8(append(a.buf, ','), i)
|
||||
return a
|
||||
}
|
||||
|
||||
// Int16 append i as a int16 to the array.
|
||||
func (a *Array) Int16(i int16) *Array {
|
||||
a.buf = json.AppendInt16(append(a.buf, ','), i)
|
||||
return a
|
||||
}
|
||||
|
||||
// Int32 append i as a int32 to the array.
|
||||
func (a *Array) Int32(i int32) *Array {
|
||||
a.buf = json.AppendInt32(append(a.buf, ','), i)
|
||||
return a
|
||||
}
|
||||
|
||||
// Int64 append i as a int64 to the array.
|
||||
func (a *Array) Int64(i int64) *Array {
|
||||
a.buf = json.AppendInt64(append(a.buf, ','), i)
|
||||
return a
|
||||
}
|
||||
|
||||
// Uint append i as a uint to the array.
|
||||
func (a *Array) Uint(i uint) *Array {
|
||||
a.buf = json.AppendUint(append(a.buf, ','), i)
|
||||
return a
|
||||
}
|
||||
|
||||
// Uint8 append i as a uint8 to the array.
|
||||
func (a *Array) Uint8(i uint8) *Array {
|
||||
a.buf = json.AppendUint8(append(a.buf, ','), i)
|
||||
return a
|
||||
}
|
||||
|
||||
// Uint16 append i as a uint16 to the array.
|
||||
func (a *Array) Uint16(i uint16) *Array {
|
||||
a.buf = json.AppendUint16(append(a.buf, ','), i)
|
||||
return a
|
||||
}
|
||||
|
||||
// Uint32 append i as a uint32 to the array.
|
||||
func (a *Array) Uint32(i uint32) *Array {
|
||||
a.buf = json.AppendUint32(append(a.buf, ','), i)
|
||||
return a
|
||||
}
|
||||
|
||||
// Uint64 append i as a uint64 to the array.
|
||||
func (a *Array) Uint64(i uint64) *Array {
|
||||
a.buf = json.AppendUint64(append(a.buf, ','), i)
|
||||
return a
|
||||
}
|
||||
|
||||
// Float32 append f as a float32 to the array.
|
||||
func (a *Array) Float32(f float32) *Array {
|
||||
a.buf = json.AppendFloat32(append(a.buf, ','), f)
|
||||
return a
|
||||
}
|
||||
|
||||
// Float64 append f as a float64 to the array.
|
||||
func (a *Array) Float64(f float64) *Array {
|
||||
a.buf = json.AppendFloat64(append(a.buf, ','), f)
|
||||
return a
|
||||
}
|
||||
|
||||
// Time append t formated as string using zerolog.TimeFieldFormat.
|
||||
func (a *Array) Time(t time.Time) *Array {
|
||||
a.buf = json.AppendTime(append(a.buf, ','), t, TimeFieldFormat)
|
||||
return a
|
||||
}
|
||||
|
||||
// Dur append d to the array.
|
||||
func (a *Array) Dur(d time.Duration) *Array {
|
||||
a.buf = json.AppendDuration(append(a.buf, ','), d, DurationFieldUnit, DurationFieldInteger)
|
||||
return a
|
||||
}
|
||||
|
||||
// Interface append i marshaled using reflection.
|
||||
func (a *Array) Interface(i interface{}) *Array {
|
||||
if obj, ok := i.(LogObjectMarshaler); ok {
|
||||
return a.Object(obj)
|
||||
}
|
||||
a.buf = json.AppendInterface(append(a.buf, ','), i)
|
||||
return a
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package zerolog
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestArray(t *testing.T) {
|
||||
a := Arr().
|
||||
Bool(true).
|
||||
Int(1).
|
||||
Int8(2).
|
||||
Int16(3).
|
||||
Int32(4).
|
||||
Int64(5).
|
||||
Uint(6).
|
||||
Uint8(7).
|
||||
Uint16(8).
|
||||
Uint32(9).
|
||||
Uint64(10).
|
||||
Float32(11).
|
||||
Float64(12).
|
||||
Str("a").
|
||||
Time(time.Time{}).
|
||||
Dur(0)
|
||||
want := `[true,1,2,3,4,5,6,7,8,9,10,11,12,"a","0001-01-01T00:00:00Z",0]`
|
||||
if got := string(a.write([]byte{})); got != want {
|
||||
t.Errorf("Array.write()\ngot: %s\nwant: %s", got, want)
|
||||
}
|
||||
}
|
||||
@@ -57,6 +57,18 @@ func BenchmarkContextFields(b *testing.B) {
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkContextAppend(b *testing.B) {
|
||||
logger := New(ioutil.Discard).With().
|
||||
Str("foo", "bar").
|
||||
Logger()
|
||||
b.ResetTimer()
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
logger.With().Str("bar", "baz")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkLogFields(b *testing.B) {
|
||||
logger := New(ioutil.Discard)
|
||||
b.ResetTimer()
|
||||
@@ -71,3 +83,135 @@ func BenchmarkLogFields(b *testing.B) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
type obj struct {
|
||||
Pub string
|
||||
Tag string `json:"tag"`
|
||||
priv int
|
||||
}
|
||||
|
||||
func (o obj) MarshalZerologObject(e *Event) {
|
||||
e.Str("Pub", o.Pub).
|
||||
Str("Tag", o.Tag).
|
||||
Int("priv", o.priv)
|
||||
}
|
||||
|
||||
func BenchmarkLogFieldType(b *testing.B) {
|
||||
bools := []bool{true, false, true, false, true, false, true, false, true, false}
|
||||
ints := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
|
||||
floats := []float64{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
|
||||
strings := []string{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j"}
|
||||
durations := []time.Duration{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
|
||||
times := []time.Time{
|
||||
time.Unix(0, 0),
|
||||
time.Unix(1, 0),
|
||||
time.Unix(2, 0),
|
||||
time.Unix(3, 0),
|
||||
time.Unix(4, 0),
|
||||
time.Unix(5, 0),
|
||||
time.Unix(6, 0),
|
||||
time.Unix(7, 0),
|
||||
time.Unix(8, 0),
|
||||
time.Unix(9, 0),
|
||||
}
|
||||
interfaces := []struct {
|
||||
Pub string
|
||||
Tag string `json:"tag"`
|
||||
priv int
|
||||
}{
|
||||
{"a", "a", 0},
|
||||
{"a", "a", 0},
|
||||
{"a", "a", 0},
|
||||
{"a", "a", 0},
|
||||
{"a", "a", 0},
|
||||
{"a", "a", 0},
|
||||
{"a", "a", 0},
|
||||
{"a", "a", 0},
|
||||
{"a", "a", 0},
|
||||
{"a", "a", 0},
|
||||
}
|
||||
objects := []obj{
|
||||
obj{"a", "a", 0},
|
||||
obj{"a", "a", 0},
|
||||
obj{"a", "a", 0},
|
||||
obj{"a", "a", 0},
|
||||
obj{"a", "a", 0},
|
||||
obj{"a", "a", 0},
|
||||
obj{"a", "a", 0},
|
||||
obj{"a", "a", 0},
|
||||
obj{"a", "a", 0},
|
||||
obj{"a", "a", 0},
|
||||
}
|
||||
errs := []error{errors.New("a"), errors.New("b"), errors.New("c"), errors.New("d"), errors.New("e")}
|
||||
types := map[string]func(e *Event) *Event{
|
||||
"Bool": func(e *Event) *Event {
|
||||
return e.Bool("k", bools[0])
|
||||
},
|
||||
"Bools": func(e *Event) *Event {
|
||||
return e.Bools("k", bools)
|
||||
},
|
||||
"Int": func(e *Event) *Event {
|
||||
return e.Int("k", ints[0])
|
||||
},
|
||||
"Ints": func(e *Event) *Event {
|
||||
return e.Ints("k", ints)
|
||||
},
|
||||
"Float": func(e *Event) *Event {
|
||||
return e.Float64("k", floats[0])
|
||||
},
|
||||
"Floats": func(e *Event) *Event {
|
||||
return e.Floats64("k", floats)
|
||||
},
|
||||
"Str": func(e *Event) *Event {
|
||||
return e.Str("k", strings[0])
|
||||
},
|
||||
"Strs": func(e *Event) *Event {
|
||||
return e.Strs("k", strings)
|
||||
},
|
||||
"Err": func(e *Event) *Event {
|
||||
return e.Err(errs[0])
|
||||
},
|
||||
"Errs": func(e *Event) *Event {
|
||||
return e.Errs("k", errs)
|
||||
},
|
||||
"Time": func(e *Event) *Event {
|
||||
return e.Time("k", times[0])
|
||||
},
|
||||
"Times": func(e *Event) *Event {
|
||||
return e.Times("k", times)
|
||||
},
|
||||
"Dur": func(e *Event) *Event {
|
||||
return e.Dur("k", durations[0])
|
||||
},
|
||||
"Durs": func(e *Event) *Event {
|
||||
return e.Durs("k", durations)
|
||||
},
|
||||
"Interface": func(e *Event) *Event {
|
||||
return e.Interface("k", interfaces[0])
|
||||
},
|
||||
"Interfaces": func(e *Event) *Event {
|
||||
return e.Interface("k", interfaces)
|
||||
},
|
||||
"Interface(Object)": func(e *Event) *Event {
|
||||
return e.Interface("k", objects[0])
|
||||
},
|
||||
"Interface(Objects)": func(e *Event) *Event {
|
||||
return e.Interface("k", objects)
|
||||
},
|
||||
"Object": func(e *Event) *Event {
|
||||
return e.Object("k", objects[0])
|
||||
},
|
||||
}
|
||||
logger := New(ioutil.Discard)
|
||||
b.ResetTimer()
|
||||
for name := range types {
|
||||
f := types[name]
|
||||
b.Run(name, func(b *testing.B) {
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
f(logger.Info()).Msg("")
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
package zerolog
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const (
|
||||
cReset = 0
|
||||
cBold = 1
|
||||
cRed = 31
|
||||
cGreen = 32
|
||||
cYellow = 33
|
||||
cBlue = 34
|
||||
cMagenta = 35
|
||||
cCyan = 36
|
||||
cGray = 37
|
||||
cDarkGray = 90
|
||||
)
|
||||
|
||||
var consoleBufPool = sync.Pool{
|
||||
New: func() interface{} {
|
||||
return bytes.NewBuffer(make([]byte, 0, 100))
|
||||
},
|
||||
}
|
||||
|
||||
// ConsoleWriter reads a JSON object per write operation and output an
|
||||
// optionally colored human readable version on the Out writer.
|
||||
type ConsoleWriter struct {
|
||||
Out io.Writer
|
||||
NoColor bool
|
||||
}
|
||||
|
||||
func (w ConsoleWriter) Write(p []byte) (n int, err error) {
|
||||
var event map[string]interface{}
|
||||
err = json.Unmarshal(p, &event)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
buf := consoleBufPool.Get().(*bytes.Buffer)
|
||||
defer consoleBufPool.Put(buf)
|
||||
lvlColor := cReset
|
||||
level := "????"
|
||||
if l, ok := event[LevelFieldName].(string); ok {
|
||||
if !w.NoColor {
|
||||
lvlColor = levelColor(l)
|
||||
}
|
||||
level = strings.ToUpper(l)[0:4]
|
||||
}
|
||||
fmt.Fprintf(buf, "%s |%s| %s",
|
||||
colorize(event[TimestampFieldName], cDarkGray, !w.NoColor),
|
||||
colorize(level, lvlColor, !w.NoColor),
|
||||
colorize(event[MessageFieldName], cReset, !w.NoColor))
|
||||
fields := make([]string, 0, len(event))
|
||||
for field := range event {
|
||||
switch field {
|
||||
case LevelFieldName, TimestampFieldName, MessageFieldName:
|
||||
continue
|
||||
}
|
||||
fields = append(fields, field)
|
||||
}
|
||||
sort.Strings(fields)
|
||||
for _, field := range fields {
|
||||
fmt.Fprintf(buf, " %s=", colorize(field, cCyan, !w.NoColor))
|
||||
switch value := event[field].(type) {
|
||||
case string:
|
||||
if needsQuote(value) {
|
||||
buf.WriteString(strconv.Quote(value))
|
||||
} else {
|
||||
buf.WriteString(value)
|
||||
}
|
||||
default:
|
||||
fmt.Fprint(buf, value)
|
||||
}
|
||||
}
|
||||
buf.WriteByte('\n')
|
||||
buf.WriteTo(w.Out)
|
||||
n = len(p)
|
||||
return
|
||||
}
|
||||
|
||||
func colorize(s interface{}, color int, enabled bool) string {
|
||||
if !enabled {
|
||||
return fmt.Sprintf("%v", s)
|
||||
}
|
||||
return fmt.Sprintf("\x1b[%dm%v\x1b[0m", color, s)
|
||||
}
|
||||
|
||||
func levelColor(level string) int {
|
||||
switch level {
|
||||
case "debug":
|
||||
return cMagenta
|
||||
case "info":
|
||||
return cGreen
|
||||
case "warn":
|
||||
return cYellow
|
||||
case "error", "fatal", "panic":
|
||||
return cRed
|
||||
default:
|
||||
return cReset
|
||||
}
|
||||
}
|
||||
|
||||
func needsQuote(s string) bool {
|
||||
for i := range s {
|
||||
if s[i] < 0x20 || s[i] > 0x7e || s[i] == ' ' || s[i] == '\\' || s[i] == '"' {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
+175
-22
@@ -1,6 +1,11 @@
|
||||
package zerolog
|
||||
|
||||
import "time"
|
||||
import (
|
||||
"io/ioutil"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog/internal/json"
|
||||
)
|
||||
|
||||
// Context configures a new sub-logger with contextual fields.
|
||||
type Context struct {
|
||||
@@ -12,108 +17,244 @@ func (c Context) Logger() Logger {
|
||||
return c.l
|
||||
}
|
||||
|
||||
// Fields is a helper function to use a map to set fields using type assertion.
|
||||
func (c Context) Fields(fields map[string]interface{}) Context {
|
||||
c.l.context = appendFields(c.l.context, fields)
|
||||
return c
|
||||
}
|
||||
|
||||
// Dict adds the field key with the dict to the logger context.
|
||||
func (c Context) Dict(key string, dict *Event) Context {
|
||||
dict.buf = append(dict.buf, '}')
|
||||
c.l.context = append(appendKey(c.l.context, key), dict.buf...)
|
||||
c.l.context = append(json.AppendKey(c.l.context, key), dict.buf...)
|
||||
eventPool.Put(dict)
|
||||
return c
|
||||
}
|
||||
|
||||
// Array adds the field key with an array to the event context.
|
||||
// Use zerolog.Arr() to create the array or pass a type that
|
||||
// implement the LogArrayMarshaler interface.
|
||||
func (c Context) Array(key string, arr LogArrayMarshaler) Context {
|
||||
c.l.context = json.AppendKey(c.l.context, key)
|
||||
if arr, ok := arr.(*Array); ok {
|
||||
c.l.context = arr.write(c.l.context)
|
||||
return c
|
||||
}
|
||||
var a *Array
|
||||
if aa, ok := arr.(*Array); ok {
|
||||
a = aa
|
||||
} else {
|
||||
a = Arr()
|
||||
arr.MarshalZerologArray(a)
|
||||
}
|
||||
c.l.context = a.write(c.l.context)
|
||||
return c
|
||||
}
|
||||
|
||||
// Object marshals an object that implement the LogObjectMarshaler interface.
|
||||
func (c Context) Object(key string, obj LogObjectMarshaler) Context {
|
||||
e := newEvent(levelWriterAdapter{ioutil.Discard}, 0, true)
|
||||
e.Object(key, obj)
|
||||
e.buf[0] = ',' // A new event starts as an object, we want to embed it.
|
||||
c.l.context = append(c.l.context, e.buf...)
|
||||
eventPool.Put(e)
|
||||
return c
|
||||
}
|
||||
|
||||
// Str adds the field key with val as a string to the logger context.
|
||||
func (c Context) Str(key, val string) Context {
|
||||
c.l.context = appendString(c.l.context, key, val)
|
||||
c.l.context = json.AppendString(json.AppendKey(c.l.context, key), val)
|
||||
return c
|
||||
}
|
||||
|
||||
// Strs adds the field key with val as a string to the logger context.
|
||||
func (c Context) Strs(key string, vals []string) Context {
|
||||
c.l.context = json.AppendStrings(json.AppendKey(c.l.context, key), vals)
|
||||
return c
|
||||
}
|
||||
|
||||
// Bytes adds the field key with val as a []byte to the logger context.
|
||||
func (c Context) Bytes(key string, val []byte) Context {
|
||||
c.l.context = json.AppendBytes(json.AppendKey(c.l.context, key), val)
|
||||
return c
|
||||
}
|
||||
|
||||
// AnErr adds the field key with err as a string to the logger context.
|
||||
func (c Context) AnErr(key string, err error) Context {
|
||||
c.l.context = appendErrorKey(c.l.context, key, err)
|
||||
if err != nil {
|
||||
c.l.context = json.AppendError(json.AppendKey(c.l.context, key), err)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// Errs adds the field key with errs as an array of strings to the logger context.
|
||||
func (c Context) Errs(key string, errs []error) Context {
|
||||
c.l.context = json.AppendErrors(json.AppendKey(c.l.context, key), errs)
|
||||
return c
|
||||
}
|
||||
|
||||
// Err adds the field "error" with err as a string to the logger context.
|
||||
// To customize the key name, change zerolog.ErrorFieldName.
|
||||
func (c Context) Err(err error) Context {
|
||||
c.l.context = appendError(c.l.context, err)
|
||||
if err != nil {
|
||||
c.l.context = json.AppendError(json.AppendKey(c.l.context, ErrorFieldName), err)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// Bool adds the field key with val as a Boolean to the logger context.
|
||||
// Bool adds the field key with val as a bool to the logger context.
|
||||
func (c Context) Bool(key string, b bool) Context {
|
||||
c.l.context = appendBool(c.l.context, key, b)
|
||||
c.l.context = json.AppendBool(json.AppendKey(c.l.context, key), b)
|
||||
return c
|
||||
}
|
||||
|
||||
// Bools adds the field key with val as a []bool to the logger context.
|
||||
func (c Context) Bools(key string, b []bool) Context {
|
||||
c.l.context = json.AppendBools(json.AppendKey(c.l.context, key), b)
|
||||
return c
|
||||
}
|
||||
|
||||
// Int adds the field key with i as a int to the logger context.
|
||||
func (c Context) Int(key string, i int) Context {
|
||||
c.l.context = appendInt(c.l.context, key, i)
|
||||
c.l.context = json.AppendInt(json.AppendKey(c.l.context, key), i)
|
||||
return c
|
||||
}
|
||||
|
||||
// Ints adds the field key with i as a []int to the logger context.
|
||||
func (c Context) Ints(key string, i []int) Context {
|
||||
c.l.context = json.AppendInts(json.AppendKey(c.l.context, key), i)
|
||||
return c
|
||||
}
|
||||
|
||||
// Int8 adds the field key with i as a int8 to the logger context.
|
||||
func (c Context) Int8(key string, i int8) Context {
|
||||
c.l.context = appendInt8(c.l.context, key, i)
|
||||
c.l.context = json.AppendInt8(json.AppendKey(c.l.context, key), i)
|
||||
return c
|
||||
}
|
||||
|
||||
// Ints8 adds the field key with i as a []int8 to the logger context.
|
||||
func (c Context) Ints8(key string, i []int8) Context {
|
||||
c.l.context = json.AppendInts8(json.AppendKey(c.l.context, key), i)
|
||||
return c
|
||||
}
|
||||
|
||||
// Int16 adds the field key with i as a int16 to the logger context.
|
||||
func (c Context) Int16(key string, i int16) Context {
|
||||
c.l.context = appendInt16(c.l.context, key, i)
|
||||
c.l.context = json.AppendInt16(json.AppendKey(c.l.context, key), i)
|
||||
return c
|
||||
}
|
||||
|
||||
// Ints16 adds the field key with i as a []int16 to the logger context.
|
||||
func (c Context) Ints16(key string, i []int16) Context {
|
||||
c.l.context = json.AppendInts16(json.AppendKey(c.l.context, key), i)
|
||||
return c
|
||||
}
|
||||
|
||||
// Int32 adds the field key with i as a int32 to the logger context.
|
||||
func (c Context) Int32(key string, i int32) Context {
|
||||
c.l.context = appendInt32(c.l.context, key, i)
|
||||
c.l.context = json.AppendInt32(json.AppendKey(c.l.context, key), i)
|
||||
return c
|
||||
}
|
||||
|
||||
// Ints32 adds the field key with i as a []int32 to the logger context.
|
||||
func (c Context) Ints32(key string, i []int32) Context {
|
||||
c.l.context = json.AppendInts32(json.AppendKey(c.l.context, key), i)
|
||||
return c
|
||||
}
|
||||
|
||||
// Int64 adds the field key with i as a int64 to the logger context.
|
||||
func (c Context) Int64(key string, i int64) Context {
|
||||
c.l.context = appendInt64(c.l.context, key, i)
|
||||
c.l.context = json.AppendInt64(json.AppendKey(c.l.context, key), i)
|
||||
return c
|
||||
}
|
||||
|
||||
// Ints64 adds the field key with i as a []int64 to the logger context.
|
||||
func (c Context) Ints64(key string, i []int64) Context {
|
||||
c.l.context = json.AppendInts64(json.AppendKey(c.l.context, key), i)
|
||||
return c
|
||||
}
|
||||
|
||||
// Uint adds the field key with i as a uint to the logger context.
|
||||
func (c Context) Uint(key string, i uint) Context {
|
||||
c.l.context = appendUint(c.l.context, key, i)
|
||||
c.l.context = json.AppendUint(json.AppendKey(c.l.context, key), i)
|
||||
return c
|
||||
}
|
||||
|
||||
// Uints adds the field key with i as a []uint to the logger context.
|
||||
func (c Context) Uints(key string, i []uint) Context {
|
||||
c.l.context = json.AppendUints(json.AppendKey(c.l.context, key), i)
|
||||
return c
|
||||
}
|
||||
|
||||
// Uint8 adds the field key with i as a uint8 to the logger context.
|
||||
func (c Context) Uint8(key string, i uint8) Context {
|
||||
c.l.context = appendUint8(c.l.context, key, i)
|
||||
c.l.context = json.AppendUint8(json.AppendKey(c.l.context, key), i)
|
||||
return c
|
||||
}
|
||||
|
||||
// Uints8 adds the field key with i as a []uint8 to the logger context.
|
||||
func (c Context) Uints8(key string, i []uint8) Context {
|
||||
c.l.context = json.AppendUints8(json.AppendKey(c.l.context, key), i)
|
||||
return c
|
||||
}
|
||||
|
||||
// Uint16 adds the field key with i as a uint16 to the logger context.
|
||||
func (c Context) Uint16(key string, i uint16) Context {
|
||||
c.l.context = appendUint16(c.l.context, key, i)
|
||||
c.l.context = json.AppendUint16(json.AppendKey(c.l.context, key), i)
|
||||
return c
|
||||
}
|
||||
|
||||
// Uints16 adds the field key with i as a []uint16 to the logger context.
|
||||
func (c Context) Uints16(key string, i []uint16) Context {
|
||||
c.l.context = json.AppendUints16(json.AppendKey(c.l.context, key), i)
|
||||
return c
|
||||
}
|
||||
|
||||
// Uint32 adds the field key with i as a uint32 to the logger context.
|
||||
func (c Context) Uint32(key string, i uint32) Context {
|
||||
c.l.context = appendUint32(c.l.context, key, i)
|
||||
c.l.context = json.AppendUint32(json.AppendKey(c.l.context, key), i)
|
||||
return c
|
||||
}
|
||||
|
||||
// Uints32 adds the field key with i as a []uint32 to the logger context.
|
||||
func (c Context) Uints32(key string, i []uint32) Context {
|
||||
c.l.context = json.AppendUints32(json.AppendKey(c.l.context, key), i)
|
||||
return c
|
||||
}
|
||||
|
||||
// Uint64 adds the field key with i as a uint64 to the logger context.
|
||||
func (c Context) Uint64(key string, i uint64) Context {
|
||||
c.l.context = appendUint64(c.l.context, key, i)
|
||||
c.l.context = json.AppendUint64(json.AppendKey(c.l.context, key), i)
|
||||
return c
|
||||
}
|
||||
|
||||
// Uints64 adds the field key with i as a []uint64 to the logger context.
|
||||
func (c Context) Uints64(key string, i []uint64) Context {
|
||||
c.l.context = json.AppendUints64(json.AppendKey(c.l.context, key), i)
|
||||
return c
|
||||
}
|
||||
|
||||
// Float32 adds the field key with f as a float32 to the logger context.
|
||||
func (c Context) Float32(key string, f float32) Context {
|
||||
c.l.context = appendFloat32(c.l.context, key, f)
|
||||
c.l.context = json.AppendFloat32(json.AppendKey(c.l.context, key), f)
|
||||
return c
|
||||
}
|
||||
|
||||
// Floats32 adds the field key with f as a []float32 to the logger context.
|
||||
func (c Context) Floats32(key string, f []float32) Context {
|
||||
c.l.context = json.AppendFloats32(json.AppendKey(c.l.context, key), f)
|
||||
return c
|
||||
}
|
||||
|
||||
// Float64 adds the field key with f as a float64 to the logger context.
|
||||
func (c Context) Float64(key string, f float64) Context {
|
||||
c.l.context = appendFloat64(c.l.context, key, f)
|
||||
c.l.context = json.AppendFloat64(json.AppendKey(c.l.context, key), f)
|
||||
return c
|
||||
}
|
||||
|
||||
// Floats64 adds the field key with f as a []float64 to the logger context.
|
||||
func (c Context) Floats64(key string, f []float64) Context {
|
||||
c.l.context = json.AppendFloats64(json.AppendKey(c.l.context, key), f)
|
||||
return c
|
||||
}
|
||||
|
||||
@@ -130,18 +271,30 @@ func (c Context) Timestamp() Context {
|
||||
|
||||
// Time adds the field key with t formated as string using zerolog.TimeFieldFormat.
|
||||
func (c Context) Time(key string, t time.Time) Context {
|
||||
c.l.context = appendTime(c.l.context, key, t)
|
||||
c.l.context = json.AppendTime(json.AppendKey(c.l.context, key), t, TimeFieldFormat)
|
||||
return c
|
||||
}
|
||||
|
||||
// Times adds the field key with t formated as string using zerolog.TimeFieldFormat.
|
||||
func (c Context) Times(key string, t []time.Time) Context {
|
||||
c.l.context = json.AppendTimes(json.AppendKey(c.l.context, key), t, TimeFieldFormat)
|
||||
return c
|
||||
}
|
||||
|
||||
// Dur adds the fields key with d divided by unit and stored as a float.
|
||||
func (c Context) Dur(key string, d time.Duration) Context {
|
||||
c.l.context = appendDuration(c.l.context, key, d)
|
||||
c.l.context = json.AppendDuration(json.AppendKey(c.l.context, key), d, DurationFieldUnit, DurationFieldInteger)
|
||||
return c
|
||||
}
|
||||
|
||||
// Durs adds the fields key with d divided by unit and stored as a float.
|
||||
func (c Context) Durs(key string, d []time.Duration) Context {
|
||||
c.l.context = json.AppendDurations(json.AppendKey(c.l.context, key), d, DurationFieldUnit, DurationFieldInteger)
|
||||
return c
|
||||
}
|
||||
|
||||
// Interface adds the field key with obj marshaled using reflection.
|
||||
func (c Context) Interface(key string, i interface{}) Context {
|
||||
c.l.context = appendInterface(c.l.context, key, i)
|
||||
c.l.context = json.AppendInterface(json.AppendKey(c.l.context, key), i)
|
||||
return c
|
||||
}
|
||||
|
||||
@@ -5,25 +5,42 @@ import (
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
var disabledLogger = New(ioutil.Discard).Level(Disabled)
|
||||
var disabledLogger *Logger
|
||||
|
||||
func init() {
|
||||
l := New(ioutil.Discard).Level(Disabled)
|
||||
disabledLogger = &l
|
||||
}
|
||||
|
||||
type ctxKey struct{}
|
||||
|
||||
// WithContext returns a copy of ctx with l associated.
|
||||
// WithContext returns a copy of ctx with l associated. If an instance of Logger
|
||||
// is already in the context, the pointer to this logger is updated with l.
|
||||
//
|
||||
// For instance, to add a field to an existing logger in the context, use this
|
||||
// notation:
|
||||
//
|
||||
// ctx := r.Context()
|
||||
// l := zerolog.Ctx(ctx)
|
||||
// ctx = l.With().Str("foo", "bar").WithContext(ctx)
|
||||
func (l Logger) WithContext(ctx context.Context) context.Context {
|
||||
if lp, ok := ctx.Value(ctxKey{}).(*Logger); ok {
|
||||
// Update existing pointer.
|
||||
*lp = l
|
||||
return ctx
|
||||
}
|
||||
if l.level == Disabled {
|
||||
// Do not store disabled logger.
|
||||
return ctx
|
||||
}
|
||||
return context.WithValue(ctx, ctxKey{}, &l)
|
||||
}
|
||||
|
||||
// Ctx returns the Logger associated with the ctx. If no logger
|
||||
// is associated, a disabled logger is returned.
|
||||
func Ctx(ctx context.Context) Logger {
|
||||
func Ctx(ctx context.Context) *Logger {
|
||||
if l, ok := ctx.Value(ctxKey{}).(*Logger); ok {
|
||||
return *l
|
||||
return l
|
||||
}
|
||||
return disabledLogger
|
||||
}
|
||||
|
||||
+20
-3
@@ -11,7 +11,7 @@ func TestCtx(t *testing.T) {
|
||||
log := New(ioutil.Discard)
|
||||
ctx := log.WithContext(context.Background())
|
||||
log2 := Ctx(ctx)
|
||||
if !reflect.DeepEqual(log, log2) {
|
||||
if !reflect.DeepEqual(log, *log2) {
|
||||
t.Error("Ctx did not return the expected logger")
|
||||
}
|
||||
|
||||
@@ -19,12 +19,29 @@ func TestCtx(t *testing.T) {
|
||||
log = log.Level(InfoLevel)
|
||||
ctx = log.WithContext(ctx)
|
||||
log2 = Ctx(ctx)
|
||||
if !reflect.DeepEqual(log, log2) {
|
||||
if !reflect.DeepEqual(log, *log2) {
|
||||
t.Error("Ctx did not return the expected logger")
|
||||
}
|
||||
|
||||
log2 = Ctx(context.Background())
|
||||
if !reflect.DeepEqual(log2, disabledLogger) {
|
||||
if log2 != disabledLogger {
|
||||
t.Error("Ctx did not return the expected logger")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCtxDisabled(t *testing.T) {
|
||||
ctx := disabledLogger.WithContext(context.Background())
|
||||
if ctx != context.Background() {
|
||||
t.Error("WithContext stored a disabled logger")
|
||||
}
|
||||
|
||||
ctx = New(ioutil.Discard).WithContext(ctx)
|
||||
if reflect.DeepEqual(Ctx(ctx), disabledLogger) {
|
||||
t.Error("WithContext did not store logger")
|
||||
}
|
||||
|
||||
ctx = disabledLogger.WithContext(ctx)
|
||||
if !reflect.DeepEqual(Ctx(ctx), disabledLogger) {
|
||||
t.Error("WithContext did not update logger pointer with disabled logger")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ import (
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog/internal/json"
|
||||
)
|
||||
|
||||
var eventPool = &sync.Pool{
|
||||
@@ -19,11 +21,23 @@ var eventPool = &sync.Pool{
|
||||
// Event represents a log event. It is instanced by one of the level method of
|
||||
// Logger and finalized by the Msg or Msgf method.
|
||||
type Event struct {
|
||||
buf []byte
|
||||
w LevelWriter
|
||||
level Level
|
||||
enabled bool
|
||||
done func(msg string)
|
||||
buf []byte
|
||||
w LevelWriter
|
||||
level Level
|
||||
done func(msg string)
|
||||
h []Hook
|
||||
}
|
||||
|
||||
// LogObjectMarshaler provides a strongly-typed and encoding-agnostic interface
|
||||
// to be implemented by types used with Event/Context's Object methods.
|
||||
type LogObjectMarshaler interface {
|
||||
MarshalZerologObject(e *Event)
|
||||
}
|
||||
|
||||
// LogArrayMarshaler provides a strongly-typed and encoding-agnostic interface
|
||||
// to be implemented by types used with Event/Context's Array methods.
|
||||
type LogArrayMarshaler interface {
|
||||
MarshalZerologArray(a *Array)
|
||||
}
|
||||
|
||||
func newEvent(w LevelWriter, level Level, enabled bool) *Event {
|
||||
@@ -32,15 +46,15 @@ func newEvent(w LevelWriter, level Level, enabled bool) *Event {
|
||||
}
|
||||
e := eventPool.Get().(*Event)
|
||||
e.buf = e.buf[:1]
|
||||
e.h = e.h[:0]
|
||||
e.buf[0] = '{'
|
||||
e.w = w
|
||||
e.level = level
|
||||
e.enabled = true
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *Event) write() (err error) {
|
||||
if !e.enabled {
|
||||
if e == nil {
|
||||
return nil
|
||||
}
|
||||
e.buf = append(e.buf, '}', '\n')
|
||||
@@ -52,7 +66,7 @@ func (e *Event) write() (err error) {
|
||||
// Enabled return false if the *Event is going to be filtered out by
|
||||
// log level or sampling.
|
||||
func (e *Event) Enabled() bool {
|
||||
return e.enabled
|
||||
return e != nil
|
||||
}
|
||||
|
||||
// Msg sends the *Event with msg added as the message field if not empty.
|
||||
@@ -60,11 +74,19 @@ func (e *Event) Enabled() bool {
|
||||
// NOTICE: once this method is called, the *Event should be disposed.
|
||||
// Calling Msg twice can have unexpected result.
|
||||
func (e *Event) Msg(msg string) {
|
||||
if !e.enabled {
|
||||
if e == nil {
|
||||
return
|
||||
}
|
||||
if len(e.h) > 0 {
|
||||
e.h[0].Run(e, e.level, msg)
|
||||
if len(e.h) > 1 {
|
||||
for _, hook := range e.h[1:] {
|
||||
hook.Run(e, e.level, msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
if msg != "" {
|
||||
e.buf = appendString(e.buf, MessageFieldName, msg)
|
||||
e.buf = json.AppendString(json.AppendKey(e.buf, MessageFieldName), msg)
|
||||
}
|
||||
if e.done != nil {
|
||||
defer e.done(msg)
|
||||
@@ -79,28 +101,28 @@ func (e *Event) Msg(msg string) {
|
||||
// NOTICE: once this methid is called, the *Event should be disposed.
|
||||
// Calling Msg twice can have unexpected result.
|
||||
func (e *Event) Msgf(format string, v ...interface{}) {
|
||||
if !e.enabled {
|
||||
if e == nil {
|
||||
return
|
||||
}
|
||||
msg := fmt.Sprintf(format, v...)
|
||||
if msg != "" {
|
||||
e.buf = appendString(e.buf, MessageFieldName, msg)
|
||||
}
|
||||
if e.done != nil {
|
||||
defer e.done(msg)
|
||||
}
|
||||
if err := e.write(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "zerolog: could not write event: %v", err)
|
||||
e.Msg(fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
// Fields is a helper function to use a map to set fields using type assertion.
|
||||
func (e *Event) Fields(fields map[string]interface{}) *Event {
|
||||
if e == nil {
|
||||
return e
|
||||
}
|
||||
e.buf = appendFields(e.buf, fields)
|
||||
return e
|
||||
}
|
||||
|
||||
// Dict adds the field key with a dict to the event context.
|
||||
// Use zerolog.Dict() to create the dictionary.
|
||||
func (e *Event) Dict(key string, dict *Event) *Event {
|
||||
if !e.enabled {
|
||||
if e == nil {
|
||||
return e
|
||||
}
|
||||
e.buf = append(append(appendKey(e.buf, key), dict.buf...), '}')
|
||||
e.buf = append(append(json.AppendKey(e.buf, key), dict.buf...), '}')
|
||||
eventPool.Put(dict)
|
||||
return e
|
||||
}
|
||||
@@ -112,22 +134,98 @@ func Dict() *Event {
|
||||
return newEvent(levelWriterAdapter{ioutil.Discard}, 0, true)
|
||||
}
|
||||
|
||||
// Str adds the field key with val as a string to the *Event context.
|
||||
func (e *Event) Str(key, val string) *Event {
|
||||
if !e.enabled {
|
||||
// Array adds the field key with an array to the event context.
|
||||
// Use zerolog.Arr() to create the array or pass a type that
|
||||
// implement the LogArrayMarshaler interface.
|
||||
func (e *Event) Array(key string, arr LogArrayMarshaler) *Event {
|
||||
if e == nil {
|
||||
return e
|
||||
}
|
||||
e.buf = appendString(e.buf, key, val)
|
||||
e.buf = json.AppendKey(e.buf, key)
|
||||
var a *Array
|
||||
if aa, ok := arr.(*Array); ok {
|
||||
a = aa
|
||||
} else {
|
||||
a = Arr()
|
||||
arr.MarshalZerologArray(a)
|
||||
}
|
||||
e.buf = a.write(e.buf)
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *Event) appendObject(obj LogObjectMarshaler) {
|
||||
pos := len(e.buf)
|
||||
obj.MarshalZerologObject(e)
|
||||
if pos < len(e.buf) {
|
||||
// As MarshalZerologObject will use event API, the first field will be
|
||||
// preceded by a comma. If at least one field has been added (buf grew),
|
||||
// we replace this coma by the opening bracket.
|
||||
e.buf[pos] = '{'
|
||||
} else {
|
||||
e.buf = append(e.buf, '{')
|
||||
}
|
||||
e.buf = append(e.buf, '}')
|
||||
}
|
||||
|
||||
// Object marshals an object that implement the LogObjectMarshaler interface.
|
||||
func (e *Event) Object(key string, obj LogObjectMarshaler) *Event {
|
||||
if e == nil {
|
||||
return e
|
||||
}
|
||||
e.buf = json.AppendKey(e.buf, key)
|
||||
e.appendObject(obj)
|
||||
return e
|
||||
}
|
||||
|
||||
// Str adds the field key with val as a string to the *Event context.
|
||||
func (e *Event) Str(key, val string) *Event {
|
||||
if e == nil {
|
||||
return e
|
||||
}
|
||||
e.buf = json.AppendString(json.AppendKey(e.buf, key), val)
|
||||
return e
|
||||
}
|
||||
|
||||
// Strs adds the field key with vals as a []string to the *Event context.
|
||||
func (e *Event) Strs(key string, vals []string) *Event {
|
||||
if e == nil {
|
||||
return e
|
||||
}
|
||||
e.buf = json.AppendStrings(json.AppendKey(e.buf, key), vals)
|
||||
return e
|
||||
}
|
||||
|
||||
// Bytes adds the field key with val as a string to the *Event context.
|
||||
//
|
||||
// Runes outside of normal ASCII ranges will be hex-encoded in the resulting
|
||||
// JSON.
|
||||
func (e *Event) Bytes(key string, val []byte) *Event {
|
||||
if e == nil {
|
||||
return e
|
||||
}
|
||||
e.buf = json.AppendBytes(json.AppendKey(e.buf, key), val)
|
||||
return e
|
||||
}
|
||||
|
||||
// AnErr adds the field key with err as a string to the *Event context.
|
||||
// If err is nil, no field is added.
|
||||
func (e *Event) AnErr(key string, err error) *Event {
|
||||
if !e.enabled {
|
||||
if e == nil {
|
||||
return e
|
||||
}
|
||||
e.buf = appendErrorKey(e.buf, key, err)
|
||||
if err != nil {
|
||||
e.buf = json.AppendError(json.AppendKey(e.buf, key), err)
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
// Errs adds the field key with errs as an array of strings to the *Event context.
|
||||
// If err is nil, no field is added.
|
||||
func (e *Event) Errs(key string, errs []error) *Event {
|
||||
if e == nil {
|
||||
return e
|
||||
}
|
||||
e.buf = json.AppendErrors(json.AppendKey(e.buf, key), errs)
|
||||
return e
|
||||
}
|
||||
|
||||
@@ -135,146 +233,274 @@ func (e *Event) AnErr(key string, err error) *Event {
|
||||
// If err is nil, no field is added.
|
||||
// To customize the key name, change zerolog.ErrorFieldName.
|
||||
func (e *Event) Err(err error) *Event {
|
||||
if !e.enabled {
|
||||
if e == nil {
|
||||
return e
|
||||
}
|
||||
e.buf = appendError(e.buf, err)
|
||||
if err != nil {
|
||||
e.buf = json.AppendError(json.AppendKey(e.buf, ErrorFieldName), err)
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
// Bool adds the field key with val as a Boolean to the *Event context.
|
||||
// Bool adds the field key with val as a bool to the *Event context.
|
||||
func (e *Event) Bool(key string, b bool) *Event {
|
||||
if !e.enabled {
|
||||
if e == nil {
|
||||
return e
|
||||
}
|
||||
e.buf = appendBool(e.buf, key, b)
|
||||
e.buf = json.AppendBool(json.AppendKey(e.buf, key), b)
|
||||
return e
|
||||
}
|
||||
|
||||
// Bools adds the field key with val as a []bool to the *Event context.
|
||||
func (e *Event) Bools(key string, b []bool) *Event {
|
||||
if e == nil {
|
||||
return e
|
||||
}
|
||||
e.buf = json.AppendBools(json.AppendKey(e.buf, key), b)
|
||||
return e
|
||||
}
|
||||
|
||||
// Int adds the field key with i as a int to the *Event context.
|
||||
func (e *Event) Int(key string, i int) *Event {
|
||||
if !e.enabled {
|
||||
if e == nil {
|
||||
return e
|
||||
}
|
||||
e.buf = appendInt(e.buf, key, i)
|
||||
e.buf = json.AppendInt(json.AppendKey(e.buf, key), i)
|
||||
return e
|
||||
}
|
||||
|
||||
// Ints adds the field key with i as a []int to the *Event context.
|
||||
func (e *Event) Ints(key string, i []int) *Event {
|
||||
if e == nil {
|
||||
return e
|
||||
}
|
||||
e.buf = json.AppendInts(json.AppendKey(e.buf, key), i)
|
||||
return e
|
||||
}
|
||||
|
||||
// Int8 adds the field key with i as a int8 to the *Event context.
|
||||
func (e *Event) Int8(key string, i int8) *Event {
|
||||
if !e.enabled {
|
||||
if e == nil {
|
||||
return e
|
||||
}
|
||||
e.buf = appendInt8(e.buf, key, i)
|
||||
e.buf = json.AppendInt8(json.AppendKey(e.buf, key), i)
|
||||
return e
|
||||
}
|
||||
|
||||
// Ints8 adds the field key with i as a []int8 to the *Event context.
|
||||
func (e *Event) Ints8(key string, i []int8) *Event {
|
||||
if e == nil {
|
||||
return e
|
||||
}
|
||||
e.buf = json.AppendInts8(json.AppendKey(e.buf, key), i)
|
||||
return e
|
||||
}
|
||||
|
||||
// Int16 adds the field key with i as a int16 to the *Event context.
|
||||
func (e *Event) Int16(key string, i int16) *Event {
|
||||
if !e.enabled {
|
||||
if e == nil {
|
||||
return e
|
||||
}
|
||||
e.buf = appendInt16(e.buf, key, i)
|
||||
e.buf = json.AppendInt16(json.AppendKey(e.buf, key), i)
|
||||
return e
|
||||
}
|
||||
|
||||
// Ints16 adds the field key with i as a []int16 to the *Event context.
|
||||
func (e *Event) Ints16(key string, i []int16) *Event {
|
||||
if e == nil {
|
||||
return e
|
||||
}
|
||||
e.buf = json.AppendInts16(json.AppendKey(e.buf, key), i)
|
||||
return e
|
||||
}
|
||||
|
||||
// Int32 adds the field key with i as a int32 to the *Event context.
|
||||
func (e *Event) Int32(key string, i int32) *Event {
|
||||
if !e.enabled {
|
||||
if e == nil {
|
||||
return e
|
||||
}
|
||||
e.buf = appendInt32(e.buf, key, i)
|
||||
e.buf = json.AppendInt32(json.AppendKey(e.buf, key), i)
|
||||
return e
|
||||
}
|
||||
|
||||
// Ints32 adds the field key with i as a []int32 to the *Event context.
|
||||
func (e *Event) Ints32(key string, i []int32) *Event {
|
||||
if e == nil {
|
||||
return e
|
||||
}
|
||||
e.buf = json.AppendInts32(json.AppendKey(e.buf, key), i)
|
||||
return e
|
||||
}
|
||||
|
||||
// Int64 adds the field key with i as a int64 to the *Event context.
|
||||
func (e *Event) Int64(key string, i int64) *Event {
|
||||
if !e.enabled {
|
||||
if e == nil {
|
||||
return e
|
||||
}
|
||||
e.buf = appendInt64(e.buf, key, i)
|
||||
e.buf = json.AppendInt64(json.AppendKey(e.buf, key), i)
|
||||
return e
|
||||
}
|
||||
|
||||
// Ints64 adds the field key with i as a []int64 to the *Event context.
|
||||
func (e *Event) Ints64(key string, i []int64) *Event {
|
||||
if e == nil {
|
||||
return e
|
||||
}
|
||||
e.buf = json.AppendInts64(json.AppendKey(e.buf, key), i)
|
||||
return e
|
||||
}
|
||||
|
||||
// Uint adds the field key with i as a uint to the *Event context.
|
||||
func (e *Event) Uint(key string, i uint) *Event {
|
||||
if !e.enabled {
|
||||
if e == nil {
|
||||
return e
|
||||
}
|
||||
e.buf = appendUint(e.buf, key, i)
|
||||
e.buf = json.AppendUint(json.AppendKey(e.buf, key), i)
|
||||
return e
|
||||
}
|
||||
|
||||
// Uints adds the field key with i as a []int to the *Event context.
|
||||
func (e *Event) Uints(key string, i []uint) *Event {
|
||||
if e == nil {
|
||||
return e
|
||||
}
|
||||
e.buf = json.AppendUints(json.AppendKey(e.buf, key), i)
|
||||
return e
|
||||
}
|
||||
|
||||
// Uint8 adds the field key with i as a uint8 to the *Event context.
|
||||
func (e *Event) Uint8(key string, i uint8) *Event {
|
||||
if !e.enabled {
|
||||
if e == nil {
|
||||
return e
|
||||
}
|
||||
e.buf = appendUint8(e.buf, key, i)
|
||||
e.buf = json.AppendUint8(json.AppendKey(e.buf, key), i)
|
||||
return e
|
||||
}
|
||||
|
||||
// Uints8 adds the field key with i as a []int8 to the *Event context.
|
||||
func (e *Event) Uints8(key string, i []uint8) *Event {
|
||||
if e == nil {
|
||||
return e
|
||||
}
|
||||
e.buf = json.AppendUints8(json.AppendKey(e.buf, key), i)
|
||||
return e
|
||||
}
|
||||
|
||||
// Uint16 adds the field key with i as a uint16 to the *Event context.
|
||||
func (e *Event) Uint16(key string, i uint16) *Event {
|
||||
if !e.enabled {
|
||||
if e == nil {
|
||||
return e
|
||||
}
|
||||
e.buf = appendUint16(e.buf, key, i)
|
||||
e.buf = json.AppendUint16(json.AppendKey(e.buf, key), i)
|
||||
return e
|
||||
}
|
||||
|
||||
// Uints16 adds the field key with i as a []int16 to the *Event context.
|
||||
func (e *Event) Uints16(key string, i []uint16) *Event {
|
||||
if e == nil {
|
||||
return e
|
||||
}
|
||||
e.buf = json.AppendUints16(json.AppendKey(e.buf, key), i)
|
||||
return e
|
||||
}
|
||||
|
||||
// Uint32 adds the field key with i as a uint32 to the *Event context.
|
||||
func (e *Event) Uint32(key string, i uint32) *Event {
|
||||
if !e.enabled {
|
||||
if e == nil {
|
||||
return e
|
||||
}
|
||||
e.buf = appendUint32(e.buf, key, i)
|
||||
e.buf = json.AppendUint32(json.AppendKey(e.buf, key), i)
|
||||
return e
|
||||
}
|
||||
|
||||
// Uints32 adds the field key with i as a []int32 to the *Event context.
|
||||
func (e *Event) Uints32(key string, i []uint32) *Event {
|
||||
if e == nil {
|
||||
return e
|
||||
}
|
||||
e.buf = json.AppendUints32(json.AppendKey(e.buf, key), i)
|
||||
return e
|
||||
}
|
||||
|
||||
// Uint64 adds the field key with i as a uint64 to the *Event context.
|
||||
func (e *Event) Uint64(key string, i uint64) *Event {
|
||||
if !e.enabled {
|
||||
if e == nil {
|
||||
return e
|
||||
}
|
||||
e.buf = appendUint64(e.buf, key, i)
|
||||
e.buf = json.AppendUint64(json.AppendKey(e.buf, key), i)
|
||||
return e
|
||||
}
|
||||
|
||||
// Uints64 adds the field key with i as a []int64 to the *Event context.
|
||||
func (e *Event) Uints64(key string, i []uint64) *Event {
|
||||
if e == nil {
|
||||
return e
|
||||
}
|
||||
e.buf = json.AppendUints64(json.AppendKey(e.buf, key), i)
|
||||
return e
|
||||
}
|
||||
|
||||
// Float32 adds the field key with f as a float32 to the *Event context.
|
||||
func (e *Event) Float32(key string, f float32) *Event {
|
||||
if !e.enabled {
|
||||
if e == nil {
|
||||
return e
|
||||
}
|
||||
e.buf = appendFloat32(e.buf, key, f)
|
||||
e.buf = json.AppendFloat32(json.AppendKey(e.buf, key), f)
|
||||
return e
|
||||
}
|
||||
|
||||
// Floats32 adds the field key with f as a []float32 to the *Event context.
|
||||
func (e *Event) Floats32(key string, f []float32) *Event {
|
||||
if e == nil {
|
||||
return e
|
||||
}
|
||||
e.buf = json.AppendFloats32(json.AppendKey(e.buf, key), f)
|
||||
return e
|
||||
}
|
||||
|
||||
// Float64 adds the field key with f as a float64 to the *Event context.
|
||||
func (e *Event) Float64(key string, f float64) *Event {
|
||||
if !e.enabled {
|
||||
if e == nil {
|
||||
return e
|
||||
}
|
||||
e.buf = appendFloat64(e.buf, key, f)
|
||||
e.buf = json.AppendFloat64(json.AppendKey(e.buf, key), f)
|
||||
return e
|
||||
}
|
||||
|
||||
// Floats64 adds the field key with f as a []float64 to the *Event context.
|
||||
func (e *Event) Floats64(key string, f []float64) *Event {
|
||||
if e == nil {
|
||||
return e
|
||||
}
|
||||
e.buf = json.AppendFloats64(json.AppendKey(e.buf, key), f)
|
||||
return e
|
||||
}
|
||||
|
||||
// Timestamp adds the current local time as UNIX timestamp to the *Event context with the "time" key.
|
||||
// To customize the key name, change zerolog.TimestampFieldName.
|
||||
func (e *Event) Timestamp() *Event {
|
||||
if !e.enabled {
|
||||
if e == nil {
|
||||
return e
|
||||
}
|
||||
e.buf = appendTimestamp(e.buf)
|
||||
e.buf = json.AppendTime(json.AppendKey(e.buf, TimestampFieldName), TimestampFunc(), TimeFieldFormat)
|
||||
return e
|
||||
}
|
||||
|
||||
// Time adds the field key with t formated as string using zerolog.TimeFieldFormat.
|
||||
func (e *Event) Time(key string, t time.Time) *Event {
|
||||
if !e.enabled {
|
||||
if e == nil {
|
||||
return e
|
||||
}
|
||||
e.buf = appendTime(e.buf, key, t)
|
||||
e.buf = json.AppendTime(json.AppendKey(e.buf, key), t, TimeFieldFormat)
|
||||
return e
|
||||
}
|
||||
|
||||
// Times adds the field key with t formated as string using zerolog.TimeFieldFormat.
|
||||
func (e *Event) Times(key string, t []time.Time) *Event {
|
||||
if e == nil {
|
||||
return e
|
||||
}
|
||||
e.buf = json.AppendTimes(json.AppendKey(e.buf, key), t, TimeFieldFormat)
|
||||
return e
|
||||
}
|
||||
|
||||
@@ -282,10 +508,21 @@ func (e *Event) Time(key string, t time.Time) *Event {
|
||||
// If zerolog.DurationFieldInteger is true, durations are rendered as integer
|
||||
// instead of float.
|
||||
func (e *Event) Dur(key string, d time.Duration) *Event {
|
||||
if !e.enabled {
|
||||
if e == nil {
|
||||
return e
|
||||
}
|
||||
e.buf = appendDuration(e.buf, key, d)
|
||||
e.buf = json.AppendDuration(json.AppendKey(e.buf, key), d, DurationFieldUnit, DurationFieldInteger)
|
||||
return e
|
||||
}
|
||||
|
||||
// Durs adds the field key with duration d stored as zerolog.DurationFieldUnit.
|
||||
// If zerolog.DurationFieldInteger is true, durations are rendered as integer
|
||||
// instead of float.
|
||||
func (e *Event) Durs(key string, d []time.Duration) *Event {
|
||||
if e == nil {
|
||||
return e
|
||||
}
|
||||
e.buf = json.AppendDurations(json.AppendKey(e.buf, key), d, DurationFieldUnit, DurationFieldInteger)
|
||||
return e
|
||||
}
|
||||
|
||||
@@ -293,22 +530,25 @@ func (e *Event) Dur(key string, d time.Duration) *Event {
|
||||
// If time t is not greater than start, duration will be 0.
|
||||
// Duration format follows the same principle as Dur().
|
||||
func (e *Event) TimeDiff(key string, t time.Time, start time.Time) *Event {
|
||||
if !e.enabled {
|
||||
if e == nil {
|
||||
return e
|
||||
}
|
||||
var d time.Duration
|
||||
if t.After(start) {
|
||||
d = t.Sub(start)
|
||||
}
|
||||
e.buf = appendDuration(e.buf, key, d)
|
||||
e.buf = json.AppendDuration(json.AppendKey(e.buf, key), d, DurationFieldUnit, DurationFieldInteger)
|
||||
return e
|
||||
}
|
||||
|
||||
// Interface adds the field key with i marshaled using reflection.
|
||||
func (e *Event) Interface(key string, i interface{}) *Event {
|
||||
if !e.enabled {
|
||||
if e == nil {
|
||||
return e
|
||||
}
|
||||
e.buf = appendInterface(e.buf, key, i)
|
||||
if obj, ok := i.(LogObjectMarshaler); ok {
|
||||
return e.Object(key, obj)
|
||||
}
|
||||
e.buf = json.AppendInterface(json.AppendKey(e.buf, key), i)
|
||||
return e
|
||||
}
|
||||
|
||||
@@ -1,109 +0,0 @@
|
||||
package zerolog
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
func appendKey(dst []byte, key string) []byte {
|
||||
if len(dst) > 1 {
|
||||
dst = append(dst, ',')
|
||||
}
|
||||
dst = appendJSONString(dst, key)
|
||||
return append(dst, ':')
|
||||
}
|
||||
|
||||
func appendString(dst []byte, key, val string) []byte {
|
||||
return appendJSONString(appendKey(dst, key), val)
|
||||
}
|
||||
|
||||
func appendErrorKey(dst []byte, key string, err error) []byte {
|
||||
if err == nil {
|
||||
return dst
|
||||
}
|
||||
return appendJSONString(appendKey(dst, key), err.Error())
|
||||
}
|
||||
|
||||
func appendError(dst []byte, err error) []byte {
|
||||
return appendErrorKey(dst, ErrorFieldName, err)
|
||||
}
|
||||
|
||||
func appendBool(dst []byte, key string, val bool) []byte {
|
||||
return strconv.AppendBool(appendKey(dst, key), val)
|
||||
}
|
||||
|
||||
func appendInt(dst []byte, key string, val int) []byte {
|
||||
return strconv.AppendInt(appendKey(dst, key), int64(val), 10)
|
||||
}
|
||||
|
||||
func appendInt8(dst []byte, key string, val int8) []byte {
|
||||
return strconv.AppendInt(appendKey(dst, key), int64(val), 10)
|
||||
}
|
||||
|
||||
func appendInt16(dst []byte, key string, val int16) []byte {
|
||||
return strconv.AppendInt(appendKey(dst, key), int64(val), 10)
|
||||
}
|
||||
|
||||
func appendInt32(dst []byte, key string, val int32) []byte {
|
||||
return strconv.AppendInt(appendKey(dst, key), int64(val), 10)
|
||||
}
|
||||
|
||||
func appendInt64(dst []byte, key string, val int64) []byte {
|
||||
return strconv.AppendInt(appendKey(dst, key), int64(val), 10)
|
||||
}
|
||||
|
||||
func appendUint(dst []byte, key string, val uint) []byte {
|
||||
return strconv.AppendUint(appendKey(dst, key), uint64(val), 10)
|
||||
}
|
||||
|
||||
func appendUint8(dst []byte, key string, val uint8) []byte {
|
||||
return strconv.AppendUint(appendKey(dst, key), uint64(val), 10)
|
||||
}
|
||||
|
||||
func appendUint16(dst []byte, key string, val uint16) []byte {
|
||||
return strconv.AppendUint(appendKey(dst, key), uint64(val), 10)
|
||||
}
|
||||
|
||||
func appendUint32(dst []byte, key string, val uint32) []byte {
|
||||
return strconv.AppendUint(appendKey(dst, key), uint64(val), 10)
|
||||
}
|
||||
|
||||
func appendUint64(dst []byte, key string, val uint64) []byte {
|
||||
return strconv.AppendUint(appendKey(dst, key), uint64(val), 10)
|
||||
}
|
||||
|
||||
func appendFloat32(dst []byte, key string, val float32) []byte {
|
||||
return strconv.AppendFloat(appendKey(dst, key), float64(val), 'f', -1, 32)
|
||||
}
|
||||
|
||||
func appendFloat64(dst []byte, key string, val float64) []byte {
|
||||
return strconv.AppendFloat(appendKey(dst, key), float64(val), 'f', -1, 32)
|
||||
}
|
||||
|
||||
func appendTime(dst []byte, key string, t time.Time) []byte {
|
||||
if TimeFieldFormat == "" {
|
||||
return appendInt64(dst, key, t.Unix())
|
||||
}
|
||||
return append(t.AppendFormat(append(appendKey(dst, key), '"'), TimeFieldFormat), '"')
|
||||
}
|
||||
|
||||
func appendTimestamp(dst []byte) []byte {
|
||||
return appendTime(dst, TimestampFieldName, TimestampFunc())
|
||||
}
|
||||
|
||||
func appendDuration(dst []byte, key string, d time.Duration) []byte {
|
||||
if DurationFieldInteger {
|
||||
return appendInt64(dst, key, int64(d/DurationFieldUnit))
|
||||
}
|
||||
return appendFloat64(dst, key, float64(d)/float64(DurationFieldUnit))
|
||||
}
|
||||
|
||||
func appendInterface(dst []byte, key string, i interface{}) []byte {
|
||||
marshaled, err := json.Marshal(i)
|
||||
if err != nil {
|
||||
return appendString(dst, key, fmt.Sprintf("marshaling error: %v", err))
|
||||
}
|
||||
return append(appendKey(dst, key), marshaled...)
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package zerolog
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog/internal/json"
|
||||
)
|
||||
|
||||
func appendFields(dst []byte, fields map[string]interface{}) []byte {
|
||||
keys := make([]string, 0, len(fields))
|
||||
for key := range fields {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
for _, key := range keys {
|
||||
dst = json.AppendKey(dst, key)
|
||||
switch val := fields[key].(type) {
|
||||
case string:
|
||||
dst = json.AppendString(dst, val)
|
||||
case []byte:
|
||||
dst = json.AppendBytes(dst, val)
|
||||
case error:
|
||||
dst = json.AppendError(dst, val)
|
||||
case []error:
|
||||
dst = json.AppendErrors(dst, val)
|
||||
case bool:
|
||||
dst = json.AppendBool(dst, val)
|
||||
case int:
|
||||
dst = json.AppendInt(dst, val)
|
||||
case int8:
|
||||
dst = json.AppendInt8(dst, val)
|
||||
case int16:
|
||||
dst = json.AppendInt16(dst, val)
|
||||
case int32:
|
||||
dst = json.AppendInt32(dst, val)
|
||||
case int64:
|
||||
dst = json.AppendInt64(dst, val)
|
||||
case uint:
|
||||
dst = json.AppendUint(dst, val)
|
||||
case uint8:
|
||||
dst = json.AppendUint8(dst, val)
|
||||
case uint16:
|
||||
dst = json.AppendUint16(dst, val)
|
||||
case uint32:
|
||||
dst = json.AppendUint32(dst, val)
|
||||
case uint64:
|
||||
dst = json.AppendUint64(dst, val)
|
||||
case float32:
|
||||
dst = json.AppendFloat32(dst, val)
|
||||
case float64:
|
||||
dst = json.AppendFloat64(dst, val)
|
||||
case time.Time:
|
||||
dst = json.AppendTime(dst, val, TimeFieldFormat)
|
||||
case time.Duration:
|
||||
dst = json.AppendDuration(dst, val, DurationFieldUnit, DurationFieldInteger)
|
||||
case []string:
|
||||
dst = json.AppendStrings(dst, val)
|
||||
case []bool:
|
||||
dst = json.AppendBools(dst, val)
|
||||
case []int:
|
||||
dst = json.AppendInts(dst, val)
|
||||
case []int8:
|
||||
dst = json.AppendInts8(dst, val)
|
||||
case []int16:
|
||||
dst = json.AppendInts16(dst, val)
|
||||
case []int32:
|
||||
dst = json.AppendInts32(dst, val)
|
||||
case []int64:
|
||||
dst = json.AppendInts64(dst, val)
|
||||
case []uint:
|
||||
dst = json.AppendUints(dst, val)
|
||||
// case []uint8:
|
||||
// dst = appendUints8(dst, val)
|
||||
case []uint16:
|
||||
dst = json.AppendUints16(dst, val)
|
||||
case []uint32:
|
||||
dst = json.AppendUints32(dst, val)
|
||||
case []uint64:
|
||||
dst = json.AppendUints64(dst, val)
|
||||
case []float32:
|
||||
dst = json.AppendFloats32(dst, val)
|
||||
case []float64:
|
||||
dst = json.AppendFloats64(dst, val)
|
||||
case []time.Time:
|
||||
dst = json.AppendTimes(dst, val, TimeFieldFormat)
|
||||
case []time.Duration:
|
||||
dst = json.AppendDurations(dst, val, DurationFieldUnit, DurationFieldInteger)
|
||||
case nil:
|
||||
dst = append(dst, "null"...)
|
||||
default:
|
||||
dst = json.AppendInterface(dst, val)
|
||||
}
|
||||
}
|
||||
return dst
|
||||
}
|
||||
+1
-4
@@ -16,9 +16,6 @@ var (
|
||||
// ErrorFieldName is the field name used for error fields.
|
||||
ErrorFieldName = "error"
|
||||
|
||||
// SampleFieldName is the name of the field used to report sampling.
|
||||
SampleFieldName = "sample"
|
||||
|
||||
// TimeFieldFormat defines the time format of the Time field type.
|
||||
// If set to an empty string, the time is formatted as an UNIX timestamp
|
||||
// as integer.
|
||||
@@ -63,5 +60,5 @@ func DisableSampling(v bool) {
|
||||
}
|
||||
|
||||
func samplingDisabled() bool {
|
||||
return atomic.LoadUint32(gLevel) == 1
|
||||
return atomic.LoadUint32(disableSampling) == 1
|
||||
}
|
||||
|
||||
+43
-18
@@ -5,15 +5,17 @@ import (
|
||||
"context"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/rs/xid"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/zenazn/goji/web/mutil"
|
||||
)
|
||||
|
||||
// FromRequest gets the logger in the request's context.
|
||||
// This is a shortcut for log.Ctx(r.Context())
|
||||
func FromRequest(r *http.Request) zerolog.Logger {
|
||||
func FromRequest(r *http.Request) *zerolog.Logger {
|
||||
return log.Ctx(r.Context())
|
||||
}
|
||||
|
||||
@@ -21,7 +23,10 @@ func FromRequest(r *http.Request) zerolog.Logger {
|
||||
func NewHandler(log zerolog.Logger) func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
r = r.WithContext(log.WithContext(r.Context()))
|
||||
// Create a copy of the logger (including internal context slice)
|
||||
// to prevent data race when using UpdateContext.
|
||||
l := log.With().Logger()
|
||||
r = r.WithContext(l.WithContext(r.Context()))
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
@@ -33,8 +38,9 @@ func URLHandler(fieldKey string) func(next http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
log := zerolog.Ctx(r.Context())
|
||||
log = log.With().Str(fieldKey, r.URL.String()).Logger()
|
||||
r = r.WithContext(log.WithContext(r.Context()))
|
||||
log.UpdateContext(func(c zerolog.Context) zerolog.Context {
|
||||
return c.Str(fieldKey, r.URL.String())
|
||||
})
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
@@ -46,8 +52,9 @@ func MethodHandler(fieldKey string) func(next http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
log := zerolog.Ctx(r.Context())
|
||||
log = log.With().Str(fieldKey, r.Method).Logger()
|
||||
r = r.WithContext(log.WithContext(r.Context()))
|
||||
log.UpdateContext(func(c zerolog.Context) zerolog.Context {
|
||||
return c.Str(fieldKey, r.Method)
|
||||
})
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
@@ -59,8 +66,9 @@ func RequestHandler(fieldKey string) func(next http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
log := zerolog.Ctx(r.Context())
|
||||
log = log.With().Str(fieldKey, r.Method+" "+r.URL.String()).Logger()
|
||||
r = r.WithContext(log.WithContext(r.Context()))
|
||||
log.UpdateContext(func(c zerolog.Context) zerolog.Context {
|
||||
return c.Str(fieldKey, r.Method+" "+r.URL.String())
|
||||
})
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
@@ -73,8 +81,9 @@ func RemoteAddrHandler(fieldKey string) func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if host, _, err := net.SplitHostPort(r.RemoteAddr); err == nil {
|
||||
log := zerolog.Ctx(r.Context())
|
||||
log = log.With().Str(fieldKey, host).Logger()
|
||||
r = r.WithContext(log.WithContext(r.Context()))
|
||||
log.UpdateContext(func(c zerolog.Context) zerolog.Context {
|
||||
return c.Str(fieldKey, host)
|
||||
})
|
||||
}
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
@@ -88,8 +97,9 @@ func UserAgentHandler(fieldKey string) func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if ua := r.Header.Get("User-Agent"); ua != "" {
|
||||
log := zerolog.Ctx(r.Context())
|
||||
log = log.With().Str(fieldKey, ua).Logger()
|
||||
r = r.WithContext(log.WithContext(r.Context()))
|
||||
log.UpdateContext(func(c zerolog.Context) zerolog.Context {
|
||||
return c.Str(fieldKey, ua)
|
||||
})
|
||||
}
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
@@ -103,8 +113,9 @@ func RefererHandler(fieldKey string) func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if ref := r.Header.Get("Referer"); ref != "" {
|
||||
log := zerolog.Ctx(r.Context())
|
||||
log = log.With().Str(fieldKey, ref).Logger()
|
||||
r = r.WithContext(log.WithContext(r.Context()))
|
||||
log.UpdateContext(func(c zerolog.Context) zerolog.Context {
|
||||
return c.Str(fieldKey, ref)
|
||||
})
|
||||
}
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
@@ -134,16 +145,18 @@ func IDFromRequest(r *http.Request) (id xid.ID, ok bool) {
|
||||
func RequestIDHandler(fieldKey, headerName string) func(next http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
id, ok := IDFromRequest(r)
|
||||
if !ok {
|
||||
id = xid.New()
|
||||
ctx := context.WithValue(r.Context(), idKey{}, id)
|
||||
ctx = context.WithValue(ctx, idKey{}, id)
|
||||
r = r.WithContext(ctx)
|
||||
}
|
||||
if fieldKey != "" {
|
||||
log := zerolog.Ctx(r.Context())
|
||||
log = log.With().Str(fieldKey, id.String()).Logger()
|
||||
r = r.WithContext(log.WithContext(r.Context()))
|
||||
log := zerolog.Ctx(ctx)
|
||||
log.UpdateContext(func(c zerolog.Context) zerolog.Context {
|
||||
return c.Str(fieldKey, id.String())
|
||||
})
|
||||
}
|
||||
if headerName != "" {
|
||||
w.Header().Set(headerName, id.String())
|
||||
@@ -152,3 +165,15 @@ func RequestIDHandler(fieldKey, headerName string) func(next http.Handler) http.
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// AccessHandler returns a handler that call f after each request.
|
||||
func AccessHandler(f func(r *http.Request, status, size int, duration time.Duration)) func(next http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
start := time.Now()
|
||||
lw := mutil.WrapWriter(w)
|
||||
next.ServeHTTP(lw, r)
|
||||
f(r, lw.Status(), lw.BytesWritten(), time.Since(start))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
+86
-22
@@ -5,6 +5,7 @@ package hlog
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
@@ -23,7 +24,7 @@ func TestNewHandler(t *testing.T) {
|
||||
lh := NewHandler(log)
|
||||
h := lh(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
l := FromRequest(r)
|
||||
if !reflect.DeepEqual(l, log) {
|
||||
if !reflect.DeepEqual(*l, log) {
|
||||
t.Fail()
|
||||
}
|
||||
}))
|
||||
@@ -38,12 +39,12 @@ func TestURLHandler(t *testing.T) {
|
||||
h := URLHandler("url")(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
l := FromRequest(r)
|
||||
l.Log().Msg("")
|
||||
if want, got := `{"url":"/path?foo=bar"}`+"\n", out.String(); want != got {
|
||||
t.Errorf("Invalid log output, got: %s, want: %s", got, want)
|
||||
}
|
||||
}))
|
||||
h = NewHandler(zerolog.New(out))(h)
|
||||
h.ServeHTTP(nil, r)
|
||||
if want, got := `{"url":"/path?foo=bar"}`+"\n", out.String(); want != got {
|
||||
t.Errorf("Invalid log output, got: %s, want: %s", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMethodHandler(t *testing.T) {
|
||||
@@ -54,12 +55,12 @@ func TestMethodHandler(t *testing.T) {
|
||||
h := MethodHandler("method")(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
l := FromRequest(r)
|
||||
l.Log().Msg("")
|
||||
if want, got := `{"method":"POST"}`+"\n", out.String(); want != got {
|
||||
t.Errorf("Invalid log output, got: %s, want: %s", got, want)
|
||||
}
|
||||
}))
|
||||
h = NewHandler(zerolog.New(out))(h)
|
||||
h.ServeHTTP(nil, r)
|
||||
if want, got := `{"method":"POST"}`+"\n", out.String(); want != got {
|
||||
t.Errorf("Invalid log output, got: %s, want: %s", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequestHandler(t *testing.T) {
|
||||
@@ -71,12 +72,12 @@ func TestRequestHandler(t *testing.T) {
|
||||
h := RequestHandler("request")(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
l := FromRequest(r)
|
||||
l.Log().Msg("")
|
||||
if want, got := `{"request":"POST /path?foo=bar"}`+"\n", out.String(); want != got {
|
||||
t.Errorf("Invalid log output, got: %s, want: %s", got, want)
|
||||
}
|
||||
}))
|
||||
h = NewHandler(zerolog.New(out))(h)
|
||||
h.ServeHTTP(nil, r)
|
||||
if want, got := `{"request":"POST /path?foo=bar"}`+"\n", out.String(); want != got {
|
||||
t.Errorf("Invalid log output, got: %s, want: %s", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoteAddrHandler(t *testing.T) {
|
||||
@@ -87,12 +88,12 @@ func TestRemoteAddrHandler(t *testing.T) {
|
||||
h := RemoteAddrHandler("ip")(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
l := FromRequest(r)
|
||||
l.Log().Msg("")
|
||||
if want, got := `{"ip":"1.2.3.4"}`+"\n", out.String(); want != got {
|
||||
t.Errorf("Invalid log output, got: %s, want: %s", got, want)
|
||||
}
|
||||
}))
|
||||
h = NewHandler(zerolog.New(out))(h)
|
||||
h.ServeHTTP(nil, r)
|
||||
if want, got := `{"ip":"1.2.3.4"}`+"\n", out.String(); want != got {
|
||||
t.Errorf("Invalid log output, got: %s, want: %s", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoteAddrHandlerIPv6(t *testing.T) {
|
||||
@@ -103,12 +104,12 @@ func TestRemoteAddrHandlerIPv6(t *testing.T) {
|
||||
h := RemoteAddrHandler("ip")(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
l := FromRequest(r)
|
||||
l.Log().Msg("")
|
||||
if want, got := `{"ip":"2001:db8:a0b:12f0::1"}`+"\n", out.String(); want != got {
|
||||
t.Errorf("Invalid log output, got: %s, want: %s", got, want)
|
||||
}
|
||||
}))
|
||||
h = NewHandler(zerolog.New(out))(h)
|
||||
h.ServeHTTP(nil, r)
|
||||
if want, got := `{"ip":"2001:db8:a0b:12f0::1"}`+"\n", out.String(); want != got {
|
||||
t.Errorf("Invalid log output, got: %s, want: %s", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUserAgentHandler(t *testing.T) {
|
||||
@@ -121,12 +122,12 @@ func TestUserAgentHandler(t *testing.T) {
|
||||
h := UserAgentHandler("ua")(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
l := FromRequest(r)
|
||||
l.Log().Msg("")
|
||||
if want, got := `{"ua":"some user agent string"}`+"\n", out.String(); want != got {
|
||||
t.Errorf("Invalid log output, got: %s, want: %s", got, want)
|
||||
}
|
||||
}))
|
||||
h = NewHandler(zerolog.New(out))(h)
|
||||
h.ServeHTTP(nil, r)
|
||||
if want, got := `{"ua":"some user agent string"}`+"\n", out.String(); want != got {
|
||||
t.Errorf("Invalid log output, got: %s, want: %s", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRefererHandler(t *testing.T) {
|
||||
@@ -139,12 +140,12 @@ func TestRefererHandler(t *testing.T) {
|
||||
h := RefererHandler("referer")(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
l := FromRequest(r)
|
||||
l.Log().Msg("")
|
||||
if want, got := `{"referer":"http://foo.com/bar"}`+"\n", out.String(); want != got {
|
||||
t.Errorf("Invalid log output, got: %s, want: %s", got, want)
|
||||
}
|
||||
}))
|
||||
h = NewHandler(zerolog.New(out))(h)
|
||||
h.ServeHTTP(nil, r)
|
||||
if want, got := `{"referer":"http://foo.com/bar"}`+"\n", out.String(); want != got {
|
||||
t.Errorf("Invalid log output, got: %s, want: %s", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequestIDHandler(t *testing.T) {
|
||||
@@ -171,3 +172,66 @@ func TestRequestIDHandler(t *testing.T) {
|
||||
h = NewHandler(zerolog.New(out))(h)
|
||||
h.ServeHTTP(httptest.NewRecorder(), r)
|
||||
}
|
||||
|
||||
func TestCombinedHandlers(t *testing.T) {
|
||||
out := &bytes.Buffer{}
|
||||
r := &http.Request{
|
||||
Method: "POST",
|
||||
URL: &url.URL{Path: "/path", RawQuery: "foo=bar"},
|
||||
}
|
||||
h := MethodHandler("method")(RequestHandler("request")(URLHandler("url")(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
l := FromRequest(r)
|
||||
l.Log().Msg("")
|
||||
}))))
|
||||
h = NewHandler(zerolog.New(out))(h)
|
||||
h.ServeHTTP(nil, r)
|
||||
if want, got := `{"method":"POST","request":"POST /path?foo=bar","url":"/path?foo=bar"}`+"\n", out.String(); want != got {
|
||||
t.Errorf("Invalid log output, got: %s, want: %s", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkHandlers(b *testing.B) {
|
||||
r := &http.Request{
|
||||
Method: "POST",
|
||||
URL: &url.URL{Path: "/path", RawQuery: "foo=bar"},
|
||||
}
|
||||
h1 := URLHandler("url")(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
l := FromRequest(r)
|
||||
l.Log().Msg("")
|
||||
}))
|
||||
h2 := MethodHandler("method")(RequestHandler("request")(h1))
|
||||
handlers := map[string]http.Handler{
|
||||
"Single": NewHandler(zerolog.New(ioutil.Discard))(h1),
|
||||
"Combined": NewHandler(zerolog.New(ioutil.Discard))(h2),
|
||||
"SingleDisabled": NewHandler(zerolog.New(ioutil.Discard).Level(zerolog.Disabled))(h1),
|
||||
"CombinedDisabled": NewHandler(zerolog.New(ioutil.Discard).Level(zerolog.Disabled))(h2),
|
||||
}
|
||||
for name := range handlers {
|
||||
h := handlers[name]
|
||||
b.Run(name, func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
h.ServeHTTP(nil, r)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkDataRace(b *testing.B) {
|
||||
log := zerolog.New(nil).With().
|
||||
Str("foo", "bar").
|
||||
Logger()
|
||||
lh := NewHandler(log)
|
||||
h := lh(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
l := FromRequest(r)
|
||||
l.UpdateContext(func(c zerolog.Context) zerolog.Context {
|
||||
return c.Str("bar", "baz")
|
||||
})
|
||||
l.Log().Msg("")
|
||||
}))
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
h.ServeHTTP(nil, &http.Request{})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package zerolog
|
||||
|
||||
// Hook defines an interface to a log hook.
|
||||
type Hook interface {
|
||||
// Run runs the hook with the event.
|
||||
Run(e *Event, level Level, message string)
|
||||
}
|
||||
|
||||
// LevelHook applies a different hook for each level.
|
||||
type LevelHook struct {
|
||||
NoLevelHook, DebugHook, InfoHook, WarnHook, ErrorHook, FatalHook, PanicHook Hook
|
||||
}
|
||||
|
||||
// Run implements the Hook interface.
|
||||
func (h LevelHook) Run(e *Event, level Level, message string) {
|
||||
switch level {
|
||||
case DebugLevel:
|
||||
if h.DebugHook != nil {
|
||||
h.DebugHook.Run(e, level, message)
|
||||
}
|
||||
case InfoLevel:
|
||||
if h.InfoHook != nil {
|
||||
h.InfoHook.Run(e, level, message)
|
||||
}
|
||||
case WarnLevel:
|
||||
if h.WarnHook != nil {
|
||||
h.WarnHook.Run(e, level, message)
|
||||
}
|
||||
case ErrorLevel:
|
||||
if h.ErrorHook != nil {
|
||||
h.ErrorHook.Run(e, level, message)
|
||||
}
|
||||
case FatalLevel:
|
||||
if h.FatalHook != nil {
|
||||
h.FatalHook.Run(e, level, message)
|
||||
}
|
||||
case PanicLevel:
|
||||
if h.PanicHook != nil {
|
||||
h.PanicHook.Run(e, level, message)
|
||||
}
|
||||
case NoLevel:
|
||||
if h.NoLevelHook != nil {
|
||||
h.NoLevelHook.Run(e, level, message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NewLevelHook returns a new LevelHook.
|
||||
func NewLevelHook() LevelHook {
|
||||
return LevelHook{}
|
||||
}
|
||||
+236
@@ -0,0 +1,236 @@
|
||||
package zerolog
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
type LevelNameHook struct{}
|
||||
|
||||
func (h LevelNameHook) Run(e *Event, level Level, msg string) {
|
||||
levelName := level.String()
|
||||
if level == NoLevel {
|
||||
levelName = "nolevel"
|
||||
}
|
||||
e.Str("level_name", levelName)
|
||||
}
|
||||
|
||||
type SimpleHook struct{}
|
||||
|
||||
func (h SimpleHook) Run(e *Event, level Level, msg string) {
|
||||
e.Bool("has_level", level != NoLevel)
|
||||
e.Str("test", "logged")
|
||||
}
|
||||
|
||||
type CopyHook struct{}
|
||||
|
||||
func (h CopyHook) Run(e *Event, level Level, msg string) {
|
||||
hasLevel := level != NoLevel
|
||||
e.Bool("copy_has_level", hasLevel)
|
||||
if hasLevel {
|
||||
e.Str("copy_level", level.String())
|
||||
}
|
||||
e.Str("copy_msg", msg)
|
||||
}
|
||||
|
||||
type NopHook struct{}
|
||||
|
||||
func (h NopHook) Run(e *Event, level Level, msg string) {
|
||||
}
|
||||
|
||||
var (
|
||||
levelNameHook LevelNameHook
|
||||
simpleHook SimpleHook
|
||||
copyHook CopyHook
|
||||
nopHook NopHook
|
||||
)
|
||||
|
||||
func TestHook(t *testing.T) {
|
||||
t.Run("Message", func(t *testing.T) {
|
||||
out := &bytes.Buffer{}
|
||||
log := New(out).Hook(levelNameHook)
|
||||
log.Log().Msg("test message")
|
||||
if got, want := out.String(), `{"level_name":"nolevel","message":"test message"}`+"\n"; got != want {
|
||||
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
|
||||
}
|
||||
})
|
||||
t.Run("NoLevel", func(t *testing.T) {
|
||||
out := &bytes.Buffer{}
|
||||
log := New(out).Hook(levelNameHook)
|
||||
log.Log().Msg("")
|
||||
if got, want := out.String(), `{"level_name":"nolevel"}`+"\n"; got != want {
|
||||
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
|
||||
}
|
||||
})
|
||||
t.Run("Print", func(t *testing.T) {
|
||||
out := &bytes.Buffer{}
|
||||
log := New(out).Hook(levelNameHook)
|
||||
log.Print("")
|
||||
if got, want := out.String(), `{"level":"debug","level_name":"debug"}`+"\n"; got != want {
|
||||
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
|
||||
}
|
||||
})
|
||||
t.Run("Error", func(t *testing.T) {
|
||||
out := &bytes.Buffer{}
|
||||
log := New(out).Hook(levelNameHook)
|
||||
log.Error().Msg("")
|
||||
if got, want := out.String(), `{"level":"error","level_name":"error"}`+"\n"; got != want {
|
||||
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
|
||||
}
|
||||
})
|
||||
t.Run("Copy/1", func(t *testing.T) {
|
||||
out := &bytes.Buffer{}
|
||||
log := New(out).Hook(copyHook)
|
||||
log.Log().Msg("")
|
||||
if got, want := out.String(), `{"copy_has_level":false,"copy_msg":""}`+"\n"; got != want {
|
||||
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
|
||||
}
|
||||
})
|
||||
t.Run("Copy/2", func(t *testing.T) {
|
||||
out := &bytes.Buffer{}
|
||||
log := New(out).Hook(copyHook)
|
||||
log.Info().Msg("a message")
|
||||
if got, want := out.String(), `{"level":"info","copy_has_level":true,"copy_level":"info","copy_msg":"a message","message":"a message"}`+"\n"; got != want {
|
||||
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
|
||||
}
|
||||
})
|
||||
t.Run("Multi", func(t *testing.T) {
|
||||
out := &bytes.Buffer{}
|
||||
log := New(out).Hook(levelNameHook).Hook(simpleHook)
|
||||
log.Error().Msg("")
|
||||
if got, want := out.String(), `{"level":"error","level_name":"error","has_level":true,"test":"logged"}`+"\n"; got != want {
|
||||
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
|
||||
}
|
||||
})
|
||||
t.Run("Multi/Message", func(t *testing.T) {
|
||||
out := &bytes.Buffer{}
|
||||
log := New(out).Hook(levelNameHook).Hook(simpleHook)
|
||||
log.Error().Msg("a message")
|
||||
if got, want := out.String(), `{"level":"error","level_name":"error","has_level":true,"test":"logged","message":"a message"}`+"\n"; got != want {
|
||||
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
|
||||
}
|
||||
})
|
||||
t.Run("Output/single/pre", func(t *testing.T) {
|
||||
ignored := &bytes.Buffer{}
|
||||
out := &bytes.Buffer{}
|
||||
log := New(ignored).Hook(levelNameHook).Output(out)
|
||||
log.Error().Msg("")
|
||||
if got, want := out.String(), `{"level":"error","level_name":"error"}`+"\n"; got != want {
|
||||
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
|
||||
}
|
||||
})
|
||||
t.Run("Output/single/post", func(t *testing.T) {
|
||||
ignored := &bytes.Buffer{}
|
||||
out := &bytes.Buffer{}
|
||||
log := New(ignored).Output(out).Hook(levelNameHook)
|
||||
log.Error().Msg("")
|
||||
if got, want := out.String(), `{"level":"error","level_name":"error"}`+"\n"; got != want {
|
||||
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
|
||||
}
|
||||
})
|
||||
t.Run("Output/multi/pre", func(t *testing.T) {
|
||||
ignored := &bytes.Buffer{}
|
||||
out := &bytes.Buffer{}
|
||||
log := New(ignored).Hook(levelNameHook).Hook(simpleHook).Output(out)
|
||||
log.Error().Msg("")
|
||||
if got, want := out.String(), `{"level":"error","level_name":"error","has_level":true,"test":"logged"}`+"\n"; got != want {
|
||||
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
|
||||
}
|
||||
})
|
||||
t.Run("Output/multi/post", func(t *testing.T) {
|
||||
ignored := &bytes.Buffer{}
|
||||
out := &bytes.Buffer{}
|
||||
log := New(ignored).Output(out).Hook(levelNameHook).Hook(simpleHook)
|
||||
log.Error().Msg("")
|
||||
if got, want := out.String(), `{"level":"error","level_name":"error","has_level":true,"test":"logged"}`+"\n"; got != want {
|
||||
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
|
||||
}
|
||||
})
|
||||
t.Run("Output/mixed", func(t *testing.T) {
|
||||
ignored := &bytes.Buffer{}
|
||||
out := &bytes.Buffer{}
|
||||
log := New(ignored).Hook(levelNameHook).Output(out).Hook(simpleHook)
|
||||
log.Error().Msg("")
|
||||
if got, want := out.String(), `{"level":"error","level_name":"error","has_level":true,"test":"logged"}`+"\n"; got != want {
|
||||
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
|
||||
}
|
||||
})
|
||||
t.Run("With/single/pre", func(t *testing.T) {
|
||||
out := &bytes.Buffer{}
|
||||
log := New(out).Hook(levelNameHook).With().Str("with", "pre").Logger()
|
||||
log.Error().Msg("")
|
||||
if got, want := out.String(), `{"level":"error","with":"pre","level_name":"error"}`+"\n"; got != want {
|
||||
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
|
||||
}
|
||||
})
|
||||
t.Run("With/single/post", func(t *testing.T) {
|
||||
out := &bytes.Buffer{}
|
||||
log := New(out).With().Str("with", "post").Logger().Hook(levelNameHook)
|
||||
log.Error().Msg("")
|
||||
if got, want := out.String(), `{"level":"error","with":"post","level_name":"error"}`+"\n"; got != want {
|
||||
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
|
||||
}
|
||||
})
|
||||
t.Run("With/multi/pre", func(t *testing.T) {
|
||||
out := &bytes.Buffer{}
|
||||
log := New(out).Hook(levelNameHook).Hook(simpleHook).With().Str("with", "pre").Logger()
|
||||
log.Error().Msg("")
|
||||
if got, want := out.String(), `{"level":"error","with":"pre","level_name":"error","has_level":true,"test":"logged"}`+"\n"; got != want {
|
||||
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
|
||||
}
|
||||
})
|
||||
t.Run("With/multi/post", func(t *testing.T) {
|
||||
out := &bytes.Buffer{}
|
||||
log := New(out).With().Str("with", "post").Logger().Hook(levelNameHook).Hook(simpleHook)
|
||||
log.Error().Msg("")
|
||||
if got, want := out.String(), `{"level":"error","with":"post","level_name":"error","has_level":true,"test":"logged"}`+"\n"; got != want {
|
||||
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
|
||||
}
|
||||
})
|
||||
t.Run("With/mixed", func(t *testing.T) {
|
||||
out := &bytes.Buffer{}
|
||||
log := New(out).Hook(levelNameHook).With().Str("with", "mixed").Logger().Hook(simpleHook)
|
||||
log.Error().Msg("")
|
||||
if got, want := out.String(), `{"level":"error","with":"mixed","level_name":"error","has_level":true,"test":"logged"}`+"\n"; got != want {
|
||||
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
|
||||
}
|
||||
})
|
||||
t.Run("None", func(t *testing.T) {
|
||||
out := &bytes.Buffer{}
|
||||
log := New(out)
|
||||
log.Error().Msg("")
|
||||
if got, want := out.String(), `{"level":"error"}`+"\n"; got != want {
|
||||
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkHooks(b *testing.B) {
|
||||
logger := New(ioutil.Discard)
|
||||
b.ResetTimer()
|
||||
b.Run("Nop/Single", func(b *testing.B) {
|
||||
log := logger.Hook(nopHook)
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
log.Log().Msg("")
|
||||
}
|
||||
})
|
||||
})
|
||||
b.Run("Nop/Multi", func(b *testing.B) {
|
||||
log := logger.Hook(nopHook).Hook(nopHook)
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
log.Log().Msg("")
|
||||
}
|
||||
})
|
||||
})
|
||||
b.Run("Simple", func(b *testing.B) {
|
||||
log := logger.Hook(simpleHook)
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
log.Log().Msg("")
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package json
|
||||
|
||||
func AppendKey(dst []byte, key string) []byte {
|
||||
if len(dst) > 1 {
|
||||
dst = append(dst, ',')
|
||||
}
|
||||
dst = AppendString(dst, key)
|
||||
return append(dst, ':')
|
||||
}
|
||||
|
||||
func AppendError(dst []byte, err error) []byte {
|
||||
if err == nil {
|
||||
return append(dst, `null`...)
|
||||
}
|
||||
return AppendString(dst, err.Error())
|
||||
}
|
||||
|
||||
func AppendErrors(dst []byte, errs []error) []byte {
|
||||
if len(errs) == 0 {
|
||||
return append(dst, '[', ']')
|
||||
}
|
||||
dst = append(dst, '[')
|
||||
if errs[0] != nil {
|
||||
dst = AppendString(dst, errs[0].Error())
|
||||
} else {
|
||||
dst = append(dst, "null"...)
|
||||
}
|
||||
if len(errs) > 1 {
|
||||
for _, err := range errs[1:] {
|
||||
if err == nil {
|
||||
dst = append(dst, ",null"...)
|
||||
continue
|
||||
}
|
||||
dst = AppendString(append(dst, ','), err.Error())
|
||||
}
|
||||
}
|
||||
dst = append(dst, ']')
|
||||
return dst
|
||||
}
|
||||
@@ -1,10 +1,25 @@
|
||||
package zerolog
|
||||
package json
|
||||
|
||||
import "unicode/utf8"
|
||||
|
||||
const hex = "0123456789abcdef"
|
||||
|
||||
// appendJSONString encodes the input string to json and appends
|
||||
func AppendStrings(dst []byte, vals []string) []byte {
|
||||
if len(vals) == 0 {
|
||||
return append(dst, '[', ']')
|
||||
}
|
||||
dst = append(dst, '[')
|
||||
dst = AppendString(dst, vals[0])
|
||||
if len(vals) > 1 {
|
||||
for _, val := range vals[1:] {
|
||||
dst = AppendString(append(dst, ','), val)
|
||||
}
|
||||
}
|
||||
dst = append(dst, ']')
|
||||
return dst
|
||||
}
|
||||
|
||||
// AppendString encodes the input string to json and appends
|
||||
// the encoded string to the input byte slice.
|
||||
//
|
||||
// The operation loops though each byte in the string looking
|
||||
@@ -13,7 +28,7 @@ const hex = "0123456789abcdef"
|
||||
// entirety to the byte slice.
|
||||
// If we encounter a byte that does need encoding, switch up
|
||||
// the operation and perform a byte-by-byte read-encode-append.
|
||||
func appendJSONString(dst []byte, s string) []byte {
|
||||
func AppendString(dst []byte, s string) []byte {
|
||||
// Start with a double quote.
|
||||
dst = append(dst, '"')
|
||||
// Loop through each character in the string.
|
||||
@@ -24,7 +39,7 @@ func appendJSONString(dst []byte, s string) []byte {
|
||||
if s[i] < 0x20 || s[i] > 0x7e || s[i] == '\\' || s[i] == '"' {
|
||||
// We encountered a character that needs to be encoded. Switch
|
||||
// to complex version of the algorithm.
|
||||
dst = appendJSONStringComplex(dst, s, i)
|
||||
dst = appendStringComplex(dst, s, i)
|
||||
return append(dst, '"')
|
||||
}
|
||||
}
|
||||
@@ -35,10 +50,10 @@ func appendJSONString(dst []byte, s string) []byte {
|
||||
return append(dst, '"')
|
||||
}
|
||||
|
||||
// appendJSONStringComplex is used by appendJSONString to take over an in
|
||||
// appendStringComplex is used by appendString to take over an in
|
||||
// progress JSON string encoding that encountered a character that needs
|
||||
// to be encoded.
|
||||
func appendJSONStringComplex(dst []byte, s string, i int) []byte {
|
||||
func appendStringComplex(dst []byte, s string, i int) []byte {
|
||||
start := 0
|
||||
for i < len(s) {
|
||||
b := s[i]
|
||||
@@ -94,3 +109,72 @@ func appendJSONStringComplex(dst []byte, s string, i int) []byte {
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// AppendBytes is a mirror of appendString with []byte arg
|
||||
func AppendBytes(dst, s []byte) []byte {
|
||||
dst = append(dst, '"')
|
||||
for i := 0; i < len(s); i++ {
|
||||
if s[i] < 0x20 || s[i] > 0x7e || s[i] == '\\' || s[i] == '"' {
|
||||
dst = appendBytesComplex(dst, s, i)
|
||||
return append(dst, '"')
|
||||
}
|
||||
}
|
||||
dst = append(dst, s...)
|
||||
return append(dst, '"')
|
||||
}
|
||||
|
||||
// appendBytesComplex is a mirror of the appendStringComplex
|
||||
// with []byte arg
|
||||
func appendBytesComplex(dst, s []byte, i int) []byte {
|
||||
start := 0
|
||||
for i < len(s) {
|
||||
b := s[i]
|
||||
if b >= utf8.RuneSelf {
|
||||
r, size := utf8.DecodeRune(s[i:])
|
||||
if r == utf8.RuneError && size == 1 {
|
||||
if start < i {
|
||||
dst = append(dst, s[start:i]...)
|
||||
}
|
||||
dst = append(dst, `\ufffd`...)
|
||||
i += size
|
||||
start = i
|
||||
continue
|
||||
}
|
||||
i += size
|
||||
continue
|
||||
}
|
||||
if b >= 0x20 && b <= 0x7e && b != '\\' && b != '"' {
|
||||
i++
|
||||
continue
|
||||
}
|
||||
// We encountered a character that needs to be encoded.
|
||||
// Let's append the previous simple characters to the byte slice
|
||||
// and switch our operation to read and encode the remainder
|
||||
// characters byte-by-byte.
|
||||
if start < i {
|
||||
dst = append(dst, s[start:i]...)
|
||||
}
|
||||
switch b {
|
||||
case '"', '\\':
|
||||
dst = append(dst, '\\', b)
|
||||
case '\b':
|
||||
dst = append(dst, '\\', 'b')
|
||||
case '\f':
|
||||
dst = append(dst, '\\', 'f')
|
||||
case '\n':
|
||||
dst = append(dst, '\\', 'n')
|
||||
case '\r':
|
||||
dst = append(dst, '\\', 'r')
|
||||
case '\t':
|
||||
dst = append(dst, '\\', 't')
|
||||
default:
|
||||
dst = append(dst, '\\', 'u', '0', '0', hex[b>>4], hex[b&0xF])
|
||||
}
|
||||
i++
|
||||
start = i
|
||||
}
|
||||
if start < len(s) {
|
||||
dst = append(dst, s[start:]...)
|
||||
}
|
||||
return dst
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
package json
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
var encodeStringTests = []struct {
|
||||
in string
|
||||
out string
|
||||
}{
|
||||
{"", `""`},
|
||||
{"\\", `"\\"`},
|
||||
{"\x00", `"\u0000"`},
|
||||
{"\x01", `"\u0001"`},
|
||||
{"\x02", `"\u0002"`},
|
||||
{"\x03", `"\u0003"`},
|
||||
{"\x04", `"\u0004"`},
|
||||
{"\x05", `"\u0005"`},
|
||||
{"\x06", `"\u0006"`},
|
||||
{"\x07", `"\u0007"`},
|
||||
{"\x08", `"\b"`},
|
||||
{"\x09", `"\t"`},
|
||||
{"\x0a", `"\n"`},
|
||||
{"\x0b", `"\u000b"`},
|
||||
{"\x0c", `"\f"`},
|
||||
{"\x0d", `"\r"`},
|
||||
{"\x0e", `"\u000e"`},
|
||||
{"\x0f", `"\u000f"`},
|
||||
{"\x10", `"\u0010"`},
|
||||
{"\x11", `"\u0011"`},
|
||||
{"\x12", `"\u0012"`},
|
||||
{"\x13", `"\u0013"`},
|
||||
{"\x14", `"\u0014"`},
|
||||
{"\x15", `"\u0015"`},
|
||||
{"\x16", `"\u0016"`},
|
||||
{"\x17", `"\u0017"`},
|
||||
{"\x18", `"\u0018"`},
|
||||
{"\x19", `"\u0019"`},
|
||||
{"\x1a", `"\u001a"`},
|
||||
{"\x1b", `"\u001b"`},
|
||||
{"\x1c", `"\u001c"`},
|
||||
{"\x1d", `"\u001d"`},
|
||||
{"\x1e", `"\u001e"`},
|
||||
{"\x1f", `"\u001f"`},
|
||||
{"✭", `"✭"`},
|
||||
{"foo\xc2\x7fbar", `"foo\ufffd\u007fbar"`}, // invalid sequence
|
||||
{"ascii", `"ascii"`},
|
||||
{"\"a", `"\"a"`},
|
||||
{"\x1fa", `"\u001fa"`},
|
||||
{"foo\"bar\"baz", `"foo\"bar\"baz"`},
|
||||
{"\x1ffoo\x1fbar\x1fbaz", `"\u001ffoo\u001fbar\u001fbaz"`},
|
||||
{"emoji \u2764\ufe0f!", `"emoji ❤️!"`},
|
||||
}
|
||||
|
||||
func TestappendString(t *testing.T) {
|
||||
for _, tt := range encodeStringTests {
|
||||
b := AppendString([]byte{}, tt.in)
|
||||
if got, want := string(b), tt.out; got != want {
|
||||
t.Errorf("appendString(%q) = %#q, want %#q", tt.in, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestappendBytes(t *testing.T) {
|
||||
for _, tt := range encodeStringTests {
|
||||
b := AppendBytes([]byte{}, []byte(tt.in))
|
||||
if got, want := string(b), tt.out; got != want {
|
||||
t.Errorf("appendBytes(%q) = %#q, want %#q", tt.in, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringBytes(t *testing.T) {
|
||||
t.Parallel()
|
||||
// Test that encodeState.stringBytes and encodeState.string use the same encoding.
|
||||
var r []rune
|
||||
for i := '\u0000'; i <= unicode.MaxRune; i++ {
|
||||
r = append(r, i)
|
||||
}
|
||||
s := string(r) + "\xff\xff\xffhello" // some invalid UTF-8 too
|
||||
|
||||
enc := string(AppendString([]byte{}, s))
|
||||
encBytes := string(AppendBytes([]byte{}, []byte(s)))
|
||||
|
||||
if enc != encBytes {
|
||||
i := 0
|
||||
for i < len(enc) && i < len(encBytes) && enc[i] == encBytes[i] {
|
||||
i++
|
||||
}
|
||||
enc = enc[i:]
|
||||
encBytes = encBytes[i:]
|
||||
i = 0
|
||||
for i < len(enc) && i < len(encBytes) && enc[len(enc)-i-1] == encBytes[len(encBytes)-i-1] {
|
||||
i++
|
||||
}
|
||||
enc = enc[:len(enc)-i]
|
||||
encBytes = encBytes[:len(encBytes)-i]
|
||||
|
||||
if len(enc) > 20 {
|
||||
enc = enc[:20] + "..."
|
||||
}
|
||||
if len(encBytes) > 20 {
|
||||
encBytes = encBytes[:20] + "..."
|
||||
}
|
||||
|
||||
t.Errorf("encodings differ at %#q vs %#q", enc, encBytes)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkappendString(b *testing.B) {
|
||||
tests := map[string]string{
|
||||
"NoEncoding": `aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa`,
|
||||
"EncodingFirst": `"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa`,
|
||||
"EncodingMiddle": `aaaaaaaaaaaaaaaaaaaaaaaaa"aaaaaaaaaaaaaaaaaaaaaaaa`,
|
||||
"EncodingLast": `aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"`,
|
||||
"MultiBytesFirst": `❤️aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa`,
|
||||
"MultiBytesMiddle": `aaaaaaaaaaaaaaaaaaaaaaaaa❤️aaaaaaaaaaaaaaaaaaaaaaaa`,
|
||||
"MultiBytesLast": `aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa❤️`,
|
||||
}
|
||||
for name, str := range tests {
|
||||
b.Run(name, func(b *testing.B) {
|
||||
buf := make([]byte, 0, 100)
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = AppendString(buf, str)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkappendBytes(b *testing.B) {
|
||||
tests := map[string]string{
|
||||
"NoEncoding": `aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa`,
|
||||
"EncodingFirst": `"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa`,
|
||||
"EncodingMiddle": `aaaaaaaaaaaaaaaaaaaaaaaaa"aaaaaaaaaaaaaaaaaaaaaaaa`,
|
||||
"EncodingLast": `aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"`,
|
||||
"MultiBytesFirst": `❤️aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa`,
|
||||
"MultiBytesMiddle": `aaaaaaaaaaaaaaaaaaaaaaaaa❤️aaaaaaaaaaaaaaaaaaaaaaaa`,
|
||||
"MultiBytesLast": `aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa❤️`,
|
||||
}
|
||||
for name, str := range tests {
|
||||
byt := []byte(str)
|
||||
b.Run(name, func(b *testing.B) {
|
||||
buf := make([]byte, 0, 100)
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = AppendBytes(buf, byt)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package json
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
func AppendTime(dst []byte, t time.Time, format string) []byte {
|
||||
if format == "" {
|
||||
return AppendInt64(dst, t.Unix())
|
||||
}
|
||||
return append(t.AppendFormat(append(dst, '"'), format), '"')
|
||||
}
|
||||
|
||||
func AppendTimes(dst []byte, vals []time.Time, format string) []byte {
|
||||
if format == "" {
|
||||
return appendUnixTimes(dst, vals)
|
||||
}
|
||||
if len(vals) == 0 {
|
||||
return append(dst, '[', ']')
|
||||
}
|
||||
dst = append(dst, '[')
|
||||
dst = append(vals[0].AppendFormat(append(dst, '"'), format), '"')
|
||||
if len(vals) > 1 {
|
||||
for _, t := range vals[1:] {
|
||||
dst = append(t.AppendFormat(append(dst, ',', '"'), format), '"')
|
||||
}
|
||||
}
|
||||
dst = append(dst, ']')
|
||||
return dst
|
||||
}
|
||||
|
||||
func appendUnixTimes(dst []byte, vals []time.Time) []byte {
|
||||
if len(vals) == 0 {
|
||||
return append(dst, '[', ']')
|
||||
}
|
||||
dst = append(dst, '[')
|
||||
dst = strconv.AppendInt(dst, vals[0].Unix(), 10)
|
||||
if len(vals) > 1 {
|
||||
for _, t := range vals[1:] {
|
||||
dst = strconv.AppendInt(dst, t.Unix(), 10)
|
||||
}
|
||||
}
|
||||
dst = append(dst, ']')
|
||||
return dst
|
||||
}
|
||||
|
||||
func AppendDuration(dst []byte, d time.Duration, unit time.Duration, useInt bool) []byte {
|
||||
if useInt {
|
||||
return strconv.AppendInt(dst, int64(d/unit), 10)
|
||||
}
|
||||
return AppendFloat64(dst, float64(d)/float64(unit))
|
||||
}
|
||||
|
||||
func AppendDurations(dst []byte, vals []time.Duration, unit time.Duration, useInt bool) []byte {
|
||||
if len(vals) == 0 {
|
||||
return append(dst, '[', ']')
|
||||
}
|
||||
dst = append(dst, '[')
|
||||
dst = AppendDuration(dst, vals[0], unit, useInt)
|
||||
if len(vals) > 1 {
|
||||
for _, d := range vals[1:] {
|
||||
dst = AppendDuration(append(dst, ','), d, unit, useInt)
|
||||
}
|
||||
}
|
||||
dst = append(dst, ']')
|
||||
return dst
|
||||
}
|
||||
@@ -0,0 +1,278 @@
|
||||
package json
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func AppendBool(dst []byte, val bool) []byte {
|
||||
return strconv.AppendBool(dst, val)
|
||||
}
|
||||
|
||||
func AppendBools(dst []byte, vals []bool) []byte {
|
||||
if len(vals) == 0 {
|
||||
return append(dst, '[', ']')
|
||||
}
|
||||
dst = append(dst, '[')
|
||||
dst = strconv.AppendBool(dst, vals[0])
|
||||
if len(vals) > 1 {
|
||||
for _, val := range vals[1:] {
|
||||
dst = strconv.AppendBool(append(dst, ','), val)
|
||||
}
|
||||
}
|
||||
dst = append(dst, ']')
|
||||
return dst
|
||||
}
|
||||
|
||||
func AppendInt(dst []byte, val int) []byte {
|
||||
return strconv.AppendInt(dst, int64(val), 10)
|
||||
}
|
||||
|
||||
func AppendInts(dst []byte, vals []int) []byte {
|
||||
if len(vals) == 0 {
|
||||
return append(dst, '[', ']')
|
||||
}
|
||||
dst = append(dst, '[')
|
||||
dst = strconv.AppendInt(dst, int64(vals[0]), 10)
|
||||
if len(vals) > 1 {
|
||||
for _, val := range vals[1:] {
|
||||
dst = strconv.AppendInt(append(dst, ','), int64(val), 10)
|
||||
}
|
||||
}
|
||||
dst = append(dst, ']')
|
||||
return dst
|
||||
}
|
||||
|
||||
func AppendInt8(dst []byte, val int8) []byte {
|
||||
return strconv.AppendInt(dst, int64(val), 10)
|
||||
}
|
||||
|
||||
func AppendInts8(dst []byte, vals []int8) []byte {
|
||||
if len(vals) == 0 {
|
||||
return append(dst, '[', ']')
|
||||
}
|
||||
dst = append(dst, '[')
|
||||
dst = strconv.AppendInt(dst, int64(vals[0]), 10)
|
||||
if len(vals) > 1 {
|
||||
for _, val := range vals[1:] {
|
||||
dst = strconv.AppendInt(append(dst, ','), int64(val), 10)
|
||||
}
|
||||
}
|
||||
dst = append(dst, ']')
|
||||
return dst
|
||||
}
|
||||
|
||||
func AppendInt16(dst []byte, val int16) []byte {
|
||||
return strconv.AppendInt(dst, int64(val), 10)
|
||||
}
|
||||
|
||||
func AppendInts16(dst []byte, vals []int16) []byte {
|
||||
if len(vals) == 0 {
|
||||
return append(dst, '[', ']')
|
||||
}
|
||||
dst = append(dst, '[')
|
||||
dst = strconv.AppendInt(dst, int64(vals[0]), 10)
|
||||
if len(vals) > 1 {
|
||||
for _, val := range vals[1:] {
|
||||
dst = strconv.AppendInt(append(dst, ','), int64(val), 10)
|
||||
}
|
||||
}
|
||||
dst = append(dst, ']')
|
||||
return dst
|
||||
}
|
||||
|
||||
func AppendInt32(dst []byte, val int32) []byte {
|
||||
return strconv.AppendInt(dst, int64(val), 10)
|
||||
}
|
||||
|
||||
func AppendInts32(dst []byte, vals []int32) []byte {
|
||||
if len(vals) == 0 {
|
||||
return append(dst, '[', ']')
|
||||
}
|
||||
dst = append(dst, '[')
|
||||
dst = strconv.AppendInt(dst, int64(vals[0]), 10)
|
||||
if len(vals) > 1 {
|
||||
for _, val := range vals[1:] {
|
||||
dst = strconv.AppendInt(append(dst, ','), int64(val), 10)
|
||||
}
|
||||
}
|
||||
dst = append(dst, ']')
|
||||
return dst
|
||||
}
|
||||
|
||||
func AppendInt64(dst []byte, val int64) []byte {
|
||||
return strconv.AppendInt(dst, val, 10)
|
||||
}
|
||||
|
||||
func AppendInts64(dst []byte, vals []int64) []byte {
|
||||
if len(vals) == 0 {
|
||||
return append(dst, '[', ']')
|
||||
}
|
||||
dst = append(dst, '[')
|
||||
dst = strconv.AppendInt(dst, vals[0], 10)
|
||||
if len(vals) > 1 {
|
||||
for _, val := range vals[1:] {
|
||||
dst = strconv.AppendInt(append(dst, ','), val, 10)
|
||||
}
|
||||
}
|
||||
dst = append(dst, ']')
|
||||
return dst
|
||||
}
|
||||
|
||||
func AppendUint(dst []byte, val uint) []byte {
|
||||
return strconv.AppendUint(dst, uint64(val), 10)
|
||||
}
|
||||
|
||||
func AppendUints(dst []byte, vals []uint) []byte {
|
||||
if len(vals) == 0 {
|
||||
return append(dst, '[', ']')
|
||||
}
|
||||
dst = append(dst, '[')
|
||||
dst = strconv.AppendUint(dst, uint64(vals[0]), 10)
|
||||
if len(vals) > 1 {
|
||||
for _, val := range vals[1:] {
|
||||
dst = strconv.AppendUint(append(dst, ','), uint64(val), 10)
|
||||
}
|
||||
}
|
||||
dst = append(dst, ']')
|
||||
return dst
|
||||
}
|
||||
|
||||
func AppendUint8(dst []byte, val uint8) []byte {
|
||||
return strconv.AppendUint(dst, uint64(val), 10)
|
||||
}
|
||||
|
||||
func AppendUints8(dst []byte, vals []uint8) []byte {
|
||||
if len(vals) == 0 {
|
||||
return append(dst, '[', ']')
|
||||
}
|
||||
dst = append(dst, '[')
|
||||
dst = strconv.AppendUint(dst, uint64(vals[0]), 10)
|
||||
if len(vals) > 1 {
|
||||
for _, val := range vals[1:] {
|
||||
dst = strconv.AppendUint(append(dst, ','), uint64(val), 10)
|
||||
}
|
||||
}
|
||||
dst = append(dst, ']')
|
||||
return dst
|
||||
}
|
||||
|
||||
func AppendUint16(dst []byte, val uint16) []byte {
|
||||
return strconv.AppendUint(dst, uint64(val), 10)
|
||||
}
|
||||
|
||||
func AppendUints16(dst []byte, vals []uint16) []byte {
|
||||
if len(vals) == 0 {
|
||||
return append(dst, '[', ']')
|
||||
}
|
||||
dst = append(dst, '[')
|
||||
dst = strconv.AppendUint(dst, uint64(vals[0]), 10)
|
||||
if len(vals) > 1 {
|
||||
for _, val := range vals[1:] {
|
||||
dst = strconv.AppendUint(append(dst, ','), uint64(val), 10)
|
||||
}
|
||||
}
|
||||
dst = append(dst, ']')
|
||||
return dst
|
||||
}
|
||||
|
||||
func AppendUint32(dst []byte, val uint32) []byte {
|
||||
return strconv.AppendUint(dst, uint64(val), 10)
|
||||
}
|
||||
|
||||
func AppendUints32(dst []byte, vals []uint32) []byte {
|
||||
if len(vals) == 0 {
|
||||
return append(dst, '[', ']')
|
||||
}
|
||||
dst = append(dst, '[')
|
||||
dst = strconv.AppendUint(dst, uint64(vals[0]), 10)
|
||||
if len(vals) > 1 {
|
||||
for _, val := range vals[1:] {
|
||||
dst = strconv.AppendUint(append(dst, ','), uint64(val), 10)
|
||||
}
|
||||
}
|
||||
dst = append(dst, ']')
|
||||
return dst
|
||||
}
|
||||
|
||||
func AppendUint64(dst []byte, val uint64) []byte {
|
||||
return strconv.AppendUint(dst, uint64(val), 10)
|
||||
}
|
||||
|
||||
func AppendUints64(dst []byte, vals []uint64) []byte {
|
||||
if len(vals) == 0 {
|
||||
return append(dst, '[', ']')
|
||||
}
|
||||
dst = append(dst, '[')
|
||||
dst = strconv.AppendUint(dst, vals[0], 10)
|
||||
if len(vals) > 1 {
|
||||
for _, val := range vals[1:] {
|
||||
dst = strconv.AppendUint(append(dst, ','), val, 10)
|
||||
}
|
||||
}
|
||||
dst = append(dst, ']')
|
||||
return dst
|
||||
}
|
||||
|
||||
func AppendFloat(dst []byte, val float64, bitSize int) []byte {
|
||||
// JSON does not permit NaN or Infinity. A typical JSON encoder would fail
|
||||
// with an error, but a logging library wants the data to get thru so we
|
||||
// make a tradeoff and store those types as string.
|
||||
switch {
|
||||
case math.IsNaN(val):
|
||||
return append(dst, `"NaN"`...)
|
||||
case math.IsInf(val, 1):
|
||||
return append(dst, `"+Inf"`...)
|
||||
case math.IsInf(val, -1):
|
||||
return append(dst, `"-Inf"`...)
|
||||
}
|
||||
return strconv.AppendFloat(dst, val, 'f', -1, bitSize)
|
||||
}
|
||||
|
||||
func AppendFloat32(dst []byte, val float32) []byte {
|
||||
return AppendFloat(dst, float64(val), 32)
|
||||
}
|
||||
|
||||
func AppendFloats32(dst []byte, vals []float32) []byte {
|
||||
if len(vals) == 0 {
|
||||
return append(dst, '[', ']')
|
||||
}
|
||||
dst = append(dst, '[')
|
||||
dst = AppendFloat(dst, float64(vals[0]), 32)
|
||||
if len(vals) > 1 {
|
||||
for _, val := range vals[1:] {
|
||||
dst = AppendFloat(append(dst, ','), float64(val), 32)
|
||||
}
|
||||
}
|
||||
dst = append(dst, ']')
|
||||
return dst
|
||||
}
|
||||
|
||||
func AppendFloat64(dst []byte, val float64) []byte {
|
||||
return AppendFloat(dst, val, 64)
|
||||
}
|
||||
|
||||
func AppendFloats64(dst []byte, vals []float64) []byte {
|
||||
if len(vals) == 0 {
|
||||
return append(dst, '[', ']')
|
||||
}
|
||||
dst = append(dst, '[')
|
||||
dst = AppendFloat(dst, vals[0], 32)
|
||||
if len(vals) > 1 {
|
||||
for _, val := range vals[1:] {
|
||||
dst = AppendFloat(append(dst, ','), val, 64)
|
||||
}
|
||||
}
|
||||
dst = append(dst, ']')
|
||||
return dst
|
||||
}
|
||||
|
||||
func AppendInterface(dst []byte, i interface{}) []byte {
|
||||
marshaled, err := json.Marshal(i)
|
||||
if err != nil {
|
||||
return AppendString(dst, fmt.Sprintf("marshaling error: %v", err))
|
||||
}
|
||||
return append(dst, marshaled...)
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package json
|
||||
|
||||
import (
|
||||
"math"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_appendFloat64(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input float64
|
||||
want []byte
|
||||
}{
|
||||
{"-Inf", math.Inf(-1), []byte(`"-Inf"`)},
|
||||
{"+Inf", math.Inf(1), []byte(`"+Inf"`)},
|
||||
{"NaN", math.NaN(), []byte(`"NaN"`)},
|
||||
{"0", 0, []byte(`0`)},
|
||||
{"-1.1", -1.1, []byte(`-1.1`)},
|
||||
{"1e20", 1e20, []byte(`100000000000000000000`)},
|
||||
{"1e21", 1e21, []byte(`1000000000000000000000`)},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := AppendFloat32([]byte{}, float32(tt.input)); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("appendFloat32() = %s, want %s", got, tt.want)
|
||||
}
|
||||
if got := AppendFloat64([]byte{}, tt.input); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("appendFloat32() = %s, want %s", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
package zerolog
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAppendJSONString(t *testing.T) {
|
||||
encodeStringTests := []struct {
|
||||
in string
|
||||
out string
|
||||
}{
|
||||
{"", `""`},
|
||||
{"\\", `"\\"`},
|
||||
{"\x00", `"\u0000"`},
|
||||
{"\x01", `"\u0001"`},
|
||||
{"\x02", `"\u0002"`},
|
||||
{"\x03", `"\u0003"`},
|
||||
{"\x04", `"\u0004"`},
|
||||
{"\x05", `"\u0005"`},
|
||||
{"\x06", `"\u0006"`},
|
||||
{"\x07", `"\u0007"`},
|
||||
{"\x08", `"\b"`},
|
||||
{"\x09", `"\t"`},
|
||||
{"\x0a", `"\n"`},
|
||||
{"\x0b", `"\u000b"`},
|
||||
{"\x0c", `"\f"`},
|
||||
{"\x0d", `"\r"`},
|
||||
{"\x0e", `"\u000e"`},
|
||||
{"\x0f", `"\u000f"`},
|
||||
{"\x10", `"\u0010"`},
|
||||
{"\x11", `"\u0011"`},
|
||||
{"\x12", `"\u0012"`},
|
||||
{"\x13", `"\u0013"`},
|
||||
{"\x14", `"\u0014"`},
|
||||
{"\x15", `"\u0015"`},
|
||||
{"\x16", `"\u0016"`},
|
||||
{"\x17", `"\u0017"`},
|
||||
{"\x18", `"\u0018"`},
|
||||
{"\x19", `"\u0019"`},
|
||||
{"\x1a", `"\u001a"`},
|
||||
{"\x1b", `"\u001b"`},
|
||||
{"\x1c", `"\u001c"`},
|
||||
{"\x1d", `"\u001d"`},
|
||||
{"\x1e", `"\u001e"`},
|
||||
{"\x1f", `"\u001f"`},
|
||||
{"✭", `"✭"`},
|
||||
{"foo\xc2\x7fbar", `"foo\ufffd\u007fbar"`}, // invalid sequence
|
||||
{"ascii", `"ascii"`},
|
||||
{"\"a", `"\"a"`},
|
||||
{"\x1fa", `"\u001fa"`},
|
||||
{"foo\"bar\"baz", `"foo\"bar\"baz"`},
|
||||
{"\x1ffoo\x1fbar\x1fbaz", `"\u001ffoo\u001fbar\u001fbaz"`},
|
||||
{"emoji \u2764\ufe0f!", `"emoji ❤️!"`},
|
||||
}
|
||||
|
||||
for _, tt := range encodeStringTests {
|
||||
b := appendJSONString([]byte{}, tt.in)
|
||||
if got, want := string(b), tt.out; got != want {
|
||||
t.Errorf("appendJSONString(%q) = %#q, want %#q", tt.in, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkAppendJSONString(b *testing.B) {
|
||||
tests := map[string]string{
|
||||
"NoEncoding": `aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa`,
|
||||
"EncodingFirst": `"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa`,
|
||||
"EncodingMiddle": `aaaaaaaaaaaaaaaaaaaaaaaaa"aaaaaaaaaaaaaaaaaaaaaaaa`,
|
||||
"EncodingLast": `aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"`,
|
||||
"MultiBytesFirst": `❤️aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa`,
|
||||
"MultiBytesMiddle": `aaaaaaaaaaaaaaaaaaaaaaaaa❤️aaaaaaaaaaaaaaaaaaaaaaaa`,
|
||||
"MultiBytesLast": `aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa❤️`,
|
||||
}
|
||||
for name, str := range tests {
|
||||
b.Run(name, func(b *testing.B) {
|
||||
buf := make([]byte, 0, 100)
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = appendJSONString(buf, str)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -62,16 +62,36 @@
|
||||
//
|
||||
// Sample logs:
|
||||
//
|
||||
// sampled := log.Sample(10)
|
||||
// sampled := log.Sample(&zerolog.BasicSampler{N: 10})
|
||||
// sampled.Info().Msg("will be logged every 10 messages")
|
||||
//
|
||||
// Log with contextual hooks:
|
||||
//
|
||||
// // Create the hook:
|
||||
// type SeverityHook struct{}
|
||||
//
|
||||
// func (h SeverityHook) Run(e *zerolog.Event, level zerolog.Level, msg string) {
|
||||
// if level != zerolog.NoLevel {
|
||||
// e.Str("severity", level.String())
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // And use it:
|
||||
// var h SeverityHook
|
||||
// log := zerolog.New(os.Stdout).Hook(h)
|
||||
// log.Warn().Msg("")
|
||||
// // Output: {"level":"warn","severity":"warn"}
|
||||
//
|
||||
package zerolog
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"sync/atomic"
|
||||
"strconv"
|
||||
|
||||
"github.com/rs/zerolog/internal/json"
|
||||
)
|
||||
|
||||
// Level defines log levels.
|
||||
@@ -90,6 +110,8 @@ const (
|
||||
FatalLevel
|
||||
// PanicLevel defines panic log level.
|
||||
PanicLevel
|
||||
// NoLevel defines an absent log level.
|
||||
NoLevel
|
||||
// Disabled disables the logger.
|
||||
Disabled
|
||||
)
|
||||
@@ -108,21 +130,12 @@ func (l Level) String() string {
|
||||
return "fatal"
|
||||
case PanicLevel:
|
||||
return "panic"
|
||||
case NoLevel:
|
||||
return ""
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
const (
|
||||
// Often samples log every 10 events.
|
||||
Often = 10
|
||||
// Sometimes samples log every 100 events.
|
||||
Sometimes = 100
|
||||
// Rarely samples log every 1000 events.
|
||||
Rarely = 1000
|
||||
)
|
||||
|
||||
var disabledEvent = newEvent(levelWriterAdapter{ioutil.Discard}, 0, false)
|
||||
|
||||
// A Logger represents an active logging object that generates lines
|
||||
// of JSON output to an io.Writer. Each logging operation makes a single
|
||||
// call to the Writer's Write method. There is no guaranty on access
|
||||
@@ -131,9 +144,9 @@ var disabledEvent = newEvent(levelWriterAdapter{ioutil.Discard}, 0, false)
|
||||
type Logger struct {
|
||||
w LevelWriter
|
||||
level Level
|
||||
sample uint32
|
||||
counter *uint32
|
||||
sampler Sampler
|
||||
context []byte
|
||||
hooks []Hook
|
||||
}
|
||||
|
||||
// New creates a root logger with given output writer. If the output writer implements
|
||||
@@ -159,6 +172,21 @@ func Nop() Logger {
|
||||
return New(nil).Level(Disabled)
|
||||
}
|
||||
|
||||
// Output duplicates the current logger and sets w as its output.
|
||||
func (l Logger) Output(w io.Writer) Logger {
|
||||
l2 := New(w)
|
||||
l2.level = l.level
|
||||
l2.sampler = l.sampler
|
||||
if len(l.hooks) > 0 {
|
||||
l2.hooks = append(l2.hooks, l.hooks...)
|
||||
}
|
||||
if l.context != nil {
|
||||
l2.context = make([]byte, len(l.context), cap(l.context))
|
||||
copy(l2.context, l.context)
|
||||
}
|
||||
return l2
|
||||
}
|
||||
|
||||
// With creates a child logger with the field added to its context.
|
||||
func (l Logger) With() Context {
|
||||
context := l.context
|
||||
@@ -172,88 +200,130 @@ func (l Logger) With() Context {
|
||||
return Context{l}
|
||||
}
|
||||
|
||||
// Level creates a child logger with the minimum accepted level set to level.
|
||||
func (l Logger) Level(lvl Level) Logger {
|
||||
return Logger{
|
||||
w: l.w,
|
||||
level: lvl,
|
||||
sample: l.sample,
|
||||
counter: l.counter,
|
||||
context: l.context,
|
||||
// UpdateContext updates the internal logger's context.
|
||||
//
|
||||
// Use this method with caution. If unsure, prefer the With method.
|
||||
func (l *Logger) UpdateContext(update func(c Context) Context) {
|
||||
if l == disabledLogger {
|
||||
return
|
||||
}
|
||||
if cap(l.context) == 0 {
|
||||
l.context = make([]byte, 1, 500) // first byte is timestamp flag
|
||||
}
|
||||
c := update(Context{*l})
|
||||
l.context = c.l.context
|
||||
}
|
||||
|
||||
// Sample returns a logger that only let one message out of every to pass thru.
|
||||
func (l Logger) Sample(every int) Logger {
|
||||
if every == 0 {
|
||||
// Create a child with no sampling.
|
||||
return Logger{
|
||||
w: l.w,
|
||||
level: l.level,
|
||||
context: l.context,
|
||||
}
|
||||
}
|
||||
return Logger{
|
||||
w: l.w,
|
||||
level: l.level,
|
||||
sample: uint32(every),
|
||||
counter: new(uint32),
|
||||
context: l.context,
|
||||
}
|
||||
// Level creates a child logger with the minimum accepted level set to level.
|
||||
func (l Logger) Level(lvl Level) Logger {
|
||||
l.level = lvl
|
||||
return l
|
||||
}
|
||||
|
||||
// Sample returns a logger with the s sampler.
|
||||
func (l Logger) Sample(s Sampler) Logger {
|
||||
l.sampler = s
|
||||
return l
|
||||
}
|
||||
|
||||
// Hook returns a logger with the h Hook.
|
||||
func (l Logger) Hook(h Hook) Logger {
|
||||
l.hooks = append(l.hooks, h)
|
||||
return l
|
||||
}
|
||||
|
||||
// Debug starts a new message with debug level.
|
||||
//
|
||||
// You must call Msg on the returned event in order to send the event.
|
||||
func (l Logger) Debug() *Event {
|
||||
return l.newEvent(DebugLevel, true, nil)
|
||||
func (l *Logger) Debug() *Event {
|
||||
return l.newEvent(DebugLevel, nil)
|
||||
}
|
||||
|
||||
// Info starts a new message with info level.
|
||||
//
|
||||
// You must call Msg on the returned event in order to send the event.
|
||||
func (l Logger) Info() *Event {
|
||||
return l.newEvent(InfoLevel, true, nil)
|
||||
func (l *Logger) Info() *Event {
|
||||
return l.newEvent(InfoLevel, nil)
|
||||
}
|
||||
|
||||
// Warn starts a new message with warn level.
|
||||
//
|
||||
// You must call Msg on the returned event in order to send the event.
|
||||
func (l Logger) Warn() *Event {
|
||||
return l.newEvent(WarnLevel, true, nil)
|
||||
func (l *Logger) Warn() *Event {
|
||||
return l.newEvent(WarnLevel, nil)
|
||||
}
|
||||
|
||||
// Error starts a new message with error level.
|
||||
//
|
||||
// You must call Msg on the returned event in order to send the event.
|
||||
func (l Logger) Error() *Event {
|
||||
return l.newEvent(ErrorLevel, true, nil)
|
||||
func (l *Logger) Error() *Event {
|
||||
return l.newEvent(ErrorLevel, nil)
|
||||
}
|
||||
|
||||
// Fatal starts a new message with fatal level. The os.Exit(1) function
|
||||
// is called by the Msg method.
|
||||
//
|
||||
// You must call Msg on the returned event in order to send the event.
|
||||
func (l Logger) Fatal() *Event {
|
||||
return l.newEvent(FatalLevel, true, func(msg string) { os.Exit(1) })
|
||||
func (l *Logger) Fatal() *Event {
|
||||
return l.newEvent(FatalLevel, func(msg string) { os.Exit(1) })
|
||||
}
|
||||
|
||||
// Panic starts a new message with panic level. The message is also sent
|
||||
// to the panic function.
|
||||
//
|
||||
// You must call Msg on the returned event in order to send the event.
|
||||
func (l Logger) Panic() *Event {
|
||||
return l.newEvent(PanicLevel, true, func(msg string) { panic(msg) })
|
||||
func (l *Logger) Panic() *Event {
|
||||
return l.newEvent(PanicLevel, func(msg string) { panic(msg) })
|
||||
}
|
||||
|
||||
// WithLevel starts a new message with level.
|
||||
//
|
||||
// You must call Msg on the returned event in order to send the event.
|
||||
func (l *Logger) WithLevel(level Level) *Event {
|
||||
switch level {
|
||||
case DebugLevel:
|
||||
return l.Debug()
|
||||
case InfoLevel:
|
||||
return l.Info()
|
||||
case WarnLevel:
|
||||
return l.Warn()
|
||||
case ErrorLevel:
|
||||
return l.Error()
|
||||
case FatalLevel:
|
||||
return l.Fatal()
|
||||
case PanicLevel:
|
||||
return l.Panic()
|
||||
case NoLevel:
|
||||
return l.Log()
|
||||
case Disabled:
|
||||
return nil
|
||||
default:
|
||||
panic("zerolog: WithLevel(): invalid level: " + strconv.Itoa(int(level)))
|
||||
}
|
||||
}
|
||||
|
||||
// Log starts a new message with no level. Setting GlobalLevel to Disabled
|
||||
// will still disable events produced by this method.
|
||||
//
|
||||
// You must call Msg on the returned event in order to send the event.
|
||||
func (l Logger) Log() *Event {
|
||||
// We use panic level with addLevelField=false to make Log passthrough all
|
||||
// levels except Disabled.
|
||||
return l.newEvent(PanicLevel, false, nil)
|
||||
func (l *Logger) Log() *Event {
|
||||
return l.newEvent(NoLevel, nil)
|
||||
}
|
||||
|
||||
// Print sends a log event using debug level and no extra field.
|
||||
// Arguments are handled in the manner of fmt.Print.
|
||||
func (l *Logger) Print(v ...interface{}) {
|
||||
if e := l.Debug(); e.Enabled() {
|
||||
e.Msg(fmt.Sprint(v...))
|
||||
}
|
||||
}
|
||||
|
||||
// Printf sends a log event using debug level and no extra field.
|
||||
// Arguments are handled in the manner of fmt.Printf.
|
||||
func (l *Logger) Printf(format string, v ...interface{}) {
|
||||
if e := l.Debug(); e.Enabled() {
|
||||
e.Msg(fmt.Sprintf(format, v...))
|
||||
}
|
||||
}
|
||||
|
||||
// Write implements the io.Writer interface. This is useful to set as a writer
|
||||
@@ -268,44 +338,39 @@ func (l Logger) Write(p []byte) (n int, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (l Logger) newEvent(level Level, addLevelField bool, done func(string)) *Event {
|
||||
func (l *Logger) newEvent(level Level, done func(string)) *Event {
|
||||
enabled := l.should(level)
|
||||
if !enabled {
|
||||
return disabledEvent
|
||||
return nil
|
||||
}
|
||||
lvl := InfoLevel
|
||||
if addLevelField {
|
||||
lvl = level
|
||||
}
|
||||
e := newEvent(l.w, lvl, enabled)
|
||||
e := newEvent(l.w, level, true)
|
||||
e.done = done
|
||||
if l.context != nil && len(l.context) > 0 && l.context[0] > 0 {
|
||||
// first byte of context is ts flag
|
||||
e.buf = appendTimestamp(e.buf)
|
||||
e.buf = json.AppendTime(json.AppendKey(e.buf, TimestampFieldName), TimestampFunc(), TimeFieldFormat)
|
||||
}
|
||||
if addLevelField {
|
||||
if level != NoLevel {
|
||||
e.Str(LevelFieldName, level.String())
|
||||
}
|
||||
if l.sample > 0 && SampleFieldName != "" {
|
||||
e.Uint32(SampleFieldName, l.sample)
|
||||
}
|
||||
if l.context != nil && len(l.context) > 1 {
|
||||
if len(e.buf) > 1 {
|
||||
e.buf = append(e.buf, ',')
|
||||
}
|
||||
e.buf = append(e.buf, l.context[1:]...)
|
||||
}
|
||||
if len(l.hooks) > 0 {
|
||||
e.h = append(e.h, l.hooks...)
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
// should returns true if the log event should be logged.
|
||||
func (l Logger) should(lvl Level) bool {
|
||||
func (l *Logger) should(lvl Level) bool {
|
||||
if lvl < l.level || lvl < globalLevel() {
|
||||
return false
|
||||
}
|
||||
if l.sample > 0 && l.counter != nil && !samplingDisabled() {
|
||||
c := atomic.AddUint32(l.counter, 1)
|
||||
return c%l.sample == 0
|
||||
if l.sampler != nil && !samplingDisabled() {
|
||||
return l.sampler.Sample(lvl)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
+34
-4
@@ -3,6 +3,7 @@ package log
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
@@ -11,6 +12,11 @@ import (
|
||||
// Logger is the global logger.
|
||||
var Logger = zerolog.New(os.Stderr).With().Timestamp().Logger()
|
||||
|
||||
// Output duplicates the global logger and sets w as its output.
|
||||
func Output(w io.Writer) zerolog.Logger {
|
||||
return Logger.Output(w)
|
||||
}
|
||||
|
||||
// With creates a child logger with the field added to its context.
|
||||
func With() zerolog.Context {
|
||||
return Logger.With()
|
||||
@@ -21,9 +27,14 @@ func Level(level zerolog.Level) zerolog.Logger {
|
||||
return Logger.Level(level)
|
||||
}
|
||||
|
||||
// Sample returns a logger that only let one message out of every to pass thru.
|
||||
func Sample(every int) zerolog.Logger {
|
||||
return Logger.Sample(every)
|
||||
// Sample returns a logger with the s sampler.
|
||||
func Sample(s zerolog.Sampler) zerolog.Logger {
|
||||
return Logger.Sample(s)
|
||||
}
|
||||
|
||||
// Hook returns a logger with the h Hook.
|
||||
func Hook(h zerolog.Hook) zerolog.Logger {
|
||||
return Logger.Hook(h)
|
||||
}
|
||||
|
||||
// Debug starts a new message with debug level.
|
||||
@@ -70,6 +81,13 @@ func Panic() *zerolog.Event {
|
||||
return Logger.Panic()
|
||||
}
|
||||
|
||||
// WithLevel starts a new message with level.
|
||||
//
|
||||
// You must call Msg on the returned event in order to send the event.
|
||||
func WithLevel(level zerolog.Level) *zerolog.Event {
|
||||
return Logger.WithLevel(level)
|
||||
}
|
||||
|
||||
// Log starts a new message with no level. Setting zerolog.GlobalLevel to
|
||||
// zerlog.Disabled will still disable events produced by this method.
|
||||
//
|
||||
@@ -78,8 +96,20 @@ func Log() *zerolog.Event {
|
||||
return Logger.Log()
|
||||
}
|
||||
|
||||
// Print sends a log event using debug level and no extra field.
|
||||
// Arguments are handled in the manner of fmt.Print.
|
||||
func Print(v ...interface{}) {
|
||||
Logger.Print(v...)
|
||||
}
|
||||
|
||||
// Printf sends a log event using debug level and no extra field.
|
||||
// Arguments are handled in the manner of fmt.Printf.
|
||||
func Printf(format string, v ...interface{}) {
|
||||
Logger.Printf(format, v...)
|
||||
}
|
||||
|
||||
// Ctx returns the Logger associated with the ctx. If no logger
|
||||
// is associated, a disabled logger is returned.
|
||||
func Ctx(ctx context.Context) zerolog.Logger {
|
||||
func Ctx(ctx context.Context) *zerolog.Logger {
|
||||
return zerolog.Ctx(ctx)
|
||||
}
|
||||
|
||||
+196
-3
@@ -38,15 +38,58 @@ func ExampleLogger_Level() {
|
||||
}
|
||||
|
||||
func ExampleLogger_Sample() {
|
||||
log := zerolog.New(os.Stdout).Sample(2)
|
||||
log := zerolog.New(os.Stdout).Sample(&zerolog.BasicSampler{N: 2})
|
||||
|
||||
log.Info().Msg("message 1")
|
||||
log.Info().Msg("message 2")
|
||||
log.Info().Msg("message 3")
|
||||
log.Info().Msg("message 4")
|
||||
|
||||
// Output: {"level":"info","sample":2,"message":"message 2"}
|
||||
// {"level":"info","sample":2,"message":"message 4"}
|
||||
// Output: {"level":"info","message":"message 2"}
|
||||
// {"level":"info","message":"message 4"}
|
||||
}
|
||||
|
||||
type LevelNameHook struct{}
|
||||
|
||||
func (h LevelNameHook) Run(e *zerolog.Event, l zerolog.Level, msg string) {
|
||||
if l != zerolog.NoLevel {
|
||||
e.Str("level_name", l.String())
|
||||
} else {
|
||||
e.Str("level_name", "NoLevel")
|
||||
}
|
||||
}
|
||||
|
||||
type MessageHook string
|
||||
|
||||
func (h MessageHook) Run(e *zerolog.Event, l zerolog.Level, msg string) {
|
||||
e.Str("the_message", msg)
|
||||
}
|
||||
|
||||
func ExampleLogger_Hook() {
|
||||
var levelNameHook LevelNameHook
|
||||
var messageHook MessageHook = "The message"
|
||||
|
||||
log := zerolog.New(os.Stdout).Hook(levelNameHook).Hook(messageHook)
|
||||
|
||||
log.Info().Msg("hello world")
|
||||
|
||||
// Output: {"level":"info","level_name":"info","the_message":"hello world","message":"hello world"}
|
||||
}
|
||||
|
||||
func ExampleLogger_Print() {
|
||||
log := zerolog.New(os.Stdout)
|
||||
|
||||
log.Print("hello world")
|
||||
|
||||
// Output: {"level":"debug","message":"hello world"}
|
||||
}
|
||||
|
||||
func ExampleLogger_Printf() {
|
||||
log := zerolog.New(os.Stdout)
|
||||
|
||||
log.Printf("hello %s", "world")
|
||||
|
||||
// Output: {"level":"debug","message":"hello world"}
|
||||
}
|
||||
|
||||
func ExampleLogger_Debug() {
|
||||
@@ -91,6 +134,15 @@ func ExampleLogger_Error() {
|
||||
// Output: {"level":"error","error":"some error","message":"error doing something"}
|
||||
}
|
||||
|
||||
func ExampleLogger_WithLevel() {
|
||||
log := zerolog.New(os.Stdout)
|
||||
|
||||
log.WithLevel(zerolog.InfoLevel).
|
||||
Msg("hello world")
|
||||
|
||||
// Output: {"level":"info","message":"hello world"}
|
||||
}
|
||||
|
||||
func ExampleLogger_Write() {
|
||||
log := zerolog.New(os.Stdout).With().
|
||||
Str("foo", "bar").
|
||||
@@ -129,6 +181,71 @@ func ExampleEvent_Dict() {
|
||||
// Output: {"foo":"bar","dict":{"bar":"baz","n":1},"message":"hello world"}
|
||||
}
|
||||
|
||||
type User struct {
|
||||
Name string
|
||||
Age int
|
||||
Created time.Time
|
||||
}
|
||||
|
||||
func (u User) MarshalZerologObject(e *zerolog.Event) {
|
||||
e.Str("name", u.Name).
|
||||
Int("age", u.Age).
|
||||
Time("created", u.Created)
|
||||
}
|
||||
|
||||
type Users []User
|
||||
|
||||
func (uu Users) MarshalZerologArray(a *zerolog.Array) {
|
||||
for _, u := range uu {
|
||||
a.Object(u)
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleEvent_Array() {
|
||||
log := zerolog.New(os.Stdout)
|
||||
|
||||
log.Log().
|
||||
Str("foo", "bar").
|
||||
Array("array", zerolog.Arr().
|
||||
Str("baz").
|
||||
Int(1),
|
||||
).
|
||||
Msg("hello world")
|
||||
|
||||
// Output: {"foo":"bar","array":["baz",1],"message":"hello world"}
|
||||
}
|
||||
|
||||
func ExampleEvent_Array_object() {
|
||||
log := zerolog.New(os.Stdout)
|
||||
|
||||
// Users implements zerolog.LogArrayMarshaler
|
||||
u := Users{
|
||||
User{"John", 35, time.Time{}},
|
||||
User{"Bob", 55, time.Time{}},
|
||||
}
|
||||
|
||||
log.Log().
|
||||
Str("foo", "bar").
|
||||
Array("users", u).
|
||||
Msg("hello world")
|
||||
|
||||
// Output: {"foo":"bar","users":[{"name":"John","age":35,"created":"0001-01-01T00:00:00Z"},{"name":"Bob","age":55,"created":"0001-01-01T00:00:00Z"}],"message":"hello world"}
|
||||
}
|
||||
|
||||
func ExampleEvent_Object() {
|
||||
log := zerolog.New(os.Stdout)
|
||||
|
||||
// User implements zerolog.LogObjectMarshaler
|
||||
u := User{"John", 35, time.Time{}}
|
||||
|
||||
log.Log().
|
||||
Str("foo", "bar").
|
||||
Object("user", u).
|
||||
Msg("hello world")
|
||||
|
||||
// Output: {"foo":"bar","user":{"name":"John","age":35,"created":"0001-01-01T00:00:00Z"},"message":"hello world"}
|
||||
}
|
||||
|
||||
func ExampleEvent_Interface() {
|
||||
log := zerolog.New(os.Stdout)
|
||||
|
||||
@@ -159,6 +276,22 @@ func ExampleEvent_Dur() {
|
||||
// Output: {"foo":"bar","dur":10000,"message":"hello world"}
|
||||
}
|
||||
|
||||
func ExampleEvent_Durs() {
|
||||
d := []time.Duration{
|
||||
time.Duration(10 * time.Second),
|
||||
time.Duration(20 * time.Second),
|
||||
}
|
||||
|
||||
log := zerolog.New(os.Stdout)
|
||||
|
||||
log.Log().
|
||||
Str("foo", "bar").
|
||||
Durs("durs", d).
|
||||
Msg("hello world")
|
||||
|
||||
// Output: {"foo":"bar","durs":[10000,20000],"message":"hello world"}
|
||||
}
|
||||
|
||||
func ExampleContext_Dict() {
|
||||
log := zerolog.New(os.Stdout).With().
|
||||
Str("foo", "bar").
|
||||
@@ -172,6 +305,50 @@ func ExampleContext_Dict() {
|
||||
// Output: {"foo":"bar","dict":{"bar":"baz","n":1},"message":"hello world"}
|
||||
}
|
||||
|
||||
func ExampleContext_Array() {
|
||||
log := zerolog.New(os.Stdout).With().
|
||||
Str("foo", "bar").
|
||||
Array("array", zerolog.Arr().
|
||||
Str("baz").
|
||||
Int(1),
|
||||
).Logger()
|
||||
|
||||
log.Log().Msg("hello world")
|
||||
|
||||
// Output: {"foo":"bar","array":["baz",1],"message":"hello world"}
|
||||
}
|
||||
|
||||
func ExampleContext_Array_object() {
|
||||
// Users implements zerolog.LogArrayMarshaler
|
||||
u := Users{
|
||||
User{"John", 35, time.Time{}},
|
||||
User{"Bob", 55, time.Time{}},
|
||||
}
|
||||
|
||||
log := zerolog.New(os.Stdout).With().
|
||||
Str("foo", "bar").
|
||||
Array("users", u).
|
||||
Logger()
|
||||
|
||||
log.Log().Msg("hello world")
|
||||
|
||||
// Output: {"foo":"bar","users":[{"name":"John","age":35,"created":"0001-01-01T00:00:00Z"},{"name":"Bob","age":55,"created":"0001-01-01T00:00:00Z"}],"message":"hello world"}
|
||||
}
|
||||
|
||||
func ExampleContext_Object() {
|
||||
// User implements zerolog.LogObjectMarshaler
|
||||
u := User{"John", 35, time.Time{}}
|
||||
|
||||
log := zerolog.New(os.Stdout).With().
|
||||
Str("foo", "bar").
|
||||
Object("user", u).
|
||||
Logger()
|
||||
|
||||
log.Log().Msg("hello world")
|
||||
|
||||
// Output: {"foo":"bar","user":{"name":"John","age":35,"created":"0001-01-01T00:00:00Z"},"message":"hello world"}
|
||||
}
|
||||
|
||||
func ExampleContext_Interface() {
|
||||
obj := struct {
|
||||
Name string `json:"name"`
|
||||
@@ -201,3 +378,19 @@ func ExampleContext_Dur() {
|
||||
|
||||
// Output: {"foo":"bar","dur":10000,"message":"hello world"}
|
||||
}
|
||||
|
||||
func ExampleContext_Durs() {
|
||||
d := []time.Duration{
|
||||
time.Duration(10 * time.Second),
|
||||
time.Duration(20 * time.Second),
|
||||
}
|
||||
|
||||
log := zerolog.New(os.Stdout).With().
|
||||
Str("foo", "bar").
|
||||
Durs("durs", d).
|
||||
Logger()
|
||||
|
||||
log.Log().Msg("hello world")
|
||||
|
||||
// Output: {"foo":"bar","durs":[10000,20000],"message":"hello world"}
|
||||
}
|
||||
|
||||
+216
-24
@@ -14,7 +14,7 @@ func TestLog(t *testing.T) {
|
||||
log := New(out)
|
||||
log.Log().Msg("")
|
||||
if got, want := out.String(), "{}\n"; got != want {
|
||||
t.Errorf("invalid log output: got %q, want %q", got, want)
|
||||
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -23,7 +23,7 @@ func TestLog(t *testing.T) {
|
||||
log := New(out)
|
||||
log.Log().Str("foo", "bar").Msg("")
|
||||
if got, want := out.String(), `{"foo":"bar"}`+"\n"; got != want {
|
||||
t.Errorf("invalid log output: got %q, want %q", got, want)
|
||||
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -35,7 +35,7 @@ func TestLog(t *testing.T) {
|
||||
Int("n", 123).
|
||||
Msg("")
|
||||
if got, want := out.String(), `{"foo":"bar","n":123}`+"\n"; got != want {
|
||||
t.Errorf("invalid log output: got %q, want %q", got, want)
|
||||
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -46,7 +46,7 @@ func TestInfo(t *testing.T) {
|
||||
log := New(out)
|
||||
log.Info().Msg("")
|
||||
if got, want := out.String(), `{"level":"info"}`+"\n"; got != want {
|
||||
t.Errorf("invalid log output: got %q, want %q", got, want)
|
||||
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -55,7 +55,7 @@ func TestInfo(t *testing.T) {
|
||||
log := New(out)
|
||||
log.Info().Str("foo", "bar").Msg("")
|
||||
if got, want := out.String(), `{"level":"info","foo":"bar"}`+"\n"; got != want {
|
||||
t.Errorf("invalid log output: got %q, want %q", got, want)
|
||||
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -67,7 +67,7 @@ func TestInfo(t *testing.T) {
|
||||
Int("n", 123).
|
||||
Msg("")
|
||||
if got, want := out.String(), `{"level":"info","foo":"bar","n":123}`+"\n"; got != want {
|
||||
t.Errorf("invalid log output: got %q, want %q", got, want)
|
||||
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -95,15 +95,46 @@ func TestWith(t *testing.T) {
|
||||
Logger()
|
||||
log.Log().Msg("")
|
||||
if got, want := out.String(), `{"foo":"bar","error":"some error","bool":true,"int":1,"int8":2,"int16":3,"int32":4,"int64":5,"uint":6,"uint8":7,"uint16":8,"uint32":9,"uint64":10,"float32":11,"float64":12,"time":"0001-01-01T00:00:00Z"}`+"\n"; got != want {
|
||||
t.Errorf("invalid log output: got %q, want %q", got, want)
|
||||
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFieldsMap(t *testing.T) {
|
||||
out := &bytes.Buffer{}
|
||||
log := New(out)
|
||||
log.Log().Fields(map[string]interface{}{
|
||||
"nil": nil,
|
||||
"string": "foo",
|
||||
"bytes": []byte("bar"),
|
||||
"error": errors.New("some error"),
|
||||
"bool": true,
|
||||
"int": int(1),
|
||||
"int8": int8(2),
|
||||
"int16": int16(3),
|
||||
"int32": int32(4),
|
||||
"int64": int64(5),
|
||||
"uint": uint(6),
|
||||
"uint8": uint8(7),
|
||||
"uint16": uint16(8),
|
||||
"uint32": uint32(9),
|
||||
"uint64": uint64(10),
|
||||
"float32": float32(11),
|
||||
"float64": float64(12),
|
||||
"dur": 1 * time.Second,
|
||||
"time": time.Time{},
|
||||
}).Msg("")
|
||||
if got, want := out.String(), `{"bool":true,"bytes":"bar","dur":1000,"error":"some error","float32":11,"float64":12,"int":1,"int16":3,"int32":4,"int64":5,"int8":2,"nil":null,"string":"foo","time":"0001-01-01T00:00:00Z","uint":6,"uint16":8,"uint32":9,"uint64":10,"uint8":7}`+"\n"; got != want {
|
||||
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFields(t *testing.T) {
|
||||
out := &bytes.Buffer{}
|
||||
log := New(out)
|
||||
now := time.Now()
|
||||
log.Log().
|
||||
Str("foo", "bar").
|
||||
Str("string", "foo").
|
||||
Bytes("bytes", []byte("bar")).
|
||||
AnErr("some_err", nil).
|
||||
Err(errors.New("some error")).
|
||||
Bool("bool", true).
|
||||
@@ -121,18 +152,101 @@ func TestFields(t *testing.T) {
|
||||
Float64("float64", 12).
|
||||
Dur("dur", 1*time.Second).
|
||||
Time("time", time.Time{}).
|
||||
TimeDiff("diff", time.Now(), time.Now().Add(-10*time.Second)).
|
||||
TimeDiff("diff", now, now.Add(-10*time.Second)).
|
||||
Msg("")
|
||||
if got, want := out.String(), `{"foo":"bar","error":"some error","bool":true,"int":1,"int8":2,"int16":3,"int32":4,"int64":5,"uint":6,"uint8":7,"uint16":8,"uint32":9,"uint64":10,"float32":11,"float64":12,"dur":1000,"time":"0001-01-01T00:00:00Z","diff":10000}`+"\n"; got != want {
|
||||
t.Errorf("invalid log output: got %q, want %q", got, want)
|
||||
if got, want := out.String(), `{"string":"foo","bytes":"bar","error":"some error","bool":true,"int":1,"int8":2,"int16":3,"int32":4,"int64":5,"uint":6,"uint8":7,"uint16":8,"uint32":9,"uint64":10,"float32":11,"float64":12,"dur":1000,"time":"0001-01-01T00:00:00Z","diff":10000}`+"\n"; got != want {
|
||||
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFieldsArrayEmpty(t *testing.T) {
|
||||
out := &bytes.Buffer{}
|
||||
log := New(out)
|
||||
log.Log().
|
||||
Strs("string", []string{}).
|
||||
Errs("err", []error{}).
|
||||
Bools("bool", []bool{}).
|
||||
Ints("int", []int{}).
|
||||
Ints8("int8", []int8{}).
|
||||
Ints16("int16", []int16{}).
|
||||
Ints32("int32", []int32{}).
|
||||
Ints64("int64", []int64{}).
|
||||
Uints("uint", []uint{}).
|
||||
Uints8("uint8", []uint8{}).
|
||||
Uints16("uint16", []uint16{}).
|
||||
Uints32("uint32", []uint32{}).
|
||||
Uints64("uint64", []uint64{}).
|
||||
Floats32("float32", []float32{}).
|
||||
Floats64("float64", []float64{}).
|
||||
Durs("dur", []time.Duration{}).
|
||||
Times("time", []time.Time{}).
|
||||
Msg("")
|
||||
if got, want := out.String(), `{"string":[],"err":[],"bool":[],"int":[],"int8":[],"int16":[],"int32":[],"int64":[],"uint":[],"uint8":[],"uint16":[],"uint32":[],"uint64":[],"float32":[],"float64":[],"dur":[],"time":[]}`+"\n"; got != want {
|
||||
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFieldsArraySingleElement(t *testing.T) {
|
||||
out := &bytes.Buffer{}
|
||||
log := New(out)
|
||||
log.Log().
|
||||
Strs("string", []string{"foo"}).
|
||||
Errs("err", []error{errors.New("some error")}).
|
||||
Bools("bool", []bool{true}).
|
||||
Ints("int", []int{1}).
|
||||
Ints8("int8", []int8{2}).
|
||||
Ints16("int16", []int16{3}).
|
||||
Ints32("int32", []int32{4}).
|
||||
Ints64("int64", []int64{5}).
|
||||
Uints("uint", []uint{6}).
|
||||
Uints8("uint8", []uint8{7}).
|
||||
Uints16("uint16", []uint16{8}).
|
||||
Uints32("uint32", []uint32{9}).
|
||||
Uints64("uint64", []uint64{10}).
|
||||
Floats32("float32", []float32{11}).
|
||||
Floats64("float64", []float64{12}).
|
||||
Durs("dur", []time.Duration{1 * time.Second}).
|
||||
Times("time", []time.Time{time.Time{}}).
|
||||
Msg("")
|
||||
if got, want := out.String(), `{"string":["foo"],"err":["some error"],"bool":[true],"int":[1],"int8":[2],"int16":[3],"int32":[4],"int64":[5],"uint":[6],"uint8":[7],"uint16":[8],"uint32":[9],"uint64":[10],"float32":[11],"float64":[12],"dur":[1000],"time":["0001-01-01T00:00:00Z"]}`+"\n"; got != want {
|
||||
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFieldsArrayMultipleElement(t *testing.T) {
|
||||
out := &bytes.Buffer{}
|
||||
log := New(out)
|
||||
log.Log().
|
||||
Strs("string", []string{"foo", "bar"}).
|
||||
Errs("err", []error{errors.New("some error"), nil}).
|
||||
Bools("bool", []bool{true, false}).
|
||||
Ints("int", []int{1, 0}).
|
||||
Ints8("int8", []int8{2, 0}).
|
||||
Ints16("int16", []int16{3, 0}).
|
||||
Ints32("int32", []int32{4, 0}).
|
||||
Ints64("int64", []int64{5, 0}).
|
||||
Uints("uint", []uint{6, 0}).
|
||||
Uints8("uint8", []uint8{7, 0}).
|
||||
Uints16("uint16", []uint16{8, 0}).
|
||||
Uints32("uint32", []uint32{9, 0}).
|
||||
Uints64("uint64", []uint64{10, 0}).
|
||||
Floats32("float32", []float32{11, 0}).
|
||||
Floats64("float64", []float64{12, 0}).
|
||||
Durs("dur", []time.Duration{1 * time.Second, 0}).
|
||||
Times("time", []time.Time{time.Time{}, time.Time{}}).
|
||||
Msg("")
|
||||
if got, want := out.String(), `{"string":["foo","bar"],"err":["some error",null],"bool":[true,false],"int":[1,0],"int8":[2,0],"int16":[3,0],"int32":[4,0],"int64":[5,0],"uint":[6,0],"uint8":[7,0],"uint16":[8,0],"uint32":[9,0],"uint64":[10,0],"float32":[11,0],"float64":[12,0],"dur":[1000,0],"time":["0001-01-01T00:00:00Z","0001-01-01T00:00:00Z"]}`+"\n"; got != want {
|
||||
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFieldsDisabled(t *testing.T) {
|
||||
out := &bytes.Buffer{}
|
||||
log := New(out).Level(InfoLevel)
|
||||
now := time.Now()
|
||||
log.Debug().
|
||||
Str("foo", "bar").
|
||||
Str("string", "foo").
|
||||
Bytes("bytes", []byte("bar")).
|
||||
AnErr("some_err", nil).
|
||||
Err(errors.New("some error")).
|
||||
Bool("bool", true).
|
||||
@@ -150,18 +264,19 @@ func TestFieldsDisabled(t *testing.T) {
|
||||
Float64("float64", 12).
|
||||
Dur("dur", 1*time.Second).
|
||||
Time("time", time.Time{}).
|
||||
TimeDiff("diff", time.Now(), time.Now().Add(-10*time.Second)).
|
||||
TimeDiff("diff", now, now.Add(-10*time.Second)).
|
||||
Msg("")
|
||||
if got, want := out.String(), ""; got != want {
|
||||
t.Errorf("invalid log output: got %q, want %q", got, want)
|
||||
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMsgf(t *testing.T) {
|
||||
out := &bytes.Buffer{}
|
||||
New(out).Log().Msgf("one %s %.1f %d %v", "two", 3.4, 5, errors.New("six"))
|
||||
log := New(out)
|
||||
log.Log().Msgf("one %s %.1f %d %v", "two", 3.4, 5, errors.New("six"))
|
||||
if got, want := out.String(), `{"message":"one two 3.4 5 six"}`+"\n"; got != want {
|
||||
t.Errorf("invalid log output: got %q, want %q", got, want)
|
||||
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,7 +285,7 @@ func TestWithAndFieldsCombined(t *testing.T) {
|
||||
log := New(out).With().Str("f1", "val").Str("f2", "val").Logger()
|
||||
log.Log().Str("f3", "val").Msg("")
|
||||
if got, want := out.String(), `{"f1":"val","f2":"val","f3":"val"}`+"\n"; got != want {
|
||||
t.Errorf("invalid log output: got %q, want %q", got, want)
|
||||
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,7 +295,43 @@ func TestLevel(t *testing.T) {
|
||||
log := New(out).Level(Disabled)
|
||||
log.Info().Msg("test")
|
||||
if got, want := out.String(), ""; got != want {
|
||||
t.Errorf("invalid log output: got %q, want %q", got, want)
|
||||
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("NoLevel/Disabled", func(t *testing.T) {
|
||||
out := &bytes.Buffer{}
|
||||
log := New(out).Level(Disabled)
|
||||
log.Log().Msg("test")
|
||||
if got, want := out.String(), ""; got != want {
|
||||
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("NoLevel/Info", func(t *testing.T) {
|
||||
out := &bytes.Buffer{}
|
||||
log := New(out).Level(InfoLevel)
|
||||
log.Log().Msg("test")
|
||||
if got, want := out.String(), `{"message":"test"}`+"\n"; got != want {
|
||||
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("NoLevel/Panic", func(t *testing.T) {
|
||||
out := &bytes.Buffer{}
|
||||
log := New(out).Level(PanicLevel)
|
||||
log.Log().Msg("test")
|
||||
if got, want := out.String(), `{"message":"test"}`+"\n"; got != want {
|
||||
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("NoLevel/WithLevel", func(t *testing.T) {
|
||||
out := &bytes.Buffer{}
|
||||
log := New(out).Level(InfoLevel)
|
||||
log.WithLevel(NoLevel).Msg("test")
|
||||
if got, want := out.String(), `{"message":"test"}`+"\n"; got != want {
|
||||
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -189,20 +340,20 @@ func TestLevel(t *testing.T) {
|
||||
log := New(out).Level(InfoLevel)
|
||||
log.Info().Msg("test")
|
||||
if got, want := out.String(), `{"level":"info","message":"test"}`+"\n"; got != want {
|
||||
t.Errorf("invalid log output: got %q, want %q", got, want)
|
||||
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestSampling(t *testing.T) {
|
||||
out := &bytes.Buffer{}
|
||||
log := New(out).Sample(2)
|
||||
log := New(out).Sample(&BasicSampler{N: 2})
|
||||
log.Log().Int("i", 1).Msg("")
|
||||
log.Log().Int("i", 2).Msg("")
|
||||
log.Log().Int("i", 3).Msg("")
|
||||
log.Log().Int("i", 4).Msg("")
|
||||
if got, want := out.String(), "{\"sample\":2,\"i\":2}\n{\"sample\":2,\"i\":4}\n"; got != want {
|
||||
t.Errorf("invalid log output: got %q, want %q", got, want)
|
||||
if got, want := out.String(), "{\"i\":2}\n{\"i\":4}\n"; got != want {
|
||||
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -237,6 +388,13 @@ func TestLevelWriter(t *testing.T) {
|
||||
log.Info().Msg("2")
|
||||
log.Warn().Msg("3")
|
||||
log.Error().Msg("4")
|
||||
log.Log().Msg("nolevel-1")
|
||||
log.WithLevel(DebugLevel).Msg("5")
|
||||
log.WithLevel(InfoLevel).Msg("6")
|
||||
log.WithLevel(WarnLevel).Msg("7")
|
||||
log.WithLevel(ErrorLevel).Msg("8")
|
||||
log.WithLevel(NoLevel).Msg("nolevel-2")
|
||||
|
||||
want := []struct {
|
||||
l Level
|
||||
p string
|
||||
@@ -245,6 +403,12 @@ func TestLevelWriter(t *testing.T) {
|
||||
{InfoLevel, `{"level":"info","message":"2"}` + "\n"},
|
||||
{WarnLevel, `{"level":"warn","message":"3"}` + "\n"},
|
||||
{ErrorLevel, `{"level":"error","message":"4"}` + "\n"},
|
||||
{NoLevel, `{"message":"nolevel-1"}` + "\n"},
|
||||
{DebugLevel, `{"level":"debug","message":"5"}` + "\n"},
|
||||
{InfoLevel, `{"level":"info","message":"6"}` + "\n"},
|
||||
{WarnLevel, `{"level":"warn","message":"7"}` + "\n"},
|
||||
{ErrorLevel, `{"level":"error","message":"8"}` + "\n"},
|
||||
{NoLevel, `{"message":"nolevel-2"}` + "\n"},
|
||||
}
|
||||
if got := lw.ops; !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("invalid ops:\ngot:\n%v\nwant:\n%v", got, want)
|
||||
@@ -263,7 +427,7 @@ func TestContextTimestamp(t *testing.T) {
|
||||
log.Log().Msg("hello world")
|
||||
|
||||
if got, want := out.String(), `{"time":"2001-02-03T04:05:06Z","foo":"bar","message":"hello world"}`+"\n"; got != want {
|
||||
t.Errorf("invalid log output: got %q, want %q", got, want)
|
||||
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -279,6 +443,34 @@ func TestEventTimestamp(t *testing.T) {
|
||||
log.Log().Timestamp().Msg("hello world")
|
||||
|
||||
if got, want := out.String(), `{"foo":"bar","time":"2001-02-03T04:05:06Z","message":"hello world"}`+"\n"; got != want {
|
||||
t.Errorf("invalid log output: got %q, want %q", got, want)
|
||||
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOutputWithoutTimestamp(t *testing.T) {
|
||||
ignoredOut := &bytes.Buffer{}
|
||||
out := &bytes.Buffer{}
|
||||
log := New(ignoredOut).Output(out).With().Str("foo", "bar").Logger()
|
||||
log.Log().Msg("hello world")
|
||||
|
||||
if got, want := out.String(), `{"foo":"bar","message":"hello world"}`+"\n"; got != want {
|
||||
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOutputWithTimestamp(t *testing.T) {
|
||||
TimestampFunc = func() time.Time {
|
||||
return time.Date(2001, time.February, 3, 4, 5, 6, 7, time.UTC)
|
||||
}
|
||||
defer func() {
|
||||
TimestampFunc = time.Now
|
||||
}()
|
||||
ignoredOut := &bytes.Buffer{}
|
||||
out := &bytes.Buffer{}
|
||||
log := New(ignoredOut).Output(out).With().Timestamp().Str("foo", "bar").Logger()
|
||||
log.Log().Msg("hello world")
|
||||
|
||||
if got, want := out.String(), `{"time":"2001-02-03T04:05:06Z","foo":"bar","message":"hello world"}`+"\n"; got != want {
|
||||
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 377 KiB |
+126
@@ -0,0 +1,126 @@
|
||||
package zerolog
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
// Often samples log every ~ 10 events.
|
||||
Often = RandomSampler(10)
|
||||
// Sometimes samples log every ~ 100 events.
|
||||
Sometimes = RandomSampler(100)
|
||||
// Rarely samples log every ~ 1000 events.
|
||||
Rarely = RandomSampler(1000)
|
||||
)
|
||||
|
||||
// Sampler defines an interface to a log sampler.
|
||||
type Sampler interface {
|
||||
// Sample returns true if the event should be part of the sample, false if
|
||||
// the event should be dropped.
|
||||
Sample(lvl Level) bool
|
||||
}
|
||||
|
||||
// RandomSampler use a PRNG to randomly sample an event out of N events,
|
||||
// regardless of their level.
|
||||
type RandomSampler uint32
|
||||
|
||||
// Sample implements the Sampler interface.
|
||||
func (s RandomSampler) Sample(lvl Level) bool {
|
||||
if s <= 0 {
|
||||
return false
|
||||
}
|
||||
if rand.Intn(int(s)) != 0 {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// BasicSampler is a sampler that will send every Nth events, regardless of
|
||||
// there level.
|
||||
type BasicSampler struct {
|
||||
N uint32
|
||||
counter uint32
|
||||
}
|
||||
|
||||
// Sample implements the Sampler interface.
|
||||
func (s *BasicSampler) Sample(lvl Level) bool {
|
||||
c := atomic.AddUint32(&s.counter, 1)
|
||||
return c%s.N == 0
|
||||
}
|
||||
|
||||
// BurstSampler lets Burst events pass per Period then pass the decision to
|
||||
// NextSampler. If Sampler is not set, all subsequent events are rejected.
|
||||
type BurstSampler struct {
|
||||
// Burst is the maximum number of event per period allowed before calling
|
||||
// NextSampler.
|
||||
Burst uint32
|
||||
// Period defines the burst period. If 0, NextSampler is always called.
|
||||
Period time.Duration
|
||||
// NextSampler is the sampler used after the burst is reached. If nil,
|
||||
// events are always rejected after the burst.
|
||||
NextSampler Sampler
|
||||
|
||||
counter uint32
|
||||
resetAt int64
|
||||
}
|
||||
|
||||
// Sample implements the Sampler interface.
|
||||
func (s *BurstSampler) Sample(lvl Level) bool {
|
||||
if s.Burst > 0 && s.Period > 0 {
|
||||
if s.inc() <= s.Burst {
|
||||
return true
|
||||
}
|
||||
}
|
||||
if s.NextSampler == nil {
|
||||
return false
|
||||
}
|
||||
return s.NextSampler.Sample(lvl)
|
||||
}
|
||||
|
||||
func (s *BurstSampler) inc() uint32 {
|
||||
now := time.Now().UnixNano()
|
||||
resetAt := atomic.LoadInt64(&s.resetAt)
|
||||
var c uint32
|
||||
if now > resetAt {
|
||||
c = 1
|
||||
atomic.StoreUint32(&s.counter, c)
|
||||
newResetAt := now + s.Period.Nanoseconds()
|
||||
reset := atomic.CompareAndSwapInt64(&s.resetAt, resetAt, newResetAt)
|
||||
if !reset {
|
||||
// Lost the race with another goroutine trying to reset.
|
||||
c = atomic.AddUint32(&s.counter, 1)
|
||||
}
|
||||
} else {
|
||||
c = atomic.AddUint32(&s.counter, 1)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// LevelSampler applies a different sampler for each level.
|
||||
type LevelSampler struct {
|
||||
DebugSampler, InfoSampler, WarnSampler, ErrorSampler Sampler
|
||||
}
|
||||
|
||||
func (s LevelSampler) Sample(lvl Level) bool {
|
||||
switch lvl {
|
||||
case DebugLevel:
|
||||
if s.DebugSampler != nil {
|
||||
return s.DebugSampler.Sample(lvl)
|
||||
}
|
||||
case InfoLevel:
|
||||
if s.InfoSampler != nil {
|
||||
return s.InfoSampler.Sample(lvl)
|
||||
}
|
||||
case WarnLevel:
|
||||
if s.WarnSampler != nil {
|
||||
return s.WarnSampler.Sample(lvl)
|
||||
}
|
||||
case ErrorLevel:
|
||||
if s.ErrorSampler != nil {
|
||||
return s.ErrorSampler.Sample(lvl)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package zerolog
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var samplers = []struct {
|
||||
name string
|
||||
sampler func() Sampler
|
||||
total int
|
||||
wantMin int
|
||||
wantMax int
|
||||
}{
|
||||
{
|
||||
"BasicSampler",
|
||||
func() Sampler {
|
||||
return &BasicSampler{N: 5}
|
||||
},
|
||||
100, 20, 20,
|
||||
},
|
||||
{
|
||||
"RandomSampler",
|
||||
func() Sampler {
|
||||
return RandomSampler(5)
|
||||
},
|
||||
100, 10, 30,
|
||||
},
|
||||
{
|
||||
"BurstSampler",
|
||||
func() Sampler {
|
||||
return &BurstSampler{Burst: 20, Period: time.Second}
|
||||
},
|
||||
100, 20, 20,
|
||||
},
|
||||
{
|
||||
"BurstSamplerNext",
|
||||
func() Sampler {
|
||||
return &BurstSampler{Burst: 20, Period: time.Second, NextSampler: &BasicSampler{N: 5}}
|
||||
},
|
||||
120, 40, 40,
|
||||
},
|
||||
}
|
||||
|
||||
func TestSamplers(t *testing.T) {
|
||||
for i := range samplers {
|
||||
s := samplers[i]
|
||||
t.Run(s.name, func(t *testing.T) {
|
||||
sampler := s.sampler()
|
||||
got := 0
|
||||
for t := s.total; t > 0; t-- {
|
||||
if sampler.Sample(0) {
|
||||
got++
|
||||
}
|
||||
}
|
||||
if got < s.wantMin || got > s.wantMax {
|
||||
t.Errorf("%s.Sample(0) == true %d on %d, want [%d, %d]", s.name, got, s.total, s.wantMin, s.wantMax)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSamplers(b *testing.B) {
|
||||
for i := range samplers {
|
||||
s := samplers[i]
|
||||
b.Run(s.name, func(b *testing.B) {
|
||||
sampler := s.sampler()
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
sampler.Sample(0)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -44,6 +44,8 @@ func (sw syslogWriter) WriteLevel(level Level, p []byte) (n int, err error) {
|
||||
err = sw.w.Emerg(string(p))
|
||||
case PanicLevel:
|
||||
err = sw.w.Crit(string(p))
|
||||
case NoLevel:
|
||||
err = sw.w.Info(string(p))
|
||||
default:
|
||||
panic("invalid level")
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// +build !windows
|
||||
|
||||
package zerolog
|
||||
|
||||
import "testing"
|
||||
@@ -46,11 +48,13 @@ func TestSyslogWriter(t *testing.T) {
|
||||
log.Info().Msg("info")
|
||||
log.Warn().Msg("warn")
|
||||
log.Error().Msg("error")
|
||||
log.Log().Msg("nolevel")
|
||||
want := []syslogEvent{
|
||||
{"Debug", `{"level":"debug","message":"debug"}` + "\n"},
|
||||
{"Info", `{"level":"info","message":"info"}` + "\n"},
|
||||
{"Warning", `{"level":"warn","message":"warn"}` + "\n"},
|
||||
{"Err", `{"level":"error","message":"error"}` + "\n"},
|
||||
{"Info", `{"message":"nolevel"}` + "\n"},
|
||||
}
|
||||
if got := sw.events; !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("Invalid syslog message routing: want %v, got %v", want, got)
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// +build !windows
|
||||
|
||||
package zerolog
|
||||
|
||||
import (
|
||||
@@ -12,11 +14,13 @@ func TestMultiSyslogWriter(t *testing.T) {
|
||||
log.Info().Msg("info")
|
||||
log.Warn().Msg("warn")
|
||||
log.Error().Msg("error")
|
||||
log.Log().Msg("nolevel")
|
||||
want := []syslogEvent{
|
||||
{"Debug", `{"level":"debug","message":"debug"}` + "\n"},
|
||||
{"Info", `{"level":"info","message":"info"}` + "\n"},
|
||||
{"Warning", `{"level":"warn","message":"warn"}` + "\n"},
|
||||
{"Err", `{"level":"error","message":"error"}` + "\n"},
|
||||
{"Info", `{"message":"nolevel"}` + "\n"},
|
||||
}
|
||||
if got := sw.events; !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("Invalid syslog message routing: want %v, got %v", want, got)
|
||||
|
||||
Reference in New Issue
Block a user