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