mirror of
https://github.com/rs/zerolog
synced 2026-06-08 17:13:30 +00:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b53826c57a | |||
| 1cc67e6325 | |||
| c2fc1c63dc | |||
| c3d02683c7 | |||
| 1251b38a89 | |||
| c8e50a6043 | |||
| 9a65e7ccd2 | |||
| 89e128fdc1 | |||
| 9d194eb6f5 | |||
| 3ac71fc58d | |||
| 26094019c8 | |||
| 8c682b3b12 | |||
| 96c2125038 | |||
| d76a89fffc | |||
| e26050b2a3 | |||
| 560e8848f1 | |||
| 9e5c06cf0e | |||
| 46339da83a | |||
| 2ed2f2c974 | |||
| 90fdb63d84 |
+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,11 +4,13 @@
|
||||
|
||||
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. Pretty logging on the console is made possible using the provided `zerolog.ConsoleWriter`.
|
||||
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
|
||||
|
||||
@@ -16,6 +18,7 @@ To keep the code base and the API simple, zerolog focuses on JSON logging only.
|
||||
* Low to zero allocation
|
||||
* Level logging
|
||||
* Sampling
|
||||
* Hooks
|
||||
* Contextual fields
|
||||
* `context.Context` integration
|
||||
* `net/http` helpers
|
||||
@@ -29,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")
|
||||
|
||||
@@ -152,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
|
||||
@@ -247,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.
|
||||
@@ -273,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):
|
||||
|
||||
@@ -285,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:
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
+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
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,11 +21,11 @@ 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
|
||||
@@ -46,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')
|
||||
@@ -66,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.
|
||||
@@ -74,9 +74,17 @@ 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 = json.AppendString(json.AppendKey(e.buf, MessageFieldName), msg)
|
||||
}
|
||||
@@ -93,24 +101,15 @@ 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 = json.AppendString(json.AppendKey(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.enabled {
|
||||
if e == nil {
|
||||
return e
|
||||
}
|
||||
e.buf = appendFields(e.buf, fields)
|
||||
@@ -120,7 +119,7 @@ func (e *Event) Fields(fields map[string]interface{}) *Event {
|
||||
// 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(json.AppendKey(e.buf, key), dict.buf...), '}')
|
||||
@@ -139,7 +138,7 @@ func Dict() *Event {
|
||||
// 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.enabled {
|
||||
if e == nil {
|
||||
return e
|
||||
}
|
||||
e.buf = json.AppendKey(e.buf, key)
|
||||
@@ -170,7 +169,7 @@ func (e *Event) appendObject(obj LogObjectMarshaler) {
|
||||
|
||||
// Object marshals an object that implement the LogObjectMarshaler interface.
|
||||
func (e *Event) Object(key string, obj LogObjectMarshaler) *Event {
|
||||
if !e.enabled {
|
||||
if e == nil {
|
||||
return e
|
||||
}
|
||||
e.buf = json.AppendKey(e.buf, key)
|
||||
@@ -180,7 +179,7 @@ func (e *Event) Object(key string, obj LogObjectMarshaler) *Event {
|
||||
|
||||
// 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 {
|
||||
if e == nil {
|
||||
return e
|
||||
}
|
||||
e.buf = json.AppendString(json.AppendKey(e.buf, key), val)
|
||||
@@ -189,7 +188,7 @@ func (e *Event) Str(key, val string) *Event {
|
||||
|
||||
// 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.enabled {
|
||||
if e == nil {
|
||||
return e
|
||||
}
|
||||
e.buf = json.AppendStrings(json.AppendKey(e.buf, key), vals)
|
||||
@@ -201,7 +200,7 @@ func (e *Event) Strs(key string, vals []string) *Event {
|
||||
// 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.enabled {
|
||||
if e == nil {
|
||||
return e
|
||||
}
|
||||
e.buf = json.AppendBytes(json.AppendKey(e.buf, key), val)
|
||||
@@ -211,7 +210,7 @@ func (e *Event) Bytes(key string, val []byte) *Event {
|
||||
// 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
|
||||
}
|
||||
if err != nil {
|
||||
@@ -223,7 +222,7 @@ func (e *Event) AnErr(key string, err error) *Event {
|
||||
// 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.enabled {
|
||||
if e == nil {
|
||||
return e
|
||||
}
|
||||
e.buf = json.AppendErrors(json.AppendKey(e.buf, key), errs)
|
||||
@@ -234,7 +233,7 @@ func (e *Event) Errs(key string, errs []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
|
||||
}
|
||||
if err != nil {
|
||||
@@ -245,7 +244,7 @@ func (e *Event) Err(err error) *Event {
|
||||
|
||||
// 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 = json.AppendBool(json.AppendKey(e.buf, key), b)
|
||||
@@ -254,7 +253,7 @@ func (e *Event) Bool(key string, b bool) *Event {
|
||||
|
||||
// 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.enabled {
|
||||
if e == nil {
|
||||
return e
|
||||
}
|
||||
e.buf = json.AppendBools(json.AppendKey(e.buf, key), b)
|
||||
@@ -263,7 +262,7 @@ func (e *Event) Bools(key string, b []bool) *Event {
|
||||
|
||||
// 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 = json.AppendInt(json.AppendKey(e.buf, key), i)
|
||||
@@ -272,7 +271,7 @@ func (e *Event) Int(key string, i int) *Event {
|
||||
|
||||
// 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.enabled {
|
||||
if e == nil {
|
||||
return e
|
||||
}
|
||||
e.buf = json.AppendInts(json.AppendKey(e.buf, key), i)
|
||||
@@ -281,7 +280,7 @@ func (e *Event) Ints(key string, i []int) *Event {
|
||||
|
||||
// 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 = json.AppendInt8(json.AppendKey(e.buf, key), i)
|
||||
@@ -290,7 +289,7 @@ func (e *Event) Int8(key string, i int8) *Event {
|
||||
|
||||
// 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.enabled {
|
||||
if e == nil {
|
||||
return e
|
||||
}
|
||||
e.buf = json.AppendInts8(json.AppendKey(e.buf, key), i)
|
||||
@@ -299,7 +298,7 @@ func (e *Event) Ints8(key string, i []int8) *Event {
|
||||
|
||||
// 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 = json.AppendInt16(json.AppendKey(e.buf, key), i)
|
||||
@@ -308,7 +307,7 @@ func (e *Event) Int16(key string, i int16) *Event {
|
||||
|
||||
// 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.enabled {
|
||||
if e == nil {
|
||||
return e
|
||||
}
|
||||
e.buf = json.AppendInts16(json.AppendKey(e.buf, key), i)
|
||||
@@ -317,7 +316,7 @@ func (e *Event) Ints16(key string, i []int16) *Event {
|
||||
|
||||
// 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 = json.AppendInt32(json.AppendKey(e.buf, key), i)
|
||||
@@ -326,7 +325,7 @@ func (e *Event) Int32(key string, i int32) *Event {
|
||||
|
||||
// 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.enabled {
|
||||
if e == nil {
|
||||
return e
|
||||
}
|
||||
e.buf = json.AppendInts32(json.AppendKey(e.buf, key), i)
|
||||
@@ -335,7 +334,7 @@ func (e *Event) Ints32(key string, i []int32) *Event {
|
||||
|
||||
// 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 = json.AppendInt64(json.AppendKey(e.buf, key), i)
|
||||
@@ -344,7 +343,7 @@ func (e *Event) Int64(key string, i int64) *Event {
|
||||
|
||||
// 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.enabled {
|
||||
if e == nil {
|
||||
return e
|
||||
}
|
||||
e.buf = json.AppendInts64(json.AppendKey(e.buf, key), i)
|
||||
@@ -353,7 +352,7 @@ func (e *Event) Ints64(key string, i []int64) *Event {
|
||||
|
||||
// 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 = json.AppendUint(json.AppendKey(e.buf, key), i)
|
||||
@@ -362,7 +361,7 @@ func (e *Event) Uint(key string, i uint) *Event {
|
||||
|
||||
// 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.enabled {
|
||||
if e == nil {
|
||||
return e
|
||||
}
|
||||
e.buf = json.AppendUints(json.AppendKey(e.buf, key), i)
|
||||
@@ -371,7 +370,7 @@ func (e *Event) Uints(key string, i []uint) *Event {
|
||||
|
||||
// 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 = json.AppendUint8(json.AppendKey(e.buf, key), i)
|
||||
@@ -380,7 +379,7 @@ func (e *Event) Uint8(key string, i uint8) *Event {
|
||||
|
||||
// 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.enabled {
|
||||
if e == nil {
|
||||
return e
|
||||
}
|
||||
e.buf = json.AppendUints8(json.AppendKey(e.buf, key), i)
|
||||
@@ -389,7 +388,7 @@ func (e *Event) Uints8(key string, i []uint8) *Event {
|
||||
|
||||
// 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 = json.AppendUint16(json.AppendKey(e.buf, key), i)
|
||||
@@ -398,7 +397,7 @@ func (e *Event) Uint16(key string, i uint16) *Event {
|
||||
|
||||
// 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.enabled {
|
||||
if e == nil {
|
||||
return e
|
||||
}
|
||||
e.buf = json.AppendUints16(json.AppendKey(e.buf, key), i)
|
||||
@@ -407,7 +406,7 @@ func (e *Event) Uints16(key string, i []uint16) *Event {
|
||||
|
||||
// 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 = json.AppendUint32(json.AppendKey(e.buf, key), i)
|
||||
@@ -416,7 +415,7 @@ func (e *Event) Uint32(key string, i uint32) *Event {
|
||||
|
||||
// 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.enabled {
|
||||
if e == nil {
|
||||
return e
|
||||
}
|
||||
e.buf = json.AppendUints32(json.AppendKey(e.buf, key), i)
|
||||
@@ -425,7 +424,7 @@ func (e *Event) Uints32(key string, i []uint32) *Event {
|
||||
|
||||
// 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 = json.AppendUint64(json.AppendKey(e.buf, key), i)
|
||||
@@ -434,7 +433,7 @@ func (e *Event) Uint64(key string, i uint64) *Event {
|
||||
|
||||
// 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.enabled {
|
||||
if e == nil {
|
||||
return e
|
||||
}
|
||||
e.buf = json.AppendUints64(json.AppendKey(e.buf, key), i)
|
||||
@@ -443,7 +442,7 @@ func (e *Event) Uints64(key string, i []uint64) *Event {
|
||||
|
||||
// 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 = json.AppendFloat32(json.AppendKey(e.buf, key), f)
|
||||
@@ -452,7 +451,7 @@ func (e *Event) Float32(key string, f float32) *Event {
|
||||
|
||||
// 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.enabled {
|
||||
if e == nil {
|
||||
return e
|
||||
}
|
||||
e.buf = json.AppendFloats32(json.AppendKey(e.buf, key), f)
|
||||
@@ -461,7 +460,7 @@ func (e *Event) Floats32(key string, f []float32) *Event {
|
||||
|
||||
// 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 = json.AppendFloat64(json.AppendKey(e.buf, key), f)
|
||||
@@ -470,7 +469,7 @@ func (e *Event) Float64(key string, f float64) *Event {
|
||||
|
||||
// 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.enabled {
|
||||
if e == nil {
|
||||
return e
|
||||
}
|
||||
e.buf = json.AppendFloats64(json.AppendKey(e.buf, key), f)
|
||||
@@ -480,7 +479,7 @@ func (e *Event) Floats64(key string, f []float64) *Event {
|
||||
// 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 = json.AppendTime(json.AppendKey(e.buf, TimestampFieldName), TimestampFunc(), TimeFieldFormat)
|
||||
@@ -489,7 +488,7 @@ func (e *Event) Timestamp() *Event {
|
||||
|
||||
// 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 = json.AppendTime(json.AppendKey(e.buf, key), t, TimeFieldFormat)
|
||||
@@ -498,7 +497,7 @@ func (e *Event) Time(key string, t time.Time) *Event {
|
||||
|
||||
// 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.enabled {
|
||||
if e == nil {
|
||||
return e
|
||||
}
|
||||
e.buf = json.AppendTimes(json.AppendKey(e.buf, key), t, TimeFieldFormat)
|
||||
@@ -509,7 +508,7 @@ func (e *Event) Times(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 = json.AppendDuration(json.AppendKey(e.buf, key), d, DurationFieldUnit, DurationFieldInteger)
|
||||
@@ -520,7 +519,7 @@ func (e *Event) Dur(key string, d time.Duration) *Event {
|
||||
// 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.enabled {
|
||||
if e == nil {
|
||||
return e
|
||||
}
|
||||
e.buf = json.AppendDurations(json.AppendKey(e.buf, key), d, DurationFieldUnit, DurationFieldInteger)
|
||||
@@ -531,7 +530,7 @@ func (e *Event) Durs(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
|
||||
@@ -544,7 +543,7 @@ func (e *Event) TimeDiff(key string, t time.Time, start time.Time) *Event {
|
||||
|
||||
// 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
|
||||
}
|
||||
if obj, ok := i.(LogObjectMarshaler); ok {
|
||||
|
||||
@@ -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.
|
||||
|
||||
+29
-18
@@ -15,7 +15,7 @@ import (
|
||||
|
||||
// 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())
|
||||
}
|
||||
|
||||
@@ -23,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)
|
||||
})
|
||||
}
|
||||
@@ -35,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)
|
||||
})
|
||||
}
|
||||
@@ -48,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)
|
||||
})
|
||||
}
|
||||
@@ -61,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)
|
||||
})
|
||||
}
|
||||
@@ -75,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)
|
||||
})
|
||||
@@ -90,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)
|
||||
})
|
||||
@@ -105,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)
|
||||
})
|
||||
@@ -136,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())
|
||||
|
||||
+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("")
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -62,17 +62,34 @@
|
||||
//
|
||||
// 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"
|
||||
"strconv"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/rs/zerolog/internal/json"
|
||||
)
|
||||
@@ -93,6 +110,8 @@ const (
|
||||
FatalLevel
|
||||
// PanicLevel defines panic log level.
|
||||
PanicLevel
|
||||
// NoLevel defines an absent log level.
|
||||
NoLevel
|
||||
// Disabled disables the logger.
|
||||
Disabled
|
||||
)
|
||||
@@ -111,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
|
||||
@@ -134,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
|
||||
@@ -166,8 +176,14 @@ func Nop() Logger {
|
||||
func (l Logger) Output(w io.Writer) Logger {
|
||||
l2 := New(w)
|
||||
l2.level = l.level
|
||||
l2.sample = l.sample
|
||||
copy(l2.context, l.context)
|
||||
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
|
||||
}
|
||||
|
||||
@@ -184,84 +200,86 @@ 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 {
|
||||
func (l *Logger) WithLevel(level Level) *Event {
|
||||
switch level {
|
||||
case DebugLevel:
|
||||
return l.Debug()
|
||||
@@ -275,8 +293,10 @@ func (l Logger) WithLevel(level Level) *Event {
|
||||
return l.Fatal()
|
||||
case PanicLevel:
|
||||
return l.Panic()
|
||||
case NoLevel:
|
||||
return l.Log()
|
||||
case Disabled:
|
||||
return disabledEvent
|
||||
return nil
|
||||
default:
|
||||
panic("zerolog: WithLevel(): invalid level: " + strconv.Itoa(int(level)))
|
||||
}
|
||||
@@ -286,10 +306,24 @@ func (l Logger) WithLevel(level Level) *Event {
|
||||
// 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
|
||||
@@ -304,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 = 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
|
||||
}
|
||||
|
||||
+28
-4
@@ -27,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.
|
||||
@@ -76,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.
|
||||
//
|
||||
@@ -84,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)
|
||||
}
|
||||
|
||||
+46
-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() {
|
||||
|
||||
+72
-3
@@ -273,7 +273,8 @@ func TestFieldsDisabled(t *testing.T) {
|
||||
|
||||
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:\ngot: %v\nwant: %v", got, want)
|
||||
}
|
||||
@@ -298,6 +299,42 @@ func TestLevel(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
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)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Info", func(t *testing.T) {
|
||||
out := &bytes.Buffer{}
|
||||
log := New(out).Level(InfoLevel)
|
||||
@@ -310,12 +347,12 @@ func TestLevel(t *testing.T) {
|
||||
|
||||
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 {
|
||||
if got, want := out.String(), "{\"i\":2}\n{\"i\":4}\n"; got != want {
|
||||
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
|
||||
}
|
||||
}
|
||||
@@ -351,10 +388,12 @@ 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
|
||||
@@ -364,10 +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)
|
||||
@@ -405,3 +446,31 @@ func TestEventTimestamp(t *testing.T) {
|
||||
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