Write directly into a single shared buffer per event for even better perf

This commit is contained in:
Olivier Poitrey 2017-05-19 19:43:59 -07:00
parent 19dfb55aa8
commit d0cfcbbafe
9 changed files with 339 additions and 347 deletions

View File

@ -15,11 +15,11 @@ The zerolog package provides a fast and simple logger dedicated to JSON output.
All operations are allocation free (those numbers *include* JSON encoding): All operations are allocation free (those numbers *include* JSON encoding):
``` ```
BenchmarkLogEmpty-8 50000000 22 ns/op 0 B/op 0 allocs/op BenchmarkLogEmpty-8 50000000 19.8 ns/op 0 B/op 0 allocs/op
BenchmarkDisabled-8 100000000 9 ns/op 0 B/op 0 allocs/op BenchmarkDisabled-8 100000000 4.73 ns/op 0 B/op 0 allocs/op
BenchmarkInfo-8 10000000 210 ns/op 0 B/op 0 allocs/op BenchmarkInfo-8 10000000 85.1 ns/op 0 B/op 0 allocs/op
BenchmarkContextFields-8 10000000 254 ns/op 0 B/op 0 allocs/op BenchmarkContextFields-8 10000000 81.9 ns/op 0 B/op 0 allocs/op
BenchmarkLogFields-8 5000000 377 ns/op 0 B/op 0 allocs/op BenchmarkLogFields-8 5000000 247 ns/op 0 B/op 0 allocs/op
``` ```
## Usage ## Usage

View File

