Initial commit
This commit is contained in:
commit
7f302b00ec
|
@ -0,0 +1,24 @@
|
||||||
|
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||||
|
*.o
|
||||||
|
*.a
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Folders
|
||||||
|
_obj
|
||||||
|
_test
|
||||||
|
|
||||||
|
# Architecture specific extensions/prefixes
|
||||||
|
*.[568vq]
|
||||||
|
[568vq].out
|
||||||
|
|
||||||
|
*.cgo1.go
|
||||||
|
*.cgo2.c
|
||||||
|
_cgo_defun.c
|
||||||
|
_cgo_gotypes.go
|
||||||
|
_cgo_export.*
|
||||||
|
|
||||||
|
_testmain.go
|
||||||
|
|
||||||
|
*.exe
|
||||||
|
*.test
|
||||||
|
*.prof
|
|
@ -0,0 +1,10 @@
|
||||||
|
language: go
|
||||||
|
go:
|
||||||
|
- 1.7
|
||||||
|
- 1.8
|
||||||
|
- tip
|
||||||
|
matrix:
|
||||||
|
allow_failures:
|
||||||
|
- go: tip
|
||||||
|
script:
|
||||||
|
go test -v -race -cpu=1,2,4 ./...
|
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2017 Olivier Poitrey
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
|
@ -0,0 +1,165 @@
|
||||||
|
# Zero Allocation JSON Logger
|
||||||
|
|
||||||
|
[![godoc](http://img.shields.io/badge/godoc-reference-blue.svg?style=flat)](https://godoc.org/github.com/rs/zerolog) [![license](http://img.shields.io/badge/license-MIT-red.svg?style=flat)](https://raw.githubusercontent.com/rs/zerolog/master/LICENSE) [![Build Status](https://travis-ci.org/rs/zerolog.svg?branch=master)](https://travis-ci.org/rs/zerolog) [![Coverage](http://gocover.io/_badge/github.com/rs/zerolog)](http://gocover.io/github.com/rs/zerolog)
|
||||||
|
|
||||||
|
The zerolog package provides a fast and simple logger dedicated to JSON output. It is inspired by uber's [zap](https://godoc.org/go.uber.org/zap) but with a mutch simpler to use API and smaller code base.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
* Level logging
|
||||||
|
* Sampling
|
||||||
|
* Contextual fields
|
||||||
|
|
||||||
|
## Benchmark
|
||||||
|
|
||||||
|
All operations are allocation free:
|
||||||
|
|
||||||
|
```
|
||||||
|
BenchmarkLogEmpty-8 50000000 22 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkDisabled-8 100000000 10 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkInfo-8 10000000 210 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkContextFields-8 10000000 254 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkLogFields-8 5000000 377 ns/op 0 B/op 0 allocs/op
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```go
|
||||||
|
import "github.com/rs/zerolog/log"
|
||||||
|
```
|
||||||
|
|
||||||
|
### A global logger can be use for simple logging
|
||||||
|
|
||||||
|
```go
|
||||||
|
log.Info().Msg("hello world")
|
||||||
|
|
||||||
|
// Output: {"level":"info","time":1494567715,"message":"hello world"}
|
||||||
|
```
|
||||||
|
|
||||||
|
NOTE: To import the global logger, import the `log` subpackage `github.com/rs/zerolog/log`.
|
||||||
|
|
||||||
|
```go
|
||||||
|
log.Fatal().
|
||||||
|
Err(err).
|
||||||
|
Str("service", service).
|
||||||
|
Msgf("Cannot start %s", service)
|
||||||
|
|
||||||
|
// Output: {"level":"fatal","time":1494567715,"message":"Cannot start myservice","error":"some error","service":"myservice"}
|
||||||
|
// Exit 1
|
||||||
|
```
|
||||||
|
|
||||||
|
NOTE: Using `Msgf` generates an allocation even when the logger is disabled.
|
||||||
|
|
||||||
|
### Fields can be added to log messages
|
||||||
|
|
||||||
|
```go
|
||||||
|
log.Info().
|
||||||
|
Str("foo", "bar").
|
||||||
|
Int("n", 123).
|
||||||
|
Msg("hello world")
|
||||||
|
|
||||||
|
// Output: {"level":"info","time":1494567715,"foo":"bar","n":123,"message":"hello world"}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Create logger instance to manage different outputs
|
||||||
|
|
||||||
|
```go
|
||||||
|
logger := zerolog.New(os.Stderr).With().Timestamp().Logger()
|
||||||
|
|
||||||
|
logger.Info().Str("foo", "bar").Msg("hello world")
|
||||||
|
|
||||||
|
// Output: {"level":"info","time":1494567715,"message":"hello world","foo":"bar"}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Sub-loggers let you chain loggers with additional context
|
||||||
|
|
||||||
|
```go
|
||||||
|
sublogger := log.With().
|
||||||
|
Str("component": "foo").
|
||||||
|
Logger()
|
||||||
|
sublogger.Info().Msg("hello world")
|
||||||
|
|
||||||
|
// Output: {"level":"info","time":1494567715,"message":"hello world","component":"foo"}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Level logging
|
||||||
|
|
||||||
|
```go
|
||||||
|
zerolog.GlobalLevel = 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,"routed message"}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Customize automatic field names
|
||||||
|
|
||||||
|
```go
|
||||||
|
zerolog.TimestampFieldName = "t"
|
||||||
|
zerolog.LevelFieldName = "l"
|
||||||
|
zerolog.MessageFieldName = "m"
|
||||||
|
|
||||||
|
log.Info().Msg("hello world")
|
||||||
|
|
||||||
|
// Output: {"l":"info","t":1494567715,"m":"hello world"}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Log with no level nor message
|
||||||
|
|
||||||
|
```go
|
||||||
|
log.Log().Str("foo","bar").Msg("")
|
||||||
|
|
||||||
|
// Output: {"time":1494567715,"foo":"bar"}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Add contextual fields to the global logger
|
||||||
|
|
||||||
|
```go
|
||||||
|
log.Logger = log.With().Str("foo", "bar").Logger()
|
||||||
|
```
|
||||||
|
|
||||||
|
### Log Sampling
|
||||||
|
|
||||||
|
```go
|
||||||
|
sampled := log.Sample(10)
|
||||||
|
sampled.Info().Msg("will be logged every 10 messages")
|
||||||
|
```
|
||||||
|
|
||||||
|
## Global Settings
|
||||||
|
|
||||||
|
Some settings can be changed and will by applied to all loggers:
|
||||||
|
|
||||||
|
* `log.Logger`: You can set this value to customize the global logger (the one used by package level methods).
|
||||||
|
* `zerolog.GlobalLevel`: Can raise the mimimum level of all loggers. Set this to `zerolog.Disable` to disable logging altogether (quiet mode).
|
||||||
|
* `zerolog.SamplingDisable`: If set to `true`, all sampled loggers will stop sampling and issue 100% of their log events.
|
||||||
|
* `zerolog.TimestampFieldName`: Can be set to customize `Timestamp` field name.
|
||||||
|
* `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.
|
||||||
|
|
||||||
|
## Field Types
|
||||||
|
|
||||||
|
### Standard Types
|
||||||
|
|
||||||
|
* `Str`
|
||||||
|
* `Bool`
|
||||||
|
* `Int`, `Int8`, `Int16`, `Int32`, `Int64`
|
||||||
|
* `Uint`, `Uint8`, `Uint16`, `Uint32`, `Uint64`
|
||||||
|
* `Float32`, `Float64`
|
||||||
|
|
||||||
|
### Advanced Fields
|
||||||
|
|
||||||
|
* `Timestamp`: Insert UNIX timestamp field with `zerolog.TimestampFieldName` field name.
|
||||||
|
* `Time`: Add a field with the time formated with the `zerolog.TimeFieldFormat`.
|
||||||
|
* `Err`: Takes an `error` and render it as a string using the `zerolog.ErrorFieldName` field name.
|
||||||
|
|
|
@ -0,0 +1,72 @@
|
||||||
|
package zerolog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"io/ioutil"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errExample = errors.New("fail")
|
||||||
|
fakeMessage = "Test logging, but use a somewhat realistic message length."
|
||||||
|
)
|
||||||
|
|
||||||
|
func BenchmarkLogEmpty(b *testing.B) {
|
||||||
|
logger := New(ioutil.Discard)
|
||||||
|
b.ResetTimer()
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
logger.Log().Msg("")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkDisabled(b *testing.B) {
|
||||||
|
logger := New(ioutil.Discard).Level(Disabled)
|
||||||
|
b.ResetTimer()
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
logger.Info().Msg(fakeMessage)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkInfo(b *testing.B) {
|
||||||
|
logger := New(ioutil.Discard)
|
||||||
|
b.ResetTimer()
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
logger.Info().Msg(fakeMessage)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkContextFields(b *testing.B) {
|
||||||
|
logger := New(ioutil.Discard).With().
|
||||||
|
Str("string", "four!").
|
||||||
|
Str("time", "now"). // XXX
|
||||||
|
Str("duration", "123"). //XXX
|
||||||
|
Str("another string", "done!").
|
||||||
|
Logger()
|
||||||
|
b.ResetTimer()
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
logger.Info().Msg(fakeMessage)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkLogFields(b *testing.B) {
|
||||||
|
logger := New(ioutil.Discard)
|
||||||
|
b.ResetTimer()
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
logger.Info().
|
||||||
|
Str("string", "four!").
|
||||||
|
Str("time", "now"). // XXX
|
||||||
|
Str("duration", "123"). //XXX
|
||||||
|
Str("another string", "done!").
|
||||||
|
Msg(fakeMessage)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,94 @@
|
||||||
|
package zerolog
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// Context configures a new sub-logger with contextual fields.
|
||||||
|
type Context struct {
|
||||||
|
l Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logger returns the logger with the context previously set.
|
||||||
|
func (c Context) Logger() Logger {
|
||||||
|
return c.l
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Context) append(f field) Context {
|
||||||
|
return Context{
|
||||||
|
l: Logger{
|
||||||
|
parent: c.l,
|
||||||
|
w: c.l.w,
|
||||||
|
field: f.compileJSON(),
|
||||||
|
level: c.l.level,
|
||||||
|
sample: c.l.sample,
|
||||||
|
counter: c.l.counter,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Context) Str(key, val string) Context {
|
||||||
|
return c.append(fStr(key, val))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Context) Err(err error) Context {
|
||||||
|
return c.append(fErr(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Context) Bool(key string, b bool) Context {
|
||||||
|
return c.append(fBool(key, b))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Context) Int(key string, i int) Context {
|
||||||
|
return c.append(fInt(key, i))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Context) Int8(key string, i int8) Context {
|
||||||
|
return c.append(fInt8(key, i))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Context) Int16(key string, i int16) Context {
|
||||||
|
return c.append(fInt16(key, i))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Context) Int32(key string, i int32) Context {
|
||||||
|
return c.append(fInt32(key, i))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Context) Int64(key string, i int64) Context {
|
||||||
|
return c.append(fInt64(key, i))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Context) Uint(key string, i uint) Context {
|
||||||
|
return c.append(fUint(key, i))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Context) Uint8(key string, i uint8) Context {
|
||||||
|
return c.append(fUint8(key, i))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Context) Uint16(key string, i uint16) Context {
|
||||||
|
return c.append(fUint16(key, i))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Context) Uint32(key string, i uint32) Context {
|
||||||
|
return c.append(fUint32(key, i))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Context) Uint64(key string, i uint64) Context {
|
||||||
|
return c.append(fUint64(key, i))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Context) Float32(key string, f float32) Context {
|
||||||
|
return c.append(fFloat32(key, f))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Context) Float64(key string, f float64) Context {
|
||||||
|
return c.append(fFloat64(key, f))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Context) Timestamp() Context {
|
||||||
|
return c.append(fTimestamp())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Context) Time(key string, t time.Time) Context {
|
||||||
|
return c.append(fTime(key, t))
|
||||||
|
}
|
|
@ -0,0 +1,213 @@
|
||||||
|
package zerolog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var pool = &sync.Pool{
|
||||||
|
New: func() interface{} {
|
||||||
|
return bytes.NewBuffer(make([]byte, 500))
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
type Event struct {
|
||||||
|
buf *bytes.Buffer
|
||||||
|
w LevelWriter
|
||||||
|
level Level
|
||||||
|
enabled bool
|
||||||
|
done func(msg string)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newEvent(w LevelWriter, level Level, enabled bool) Event {
|
||||||
|
if !enabled {
|
||||||
|
return Event{}
|
||||||
|
}
|
||||||
|
buf := pool.Get().(*bytes.Buffer)
|
||||||
|
buf.Reset()
|
||||||
|
buf.WriteByte('{')
|
||||||
|
return Event{
|
||||||
|
buf: buf,
|
||||||
|
w: w,
|
||||||
|
level: level,
|
||||||
|
enabled: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Event) write() (n int, err error) {
|
||||||
|
if !e.enabled {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
e.buf.WriteByte('}')
|
||||||
|
e.buf.WriteByte('\n')
|
||||||
|
n, err = e.w.WriteLevel(e.level, e.buf.Bytes())
|
||||||
|
pool.Put(e.buf)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Event) append(f field) Event {
|
||||||
|
if !e.enabled {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
if e.buf.Len() > 1 {
|
||||||
|
e.buf.WriteByte(',')
|
||||||
|
}
|
||||||
|
f.writeJSON(e.buf)
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// Msg sends the event with msg added as the message field if not empty.
|
||||||
|
func (e Event) Msg(msg string) (n int, err error) {
|
||||||
|
if !e.enabled {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
if msg != "" {
|
||||||
|
e.append(fStr(MessageFieldName, msg))
|
||||||
|
}
|
||||||
|
if e.done != nil {
|
||||||
|
defer e.done(msg)
|
||||||
|
}
|
||||||
|
return e.write()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Msgf sends the event with formated msg added as the message field if not empty.
|
||||||
|
func (e Event) Msgf(format string, v ...interface{}) (n int, err error) {
|
||||||
|
if !e.enabled {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
msg := fmt.Sprintf(format, v...)
|
||||||
|
if msg != "" {
|
||||||
|
e.append(fStr(MessageFieldName, msg))
|
||||||
|
}
|
||||||
|
if e.done != nil {
|
||||||
|
defer e.done(msg)
|
||||||
|
}
|
||||||
|
return e.write()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Event) Str(key, val string) Event {
|
||||||
|
if !e.enabled {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
return e.append(fStr(key, val))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Event) Err(err error) Event {
|
||||||
|
if !e.enabled {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
return e.append(fErr(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Event) Bool(key string, b bool) Event {
|
||||||
|
if !e.enabled {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
return e.append(fBool(key, b))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Event) Int(key string, i int) Event {
|
||||||
|
if !e.enabled {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
return e.append(fInt(key, i))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Event) Int8(key string, i int8) Event {
|
||||||
|
if !e.enabled {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
return e.append(fInt8(key, i))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Event) Int16(key string, i int16) Event {
|
||||||
|
if !e.enabled {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
return e.append(fInt16(key, i))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Event) Int32(key string, i int32) Event {
|
||||||
|
if !e.enabled {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
return e.append(fInt32(key, i))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Event) Int64(key string, i int64) Event {
|
||||||
|
if !e.enabled {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
return e.append(fInt64(key, i))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Event) Uint(key string, i uint) Event {
|
||||||
|
if !e.enabled {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
return e.append(fUint(key, i))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Event) Uint8(key string, i uint8) Event {
|
||||||
|
if !e.enabled {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
return e.append(fUint8(key, i))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Event) Uint16(key string, i uint16) Event {
|
||||||
|
if !e.enabled {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
return e.append(fUint16(key, i))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Event) Uint32(key string, i uint32) Event {
|
||||||
|
if !e.enabled {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
return e.append(fUint32(key, i))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Event) Uint64(key string, i uint64) Event {
|
||||||
|
if !e.enabled {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
return e.append(fUint64(key, i))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Event) Float32(key string, f float32) Event {
|
||||||
|
if !e.enabled {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
return e.append(fFloat32(key, f))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Event) Float64(key string, f float64) Event {
|
||||||
|
if !e.enabled {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
return e.append(fFloat64(key, f))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Event) Timestamp() Event {
|
||||||
|
if !e.enabled {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
return e.append(fTimestamp())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Event) Time(key string, t time.Time) Event {
|
||||||
|
if !e.enabled {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
return e.append(fTime(key, t))
|
||||||
|
}
|
|
@ -0,0 +1,158 @@
|
||||||
|
package zerolog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// TimestampFieldName is the field name used for the timestamp field.
|
||||||
|
TimestampFieldName = "time"
|
||||||
|
|
||||||
|
// LevelFieldName is the field name used for the level field.
|
||||||
|
LevelFieldName = "level"
|
||||||
|
|
||||||
|
// MessageFieldName is the field name used for the message field.
|
||||||
|
MessageFieldName = "message"
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
TimeFieldFormat = time.RFC3339
|
||||||
|
|
||||||
|
now = time.Now
|
||||||
|
)
|
||||||
|
|
||||||
|
type FieldMode uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
zeroFieldMode FieldMode = iota
|
||||||
|
rawFieldMode
|
||||||
|
quotedFieldMode
|
||||||
|
precomputedFieldMode
|
||||||
|
timestampFieldMode
|
||||||
|
)
|
||||||
|
|
||||||
|
// field define a logger field.
|
||||||
|
type field struct {
|
||||||
|
key string
|
||||||
|
mode FieldMode
|
||||||
|
val string
|
||||||
|
json []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f field) writeJSON(buf *bytes.Buffer) {
|
||||||
|
switch f.mode {
|
||||||
|
case zeroFieldMode:
|
||||||
|
return
|
||||||
|
case precomputedFieldMode:
|
||||||
|
buf.Write(f.json)
|
||||||
|
return
|
||||||
|
case timestampFieldMode:
|
||||||
|
writeJSONString(buf, TimestampFieldName)
|
||||||
|
buf.WriteByte(':')
|
||||||
|
buf.WriteString(strconv.FormatInt(now().Unix(), 10))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writeJSONString(buf, f.key)
|
||||||
|
buf.WriteByte(':')
|
||||||
|
switch f.mode {
|
||||||
|
case quotedFieldMode:
|
||||||
|
writeJSONString(buf, f.val)
|
||||||
|
case rawFieldMode:
|
||||||
|
buf.WriteString(f.val)
|
||||||
|
default:
|
||||||
|
panic("unknown field mode")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f field) compileJSON() field {
|
||||||
|
switch f.mode {
|
||||||
|
case zeroFieldMode, precomputedFieldMode, timestampFieldMode:
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
f.writeJSON(buf)
|
||||||
|
cf := field{
|
||||||
|
mode: precomputedFieldMode,
|
||||||
|
json: buf.Bytes(),
|
||||||
|
}
|
||||||
|
return cf
|
||||||
|
}
|
||||||
|
|
||||||
|
func fStr(key, val string) field {
|
||||||
|
return field{key, quotedFieldMode, val, nil}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fErr(err error) field {
|
||||||
|
return field{ErrorFieldName, quotedFieldMode, err.Error(), nil}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fBool(key string, b bool) field {
|
||||||
|
if b {
|
||||||
|
return field{key, rawFieldMode, "true", nil}
|
||||||
|
}
|
||||||
|
return field{key, rawFieldMode, "false", nil}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fInt(key string, i int) field {
|
||||||
|
return field{key, rawFieldMode, strconv.FormatInt(int64(i), 10), nil}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fInt8(key string, i int8) field {
|
||||||
|
return field{key, rawFieldMode, strconv.FormatInt(int64(i), 10), nil}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fInt16(key string, i int16) field {
|
||||||
|
return field{key, rawFieldMode, strconv.FormatInt(int64(i), 10), nil}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fInt32(key string, i int32) field {
|
||||||
|
return field{key, rawFieldMode, strconv.FormatInt(int64(i), 10), nil}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fInt64(key string, i int64) field {
|
||||||
|
return field{key, rawFieldMode, strconv.FormatInt(i, 10), nil}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fUint(key string, i uint) field {
|
||||||
|
return field{key, rawFieldMode, strconv.FormatUint(uint64(i), 10), nil}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fUint8(key string, i uint8) field {
|
||||||
|
return field{key, rawFieldMode, strconv.FormatUint(uint64(i), 10), nil}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fUint16(key string, i uint16) field {
|
||||||
|
return field{key, rawFieldMode, strconv.FormatUint(uint64(i), 10), nil}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fUint32(key string, i uint32) field {
|
||||||
|
return field{key, rawFieldMode, strconv.FormatUint(uint64(i), 10), nil}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fUint64(key string, i uint64) field {
|
||||||
|
return field{key, rawFieldMode, strconv.FormatUint(i, 10), nil}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fFloat32(key string, f float32) field {
|
||||||
|
return field{key, rawFieldMode, strconv.FormatFloat(float64(f), 'f', -1, 32), nil}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fFloat64(key string, f float64) field {
|
||||||
|
return field{key, rawFieldMode, strconv.FormatFloat(f, 'f', -1, 64), nil}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fTimestamp() field {
|
||||||
|
return field{mode: timestampFieldMode}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fTime(key string, t time.Time) field {
|
||||||
|
return field{key, quotedFieldMode, t.Format(TimeFieldFormat), nil}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
package zerolog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
const hex = "0123456789abcdef"
|
||||||
|
|
||||||
|
func writeJSONString(buf *bytes.Buffer, s string) {
|
||||||
|
buf.WriteByte('"')
|
||||||
|
for i := 0; i < len(s); {
|
||||||
|
if b := s[i]; b < utf8.RuneSelf {
|
||||||
|
switch b {
|
||||||
|
case '"', '\\':
|
||||||
|
buf.WriteByte('\\')
|
||||||
|
buf.WriteByte(b)
|
||||||
|
case '\b':
|
||||||
|
buf.WriteByte('\\')
|
||||||
|
buf.WriteByte('b')
|
||||||
|
case '\f':
|
||||||
|
buf.WriteByte('\\')
|
||||||
|
buf.WriteByte('f')
|
||||||
|
case '\n':
|
||||||
|
buf.WriteByte('\\')
|
||||||
|
buf.WriteByte('n')
|
||||||
|
case '\r':
|
||||||
|
buf.WriteByte('\\')
|
||||||
|
buf.WriteByte('r')
|
||||||
|
case '\t':
|
||||||
|
buf.WriteByte('\\')
|
||||||
|
buf.WriteByte('t')
|
||||||
|
default:
|
||||||
|
if b >= 0x20 {
|
||||||
|
buf.WriteByte(b)
|
||||||
|
} else {
|
||||||
|
buf.WriteString(`\u00`)
|
||||||
|
buf.WriteByte(hex[b>>4])
|
||||||
|
buf.WriteByte(hex[b&0xF])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
r, size := utf8.DecodeRuneInString(s[i:])
|
||||||
|
if r == utf8.RuneError && size == 1 {
|
||||||
|
buf.WriteString(`\ufffd`)
|
||||||
|
i++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
buf.WriteString(s[i : i+size])
|
||||||
|
i += size
|
||||||
|
}
|
||||||
|
buf.WriteByte('"')
|
||||||
|
}
|
|
@ -0,0 +1,290 @@
|
||||||
|
// Package zerolog provides a lightweight logging library dedicated to JSON logging.
|
||||||
|
//
|
||||||
|
// A global Logger can be use for simple logging:
|
||||||
|
//
|
||||||
|
// import "github.com/rs/zerolog/log"
|
||||||
|
//
|
||||||
|
// log.Info().Msg("hello world")
|
||||||
|
// // Output: {"time":1494567715,"level":"info","message":"hello world"}
|
||||||
|
//
|
||||||
|
// NOTE: To import the global logger, import the "log" subpackage "github.com/rs/zerolog/log".
|
||||||
|
//
|
||||||
|
// Fields can be added to log messages:
|
||||||
|
//
|
||||||
|
// log.Info().Str("foo", "bar").Msg("hello world")
|
||||||
|
// // Output: {"time":1494567715,"level":"info","message":"hello world","foo":"bar"}
|
||||||
|
//
|
||||||
|
// Create logger instance to manage different outputs:
|
||||||
|
//
|
||||||
|
// logger := zerolog.New(os.Stderr).With().Timestamp().Logger()
|
||||||
|
// logger.Info().
|
||||||
|
// Str("foo", "bar").
|
||||||
|
// Msg("hello world")
|
||||||
|
// // Output: {"time":1494567715,"level":"info","message":"hello world","foo":"bar"}
|
||||||
|
//
|
||||||
|
// Sub-loggers let you chain loggers with additional context:
|
||||||
|
//
|
||||||
|
// sublogger := log.With().Str("component": "foo").Logger()
|
||||||
|
// sublogger.Info().Msg("hello world")
|
||||||
|
// // Output: {"time":1494567715,"level":"info","message":"hello world","component":"foo"}
|
||||||
|
//
|
||||||
|
// Level logging
|
||||||
|
//
|
||||||
|
// zerolog.GlobalLevel = 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,"routed message"}
|
||||||
|
//
|
||||||
|
// Customize automatic field names:
|
||||||
|
//
|
||||||
|
// log.TimestampFieldName = "t"
|
||||||
|
// log.LevelFieldName = "p"
|
||||||
|
// log.MessageFieldName = "m"
|
||||||
|
//
|
||||||
|
// log.Info().Msg("hello world")
|
||||||
|
// // Output: {"t":1494567715,"p":"info","m":"hello world"}
|
||||||
|
//
|
||||||
|
// Log with no level and message:
|
||||||
|
//
|
||||||
|
// log.Log().Str("foo","bar").Msg("")
|
||||||
|
// // Output: {"time":1494567715,"foo":"bar"}
|
||||||
|
//
|
||||||
|
// Add contextual fields to global Logger:
|
||||||
|
//
|
||||||
|
// log.Logger = log.With().Str("foo", "bar").Logger()
|
||||||
|
//
|
||||||
|
// Sample logs:
|
||||||
|
//
|
||||||
|
// sampled := log.Sample(10)
|
||||||
|
// sampled.Info().Msg("will be logged every 10 messages")
|
||||||
|
//
|
||||||
|
package zerolog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"sync/atomic"
|
||||||
|
)
|
||||||
|
|
||||||
|
type parentLogger interface {
|
||||||
|
addContextField(Event)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Level defines log levels.
|
||||||
|
type Level uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
// DebugLevel defines debug log level.
|
||||||
|
DebugLevel Level = iota
|
||||||
|
// InfoLevel defines info log level.
|
||||||
|
InfoLevel
|
||||||
|
// WarnLevel defines warn log level.
|
||||||
|
WarnLevel
|
||||||
|
// ErrorLevel defines error log level.
|
||||||
|
ErrorLevel
|
||||||
|
// FatalLevel defines fatal log level.
|
||||||
|
FatalLevel
|
||||||
|
// PanicLevel defines panic log level.
|
||||||
|
PanicLevel
|
||||||
|
// Disabled disables the logger.
|
||||||
|
Disabled
|
||||||
|
)
|
||||||
|
|
||||||
|
func (l Level) String() string {
|
||||||
|
switch l {
|
||||||
|
case DebugLevel:
|
||||||
|
return "debug"
|
||||||
|
case InfoLevel:
|
||||||
|
return "info"
|
||||||
|
case WarnLevel:
|
||||||
|
return "warning"
|
||||||
|
case ErrorLevel:
|
||||||
|
return "error"
|
||||||
|
case FatalLevel:
|
||||||
|
return "fatal"
|
||||||
|
case PanicLevel:
|
||||||
|
return "panic"
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Often samples log every 10 events.
|
||||||
|
Often = int64(10)
|
||||||
|
// Sometimes samples log every 100 events.
|
||||||
|
Sometimes = int64(100)
|
||||||
|
// Rarely samples log every 1000 events.
|
||||||
|
Rarely = int64(1000)
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// GlobalLevel defines the global override for log level. If this
|
||||||
|
// values is raised, all Loggers will use at least this value.
|
||||||
|
//
|
||||||
|
// To globally disable logs, set GlobalLevel to Disabled.
|
||||||
|
GlobalLevel = DebugLevel
|
||||||
|
|
||||||
|
// DisableSampling will disable sampling in all Loggers if true.
|
||||||
|
DisableSampling = 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
|
||||||
|
// serialization to the Writer. If your Writer is not thread safe,
|
||||||
|
// you may consider a sync wrapper.
|
||||||
|
type Logger struct {
|
||||||
|
root bool
|
||||||
|
parent parentLogger
|
||||||
|
w LevelWriter
|
||||||
|
field field
|
||||||
|
level Level
|
||||||
|
sample uint32
|
||||||
|
counter *uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a root logger with given output writer.
|
||||||
|
func New(w io.Writer) Logger {
|
||||||
|
if w == nil {
|
||||||
|
panic("w is nil")
|
||||||
|
}
|
||||||
|
lw, ok := w.(LevelWriter)
|
||||||
|
if !ok {
|
||||||
|
lw = levelWriterAdapter{w}
|
||||||
|
}
|
||||||
|
return Logger{
|
||||||
|
root: true,
|
||||||
|
w: lw,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// With creates a child logger with the field added to its context.
|
||||||
|
func (l Logger) With() Context {
|
||||||
|
return Context{l}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Level crestes a child logger with the minium accepted level set to level.
|
||||||
|
func (l Logger) Level(lvl Level) Logger {
|
||||||
|
return Logger{
|
||||||
|
parent: l,
|
||||||
|
w: l.w,
|
||||||
|
level: lvl,
|
||||||
|
sample: l.sample,
|
||||||
|
counter: l.counter,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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{
|
||||||
|
parent: l,
|
||||||
|
w: l.w,
|
||||||
|
level: l.level,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Logger{
|
||||||
|
parent: l,
|
||||||
|
w: l.w,
|
||||||
|
level: l.level,
|
||||||
|
sample: uint32(every),
|
||||||
|
counter: new(uint32),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) })
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log starts a new message with no level. Setting GlobalLevel to Disabled
|
||||||
|
// will still disable events produced by this method.
|
||||||
|
//
|
||||||
|
// You must call Msg on the returned event in order to send the event.
|
||||||
|
func (l Logger) Log() Event {
|
||||||
|
return l.newEvent(ErrorLevel, false, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l Logger) newEvent(level Level, addLevelField bool, done func(string)) Event {
|
||||||
|
lvl := InfoLevel
|
||||||
|
if addLevelField {
|
||||||
|
lvl = level
|
||||||
|
}
|
||||||
|
e := newEvent(l.w, lvl, l.should(level))
|
||||||
|
if addLevelField {
|
||||||
|
e.Str(LevelFieldName, level.String())
|
||||||
|
}
|
||||||
|
if l.sample > 0 && SampleFieldName != "" {
|
||||||
|
e.Uint32(SampleFieldName, l.sample)
|
||||||
|
}
|
||||||
|
l.addContextField(e)
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
// should returns true if the log event should be logged.
|
||||||
|
func (l Logger) should(lvl Level) bool {
|
||||||
|
if lvl < l.level || lvl < GlobalLevel {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !DisableSampling && l.sample > 0 && l.counter != nil {
|
||||||
|
c := atomic.AddUint32(l.counter, 1)
|
||||||
|
return c%l.sample == 0
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l Logger) addContextField(e Event) {
|
||||||
|
if !l.root {
|
||||||
|
l.parent.addContextField(e)
|
||||||
|
}
|
||||||
|
if l.field.mode != zeroFieldMode {
|
||||||
|
e.append(l.field)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
// Package log provides a global logger for zerolog.
|
||||||
|
package log
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Logger is the global logger.
|
||||||
|
var Logger = zerolog.New(os.Stderr).With().Timestamp().Logger()
|
||||||
|
|
||||||
|
// With creates a child logger with the field added to its context.
|
||||||
|
func With() zerolog.Context {
|
||||||
|
return Logger.With()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Level crestes a child logger with the minium 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug starts a new message with debug level.
|
||||||
|
//
|
||||||
|
// You must call Msg on the returned event in order to send the event.
|
||||||
|
func Debug() zerolog.Event {
|
||||||
|
return Logger.Debug()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Info starts a new message with info level.
|
||||||
|
//
|
||||||
|
// You must call Msg on the returned event in order to send the event.
|
||||||
|
func Info() zerolog.Event {
|
||||||
|
return Logger.Info()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warn starts a new message with warn level.
|
||||||
|
//
|
||||||
|
// You must call Msg on the returned event in order to send the event.
|
||||||
|
func Warn() zerolog.Event {
|
||||||
|
return Logger.Warn()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error starts a new message with error level.
|
||||||
|
//
|
||||||
|
// You must call Msg on the returned event in order to send the event.
|
||||||
|
func Error() zerolog.Event {
|
||||||
|
return Logger.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 Fatal() zerolog.Event {
|
||||||
|
return Logger.Fatal()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 Panic() zerolog.Event {
|
||||||
|
return Logger.Panic()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log starts a new message with no level. Setting zerolog.GlobalLevel to
|
||||||
|
// zerlog.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()
|
||||||
|
}
|
|
@ -0,0 +1,101 @@
|
||||||
|
package zerolog_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ExampleNew() {
|
||||||
|
log := zerolog.New(os.Stdout)
|
||||||
|
|
||||||
|
log.Info().Msg("hello world")
|
||||||
|
|
||||||
|
// Output: {"level":"info","message":"hello world"}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleLogger_With() {
|
||||||
|
log := zerolog.New(os.Stdout).
|
||||||
|
With().
|
||||||
|
Str("foo", "bar").
|
||||||
|
Logger()
|
||||||
|
|
||||||
|
log.Info().Msg("hello world")
|
||||||
|
|
||||||
|
// Output: {"level":"info","foo":"bar","message":"hello world"}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleLogger_Level() {
|
||||||
|
log := zerolog.New(os.Stdout).Level(zerolog.WarnLevel)
|
||||||
|
|
||||||
|
log.Info().Msg("filtered out message")
|
||||||
|
log.Error().Msg("kept message")
|
||||||
|
|
||||||
|
// Output: {"level":"error","message":"kept message"}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleLogger_Sample() {
|
||||||
|
log := zerolog.New(os.Stdout).Sample(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"}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleLogger_Debug() {
|
||||||
|
log := zerolog.New(os.Stdout)
|
||||||
|
|
||||||
|
log.Debug().
|
||||||
|
Str("foo", "bar").
|
||||||
|
Int("n", 123).
|
||||||
|
Msg("hello world")
|
||||||
|
|
||||||
|
// Output: {"level":"debug","foo":"bar","n":123,"message":"hello world"}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleLogger_Info() {
|
||||||
|
log := zerolog.New(os.Stdout)
|
||||||
|
|
||||||
|
log.Info().
|
||||||
|
Str("foo", "bar").
|
||||||
|
Int("n", 123).
|
||||||
|
Msg("hello world")
|
||||||
|
|
||||||
|
// Output: {"level":"info","foo":"bar","n":123,"message":"hello world"}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleLogger_Warn() {
|
||||||
|
log := zerolog.New(os.Stdout)
|
||||||
|
|
||||||
|
log.Warn().
|
||||||
|
Str("foo", "bar").
|
||||||
|
Msg("a warning message")
|
||||||
|
|
||||||
|
// Output: {"level":"warning","foo":"bar","message":"a warning message"}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleLogger_Error() {
|
||||||
|
log := zerolog.New(os.Stdout)
|
||||||
|
|
||||||
|
log.Error().
|
||||||
|
Err(errors.New("some error")).
|
||||||
|
Msg("error doing something")
|
||||||
|
|
||||||
|
// Output: {"level":"error","error":"some error","message":"error doing something"}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleLogger_Log() {
|
||||||
|
log := zerolog.New(os.Stdout)
|
||||||
|
|
||||||
|
log.Log().
|
||||||
|
Str("foo", "bar").
|
||||||
|
Str("bar", "baz").
|
||||||
|
Msg("")
|
||||||
|
|
||||||
|
// Output: {"foo":"bar","bar":"baz"}
|
||||||
|
}
|
|
@ -0,0 +1,157 @@
|
||||||
|
package zerolog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLog(t *testing.T) {
|
||||||
|
t.Run("empty", func(t *testing.T) {
|
||||||
|
out := &bytes.Buffer{}
|
||||||
|
log := New(out)
|
||||||
|
log.Log().Msg("")
|
||||||
|
if got, want := out.String(), "{}\n"; got != want {
|
||||||
|
t.Errorf("invalid log output: got %q, want %q", got, want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("one-field", func(t *testing.T) {
|
||||||
|
out := &bytes.Buffer{}
|
||||||
|
log := New(out)
|
||||||
|
log.Log().Str("foo", "bar").Msg("")
|
||||||
|
if got, want := out.String(), `{"foo":"bar"}`+"\n"; got != want {
|
||||||
|
t.Errorf("invalid log output: got %q, want %q", got, want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("two-field", func(t *testing.T) {
|
||||||
|
out := &bytes.Buffer{}
|
||||||
|
log := New(out)
|
||||||
|
log.Log().
|
||||||
|
Str("foo", "bar").
|
||||||
|
Int("n", 123).
|
||||||
|
Msg("")
|
||||||
|
if got, want := out.String(), `{"foo":"bar","n":123}`+"\n"; got != want {
|
||||||
|
t.Errorf("invalid log output: got %q, want %q", got, want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInfo(t *testing.T) {
|
||||||
|
t.Run("empty", func(t *testing.T) {
|
||||||
|
out := &bytes.Buffer{}
|
||||||
|
log := New(out)
|
||||||
|
log.Info().Msg("")
|
||||||
|
if got, want := out.String(), `{"level":"info"}`+"\n"; got != want {
|
||||||
|
t.Errorf("invalid log output: got %q, want %q", got, want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("one-field", func(t *testing.T) {
|
||||||
|
out := &bytes.Buffer{}
|
||||||
|
log := New(out)
|
||||||
|
log.Info().Str("foo", "bar").Msg("")
|
||||||
|
if got, want := out.String(), `{"level":"info","foo":"bar"}`+"\n"; got != want {
|
||||||
|
t.Errorf("invalid log output: got %q, want %q", got, want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("two-field", func(t *testing.T) {
|
||||||
|
out := &bytes.Buffer{}
|
||||||
|
log := New(out)
|
||||||
|
log.Info().
|
||||||
|
Str("foo", "bar").
|
||||||
|
Int("n", 123).
|
||||||
|
Msg("")
|
||||||
|
if got, want := out.String(), `{"level":"info","foo":"bar","n":123}`+"\n"; got != want {
|
||||||
|
t.Errorf("invalid log output: got %q, want %q", got, want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWith(t *testing.T) {
|
||||||
|
out := &bytes.Buffer{}
|
||||||
|
log := New(out).With().Str("f1", "val").Str("f2", "val").Logger()
|
||||||
|
log.Log().Str("f3", "val").Msg("")
|
||||||
|
if got, want := out.String(), `{"f1":"val","f2":"val","f3":"val"}`+"\n"; got != want {
|
||||||
|
t.Errorf("invalid log output: got %q, want %q", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLevel(t *testing.T) {
|
||||||
|
t.Run("Disabled", func(t *testing.T) {
|
||||||
|
out := &bytes.Buffer{}
|
||||||
|
log := New(out).Level(Disabled)
|
||||||
|
log.Info().Msg("test")
|
||||||
|
if got, want := out.String(), ""; got != want {
|
||||||
|
t.Errorf("invalid log output: got %q, want %q", got, want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Info", func(t *testing.T) {
|
||||||
|
out := &bytes.Buffer{}
|
||||||
|
log := New(out).Level(InfoLevel)
|
||||||
|
log.Info().Msg("test")
|
||||||
|
if got, want := out.String(), `{"level":"info","message":"test"}`+"\n"; got != want {
|
||||||
|
t.Errorf("invalid log output: got %q, want %q", got, want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSampling(t *testing.T) {
|
||||||
|
out := &bytes.Buffer{}
|
||||||
|
log := New(out).Sample(2)
|
||||||
|
log.Log().Int("i", 1).Msg("")
|
||||||
|
log.Log().Int("i", 2).Msg("")
|
||||||
|
log.Log().Int("i", 3).Msg("")
|
||||||
|
log.Log().Int("i", 4).Msg("")
|
||||||
|
if got, want := out.String(), "{\"sample\":2,\"i\":2}\n{\"sample\":2,\"i\":4}\n"; got != want {
|
||||||
|
t.Errorf("invalid log output: got %q, want %q", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type levelWriter struct {
|
||||||
|
ops []struct {
|
||||||
|
l Level
|
||||||
|
p string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lw *levelWriter) Write(p []byte) (int, error) {
|
||||||
|
return len(p), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lw *levelWriter) WriteLevel(lvl Level, p []byte) (int, error) {
|
||||||
|
lw.ops = append(lw.ops, struct {
|
||||||
|
l Level
|
||||||
|
p string
|
||||||
|
}{lvl, string(p)})
|
||||||
|
return len(p), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLevelWriter(t *testing.T) {
|
||||||
|
lw := &levelWriter{
|
||||||
|
ops: []struct {
|
||||||
|
l Level
|
||||||
|
p string
|
||||||
|
}{},
|
||||||
|
}
|
||||||
|
log := New(lw)
|
||||||
|
log.Debug().Msg("1")
|
||||||
|
log.Info().Msg("2")
|
||||||
|
log.Warn().Msg("3")
|
||||||
|
log.Error().Msg("4")
|
||||||
|
want := []struct {
|
||||||
|
l Level
|
||||||
|
p string
|
||||||
|
}{
|
||||||
|
{DebugLevel, `{"level":"debug","message":"1"}` + "\n"},
|
||||||
|
{InfoLevel, `{"level":"info","message":"2"}` + "\n"},
|
||||||
|
{WarnLevel, `{"level":"warning","message":"3"}` + "\n"},
|
||||||
|
{ErrorLevel, `{"level":"error","message":"4"}` + "\n"},
|
||||||
|
}
|
||||||
|
if got := lw.ops; !reflect.DeepEqual(got, want) {
|
||||||
|
t.Errorf("invalid ops:\ngot:\n%v\nwant:\n%v", got, want)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package zerolog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log/syslog"
|
||||||
|
)
|
||||||
|
|
||||||
|
type syslogWriter struct {
|
||||||
|
w *syslog.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
// SyslogWriter wraps a syslog.Writer and set the right syslog level
|
||||||
|
// matching the log even level.
|
||||||
|
func SyslogWriter(w *syslog.Writer) LevelWriter {
|
||||||
|
return syslogWriter{w}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sw syslogWriter) Write(p []byte) (n int, err error) {
|
||||||
|
return sw.w.Write(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteLevel implements LevelWriter interface.
|
||||||
|
func (sw syslogWriter) WriteLevel(level Level, p []byte) (n int, err error) {
|
||||||
|
switch level {
|
||||||
|
case DebugLevel:
|
||||||
|
err = sw.w.Debug(string(p))
|
||||||
|
case InfoLevel:
|
||||||
|
err = sw.w.Info(string(p))
|
||||||
|
case WarnLevel:
|
||||||
|
err = sw.w.Warning(string(p))
|
||||||
|
case ErrorLevel:
|
||||||
|
err = sw.w.Err(string(p))
|
||||||
|
case FatalLevel:
|
||||||
|
err = sw.w.Emerg(string(p))
|
||||||
|
case PanicLevel:
|
||||||
|
err = sw.w.Crit(string(p))
|
||||||
|
default:
|
||||||
|
panic("invalid level")
|
||||||
|
}
|
||||||
|
n = len(p)
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
package zerolog
|
||||||
|
|
||||||
|
import "io"
|
||||||
|
|
||||||
|
// LevelWriter defines as interface a writer may implement in order
|
||||||
|
// to receive level information with payload.
|
||||||
|
type LevelWriter interface {
|
||||||
|
io.Writer
|
||||||
|
WriteLevel(level Level, p []byte) (n int, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type levelWriterAdapter struct {
|
||||||
|
io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lw levelWriterAdapter) WriteLevel(level Level, p []byte) (n int, err error) {
|
||||||
|
return lw.Write(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
type multiLevelWriter struct {
|
||||||
|
writers []LevelWriter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t multiLevelWriter) Write(p []byte) (n int, err error) {
|
||||||
|
for _, w := range t.writers {
|
||||||
|
n, err = w.Write(p)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if n != len(p) {
|
||||||
|
err = io.ErrShortWrite
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return len(p), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t multiLevelWriter) WriteLevel(l Level, p []byte) (n int, err error) {
|
||||||
|
for _, w := range t.writers {
|
||||||
|
n, err = w.WriteLevel(l, p)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if n != len(p) {
|
||||||
|
err = io.ErrShortWrite
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return len(p), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MultiLevelWriter creates a writer that duplicates its writes to all the
|
||||||
|
// provided writers, similar to the Unix tee(1) command. If some writers
|
||||||
|
// implement LevelWriter, their WriteLevel method will be used instead of Write.
|
||||||
|
func MultiLevelWriter(writers ...io.Writer) LevelWriter {
|
||||||
|
lwriters := make([]LevelWriter, 0, len(writers))
|
||||||
|
for _, w := range writers {
|
||||||
|
if lw, ok := w.(LevelWriter); ok {
|
||||||
|
lwriters = append(lwriters, lw)
|
||||||
|
} else {
|
||||||
|
lwriters = append(lwriters, levelWriterAdapter{w})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return multiLevelWriter{lwriters}
|
||||||
|
}
|
Loading…
Reference in New Issue