diff --git a/benchmark_test.go b/benchmark_test.go index 1b6b67d..1732fb6 100644 --- a/benchmark_test.go +++ b/benchmark_test.go @@ -102,7 +102,34 @@ func BenchmarkLogFieldType(b *testing.B) { time.Unix(8, 0), time.Unix(9, 0), } - o := obj{"a", "a", 0} + interfaces := []struct { + Pub string + Tag string `json:"tag"` + priv int + }{ + {"a", "a", 0}, + {"a", "a", 0}, + {"a", "a", 0}, + {"a", "a", 0}, + {"a", "a", 0}, + {"a", "a", 0}, + {"a", "a", 0}, + {"a", "a", 0}, + {"a", "a", 0}, + {"a", "a", 0}, + } + objects := []obj{ + obj{"a", "a", 0}, + obj{"a", "a", 0}, + obj{"a", "a", 0}, + obj{"a", "a", 0}, + obj{"a", "a", 0}, + obj{"a", "a", 0}, + obj{"a", "a", 0}, + obj{"a", "a", 0}, + obj{"a", "a", 0}, + obj{"a", "a", 0}, + } errs := []error{errors.New("a"), errors.New("b"), errors.New("c"), errors.New("d"), errors.New("e")} types := map[string]func(e *Event) *Event{ "Bool": func(e *Event) *Event { @@ -148,10 +175,19 @@ func BenchmarkLogFieldType(b *testing.B) { return e.Durs("k", durations) }, "Interface": func(e *Event) *Event { - return e.Interface("k", o) + return e.Interface("k", interfaces[0]) + }, + "Interfaces": func(e *Event) *Event { + return e.Interface("k", interfaces) + }, + "Interface(Object)": func(e *Event) *Event { + return e.Interface("k", objects[0]) + }, + "Interface(Objects)": func(e *Event) *Event { + return e.Interface("k", objects) }, "Object": func(e *Event) *Event { - return e.Object("k", o) + return e.Object("k", objects[0]) }, } logger := New(ioutil.Discard) diff --git a/context.go b/context.go index 68aaef7..021659b 100644 --- a/context.go +++ b/context.go @@ -3,6 +3,8 @@ package zerolog import ( "io/ioutil" "time" + + "github.com/rs/zerolog/internal/json" ) // Context configures a new sub-logger with contextual fields. @@ -24,7 +26,7 @@ func (c Context) Fields(fields map[string]interface{}) Context { // Dict adds the field key with the dict to the logger context. func (c Context) Dict(key string, dict *Event) Context { dict.buf = append(dict.buf, '}') - c.l.context = append(appendKey(c.l.context, key), dict.buf...) + c.l.context = append(json.AppendKey(c.l.context, key), dict.buf...) eventPool.Put(dict) return c } @@ -41,194 +43,198 @@ func (c Context) Object(key string, obj LogObjectMarshaler) Context { // Str adds the field key with val as a string to the logger context. func (c Context) Str(key, val string) Context { - c.l.context = appendString(c.l.context, key, val) + c.l.context = json.AppendString(json.AppendKey(c.l.context, key), val) return c } // Strs adds the field key with val as a string to the logger context. func (c Context) Strs(key string, vals []string) Context { - c.l.context = appendStrings(c.l.context, key, vals) + c.l.context = json.AppendStrings(json.AppendKey(c.l.context, key), vals) return c } // Bytes adds the field key with val as a []byte to the logger context. func (c Context) Bytes(key string, val []byte) Context { - c.l.context = appendBytes(c.l.context, key, val) + c.l.context = json.AppendBytes(json.AppendKey(c.l.context, key), val) return c } // AnErr adds the field key with err as a string to the logger context. func (c Context) AnErr(key string, err error) Context { - c.l.context = appendErrorKey(c.l.context, key, err) + if err != nil { + c.l.context = json.AppendError(json.AppendKey(c.l.context, key), err) + } return c } // Errs adds the field key with errs as an array of strings to the logger context. func (c Context) Errs(key string, errs []error) Context { - c.l.context = appendErrorsKey(c.l.context, key, errs) + c.l.context = json.AppendErrors(json.AppendKey(c.l.context, key), errs) return c } // Err adds the field "error" with err as a string to the logger context. // To customize the key name, change zerolog.ErrorFieldName. func (c Context) Err(err error) Context { - c.l.context = appendError(c.l.context, err) + if err != nil { + c.l.context = json.AppendError(json.AppendKey(c.l.context, ErrorFieldName), err) + } return c } // Bool adds the field key with val as a bool to the logger context. func (c Context) Bool(key string, b bool) Context { - c.l.context = appendBool(c.l.context, key, b) + c.l.context = json.AppendBool(json.AppendKey(c.l.context, key), b) return c } // Bools adds the field key with val as a []bool to the logger context. func (c Context) Bools(key string, b []bool) Context { - c.l.context = appendBools(c.l.context, key, b) + c.l.context = json.AppendBools(json.AppendKey(c.l.context, key), b) return c } // Int adds the field key with i as a int to the logger context. func (c Context) Int(key string, i int) Context { - c.l.context = appendInt(c.l.context, key, i) + c.l.context = json.AppendInt(json.AppendKey(c.l.context, key), i) return c } // Ints adds the field key with i as a []int to the logger context. func (c Context) Ints(key string, i []int) Context { - c.l.context = appendInts(c.l.context, key, i) + c.l.context = json.AppendInts(json.AppendKey(c.l.context, key), i) return c } // Int8 adds the field key with i as a int8 to the logger context. func (c Context) Int8(key string, i int8) Context { - c.l.context = appendInt8(c.l.context, key, i) + c.l.context = json.AppendInt8(json.AppendKey(c.l.context, key), i) return c } // Ints8 adds the field key with i as a []int8 to the logger context. func (c Context) Ints8(key string, i []int8) Context { - c.l.context = appendInts8(c.l.context, key, i) + c.l.context = json.AppendInts8(json.AppendKey(c.l.context, key), i) return c } // Int16 adds the field key with i as a int16 to the logger context. func (c Context) Int16(key string, i int16) Context { - c.l.context = appendInt16(c.l.context, key, i) + c.l.context = json.AppendInt16(json.AppendKey(c.l.context, key), i) return c } // Ints16 adds the field key with i as a []int16 to the logger context. func (c Context) Ints16(key string, i []int16) Context { - c.l.context = appendInts16(c.l.context, key, i) + c.l.context = json.AppendInts16(json.AppendKey(c.l.context, key), i) return c } // Int32 adds the field key with i as a int32 to the logger context. func (c Context) Int32(key string, i int32) Context { - c.l.context = appendInt32(c.l.context, key, i) + c.l.context = json.AppendInt32(json.AppendKey(c.l.context, key), i) return c } // Ints32 adds the field key with i as a []int32 to the logger context. func (c Context) Ints32(key string, i []int32) Context { - c.l.context = appendInts32(c.l.context, key, i) + c.l.context = json.AppendInts32(json.AppendKey(c.l.context, key), i) return c } // Int64 adds the field key with i as a int64 to the logger context. func (c Context) Int64(key string, i int64) Context { - c.l.context = appendInt64(c.l.context, key, i) + c.l.context = json.AppendInt64(json.AppendKey(c.l.context, key), i) return c } // Ints64 adds the field key with i as a []int64 to the logger context. func (c Context) Ints64(key string, i []int64) Context { - c.l.context = appendInts64(c.l.context, key, i) + c.l.context = json.AppendInts64(json.AppendKey(c.l.context, key), i) return c } // Uint adds the field key with i as a uint to the logger context. func (c Context) Uint(key string, i uint) Context { - c.l.context = appendUint(c.l.context, key, i) + c.l.context = json.AppendUint(json.AppendKey(c.l.context, key), i) return c } // Uints adds the field key with i as a []uint to the logger context. func (c Context) Uints(key string, i []uint) Context { - c.l.context = appendUints(c.l.context, key, i) + c.l.context = json.AppendUints(json.AppendKey(c.l.context, key), i) return c } // Uint8 adds the field key with i as a uint8 to the logger context. func (c Context) Uint8(key string, i uint8) Context { - c.l.context = appendUint8(c.l.context, key, i) + c.l.context = json.AppendUint8(json.AppendKey(c.l.context, key), i) return c } // Uints8 adds the field key with i as a []uint8 to the logger context. func (c Context) Uints8(key string, i []uint8) Context { - c.l.context = appendUints8(c.l.context, key, i) + c.l.context = json.AppendUints8(json.AppendKey(c.l.context, key), i) return c } // Uint16 adds the field key with i as a uint16 to the logger context. func (c Context) Uint16(key string, i uint16) Context { - c.l.context = appendUint16(c.l.context, key, i) + c.l.context = json.AppendUint16(json.AppendKey(c.l.context, key), i) return c } // Uints16 adds the field key with i as a []uint16 to the logger context. func (c Context) Uints16(key string, i []uint16) Context { - c.l.context = appendUints16(c.l.context, key, i) + c.l.context = json.AppendUints16(json.AppendKey(c.l.context, key), i) return c } // Uint32 adds the field key with i as a uint32 to the logger context. func (c Context) Uint32(key string, i uint32) Context { - c.l.context = appendUint32(c.l.context, key, i) + c.l.context = json.AppendUint32(json.AppendKey(c.l.context, key), i) return c } // Uints32 adds the field key with i as a []uint32 to the logger context. func (c Context) Uints32(key string, i []uint32) Context { - c.l.context = appendUints32(c.l.context, key, i) + c.l.context = json.AppendUints32(json.AppendKey(c.l.context, key), i) return c } // Uint64 adds the field key with i as a uint64 to the logger context. func (c Context) Uint64(key string, i uint64) Context { - c.l.context = appendUint64(c.l.context, key, i) + c.l.context = json.AppendUint64(json.AppendKey(c.l.context, key), i) return c } // Uints64 adds the field key with i as a []uint64 to the logger context. func (c Context) Uints64(key string, i []uint64) Context { - c.l.context = appendUints64(c.l.context, key, i) + c.l.context = json.AppendUints64(json.AppendKey(c.l.context, key), i) return c } // Float32 adds the field key with f as a float32 to the logger context. func (c Context) Float32(key string, f float32) Context { - c.l.context = appendFloat32(c.l.context, key, f) + c.l.context = json.AppendFloat32(json.AppendKey(c.l.context, key), f) return c } // Floats32 adds the field key with f as a []float32 to the logger context. func (c Context) Floats32(key string, f []float32) Context { - c.l.context = appendFloats32(c.l.context, key, f) + c.l.context = json.AppendFloats32(json.AppendKey(c.l.context, key), f) return c } // Float64 adds the field key with f as a float64 to the logger context. func (c Context) Float64(key string, f float64) Context { - c.l.context = appendFloat64(c.l.context, key, f) + c.l.context = json.AppendFloat64(json.AppendKey(c.l.context, key), f) return c } // Floats64 adds the field key with f as a []float64 to the logger context. func (c Context) Floats64(key string, f []float64) Context { - c.l.context = appendFloats64(c.l.context, key, f) + c.l.context = json.AppendFloats64(json.AppendKey(c.l.context, key), f) return c } @@ -245,30 +251,30 @@ func (c Context) Timestamp() Context { // Time adds the field key with t formated as string using zerolog.TimeFieldFormat. func (c Context) Time(key string, t time.Time) Context { - c.l.context = appendTime(c.l.context, key, t) + c.l.context = json.AppendTime(json.AppendKey(c.l.context, key), t, TimeFieldFormat) return c } // Times adds the field key with t formated as string using zerolog.TimeFieldFormat. func (c Context) Times(key string, t []time.Time) Context { - c.l.context = appendTimes(c.l.context, key, t) + c.l.context = json.AppendTimes(json.AppendKey(c.l.context, key), t, TimeFieldFormat) return c } // Dur adds the fields key with d divided by unit and stored as a float. func (c Context) Dur(key string, d time.Duration) Context { - c.l.context = appendDuration(c.l.context, key, d) + c.l.context = json.AppendDuration(json.AppendKey(c.l.context, key), d, DurationFieldUnit, DurationFieldInteger) return c } // Durs adds the fields key with d divided by unit and stored as a float. func (c Context) Durs(key string, d []time.Duration) Context { - c.l.context = appendDurations(c.l.context, key, d) + c.l.context = json.AppendDurations(json.AppendKey(c.l.context, key), d, DurationFieldUnit, DurationFieldInteger) return c } // Interface adds the field key with obj marshaled using reflection. func (c Context) Interface(key string, i interface{}) Context { - c.l.context = appendInterface(c.l.context, key, i) + c.l.context = json.AppendInterface(json.AppendKey(c.l.context, key), i) return c } diff --git a/event.go b/event.go index 3778be2..72e3288 100644 --- a/event.go +++ b/event.go @@ -6,6 +6,8 @@ import ( "os" "sync" "time" + + "github.com/rs/zerolog/internal/json" ) var eventPool = &sync.Pool{ @@ -68,7 +70,7 @@ func (e *Event) Msg(msg string) { return } if msg != "" { - e.buf = appendString(e.buf, MessageFieldName, msg) + e.buf = json.AppendString(json.AppendKey(e.buf, MessageFieldName), msg) } if e.done != nil { defer e.done(msg) @@ -88,7 +90,7 @@ func (e *Event) Msgf(format string, v ...interface{}) { } msg := fmt.Sprintf(format, v...) if msg != "" { - e.buf = appendString(e.buf, MessageFieldName, msg) + e.buf = json.AppendString(json.AppendKey(e.buf, MessageFieldName), msg) } if e.done != nil { defer e.done(msg) @@ -113,7 +115,7 @@ func (e *Event) Dict(key string, dict *Event) *Event { if !e.enabled { return e } - e.buf = append(append(appendKey(e.buf, key), dict.buf...), '}') + e.buf = append(append(json.AppendKey(e.buf, key), dict.buf...), '}') eventPool.Put(dict) return e } @@ -125,9 +127,7 @@ func Dict() *Event { return newEvent(levelWriterAdapter{ioutil.Discard}, 0, true) } -// Object marshals an object that implement the LogObjectMarshaler interface. -func (e *Event) Object(key string, obj LogObjectMarshaler) *Event { - e.buf = appendKey(e.buf, key) +func (e *Event) appendObject(obj LogObjectMarshaler) { pos := len(e.buf) obj.MarshalZerologObject(e) if pos < len(e.buf) { @@ -139,6 +139,12 @@ func (e *Event) Object(key string, obj LogObjectMarshaler) *Event { e.buf = append(e.buf, '{') } e.buf = append(e.buf, '}') +} + +// Object marshals an object that implement the LogObjectMarshaler interface. +func (e *Event) Object(key string, obj LogObjectMarshaler) *Event { + e.buf = json.AppendKey(e.buf, key) + e.appendObject(obj) return e } @@ -147,7 +153,7 @@ func (e *Event) Str(key, val string) *Event { if !e.enabled { return e } - e.buf = appendString(e.buf, key, val) + e.buf = json.AppendString(json.AppendKey(e.buf, key), val) return e } @@ -156,7 +162,7 @@ func (e *Event) Strs(key string, vals []string) *Event { if !e.enabled { return e } - e.buf = appendStrings(e.buf, key, vals) + e.buf = json.AppendStrings(json.AppendKey(e.buf, key), vals) return e } @@ -165,7 +171,7 @@ func (e *Event) Bytes(key string, val []byte) *Event { if !e.enabled { return e } - e.buf = appendBytes(e.buf, key, val) + e.buf = json.AppendBytes(json.AppendKey(e.buf, key), val) return e } @@ -175,7 +181,9 @@ func (e *Event) AnErr(key string, err error) *Event { if !e.enabled { return e } - e.buf = appendErrorKey(e.buf, key, err) + if err != nil { + e.buf = json.AppendError(json.AppendKey(e.buf, key), err) + } return e } @@ -185,7 +193,7 @@ func (e *Event) Errs(key string, errs []error) *Event { if !e.enabled { return e } - e.buf = appendErrorsKey(e.buf, key, errs) + e.buf = json.AppendErrors(json.AppendKey(e.buf, key), errs) return e } @@ -196,7 +204,9 @@ func (e *Event) Err(err error) *Event { if !e.enabled { return e } - e.buf = appendError(e.buf, err) + if err != nil { + e.buf = json.AppendError(json.AppendKey(e.buf, ErrorFieldName), err) + } return e } @@ -205,7 +215,7 @@ func (e *Event) Bool(key string, b bool) *Event { if !e.enabled { return e } - e.buf = appendBool(e.buf, key, b) + e.buf = json.AppendBool(json.AppendKey(e.buf, key), b) return e } @@ -214,7 +224,7 @@ func (e *Event) Bools(key string, b []bool) *Event { if !e.enabled { return e } - e.buf = appendBools(e.buf, key, b) + e.buf = json.AppendBools(json.AppendKey(e.buf, key), b) return e } @@ -223,7 +233,7 @@ func (e *Event) Int(key string, i int) *Event { if !e.enabled { return e } - e.buf = appendInt(e.buf, key, i) + e.buf = json.AppendInt(json.AppendKey(e.buf, key), i) return e } @@ -232,7 +242,7 @@ func (e *Event) Ints(key string, i []int) *Event { if !e.enabled { return e } - e.buf = appendInts(e.buf, key, i) + e.buf = json.AppendInts(json.AppendKey(e.buf, key), i) return e } @@ -241,7 +251,7 @@ func (e *Event) Int8(key string, i int8) *Event { if !e.enabled { return e } - e.buf = appendInt8(e.buf, key, i) + e.buf = json.AppendInt8(json.AppendKey(e.buf, key), i) return e } @@ -250,7 +260,7 @@ func (e *Event) Ints8(key string, i []int8) *Event { if !e.enabled { return e } - e.buf = appendInts8(e.buf, key, i) + e.buf = json.AppendInts8(json.AppendKey(e.buf, key), i) return e } @@ -259,7 +269,7 @@ func (e *Event) Int16(key string, i int16) *Event { if !e.enabled { return e } - e.buf = appendInt16(e.buf, key, i) + e.buf = json.AppendInt16(json.AppendKey(e.buf, key), i) return e } @@ -268,7 +278,7 @@ func (e *Event) Ints16(key string, i []int16) *Event { if !e.enabled { return e } - e.buf = appendInts16(e.buf, key, i) + e.buf = json.AppendInts16(json.AppendKey(e.buf, key), i) return e } @@ -277,7 +287,7 @@ func (e *Event) Int32(key string, i int32) *Event { if !e.enabled { return e } - e.buf = appendInt32(e.buf, key, i) + e.buf = json.AppendInt32(json.AppendKey(e.buf, key), i) return e } @@ -286,7 +296,7 @@ func (e *Event) Ints32(key string, i []int32) *Event { if !e.enabled { return e } - e.buf = appendInts32(e.buf, key, i) + e.buf = json.AppendInts32(json.AppendKey(e.buf, key), i) return e } @@ -295,7 +305,7 @@ func (e *Event) Int64(key string, i int64) *Event { if !e.enabled { return e } - e.buf = appendInt64(e.buf, key, i) + e.buf = json.AppendInt64(json.AppendKey(e.buf, key), i) return e } @@ -304,7 +314,7 @@ func (e *Event) Ints64(key string, i []int64) *Event { if !e.enabled { return e } - e.buf = appendInts64(e.buf, key, i) + e.buf = json.AppendInts64(json.AppendKey(e.buf, key), i) return e } @@ -313,7 +323,7 @@ func (e *Event) Uint(key string, i uint) *Event { if !e.enabled { return e } - e.buf = appendUint(e.buf, key, i) + e.buf = json.AppendUint(json.AppendKey(e.buf, key), i) return e } @@ -322,7 +332,7 @@ func (e *Event) Uints(key string, i []uint) *Event { if !e.enabled { return e } - e.buf = appendUints(e.buf, key, i) + e.buf = json.AppendUints(json.AppendKey(e.buf, key), i) return e } @@ -331,7 +341,7 @@ func (e *Event) Uint8(key string, i uint8) *Event { if !e.enabled { return e } - e.buf = appendUint8(e.buf, key, i) + e.buf = json.AppendUint8(json.AppendKey(e.buf, key), i) return e } @@ -340,7 +350,7 @@ func (e *Event) Uints8(key string, i []uint8) *Event { if !e.enabled { return e } - e.buf = appendUints8(e.buf, key, i) + e.buf = json.AppendUints8(json.AppendKey(e.buf, key), i) return e } @@ -349,7 +359,7 @@ func (e *Event) Uint16(key string, i uint16) *Event { if !e.enabled { return e } - e.buf = appendUint16(e.buf, key, i) + e.buf = json.AppendUint16(json.AppendKey(e.buf, key), i) return e } @@ -358,7 +368,7 @@ func (e *Event) Uints16(key string, i []uint16) *Event { if !e.enabled { return e } - e.buf = appendUints16(e.buf, key, i) + e.buf = json.AppendUints16(json.AppendKey(e.buf, key), i) return e } @@ -367,7 +377,7 @@ func (e *Event) Uint32(key string, i uint32) *Event { if !e.enabled { return e } - e.buf = appendUint32(e.buf, key, i) + e.buf = json.AppendUint32(json.AppendKey(e.buf, key), i) return e } @@ -376,7 +386,7 @@ func (e *Event) Uints32(key string, i []uint32) *Event { if !e.enabled { return e } - e.buf = appendUints32(e.buf, key, i) + e.buf = json.AppendUints32(json.AppendKey(e.buf, key), i) return e } @@ -385,7 +395,7 @@ func (e *Event) Uint64(key string, i uint64) *Event { if !e.enabled { return e } - e.buf = appendUint64(e.buf, key, i) + e.buf = json.AppendUint64(json.AppendKey(e.buf, key), i) return e } @@ -394,7 +404,7 @@ func (e *Event) Uints64(key string, i []uint64) *Event { if !e.enabled { return e } - e.buf = appendUints64(e.buf, key, i) + e.buf = json.AppendUints64(json.AppendKey(e.buf, key), i) return e } @@ -403,7 +413,7 @@ func (e *Event) Float32(key string, f float32) *Event { if !e.enabled { return e } - e.buf = appendFloat32(e.buf, key, f) + e.buf = json.AppendFloat32(json.AppendKey(e.buf, key), f) return e } @@ -412,7 +422,7 @@ func (e *Event) Floats32(key string, f []float32) *Event { if !e.enabled { return e } - e.buf = appendFloats32(e.buf, key, f) + e.buf = json.AppendFloats32(json.AppendKey(e.buf, key), f) return e } @@ -421,7 +431,7 @@ func (e *Event) Float64(key string, f float64) *Event { if !e.enabled { return e } - e.buf = appendFloat64(e.buf, key, f) + e.buf = json.AppendFloat64(json.AppendKey(e.buf, key), f) return e } @@ -430,7 +440,7 @@ func (e *Event) Floats64(key string, f []float64) *Event { if !e.enabled { return e } - e.buf = appendFloats64(e.buf, key, f) + e.buf = json.AppendFloats64(json.AppendKey(e.buf, key), f) return e } @@ -440,7 +450,7 @@ func (e *Event) Timestamp() *Event { if !e.enabled { return e } - e.buf = appendTimestamp(e.buf) + e.buf = json.AppendTime(json.AppendKey(e.buf, TimestampFieldName), TimestampFunc(), TimeFieldFormat) return e } @@ -449,7 +459,7 @@ func (e *Event) Time(key string, t time.Time) *Event { if !e.enabled { return e } - e.buf = appendTime(e.buf, key, t) + e.buf = json.AppendTime(json.AppendKey(e.buf, key), t, TimeFieldFormat) return e } @@ -458,7 +468,7 @@ func (e *Event) Times(key string, t []time.Time) *Event { if !e.enabled { return e } - e.buf = appendTimes(e.buf, key, t) + e.buf = json.AppendTimes(json.AppendKey(e.buf, key), t, TimeFieldFormat) return e } @@ -469,7 +479,7 @@ func (e *Event) Dur(key string, d time.Duration) *Event { if !e.enabled { return e } - e.buf = appendDuration(e.buf, key, d) + e.buf = json.AppendDuration(json.AppendKey(e.buf, key), d, DurationFieldUnit, DurationFieldInteger) return e } @@ -480,7 +490,7 @@ func (e *Event) Durs(key string, d []time.Duration) *Event { if !e.enabled { return e } - e.buf = appendDurations(e.buf, key, d) + e.buf = json.AppendDurations(json.AppendKey(e.buf, key), d, DurationFieldUnit, DurationFieldInteger) return e } @@ -495,7 +505,7 @@ func (e *Event) TimeDiff(key string, t time.Time, start time.Time) *Event { if t.After(start) { d = t.Sub(start) } - e.buf = appendDuration(e.buf, key, d) + e.buf = json.AppendDuration(json.AppendKey(e.buf, key), d, DurationFieldUnit, DurationFieldInteger) return e } @@ -504,6 +514,9 @@ func (e *Event) Interface(key string, i interface{}) *Event { if !e.enabled { return e } - e.buf = appendInterface(e.buf, key, i) + if obj, ok := i.(LogObjectMarshaler); ok { + return e.Object(key, obj) + } + e.buf = json.AppendInterface(json.AppendKey(e.buf, key), i) return e } diff --git a/field.go b/field.go deleted file mode 100644 index 4aa6790..0000000 --- a/field.go +++ /dev/null @@ -1,519 +0,0 @@ -package zerolog - -import ( - "encoding/json" - "fmt" - "math" - "sort" - "strconv" - "time" -) - -func appendFields(dst []byte, fields map[string]interface{}) []byte { - keys := make([]string, 0, len(fields)) - for key, _ := range fields { - keys = append(keys, key) - } - sort.Strings(keys) - for _, key := range keys { - switch val := fields[key].(type) { - case string: - dst = appendString(dst, key, val) - case []byte: - dst = appendBytes(dst, key, val) - case error: - dst = appendErrorKey(dst, key, val) - case []error: - dst = appendErrorsKey(dst, key, val) - case bool: - dst = appendBool(dst, key, val) - case int: - dst = appendInt(dst, key, val) - case int8: - dst = appendInt8(dst, key, val) - case int16: - dst = appendInt16(dst, key, val) - case int32: - dst = appendInt32(dst, key, val) - case int64: - dst = appendInt64(dst, key, val) - case uint: - dst = appendUint(dst, key, val) - case uint8: - dst = appendUint8(dst, key, val) - case uint16: - dst = appendUint16(dst, key, val) - case uint32: - dst = appendUint32(dst, key, val) - case uint64: - dst = appendUint64(dst, key, val) - case float32: - dst = appendFloat32(dst, key, val) - case float64: - dst = appendFloat64(dst, key, val) - case time.Time: - dst = appendTime(dst, key, val) - case time.Duration: - dst = appendDuration(dst, key, val) - case []string: - dst = appendStrings(dst, key, val) - case []bool: - dst = appendBools(dst, key, val) - case []int: - dst = appendInts(dst, key, val) - case []int8: - dst = appendInts8(dst, key, val) - case []int16: - dst = appendInts16(dst, key, val) - case []int32: - dst = appendInts32(dst, key, val) - case []int64: - dst = appendInts64(dst, key, val) - case []uint: - dst = appendUints(dst, key, val) - // case []uint8: - // dst = appendUints8(dst, key, val) - case []uint16: - dst = appendUints16(dst, key, val) - case []uint32: - dst = appendUints32(dst, key, val) - case []uint64: - dst = appendUints64(dst, key, val) - case []float32: - dst = appendFloats32(dst, key, val) - case []float64: - dst = appendFloats64(dst, key, val) - case []time.Time: - dst = appendTimes(dst, key, val) - case []time.Duration: - dst = appendDurations(dst, key, val) - case nil: - dst = append(appendKey(dst, key), "null"...) - default: - dst = appendInterface(dst, key, val) - } - } - return dst -} - -func appendKey(dst []byte, key string) []byte { - if len(dst) > 1 { - dst = append(dst, ',') - } - dst = appendJSONString(dst, key) - return append(dst, ':') -} - -func appendString(dst []byte, key, val string) []byte { - return appendJSONString(appendKey(dst, key), val) -} - -func appendStrings(dst []byte, key string, vals []string) []byte { - if len(vals) == 0 { - return append(appendKey(dst, key), '[', ']') - } - dst = append(appendKey(dst, key), '[') - dst = appendJSONString(dst, vals[0]) - if len(vals) > 1 { - for _, val := range vals[1:] { - dst = appendJSONString(append(dst, ','), val) - } - } - dst = append(dst, ']') - return dst -} - -func appendBytes(dst []byte, key string, val []byte) []byte { - return appendJSONBytes(appendKey(dst, key), val) -} - -func appendErrorKey(dst []byte, key string, err error) []byte { - if err == nil { - return dst - } - return appendJSONString(appendKey(dst, key), err.Error()) -} - -func appendErrorsKey(dst []byte, key string, errs []error) []byte { - if len(errs) == 0 { - return append(appendKey(dst, key), '[', ']') - } - dst = append(appendKey(dst, key), '[') - if errs[0] != nil { - dst = appendJSONString(dst, errs[0].Error()) - } else { - dst = append(dst, "null"...) - } - if len(errs) > 1 { - for _, err := range errs[1:] { - if err == nil { - dst = append(dst, ",null"...) - continue - } - dst = appendJSONString(append(dst, ','), err.Error()) - } - } - dst = append(dst, ']') - return dst -} - -func appendError(dst []byte, err error) []byte { - if err == nil { - return dst - } - return appendErrorKey(dst, ErrorFieldName, err) -} - -func appendBool(dst []byte, key string, val bool) []byte { - return strconv.AppendBool(appendKey(dst, key), val) -} - -func appendBools(dst []byte, key string, vals []bool) []byte { - if len(vals) == 0 { - return append(appendKey(dst, key), '[', ']') - } - dst = append(appendKey(dst, key), '[') - dst = strconv.AppendBool(dst, vals[0]) - if len(vals) > 1 { - for _, val := range vals[1:] { - dst = strconv.AppendBool(append(dst, ','), val) - } - } - dst = append(dst, ']') - return dst -} - -func appendInt(dst []byte, key string, val int) []byte { - return strconv.AppendInt(appendKey(dst, key), int64(val), 10) -} - -func appendInts(dst []byte, key string, vals []int) []byte { - if len(vals) == 0 { - return append(appendKey(dst, key), '[', ']') - } - dst = append(appendKey(dst, key), '[') - dst = strconv.AppendInt(dst, int64(vals[0]), 10) - if len(vals) > 1 { - for _, val := range vals[1:] { - dst = strconv.AppendInt(append(dst, ','), int64(val), 10) - } - } - dst = append(dst, ']') - return dst -} - -func appendInt8(dst []byte, key string, val int8) []byte { - return strconv.AppendInt(appendKey(dst, key), int64(val), 10) -} - -func appendInts8(dst []byte, key string, vals []int8) []byte { - if len(vals) == 0 { - return append(appendKey(dst, key), '[', ']') - } - dst = append(appendKey(dst, key), '[') - dst = strconv.AppendInt(dst, int64(vals[0]), 10) - if len(vals) > 1 { - for _, val := range vals[1:] { - dst = strconv.AppendInt(append(dst, ','), int64(val), 10) - } - } - dst = append(dst, ']') - return dst -} - -func appendInt16(dst []byte, key string, val int16) []byte { - return strconv.AppendInt(appendKey(dst, key), int64(val), 10) -} - -func appendInts16(dst []byte, key string, vals []int16) []byte { - if len(vals) == 0 { - return append(appendKey(dst, key), '[', ']') - } - dst = append(appendKey(dst, key), '[') - dst = strconv.AppendInt(dst, int64(vals[0]), 10) - if len(vals) > 1 { - for _, val := range vals[1:] { - dst = strconv.AppendInt(append(dst, ','), int64(val), 10) - } - } - dst = append(dst, ']') - return dst -} - -func appendInt32(dst []byte, key string, val int32) []byte { - return strconv.AppendInt(appendKey(dst, key), int64(val), 10) -} - -func appendInts32(dst []byte, key string, vals []int32) []byte { - if len(vals) == 0 { - return append(appendKey(dst, key), '[', ']') - } - dst = append(appendKey(dst, key), '[') - dst = strconv.AppendInt(dst, int64(vals[0]), 10) - if len(vals) > 1 { - for _, val := range vals[1:] { - dst = strconv.AppendInt(append(dst, ','), int64(val), 10) - } - } - dst = append(dst, ']') - return dst -} - -func appendInt64(dst []byte, key string, val int64) []byte { - return strconv.AppendInt(appendKey(dst, key), val, 10) -} - -func appendInts64(dst []byte, key string, vals []int64) []byte { - if len(vals) == 0 { - return append(appendKey(dst, key), '[', ']') - } - dst = append(appendKey(dst, key), '[') - dst = strconv.AppendInt(dst, vals[0], 10) - if len(vals) > 1 { - for _, val := range vals[1:] { - dst = strconv.AppendInt(append(dst, ','), val, 10) - } - } - dst = append(dst, ']') - return dst -} - -func appendUint(dst []byte, key string, val uint) []byte { - return strconv.AppendUint(appendKey(dst, key), uint64(val), 10) -} - -func appendUints(dst []byte, key string, vals []uint) []byte { - if len(vals) == 0 { - return append(appendKey(dst, key), '[', ']') - } - dst = append(appendKey(dst, key), '[') - dst = strconv.AppendUint(dst, uint64(vals[0]), 10) - if len(vals) > 1 { - for _, val := range vals[1:] { - dst = strconv.AppendUint(append(dst, ','), uint64(val), 10) - } - } - dst = append(dst, ']') - return dst -} - -func appendUint8(dst []byte, key string, val uint8) []byte { - return strconv.AppendUint(appendKey(dst, key), uint64(val), 10) -} - -func appendUints8(dst []byte, key string, vals []uint8) []byte { - if len(vals) == 0 { - return append(appendKey(dst, key), '[', ']') - } - dst = append(appendKey(dst, key), '[') - dst = strconv.AppendUint(dst, uint64(vals[0]), 10) - if len(vals) > 1 { - for _, val := range vals[1:] { - dst = strconv.AppendUint(append(dst, ','), uint64(val), 10) - } - } - dst = append(dst, ']') - return dst -} - -func appendUint16(dst []byte, key string, val uint16) []byte { - return strconv.AppendUint(appendKey(dst, key), uint64(val), 10) -} - -func appendUints16(dst []byte, key string, vals []uint16) []byte { - if len(vals) == 0 { - return append(appendKey(dst, key), '[', ']') - } - dst = append(appendKey(dst, key), '[') - dst = strconv.AppendUint(dst, uint64(vals[0]), 10) - if len(vals) > 1 { - for _, val := range vals[1:] { - dst = strconv.AppendUint(append(dst, ','), uint64(val), 10) - } - } - dst = append(dst, ']') - return dst -} - -func appendUint32(dst []byte, key string, val uint32) []byte { - return strconv.AppendUint(appendKey(dst, key), uint64(val), 10) -} - -func appendUints32(dst []byte, key string, vals []uint32) []byte { - if len(vals) == 0 { - return append(appendKey(dst, key), '[', ']') - } - dst = append(appendKey(dst, key), '[') - dst = strconv.AppendUint(dst, uint64(vals[0]), 10) - if len(vals) > 1 { - for _, val := range vals[1:] { - dst = strconv.AppendUint(append(dst, ','), uint64(val), 10) - } - } - dst = append(dst, ']') - return dst -} - -func appendUint64(dst []byte, key string, val uint64) []byte { - return strconv.AppendUint(appendKey(dst, key), uint64(val), 10) -} - -func appendUints64(dst []byte, key string, vals []uint64) []byte { - if len(vals) == 0 { - return append(appendKey(dst, key), '[', ']') - } - dst = append(appendKey(dst, key), '[') - dst = strconv.AppendUint(dst, vals[0], 10) - if len(vals) > 1 { - for _, val := range vals[1:] { - dst = strconv.AppendUint(append(dst, ','), val, 10) - } - } - dst = append(dst, ']') - return dst -} - -func appendFloat(dst []byte, val float64, bitSize int) []byte { - // JSON does not permit NaN or Infinity. A typical JSON encoder would fail - // with an error, but a logging library wants the data to get thru so we - // make a tradeoff and store those types as string. - switch { - case math.IsNaN(val): - return append(dst, `"NaN"`...) - case math.IsInf(val, 1): - return append(dst, `"+Inf"`...) - case math.IsInf(val, -1): - return append(dst, `"-Inf"`...) - } - return strconv.AppendFloat(dst, val, 'f', -1, bitSize) -} - -func appendFloat32(dst []byte, key string, val float32) []byte { - return appendFloat(appendKey(dst, key), float64(val), 32) -} - -func appendFloats32(dst []byte, key string, vals []float32) []byte { - if len(vals) == 0 { - return append(appendKey(dst, key), '[', ']') - } - dst = append(appendKey(dst, key), '[') - dst = appendFloat(dst, float64(vals[0]), 32) - if len(vals) > 1 { - for _, val := range vals[1:] { - dst = appendFloat(append(dst, ','), float64(val), 32) - } - } - dst = append(dst, ']') - return dst -} - -func appendFloat64(dst []byte, key string, val float64) []byte { - return appendFloat(appendKey(dst, key), val, 64) -} - -func appendFloats64(dst []byte, key string, vals []float64) []byte { - if len(vals) == 0 { - return append(appendKey(dst, key), '[', ']') - } - dst = append(appendKey(dst, key), '[') - dst = appendFloat(dst, vals[0], 32) - if len(vals) > 1 { - for _, val := range vals[1:] { - dst = appendFloat(append(dst, ','), val, 64) - } - } - dst = append(dst, ']') - return dst -} - -func appendTime(dst []byte, key string, t time.Time) []byte { - if TimeFieldFormat == "" { - return appendInt64(dst, key, t.Unix()) - } - return append(t.AppendFormat(append(appendKey(dst, key), '"'), TimeFieldFormat), '"') -} - -func appendTimes(dst []byte, key string, vals []time.Time) []byte { - if TimeFieldFormat == "" { - return appendUnixTimes(dst, key, vals) - } - if len(vals) == 0 { - return append(appendKey(dst, key), '[', ']') - } - dst = append(appendKey(dst, key), '[') - dst = append(vals[0].AppendFormat(append(dst, '"'), TimeFieldFormat), '"') - if len(vals) > 1 { - for _, t := range vals[1:] { - dst = append(t.AppendFormat(append(dst, ',', '"'), TimeFieldFormat), '"') - } - } - dst = append(dst, ']') - return dst -} - -func appendUnixTimes(dst []byte, key string, vals []time.Time) []byte { - if len(vals) == 0 { - return append(appendKey(dst, key), '[', ']') - } - dst = append(appendKey(dst, key), '[') - dst = strconv.AppendInt(dst, vals[0].Unix(), 10) - if len(vals) > 1 { - for _, t := range vals[1:] { - dst = strconv.AppendInt(dst, t.Unix(), 10) - } - } - dst = append(dst, ']') - return dst -} - -func appendTimestamp(dst []byte) []byte { - return appendTime(dst, TimestampFieldName, TimestampFunc()) -} - -func appendDuration(dst []byte, key string, d time.Duration) []byte { - if DurationFieldInteger { - return appendInt64(dst, key, int64(d/DurationFieldUnit)) - } - return appendFloat64(dst, key, float64(d)/float64(DurationFieldUnit)) -} - -func appendDurations(dst []byte, key string, vals []time.Duration) []byte { - if DurationFieldInteger { - return appendIntDurations(dst, key, vals) - } - if len(vals) == 0 { - return append(appendKey(dst, key), '[', ']') - } - dst = append(appendKey(dst, key), '[') - dst = strconv.AppendFloat(dst, float64(vals[0])/float64(DurationFieldUnit), 'f', -1, 32) - if len(vals) > 1 { - for _, d := range vals[1:] { - dst = strconv.AppendFloat(append(dst, ','), float64(d)/float64(DurationFieldUnit), 'f', -1, 32) - } - } - dst = append(dst, ']') - return dst -} - -func appendIntDurations(dst []byte, key string, vals []time.Duration) []byte { - if len(vals) == 0 { - return append(appendKey(dst, key), '[', ']') - } - dst = append(appendKey(dst, key), '[') - dst = strconv.AppendInt(dst, int64(vals[0]/DurationFieldUnit), 10) - if len(vals) > 1 { - for _, d := range vals[1:] { - dst = strconv.AppendInt(append(dst, ','), int64(d/DurationFieldUnit), 10) - } - } - dst = append(dst, ']') - return dst -} - -func appendInterface(dst []byte, key string, i interface{}) []byte { - marshaled, err := json.Marshal(i) - if err != nil { - return appendString(dst, key, fmt.Sprintf("marshaling error: %v", err)) - } - return append(appendKey(dst, key), marshaled...) -} diff --git a/field_test.go b/field_test.go deleted file mode 100644 index e1d3f95..0000000 --- a/field_test.go +++ /dev/null @@ -1,33 +0,0 @@ -package zerolog - -import ( - "math" - "reflect" - "testing" -) - -func Test_appendFloat64(t *testing.T) { - tests := []struct { - name string - input float64 - want []byte - }{ - {"-Inf", math.Inf(-1), []byte(`"foo":"-Inf"`)}, - {"+Inf", math.Inf(1), []byte(`"foo":"+Inf"`)}, - {"NaN", math.NaN(), []byte(`"foo":"NaN"`)}, - {"0", 0, []byte(`"foo":0`)}, - {"-1.1", -1.1, []byte(`"foo":-1.1`)}, - {"1e20", 1e20, []byte(`"foo":100000000000000000000`)}, - {"1e21", 1e21, []byte(`"foo":1000000000000000000000`)}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := appendFloat32([]byte{}, "foo", float32(tt.input)); !reflect.DeepEqual(got, tt.want) { - t.Errorf("appendFloat32() = %s, want %s", got, tt.want) - } - if got := appendFloat64([]byte{}, "foo", tt.input); !reflect.DeepEqual(got, tt.want) { - t.Errorf("appendFloat32() = %s, want %s", got, tt.want) - } - }) - } -} diff --git a/fields.go b/fields.go new file mode 100644 index 0000000..6a19392 --- /dev/null +++ b/fields.go @@ -0,0 +1,96 @@ +package zerolog + +import ( + "sort" + "time" + + "github.com/rs/zerolog/internal/json" +) + +func appendFields(dst []byte, fields map[string]interface{}) []byte { + keys := make([]string, 0, len(fields)) + for key := range fields { + keys = append(keys, key) + } + sort.Strings(keys) + for _, key := range keys { + dst = json.AppendKey(dst, key) + switch val := fields[key].(type) { + case string: + dst = json.AppendString(dst, val) + case []byte: + dst = json.AppendBytes(dst, val) + case error: + dst = json.AppendError(dst, val) + case []error: + dst = json.AppendErrors(dst, val) + case bool: + dst = json.AppendBool(dst, val) + case int: + dst = json.AppendInt(dst, val) + case int8: + dst = json.AppendInt8(dst, val) + case int16: + dst = json.AppendInt16(dst, val) + case int32: + dst = json.AppendInt32(dst, val) + case int64: + dst = json.AppendInt64(dst, val) + case uint: + dst = json.AppendUint(dst, val) + case uint8: + dst = json.AppendUint8(dst, val) + case uint16: + dst = json.AppendUint16(dst, val) + case uint32: + dst = json.AppendUint32(dst, val) + case uint64: + dst = json.AppendUint64(dst, val) + case float32: + dst = json.AppendFloat32(dst, val) + case float64: + dst = json.AppendFloat64(dst, val) + case time.Time: + dst = json.AppendTime(dst, val, TimeFieldFormat) + case time.Duration: + dst = json.AppendDuration(dst, val, DurationFieldUnit, DurationFieldInteger) + case []string: + dst = json.AppendStrings(dst, val) + case []bool: + dst = json.AppendBools(dst, val) + case []int: + dst = json.AppendInts(dst, val) + case []int8: + dst = json.AppendInts8(dst, val) + case []int16: + dst = json.AppendInts16(dst, val) + case []int32: + dst = json.AppendInts32(dst, val) + case []int64: + dst = json.AppendInts64(dst, val) + case []uint: + dst = json.AppendUints(dst, val) + // case []uint8: + // dst = appendUints8(dst, val) + case []uint16: + dst = json.AppendUints16(dst, val) + case []uint32: + dst = json.AppendUints32(dst, val) + case []uint64: + dst = json.AppendUints64(dst, val) + case []float32: + dst = json.AppendFloats32(dst, val) + case []float64: + dst = json.AppendFloats64(dst, val) + case []time.Time: + dst = json.AppendTimes(dst, val, TimeFieldFormat) + case []time.Duration: + dst = json.AppendDurations(dst, val, DurationFieldUnit, DurationFieldInteger) + case nil: + dst = append(dst, "null"...) + default: + dst = json.AppendInterface(dst, val) + } + } + return dst +} diff --git a/internal/json/base.go b/internal/json/base.go new file mode 100644 index 0000000..7baeec5 --- /dev/null +++ b/internal/json/base.go @@ -0,0 +1,39 @@ +package json + +func AppendKey(dst []byte, key string) []byte { + if len(dst) > 1 { + dst = append(dst, ',') + } + dst = AppendString(dst, key) + return append(dst, ':') +} + +func AppendError(dst []byte, err error) []byte { + if err == nil { + return append(dst, `null`...) + } + return AppendString(dst, err.Error()) +} + +func AppendErrors(dst []byte, errs []error) []byte { + if len(errs) == 0 { + return append(dst, '[', ']') + } + dst = append(dst, '[') + if errs[0] != nil { + dst = AppendString(dst, errs[0].Error()) + } else { + dst = append(dst, "null"...) + } + if len(errs) > 1 { + for _, err := range errs[1:] { + if err == nil { + dst = append(dst, ",null"...) + continue + } + dst = AppendString(append(dst, ','), err.Error()) + } + } + dst = append(dst, ']') + return dst +} diff --git a/json.go b/internal/json/string.go similarity index 82% rename from json.go rename to internal/json/string.go index d55a0fd..8f8f4df 100644 --- a/json.go +++ b/internal/json/string.go @@ -1,10 +1,25 @@ -package zerolog +package json import "unicode/utf8" const hex = "0123456789abcdef" -// appendJSONString encodes the input string to json and appends +func AppendStrings(dst []byte, vals []string) []byte { + if len(vals) == 0 { + return append(dst, '[', ']') + } + dst = append(dst, '[') + dst = AppendString(dst, vals[0]) + if len(vals) > 1 { + for _, val := range vals[1:] { + dst = AppendString(append(dst, ','), val) + } + } + dst = append(dst, ']') + return dst +} + +// AppendString encodes the input string to json and appends // the encoded string to the input byte slice. // // The operation loops though each byte in the string looking @@ -13,7 +28,7 @@ const hex = "0123456789abcdef" // entirety to the byte slice. // If we encounter a byte that does need encoding, switch up // the operation and perform a byte-by-byte read-encode-append. -func appendJSONString(dst []byte, s string) []byte { +func AppendString(dst []byte, s string) []byte { // Start with a double quote. dst = append(dst, '"') // Loop through each character in the string. @@ -24,7 +39,7 @@ func appendJSONString(dst []byte, s string) []byte { if s[i] < 0x20 || s[i] > 0x7e || s[i] == '\\' || s[i] == '"' { // We encountered a character that needs to be encoded. Switch // to complex version of the algorithm. - dst = appendJSONStringComplex(dst, s, i) + dst = appendStringComplex(dst, s, i) return append(dst, '"') } } @@ -35,10 +50,10 @@ func appendJSONString(dst []byte, s string) []byte { return append(dst, '"') } -// appendJSONStringComplex is used by appendJSONString to take over an in +// appendStringComplex is used by appendString to take over an in // progress JSON string encoding that encountered a character that needs // to be encoded. -func appendJSONStringComplex(dst []byte, s string, i int) []byte { +func appendStringComplex(dst []byte, s string, i int) []byte { start := 0 for i < len(s) { b := s[i] @@ -95,12 +110,12 @@ func appendJSONStringComplex(dst []byte, s string, i int) []byte { return dst } -// appendJSONBytes is a mirror of appendJSONString with []byte arg -func appendJSONBytes(dst, s []byte) []byte { +// AppendBytes is a mirror of appendString with []byte arg +func AppendBytes(dst, s []byte) []byte { dst = append(dst, '"') for i := 0; i < len(s); i++ { if s[i] < 0x20 || s[i] > 0x7e || s[i] == '\\' || s[i] == '"' { - dst = appendJSONBytesComplex(dst, s, i) + dst = appendBytesComplex(dst, s, i) return append(dst, '"') } } @@ -108,9 +123,9 @@ func appendJSONBytes(dst, s []byte) []byte { return append(dst, '"') } -// appendJSONBytesComplex is a mirror of the appendJSONStringComplex +// appendBytesComplex is a mirror of the appendStringComplex // with []byte arg -func appendJSONBytesComplex(dst, s []byte, i int) []byte { +func appendBytesComplex(dst, s []byte, i int) []byte { start := 0 for i < len(s) { b := s[i] diff --git a/json_test.go b/internal/json/string_test.go similarity index 85% rename from json_test.go rename to internal/json/string_test.go index 69a344a..b0c2c32 100644 --- a/json_test.go +++ b/internal/json/string_test.go @@ -1,4 +1,4 @@ -package zerolog +package json import ( "testing" @@ -53,20 +53,20 @@ var encodeStringTests = []struct { {"emoji \u2764\ufe0f!", `"emoji ❤️!"`}, } -func TestAppendJSONString(t *testing.T) { +func TestappendString(t *testing.T) { for _, tt := range encodeStringTests { - b := appendJSONString([]byte{}, tt.in) + b := AppendString([]byte{}, tt.in) if got, want := string(b), tt.out; got != want { - t.Errorf("appendJSONString(%q) = %#q, want %#q", tt.in, got, want) + t.Errorf("appendString(%q) = %#q, want %#q", tt.in, got, want) } } } -func TestAppendJSONBytes(t *testing.T) { +func TestappendBytes(t *testing.T) { for _, tt := range encodeStringTests { - b := appendJSONBytes([]byte{}, []byte(tt.in)) + b := AppendBytes([]byte{}, []byte(tt.in)) if got, want := string(b), tt.out; got != want { - t.Errorf("appendJSONBytes(%q) = %#q, want %#q", tt.in, got, want) + t.Errorf("appendBytes(%q) = %#q, want %#q", tt.in, got, want) } } } @@ -80,8 +80,8 @@ func TestStringBytes(t *testing.T) { } s := string(r) + "\xff\xff\xffhello" // some invalid UTF-8 too - enc := string(appendJSONString([]byte{}, s)) - encBytes := string(appendJSONBytes([]byte{}, []byte(s))) + enc := string(AppendString([]byte{}, s)) + encBytes := string(AppendBytes([]byte{}, []byte(s))) if enc != encBytes { i := 0 @@ -108,7 +108,7 @@ func TestStringBytes(t *testing.T) { } } -func BenchmarkAppendJSONString(b *testing.B) { +func BenchmarkappendString(b *testing.B) { tests := map[string]string{ "NoEncoding": `aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa`, "EncodingFirst": `"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa`, @@ -122,13 +122,13 @@ func BenchmarkAppendJSONString(b *testing.B) { b.Run(name, func(b *testing.B) { buf := make([]byte, 0, 100) for i := 0; i < b.N; i++ { - _ = appendJSONString(buf, str) + _ = AppendString(buf, str) } }) } } -func BenchmarkAppendJSONBytes(b *testing.B) { +func BenchmarkappendBytes(b *testing.B) { tests := map[string]string{ "NoEncoding": `aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa`, "EncodingFirst": `"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa`, @@ -143,7 +143,7 @@ func BenchmarkAppendJSONBytes(b *testing.B) { b.Run(name, func(b *testing.B) { buf := make([]byte, 0, 100) for i := 0; i < b.N; i++ { - _ = appendJSONBytes(buf, byt) + _ = AppendBytes(buf, byt) } }) } diff --git a/internal/json/time.go b/internal/json/time.go new file mode 100644 index 0000000..612438d --- /dev/null +++ b/internal/json/time.go @@ -0,0 +1,68 @@ +package json + +import ( + "strconv" + "time" +) + +func AppendTime(dst []byte, t time.Time, format string) []byte { + if format == "" { + return AppendInt64(dst, t.Unix()) + } + return append(t.AppendFormat(append(dst, '"'), format), '"') +} + +func AppendTimes(dst []byte, vals []time.Time, format string) []byte { + if format == "" { + return appendUnixTimes(dst, vals) + } + if len(vals) == 0 { + return append(dst, '[', ']') + } + dst = append(dst, '[') + dst = append(vals[0].AppendFormat(append(dst, '"'), format), '"') + if len(vals) > 1 { + for _, t := range vals[1:] { + dst = append(t.AppendFormat(append(dst, ',', '"'), format), '"') + } + } + dst = append(dst, ']') + return dst +} + +func appendUnixTimes(dst []byte, vals []time.Time) []byte { + if len(vals) == 0 { + return append(dst, '[', ']') + } + dst = append(dst, '[') + dst = strconv.AppendInt(dst, vals[0].Unix(), 10) + if len(vals) > 1 { + for _, t := range vals[1:] { + dst = strconv.AppendInt(dst, t.Unix(), 10) + } + } + dst = append(dst, ']') + return dst +} + +func AppendDuration(dst []byte, d time.Duration, unit time.Duration, useInt bool) []byte { + if useInt { + return strconv.AppendInt(dst, int64(d/unit), 10) + } + return AppendFloat64(dst, float64(d)/float64(unit)) +} + +func AppendDurations(dst []byte, vals []time.Duration, unit time.Duration, useInt bool) []byte { + if len(vals) == 0 { + return append(dst, '[', ']') + } + dst = append(dst, '[') + dst = AppendDuration(dst, vals[0], unit, useInt) + if len(vals) > 1 { + for _, d := range vals[1:] { + dst = AppendDuration(append(dst, ','), d, unit, useInt) + } + } + dst = append(dst, ']') + return dst +} diff --git a/internal/json/types.go b/internal/json/types.go new file mode 100644 index 0000000..bbc8e42 --- /dev/null +++ b/internal/json/types.go @@ -0,0 +1,278 @@ +package json + +import ( + "encoding/json" + "fmt" + "math" + "strconv" +) + +func AppendBool(dst []byte, val bool) []byte { + return strconv.AppendBool(dst, val) +} + +func AppendBools(dst []byte, vals []bool) []byte { + if len(vals) == 0 { + return append(dst, '[', ']') + } + dst = append(dst, '[') + dst = strconv.AppendBool(dst, vals[0]) + if len(vals) > 1 { + for _, val := range vals[1:] { + dst = strconv.AppendBool(append(dst, ','), val) + } + } + dst = append(dst, ']') + return dst +} + +func AppendInt(dst []byte, val int) []byte { + return strconv.AppendInt(dst, int64(val), 10) +} + +func AppendInts(dst []byte, vals []int) []byte { + if len(vals) == 0 { + return append(dst, '[', ']') + } + dst = append(dst, '[') + dst = strconv.AppendInt(dst, int64(vals[0]), 10) + if len(vals) > 1 { + for _, val := range vals[1:] { + dst = strconv.AppendInt(append(dst, ','), int64(val), 10) + } + } + dst = append(dst, ']') + return dst +} + +func AppendInt8(dst []byte, val int8) []byte { + return strconv.AppendInt(dst, int64(val), 10) +} + +func AppendInts8(dst []byte, vals []int8) []byte { + if len(vals) == 0 { + return append(dst, '[', ']') + } + dst = append(dst, '[') + dst = strconv.AppendInt(dst, int64(vals[0]), 10) + if len(vals) > 1 { + for _, val := range vals[1:] { + dst = strconv.AppendInt(append(dst, ','), int64(val), 10) + } + } + dst = append(dst, ']') + return dst +} + +func AppendInt16(dst []byte, val int16) []byte { + return strconv.AppendInt(dst, int64(val), 10) +} + +func AppendInts16(dst []byte, vals []int16) []byte { + if len(vals) == 0 { + return append(dst, '[', ']') + } + dst = append(dst, '[') + dst = strconv.AppendInt(dst, int64(vals[0]), 10) + if len(vals) > 1 { + for _, val := range vals[1:] { + dst = strconv.AppendInt(append(dst, ','), int64(val), 10) + } + } + dst = append(dst, ']') + return dst +} + +func AppendInt32(dst []byte, val int32) []byte { + return strconv.AppendInt(dst, int64(val), 10) +} + +func AppendInts32(dst []byte, vals []int32) []byte { + if len(vals) == 0 { + return append(dst, '[', ']') + } + dst = append(dst, '[') + dst = strconv.AppendInt(dst, int64(vals[0]), 10) + if len(vals) > 1 { + for _, val := range vals[1:] { + dst = strconv.AppendInt(append(dst, ','), int64(val), 10) + } + } + dst = append(dst, ']') + return dst +} + +func AppendInt64(dst []byte, val int64) []byte { + return strconv.AppendInt(dst, val, 10) +} + +func AppendInts64(dst []byte, vals []int64) []byte { + if len(vals) == 0 { + return append(dst, '[', ']') + } + dst = append(dst, '[') + dst = strconv.AppendInt(dst, vals[0], 10) + if len(vals) > 1 { + for _, val := range vals[1:] { + dst = strconv.AppendInt(append(dst, ','), val, 10) + } + } + dst = append(dst, ']') + return dst +} + +func AppendUint(dst []byte, val uint) []byte { + return strconv.AppendUint(dst, uint64(val), 10) +} + +func AppendUints(dst []byte, vals []uint) []byte { + if len(vals) == 0 { + return append(dst, '[', ']') + } + dst = append(dst, '[') + dst = strconv.AppendUint(dst, uint64(vals[0]), 10) + if len(vals) > 1 { + for _, val := range vals[1:] { + dst = strconv.AppendUint(append(dst, ','), uint64(val), 10) + } + } + dst = append(dst, ']') + return dst +} + +func AppendUint8(dst []byte, val uint8) []byte { + return strconv.AppendUint(dst, uint64(val), 10) +} + +func AppendUints8(dst []byte, vals []uint8) []byte { + if len(vals) == 0 { + return append(dst, '[', ']') + } + dst = append(dst, '[') + dst = strconv.AppendUint(dst, uint64(vals[0]), 10) + if len(vals) > 1 { + for _, val := range vals[1:] { + dst = strconv.AppendUint(append(dst, ','), uint64(val), 10) + } + } + dst = append(dst, ']') + return dst +} + +func AppendUint16(dst []byte, val uint16) []byte { + return strconv.AppendUint(dst, uint64(val), 10) +} + +func AppendUints16(dst []byte, vals []uint16) []byte { + if len(vals) == 0 { + return append(dst, '[', ']') + } + dst = append(dst, '[') + dst = strconv.AppendUint(dst, uint64(vals[0]), 10) + if len(vals) > 1 { + for _, val := range vals[1:] { + dst = strconv.AppendUint(append(dst, ','), uint64(val), 10) + } + } + dst = append(dst, ']') + return dst +} + +func AppendUint32(dst []byte, val uint32) []byte { + return strconv.AppendUint(dst, uint64(val), 10) +} + +func AppendUints32(dst []byte, vals []uint32) []byte { + if len(vals) == 0 { + return append(dst, '[', ']') + } + dst = append(dst, '[') + dst = strconv.AppendUint(dst, uint64(vals[0]), 10) + if len(vals) > 1 { + for _, val := range vals[1:] { + dst = strconv.AppendUint(append(dst, ','), uint64(val), 10) + } + } + dst = append(dst, ']') + return dst +} + +func AppendUint64(dst []byte, val uint64) []byte { + return strconv.AppendUint(dst, uint64(val), 10) +} + +func AppendUints64(dst []byte, vals []uint64) []byte { + if len(vals) == 0 { + return append(dst, '[', ']') + } + dst = append(dst, '[') + dst = strconv.AppendUint(dst, vals[0], 10) + if len(vals) > 1 { + for _, val := range vals[1:] { + dst = strconv.AppendUint(append(dst, ','), val, 10) + } + } + dst = append(dst, ']') + return dst +} + +func AppendFloat(dst []byte, val float64, bitSize int) []byte { + // JSON does not permit NaN or Infinity. A typical JSON encoder would fail + // with an error, but a logging library wants the data to get thru so we + // make a tradeoff and store those types as string. + switch { + case math.IsNaN(val): + return append(dst, `"NaN"`...) + case math.IsInf(val, 1): + return append(dst, `"+Inf"`...) + case math.IsInf(val, -1): + return append(dst, `"-Inf"`...) + } + return strconv.AppendFloat(dst, val, 'f', -1, bitSize) +} + +func AppendFloat32(dst []byte, val float32) []byte { + return AppendFloat(dst, float64(val), 32) +} + +func AppendFloats32(dst []byte, vals []float32) []byte { + if len(vals) == 0 { + return append(dst, '[', ']') + } + dst = append(dst, '[') + dst = AppendFloat(dst, float64(vals[0]), 32) + if len(vals) > 1 { + for _, val := range vals[1:] { + dst = AppendFloat(append(dst, ','), float64(val), 32) + } + } + dst = append(dst, ']') + return dst +} + +func AppendFloat64(dst []byte, val float64) []byte { + return AppendFloat(dst, val, 64) +} + +func AppendFloats64(dst []byte, vals []float64) []byte { + if len(vals) == 0 { + return append(dst, '[', ']') + } + dst = append(dst, '[') + dst = AppendFloat(dst, vals[0], 32) + if len(vals) > 1 { + for _, val := range vals[1:] { + dst = AppendFloat(append(dst, ','), val, 64) + } + } + dst = append(dst, ']') + return dst +} + +func AppendInterface(dst []byte, i interface{}) []byte { + marshaled, err := json.Marshal(i) + if err != nil { + return AppendString(dst, fmt.Sprintf("marshaling error: %v", err)) + } + return append(dst, marshaled...) +} diff --git a/internal/json/types_test.go b/internal/json/types_test.go new file mode 100644 index 0000000..6a297a8 --- /dev/null +++ b/internal/json/types_test.go @@ -0,0 +1,33 @@ +package json + +import ( + "math" + "reflect" + "testing" +) + +func Test_appendFloat64(t *testing.T) { + tests := []struct { + name string + input float64 + want []byte + }{ + {"-Inf", math.Inf(-1), []byte(`"-Inf"`)}, + {"+Inf", math.Inf(1), []byte(`"+Inf"`)}, + {"NaN", math.NaN(), []byte(`"NaN"`)}, + {"0", 0, []byte(`0`)}, + {"-1.1", -1.1, []byte(`-1.1`)}, + {"1e20", 1e20, []byte(`100000000000000000000`)}, + {"1e21", 1e21, []byte(`1000000000000000000000`)}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := AppendFloat32([]byte{}, float32(tt.input)); !reflect.DeepEqual(got, tt.want) { + t.Errorf("appendFloat32() = %s, want %s", got, tt.want) + } + if got := AppendFloat64([]byte{}, tt.input); !reflect.DeepEqual(got, tt.want) { + t.Errorf("appendFloat32() = %s, want %s", got, tt.want) + } + }) + } +} diff --git a/log.go b/log.go index 3247e9d..71db0b5 100644 --- a/log.go +++ b/log.go @@ -73,6 +73,8 @@ import ( "os" "strconv" "sync/atomic" + + "github.com/rs/zerolog/internal/json" ) // Level defines log levels. @@ -306,7 +308,7 @@ func (l Logger) newEvent(level Level, addLevelField bool, done func(string)) *Ev e.done = done if l.context != nil && len(l.context) > 0 && l.context[0] > 0 { // first byte of context is ts flag - e.buf = appendTimestamp(e.buf) + e.buf = json.AppendTime(json.AppendKey(e.buf, TimestampFieldName), TimestampFunc(), TimeFieldFormat) } if addLevelField { e.Str(LevelFieldName, level.String())