@ -4,6 +4,7 @@ import (
"errors" "errors"
"io/ioutil" "io/ioutil"
"testing" "testing"
"time"
) )
var ( var (
@ -44,9 +45,9 @@ func BenchmarkInfo(b *testing.B) {
func BenchmarkContextFields(b *testing.B) { func BenchmarkContextFields(b *testing.B) {
logger := New(ioutil.Discard).With(). logger := New(ioutil.Discard).With().
Str("string", "four!"). Str("string", "four!").
Str("time", "now"). // XXX Time("time", time.Time{}).
Str("duration", "123"). //XXX Int("int", 123).
Str("another string", "done!"). Float32("float", -2.203230293249593).
Logger() Logger()
b.ResetTimer() b.ResetTimer()
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
@ -63,9 +64,9 @@ func BenchmarkLogFields(b *testing.B) {
for pb.Next() { for pb.Next() {
logger.Info(). logger.Info().
Str("string", "four!"). Str("string", "four!").
Str("time", "now"). // XXX Time("time", time.Time{}).
Str("duration", "123"). //XXX Int("int", 123).
Str("another string", "done!"). Float32("float", -2.203230293249593).
Msg(fakeMessage) Msg(fakeMessage)
} }
}) })

View File

@ -12,108 +12,118 @@ func (c Context) Logger() Logger {
return c.l 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,
},
}
}
// Dict adds the field key with the dict to the logger context. // Dict adds the field key with the dict to the logger context.
func (c Context) Dict(key string, dict Event) Context { func (c Context) Dict(key string, dict *Event) Context {
dict.buf.WriteByte('}') dict.buf = append(dict.buf, '}')
return c.append(fRaw(key, dict.buf.String())) c.l.context = append(appendKey(c.l.context, key), dict.buf...)
eventPool.Put(dict)
return c
} }
// Str adds the field key with val as a string to the logger context. // Str adds the field key with val as a string to the logger context.
func (c Context) Str(key, val string) Context { func (c Context) Str(key, val string) Context {
return c.append(fStr(key, val)) c.l.context = appendString(c.l.context, key, val)
return c
} }
// Err adds the field "error" with err as a string to the logger context. // Err adds the field "error" with err as a string to the logger context.
// To customize the key name, change zerolog.ErrorFieldName. // To customize the key name, change zerolog.ErrorFieldName.
func (c Context) Err(err error) Context { func (c Context) Err(err error) Context {
return c.append(fErr(err)) c.l.context = appendError(c.l.context, err)
return c
} }
// Bool adds the field key with val as a Boolean to the logger context. // Bool adds the field key with val as a Boolean to the logger context.
func (c Context) Bool(key string, b bool) Context { func (c Context) Bool(key string, b bool) Context {
return c.append(fBool(key, b)) c.l.context = appendBool(c.l.context, key, b)
return c
} }
// Int adds the field key with i as a int to the logger context. // Int adds the field key with i as a int to the logger context.
func (c Context) Int(key string, i int) Context { func (c Context) Int(key string, i int) Context {
return c.append(fInt(key, i)) c.l.context = appendInt(c.l.context, key, i)
return c
} }
// Int8 adds the field key with i as a int8 to the logger context. // Int8 adds the field key with i as a int8 to the logger context.
func (c Context) Int8(key string, i int8) Context { func (c Context) Int8(key string, i int8) Context {
return c.append(fInt8(key, i)) c.l.context = appendInt8(c.l.context, key, i)
return c
} }
// Int16 adds the field key with i as a int16 to the logger context. // Int16 adds the field key with i as a int16 to the logger context.
func (c Context) Int16(key string, i int16) Context { func (c Context) Int16(key string, i int16) Context {
return c.append(fInt16(key, i)) c.l.context = appendInt16(c.l.context, key, i)
return c
} }
// Int32 adds the field key with i as a int32 to the logger context. // Int32 adds the field key with i as a int32 to the logger context.
func (c Context) Int32(key string, i int32) Context { func (c Context) Int32(key string, i int32) Context {
return c.append(fInt32(key, i)) c.l.context = appendInt32(c.l.context, key, i)
return c
} }
// Int64 adds the field key with i as a int64 to the logger context. // Int64 adds the field key with i as a int64 to the logger context.
func (c Context) Int64(key string, i int64) Context { func (c Context) Int64(key string, i int64) Context {
return c.append(fInt64(key, i)) c.l.context = appendInt64(c.l.context, key, i)
return c
} }
// Uint adds the field key with i as a uint to the logger context. // Uint adds the field key with i as a uint to the logger context.
func (c Context) Uint(key string, i uint) Context { func (c Context) Uint(key string, i uint) Context {
return c.append(fUint(key, i)) c.l.context = appendUint(c.l.context, key, i)
return c
} }
// Uint8 adds the field key with i as a uint8 to the logger context. // Uint8 adds the field key with i as a uint8 to the logger context.
func (c Context) Uint8(key string, i uint8) Context { func (c Context) Uint8(key string, i uint8) Context {
return c.append(fUint8(key, i)) c.l.context = appendUint8(c.l.context, key, i)
return c
} }
// Uint16 adds the field key with i as a uint16 to the logger context. // Uint16 adds the field key with i as a uint16 to the logger context.
func (c Context) Uint16(key string, i uint16) Context { func (c Context) Uint16(key string, i uint16) Context {
return c.append(fUint16(key, i)) c.l.context = appendUint16(c.l.context, key, i)
return c
} }
// Uint32 adds the field key with i as a uint32 to the logger context. // Uint32 adds the field key with i as a uint32 to the logger context.
func (c Context) Uint32(key string, i uint32) Context { func (c Context) Uint32(key string, i uint32) Context {
return c.append(fUint32(key, i)) c.l.context = appendUint32(c.l.context, key, i)
return c
} }
// Uint64 adds the field key with i as a uint64 to the logger context. // Uint64 adds the field key with i as a uint64 to the logger context.
func (c Context) Uint64(key string, i uint64) Context { func (c Context) Uint64(key string, i uint64) Context {
return c.append(fUint64(key, i)) c.l.context = appendUint64(c.l.context, key, i)
return c
} }
// Float32 adds the field key with f as a float32 to the logger context. // Float32 adds the field key with f as a float32 to the logger context.
func (c Context) Float32(key string, f float32) Context { func (c Context) Float32(key string, f float32) Context {
return c.append(fFloat32(key, f)) c.l.context = appendFloat32(c.l.context, key, f)
return c
} }
// Float64 adds the field key with f as a float64 to the logger context. // Float64 adds the field key with f as a float64 to the logger context.
func (c Context) Float64(key string, f float64) Context { func (c Context) Float64(key string, f float64) Context {
return c.append(fFloat64(key, f)) c.l.context = appendFloat64(c.l.context, key, f)
return c
} }
// Timestamp adds the current local time as UNIX timestamp to the logger context with the "time" key. // Timestamp adds the current local time as UNIX timestamp to the logger context with the "time" key.
// To customize the key name, change zerolog.TimestampFieldName. // To customize the key name, change zerolog.TimestampFieldName.
func (c Context) Timestamp() Context { func (c Context) Timestamp() Context {
return c.append(fTimestamp()) if len(c.l.context) > 0 {
c.l.context[0] = 1
} else {
c.l.context = append(c.l.context, 1)
}
return c
} }
// Time adds the field key with t formated as string using zerolog.TimeFieldFormat. // Time adds the field key with t formated as string using zerolog.TimeFieldFormat.
func (c Context) Time(key string, t time.Time) Context { func (c Context) Time(key string, t time.Time) Context {
return c.append(fTime(key, t)) c.l.context = appendTime(c.l.context, key, t)
return c
} }

214
event.go
View File

