1
0
mirror of https://github.com/rs/zerolog synced 2026-06-08 17:13:30 +00:00

Compare commits

...

26 Commits

Author SHA1 Message Date
Olivier Poitrey 56a970de51 Add RawJSON field type 2018-02-12 16:05:27 -08:00
Olivier Poitrey 9a92fd2536 Fix typoes 2018-02-10 18:57:53 -08:00
Olivier Poitrey 7d8f9e5cf0 Fix unit tests 2018-02-08 21:29:16 -08:00
Dan Gillis 8eb285b62b Updates to README.md to help with readability (#32)
* Added missed >m< in github.com
* Added backquotes to denote "code" where appropriate
* Cleanup based on feedback
* Added three examples from README
* Removed Output for 3 examples
* Updated Setting Global Log Level section w/ flags
* Removed unnecessary blank lines
* Moved flags from init into main
* Update log_example_test.go
* Cosmetic change based on Olivier's feedback
* Pushed back to original
* New Examples for Log package
* Removed extra spaces
* Reorganized file and added examples
2018-02-08 21:00:09 -08:00
Olivier Poitrey 27e0a22cbc Add the ability to capture the logger caller file and line number
Fixes #34, #22
2018-02-07 13:54:26 -08:00
Olivier Poitrey fcbdf23e9e Use new hook internally to handle timestamp in context 2018-02-07 13:31:00 -08:00
Olivier Poitrey cbec2377ee Add benchmarks for context 2018-02-07 13:07:46 -08:00
Olivier Poitrey b53826c57a Add go 1.9 to travis file 2018-01-04 11:28:02 -08:00
zy 1cc67e6325 fix: performance link to invalid section in README.md (#30)
- No performance section in README.md, change link to performance to benchmarks
2018-01-04 11:19:48 -08:00
nogoegst c2fc1c63dc Add missing WithLevel method to log package (#27) 2017-12-14 10:33:32 -08:00
Rodrigo Coelho c3d02683c7 Add hook support (#24) 2017-12-01 10:52:37 -07:00
millerlogic 1251b38a89 Fix sampler for low Burst values (#19) 2017-11-27 10:05:35 -08:00
Rodrigo Coelho c8e50a6043 Fix tests for Windows. (#23) 2017-11-27 10:01:32 -08:00
Ravi Raju 9a65e7ccd2 Fix Output with existing context (fix #20)
Also includes tests for Output()
2017-11-08 10:47:56 -08:00
Giovanni Bajo 89e128fdc1 Speed up operations when logging is disabled. (#18)
Low-level optimizations to help the compiler generate better code
when logging is disabled. Measured improvement is ~30% on amd64
(from 21 ns/op to 16 ns/op).
2017-11-05 05:22:20 -08:00
Olivier Poitrey 9d194eb6f5 Remove redundant condition 2017-09-11 14:52:32 -07:00
Olivier Poitrey 3ac71fc58d Simplify copies 2017-09-03 11:01:28 -07:00
Olivier Poitrey 26094019c8 Fix godoc 2017-09-01 20:07:47 -07:00
Olivier Poitrey 8c682b3b12 Add Print and Printf top level methods 2017-09-01 19:59:48 -07:00
Olivier Poitrey 96c2125038 Update readme to reference Mário Freitas' benchmarks 2017-09-01 19:45:46 -07:00
Olivier Poitrey d76a89fffc Add benchmark for context appending 2017-08-29 23:10:40 -07:00
Olivier Poitrey e26050b2a3 Improve hlog handlers performance by switching to pointer logger
Update request logger's context thru its pointer in order to avoid
multiple copies/allocations.
2017-08-29 22:53:32 -07:00
Olivier Poitrey 560e8848f1 Remove the SampleFieldName global 2017-08-29 10:35:43 -07:00
Olivier Poitrey 9e5c06cf0e Add more advanced sampling modes 2017-08-28 23:30:54 -07:00
Olivier Poitrey 46339da83a Improve doc for WithContext 2017-08-12 16:16:31 -07:00
Olivier Poitrey 2ed2f2c974 Fix pretty logging and add a screenshot to the README 2017-08-05 20:45:41 -07:00
25 changed files with 1514 additions and 266 deletions
+2 -1
View File
@@ -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 ./...
+174 -40
View File
@@ -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.
Uber's [zap](https://godoc.org/go.uber.org/zap) library pioneered this approach. Zerolog is taking this concept to the next level with a 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`.
![](pretty.png)
## Features
@@ -16,41 +18,150 @@ 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
* Pretty logging for development
## Usage
## Installation
```go
go get -u github.com/rs/zerolog/log
```
## Getting Started
### Simple Logging Example
For simple logging, import the global logger package **github.com/rs/zerolog/log**
```go
package main
import (
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
)
func main() {
// UNIX Time is faster and smaller than most timestamps
// If you set zerolog.TimeFieldFormat to an empty string,
// logs will write with UNIX time
zerolog.TimeFieldFormat = ""
log.Print("hello world")
}
// Output: {"time":1516134303,"level":"debug","message":"hello world"}
```
> Note: The default log level for `log.Print` is *debug*
----
### Leveled Logging
#### Simple Leveled Logging Example
```go
import "github.com/rs/zerolog/log"
package main
import (
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
)
func main() {
zerolog.TimeFieldFormat = ""
log.Info().Msg("hello world")
}
// Output: {"time":1516134303,"level":"info","message":"hello world"}
```
### A global logger can be use for simple logging
**zerolog** allows for logging at the following levels (from highest to lowest):
- panic (`zerolog.PanicLevel`, 5)
- fatal (`zerolog.FatalLevel`, 4)
- error (`zerolog.ErrorLevel`, 3)
- warn (`zerolog.WarnLevel`, 2)
- info (`zerolog.InfoLevel`, 1)
- debug (`zerolog.DebugLevel`, 0)
You can set the Global logging level to any of these options using the `SetGlobalLevel` function in the zerolog package, passing in one of the given constants above, e.g. `zerolog.InfoLevel` would be the "info" level. Whichever level is chosen, all logs with a level greater than or equal to that level will be written. To turn off logging entirely, pass the `zerolog.Disabled` constant.
#### Setting Global Log Level
This example uses command-line flags to demonstrate various outputs depending on the chosen log level.
```go
log.Info().Msg("hello world")
package main
// Output: {"level":"info","time":1494567715,"message":"hello world"}
import (
"flag"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
)
func main() {
zerolog.TimeFieldFormat = ""
debug := flag.Bool("debug", false, "sets log level to debug")
flag.Parse()
// Default level for this example is info, unless debug flag is present
zerolog.SetGlobalLevel(zerolog.InfoLevel)
if *debug {
zerolog.SetGlobalLevel(zerolog.DebugLevel)
}
log.Debug().Msg("This message appears only when log level set to Debug")
log.Info().Msg("This message appears when log level set to Debug or Info")
if e := log.Debug(); e.Enabled() {
// Compute log output only if enabled.
value := "bar"
e.Str("foo", value).Msg("some debug message")
}
}
```
Info Output (no flag)
```bash
$ ./logLevelExample
{"time":1516387492,"level":"info","message":"This message appears when log level set to Debug or Info"}
```
NOTE: To import the global logger, import the `log` subpackage `github.com/rs/zerolog/log`.
Debug Output (debug flag set)
```bash
$ ./logLevelExample -debug
{"time":1516387573,"level":"debug","message":"This message appears only when log level set to Debug"}
{"time":1516387573,"level":"info","message":"This message appears when log level set to Debug or Info"}
{"time":1516387573,"level":"debug","foo":"bar","message":"some debug message"}
```
#### Logging Fatal Messages
```go
log.Fatal().
Err(err).
Str("service", service).
Msgf("Cannot start %s", service)
package main
// Output: {"level":"fatal","time":1494567715,"message":"Cannot start myservice","error":"some error","service":"myservice"}
// Exit 1
import (
"errors"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
)
func main() {
err := errors.New("A repo man spends his life getting into tense situations")
service := "myservice"
zerolog.TimeFieldFormat = ""
log.Fatal().
Err(err).
Str("service", service).
Msgf("Cannot start %s", service)
}
// Output: {"time":1516133263,"level":"fatal","error":"A repo man spends his life getting into tense situations","service":"myservice","message":"Cannot start myservice"}
// exit status 1
```
> NOTE: Using `Msgf` generates one allocation even when the logger is disabled.
----------------
### Contextual Logging
NOTE: Using `Msgf` generates one allocation even when the logger is disabled.
### Fields can be added to log messages
#### Fields can be added to log messages
```go
log.Info().
Str("foo", "bar").
@@ -81,22 +192,7 @@ sublogger.Info().Msg("hello world")
// Output: {"level":"info","time":1494567715,"message":"hello world","component":"foo"}
```
### Level logging
```go
zerolog.SetGlobalLevel(zerolog.InfoLevel)
log.Debug().Msg("filtered out message")
log.Info().Msg("routed message")
if e := log.Debug(); e.Enabled() {
// Compute log output only if enabled.
value := compute()
e.Str("foo": value).Msg("some debug message")
}
// Output: {"level":"info","time":1494567715,"message":"routed message"}
```
### Pretty logging
@@ -152,10 +248,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 +378,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 +403,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 +415,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:
@@ -326,4 +461,3 @@ Log a static string, without any context or `printf`-style templating:
| logrus | 1244 ns/op | 1505 B/op | 27 allocs/op |
| apex/log | 2751 ns/op | 584 B/op | 11 allocs/op |
| log15 | 5181 ns/op | 1592 B/op | 26 allocs/op |
+139
View File
@@ -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()
@@ -203,3 +215,130 @@ func BenchmarkLogFieldType(b *testing.B) {
})
}
}
func BenchmarkContextFieldType(b *testing.B) {
oldFormat := TimeFieldFormat
TimeFieldFormat = ""
defer func() { TimeFieldFormat = oldFormat }()
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(c Context) Context{
"Bool": func(c Context) Context {
return c.Bool("k", bools[0])
},
"Bools": func(c Context) Context {
return c.Bools("k", bools)
},
"Int": func(c Context) Context {
return c.Int("k", ints[0])
},
"Ints": func(c Context) Context {
return c.Ints("k", ints)
},
"Float": func(c Context) Context {
return c.Float64("k", floats[0])
},
"Floats": func(c Context) Context {
return c.Floats64("k", floats)
},
"Str": func(c Context) Context {
return c.Str("k", strings[0])
},
"Strs": func(c Context) Context {
return c.Strs("k", strings)
},
"Err": func(c Context) Context {
return c.Err(errs[0])
},
"Errs": func(c Context) Context {
return c.Errs("k", errs)
},
"Time": func(c Context) Context {
return c.Time("k", times[0])
},
"Times": func(c Context) Context {
return c.Times("k", times)
},
"Dur": func(c Context) Context {
return c.Dur("k", durations[0])
},
"Durs": func(c Context) Context {
return c.Durs("k", durations)
},
"Interface": func(c Context) Context {
return c.Interface("k", interfaces[0])
},
"Interfaces": func(c Context) Context {
return c.Interface("k", interfaces)
},
"Interface(Object)": func(c Context) Context {
return c.Interface("k", objects[0])
},
"Interface(Objects)": func(c Context) Context {
return c.Interface("k", objects)
},
"Object": func(c Context) Context {
return c.Object("k", objects[0])
},
"Timestamp": func(c Context) Context {
return c.Timestamp()
},
}
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() {
l := f(logger.With()).Logger()
l.Info().Msg("")
}
})
})
}
}
+2 -2
View File
@@ -95,7 +95,7 @@ func colorize(s interface{}, color int, enabled bool) string {
func levelColor(level string) int {
switch level {
case "debug":
return cGray
return cMagenta
case "info":
return cGreen
case "warn":
@@ -109,7 +109,7 @@ func levelColor(level string) int {
func needsQuote(s string) bool {
for i := range s {
if s[i] < 0x20 || s[i] > 0x7e || s[i] == '\\' || s[i] == '"' {
if s[i] < 0x20 || s[i] > 0x7e || s[i] == ' ' || s[i] == '\\' || s[i] == '"' {
return true
}
}
+32 -5
View File
@@ -79,6 +79,15 @@ func (c Context) Bytes(key string, val []byte) Context {
return c
}
// RawJSON adds already encoded JSON to context.
//
// No sanity check is performed on b; it must not contain carriage returns and
// be valid JSON.
func (c Context) RawJSON(key string, b []byte) Context {
c.l.context = append(json.AppendKey(c.l.context, key), b...)
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 {
if err != nil {
@@ -258,14 +267,18 @@ func (c Context) Floats64(key string, f []float64) Context {
return c
}
type timestampHook struct{}
func (ts timestampHook) Run(e *Event, level Level, msg string) {
e.Timestamp()
}
var th = timestampHook{}
// Timestamp adds the current local time as UNIX timestamp to the logger context with the "time" key.
// To customize the key name, change zerolog.TimestampFieldName.
func (c Context) Timestamp() Context {
if len(c.l.context) > 0 {
c.l.context[0] = 1
} else {
c.l.context = append(c.l.context, 1)
}
c.l = c.l.Hook(th)
return c
}
@@ -298,3 +311,17 @@ func (c Context) Interface(key string, i interface{}) Context {
c.l.context = json.AppendInterface(json.AppendKey(c.l.context, key), i)
return c
}
type callerHook struct{}
func (ch callerHook) Run(e *Event, level Level, msg string) {
e.caller(4)
}
var ch = callerHook{}
// Caller adds the file:line of the caller with the zerolog.CallerFieldName key.
func (c Context) Caller() Context {
c.l = c.l.Hook(ch)
return c
}
+21 -4
View File
@@ -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
View File
@@ -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")
}
}
+99 -63
View File
@@ -4,6 +4,8 @@ import (
"fmt"
"io/ioutil"
"os"
"runtime"
"strconv"
"sync"
"time"
@@ -21,11 +23,12 @@ 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)
ch []Hook // hooks from context
h []Hook
}
// LogObjectMarshaler provides a strongly-typed and encoding-agnostic interface
@@ -46,15 +49,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 +69,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 +77,25 @@ 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.ch) > 0 {
e.ch[0].Run(e, e.level, msg)
if len(e.ch) > 1 {
for _, hook := range e.ch[1:] {
hook.Run(e, e.level, msg)
}
}
}
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 +112,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 +130,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 +149,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 +180,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 +190,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 +199,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,17 +211,26 @@ 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)
return e
}
// RawJSON adds already encoded JSON to the log line under key.
//
// No sanity check is performed on b; it must not contain carriage returns and
// be valid JSON.
func (e *Event) RawJSON(key string, b []byte) *Event {
e.buf = append(json.AppendKey(e.buf, key), b...)
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
}
if err != nil {
@@ -223,7 +242,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 +253,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 +264,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 +273,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 +282,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 +291,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 +300,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 +309,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 +318,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 +327,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 +336,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 +345,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 +354,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 +363,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 +372,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 +381,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 +390,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 +399,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 +408,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 +417,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 +426,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 +435,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 +444,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 +453,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 +462,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 +471,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 +480,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 +489,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 +499,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 +508,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 +517,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 +528,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 +539,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 +550,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 +563,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 {
@@ -553,3 +572,20 @@ func (e *Event) Interface(key string, i interface{}) *Event {
e.buf = json.AppendInterface(json.AppendKey(e.buf, key), i)
return e
}
// Caller adds the file:line of the caller with the zerolog.CallerFieldName key.
func (e *Event) Caller() *Event {
return e.caller(2)
}
func (e *Event) caller(skip int) *Event {
if e == nil {
return e
}
_, file, line, ok := runtime.Caller(skip)
if !ok {
return e
}
e.buf = json.AppendString(json.AppendKey(e.buf, CallerFieldName), file+":"+strconv.Itoa(line))
return e
}
+2 -2
View File
@@ -16,8 +16,8 @@ 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"
// CallerFieldName is the field name used for caller field.
CallerFieldName = "caller"
// TimeFieldFormat defines the time format of the Time field type.
// If set to an empty string, the time is formatted as an UNIX timestamp
+30 -19
View File
@@ -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)
})
@@ -115,7 +124,7 @@ func RefererHandler(fieldKey string) func(next http.Handler) http.Handler {
type idKey struct{}
// IDFromRequest returns the unique id accociated to the request if any.
// IDFromRequest returns the unique id associated to the request if any.
func IDFromRequest(r *http.Request) (id xid.ID, ok bool) {
if r == nil {
return
@@ -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())
+1 -1
View File
@@ -67,5 +67,5 @@ func Example_handler() {
h.ServeHTTP(httptest.NewRecorder(), &http.Request{})
// Output: {"time":"2001-02-03T04:05:06Z","level":"info","role":"my-service","host":"local-hostname","user":"current user","status":"ok","message":"Something happend"}
// Output: {"level":"info","role":"my-service","host":"local-hostname","user":"current user","status":"ok","time":"2001-02-03T04:05:06Z","message":"Something happend"}
}
+86 -22
View File
@@ -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{})
}
})
}
+51
View File
@@ -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
View File
@@ -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("")
}
})
})
}
+104 -86
View File
@@ -62,19 +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"
)
// Level defines log levels.
@@ -93,6 +108,8 @@ const (
FatalLevel
// PanicLevel defines panic log level.
PanicLevel
// NoLevel defines an absent log level.
NoLevel
// Disabled disables the logger.
Disabled
)
@@ -111,21 +128,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 +142,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 +174,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
}
@@ -177,91 +191,90 @@ func (l Logger) With() Context {
l.context = make([]byte, 0, 500)
if context != nil {
l.context = append(l.context, context...)
} else {
// first byte of context is presence of timestamp or not
l.context = append(l.context, 0)
}
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, 0, 500)
}
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 +288,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 +301,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 +333,33 @@ 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 {
e.ch = l.hooks
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(l.context) > 0 {
if len(e.buf) > 1 {
e.buf = append(e.buf, ',')
}
e.buf = append(e.buf, l.context[1:]...)
e.buf = append(e.buf, l.context...)
}
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
}
+30 -6
View File
@@ -22,14 +22,19 @@ func With() zerolog.Context {
return Logger.With()
}
// Level crestes a child logger with the minium accepted level set to level.
// Level creates a child logger with the minimum accepted level set to level.
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,16 +81,35 @@ 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.
// zerolog.Disabled will still disable events produced by this method.
//
// You must call Msg on the returned event in order to send the event.
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)
}
+141
View File
@@ -0,0 +1,141 @@
package log_test
import (
"errors"
"flag"
"os"
"time"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
)
// setup would normally be an init() function, however, there seems
// to be something awry with the testing framework when we set the
// global Logger from an init()
func setup() {
// UNIX Time is faster and smaller than most timestamps
// If you set zerolog.TimeFieldFormat to an empty string,
// logs will write with UNIX time
zerolog.TimeFieldFormat = ""
// In order to always output a static time to stdout for these
// examples to pass, we need to override zerolog.TimestampFunc
// and log.Logger globals -- you would not normally need to do this
zerolog.TimestampFunc = func() time.Time {
return time.Date(2008, 1, 8, 17, 5, 05, 0, time.UTC)
}
log.Logger = zerolog.New(os.Stdout).With().Timestamp().Logger()
}
// Simple logging example using the Print function in the log package
// Note that both Print and Printf are at the debug log level by default
func ExamplePrint() {
setup()
log.Print("hello world")
// Output: {"level":"debug","time":1199811905,"message":"hello world"}
}
// Simple logging example using the Printf function in the log package
func ExamplePrintf() {
setup()
log.Printf("hello %s", "world")
// Output: {"level":"debug","time":1199811905,"message":"hello world"}
}
// Example of a log with no particular "level"
func ExampleLog() {
setup()
log.Log().Msg("hello world")
// Output: {"time":1199811905,"message":"hello world"}
}
// Example of a log at a particular "level" (in this case, "debug")
func ExampleDebug() {
setup()
log.Debug().Msg("hello world")
// Output: {"level":"debug","time":1199811905,"message":"hello world"}
}
// Example of a log at a particular "level" (in this case, "info")
func ExampleInfo() {
setup()
log.Info().Msg("hello world")
// Output: {"level":"info","time":1199811905,"message":"hello world"}
}
// Example of a log at a particular "level" (in this case, "warn")
func ExampleWarn() {
setup()
log.Warn().Msg("hello world")
// Output: {"level":"warn","time":1199811905,"message":"hello world"}
}
// Example of a log at a particular "level" (in this case, "error")
func ExampleError() {
setup()
log.Error().Msg("hello world")
// Output: {"level":"error","time":1199811905,"message":"hello world"}
}
// Example of a log at a particular "level" (in this case, "fatal")
func ExampleFatal() {
setup()
err := errors.New("A repo man spends his life getting into tense situations")
service := "myservice"
log.Fatal().
Err(err).
Str("service", service).
Msgf("Cannot start %s", service)
// Outputs: {"level":"fatal","time":1199811905,"error":"A repo man spends his life getting into tense situations","service":"myservice","message":"Cannot start myservice"}
}
// TODO: Panic
// This example uses command-line flags to demonstrate various outputs
// depending on the chosen log level.
func Example_LevelFlag() {
setup()
debug := flag.Bool("debug", false, "sets log level to debug")
flag.Parse()
// Default level for this example is info, unless debug flag is present
zerolog.SetGlobalLevel(zerolog.InfoLevel)
if *debug {
zerolog.SetGlobalLevel(zerolog.DebugLevel)
}
log.Debug().Msg("This message appears only when log level set to Debug")
log.Info().Msg("This message appears when log level set to Debug or Info")
if e := log.Debug(); e.Enabled() {
// Compute log output only if enabled.
value := "bar"
e.Str("foo", value).Msg("some debug message")
}
// Output: {"level":"info","time":1199811905,"message":"This message appears when log level set to Debug or Info"}
}
// TODO: Output
// TODO: With
// TODO: Level
// TODO: Sample
// TODO: Hook
// TODO: WithLevel
// TODO: Ctx
+46 -3
View File
@@ -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() {
+87 -9
View File
@@ -3,7 +3,9 @@ package zerolog
import (
"bytes"
"errors"
"fmt"
"reflect"
"runtime"
"testing"
"time"
)
@@ -74,8 +76,9 @@ func TestInfo(t *testing.T) {
func TestWith(t *testing.T) {
out := &bytes.Buffer{}
log := New(out).With().
ctx := New(out).With().
Str("foo", "bar").
RawJSON("json", []byte(`{"some":"json"}`)).
AnErr("some_err", nil).
Err(errors.New("some error")).
Bool("bool", true).
@@ -91,10 +94,12 @@ func TestWith(t *testing.T) {
Uint64("uint64", 10).
Float32("float32", 11).
Float64("float64", 12).
Time("time", time.Time{}).
Logger()
Time("time", time.Time{})
_, file, line, _ := runtime.Caller(0)
caller := fmt.Sprintf("%s:%d", file, line+3)
log := ctx.Caller().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 {
if got, want := out.String(), `{"foo":"bar","json":{"some":"json"},"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","caller":"`+caller+`"}`+"\n"; got != want {
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
}
}
@@ -132,9 +137,13 @@ func TestFields(t *testing.T) {
out := &bytes.Buffer{}
log := New(out)
now := time.Now()
_, file, line, _ := runtime.Caller(0)
caller := fmt.Sprintf("%s:%d", file, line+3)
log.Log().
Caller().
Str("string", "foo").
Bytes("bytes", []byte("bar")).
RawJSON("json", []byte(`{"some":"json"}`)).
AnErr("some_err", nil).
Err(errors.New("some error")).
Bool("bool", true).
@@ -154,7 +163,7 @@ func TestFields(t *testing.T) {
Time("time", time.Time{}).
TimeDiff("diff", now, now.Add(-10*time.Second)).
Msg("")
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 {
if got, want := out.String(), `{"caller":"`+caller+`","string":"foo","bytes":"bar","json":{"some":"json"},"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)
}
}
@@ -273,7 +282,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 +308,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 +356,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 +397,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 +412,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)
@@ -385,7 +435,7 @@ func TestContextTimestamp(t *testing.T) {
log := New(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 {
if got, want := out.String(), `{"foo":"bar","time":"2001-02-03T04:05:06Z","message":"hello world"}`+"\n"; got != want {
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
}
}
@@ -405,3 +455,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(), `{"foo":"bar","time":"2001-02-03T04:05:06Z","message":"hello world"}`+"\n"; got != want {
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
}
}
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 377 KiB

+126
View File
@@ -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
}
+75
View File
@@ -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)
}
})
})
}
}
+2
View File
@@ -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")
}
+4
View File
@@ -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)
+4
View File
@@ -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)