@ -1,83 +1,69 @@
package zerolog package zerolog
import ( import (
"bytes"
"fmt" "fmt"
"io"
"io/ioutil" "io/ioutil"
"sync" "sync"
"time" "time"
) )
var pool = &sync.Pool{ var eventPool = &sync.Pool{
New: func() interface{} { New: func() interface{} {
return bytes.NewBuffer(make([]byte, 500)) return &Event{
buf: make([]byte, 0, 500),
}
}, },
} }
// Event represents a log event. It is instancied by one of the level method of // Event represents a log event. It is instancied by one of the level method of
// Logger and finalized by the Msg or Msgf method. // Logger and finalized by the Msg or Msgf method.
type Event struct { type Event struct {
buf *bytes.Buffer buf []byte
w LevelWriter w LevelWriter
level Level level Level
enabled bool enabled bool
done func(msg string) done func(msg string)
} }
func newEvent(w LevelWriter, level Level, enabled bool) Event { func newEvent(w LevelWriter, level Level, enabled bool) *Event {
if !enabled { if !enabled {
return Event{} return &Event{}
} }
buf := pool.Get().(*bytes.Buffer) e := eventPool.Get().(*Event)
buf.Reset() e.buf = e.buf[:1]
buf.WriteByte('{') e.buf[0] = '{'
return Event{ e.w = w
buf: buf, e.level = level
w: w, e.enabled = true
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 return e
} }
// Enabled return false if the event is going to be filtered out by func (e *Event) write() (n int, err error) {
if !e.enabled {
return 0, nil
}
e.buf = append(e.buf, '}', '\n')
n, err = e.w.WriteLevel(e.level, e.buf)
eventPool.Put(e)
return
}
// Enabled return false if the *Event is going to be filtered out by
// log level or sampling. // log level or sampling.
func (e Event) Enabled() bool { func (e *Event) Enabled() bool {
return e.enabled return e.enabled
} }
// Msg sends the event with msg added as the message field if not empty. // Msg sends the *Event with msg added as the message field if not empty.
// //
// NOTICE: once this methid is called, the Event should be disposed. // NOTICE: once this methid is called, the *Event should be disposed.
// Calling Msg twice can have unexpected result. // Calling Msg twice can have unexpected result.
func (e Event) Msg(msg string) (n int, err error) { func (e *Event) Msg(msg string) (n int, err error) {
if !e.enabled { if !e.enabled {
return 0, nil return 0, nil
} }
if msg != "" { if msg != "" {
e.append(fStr(MessageFieldName, msg)) e.buf = appendString(e.buf, MessageFieldName, msg)
} }
if e.done != nil { if e.done != nil {
defer e.done(msg) defer e.done(msg)
@ -85,17 +71,17 @@ func (e Event) Msg(msg string) (n int, err error) {
return e.write() return e.write()
} }
// Msgf sends the event with formated msg added as the message field if not empty. // Msgf sends the *Event with formated msg added as the message field if not empty.
// //
// NOTICE: once this methid is called, the Event should be disposed. // NOTICE: once this methid is called, the *Event should be disposed.
// Calling Msg twice can have unexpected result. // Calling Msg twice can have unexpected result.
func (e Event) Msgf(format string, v ...interface{}) (n int, err error) { func (e *Event) Msgf(format string, v ...interface{}) (n int, err error) {
if !e.enabled { if !e.enabled {
return 0, nil return 0, nil
} }
msg := fmt.Sprintf(format, v...) msg := fmt.Sprintf(format, v...)
if msg != "" { if msg != "" {
e.append(fStr(MessageFieldName, msg)) e.buf = appendString(e.buf, MessageFieldName, msg)
} }
if e.done != nil { if e.done != nil {
defer e.done(msg) defer e.done(msg)
@ -103,161 +89,175 @@ func (e Event) Msgf(format string, v ...interface{}) (n int, err error) {
return e.write() return e.write()
} }
// Dict adds the field key with a dict to the event context. // Dict adds the field key with a dict to the *Event context.
// Use zerolog.Dict() to create the dictionary. // Use zerolog.Dict() to create the dictionary.
func (e Event) Dict(key string, dict Event) Event { func (e *Event) Dict(key string, dict *Event) *Event {
if !e.enabled { if !e.enabled {
return e return e
} }
if e.buf.Len() > 1 { e.buf = append(append(appendKey(e.buf, key), dict.buf...), '}')
e.buf.WriteByte(',') eventPool.Put(dict)
}
io.Copy(e.buf, dict.buf)
e.buf.WriteByte('}')
return e return e
} }
// Dict creates an Event to be used with the event.Dict method. // Dict creates an Event to be used with the *Event.Dict method.
// Call usual field methods like Str, Int etc to add fields to this // Call usual field methods like Str, Int etc to add fields to this
// event and give it as argument the event.Dict method. // event and give it as argument the *Event.Dict method.
func Dict() Event { func Dict() *Event {
return newEvent(levelWriterAdapter{ioutil.Discard}, 0, true) return newEvent(levelWriterAdapter{ioutil.Discard}, 0, true)
} }
// Str adds the field key with val as a string to the event context. // Str adds the field key with val as a string to the *Event context.
func (e Event) Str(key, val string) Event { func (e *Event) Str(key, val string) *Event {
if !e.enabled { if !e.enabled {
return e return e
} }
return e.append(fStr(key, val)) e.buf = appendString(e.buf, key, val)
return e
} }
// Err adds the field "error" with err as a string to the event context. // Err adds the field "error" with err as a string to the *Event context.
// To customize the key name, change zerolog.ErrorFieldName. // To customize the key name, change zerolog.ErrorFieldName.
func (e Event) Err(err error) Event { func (e *Event) Err(err error) *Event {
if !e.enabled { if !e.enabled {
return e return e
} }
return e.append(fErr(err)) e.buf = appendError(e.buf, err)
return e
} }
// Bool adds the field key with val as a Boolean to the event context. // Bool adds the field key with val as a Boolean to the *Event context.
func (e Event) Bool(key string, b bool) Event { func (e *Event) Bool(key string, b bool) *Event {
if !e.enabled { if !e.enabled {
return e return e
} }
return e.append(fBool(key, b)) e.buf = appendBool(e.buf, key, b)
return e
} }
// Int adds the field key with i as a int to the event context. // Int adds the field key with i as a int to the *Event context.
func (e Event) Int(key string, i int) Event { func (e *Event) Int(key string, i int) *Event {
if !e.enabled { if !e.enabled {
return e return e
} }
return e.append(fInt(key, i)) e.buf = appendInt(e.buf, key, i)
return e
} }
// Int8 adds the field key with i as a int8 to the event context. // Int8 adds the field key with i as a int8 to the *Event context.
func (e Event) Int8(key string, i int8) Event { func (e *Event) Int8(key string, i int8) *Event {
if !e.enabled { if !e.enabled {
return e return e
} }
return e.append(fInt8(key, i)) e.buf = appendInt8(e.buf, key, i)
return e
} }
// Int16 adds the field key with i as a int16 to the event context. // Int16 adds the field key with i as a int16 to the *Event context.
func (e Event) Int16(key string, i int16) Event { func (e *Event) Int16(key string, i int16) *Event {
if !e.enabled { if !e.enabled {
return e return e
} }
return e.append(fInt16(key, i)) e.buf = appendInt16(e.buf, key, i)
return e
} }
// Int32 adds the field key with i as a int32 to the event context. // Int32 adds the field key with i as a int32 to the *Event context.
func (e Event) Int32(key string, i int32) Event { func (e *Event) Int32(key string, i int32) *Event {
if !e.enabled { if !e.enabled {
return e return e
} }
return e.append(fInt32(key, i)) e.buf = appendInt32(e.buf, key, i)
return e
} }
// Int64 adds the field key with i as a int64 to the event context. // Int64 adds the field key with i as a int64 to the *Event context.
func (e Event) Int64(key string, i int64) Event { func (e *Event) Int64(key string, i int64) *Event {
if !e.enabled { if !e.enabled {
return e return e
} }
return e.append(fInt64(key, i)) e.buf = appendInt64(e.buf, key, i)
return e
} }
// Uint adds the field key with i as a uint to the event context. // Uint adds the field key with i as a uint to the *Event context.
func (e Event) Uint(key string, i uint) Event { func (e *Event) Uint(key string, i uint) *Event {
if !e.enabled { if !e.enabled {
return e return e
} }
return e.append(fUint(key, i)) e.buf = appendUint(e.buf, key, i)
return e
} }
// Uint8 adds the field key with i as a uint8 to the event context. // Uint8 adds the field key with i as a uint8 to the *Event context.
func (e Event) Uint8(key string, i uint8) Event { func (e *Event) Uint8(key string, i uint8) *Event {
if !e.enabled { if !e.enabled {
return e return e
} }
return e.append(fUint8(key, i)) e.buf = appendUint8(e.buf, key, i)
return e
} }
// Uint16 adds the field key with i as a uint16 to the event context. // Uint16 adds the field key with i as a uint16 to the *Event context.
func (e Event) Uint16(key string, i uint16) Event { func (e *Event) Uint16(key string, i uint16) *Event {
if !e.enabled { if !e.enabled {
return e return e
} }
return e.append(fUint16(key, i)) e.buf = appendUint16(e.buf, key, i)
return e
} }
// Uint32 adds the field key with i as a uint32 to the event context. // Uint32 adds the field key with i as a uint32 to the *Event context.
func (e Event) Uint32(key string, i uint32) Event { func (e *Event) Uint32(key string, i uint32) *Event {
if !e.enabled { if !e.enabled {
return e return e
} }
return e.append(fUint32(key, i)) e.buf = appendUint32(e.buf, key, i)
return e
} }
// Uint64 adds the field key with i as a uint64 to the event context. // Uint64 adds the field key with i as a uint64 to the *Event context.
func (e Event) Uint64(key string, i uint64) Event { func (e *Event) Uint64(key string, i uint64) *Event {
if !e.enabled { if !e.enabled {
return e return e
} }
return e.append(fUint64(key, i)) e.buf = appendUint64(e.buf, key, i)
return e
} }
// Float32 adds the field key with f as a float32 to the event context. // Float32 adds the field key with f as a float32 to the *Event context.
func (e Event) Float32(key string, f float32) Event { func (e *Event) Float32(key string, f float32) *Event {
if !e.enabled { if !e.enabled {
return e return e
} }
return e.append(fFloat32(key, f)) e.buf = appendFloat32(e.buf, key, f)
return e
} }
// Float64 adds the field key with f as a float64 to the event context. // Float64 adds the field key with f as a float64 to the *Event context.
func (e Event) Float64(key string, f float64) Event { func (e *Event) Float64(key string, f float64) *Event {
if !e.enabled { if !e.enabled {
return e return e
} }
return e.append(fFloat64(key, f)) e.buf = appendFloat64(e.buf, key, f)
return e
} }
// Timestamp adds the current local time as UNIX timestamp to the event context with the "time" key. // Timestamp adds the current local time as UNIX timestamp to the *Event context with the "time" key.
// To customize the key name, change zerolog.TimestampFieldName. // To customize the key name, change zerolog.TimestampFieldName.
func (e Event) Timestamp() Event { func (e *Event) Timestamp() *Event {
if !e.enabled { if !e.enabled {
return e return e
} }
return e.append(fTimestamp()) e.buf = appendTimestamp(e.buf)
return e
} }
// Time adds the field key with t formated as string using zerolog.TimeFieldFormat. // Time adds the field key with t formated as string using zerolog.TimeFieldFormat.
func (e Event) Time(key string, t time.Time) Event { func (e *Event) Time(key string, t time.Time) *Event {
if !e.enabled { if !e.enabled {
return e return e
} }
return e.append(fTime(key, t)) e.buf = appendTime(e.buf, key, t)
return e
} }

211
field.go
View File

@ -1,141 +1,94 @@
package zerolog package zerolog
import ( import (
"bytes" "encoding/json"
"fmt"
"strconv" "strconv"
"time" "time"
) )
var now = time.Now var now = time.Now
type fieldMode uint8 func appendKey(dst []byte, key string) []byte {
if len(dst) > 1 {
const ( dst = append(dst, ',')
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) dst = appendJSONString(dst, key)
buf.WriteByte(':') return append(dst, ':')
switch f.mode { }
case quotedFieldMode:
writeJSONString(buf, f.val) func appendString(dst []byte, key, val string) []byte {
case rawFieldMode: return appendJSONString(appendKey(dst, key), val)
buf.WriteString(f.val) }
default:
panic("unknown field mode") func appendError(dst []byte, err error) []byte {
return appendJSONString(appendKey(dst, ErrorFieldName), err.Error())
}
func appendBool(dst []byte, key string, val bool) []byte {
return strconv.AppendBool(appendKey(dst, key), val)
}
func appendInt(dst []byte, key string, val int) []byte {
return strconv.AppendInt(appendKey(dst, key), int64(val), 10)
}
func appendInt8(dst []byte, key string, val int8) []byte {
return strconv.AppendInt(appendKey(dst, key), int64(val), 10)
}
func appendInt16(dst []byte, key string, val int16) []byte {
return strconv.AppendInt(appendKey(dst, key), int64(val), 10)
}
func appendInt32(dst []byte, key string, val int32) []byte {
return strconv.AppendInt(appendKey(dst, key), int64(val), 10)
}
func appendInt64(dst []byte, key string, val int64) []byte {
return strconv.AppendInt(appendKey(dst, key), int64(val), 10)
}
func appendUint(dst []byte, key string, val uint) []byte {
return strconv.AppendUint(appendKey(dst, key), uint64(val), 10)
}
func appendUint8(dst []byte, key string, val uint8) []byte {
return strconv.AppendUint(appendKey(dst, key), uint64(val), 10)
}
func appendUint16(dst []byte, key string, val uint16) []byte {
return strconv.AppendUint(appendKey(dst, key), uint64(val), 10)
}
func appendUint32(dst []byte, key string, val uint32) []byte {
return strconv.AppendUint(appendKey(dst, key), uint64(val), 10)
}
func appendUint64(dst []byte, key string, val uint64) []byte {
return strconv.AppendUint(appendKey(dst, key), uint64(val), 10)
}
func appendFloat32(dst []byte, key string, val float32) []byte {
return strconv.AppendFloat(appendKey(dst, key), float64(val), 'f', -1, 32)
}
func appendFloat64(dst []byte, key string, val float64) []byte {
return strconv.AppendFloat(appendKey(dst, key), float64(val), 'f', -1, 32)
}
func appendTime(dst []byte, key string, t time.Time) []byte {
return append(t.AppendFormat(append(appendKey(dst, key), '"'), TimeFieldFormat), '"')
}
func appendTimestamp(dst []byte) []byte {
return appendInt64(dst, TimestampFieldName, now().Unix())
}
func appendObject(dst []byte, key string, obj interface{}) []byte {
marshaled, err := json.Marshal(obj)
if err != nil {
return appendString(dst, key, fmt.Sprintf("marshaling error: %v", err))
} }
} return append(appendKey(dst, key), marshaled...)
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}
}
func fRaw(key string, raw string) field {
return field{key, rawFieldMode, raw, nil}
} }

78
json.go
View File

@ -1,42 +1,31 @@
package zerolog package zerolog
import ( import "unicode/utf8"
"bytes"
"unicode/utf8"
)
const hex = "0123456789abcdef" const hex = "0123456789abcdef"
func writeJSONString(buf *bytes.Buffer, s string) { func appendJSONString(dst []byte, s string) []byte {
buf.WriteByte('"') dst = append(dst, '"')
for i := 0; i < len(s); { for i := 0; i < len(s); {
if b := s[i]; b < utf8.RuneSelf { if b := s[i]; b < utf8.RuneSelf {
switch b { switch b {
case '"', '\\': case '"', '\\':
buf.WriteByte('\\') dst = append(dst, '\\', b)
buf.WriteByte(b)
case '\b': case '\b':
buf.WriteByte('\\') dst = append(dst, '\\', 'b')
buf.WriteByte('b')
case '\f': case '\f':
buf.WriteByte('\\') dst = append(dst, '\\', 'f')
buf.WriteByte('f')
case '\n': case '\n':
buf.WriteByte('\\') dst = append(dst, '\\', 'n')
buf.WriteByte('n')
case '\r': case '\r':
buf.WriteByte('\\') dst = append(dst, '\\', 'r')
buf.WriteByte('r')
case '\t': case '\t':
buf.WriteByte('\\') dst = append(dst, '\\', 't')
buf.WriteByte('t')
default: default:
if b >= 0x20 { if b >= 0x20 {
buf.WriteByte(b) dst = append(dst, b)
} else { } else {
buf.WriteString(`\u00`) dst = append(dst, '\\', 'u', '0', '0', hex[b>>4], hex[b&0xF])
buf.WriteByte(hex[b>>4])
buf.WriteByte(hex[b&0xF])
} }
} }
i++ i++
@ -44,12 +33,51 @@ func writeJSONString(buf *bytes.Buffer, s string) {
} }
r, size := utf8.DecodeRuneInString(s[i:]) r, size := utf8.DecodeRuneInString(s[i:])
if r == utf8.RuneError && size == 1 { if r == utf8.RuneError && size == 1 {
buf.WriteString(`\ufffd`) dst = append(dst, `\ufffd`...)
i++ i++
continue continue
} }
buf.WriteString(s[i : i+size]) dst = append(dst, s[i:i+size]...)
i += size i += size
} }
buf.WriteByte('"') return append(dst, '"')
}
func appendJSONBytes(dst []byte, s []byte) []byte {
dst = append(dst, '"')
for i := 0; i < len(s); {
if b := s[i]; b < utf8.RuneSelf {
switch b {
case '"', '\\':
dst = append(dst, '\\', b)
case '\b':
dst = append(dst, '\\', 'b')
case '\f':
dst = append(dst, '\\', 'f')
case '\n':
dst = append(dst, '\\', 'n')
case '\r':
dst = append(dst, '\\', 'r')
case '\t':
dst = append(dst, '\\', 't')
default:
if b >= 0x20 {
dst = append(dst, b)
} else {
dst = append(dst, '\\', 'u', '0', '0', hex[b>>4], hex[b&0xF])
}
}
i++
continue
}
r, size := utf8.DecodeRune(s[i:])
if r == utf8.RuneError && size == 1 {
dst = append(dst, `\ufffd`...)
i++
continue
}
dst = append(dst, s[i:i+size]...)
i += size
}
return append(dst, '"')
} }

68
log.go
View File

@ -74,10 +74,6 @@ import (
"sync/atomic" "sync/atomic"
) )
type parentLogger interface {
addContextField(Event)
}
// Level defines log levels. // Level defines log levels.
type Level uint8 type Level uint8
@ -133,13 +129,11 @@ var disabledEvent = newEvent(levelWriterAdapter{ioutil.Discard}, 0, false)
// serialization to the Writer. If your Writer is not thread safe, // serialization to the Writer. If your Writer is not thread safe,
// you may consider a sync wrapper. // you may consider a sync wrapper.
type Logger struct { type Logger struct {
root bool
parent parentLogger
w LevelWriter w LevelWriter
field field
level Level level Level
sample uint32 sample uint32
counter *uint32 counter *uint32
context []byte
} }
// New creates a root logger with given output writer. If the output writer implements // New creates a root logger with given output writer. If the output writer implements
@ -157,25 +151,30 @@ func New(w io.Writer) Logger {
if !ok { if !ok {
lw = levelWriterAdapter{w} lw = levelWriterAdapter{w}
} }
return Logger{ return Logger{w: lw}
root: true,
w: lw,
}
} }
// With creates a child logger with the field added to its context. // With creates a child logger with the field added to its context.
func (l Logger) With() Context { func (l Logger) With() Context {
context := l.context
l.context = make([]byte, 0, 500)
if context != nil {
l.context = append(l.context, context...)
} else {
// first byte of context is presence of timestamp or not
l.context = append(l.context, 0)
}
return Context{l} return Context{l}
} }
// Level crestes a child logger with the minium accepted level set to level. // Level crestes a child logger with the minium accepted level set to level.
func (l Logger) Level(lvl Level) Logger { func (l Logger) Level(lvl Level) Logger {
return Logger{ return Logger{
parent: l,
w: l.w, w: l.w,
level: lvl, level: lvl,
sample: l.sample, sample: l.sample,
counter: l.counter, counter: l.counter,
context: l.context,
} }
} }
@ -184,45 +183,45 @@ func (l Logger) Sample(every int) Logger {
if every == 0 { if every == 0 {
// Create a child with no sampling. // Create a child with no sampling.
return Logger{ return Logger{
parent: l, w: l.w,
w: l.w, level: l.level,
level: l.level, context: l.context,
} }
} }
return Logger{ return Logger{
parent: l,
w: l.w, w: l.w,
level: l.level, level: l.level,
sample: uint32(every), sample: uint32(every),
counter: new(uint32), counter: new(uint32),
context: l.context,
} }
} }
// Debug starts a new message with debug level. // Debug starts a new message with debug level.
// //
// You must call Msg on the returned event in order to send the event. // You must call Msg on the returned event in order to send the event.
func (l Logger) Debug() Event { func (l Logger) Debug() *Event {
return l.newEvent(DebugLevel, true, nil) return l.newEvent(DebugLevel, true, nil)
} }
// Info starts a new message with info level. // Info starts a new message with info level.
// //
// You must call Msg on the returned event in order to send the event. // You must call Msg on the returned event in order to send the event.
func (l Logger) Info() Event { func (l Logger) Info() *Event {
return l.newEvent(InfoLevel, true, nil) return l.newEvent(InfoLevel, true, nil)
} }
// Warn starts a new message with warn level. // Warn starts a new message with warn level.
// //
// You must call Msg on the returned event in order to send the event. // You must call Msg on the returned event in order to send the event.
func (l Logger) Warn() Event { func (l Logger) Warn() *Event {
return l.newEvent(WarnLevel, true, nil) return l.newEvent(WarnLevel, true, nil)
} }
// Error starts a new message with error level. // Error starts a new message with error level.
// //
// You must call Msg on the returned event in order to send the event. // You must call Msg on the returned event in order to send the event.
func (l Logger) Error() Event { func (l Logger) Error() *Event {
return l.newEvent(ErrorLevel, true, nil) return l.newEvent(ErrorLevel, true, nil)
} }
@ -230,7 +229,7 @@ func (l Logger) Error() Event {
// is called by the Msg method. // is called by the Msg method.
// //
// You must call Msg on the returned event in order to send the event. // You must call Msg on the returned event in order to send the event.
func (l Logger) Fatal() Event { func (l Logger) Fatal() *Event {
return l.newEvent(FatalLevel, true, func(msg string) { os.Exit(1) }) return l.newEvent(FatalLevel, true, func(msg string) { os.Exit(1) })
} }
@ -238,7 +237,7 @@ func (l Logger) Fatal() Event {
// to the panic function. // to the panic function.
// //
// You must call Msg on the returned event in order to send the event. // You must call Msg on the returned event in order to send the event.
func (l Logger) Panic() Event { func (l Logger) Panic() *Event {
return l.newEvent(PanicLevel, true, func(msg string) { panic(msg) }) return l.newEvent(PanicLevel, true, func(msg string) { panic(msg) })
} }
@ -246,11 +245,11 @@ func (l Logger) Panic() Event {
// will still disable events produced by this method. // will still disable events produced by this method.
// //
// You must call Msg on the returned event in order to send the event. // You must call Msg on the returned event in order to send the event.
func (l Logger) Log() Event { func (l Logger) Log() *Event {
return l.newEvent(ErrorLevel, false, nil) return l.newEvent(ErrorLevel, false, nil)
} }
func (l Logger) newEvent(level Level, addLevelField bool, done func(string)) Event { func (l Logger) newEvent(level Level, addLevelField bool, done func(string)) *Event {
enabled := l.should(level) enabled := l.should(level)
if !enabled { if !enabled {
return disabledEvent return disabledEvent
@ -266,7 +265,17 @@ func (l Logger) newEvent(level Level, addLevelField bool, done func(string)) Eve
if l.sample > 0 && SampleFieldName != "" { if l.sample > 0 && SampleFieldName != "" {
e.Uint32(SampleFieldName, l.sample) e.Uint32(SampleFieldName, l.sample)
} }
l.addContextField(e) if l.context != nil && len(l.context) > 0 {
if l.context[0] > 0 { // ts flag
e.buf = appendTimestamp(e.buf)
}
if len(l.context) > 1 {
if len(e.buf) > 1 {
e.buf = append(e.buf, ',')
}
e.buf = append(e.buf, l.context[1:]...)
}
}
return e return e
} }
@ -281,12 +290,3 @@ func (l Logger) should(lvl Level) bool {
} }
return true return true
} }
func (l Logger) addContextField(e Event) {
if !l.root {
l.parent.addContextField(e)
}
if l.field.mode != zeroFieldMode {
e.append(l.field)
}
}

View File

@ -28,28 +28,28 @@ func Sample(every int) zerolog.Logger {
// Debug starts a new message with debug level. // Debug starts a new message with debug level.
// //
// You must call Msg on the returned event in order to send the event. // You must call Msg on the returned event in order to send the event.
func Debug() zerolog.Event { func Debug() *zerolog.Event {
return Logger.Debug() return Logger.Debug()
} }
// Info starts a new message with info level. // Info starts a new message with info level.
// //
// You must call Msg on the returned event in order to send the event. // You must call Msg on the returned event in order to send the event.
func Info() zerolog.Event { func Info() *zerolog.Event {
return Logger.Info() return Logger.Info()
} }
// Warn starts a new message with warn level. // Warn starts a new message with warn level.
// //
// You must call Msg on the returned event in order to send the event. // You must call Msg on the returned event in order to send the event.
func Warn() zerolog.Event { func Warn() *zerolog.Event {
return Logger.Warn() return Logger.Warn()
} }
// Error starts a new message with error level. // Error starts a new message with error level.
// //
// You must call Msg on the returned event in order to send the event. // You must call Msg on the returned event in order to send the event.
func Error() zerolog.Event { func Error() *zerolog.Event {
return Logger.Error() return Logger.Error()
} }
@ -57,7 +57,7 @@ func Error() zerolog.Event {
// is called by the Msg method. // is called by the Msg method.
// //
// You must call Msg on the returned event in order to send the event. // You must call Msg on the returned event in order to send the event.
func Fatal() zerolog.Event { func Fatal() *zerolog.Event {
return Logger.Fatal() return Logger.Fatal()
} }
@ -65,7 +65,7 @@ func Fatal() zerolog.Event {
// to the panic function. // to the panic function.
// //
// You must call Msg on the returned event in order to send the event. // You must call Msg on the returned event in order to send the event.
func Panic() zerolog.Event { func Panic() *zerolog.Event {
return Logger.Panic() return Logger.Panic()
} }
@ -73,6 +73,6 @@ func Panic() zerolog.Event {
// zerlog.Disabled will still disable events produced by this method. // zerlog.Disabled will still disable events produced by this method.
// //
// You must call Msg on the returned event in order to send the event. // You must call Msg on the returned event in order to send the event.
func Log() zerolog.Event { func Log() *zerolog.Event {
return Logger.Log() return Logger.Log()
} }

View File

@ -111,7 +111,7 @@ func ExampleEvent_Dict() {
). ).
Msg("hello world") Msg("hello world")
// Output: {"foo":"bar",{"bar":"baz","n":1},"message":"hello world"} // Output: {"foo":"bar","dict":{"bar":"baz","n":1},"message":"hello world"}
} }
func ExampleContext_Dict() { func ExampleContext_Dict() {