Binary format support (#37)

Adds support for binary logging (with cbor encoding) in addition to JSON. Use the binary_log compile tag to enable the feature.
This commit is contained in:
Ravi Raju 2018-03-28 11:49:41 -07:00 committed by Olivier Poitrey
parent 3ab376bc30
commit ddfae1b613
37 changed files with 3224 additions and 262 deletions

View File

@ -9,4 +9,5 @@ matrix:
allow_failures:
- go: "master"
script:
go test -v -race -cpu=1,2,4 -bench . -benchmem ./...
- go test -v -race -cpu=1,2,4 -bench . -benchmem ./...
- go test -v -tags binary_log -race -cpu=1,2,4 -bench . -benchmem ./...

View File

@ -3,8 +3,6 @@ package zerolog
import (
"sync"
"time"
"github.com/rs/zerolog/internal/json"
)
var arrayPool = &sync.Pool{
@ -15,6 +13,8 @@ var arrayPool = &sync.Pool{
},
}
// Array is used to prepopulate an array of items
// which can be re-used to add to log messages.
type Array struct {
buf []byte
}
@ -26,16 +26,17 @@ func Arr() *Array {
return a
}
// MarshalZerologArray method here is no-op - since data is
// already in the needed format.
func (*Array) MarshalZerologArray(*Array) {
}
func (a *Array) write(dst []byte) []byte {
if len(a.buf) == 0 {
dst = append(dst, `[]`...)
} else {
a.buf[0] = '['
dst = append(append(dst, a.buf...), ']')
dst = appendArrayStart(dst)
if len(a.buf) > 0 {
dst = append(append(dst, a.buf...))
}
dst = appendArrayEnd(dst)
arrayPool.Put(a)
return dst
}
@ -43,126 +44,125 @@ func (a *Array) write(dst []byte) []byte {
// Object marshals an object that implement the LogObjectMarshaler
// interface and append it to the array.
func (a *Array) Object(obj LogObjectMarshaler) *Array {
a.buf = append(a.buf, ',')
e := Dict()
obj.MarshalZerologObject(e)
e.buf = append(e.buf, '}')
a.buf = append(a.buf, e.buf...)
e.buf = appendEndMarker(e.buf)
a.buf = append(appendArrayDelim(a.buf), e.buf...)
eventPool.Put(e)
return a
}
// Str append the val as a string to the array.
func (a *Array) Str(val string) *Array {
a.buf = json.AppendString(append(a.buf, ','), val)
a.buf = appendString(appendArrayDelim(a.buf), val)
return a
}
// Bytes append the val as a string to the array.
func (a *Array) Bytes(val []byte) *Array {
a.buf = json.AppendBytes(append(a.buf, ','), val)
a.buf = appendBytes(appendArrayDelim(a.buf), val)
return a
}
// Hex append the val as a hex string to the array.
func (a *Array) Hex(val []byte) *Array {
a.buf = json.AppendHex(append(a.buf, ','), val)
a.buf = appendHex(appendArrayDelim(a.buf), val)
return a
}
// Err append the err as a string to the array.
func (a *Array) Err(err error) *Array {
a.buf = json.AppendError(append(a.buf, ','), err)
a.buf = appendError(appendArrayDelim(a.buf), err)
return a
}
// Bool append the val as a bool to the array.
func (a *Array) Bool(b bool) *Array {
a.buf = json.AppendBool(append(a.buf, ','), b)
a.buf = appendBool(appendArrayDelim(a.buf), b)
return a
}
// Int append i as a int to the array.
func (a *Array) Int(i int) *Array {
a.buf = json.AppendInt(append(a.buf, ','), i)
a.buf = appendInt(appendArrayDelim(a.buf), i)
return a
}
// Int8 append i as a int8 to the array.
func (a *Array) Int8(i int8) *Array {
a.buf = json.AppendInt8(append(a.buf, ','), i)
a.buf = appendInt8(appendArrayDelim(a.buf), i)
return a
}
// Int16 append i as a int16 to the array.
func (a *Array) Int16(i int16) *Array {
a.buf = json.AppendInt16(append(a.buf, ','), i)
a.buf = appendInt16(appendArrayDelim(a.buf), i)
return a
}
// Int32 append i as a int32 to the array.
func (a *Array) Int32(i int32) *Array {
a.buf = json.AppendInt32(append(a.buf, ','), i)
a.buf = appendInt32(appendArrayDelim(a.buf), i)
return a
}
// Int64 append i as a int64 to the array.
func (a *Array) Int64(i int64) *Array {
a.buf = json.AppendInt64(append(a.buf, ','), i)
a.buf = appendInt64(appendArrayDelim(a.buf), i)
return a
}
// Uint append i as a uint to the array.
func (a *Array) Uint(i uint) *Array {
a.buf = json.AppendUint(append(a.buf, ','), i)
a.buf = appendUint(appendArrayDelim(a.buf), i)
return a
}
// Uint8 append i as a uint8 to the array.
func (a *Array) Uint8(i uint8) *Array {
a.buf = json.AppendUint8(append(a.buf, ','), i)
a.buf = appendUint8(appendArrayDelim(a.buf), i)
return a
}
// Uint16 append i as a uint16 to the array.
func (a *Array) Uint16(i uint16) *Array {
a.buf = json.AppendUint16(append(a.buf, ','), i)
a.buf = appendUint16(appendArrayDelim(a.buf), i)
return a
}
// Uint32 append i as a uint32 to the array.
func (a *Array) Uint32(i uint32) *Array {
a.buf = json.AppendUint32(append(a.buf, ','), i)
a.buf = appendUint32(appendArrayDelim(a.buf), i)
return a
}
// Uint64 append i as a uint64 to the array.
func (a *Array) Uint64(i uint64) *Array {
a.buf = json.AppendUint64(append(a.buf, ','), i)
a.buf = appendUint64(appendArrayDelim(a.buf), i)
return a
}
// Float32 append f as a float32 to the array.
func (a *Array) Float32(f float32) *Array {
a.buf = json.AppendFloat32(append(a.buf, ','), f)
a.buf = appendFloat32(appendArrayDelim(a.buf), f)
return a
}
// Float64 append f as a float64 to the array.
func (a *Array) Float64(f float64) *Array {
a.buf = json.AppendFloat64(append(a.buf, ','), f)
a.buf = appendFloat64(appendArrayDelim(a.buf), f)
return a
}
// Time append t formated as string using zerolog.TimeFieldFormat.
func (a *Array) Time(t time.Time) *Array {
a.buf = json.AppendTime(append(a.buf, ','), t, TimeFieldFormat)
a.buf = appendTime(appendArrayDelim(a.buf), t, TimeFieldFormat)
return a
}
// Dur append d to the array.
func (a *Array) Dur(d time.Duration) *Array {
a.buf = json.AppendDuration(append(a.buf, ','), d, DurationFieldUnit, DurationFieldInteger)
a.buf = appendDuration(appendArrayDelim(a.buf), d, DurationFieldUnit, DurationFieldInteger)
return a
}
@ -171,6 +171,6 @@ func (a *Array) Interface(i interface{}) *Array {
if obj, ok := i.(LogObjectMarshaler); ok {
return a.Object(obj)
}
a.buf = json.AppendInterface(append(a.buf, ','), i)
a.buf = appendInterface(appendArrayDelim(a.buf), i)
return a
}

View File

@ -26,7 +26,7 @@ func TestArray(t *testing.T) {
Time(time.Time{}).
Dur(0)
want := `[true,1,2,3,4,5,6,7,8,9,10,11,12,"a","b","1f","0001-01-01T00:00:00Z",0]`
if got := string(a.write([]byte{})); got != want {
if got := decodeObjectToStr(a.write([]byte{})); got != want {
t.Errorf("Array.write()\ngot: %s\nwant: %s", got, want)
}
}

453
binary_test.go Normal file
View File

@ -0,0 +1,453 @@
// +build binary_log
package zerolog
import (
"bytes"
"errors"
"fmt"
// "io/ioutil"
stdlog "log"
"time"
)
func ExampleBinaryNew() {
dst := bytes.Buffer{}
log := New(&dst)
log.Info().Msg("hello world")
fmt.Println(decodeIfBinaryToString(dst.Bytes()))
// Output: {"level":"info","message":"hello world"}
}
func ExampleLogger_With() {
dst := bytes.Buffer{}
log := New(&dst).
With().
Str("foo", "bar").
Logger()
log.Info().Msg("hello world")
fmt.Println(decodeIfBinaryToString(dst.Bytes()))
// Output: {"level":"info","foo":"bar","message":"hello world"}
}
func ExampleLogger_Level() {
dst := bytes.Buffer{}
log := New(&dst).Level(WarnLevel)
log.Info().Msg("filtered out message")
log.Error().Msg("kept message")
fmt.Println(decodeIfBinaryToString(dst.Bytes()))
// Output: {"level":"error","message":"kept message"}
}
func ExampleLogger_Sample() {
dst := bytes.Buffer{}
log := New(&dst).Sample(&BasicSampler{N: 2})
log.Info().Msg("message 1")
log.Info().Msg("message 2")
log.Info().Msg("message 3")
log.Info().Msg("message 4")
fmt.Println(decodeIfBinaryToString(dst.Bytes()))
// Output: {"level":"info","message":"message 2"}
// {"level":"info","message":"message 4"}
}
type LevelNameHook1 struct{}
func (h LevelNameHook1) Run(e *Event, l Level, msg string) {
if l != NoLevel {
e.Str("level_name", l.String())
} else {
e.Str("level_name", "NoLevel")
}
}
type MessageHook string
func (h MessageHook) Run(e *Event, l Level, msg string) {
e.Str("the_message", msg)
}
func ExampleLogger_Hook() {
var levelNameHook LevelNameHook1
var messageHook MessageHook = "The message"
dst := bytes.Buffer{}
log := New(&dst).Hook(levelNameHook).Hook(messageHook)
log.Info().Msg("hello world")
fmt.Println(decodeIfBinaryToString(dst.Bytes()))
// Output: {"level":"info","level_name":"info","the_message":"hello world","message":"hello world"}
}
func ExampleLogger_Print() {
dst := bytes.Buffer{}
log := New(&dst)
log.Print("hello world")
fmt.Println(decodeIfBinaryToString(dst.Bytes()))
// Output: {"level":"debug","message":"hello world"}
}
func ExampleLogger_Printf() {
dst := bytes.Buffer{}
log := New(&dst)
log.Printf("hello %s", "world")
fmt.Println(decodeIfBinaryToString(dst.Bytes()))
// Output: {"level":"debug","message":"hello world"}
}
func ExampleLogger_Debug() {
dst := bytes.Buffer{}
log := New(&dst)
log.Debug().
Str("foo", "bar").
Int("n", 123).
Msg("hello world")
fmt.Println(decodeIfBinaryToString(dst.Bytes()))
// Output: {"level":"debug","foo":"bar","n":123,"message":"hello world"}
}
func ExampleLogger_Info() {
dst := bytes.Buffer{}
log := New(&dst)
log.Info().
Str("foo", "bar").
Int("n", 123).
Msg("hello world")
fmt.Println(decodeIfBinaryToString(dst.Bytes()))
// Output: {"level":"info","foo":"bar","n":123,"message":"hello world"}
}
func ExampleLogger_Warn() {
dst := bytes.Buffer{}
log := New(&dst)
log.Warn().
Str("foo", "bar").
Msg("a warning message")
fmt.Println(decodeIfBinaryToString(dst.Bytes()))
// Output: {"level":"warn","foo":"bar","message":"a warning message"}
}
func ExampleLogger_Error() {
dst := bytes.Buffer{}
log := New(&dst)
log.Error().
Err(errors.New("some error")).
Msg("error doing something")
fmt.Println(decodeIfBinaryToString(dst.Bytes()))
// Output: {"level":"error","error":"some error","message":"error doing something"}
}
func ExampleLogger_WithLevel() {
dst := bytes.Buffer{}
log := New(&dst)
log.WithLevel(InfoLevel).
Msg("hello world")
fmt.Println(decodeIfBinaryToString(dst.Bytes()))
// Output: {"level":"info","message":"hello world"}
}
func ExampleLogger_Write() {
dst := bytes.Buffer{}
log := New(&dst).With().
Str("foo", "bar").
Logger()
stdlog.SetFlags(0)
stdlog.SetOutput(log)
stdlog.Print("hello world")
fmt.Println(decodeIfBinaryToString(dst.Bytes()))
// Output: {"foo":"bar","message":"hello world"}
}
func ExampleLogger_Log() {
dst := bytes.Buffer{}
log := New(&dst)
log.Log().
Str("foo", "bar").
Str("bar", "baz").
Msg("")
fmt.Println(decodeIfBinaryToString(dst.Bytes()))
// Output: {"foo":"bar","bar":"baz"}
}
func ExampleEvent_Dict() {
dst := bytes.Buffer{}
log := New(&dst)
log.Log().
Str("foo", "bar").
Dict("dict", Dict().
Str("bar", "baz").
Int("n", 1),
).
Msg("hello world")
fmt.Println(decodeIfBinaryToString(dst.Bytes()))
// Output: {"foo":"bar","dict":{"bar":"baz","n":1},"message":"hello world"}
}
type User struct {
Name string
Age int
Created time.Time
}
func (u User) MarshalZerologObject(e *Event) {
e.Str("name", u.Name).
Int("age", u.Age).
Time("created", u.Created)
}
type Users []User
func (uu Users) MarshalZerologArray(a *Array) {
for _, u := range uu {
a.Object(u)
}
}
func ExampleEvent_Array() {
dst := bytes.Buffer{}
log := New(&dst)
log.Log().
Str("foo", "bar").
Array("array", Arr().
Str("baz").
Int(1),
).
Msg("hello world")
fmt.Println(decodeIfBinaryToString(dst.Bytes()))
// Output: {"foo":"bar","array":["baz",1],"message":"hello world"}
}
func ExampleEvent_Array_object() {
dst := bytes.Buffer{}
log := New(&dst)
// Users implements LogArrayMarshaler
u := Users{
User{"John", 35, time.Time{}},
User{"Bob", 55, time.Time{}},
}
log.Log().
Str("foo", "bar").
Array("users", u).
Msg("hello world")
fmt.Println(decodeIfBinaryToString(dst.Bytes()))
// Output: {"foo":"bar","users":[{"name":"John","age":35,"created":"0001-01-01T00:00:00Z"},{"name":"Bob","age":55,"created":"0001-01-01T00:00:00Z"}],"message":"hello world"}
}
func ExampleEvent_Object() {
dst := bytes.Buffer{}
log := New(&dst)
// User implements LogObjectMarshaler
u := User{"John", 35, time.Time{}}
log.Log().
Str("foo", "bar").
Object("user", u).
Msg("hello world")
fmt.Println(decodeIfBinaryToString(dst.Bytes()))
// Output: {"foo":"bar","user":{"name":"John","age":35,"created":"0001-01-01T00:00:00Z"},"message":"hello world"}
}
func ExampleEvent_Interface() {
dst := bytes.Buffer{}
log := New(&dst)
obj := struct {
Name string `json:"name"`
}{
Name: "john",
}
log.Log().
Str("foo", "bar").
Interface("obj", obj).
Msg("hello world")
fmt.Println(decodeIfBinaryToString(dst.Bytes()))
// Output: {"foo":"bar","obj":{"name":"john"},"message":"hello world"}
}
func ExampleEvent_Dur() {
d := time.Duration(10 * time.Second)
dst := bytes.Buffer{}
log := New(&dst)
log.Log().
Str("foo", "bar").
Dur("dur", d).
Msg("hello world")
fmt.Println(decodeIfBinaryToString(dst.Bytes()))
// Output: {"foo":"bar","dur":10000,"message":"hello world"}
}
func ExampleEvent_Durs() {
d := []time.Duration{
time.Duration(10 * time.Second),
time.Duration(20 * time.Second),
}
dst := bytes.Buffer{}
log := New(&dst)
log.Log().
Str("foo", "bar").
Durs("durs", d).
Msg("hello world")
fmt.Println(decodeIfBinaryToString(dst.Bytes()))
// Output: {"foo":"bar","durs":[10000,20000],"message":"hello world"}
}
func ExampleContext_Dict() {
dst := bytes.Buffer{}
log := New(&dst).With().
Str("foo", "bar").
Dict("dict", Dict().
Str("bar", "baz").
Int("n", 1),
).Logger()
log.Log().Msg("hello world")
fmt.Println(decodeIfBinaryToString(dst.Bytes()))
// Output: {"foo":"bar","dict":{"bar":"baz","n":1},"message":"hello world"}
}
func ExampleContext_Array() {
dst := bytes.Buffer{}
log := New(&dst).With().
Str("foo", "bar").
Array("array", Arr().
Str("baz").
Int(1),
).Logger()
log.Log().Msg("hello world")
fmt.Println(decodeIfBinaryToString(dst.Bytes()))
// Output: {"foo":"bar","array":["baz",1],"message":"hello world"}
}
func ExampleContext_Array_object() {
// Users implements LogArrayMarshaler
u := Users{
User{"John", 35, time.Time{}},
User{"Bob", 55, time.Time{}},
}
dst := bytes.Buffer{}
log := New(&dst).With().
Str("foo", "bar").
Array("users", u).
Logger()
log.Log().Msg("hello world")
fmt.Println(decodeIfBinaryToString(dst.Bytes()))
// Output: {"foo":"bar","users":[{"name":"John","age":35,"created":"0001-01-01T00:00:00Z"},{"name":"Bob","age":55,"created":"0001-01-01T00:00:00Z"}],"message":"hello world"}
}
func ExampleContext_Object() {
// User implements LogObjectMarshaler
u := User{"John", 35, time.Time{}}
dst := bytes.Buffer{}
log := New(&dst).With().
Str("foo", "bar").
Object("user", u).
Logger()
log.Log().Msg("hello world")
fmt.Println(decodeIfBinaryToString(dst.Bytes()))
// Output: {"foo":"bar","user":{"name":"John","age":35,"created":"0001-01-01T00:00:00Z"},"message":"hello world"}
}
func ExampleContext_Interface() {
obj := struct {
Name string `json:"name"`
}{
Name: "john",
}
dst := bytes.Buffer{}
log := New(&dst).With().
Str("foo", "bar").
Interface("obj", obj).
Logger()
log.Log().Msg("hello world")
fmt.Println(decodeIfBinaryToString(dst.Bytes()))
// Output: {"foo":"bar","obj":{"name":"john"},"message":"hello world"}
}
func ExampleContext_Dur() {
d := time.Duration(10 * time.Second)
dst := bytes.Buffer{}
log := New(&dst).With().
Str("foo", "bar").
Dur("dur", d).
Logger()
log.Log().Msg("hello world")
fmt.Println(decodeIfBinaryToString(dst.Bytes()))
// Output: {"foo":"bar","dur":10000,"message":"hello world"}
}
func ExampleContext_Durs() {
d := []time.Duration{
time.Duration(10 * time.Second),
time.Duration(20 * time.Second),
}
dst := bytes.Buffer{}
log := New(&dst).With().
Str("foo", "bar").
Durs("durs", d).
Logger()
log.Log().Msg("hello world")
fmt.Println(decodeIfBinaryToString(dst.Bytes()))
// Output: {"foo":"bar","durs":[10000,20000],"message":"hello world"}
}

View File

@ -39,6 +39,7 @@ type ConsoleWriter struct {
func (w ConsoleWriter) Write(p []byte) (n int, err error) {
var event map[string]interface{}
p = decodeIfBinaryToBytes(p)
err = json.Unmarshal(p, &event)
if err != nil {
return

14
console_test.go Normal file
View File

@ -0,0 +1,14 @@
package zerolog_test
import (
"os"
"github.com/rs/zerolog"
)
func ExampleConsoleWriter_Write() {
log := zerolog.New(zerolog.ConsoleWriter{Out: os.Stdout, NoColor: true})
log.Info().Msg("hello world")
// Output: <nil> |INFO| hello world
}

View File

@ -3,8 +3,6 @@ package zerolog
import (
"io/ioutil"
"time"
"github.com/rs/zerolog/internal/json"
)
// Context configures a new sub-logger with contextual fields.
@ -25,8 +23,8 @@ 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(json.AppendKey(c.l.context, key), dict.buf...)
dict.buf = appendEndMarker(dict.buf)
c.l.context = append(appendKey(c.l.context, key), dict.buf...)
eventPool.Put(dict)
return c
}
@ -35,7 +33,7 @@ func (c Context) Dict(key string, dict *Event) Context {
// Use zerolog.Arr() to create the array or pass a type that
// implement the LogArrayMarshaler interface.
func (c Context) Array(key string, arr LogArrayMarshaler) Context {
c.l.context = json.AppendKey(c.l.context, key)
c.l.context = appendKey(c.l.context, key)
if arr, ok := arr.(*Array); ok {
c.l.context = arr.write(c.l.context)
return c
@ -55,33 +53,32 @@ func (c Context) Array(key string, arr LogArrayMarshaler) Context {
func (c Context) Object(key string, obj LogObjectMarshaler) Context {
e := newEvent(levelWriterAdapter{ioutil.Discard}, 0, true)
e.Object(key, obj)
e.buf[0] = ',' // A new event starts as an object, we want to embed it.
c.l.context = append(c.l.context, e.buf...)
c.l.context = appendObjectData(c.l.context, e.buf)
eventPool.Put(e)
return c
}
// 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 = json.AppendString(json.AppendKey(c.l.context, key), val)
c.l.context = appendString(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 = json.AppendStrings(json.AppendKey(c.l.context, key), vals)
c.l.context = appendStrings(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 = json.AppendBytes(json.AppendKey(c.l.context, key), val)
c.l.context = appendBytes(appendKey(c.l.context, key), val)
return c
}
// Hex adds the field key with val as a hex string to the logger context.
func (c Context) Hex(key string, val []byte) Context {
c.l.context = json.AppendHex(json.AppendKey(c.l.context, key), val)
c.l.context = appendHex(appendKey(c.l.context, key), val)
return c
}
@ -90,21 +87,21 @@ func (c Context) Hex(key string, val []byte) Context {
// No sanity check is performed on b; it must not contain carriage returns and
// be valid JSON.
func (c Context) RawJSON(key string, b []byte) Context {
c.l.context = append(json.AppendKey(c.l.context, key), b...)
c.l.context = appendJSON(appendKey(c.l.context, key), b)
return c
}
// AnErr adds the field key with err as a string to the logger context.
func (c Context) AnErr(key string, err error) Context {
if err != nil {
c.l.context = json.AppendError(json.AppendKey(c.l.context, key), err)
c.l.context = appendError(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 = json.AppendErrors(json.AppendKey(c.l.context, key), errs)
c.l.context = appendErrors(appendKey(c.l.context, key), errs)
return c
}
@ -112,164 +109,164 @@ func (c Context) Errs(key string, errs []error) Context {
// To customize the key name, change zerolog.ErrorFieldName.
func (c Context) Err(err error) Context {
if err != nil {
c.l.context = json.AppendError(json.AppendKey(c.l.context, ErrorFieldName), err)
c.l.context = appendError(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 = json.AppendBool(json.AppendKey(c.l.context, key), b)
c.l.context = appendBool(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 = json.AppendBools(json.AppendKey(c.l.context, key), b)
c.l.context = appendBools(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 = json.AppendInt(json.AppendKey(c.l.context, key), i)
c.l.context = appendInt(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 = json.AppendInts(json.AppendKey(c.l.context, key), i)
c.l.context = appendInts(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 = json.AppendInt8(json.AppendKey(c.l.context, key), i)
c.l.context = appendInt8(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 = json.AppendInts8(json.AppendKey(c.l.context, key), i)
c.l.context = appendInts8(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 = json.AppendInt16(json.AppendKey(c.l.context, key), i)
c.l.context = appendInt16(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 = json.AppendInts16(json.AppendKey(c.l.context, key), i)
c.l.context = appendInts16(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 = json.AppendInt32(json.AppendKey(c.l.context, key), i)
c.l.context = appendInt32(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 = json.AppendInts32(json.AppendKey(c.l.context, key), i)
c.l.context = appendInts32(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 = json.AppendInt64(json.AppendKey(c.l.context, key), i)
c.l.context = appendInt64(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 = json.AppendInts64(json.AppendKey(c.l.context, key), i)
c.l.context = appendInts64(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 = json.AppendUint(json.AppendKey(c.l.context, key), i)
c.l.context = appendUint(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 = json.AppendUints(json.AppendKey(c.l.context, key), i)
c.l.context = appendUints(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 = json.AppendUint8(json.AppendKey(c.l.context, key), i)
c.l.context = appendUint8(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 = json.AppendUints8(json.AppendKey(c.l.context, key), i)
c.l.context = appendUints8(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 = json.AppendUint16(json.AppendKey(c.l.context, key), i)
c.l.context = appendUint16(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 = json.AppendUints16(json.AppendKey(c.l.context, key), i)
c.l.context = appendUints16(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 = json.AppendUint32(json.AppendKey(c.l.context, key), i)
c.l.context = appendUint32(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 = json.AppendUints32(json.AppendKey(c.l.context, key), i)
c.l.context = appendUints32(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 = json.AppendUint64(json.AppendKey(c.l.context, key), i)
c.l.context = appendUint64(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 = json.AppendUints64(json.AppendKey(c.l.context, key), i)
c.l.context = appendUints64(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 = json.AppendFloat32(json.AppendKey(c.l.context, key), f)
c.l.context = appendFloat32(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 = json.AppendFloats32(json.AppendKey(c.l.context, key), f)
c.l.context = appendFloats32(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 = json.AppendFloat64(json.AppendKey(c.l.context, key), f)
c.l.context = appendFloat64(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 = json.AppendFloats64(json.AppendKey(c.l.context, key), f)
c.l.context = appendFloats64(appendKey(c.l.context, key), f)
return c
}
@ -292,31 +289,31 @@ 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 = json.AppendTime(json.AppendKey(c.l.context, key), t, TimeFieldFormat)
c.l.context = appendTime(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 = json.AppendTimes(json.AppendKey(c.l.context, key), t, TimeFieldFormat)
c.l.context = appendTimes(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 = json.AppendDuration(json.AppendKey(c.l.context, key), d, DurationFieldUnit, DurationFieldInteger)
c.l.context = appendDuration(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 = json.AppendDurations(json.AppendKey(c.l.context, key), d, DurationFieldUnit, DurationFieldInteger)
c.l.context = appendDurations(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 = json.AppendInterface(json.AppendKey(c.l.context, key), i)
c.l.context = appendInterface(appendKey(c.l.context, key), i)
return c
}

View File

@ -0,0 +1,26 @@
// +build !binary_log
package diode_test
import (
"fmt"
"os"
"time"
diodes "code.cloudfoundry.org/go-diodes"
"github.com/rs/zerolog"
"github.com/rs/zerolog/diode"
)
func ExampleNewWriter() {
d := diodes.NewManyToOne(1000, diodes.AlertFunc(func(missed int) {
fmt.Printf("Dropped %d messages\n", missed)
}))
w := diode.NewWriter(os.Stdout, d, 10*time.Millisecond)
log := zerolog.New(w)
log.Print("test")
w.Close()
// Output: {"level":"debug","message":"test"}
}

View File

@ -1,6 +1,7 @@
package diode_test
import (
"bytes"
"fmt"
"io/ioutil"
"log"
@ -11,19 +12,24 @@ import (
diodes "code.cloudfoundry.org/go-diodes"
"github.com/rs/zerolog"
"github.com/rs/zerolog/diode"
"github.com/rs/zerolog/internal/cbor"
)
func ExampleNewWriter() {
func TestNewWriter(t *testing.T) {
d := diodes.NewManyToOne(1000, diodes.AlertFunc(func(missed int) {
fmt.Printf("Dropped %d messages\n", missed)
}))
w := diode.NewWriter(os.Stdout, d, 10*time.Millisecond)
buf := bytes.Buffer{}
w := diode.NewWriter(&buf, d, 10*time.Millisecond)
log := zerolog.New(w)
log.Print("test")
w.Close()
// Output: {"level":"debug","message":"test"}
want := "{\"level\":\"debug\",\"message\":\"test\"}\n"
got := cbor.DecodeIfBinaryToString(buf.Bytes())
if got != want {
t.Errorf("Diode New Writer Test failed. got:%s, want:%s!", got, want)
}
}
func Benchmark(b *testing.B) {

218
encoder_cbor.go Normal file
View File

@ -0,0 +1,218 @@
// +build binary_log
package zerolog
// This file contains bindings to do binary encoding.
import (
"time"
"github.com/rs/zerolog/internal/cbor"
)
func appendInterface(dst []byte, i interface{}) []byte {
return cbor.AppendInterface(dst, i)
}
func appendKey(dst []byte, s string) []byte {
return cbor.AppendKey(dst, s)
}
func appendFloats64(dst []byte, f []float64) []byte {
return cbor.AppendFloats64(dst, f)
}
func appendFloat64(dst []byte, f float64) []byte {
return cbor.AppendFloat64(dst, f)
}
func appendFloats32(dst []byte, f []float32) []byte {
return cbor.AppendFloats32(dst, f)
}
func appendFloat32(dst []byte, f float32) []byte {
return cbor.AppendFloat32(dst, f)
}
func appendUints64(dst []byte, i []uint64) []byte {
return cbor.AppendUints64(dst, i)
}
func appendUint64(dst []byte, i uint64) []byte {
return cbor.AppendUint64(dst, i)
}
func appendUints32(dst []byte, i []uint32) []byte {
return cbor.AppendUints32(dst, i)
}
func appendUint32(dst []byte, i uint32) []byte {
return cbor.AppendUint32(dst, i)
}
func appendUints16(dst []byte, i []uint16) []byte {
return cbor.AppendUints16(dst, i)
}
func appendUint16(dst []byte, i uint16) []byte {
return cbor.AppendUint16(dst, i)
}
func appendUints8(dst []byte, i []uint8) []byte {
return cbor.AppendUints8(dst, i)
}
func appendUint8(dst []byte, i uint8) []byte {
return cbor.AppendUint8(dst, i)
}
func appendUints(dst []byte, i []uint) []byte {
return cbor.AppendUints(dst, i)
}
func appendUint(dst []byte, i uint) []byte {
return cbor.AppendUint(dst, i)
}
func appendInts64(dst []byte, i []int64) []byte {
return cbor.AppendInts64(dst, i)
}
func appendInt64(dst []byte, i int64) []byte {
return cbor.AppendInt64(dst, i)
}
func appendInts32(dst []byte, i []int32) []byte {
return cbor.AppendInts32(dst, i)
}
func appendInt32(dst []byte, i int32) []byte {
return cbor.AppendInt32(dst, i)
}
func appendInts16(dst []byte, i []int16) []byte {
return cbor.AppendInts16(dst, i)
}
func appendInt16(dst []byte, i int16) []byte {
return cbor.AppendInt16(dst, i)
}
func appendInts8(dst []byte, i []int8) []byte {
return cbor.AppendInts8(dst, i)
}
func appendInt8(dst []byte, i int8) []byte {
return cbor.AppendInt8(dst, i)
}
func appendInts(dst []byte, i []int) []byte {
return cbor.AppendInts(dst, i)
}
func appendInt(dst []byte, i int) []byte {
return cbor.AppendInt(dst, i)
}
func appendBools(dst []byte, b []bool) []byte {
return cbor.AppendBools(dst, b)
}
func appendBool(dst []byte, b bool) []byte {
return cbor.AppendBool(dst, b)
}
func appendError(dst []byte, e error) []byte {
return cbor.AppendError(dst, e)
}
func appendErrors(dst []byte, e []error) []byte {
return cbor.AppendErrors(dst, e)
}
func appendString(dst []byte, s string) []byte {
return cbor.AppendString(dst, s)
}
func appendStrings(dst []byte, s []string) []byte {
return cbor.AppendStrings(dst, s)
}
func appendDuration(dst []byte, t time.Duration, d time.Duration, fmt bool) []byte {
return cbor.AppendDuration(dst, t, d, fmt)
}
func appendDurations(dst []byte, t []time.Duration, d time.Duration, fmt bool) []byte {
return cbor.AppendDurations(dst, t, d, fmt)
}
func appendTimes(dst []byte, t []time.Time, fmt string) []byte {
return cbor.AppendTimes(dst, t, fmt)
}
func appendTime(dst []byte, t time.Time, fmt string) []byte {
return cbor.AppendTime(dst, t, fmt)
}
func appendEndMarker(dst []byte) []byte {
return cbor.AppendEndMarker(dst)
}
func appendLineBreak(dst []byte) []byte {
// No line breaks needed in binary format.
return dst
}
func appendBeginMarker(dst []byte) []byte {
return cbor.AppendBeginMarker(dst)
}
func appendBytes(dst []byte, b []byte) []byte {
return cbor.AppendBytes(dst, b)
}
func appendArrayStart(dst []byte) []byte {
return cbor.AppendArrayStart(dst)
}
func appendArrayEnd(dst []byte) []byte {
return cbor.AppendArrayEnd(dst)
}
func appendArrayDelim(dst []byte) []byte {
return cbor.AppendArrayDelim(dst)
}
func appendObjectData(dst []byte, src []byte) []byte {
// Map begin character is present in the src, which
// should not be copied when appending to existing data.
return cbor.AppendObjectData(dst, src[1:])
}
func appendJSON(dst []byte, j []byte) []byte {
return cbor.AppendEmbeddedJSON(dst, j)
}
func appendNil(dst []byte) []byte {
return cbor.AppendNull(dst)
}
func appendHex(dst []byte, val []byte) []byte {
return cbor.AppendHex(dst, val)
}
// decodeIfBinaryToString - converts a binary formatted log msg to a
// JSON formatted String Log message.
func decodeIfBinaryToString(in []byte) string {
return cbor.DecodeIfBinaryToString(in)
}
func decodeObjectToStr(in []byte) string {
return cbor.DecodeObjectToStr(in)
}
// decodeIfBinaryToBytes - converts a binary formatted log msg to a
// JSON formatted Bytes Log message.
func decodeIfBinaryToBytes(in []byte) []byte {
return cbor.DecodeIfBinaryToBytes(in)
}

216
encoder_json.go Normal file
View File

@ -0,0 +1,216 @@
// +build !binary_log
package zerolog
// encoder_json.go file contains bindings to generate
// JSON encoded byte stream.
import (
"strconv"
"time"
"github.com/rs/zerolog/internal/json"
)
func appendInterface(dst []byte, i interface{}) []byte {
return json.AppendInterface(dst, i)
}
func appendKey(dst []byte, s string) []byte {
return json.AppendKey(dst, s)
}
func appendFloats64(dst []byte, f []float64) []byte {
return json.AppendFloats64(dst, f)
}
func appendFloat64(dst []byte, f float64) []byte {
return json.AppendFloat64(dst, f)
}
func appendFloats32(dst []byte, f []float32) []byte {
return json.AppendFloats32(dst, f)
}
func appendFloat32(dst []byte, f float32) []byte {
return json.AppendFloat32(dst, f)
}
func appendUints64(dst []byte, i []uint64) []byte {
return json.AppendUints64(dst, i)
}
func appendUint64(dst []byte, i uint64) []byte {
return strconv.AppendUint(dst, uint64(i), 10)
}
func appendUints32(dst []byte, i []uint32) []byte {
return json.AppendUints32(dst, i)
}
func appendUint32(dst []byte, i uint32) []byte {
return strconv.AppendUint(dst, uint64(i), 10)
}
func appendUints16(dst []byte, i []uint16) []byte {
return json.AppendUints16(dst, i)
}
func appendUint16(dst []byte, i uint16) []byte {
return strconv.AppendUint(dst, uint64(i), 10)
}
func appendUints8(dst []byte, i []uint8) []byte {
return json.AppendUints8(dst, i)
}
func appendUint8(dst []byte, i uint8) []byte {
return strconv.AppendUint(dst, uint64(i), 10)
}
func appendUints(dst []byte, i []uint) []byte {
return json.AppendUints(dst, i)
}
func appendUint(dst []byte, i uint) []byte {
return strconv.AppendUint(dst, uint64(i), 10)
}
func appendInts64(dst []byte, i []int64) []byte {
return json.AppendInts64(dst, i)
}
func appendInt64(dst []byte, i int64) []byte {
return strconv.AppendInt(dst, int64(i), 10)
}
func appendInts32(dst []byte, i []int32) []byte {
return json.AppendInts32(dst, i)
}
func appendInt32(dst []byte, i int32) []byte {
return strconv.AppendInt(dst, int64(i), 10)
}
func appendInts16(dst []byte, i []int16) []byte {
return json.AppendInts16(dst, i)
}
func appendInt16(dst []byte, i int16) []byte {
return strconv.AppendInt(dst, int64(i), 10)
}
func appendInts8(dst []byte, i []int8) []byte {
return json.AppendInts8(dst, i)
}
func appendInt8(dst []byte, i int8) []byte {
return strconv.AppendInt(dst, int64(i), 10)
}
func appendInts(dst []byte, i []int) []byte {
return json.AppendInts(dst, i)
}
func appendInt(dst []byte, i int) []byte {
return strconv.AppendInt(dst, int64(i), 10)
}
func appendBools(dst []byte, b []bool) []byte {
return json.AppendBools(dst, b)
}
func appendBool(dst []byte, b bool) []byte {
return strconv.AppendBool(dst, b)
}
func appendError(dst []byte, e error) []byte {
return json.AppendError(dst, e)
}
func appendErrors(dst []byte, e []error) []byte {
return json.AppendErrors(dst, e)
}
func appendString(dst []byte, s string) []byte {
return json.AppendString(dst, s)
}
func appendStrings(dst []byte, s []string) []byte {
return json.AppendStrings(dst, s)
}
func appendDuration(dst []byte, t time.Duration, d time.Duration, fmt bool) []byte {
return json.AppendDuration(dst, t, d, fmt)
}
func appendDurations(dst []byte, t []time.Duration, d time.Duration, fmt bool) []byte {
return json.AppendDurations(dst, t, d, fmt)
}
func appendTimes(dst []byte, t []time.Time, fmt string) []byte {
return json.AppendTimes(dst, t, fmt)
}
func appendTime(dst []byte, t time.Time, fmt string) []byte {
return json.AppendTime(dst, t, fmt)
}
func appendEndMarker(dst []byte) []byte {
return append(dst, '}')
}
func appendLineBreak(dst []byte) []byte {
return append(dst, '\n')
}
func appendBeginMarker(dst []byte) []byte {
return append(dst, '{')
}
func appendBytes(dst []byte, b []byte) []byte {
return json.AppendBytes(dst, b)
}
func appendArrayStart(dst []byte) []byte {
return append(dst, '[')
}
func appendArrayEnd(dst []byte) []byte {
return append(dst, ']')
}
func appendArrayDelim(dst []byte) []byte {
if len(dst) > 0 {
return append(dst, ',')
}
return dst
}
func appendObjectData(dst []byte, src []byte) []byte {
return json.AppendObjectData(dst, src)
}
func appendJSON(dst []byte, j []byte) []byte {
return append(dst, j...)
}
func appendNil(dst []byte) []byte {
return append(dst, "null"...)
}
func decodeIfBinaryToString(in []byte) string {
return string(in)
}
func decodeObjectToStr(in []byte) string {
return string(in)
}
func decodeIfBinaryToBytes(in []byte) []byte {
return in
}
func appendHex(in []byte, val []byte) []byte {
return json.AppendHex(in, val)
}

114
event.go
View File

@ -7,8 +7,6 @@ import (
"strconv"
"sync"
"time"
"github.com/rs/zerolog/internal/json"
)
var eventPool = &sync.Pool{
@ -47,9 +45,9 @@ func newEvent(w LevelWriter, level Level, enabled bool) *Event {
return &Event{}
}
e := eventPool.Get().(*Event)
e.buf = e.buf[:1]
e.buf = e.buf[:0]
e.h = e.h[:0]
e.buf[0] = '{'
e.buf = appendBeginMarker(e.buf)
e.w = w
e.level = level
return e
@ -59,7 +57,8 @@ func (e *Event) write() (err error) {
if e == nil {
return nil
}
e.buf = append(e.buf, '}', '\n')
e.buf = appendEndMarker(e.buf)
e.buf = appendLineBreak(e.buf)
if e.w != nil {
_, err = e.w.WriteLevel(e.level, e.buf)
}
@ -98,7 +97,7 @@ func (e *Event) Msg(msg string) {
}
}
if msg != "" {
e.buf = json.AppendString(json.AppendKey(e.buf, MessageFieldName), msg)
e.buf = appendString(appendKey(e.buf, MessageFieldName), msg)
}
if e.done != nil {
defer e.done(msg)
@ -134,7 +133,8 @@ func (e *Event) Dict(key string, dict *Event) *Event {
if e == nil {
return e
}
e.buf = append(append(json.AppendKey(e.buf, key), dict.buf...), '}')
dict.buf = appendEndMarker(dict.buf)
e.buf = append(appendKey(e.buf, key), dict.buf...)
eventPool.Put(dict)
return e
}
@ -153,7 +153,7 @@ func (e *Event) Array(key string, arr LogArrayMarshaler) *Event {
if e == nil {
return e
}
e.buf = json.AppendKey(e.buf, key)
e.buf = appendKey(e.buf, key)
var a *Array
if aa, ok := arr.(*Array); ok {
a = aa
@ -166,17 +166,9 @@ func (e *Event) Array(key string, arr LogArrayMarshaler) *Event {
}
func (e *Event) appendObject(obj LogObjectMarshaler) {
pos := len(e.buf)
e.buf = appendBeginMarker(e.buf)
obj.MarshalZerologObject(e)
if pos < len(e.buf) {
// As MarshalZerologObject will use event API, the first field will be
// preceded by a comma. If at least one field has been added (buf grew),
// we replace this coma by the opening bracket.
e.buf[pos] = '{'
} else {
e.buf = append(e.buf, '{')
}
e.buf = append(e.buf, '}')
e.buf = appendEndMarker(e.buf)
}
// Object marshals an object that implement the LogObjectMarshaler interface.
@ -184,7 +176,7 @@ func (e *Event) Object(key string, obj LogObjectMarshaler) *Event {
if e == nil {
return e
}
e.buf = json.AppendKey(e.buf, key)
e.buf = appendKey(e.buf, key)
e.appendObject(obj)
return e
}
@ -194,7 +186,7 @@ func (e *Event) Str(key, val string) *Event {
if e == nil {
return e
}
e.buf = json.AppendString(json.AppendKey(e.buf, key), val)
e.buf = appendString(appendKey(e.buf, key), val)
return e
}
@ -203,7 +195,7 @@ func (e *Event) Strs(key string, vals []string) *Event {
if e == nil {
return e
}
e.buf = json.AppendStrings(json.AppendKey(e.buf, key), vals)
e.buf = appendStrings(appendKey(e.buf, key), vals)
return e
}
@ -215,7 +207,7 @@ func (e *Event) Bytes(key string, val []byte) *Event {
if e == nil {
return e
}
e.buf = json.AppendBytes(json.AppendKey(e.buf, key), val)
e.buf = appendBytes(appendKey(e.buf, key), val)
return e
}
@ -224,7 +216,7 @@ func (e *Event) Hex(key string, val []byte) *Event {
if e == nil {
return e
}
e.buf = json.AppendHex(json.AppendKey(e.buf, key), val)
e.buf = appendHex(appendKey(e.buf, key), val)
return e
}
@ -236,7 +228,7 @@ func (e *Event) RawJSON(key string, b []byte) *Event {
if e == nil {
return e
}
e.buf = append(json.AppendKey(e.buf, key), b...)
e.buf = appendJSON(appendKey(e.buf, key), b)
return e
}
@ -247,7 +239,7 @@ func (e *Event) AnErr(key string, err error) *Event {
return e
}
if err != nil {
e.buf = json.AppendError(json.AppendKey(e.buf, key), err)
e.buf = appendError(appendKey(e.buf, key), err)
}
return e
}
@ -258,7 +250,7 @@ func (e *Event) Errs(key string, errs []error) *Event {
if e == nil {
return e
}
e.buf = json.AppendErrors(json.AppendKey(e.buf, key), errs)
e.buf = appendErrors(appendKey(e.buf, key), errs)
return e
}
@ -270,7 +262,7 @@ func (e *Event) Err(err error) *Event {
return e
}
if err != nil {
e.buf = json.AppendError(json.AppendKey(e.buf, ErrorFieldName), err)
e.buf = appendError(appendKey(e.buf, ErrorFieldName), err)
}
return e
}
@ -280,7 +272,7 @@ func (e *Event) Bool(key string, b bool) *Event {
if e == nil {
return e
}
e.buf = json.AppendBool(json.AppendKey(e.buf, key), b)
e.buf = appendBool(appendKey(e.buf, key), b)
return e
}
@ -289,7 +281,7 @@ func (e *Event) Bools(key string, b []bool) *Event {
if e == nil {
return e
}
e.buf = json.AppendBools(json.AppendKey(e.buf, key), b)
e.buf = appendBools(appendKey(e.buf, key), b)
return e
}
@ -298,7 +290,7 @@ func (e *Event) Int(key string, i int) *Event {
if e == nil {
return e
}
e.buf = json.AppendInt(json.AppendKey(e.buf, key), i)
e.buf = appendInt(appendKey(e.buf, key), i)
return e
}
@ -307,7 +299,7 @@ func (e *Event) Ints(key string, i []int) *Event {
if e == nil {
return e
}
e.buf = json.AppendInts(json.AppendKey(e.buf, key), i)
e.buf = appendInts(appendKey(e.buf, key), i)
return e
}
@ -316,7 +308,7 @@ func (e *Event) Int8(key string, i int8) *Event {
if e == nil {
return e
}
e.buf = json.AppendInt8(json.AppendKey(e.buf, key), i)
e.buf = appendInt8(appendKey(e.buf, key), i)
return e
}
@ -325,7 +317,7 @@ func (e *Event) Ints8(key string, i []int8) *Event {
if e == nil {
return e
}
e.buf = json.AppendInts8(json.AppendKey(e.buf, key), i)
e.buf = appendInts8(appendKey(e.buf, key), i)
return e
}
@ -334,7 +326,7 @@ func (e *Event) Int16(key string, i int16) *Event {
if e == nil {
return e
}
e.buf = json.AppendInt16(json.AppendKey(e.buf, key), i)
e.buf = appendInt16(appendKey(e.buf, key), i)
return e
}
@ -343,7 +335,7 @@ func (e *Event) Ints16(key string, i []int16) *Event {
if e == nil {
return e
}
e.buf = json.AppendInts16(json.AppendKey(e.buf, key), i)
e.buf = appendInts16(appendKey(e.buf, key), i)
return e
}
@ -352,7 +344,7 @@ func (e *Event) Int32(key string, i int32) *Event {
if e == nil {
return e
}
e.buf = json.AppendInt32(json.AppendKey(e.buf, key), i)
e.buf = appendInt32(appendKey(e.buf, key), i)
return e
}
@ -361,7 +353,7 @@ func (e *Event) Ints32(key string, i []int32) *Event {
if e == nil {
return e
}
e.buf = json.AppendInts32(json.AppendKey(e.buf, key), i)
e.buf = appendInts32(appendKey(e.buf, key), i)
return e
}
@ -370,7 +362,7 @@ func (e *Event) Int64(key string, i int64) *Event {
if e == nil {
return e
}
e.buf = json.AppendInt64(json.AppendKey(e.buf, key), i)
e.buf = appendInt64(appendKey(e.buf, key), i)
return e
}
@ -379,7 +371,7 @@ func (e *Event) Ints64(key string, i []int64) *Event {
if e == nil {
return e
}
e.buf = json.AppendInts64(json.AppendKey(e.buf, key), i)
e.buf = appendInts64(appendKey(e.buf, key), i)
return e
}
@ -388,7 +380,7 @@ func (e *Event) Uint(key string, i uint) *Event {
if e == nil {
return e
}
e.buf = json.AppendUint(json.AppendKey(e.buf, key), i)
e.buf = appendUint(appendKey(e.buf, key), i)
return e
}
@ -397,7 +389,7 @@ func (e *Event) Uints(key string, i []uint) *Event {
if e == nil {
return e
}
e.buf = json.AppendUints(json.AppendKey(e.buf, key), i)
e.buf = appendUints(appendKey(e.buf, key), i)
return e
}
@ -406,7 +398,7 @@ func (e *Event) Uint8(key string, i uint8) *Event {
if e == nil {
return e
}
e.buf = json.AppendUint8(json.AppendKey(e.buf, key), i)
e.buf = appendUint8(appendKey(e.buf, key), i)
return e
}
@ -415,7 +407,7 @@ func (e *Event) Uints8(key string, i []uint8) *Event {
if e == nil {
return e
}
e.buf = json.AppendUints8(json.AppendKey(e.buf, key), i)
e.buf = appendUints8(appendKey(e.buf, key), i)
return e
}
@ -424,7 +416,7 @@ func (e *Event) Uint16(key string, i uint16) *Event {
if e == nil {
return e
}
e.buf = json.AppendUint16(json.AppendKey(e.buf, key), i)
e.buf = appendUint16(appendKey(e.buf, key), i)
return e
}
@ -433,7 +425,7 @@ func (e *Event) Uints16(key string, i []uint16) *Event {
if e == nil {
return e
}
e.buf = json.AppendUints16(json.AppendKey(e.buf, key), i)
e.buf = appendUints16(appendKey(e.buf, key), i)
return e
}
@ -442,7 +434,7 @@ func (e *Event) Uint32(key string, i uint32) *Event {
if e == nil {
return e
}
e.buf = json.AppendUint32(json.AppendKey(e.buf, key), i)
e.buf = appendUint32(appendKey(e.buf, key), i)
return e
}
@ -451,7 +443,7 @@ func (e *Event) Uints32(key string, i []uint32) *Event {
if e == nil {
return e
}
e.buf = json.AppendUints32(json.AppendKey(e.buf, key), i)
e.buf = appendUints32(appendKey(e.buf, key), i)
return e
}
@ -460,7 +452,7 @@ func (e *Event) Uint64(key string, i uint64) *Event {
if e == nil {
return e
}
e.buf = json.AppendUint64(json.AppendKey(e.buf, key), i)
e.buf = appendUint64(appendKey(e.buf, key), i)
return e
}
@ -469,7 +461,7 @@ func (e *Event) Uints64(key string, i []uint64) *Event {
if e == nil {
return e
}
e.buf = json.AppendUints64(json.AppendKey(e.buf, key), i)
e.buf = appendUints64(appendKey(e.buf, key), i)
return e
}
@ -478,7 +470,7 @@ func (e *Event) Float32(key string, f float32) *Event {
if e == nil {
return e
}
e.buf = json.AppendFloat32(json.AppendKey(e.buf, key), f)
e.buf = appendFloat32(appendKey(e.buf, key), f)
return e
}
@ -487,7 +479,7 @@ func (e *Event) Floats32(key string, f []float32) *Event {
if e == nil {
return e
}
e.buf = json.AppendFloats32(json.AppendKey(e.buf, key), f)
e.buf = appendFloats32(appendKey(e.buf, key), f)
return e
}
@ -496,7 +488,7 @@ func (e *Event) Float64(key string, f float64) *Event {
if e == nil {
return e
}
e.buf = json.AppendFloat64(json.AppendKey(e.buf, key), f)
e.buf = appendFloat64(appendKey(e.buf, key), f)
return e
}
@ -505,7 +497,7 @@ func (e *Event) Floats64(key string, f []float64) *Event {
if e == nil {
return e
}
e.buf = json.AppendFloats64(json.AppendKey(e.buf, key), f)
e.buf = appendFloats64(appendKey(e.buf, key), f)
return e
}
@ -518,7 +510,7 @@ func (e *Event) Timestamp() *Event {
if e == nil {
return e
}
e.buf = json.AppendTime(json.AppendKey(e.buf, TimestampFieldName), TimestampFunc(), TimeFieldFormat)
e.buf = appendTime(appendKey(e.buf, TimestampFieldName), TimestampFunc(), TimeFieldFormat)
return e
}
@ -527,7 +519,7 @@ func (e *Event) Time(key string, t time.Time) *Event {
if e == nil {
return e
}
e.buf = json.AppendTime(json.AppendKey(e.buf, key), t, TimeFieldFormat)
e.buf = appendTime(appendKey(e.buf, key), t, TimeFieldFormat)
return e
}
@ -536,7 +528,7 @@ func (e *Event) Times(key string, t []time.Time) *Event {
if e == nil {
return e
}
e.buf = json.AppendTimes(json.AppendKey(e.buf, key), t, TimeFieldFormat)
e.buf = appendTimes(appendKey(e.buf, key), t, TimeFieldFormat)
return e
}
@ -547,7 +539,7 @@ func (e *Event) Dur(key string, d time.Duration) *Event {
if e == nil {
return e
}
e.buf = json.AppendDuration(json.AppendKey(e.buf, key), d, DurationFieldUnit, DurationFieldInteger)
e.buf = appendDuration(appendKey(e.buf, key), d, DurationFieldUnit, DurationFieldInteger)
return e
}
@ -558,7 +550,7 @@ func (e *Event) Durs(key string, d []time.Duration) *Event {
if e == nil {
return e
}
e.buf = json.AppendDurations(json.AppendKey(e.buf, key), d, DurationFieldUnit, DurationFieldInteger)
e.buf = appendDurations(appendKey(e.buf, key), d, DurationFieldUnit, DurationFieldInteger)
return e
}
@ -573,7 +565,7 @@ func (e *Event) TimeDiff(key string, t time.Time, start time.Time) *Event {
if t.After(start) {
d = t.Sub(start)
}
e.buf = json.AppendDuration(json.AppendKey(e.buf, key), d, DurationFieldUnit, DurationFieldInteger)
e.buf = appendDuration(appendKey(e.buf, key), d, DurationFieldUnit, DurationFieldInteger)
return e
}
@ -585,7 +577,7 @@ func (e *Event) Interface(key string, i interface{}) *Event {
if obj, ok := i.(LogObjectMarshaler); ok {
return e.Object(key, obj)
}
e.buf = json.AppendInterface(json.AppendKey(e.buf, key), i)
e.buf = appendInterface(appendKey(e.buf, key), i)
return e
}
@ -602,6 +594,6 @@ func (e *Event) caller(skip int) *Event {
if !ok {
return e
}
e.buf = json.AppendString(json.AppendKey(e.buf, CallerFieldName), file+":"+strconv.Itoa(line))
e.buf = appendString(appendKey(e.buf, CallerFieldName), file+":"+strconv.Itoa(line))
return e
}

108
fields.go
View File

@ -3,8 +3,6 @@ package zerolog
import (
"sort"
"time"
"github.com/rs/zerolog/internal/json"
)
func appendFields(dst []byte, fields map[string]interface{}) []byte {
@ -14,114 +12,114 @@ func appendFields(dst []byte, fields map[string]interface{}) []byte {
}
sort.Strings(keys)
for _, key := range keys {
dst = json.AppendKey(dst, key)
dst = appendKey(dst, key)
switch val := fields[key].(type) {
case string:
dst = json.AppendString(dst, val)
dst = appendString(dst, val)
case []byte:
dst = json.AppendBytes(dst, val)
dst = appendBytes(dst, val)
case error:
dst = json.AppendError(dst, val)
dst = appendError(dst, val)
case []error:
dst = json.AppendErrors(dst, val)
dst = appendErrors(dst, val)
case bool:
dst = json.AppendBool(dst, val)
dst = appendBool(dst, val)
case int:
dst = json.AppendInt(dst, val)
dst = appendInt(dst, val)
case int8:
dst = json.AppendInt8(dst, val)
dst = appendInt8(dst, val)
case int16:
dst = json.AppendInt16(dst, val)
dst = appendInt16(dst, val)
case int32:
dst = json.AppendInt32(dst, val)
dst = appendInt32(dst, val)
case int64:
dst = json.AppendInt64(dst, val)
dst = appendInt64(dst, val)
case uint:
dst = json.AppendUint(dst, val)
dst = appendUint(dst, val)
case uint8:
dst = json.AppendUint8(dst, val)
dst = appendUint8(dst, val)
case uint16:
dst = json.AppendUint16(dst, val)
dst = appendUint16(dst, val)
case uint32:
dst = json.AppendUint32(dst, val)
dst = appendUint32(dst, val)
case uint64:
dst = json.AppendUint64(dst, val)
dst = appendUint64(dst, val)
case float32:
dst = json.AppendFloat32(dst, val)
dst = appendFloat32(dst, val)
case float64:
dst = json.AppendFloat64(dst, val)
dst = appendFloat64(dst, val)
case time.Time:
dst = json.AppendTime(dst, val, TimeFieldFormat)
dst = appendTime(dst, val, TimeFieldFormat)
case time.Duration:
dst = json.AppendDuration(dst, val, DurationFieldUnit, DurationFieldInteger)
dst = appendDuration(dst, val, DurationFieldUnit, DurationFieldInteger)
case *string:
dst = json.AppendString(dst, *val)
dst = appendString(dst, *val)
case *bool:
dst = json.AppendBool(dst, *val)
dst = appendBool(dst, *val)
case *int:
dst = json.AppendInt(dst, *val)
dst = appendInt(dst, *val)
case *int8:
dst = json.AppendInt8(dst, *val)
dst = appendInt8(dst, *val)
case *int16:
dst = json.AppendInt16(dst, *val)
dst = appendInt16(dst, *val)
case *int32:
dst = json.AppendInt32(dst, *val)
dst = appendInt32(dst, *val)
case *int64:
dst = json.AppendInt64(dst, *val)
dst = appendInt64(dst, *val)
case *uint:
dst = json.AppendUint(dst, *val)
dst = appendUint(dst, *val)
case *uint8:
dst = json.AppendUint8(dst, *val)
dst = appendUint8(dst, *val)
case *uint16:
dst = json.AppendUint16(dst, *val)
dst = appendUint16(dst, *val)
case *uint32:
dst = json.AppendUint32(dst, *val)
dst = appendUint32(dst, *val)
case *uint64:
dst = json.AppendUint64(dst, *val)
dst = appendUint64(dst, *val)
case *float32:
dst = json.AppendFloat32(dst, *val)
dst = appendFloat32(dst, *val)
case *float64:
dst = json.AppendFloat64(dst, *val)
dst = appendFloat64(dst, *val)
case *time.Time:
dst = json.AppendTime(dst, *val, TimeFieldFormat)
dst = appendTime(dst, *val, TimeFieldFormat)
case *time.Duration:
dst = json.AppendDuration(dst, *val, DurationFieldUnit, DurationFieldInteger)
dst = appendDuration(dst, *val, DurationFieldUnit, DurationFieldInteger)
case []string:
dst = json.AppendStrings(dst, val)
dst = appendStrings(dst, val)
case []bool:
dst = json.AppendBools(dst, val)
dst = appendBools(dst, val)
case []int:
dst = json.AppendInts(dst, val)
dst = appendInts(dst, val)
case []int8:
dst = json.AppendInts8(dst, val)
dst = appendInts8(dst, val)
case []int16:
dst = json.AppendInts16(dst, val)
dst = appendInts16(dst, val)
case []int32:
dst = json.AppendInts32(dst, val)
dst = appendInts32(dst, val)
case []int64:
dst = json.AppendInts64(dst, val)
dst = appendInts64(dst, val)
case []uint:
dst = json.AppendUints(dst, val)
dst = appendUints(dst, val)
// case []uint8:
// dst = appendUints8(dst, val)
case []uint16:
dst = json.AppendUints16(dst, val)
dst = appendUints16(dst, val)
case []uint32:
dst = json.AppendUints32(dst, val)
dst = appendUints32(dst, val)
case []uint64:
dst = json.AppendUints64(dst, val)
dst = appendUints64(dst, val)
case []float32:
dst = json.AppendFloats32(dst, val)
dst = appendFloats32(dst, val)
case []float64:
dst = json.AppendFloats64(dst, val)
dst = appendFloats64(dst, val)
case []time.Time:
dst = json.AppendTimes(dst, val, TimeFieldFormat)
dst = appendTimes(dst, val, TimeFieldFormat)
case []time.Duration:
dst = json.AppendDurations(dst, val, DurationFieldUnit, DurationFieldInteger)
dst = appendDurations(dst, val, DurationFieldUnit, DurationFieldInteger)
case nil:
dst = append(dst, "null"...)
dst = appendNil(dst)
default:
dst = json.AppendInterface(dst, val)
dst = appendInterface(dst, val)
}
}
return dst

View File

@ -1,3 +1,5 @@
// +build !binary_log
package hlog_test
import (

View File

@ -15,8 +15,17 @@ import (
"net/http/httptest"
"github.com/rs/zerolog"
"github.com/rs/zerolog/internal/cbor"
)
func decodeIfBinary(out *bytes.Buffer) string {
p := out.Bytes()
if len(p) == 0 || p[0] < 0x7F {
return out.String()
}
return cbor.DecodeObjectToStr(p) + "\n"
}
func TestNewHandler(t *testing.T) {
log := zerolog.New(nil).With().
Str("foo", "bar").
@ -42,7 +51,7 @@ func TestURLHandler(t *testing.T) {
}))
h = NewHandler(zerolog.New(out))(h)
h.ServeHTTP(nil, r)
if want, got := `{"url":"/path?foo=bar"}`+"\n", out.String(); want != got {
if want, got := `{"url":"/path?foo=bar"}`+"\n", decodeIfBinary(out); want != got {
t.Errorf("Invalid log output, got: %s, want: %s", got, want)
}
}
@ -58,7 +67,7 @@ func TestMethodHandler(t *testing.T) {
}))
h = NewHandler(zerolog.New(out))(h)
h.ServeHTTP(nil, r)
if want, got := `{"method":"POST"}`+"\n", out.String(); want != got {
if want, got := `{"method":"POST"}`+"\n", decodeIfBinary(out); want != got {
t.Errorf("Invalid log output, got: %s, want: %s", got, want)
}
}
@ -75,7 +84,7 @@ func TestRequestHandler(t *testing.T) {
}))
h = NewHandler(zerolog.New(out))(h)
h.ServeHTTP(nil, r)
if want, got := `{"request":"POST /path?foo=bar"}`+"\n", out.String(); want != got {
if want, got := `{"request":"POST /path?foo=bar"}`+"\n", decodeIfBinary(out); want != got {
t.Errorf("Invalid log output, got: %s, want: %s", got, want)
}
}
@ -91,7 +100,7 @@ func TestRemoteAddrHandler(t *testing.T) {
}))
h = NewHandler(zerolog.New(out))(h)
h.ServeHTTP(nil, r)
if want, got := `{"ip":"1.2.3.4"}`+"\n", out.String(); want != got {
if want, got := `{"ip":"1.2.3.4"}`+"\n", decodeIfBinary(out); want != got {
t.Errorf("Invalid log output, got: %s, want: %s", got, want)
}
}
@ -107,7 +116,7 @@ func TestRemoteAddrHandlerIPv6(t *testing.T) {
}))
h = NewHandler(zerolog.New(out))(h)
h.ServeHTTP(nil, r)
if want, got := `{"ip":"2001:db8:a0b:12f0::1"}`+"\n", out.String(); want != got {
if want, got := `{"ip":"2001:db8:a0b:12f0::1"}`+"\n", decodeIfBinary(out); want != got {
t.Errorf("Invalid log output, got: %s, want: %s", got, want)
}
}
@ -125,7 +134,7 @@ func TestUserAgentHandler(t *testing.T) {
}))
h = NewHandler(zerolog.New(out))(h)
h.ServeHTTP(nil, r)
if want, got := `{"ua":"some user agent string"}`+"\n", out.String(); want != got {
if want, got := `{"ua":"some user agent string"}`+"\n", decodeIfBinary(out); want != got {
t.Errorf("Invalid log output, got: %s, want: %s", got, want)
}
}
@ -143,7 +152,7 @@ func TestRefererHandler(t *testing.T) {
}))
h = NewHandler(zerolog.New(out))(h)
h.ServeHTTP(nil, r)
if want, got := `{"referer":"http://foo.com/bar"}`+"\n", out.String(); want != got {
if want, got := `{"referer":"http://foo.com/bar"}`+"\n", decodeIfBinary(out); want != got {
t.Errorf("Invalid log output, got: %s, want: %s", got, want)
}
}
@ -165,7 +174,7 @@ func TestRequestIDHandler(t *testing.T) {
}
l := FromRequest(r)
l.Log().Msg("")
if want, got := fmt.Sprintf(`{"id":"%s"}`+"\n", id), out.String(); want != got {
if want, got := fmt.Sprintf(`{"id":"%s"}`+"\n", id), decodeIfBinary(out); want != got {
t.Errorf("Invalid log output, got: %s, want: %s", got, want)
}
}))
@ -185,7 +194,7 @@ func TestCombinedHandlers(t *testing.T) {
}))))
h = NewHandler(zerolog.New(out))(h)
h.ServeHTTP(nil, r)
if want, got := `{"method":"POST","request":"POST /path?foo=bar","url":"/path?foo=bar"}`+"\n", out.String(); want != got {
if want, got := `{"method":"POST","request":"POST /path?foo=bar","url":"/path?foo=bar"}`+"\n", decodeIfBinary(out); want != got {
t.Errorf("Invalid log output, got: %s, want: %s", got, want)
}
}

View File

@ -1,9 +1,9 @@
package zerolog
import (
"testing"
"bytes"
"io/ioutil"
"testing"
)
type LevelNameHook struct{}
@ -51,7 +51,7 @@ func TestHook(t *testing.T) {
out := &bytes.Buffer{}
log := New(out).Hook(levelNameHook)
log.Log().Msg("test message")
if got, want := out.String(), `{"level_name":"nolevel","message":"test message"}`+"\n"; got != want {
if got, want := decodeIfBinaryToString(out.Bytes()), `{"level_name":"nolevel","message":"test message"}`+"\n"; got != want {
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
}
})
@ -59,7 +59,7 @@ func TestHook(t *testing.T) {
out := &bytes.Buffer{}
log := New(out).Hook(levelNameHook)
log.Log().Msg("")
if got, want := out.String(), `{"level_name":"nolevel"}`+"\n"; got != want {
if got, want := decodeIfBinaryToString(out.Bytes()), `{"level_name":"nolevel"}`+"\n"; got != want {
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
}
})
@ -67,7 +67,7 @@ func TestHook(t *testing.T) {
out := &bytes.Buffer{}
log := New(out).Hook(levelNameHook)
log.Print("")
if got, want := out.String(), `{"level":"debug","level_name":"debug"}`+"\n"; got != want {
if got, want := decodeIfBinaryToString(out.Bytes()), `{"level":"debug","level_name":"debug"}`+"\n"; got != want {
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
}
})
@ -75,7 +75,7 @@ func TestHook(t *testing.T) {
out := &bytes.Buffer{}
log := New(out).Hook(levelNameHook)
log.Error().Msg("")
if got, want := out.String(), `{"level":"error","level_name":"error"}`+"\n"; got != want {
if got, want := decodeIfBinaryToString(out.Bytes()), `{"level":"error","level_name":"error"}`+"\n"; got != want {
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
}
})
@ -83,7 +83,7 @@ func TestHook(t *testing.T) {
out := &bytes.Buffer{}
log := New(out).Hook(copyHook)
log.Log().Msg("")
if got, want := out.String(), `{"copy_has_level":false,"copy_msg":""}`+"\n"; got != want {
if got, want := decodeIfBinaryToString(out.Bytes()), `{"copy_has_level":false,"copy_msg":""}`+"\n"; got != want {
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
}
})
@ -91,7 +91,7 @@ func TestHook(t *testing.T) {
out := &bytes.Buffer{}
log := New(out).Hook(copyHook)
log.Info().Msg("a message")
if got, want := out.String(), `{"level":"info","copy_has_level":true,"copy_level":"info","copy_msg":"a message","message":"a message"}`+"\n"; got != want {
if got, want := decodeIfBinaryToString(out.Bytes()), `{"level":"info","copy_has_level":true,"copy_level":"info","copy_msg":"a message","message":"a message"}`+"\n"; got != want {
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
}
})
@ -99,7 +99,7 @@ func TestHook(t *testing.T) {
out := &bytes.Buffer{}
log := New(out).Hook(levelNameHook).Hook(simpleHook)
log.Error().Msg("")
if got, want := out.String(), `{"level":"error","level_name":"error","has_level":true,"test":"logged"}`+"\n"; got != want {
if got, want := decodeIfBinaryToString(out.Bytes()), `{"level":"error","level_name":"error","has_level":true,"test":"logged"}`+"\n"; got != want {
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
}
})
@ -107,7 +107,7 @@ func TestHook(t *testing.T) {
out := &bytes.Buffer{}
log := New(out).Hook(levelNameHook).Hook(simpleHook)
log.Error().Msg("a message")
if got, want := out.String(), `{"level":"error","level_name":"error","has_level":true,"test":"logged","message":"a message"}`+"\n"; got != want {
if got, want := decodeIfBinaryToString(out.Bytes()), `{"level":"error","level_name":"error","has_level":true,"test":"logged","message":"a message"}`+"\n"; got != want {
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
}
})
@ -116,7 +116,7 @@ func TestHook(t *testing.T) {
out := &bytes.Buffer{}
log := New(ignored).Hook(levelNameHook).Output(out)
log.Error().Msg("")
if got, want := out.String(), `{"level":"error","level_name":"error"}`+"\n"; got != want {
if got, want := decodeIfBinaryToString(out.Bytes()), `{"level":"error","level_name":"error"}`+"\n"; got != want {
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
}
})
@ -125,7 +125,7 @@ func TestHook(t *testing.T) {
out := &bytes.Buffer{}
log := New(ignored).Output(out).Hook(levelNameHook)
log.Error().Msg("")
if got, want := out.String(), `{"level":"error","level_name":"error"}`+"\n"; got != want {
if got, want := decodeIfBinaryToString(out.Bytes()), `{"level":"error","level_name":"error"}`+"\n"; got != want {
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
}
})
@ -134,7 +134,7 @@ func TestHook(t *testing.T) {
out := &bytes.Buffer{}
log := New(ignored).Hook(levelNameHook).Hook(simpleHook).Output(out)
log.Error().Msg("")
if got, want := out.String(), `{"level":"error","level_name":"error","has_level":true,"test":"logged"}`+"\n"; got != want {
if got, want := decodeIfBinaryToString(out.Bytes()), `{"level":"error","level_name":"error","has_level":true,"test":"logged"}`+"\n"; got != want {
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
}
})
@ -143,7 +143,7 @@ func TestHook(t *testing.T) {
out := &bytes.Buffer{}
log := New(ignored).Output(out).Hook(levelNameHook).Hook(simpleHook)
log.Error().Msg("")
if got, want := out.String(), `{"level":"error","level_name":"error","has_level":true,"test":"logged"}`+"\n"; got != want {
if got, want := decodeIfBinaryToString(out.Bytes()), `{"level":"error","level_name":"error","has_level":true,"test":"logged"}`+"\n"; got != want {
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
}
})
@ -152,7 +152,7 @@ func TestHook(t *testing.T) {
out := &bytes.Buffer{}
log := New(ignored).Hook(levelNameHook).Output(out).Hook(simpleHook)
log.Error().Msg("")
if got, want := out.String(), `{"level":"error","level_name":"error","has_level":true,"test":"logged"}`+"\n"; got != want {
if got, want := decodeIfBinaryToString(out.Bytes()), `{"level":"error","level_name":"error","has_level":true,"test":"logged"}`+"\n"; got != want {
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
}
})
@ -160,7 +160,7 @@ func TestHook(t *testing.T) {
out := &bytes.Buffer{}
log := New(out).Hook(levelNameHook).With().Str("with", "pre").Logger()
log.Error().Msg("")
if got, want := out.String(), `{"level":"error","with":"pre","level_name":"error"}`+"\n"; got != want {
if got, want := decodeIfBinaryToString(out.Bytes()), `{"level":"error","with":"pre","level_name":"error"}`+"\n"; got != want {
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
}
})
@ -168,7 +168,7 @@ func TestHook(t *testing.T) {
out := &bytes.Buffer{}
log := New(out).With().Str("with", "post").Logger().Hook(levelNameHook)
log.Error().Msg("")
if got, want := out.String(), `{"level":"error","with":"post","level_name":"error"}`+"\n"; got != want {
if got, want := decodeIfBinaryToString(out.Bytes()), `{"level":"error","with":"post","level_name":"error"}`+"\n"; got != want {
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
}
})
@ -176,7 +176,7 @@ func TestHook(t *testing.T) {
out := &bytes.Buffer{}
log := New(out).Hook(levelNameHook).Hook(simpleHook).With().Str("with", "pre").Logger()
log.Error().Msg("")
if got, want := out.String(), `{"level":"error","with":"pre","level_name":"error","has_level":true,"test":"logged"}`+"\n"; got != want {
if got, want := decodeIfBinaryToString(out.Bytes()), `{"level":"error","with":"pre","level_name":"error","has_level":true,"test":"logged"}`+"\n"; got != want {
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
}
})
@ -184,7 +184,7 @@ func TestHook(t *testing.T) {
out := &bytes.Buffer{}
log := New(out).With().Str("with", "post").Logger().Hook(levelNameHook).Hook(simpleHook)
log.Error().Msg("")
if got, want := out.String(), `{"level":"error","with":"post","level_name":"error","has_level":true,"test":"logged"}`+"\n"; got != want {
if got, want := decodeIfBinaryToString(out.Bytes()), `{"level":"error","with":"post","level_name":"error","has_level":true,"test":"logged"}`+"\n"; got != want {
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
}
})
@ -192,7 +192,7 @@ func TestHook(t *testing.T) {
out := &bytes.Buffer{}
log := New(out).Hook(levelNameHook).With().Str("with", "mixed").Logger().Hook(simpleHook)
log.Error().Msg("")
if got, want := out.String(), `{"level":"error","with":"mixed","level_name":"error","has_level":true,"test":"logged"}`+"\n"; got != want {
if got, want := decodeIfBinaryToString(out.Bytes()), `{"level":"error","with":"mixed","level_name":"error","has_level":true,"test":"logged"}`+"\n"; got != want {
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
}
})
@ -200,7 +200,7 @@ func TestHook(t *testing.T) {
out := &bytes.Buffer{}
log := New(out)
log.Error().Msg("")
if got, want := out.String(), `{"level":"error"}`+"\n"; got != want {
if got, want := decodeIfBinaryToString(out.Bytes()), `{"level":"error"}`+"\n"; got != want {
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
}
})

74
internal/cbor/README.md Normal file
View File

@ -0,0 +1,74 @@
Reference:
CBOR Encoding is described in RFC7049 https://tools.ietf.org/html/rfc7049
Tests and benchmark:
```
sprint @ cbor>go test -v -benchmem -bench=.
=== RUN TestDecodeInteger
--- PASS: TestDecodeInteger (0.00s)
=== RUN TestDecodeString
--- PASS: TestDecodeString (0.00s)
=== RUN TestDecodeArray
--- PASS: TestDecodeArray (0.00s)
=== RUN TestDecodeMap
--- PASS: TestDecodeMap (0.00s)
=== RUN TestDecodeBool
--- PASS: TestDecodeBool (0.00s)
=== RUN TestDecodeFloat
--- PASS: TestDecodeFloat (0.00s)
=== RUN TestDecodeTimestamp
--- PASS: TestDecodeTimestamp (0.00s)
=== RUN TestDecodeCbor2Json
--- PASS: TestDecodeCbor2Json (0.00s)
=== RUN TestAppendString
--- PASS: TestAppendString (0.00s)
=== RUN TestAppendBytes
--- PASS: TestAppendBytes (0.00s)
=== RUN TestAppendTimeNow
--- PASS: TestAppendTimeNow (0.00s)
=== RUN TestAppendTimePastPresentInteger
--- PASS: TestAppendTimePastPresentInteger (0.00s)
=== RUN TestAppendTimePastPresentFloat
--- PASS: TestAppendTimePastPresentFloat (0.00s)
=== RUN TestAppendNull
--- PASS: TestAppendNull (0.00s)
=== RUN TestAppendBool
--- PASS: TestAppendBool (0.00s)
=== RUN TestAppendBoolArray
--- PASS: TestAppendBoolArray (0.00s)
=== RUN TestAppendInt
--- PASS: TestAppendInt (0.00s)
=== RUN TestAppendIntArray
--- PASS: TestAppendIntArray (0.00s)
=== RUN TestAppendFloat32
--- PASS: TestAppendFloat32 (0.00s)
goos: linux
goarch: amd64
pkg: github.com/toravir/zerolog/internal/cbor
BenchmarkAppendString/MultiBytesLast-4 30000000 43.3 ns/op 0 B/op 0 allocs/op
BenchmarkAppendString/NoEncoding-4 30000000 48.2 ns/op 0 B/op 0 allocs/op
BenchmarkAppendString/EncodingFirst-4 30000000 48.2 ns/op 0 B/op 0 allocs/op
BenchmarkAppendString/EncodingMiddle-4 30000000 41.7 ns/op 0 B/op 0 allocs/op
BenchmarkAppendString/EncodingLast-4 30000000 51.8 ns/op 0 B/op 0 allocs/op
BenchmarkAppendString/MultiBytesFirst-4 50000000 38.0 ns/op 0 B/op 0 allocs/op
BenchmarkAppendString/MultiBytesMiddle-4 50000000 38.0 ns/op 0 B/op 0 allocs/op
BenchmarkAppendTime/Integer-4 50000000 39.6 ns/op 0 B/op 0 allocs/op
BenchmarkAppendTime/Float-4 30000000 56.1 ns/op 0 B/op 0 allocs/op
BenchmarkAppendInt/uint8-4 50000000 29.1 ns/op 0 B/op 0 allocs/op
BenchmarkAppendInt/uint16-4 50000000 30.3 ns/op 0 B/op 0 allocs/op
BenchmarkAppendInt/uint32-4 50000000 37.1 ns/op 0 B/op 0 allocs/op
BenchmarkAppendInt/int8-4 100000000 21.5 ns/op 0 B/op 0 allocs/op
BenchmarkAppendInt/int16-4 50000000 25.8 ns/op 0 B/op 0 allocs/op
BenchmarkAppendInt/int32-4 50000000 26.7 ns/op 0 B/op 0 allocs/op
BenchmarkAppendInt/int-Positive-4 100000000 21.5 ns/op 0 B/op 0 allocs/op
BenchmarkAppendInt/int-Negative-4 100000000 20.7 ns/op 0 B/op 0 allocs/op
BenchmarkAppendInt/uint64-4 50000000 36.7 ns/op 0 B/op 0 allocs/op
BenchmarkAppendInt/int64-4 30000000 39.6 ns/op 0 B/op 0 allocs/op
BenchmarkAppendFloat/Float32-4 50000000 23.9 ns/op 0 B/op 0 allocs/op
BenchmarkAppendFloat/Float64-4 50000000 32.8 ns/op 0 B/op 0 allocs/op
PASS
ok github.com/toravir/zerolog/internal/cbor 34.969s
sprint @ cbor>
```

43
internal/cbor/base.go Normal file
View File

@ -0,0 +1,43 @@
package cbor
// AppendKey adds a key (string) to the binary encoded log message
func AppendKey(dst []byte, key string) []byte {
if len(dst) < 1 {
dst = AppendBeginMarker(dst)
}
return AppendString(dst, key)
}
// AppendError adds the Error to the log message if error is NOT nil
func AppendError(dst []byte, err error) []byte {
if err == nil {
return append(dst, `null`...)
}
return AppendString(dst, err.Error())
}
// AppendErrors when given an array of errors,
// adds them to the log message if a specific error is nil, then
// Nil is added, or else the error string is added.
func AppendErrors(dst []byte, errs []error) []byte {
if len(errs) == 0 {
return AppendArrayEnd(AppendArrayStart(dst))
}
dst = AppendArrayStart(dst)
if errs[0] != nil {
dst = AppendString(dst, errs[0].Error())
} else {
dst = AppendNull(dst)
}
if len(errs) > 1 {
for _, err := range errs[1:] {
if err == nil {
dst = AppendNull(dst)
continue
}
dst = AppendString(dst, err.Error())
}
}
dst = AppendArrayEnd(dst)
return dst
}

91
internal/cbor/cbor.go Normal file
View File

@ -0,0 +1,91 @@
// Package cbor provides primitives for storing different data
// in the CBOR (binary) format. CBOR is defined in RFC7049.
package cbor
import "time"
const (
majorOffset = 5
additionalMax = 23
//Non Values
additionalTypeBoolFalse byte = 20
additionalTypeBoolTrue byte = 21
additionalTypeNull byte = 22
//Integer (+ve and -ve) Sub-types
additionalTypeIntUint8 byte = 24
additionalTypeIntUint16 byte = 25
additionalTypeIntUint32 byte = 26
additionalTypeIntUint64 byte = 27
//Float Sub-types
additionalTypeFloat16 byte = 25
additionalTypeFloat32 byte = 26
additionalTypeFloat64 byte = 27
additionalTypeBreak byte = 31
//Tag Sub-types
additionalTypeTimestamp byte = 01
additionalTypeEmbeddedJSON byte = 31
additionalTypeTagHexString uint16 = 262
//Unspecified number of elements
additionalTypeInfiniteCount byte = 31
)
const (
majorTypeUnsignedInt byte = iota << majorOffset // Major type 0
majorTypeNegativeInt // Major type 1
majorTypeByteString // Major type 2
majorTypeUtf8String // Major type 3
majorTypeArray // Major type 4
majorTypeMap // Major type 5
majorTypeTags // Major type 6
majorTypeSimpleAndFloat // Major type 7
)
const (
maskOutAdditionalType byte = (7 << majorOffset)
maskOutMajorType byte = 31
)
const (
float32Nan = "\xfa\x7f\xc0\x00\x00"
float32PosInfinity = "\xfa\x7f\x80\x00\x00"
float32NegInfinity = "\xfa\xff\x80\x00\x00"
float64Nan = "\xfb\x7f\xf8\x00\x00\x00\x00\x00\x00"
float64PosInfinity = "\xfb\x7f\xf0\x00\x00\x00\x00\x00\x00"
float64NegInfinity = "\xfb\xff\xf0\x00\x00\x00\x00\x00\x00"
)
// IntegerTimeFieldFormat indicates the format of timestamp decoded
// from an integer (time in seconds).
var IntegerTimeFieldFormat = time.RFC3339
// NanoTimeFieldFormat indicates the format of timestamp decoded
// from a float value (time in seconds and nano seconds).
var NanoTimeFieldFormat = time.RFC3339Nano
func appendCborTypePrefix(dst []byte, major byte, number uint64) []byte {
byteCount := 8
var minor byte
switch {
case number < 256:
byteCount = 1
minor = additionalTypeIntUint8
case number < 65536:
byteCount = 2
minor = additionalTypeIntUint16
case number < 4294967296:
byteCount = 4
minor = additionalTypeIntUint32
default:
byteCount = 8
minor = additionalTypeIntUint64
}
dst = append(dst, byte(major|minor))
byteCount--
for ; byteCount >= 0; byteCount-- {
dst = append(dst, byte(number>>(uint(byteCount)*8)))
}
return dst
}

548
internal/cbor/decoder.go Normal file
View File

@ -0,0 +1,548 @@
package cbor
import (
"bytes"
"fmt"
"io"
"math"
"strconv"
"time"
"unicode/utf8"
)
var decodeTimeZone *time.Location
const hexTable = "0123456789abcdef"
func decodeIntAdditonalType(src []byte, minor byte) (int64, uint, error) {
val := int64(0)
bytesRead := 0
if minor <= 23 {
val = int64(minor)
bytesRead = 0
} else {
switch minor {
case additionalTypeIntUint8:
bytesRead = 1
case additionalTypeIntUint16:
bytesRead = 2
case additionalTypeIntUint32:
bytesRead = 4
case additionalTypeIntUint64:
bytesRead = 8
default:
return 0, 0, fmt.Errorf("Invalid Additional Type: %d in decodeInteger (expected <28)", minor)
}
for i := 0; i < bytesRead; i++ {
val = val * 256
val += int64(src[i])
}
}
return val, uint(bytesRead), nil
}
func decodeInteger(src []byte) (int64, uint, error) {
major := src[0] & maskOutAdditionalType
minor := src[0] & maskOutMajorType
if major != majorTypeUnsignedInt && major != majorTypeNegativeInt {
return 0, 0, fmt.Errorf("Major type is: %d in decodeInteger!! (expected 0 or 1)", major)
}
val, bytesRead, err := decodeIntAdditonalType(src[1:], minor)
if err != nil {
return 0, 0, err
}
if major == 0 {
return val, 1 + bytesRead, nil
}
return (-1 - val), 1 + bytesRead, nil
}
func decodeFloat(src []byte) (float64, uint, error) {
major := (src[0] & maskOutAdditionalType)
minor := src[0] & maskOutMajorType
if major != majorTypeSimpleAndFloat {
return 0, 0, fmt.Errorf("Incorrect Major type is: %d in decodeFloat", major)
}
switch minor {
case additionalTypeFloat16:
return 0, 0, fmt.Errorf("float16 is not suppported in decodeFloat")
case additionalTypeFloat32:
switch string(src[1:5]) {
case float32Nan:
return math.NaN(), 5, nil
case float32PosInfinity:
return math.Inf(0), 5, nil
case float32NegInfinity:
return math.Inf(-1), 5, nil
}
n := uint32(0)
for i := 0; i < 4; i++ {
n = n * 256
n += uint32(src[i+1])
}
val := math.Float32frombits(n)
return float64(val), 5, nil
case additionalTypeFloat64:
switch string(src[1:9]) {
case float64Nan:
return math.NaN(), 9, nil
case float64PosInfinity:
return math.Inf(0), 9, nil
case float64NegInfinity:
return math.Inf(-1), 9, nil
}
n := uint64(0)
for i := 0; i < 8; i++ {
n = n * 256
n += uint64(src[i+1])
}
val := math.Float64frombits(n)
return val, 9, nil
}
return 0, 0, fmt.Errorf("Invalid Additional Type: %d in decodeFloat", minor)
}
func decodeStringComplex(dst []byte, s string, pos uint) []byte {
i := int(pos)
const hex = "0123456789abcdef"
start := 0
for i < len(s) {
b := s[i]
if b >= utf8.RuneSelf {
r, size := utf8.DecodeRuneInString(s[i:])
if r == utf8.RuneError && size == 1 {
// In case of error, first append previous simple characters to
// the byte slice if any and append a replacement character code
// in place of the invalid sequence.
if start < i {
dst = append(dst, s[start:i]...)
}
dst = append(dst, `\ufffd`...)
i += size
start = i
continue
}
i += size
continue
}
if b >= 0x20 && b <= 0x7e && b != '\\' && b != '"' {
i++
continue
}
// We encountered a character that needs to be encoded.
// Let's append the previous simple characters to the byte slice
// and switch our operation to read and encode the remainder
// characters byte-by-byte.
if start < i {
dst = append(dst, s[start:i]...)
}
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:
dst = append(dst, '\\', 'u', '0', '0', hex[b>>4], hex[b&0xF])
}
i++
start = i
}
if start < len(s) {
dst = append(dst, s[start:]...)
}
return dst
}
func decodeString(src []byte, noQuotes bool) ([]byte, uint, error) {
major := src[0] & maskOutAdditionalType
minor := src[0] & maskOutMajorType
if major != majorTypeByteString {
return []byte{}, 0, fmt.Errorf("Major type is: %d in decodeString", major)
}
result := []byte{'"'}
if noQuotes {
result = []byte{}
}
length, bytesRead, err := decodeIntAdditonalType(src[1:], minor)
if err != nil {
return []byte{}, 0, err
}
bytesRead++
st := bytesRead
len := uint(length)
bytesRead += len
result = append(result, src[st:st+len]...)
if noQuotes {
return result, bytesRead, nil
}
return append(result, '"'), bytesRead, nil
}
func decodeUTF8String(src []byte) ([]byte, uint, error) {
major := src[0] & maskOutAdditionalType
minor := src[0] & maskOutMajorType
if major != majorTypeUtf8String {
return []byte{}, 0, fmt.Errorf("Major type is: %d in decodeUTF8String", major)
}
result := []byte{'"'}
length, bytesRead, err := decodeIntAdditonalType(src[1:], minor)
if err != nil {
return []byte{}, 0, err
}
bytesRead++
st := bytesRead
len := uint(length)
bytesRead += len
for i := st; i < bytesRead; i++ {
// Check if the character needs encoding. Control characters, slashes,
// and the double quote need json encoding. Bytes above the ascii
// boundary needs utf8 encoding.
if src[i] < 0x20 || src[i] > 0x7e || src[i] == '\\' || src[i] == '"' {
// We encountered a character that needs to be encoded. Switch
// to complex version of the algorithm.
dst := []byte{'"'}
dst = decodeStringComplex(dst, string(src[st:st+len]), i-st)
return append(dst, '"'), bytesRead, nil
}
}
// The string has no need for encoding an therefore is directly
// appended to the byte slice.
result = append(result, src[st:st+len]...)
return append(result, '"'), bytesRead, nil
}
func array2Json(src []byte, dst io.Writer) (uint, error) {
dst.Write([]byte{'['})
major := (src[0] & maskOutAdditionalType)
minor := src[0] & maskOutMajorType
if major != majorTypeArray {
return 0, fmt.Errorf("Major type is: %d in array2Json", major)
}
len := 0
bytesRead := uint(0)
unSpecifiedCount := false
if minor == additionalTypeInfiniteCount {
unSpecifiedCount = true
bytesRead = 1
} else {
var length int64
var err error
length, bytesRead, err = decodeIntAdditonalType(src[1:], minor)
if err != nil {
fmt.Println("Error!!!")
return 0, err
}
len = int(length)
bytesRead++
}
curPos := bytesRead
for i := 0; unSpecifiedCount || i < len; i++ {
bc, err := Cbor2JsonOneObject(src[curPos:], dst)
if err != nil {
if src[curPos] == byte(majorTypeSimpleAndFloat|additionalTypeBreak) {
bytesRead++
break
}
return 0, err
}
curPos += bc
bytesRead += bc
if unSpecifiedCount {
if src[curPos] == byte(majorTypeSimpleAndFloat|additionalTypeBreak) {
bytesRead++
break
}
dst.Write([]byte{','})
} else if i+1 < len {
dst.Write([]byte{','})
}
}
dst.Write([]byte{']'})
return bytesRead, nil
}
func map2Json(src []byte, dst io.Writer) (uint, error) {
major := (src[0] & maskOutAdditionalType)
minor := src[0] & maskOutMajorType
if major != majorTypeMap {
return 0, fmt.Errorf("Major type is: %d in map2Json", major)
}
len := 0
bytesRead := uint(0)
unSpecifiedCount := false
if minor == additionalTypeInfiniteCount {
unSpecifiedCount = true
bytesRead = 1
} else {
var length int64
var err error
length, bytesRead, err = decodeIntAdditonalType(src[1:], minor)
if err != nil {
fmt.Println("Error!!!")
return 0, err
}
len = int(length)
bytesRead++
}
if len%2 == 1 {
return 0, fmt.Errorf("Invalid Length of map %d - has to be even", len)
}
dst.Write([]byte{'{'})
curPos := bytesRead
for i := 0; unSpecifiedCount || i < len; i++ {
bc, err := Cbor2JsonOneObject(src[curPos:], dst)
if err != nil {
//We hit the BREAK
if src[curPos] == byte(majorTypeSimpleAndFloat|additionalTypeBreak) {
bytesRead++
break
}
return 0, err
}
curPos += bc
bytesRead += bc
if i%2 == 0 {
//Even position values are keys
dst.Write([]byte{':'})
} else {
if unSpecifiedCount {
if src[curPos] == byte(majorTypeSimpleAndFloat|additionalTypeBreak) {
bytesRead++
break
}
dst.Write([]byte{','})
} else if i+1 < len {
dst.Write([]byte{','})
}
}
}
dst.Write([]byte{'}'})
return bytesRead, nil
}
func decodeTagData(src []byte) ([]byte, uint, error) {
major := (src[0] & maskOutAdditionalType)
minor := src[0] & maskOutMajorType
if major != majorTypeTags {
return nil, 0, fmt.Errorf("Major type is: %d in decodeTagData", major)
}
if minor == additionalTypeTimestamp {
tsMajor := src[1] & maskOutAdditionalType
if tsMajor == majorTypeUnsignedInt || tsMajor == majorTypeNegativeInt {
n, bc, err := decodeInteger(src[1:])
if err != nil {
return []byte{}, 0, err
}
t := time.Unix(n, 0)
if decodeTimeZone != nil {
t = t.In(decodeTimeZone)
} else {
t = t.In(time.UTC)
}
tsb := []byte{}
tsb = append(tsb, '"')
tsb = t.AppendFormat(tsb, IntegerTimeFieldFormat)
tsb = append(tsb, '"')
return tsb, 1 + bc, nil
} else if tsMajor == majorTypeSimpleAndFloat {
n, bc, err := decodeFloat(src[1:])
if err != nil {
return []byte{}, 0, err
}
secs := int64(n)
n -= float64(secs)
n *= float64(1e9)
t := time.Unix(secs, int64(n))
if decodeTimeZone != nil {
t = t.In(decodeTimeZone)
} else {
t = t.In(time.UTC)
}
tsb := []byte{}
tsb = append(tsb, '"')
tsb = t.AppendFormat(tsb, NanoTimeFieldFormat)
tsb = append(tsb, '"')
return tsb, 1 + bc, nil
} else {
return nil, 0, fmt.Errorf("TS format is neigther int nor float: %d", tsMajor)
}
} else if minor == additionalTypeEmbeddedJSON {
dataMajor := src[1] & maskOutAdditionalType
if dataMajor == majorTypeByteString {
emb, bc, err := decodeString(src[1:], true)
if err != nil {
return nil, 0, err
}
return emb, 1 + bc, nil
}
return nil, 0, fmt.Errorf("Unsupported embedded Type: %d in decodeEmbeddedJSON", dataMajor)
} else if minor == additionalTypeIntUint16 {
val,_,_ := decodeIntAdditonalType(src[1:], minor)
if uint16(val) == additionalTypeTagHexString {
emb, bc, _ := decodeString(src[3:], true)
dst := []byte{'"'}
for _, v := range emb {
dst = append(dst, hexTable[v>>4], hexTable[v&0x0f])
}
return append(dst, '"'), 3+bc, nil
}
}
return nil, 0, fmt.Errorf("Unsupported Additional Type: %d in decodeTagData", minor)
}
func decodeSimpleFloat(src []byte) ([]byte, uint, error) {
major := (src[0] & maskOutAdditionalType)
minor := src[0] & maskOutMajorType
if major != majorTypeSimpleAndFloat {
return nil, 0, fmt.Errorf("Major type is: %d in decodeSimpleFloat", major)
}
switch minor {
case additionalTypeBoolTrue:
return []byte("true"), 1, nil
case additionalTypeBoolFalse:
return []byte("false"), 1, nil
case additionalTypeNull:
return []byte("null"), 1, nil
case additionalTypeFloat16:
fallthrough
case additionalTypeFloat32:
fallthrough
case additionalTypeFloat64:
v, bc, err := decodeFloat(src)
if err != nil {
return nil, 0, err
}
ba := []byte{}
switch {
case math.IsNaN(v):
return []byte("\"NaN\""), bc, nil
case math.IsInf(v, 1):
return []byte("\"+Inf\""), bc, nil
case math.IsInf(v, -1):
return []byte("\"-Inf\""), bc, nil
}
if bc == 5 {
ba = strconv.AppendFloat(ba, v, 'f', -1, 32)
} else {
ba = strconv.AppendFloat(ba, v, 'f', -1, 64)
}
return ba, bc, nil
default:
return nil, 0, fmt.Errorf("Invalid Additional Type: %d in decodeSimpleFloat", minor)
}
}
// Cbor2JsonOneObject takes in byte array and decodes ONE CBOR Object
// usually a MAP. Use this when only ONE CBOR object needs decoding.
// Decoded string is written to the dst.
// Returns the bytes decoded and if any error was encountered.
func Cbor2JsonOneObject(src []byte, dst io.Writer) (uint, error) {
var err error
major := (src[0] & maskOutAdditionalType)
bc := uint(0)
var s []byte
switch major {
case majorTypeUnsignedInt:
fallthrough
case majorTypeNegativeInt:
var n int64
n, bc, err = decodeInteger(src)
dst.Write([]byte(strconv.Itoa(int(n))))
case majorTypeByteString:
s, bc, err = decodeString(src, false)
dst.Write(s)
case majorTypeUtf8String:
s, bc, err = decodeUTF8String(src)
dst.Write(s)
case majorTypeArray:
bc, err = array2Json(src, dst)
case majorTypeMap:
bc, err = map2Json(src, dst)
case majorTypeTags:
s, bc, err = decodeTagData(src)
dst.Write(s)
case majorTypeSimpleAndFloat:
s, bc, err = decodeSimpleFloat(src)
dst.Write(s)
}
return bc, err
}
// Cbor2JsonManyObjects decodes all the CBOR Objects present in the
// source byte array. It keeps on decoding until it runs out of bytes.
// Decoded string is written to the dst. At the end of every CBOR Object
// newline is written to the output stream.
// Returns the number of bytes decoded and if any error was encountered.
func Cbor2JsonManyObjects(src []byte, dst io.Writer) (uint, error) {
curPos := uint(0)
totalBytes := uint(len(src))
for curPos < totalBytes {
bc, err := Cbor2JsonOneObject(src[curPos:], dst)
if err != nil {
return curPos, err
}
dst.Write([]byte("\n"))
curPos += bc
}
return curPos, nil
}
// Detect if the bytes to be printed is Binary or not.
func binaryFmt(p []byte) bool {
if len(p) > 0 && p[0] > 0x7F {
return true
}
return false
}
// DecodeIfBinaryToString converts a binary formatted log msg to a
// JSON formatted String Log message - suitable for printing to Console/Syslog.
func DecodeIfBinaryToString(in []byte) string {
if binaryFmt(in) {
var b bytes.Buffer
Cbor2JsonManyObjects(in, &b)
return b.String()
}
return string(in)
}
// DecodeObjectToStr checks if the input is a binary format, if so,
// it will decode a single Object and return the decoded string.
func DecodeObjectToStr(in []byte) string {
if binaryFmt(in) {
var b bytes.Buffer
Cbor2JsonOneObject(in, &b)
return b.String()
}
return string(in)
}
// DecodeIfBinaryToBytes checks if the input is a binary format, if so,
// it will decode all Objects and return the decoded string as byte array.
func DecodeIfBinaryToBytes(in []byte) []byte {
if binaryFmt(in) {
var b bytes.Buffer
Cbor2JsonManyObjects(in, &b)
return b.Bytes()
}
return in
}

View File

@ -0,0 +1,188 @@
package cbor
import (
"bytes"
"encoding/hex"
"testing"
"time"
)
func TestDecodeInteger(t *testing.T) {
for _, tc := range integerTestCases {
gotv, gotc, err := decodeInteger([]byte(tc.binary))
if gotv != int64(tc.val) || int(gotc) != len(tc.binary) || err != nil {
t.Errorf("decodeInteger(0x%s)=0x%d, want: 0x%d",
hex.EncodeToString([]byte(tc.binary)), gotv, tc.val)
}
}
}
func TestDecodeString(t *testing.T) {
for _, tt := range encodeStringTests {
got, _, err := decodeUTF8String([]byte(tt.binary))
if err != nil {
t.Errorf("Got Error for the case: %s", hex.EncodeToString([]byte(tt.binary)))
}
if string(got) != "\""+tt.json+"\"" {
t.Errorf("DecodeString(0x%s)=%s, want:\"%s\"\n", hex.EncodeToString([]byte(tt.binary)), string(got),
hex.EncodeToString([]byte(tt.json)))
}
}
}
func TestDecodeArray(t *testing.T) {
for _, tc := range integerArrayTestCases {
buf := bytes.NewBuffer([]byte{})
_, err := array2Json([]byte(tc.binary), buf)
if err != nil {
panic(err)
}
if buf.String() != tc.json {
t.Errorf("array2Json(0x%s)=%s, want: %s", hex.EncodeToString([]byte(tc.binary)), buf.String(), tc.json)
}
}
//Unspecified Length Array
var infiniteArrayTestCases = []struct {
in string
out string
}{
{"\x9f\x20\x00\x18\xc8\x14\xff", "[-1,0,200,20]"},
{"\x9f\x38\xc7\x29\x18\xc8\x19\x01\x90\xff", "[-200,-10,200,400]"},
{"\x9f\x01\x02\x03\xff", "[1,2,3]"},
{"\x9f\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x18\x18\x19\xff",
"[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25]"},
}
for _, tc := range infiniteArrayTestCases {
buf := bytes.NewBuffer([]byte{})
_, err := array2Json([]byte(tc.in), buf)
if err != nil {
panic(err)
}
if buf.String() != tc.out {
t.Errorf("array2Json(0x%s)=%s, want: %s", hex.EncodeToString([]byte(tc.out)), buf.String(), tc.out)
}
}
for _, tc := range booleanArrayTestCases {
buf := bytes.NewBuffer([]byte{})
_, err := array2Json([]byte(tc.binary), buf)
if err != nil {
t.Errorf("array2Json(0x%s) errored out: %s", hex.EncodeToString([]byte(tc.binary)), err.Error())
}
if buf.String() != tc.json {
t.Errorf("array2Json(0x%s)=%s, want: %s", hex.EncodeToString([]byte(tc.binary)), buf.String(), tc.json)
}
}
//TODO add cases for arrays of other types
}
var infiniteMapDecodeTestCases = []struct {
bin []byte
json string
}{
{[]byte("\xbf\x64IETF\x20\xff"), "{\"IETF\":-1}"},
{[]byte("\xbf\x65Array\x84\x20\x00\x18\xc8\x14\xff"), "{\"Array\":[-1,0,200,20]}"},
}
var mapDecodeTestCases = []struct {
bin []byte
json string
}{
{[]byte("\xa2\x64IETF\x20"), "{\"IETF\":-1}"},
{[]byte("\xa2\x65Array\x84\x20\x00\x18\xc8\x14"), "{\"Array\":[-1,0,200,20]}"},
}
func TestDecodeMap(t *testing.T) {
for _, tc := range mapDecodeTestCases {
buf := bytes.NewBuffer([]byte{})
_, err := map2Json(tc.bin, buf)
if err != nil {
t.Errorf("map2Json(0x%s) returned error", err)
}
if buf.String() != tc.json {
t.Errorf("map2Json(0x%s)=%s, want: %s", hex.EncodeToString(tc.bin), buf.String(), tc.json)
}
}
for _, tc := range infiniteMapDecodeTestCases {
buf := bytes.NewBuffer([]byte{})
_, err := map2Json(tc.bin, buf)
if err != nil {
t.Errorf("map2Json(0x%s) returned error", err)
}
if buf.String() != tc.json {
t.Errorf("map2Json(0x%s)=%s, want: %s", hex.EncodeToString(tc.bin), buf.String(), tc.json)
}
}
}
func TestDecodeBool(t *testing.T) {
for _, tc := range booleanTestCases {
got, _, err := decodeSimpleFloat([]byte(tc.binary))
if err != nil {
t.Errorf("decodeSimpleFloat(0x%s) errored %s", hex.EncodeToString([]byte(tc.binary)), err.Error())
}
if string(got) != tc.json {
t.Errorf("decodeSimpleFloat(0x%s)=%s, want:%s", hex.EncodeToString([]byte(tc.binary)), string(got), tc.json)
}
}
}
func TestDecodeFloat(t *testing.T) {
for _, tc := range float32TestCases {
got, _, err := decodeFloat([]byte(tc.binary))
if err != nil {
t.Errorf("decodeFloat(0x%s) returned error: %s", hex.EncodeToString([]byte(tc.binary)), err.Error())
}
if got != float64(tc.val) {
t.Errorf("decodeFloat(0x%s)=%f, want:%f", hex.EncodeToString([]byte(tc.binary)), got, tc.val)
}
}
}
func TestDecodeTimestamp(t *testing.T) {
decodeTimeZone, _ = time.LoadLocation("UTC")
for _, tc := range timeIntegerTestcases {
tm, _, err := decodeTagData([]byte(tc.binary))
if err != nil {
t.Errorf("decodeTagData(0x%s) returned error: %s", hex.EncodeToString([]byte(tc.binary)), err.Error())
}
if string(tm) != "\""+tc.rfcStr+"\"" {
t.Errorf("decodeFloat(0x%s)=%s, want:%s", hex.EncodeToString([]byte(tc.binary)), tm, tc.rfcStr)
}
}
for _, tc := range timeFloatTestcases {
tm, _, err := decodeTagData([]byte(tc.out))
if err != nil {
t.Errorf("decodeTagData(0x%s) returned error: %s", hex.EncodeToString([]byte(tc.out)), err.Error())
}
//Since we convert to float and back - it may be slightly off - so
//we cannot check for exact equality instead, we'll check it is
//very close to each other Less than a Microsecond (lets not yet do nanosec)
got, _ := time.Parse(string(tm), string(tm))
want, _ := time.Parse(tc.rfcStr, tc.rfcStr)
if got.Sub(want) > time.Microsecond {
t.Errorf("decodeFloat(0x%s)=%s, want:%s", hex.EncodeToString([]byte(tc.out)), tm, tc.rfcStr)
}
}
}
var compositeCborTestCases = []struct {
binary []byte
json string
}{
{[]byte("\xbf\x64IETF\x20\x65Array\x9f\x20\x00\x18\xc8\x14\xff\xff"), "{\"IETF\":-1,\"Array\":[-1,0,200,20]}\n"},
{[]byte("\xbf\x64IETF\x64YES!\x65Array\x9f\x20\x00\x18\xc8\x14\xff\xff"), "{\"IETF\":\"YES!\",\"Array\":[-1,0,200,20]}\n"},
}
func TestDecodeCbor2Json(t *testing.T) {
for _, tc := range compositeCborTestCases {
buf := bytes.NewBuffer([]byte{})
_, err := Cbor2JsonManyObjects(tc.binary, buf)
if err != nil {
t.Errorf("cbor2JsonManyObjects(0x%s) returned error", err)
}
if buf.String() != tc.json {
t.Errorf("cbor2JsonManyObjects(0x%s)=%s, want: %s", hex.EncodeToString(tc.binary), buf.String(), tc.json)
}
}
}

63
internal/cbor/string.go Normal file
View File

@ -0,0 +1,63 @@
package cbor
// AppendStrings encodes and adds an array of strings to the dst byte array.
func AppendStrings(dst []byte, vals []string) []byte {
major := majorTypeArray
l := len(vals)
if l <= additionalMax {
lb := byte(l)
dst = append(dst, byte(major|lb))
} else {
dst = appendCborTypePrefix(dst, major, uint64(l))
}
for _, v := range vals {
dst = AppendString(dst, v)
}
return dst
}
// AppendString encodes and adds a string to the dst byte array.
func AppendString(dst []byte, s string) []byte {
major := majorTypeUtf8String
l := len(s)
if l <= additionalMax {
lb := byte(l)
dst = append(dst, byte(major|lb))
} else {
dst = appendCborTypePrefix(dst, majorTypeUtf8String, uint64(l))
}
return append(dst, s...)
}
// AppendBytes encodes and adds an array of bytes to the dst byte array.
func AppendBytes(dst, s []byte) []byte {
major := majorTypeByteString
l := len(s)
if l <= additionalMax {
lb := byte(l)
dst = append(dst, byte(major|lb))
} else {
dst = appendCborTypePrefix(dst, major, uint64(l))
}
return append(dst, s...)
}
// AppendEmbeddedJSON adds a tag and embeds input JSON as such.
func AppendEmbeddedJSON(dst, s []byte) []byte {
major := majorTypeTags
minor := additionalTypeEmbeddedJSON
dst = append(dst, byte(major|minor))
major = majorTypeByteString
l := len(s)
if l <= additionalMax {
lb := byte(l)
dst = append(dst, byte(major|lb))
} else {
dst = appendCborTypePrefix(dst, major, uint64(l))
}
return append(dst, s...)
}

View File

@ -0,0 +1,118 @@
package cbor
import (
"bytes"
"testing"
)
var encodeStringTests = []struct {
plain string
binary string
json string //begin and end quotes are implied
}{
{"", "\x60", ""},
{"\\", "\x61\x5c", "\\\\"},
{"\x00", "\x61\x00", "\\u0000"},
{"\x01", "\x61\x01", "\\u0001"},
{"\x02", "\x61\x02", "\\u0002"},
{"\x03", "\x61\x03", "\\u0003"},
{"\x04", "\x61\x04", "\\u0004"},
{"*", "\x61*", "*"},
{"a", "\x61a", "a"},
{"IETF", "\x64IETF", "IETF"},
{"abcdefghijklmnopqrstuvwxyzABCD", "\x78\x1eabcdefghijklmnopqrstuvwxyzABCD", "abcdefghijklmnopqrstuvwxyzABCD"},
{"<------------------------------------ This is a 100 character string ----------------------------->" +
"<------------------------------------ This is a 100 character string ----------------------------->" +
"<------------------------------------ This is a 100 character string ----------------------------->",
"\x79\x01\x2c<------------------------------------ This is a 100 character string ----------------------------->" +
"<------------------------------------ This is a 100 character string ----------------------------->" +
"<------------------------------------ This is a 100 character string ----------------------------->",
"<------------------------------------ This is a 100 character string ----------------------------->" +
"<------------------------------------ This is a 100 character string ----------------------------->" +
"<------------------------------------ This is a 100 character string ----------------------------->"},
{"emoji \u2764\ufe0f!", "\x6demoji ❤️!", "emoji \u2764\ufe0f!"},
}
var encodeByteTests = []struct {
plain []byte
binary string
}{
{[]byte{}, "\x40"},
{[]byte("\\"), "\x41\x5c"},
{[]byte("\x00"), "\x41\x00"},
{[]byte("\x01"), "\x41\x01"},
{[]byte("\x02"), "\x41\x02"},
{[]byte("\x03"), "\x41\x03"},
{[]byte("\x04"), "\x41\x04"},
{[]byte("*"), "\x41*"},
{[]byte("a"), "\x41a"},
{[]byte("IETF"), "\x44IETF"},
{[]byte("abcdefghijklmnopqrstuvwxyzABCD"), "\x58\x1eabcdefghijklmnopqrstuvwxyzABCD"},
{[]byte("<------------------------------------ This is a 100 character string ----------------------------->" +
"<------------------------------------ This is a 100 character string ----------------------------->" +
"<------------------------------------ This is a 100 character string ----------------------------->"),
"\x59\x01\x2c<------------------------------------ This is a 100 character string ----------------------------->" +
"<------------------------------------ This is a 100 character string ----------------------------->" +
"<------------------------------------ This is a 100 character string ----------------------------->"},
{[]byte("emoji \u2764\ufe0f!"), "\x4demoji ❤️!"},
}
func TestAppendString(t *testing.T) {
for _, tt := range encodeStringTests {
b := AppendString([]byte{}, tt.plain)
if got, want := string(b), tt.binary; got != want {
t.Errorf("appendString(%q) = %#q, want %#q", tt.plain, got, want)
}
}
//Test a large string > 65535 length
var buffer bytes.Buffer
for i := 0; i < 0x00011170; i++ { //70,000 character string
buffer.WriteString("a")
}
inp := buffer.String()
want := "\x7a\x00\x01\x11\x70" + inp
b := AppendString([]byte{}, inp)
if got := string(b); got != want {
t.Errorf("appendString(%q) = %#q, want %#q", inp, got, want)
}
}
func TestAppendBytes(t *testing.T) {
for _, tt := range encodeByteTests {
b := AppendBytes([]byte{}, tt.plain)
if got, want := string(b), tt.binary; got != want {
t.Errorf("appendString(%q) = %#q, want %#q", tt.plain, got, want)
}
}
//Test a large string > 65535 length
inp := []byte{}
for i := 0; i < 0x00011170; i++ { //70,000 character string
inp = append(inp, byte('a'))
}
want := "\x5a\x00\x01\x11\x70" + string(inp)
b := AppendBytes([]byte{}, inp)
if got := string(b); got != want {
t.Errorf("appendString(%q) = %#q, want %#q", inp, got, want)
}
}
func BenchmarkAppendString(b *testing.B) {
tests := map[string]string{
"NoEncoding": `aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa`,
"EncodingFirst": `"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa`,
"EncodingMiddle": `aaaaaaaaaaaaaaaaaaaaaaaaa"aaaaaaaaaaaaaaaaaaaaaaaa`,
"EncodingLast": `aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"`,
"MultiBytesFirst": `aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa`,
"MultiBytesMiddle": `aaaaaaaaaaaaaaaaaaaaaaaaa❤aaaaaaaaaaaaaaaaaaaaaaaa`,
"MultiBytesLast": `aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa❤`,
}
for name, str := range tests {
b.Run(name, func(b *testing.B) {
buf := make([]byte, 0, 120)
for i := 0; i < b.N; i++ {
_ = AppendString(buf, str)
}
})
}
}

93
internal/cbor/time.go Normal file
View File

@ -0,0 +1,93 @@
package cbor
import (
"time"
)
func appendIntegerTimestamp(dst []byte, t time.Time) []byte {
major := majorTypeTags
minor := additionalTypeTimestamp
dst = append(dst, byte(major|minor))
secs := t.Unix()
var val uint64
if secs < 0 {
major = majorTypeNegativeInt
val = uint64(-secs - 1)
} else {
major = majorTypeUnsignedInt
val = uint64(secs)
}
dst = appendCborTypePrefix(dst, major, uint64(val))
return dst
}
func appendFloatTimestamp(dst []byte, t time.Time) []byte {
major := majorTypeTags
minor := additionalTypeTimestamp
dst = append(dst, byte(major|minor))
secs := t.Unix()
nanos := t.Nanosecond()
var val float64
val = float64(secs)*1.0 + float64(nanos)*1E-9
return AppendFloat64(dst, val)
}
// AppendTime encodes and adds a timestamp to the dst byte array.
func AppendTime(dst []byte, t time.Time, unused string) []byte {
utc := t.UTC()
if utc.Nanosecond() == 0 {
return appendIntegerTimestamp(dst, utc)
}
return appendFloatTimestamp(dst, utc)
}
// AppendTimes encodes and adds an array of timestamps to the dst byte array.
func AppendTimes(dst []byte, vals []time.Time, unused string) []byte {
major := majorTypeArray
l := len(vals)
if l == 0 {
return AppendArrayEnd(AppendArrayStart(dst))
}
if l <= additionalMax {
lb := byte(l)
dst = append(dst, byte(major|lb))
} else {
dst = appendCborTypePrefix(dst, major, uint64(l))
}
for _, t := range vals {
dst = AppendTime(dst, t, unused)
}
return dst
}
// AppendDuration encodes and adds a duration to the dst byte array.
// useInt field indicates whether to store the duration as seconds (integer) or
// as seconds+nanoseconds (float).
func AppendDuration(dst []byte, d time.Duration, unit time.Duration, useInt bool) []byte {
if useInt {
return AppendInt64(dst, int64(d/unit))
}
return AppendFloat64(dst, float64(d)/float64(unit))
}
// AppendDurations encodes and adds an array of durations to the dst byte array.
// useInt field indicates whether to store the duration as seconds (integer) or
// as seconds+nanoseconds (float).
func AppendDurations(dst []byte, vals []time.Duration, unit time.Duration, useInt bool) []byte {
major := majorTypeArray
l := len(vals)
if l == 0 {
return AppendArrayEnd(AppendArrayStart(dst))
}
if l <= additionalMax {
lb := byte(l)
dst = append(dst, byte(major|lb))
} else {
dst = appendCborTypePrefix(dst, major, uint64(l))
}
for _, d := range vals {
dst = AppendDuration(dst, d, unit, useInt)
}
return dst
}

View File

@ -0,0 +1,99 @@
package cbor
import (
"encoding/hex"
"fmt"
"math"
"testing"
"time"
)
func TestAppendTimeNow(t *testing.T) {
tm := time.Now()
s := AppendTime([]byte{}, tm, "unused")
got := string(s)
tm1 := float64(tm.Unix()) + float64(tm.Nanosecond())*1E-9
tm2 := math.Float64bits(tm1)
var tm3 [8]byte
for i := uint(0); i < 8; i++ {
tm3[i] = byte(tm2 >> ((8 - i - 1) * 8))
}
want := append([]byte{0xc1, 0xfb}, tm3[:]...)
if got != string(want) {
t.Errorf("Appendtime(%s)=0x%s, want: 0x%s",
"time.Now()", hex.EncodeToString(s),
hex.EncodeToString(want))
}
}
var timeIntegerTestcases = []struct {
txt string
binary string
rfcStr string
}{
{"2013-02-03T19:54:00-08:00", "\xc1\x1a\x51\x0f\x30\xd8", "2013-02-04T03:54:00Z"},
{"1950-02-03T19:54:00-08:00", "\xc1\x3a\x25\x71\x93\xa7", "1950-02-04T03:54:00Z"},
}
func TestAppendTimePastPresentInteger(t *testing.T) {
for _, tt := range timeIntegerTestcases {
tin, err := time.Parse(time.RFC3339, tt.txt)
if err != nil {
fmt.Println("Cannot parse input", tt.txt, ".. Skipping!", err)
continue
}
b := AppendTime([]byte{}, tin, "unused")
if got, want := string(b), tt.binary; got != want {
t.Errorf("appendString(%s) = 0x%s, want 0x%s", tt.txt,
hex.EncodeToString(b),
hex.EncodeToString([]byte(want)))
}
}
}
var timeFloatTestcases = []struct {
rfcStr string
out string
}{
{"2006-01-02T15:04:05.999999-08:00", "\xc1\xfb\x41\xd0\xee\x6c\x59\x7f\xff\xfc"},
{"1956-01-02T15:04:05.999999-08:00", "\xc1\xfb\xc1\xba\x53\x81\x1a\x00\x00\x11"},
}
func TestAppendTimePastPresentFloat(t *testing.T) {
const timeFloatFmt = "2006-01-02T15:04:05.999999-07:00"
for _, tt := range timeFloatTestcases {
tin, err := time.Parse(timeFloatFmt, tt.rfcStr)
if err != nil {
fmt.Println("Cannot parse input", tt.rfcStr, ".. Skipping!")
continue
}
b := AppendTime([]byte{}, tin, "unused")
if got, want := string(b), tt.out; got != want {
t.Errorf("appendString(%s) = 0x%s, want 0x%s", tt.rfcStr,
hex.EncodeToString(b),
hex.EncodeToString([]byte(want)))
}
}
}
func BenchmarkAppendTime(b *testing.B) {
tests := map[string]string{
"Integer": "Feb 3, 2013 at 7:54pm (PST)",
"Float": "2006-01-02T15:04:05.999999-08:00",
}
const timeFloatFmt = "2006-01-02T15:04:05.999999-07:00"
for name, str := range tests {
t, err := time.Parse(time.RFC3339, str)
if err != nil {
t, _ = time.Parse(timeFloatFmt, str)
}
b.Run(name, func(b *testing.B) {
buf := make([]byte, 0, 100)
for i := 0; i < b.N; i++ {
_ = AppendTime(buf, t, "unused")
}
})
}
}

438
internal/cbor/types.go Normal file
View File

@ -0,0 +1,438 @@
package cbor
import (
"encoding/json"
"fmt"
"math"
)
// AppendNull inserts a 'Nil' object into the dst byte array.
func AppendNull(dst []byte) []byte {
return append(dst, byte(majorTypeSimpleAndFloat|additionalTypeNull))
}
// AppendBeginMarker inserts a map start into the dst byte array.
func AppendBeginMarker(dst []byte) []byte {
return append(dst, byte(majorTypeMap|additionalTypeInfiniteCount))
}
// AppendEndMarker inserts a map end into the dst byte array.
func AppendEndMarker(dst []byte) []byte {
return append(dst, byte(majorTypeSimpleAndFloat|additionalTypeBreak))
}
// AppendBool encodes and inserts a boolean value into the dst byte array.
func AppendBool(dst []byte, val bool) []byte {
b := additionalTypeBoolFalse
if val {
b = additionalTypeBoolTrue
}
return append(dst, byte(majorTypeSimpleAndFloat|b))
}
// AppendBools encodes and inserts an array of boolean values into the dst byte array.
func AppendBools(dst []byte, vals []bool) []byte {
major := majorTypeArray
l := len(vals)
if l == 0 {
return AppendArrayEnd(AppendArrayStart(dst))
}
if l <= additionalMax {
lb := byte(l)
dst = append(dst, byte(major|lb))
} else {
dst = appendCborTypePrefix(dst, major, uint64(l))
}
for _, v := range vals {
dst = AppendBool(dst, v)
}
return dst
}
// AppendInt encodes and inserts an integer value into the dst byte array.
func AppendInt(dst []byte, val int) []byte {
major := majorTypeUnsignedInt
contentVal := val
if val < 0 {
major = majorTypeNegativeInt
contentVal = -val - 1
}
if contentVal <= additionalMax {
lb := byte(contentVal)
dst = append(dst, byte(major|lb))
} else {
dst = appendCborTypePrefix(dst, major, uint64(contentVal))
}
return dst
}
// AppendInts encodes and inserts an array of integer values into the dst byte array.
func AppendInts(dst []byte, vals []int) []byte {
major := majorTypeArray
l := len(vals)
if l == 0 {
return AppendArrayEnd(AppendArrayStart(dst))
}
if l <= additionalMax {
lb := byte(l)
dst = append(dst, byte(major|lb))
} else {
dst = appendCborTypePrefix(dst, major, uint64(l))
}
for _, v := range vals {
dst = AppendInt(dst, v)
}
return dst
}
// AppendInt8 encodes and inserts an int8 value into the dst byte array.
func AppendInt8(dst []byte, val int8) []byte {
return AppendInt(dst, int(val))
}
// AppendInts8 encodes and inserts an array of integer values into the dst byte array.
func AppendInts8(dst []byte, vals []int8) []byte {
major := majorTypeArray
l := len(vals)
if l == 0 {
return AppendArrayEnd(AppendArrayStart(dst))
}
if l <= additionalMax {
lb := byte(l)
dst = append(dst, byte(major|lb))
} else {
dst = appendCborTypePrefix(dst, major, uint64(l))
}
for _, v := range vals {
dst = AppendInt(dst, int(v))
}
return dst
}
// AppendInt16 encodes and inserts a int16 value into the dst byte array.
func AppendInt16(dst []byte, val int16) []byte {
return AppendInt(dst, int(val))
}
// AppendInts16 encodes and inserts an array of int16 values into the dst byte array.
func AppendInts16(dst []byte, vals []int16) []byte {
major := majorTypeArray
l := len(vals)
if l == 0 {
return AppendArrayEnd(AppendArrayStart(dst))
}
if l <= additionalMax {
lb := byte(l)
dst = append(dst, byte(major|lb))
} else {
dst = appendCborTypePrefix(dst, major, uint64(l))
}
for _, v := range vals {
dst = AppendInt(dst, int(v))
}
return dst
}
// AppendInt32 encodes and inserts a int32 value into the dst byte array.
func AppendInt32(dst []byte, val int32) []byte {
return AppendInt(dst, int(val))
}
// AppendInts32 encodes and inserts an array of int32 values into the dst byte array.
func AppendInts32(dst []byte, vals []int32) []byte {
major := majorTypeArray
l := len(vals)
if l == 0 {
return AppendArrayEnd(AppendArrayStart(dst))
}
if l <= additionalMax {
lb := byte(l)
dst = append(dst, byte(major|lb))
} else {
dst = appendCborTypePrefix(dst, major, uint64(l))
}
for _, v := range vals {
dst = AppendInt(dst, int(v))
}
return dst
}
// AppendInt64 encodes and inserts a int64 value into the dst byte array.
func AppendInt64(dst []byte, val int64) []byte {
major := majorTypeUnsignedInt
contentVal := val
if val < 0 {
major = majorTypeNegativeInt
contentVal = -val - 1
}
if contentVal <= additionalMax {
lb := byte(contentVal)
dst = append(dst, byte(major|lb))
} else {
dst = appendCborTypePrefix(dst, major, uint64(contentVal))
}
return dst
}
// AppendInts64 encodes and inserts an array of int64 values into the dst byte array.
func AppendInts64(dst []byte, vals []int64) []byte {
major := majorTypeArray
l := len(vals)
if l == 0 {
return AppendArrayEnd(AppendArrayStart(dst))
}
if l <= additionalMax {
lb := byte(l)
dst = append(dst, byte(major|lb))
} else {
dst = appendCborTypePrefix(dst, major, uint64(l))
}
for _, v := range vals {
dst = AppendInt64(dst, v)
}
return dst
}
// AppendUint encodes and inserts an unsigned integer value into the dst byte array.
func AppendUint(dst []byte, val uint) []byte {
return AppendInt64(dst, int64(val))
}
// AppendUints encodes and inserts an array of unsigned integer values into the dst byte array.
func AppendUints(dst []byte, vals []uint) []byte {
major := majorTypeArray
l := len(vals)
if l == 0 {
return AppendArrayEnd(AppendArrayStart(dst))
}
if l <= additionalMax {
lb := byte(l)
dst = append(dst, byte(major|lb))
} else {
dst = appendCborTypePrefix(dst, major, uint64(l))
}
for _, v := range vals {
dst = AppendUint(dst, v)
}
return dst
}
// AppendUint8 encodes and inserts a unsigned int8 value into the dst byte array.
func AppendUint8(dst []byte, val uint8) []byte {
return AppendUint(dst, uint(val))
}
// AppendUints8 encodes and inserts an array of uint8 values into the dst byte array.
func AppendUints8(dst []byte, vals []uint8) []byte {
major := majorTypeArray
l := len(vals)
if l == 0 {
return AppendArrayEnd(AppendArrayStart(dst))
}
if l <= additionalMax {
lb := byte(l)
dst = append(dst, byte(major|lb))
} else {
dst = appendCborTypePrefix(dst, major, uint64(l))
}
for _, v := range vals {
dst = AppendUint8(dst, v)
}
return dst
}
// AppendUint16 encodes and inserts a uint16 value into the dst byte array.
func AppendUint16(dst []byte, val uint16) []byte {
return AppendUint(dst, uint(val))
}
// AppendUints16 encodes and inserts an array of uint16 values into the dst byte array.
func AppendUints16(dst []byte, vals []uint16) []byte {
major := majorTypeArray
l := len(vals)
if l == 0 {
return AppendArrayEnd(AppendArrayStart(dst))
}
if l <= additionalMax {
lb := byte(l)
dst = append(dst, byte(major|lb))
} else {
dst = appendCborTypePrefix(dst, major, uint64(l))
}
for _, v := range vals {
dst = AppendUint16(dst, v)
}
return dst
}
// AppendUint32 encodes and inserts a uint32 value into the dst byte array.
func AppendUint32(dst []byte, val uint32) []byte {
return AppendUint(dst, uint(val))
}
// AppendUints32 encodes and inserts an array of uint32 values into the dst byte array.
func AppendUints32(dst []byte, vals []uint32) []byte {
major := majorTypeArray
l := len(vals)
if l == 0 {
return AppendArrayEnd(AppendArrayStart(dst))
}
if l <= additionalMax {
lb := byte(l)
dst = append(dst, byte(major|lb))
} else {
dst = appendCborTypePrefix(dst, major, uint64(l))
}
for _, v := range vals {
dst = AppendUint32(dst, v)
}
return dst
}
// AppendUint64 encodes and inserts a uint64 value into the dst byte array.
func AppendUint64(dst []byte, val uint64) []byte {
major := majorTypeUnsignedInt
contentVal := val
if contentVal <= additionalMax {
lb := byte(contentVal)
dst = append(dst, byte(major|lb))
} else {
dst = appendCborTypePrefix(dst, major, uint64(contentVal))
}
return dst
}
// AppendUints64 encodes and inserts an array of uint64 values into the dst byte array.
func AppendUints64(dst []byte, vals []uint64) []byte {
major := majorTypeArray
l := len(vals)
if l == 0 {
return AppendArrayEnd(AppendArrayStart(dst))
}
if l <= additionalMax {
lb := byte(l)
dst = append(dst, byte(major|lb))
} else {
dst = appendCborTypePrefix(dst, major, uint64(l))
}
for _, v := range vals {
dst = AppendUint64(dst, v)
}
return dst
}
// AppendFloat32 encodes and inserts a single precision float value into the dst byte array.
func AppendFloat32(dst []byte, val float32) []byte {
switch {
case math.IsNaN(float64(val)):
return append(dst, "\xfa\x7f\xc0\x00\x00"...)
case math.IsInf(float64(val), 1):
return append(dst, "\xfa\x7f\x80\x00\x00"...)
case math.IsInf(float64(val), -1):
return append(dst, "\xfa\xff\x80\x00\x00"...)
}
major := majorTypeSimpleAndFloat
subType := additionalTypeFloat32
n := math.Float32bits(val)
var buf [4]byte
for i := uint(0); i < 4; i++ {
buf[i] = byte(n >> ((3 - i) * 8))
}
return append(append(dst, byte(major|subType)), buf[0], buf[1], buf[2], buf[3])
}
// AppendFloats32 encodes and inserts an array of single precision float value into the dst byte array.
func AppendFloats32(dst []byte, vals []float32) []byte {
major := majorTypeArray
l := len(vals)
if l == 0 {
return AppendArrayEnd(AppendArrayStart(dst))
}
if l <= additionalMax {
lb := byte(l)
dst = append(dst, byte(major|lb))
} else {
dst = appendCborTypePrefix(dst, major, uint64(l))
}
for _, v := range vals {
dst = AppendFloat32(dst, v)
}
return dst
}
// AppendFloat64 encodes and inserts a double precision float value into the dst byte array.
func AppendFloat64(dst []byte, val float64) []byte {
switch {
case math.IsNaN(val):
return append(dst, "\xfb\x7f\xf8\x00\x00\x00\x00\x00\x00"...)
case math.IsInf(val, 1):
return append(dst, "\xfb\x7f\xf0\x00\x00\x00\x00\x00\x00"...)
case math.IsInf(val, -1):
return append(dst, "\xfb\xff\xf0\x00\x00\x00\x00\x00\x00"...)
}
major := majorTypeSimpleAndFloat
subType := additionalTypeFloat64
n := math.Float64bits(val)
dst = append(dst, byte(major|subType))
for i := uint(1); i <= 8; i++ {
b := byte(n >> ((8 - i) * 8))
dst = append(dst, b)
}
return dst
}
// AppendFloats64 encodes and inserts an array of double precision float values into the dst byte array.
func AppendFloats64(dst []byte, vals []float64) []byte {
major := majorTypeArray
l := len(vals)
if l == 0 {
return AppendArrayEnd(AppendArrayStart(dst))
}
if l <= additionalMax {
lb := byte(l)
dst = append(dst, byte(major|lb))
} else {
dst = appendCborTypePrefix(dst, major, uint64(l))
}
for _, v := range vals {
dst = AppendFloat64(dst, v)
}
return dst
}
// AppendInterface takes an arbitrary object and converts it to JSON and embeds it 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 AppendEmbeddedJSON(dst, marshaled)
}
// AppendObjectData takes an object in form of a byte array and appends to dst.
func AppendObjectData(dst []byte, o []byte) []byte {
return append(dst, o...)
}
// AppendArrayStart adds markers to indicate the start of an array.
func AppendArrayStart(dst []byte) []byte {
return append(dst, byte(majorTypeArray|additionalTypeInfiniteCount))
}
// AppendArrayEnd adds markers to indicate the end of an array.
func AppendArrayEnd(dst []byte) []byte {
return append(dst, byte(majorTypeSimpleAndFloat|additionalTypeBreak))
}
// AppendArrayDelim adds markers to indicate end of a particular array element.
func AppendArrayDelim(dst []byte) []byte {
//No delimiters needed in cbor
return dst
}
func AppendHex (dst []byte, val []byte) []byte {
dst = append(dst, byte(majorTypeTags|additionalTypeIntUint16))
dst = append(dst, byte(additionalTypeTagHexString>>8))
dst = append(dst, byte(additionalTypeTagHexString&0xff))
return AppendBytes(dst, val)
}

253
internal/cbor/types_test.go Normal file
View File

@ -0,0 +1,253 @@
package cbor
import (
"encoding/hex"
"testing"
)
func TestAppendNull(t *testing.T) {
s := AppendNull([]byte{})
got := string(s)
want := "\xf6"
if got != want {
t.Errorf("appendNull() = 0x%s, want: 0x%s", hex.EncodeToString(s),
hex.EncodeToString([]byte(want)))
}
}
var booleanTestCases = []struct {
val bool
binary string
json string
}{
{true, "\xf5", "true"},
{false, "\xf4", "false"},
}
func TestAppendBool(t *testing.T) {
for _, tc := range booleanTestCases {
s := AppendBool([]byte{}, tc.val)
got := string(s)
if got != tc.binary {
t.Errorf("AppendBool(%s)=0x%s, want: 0x%s",
tc.json, hex.EncodeToString(s),
hex.EncodeToString([]byte(tc.binary)))
}
}
}
var booleanArrayTestCases = []struct {
val []bool
binary string
json string
}{
{[]bool{true, false, true}, "\x83\xf5\xf4\xf5", "[true,false,true]"},
{[]bool{true, false, false, true, false, true}, "\x86\xf5\xf4\xf4\xf5\xf4\xf5", "[true,false,false,true,false,true]"},
}
func TestAppendBoolArray(t *testing.T) {
for _, tc := range booleanArrayTestCases {
s := AppendBools([]byte{}, tc.val)
got := string(s)
if got != tc.binary {
t.Errorf("AppendBools(%s)=0x%s, want: 0x%s",
tc.json, hex.EncodeToString(s),
hex.EncodeToString([]byte(tc.binary)))
}
}
}
var integerTestCases = []struct {
val int
binary string
}{
// Value included in the type.
{0, "\x00"},
{1, "\x01"},
{2, "\x02"},
{3, "\x03"},
{8, "\x08"},
{9, "\x09"},
{10, "\x0a"},
{22, "\x16"},
{23, "\x17"},
// Value in 1 byte.
{24, "\x18\x18"},
{25, "\x18\x19"},
{26, "\x18\x1a"},
{100, "\x18\x64"},
{254, "\x18\xfe"},
{255, "\x18\xff"},
// Value in 2 bytes.
{256, "\x19\x01\x00"},
{257, "\x19\x01\x01"},
{1000, "\x19\x03\xe8"},
{0xFFFF, "\x19\xff\xff"},
// Value in 4 bytes.
{0x10000, "\x1a\x00\x01\x00\x00"},
{0xFFFFFFFE, "\x1a\xff\xff\xff\xfe"},
{1000000, "\x1a\x00\x0f\x42\x40"},
// Value in 8 bytes.
{0xabcd100000000, "\x1b\x00\x0a\xbc\xd1\x00\x00\x00\x00"},
{1000000000000, "\x1b\x00\x00\x00\xe8\xd4\xa5\x10\x00"},
// Negative number test cases.
// Value included in the type.
{-1, "\x20"},
{-2, "\x21"},
{-3, "\x22"},
{-10, "\x29"},
{-21, "\x34"},
{-22, "\x35"},
{-23, "\x36"},
{-24, "\x37"},
// Value in 1 byte.
{-25, "\x38\x18"},
{-26, "\x38\x19"},
{-100, "\x38\x63"},
{-254, "\x38\xfd"},
{-255, "\x38\xfe"},
{-256, "\x38\xff"},
// Value in 2 bytes.
{-257, "\x39\x01\x00"},
{-258, "\x39\x01\x01"},
{-1000, "\x39\x03\xe7"},
// Value in 4 bytes.
{-0x10001, "\x3a\x00\x01\x00\x00"},
{-0xFFFFFFFE, "\x3a\xff\xff\xff\xfd"},
{-1000000, "\x3a\x00\x0f\x42\x3f"},
// Value in 8 bytes.
{-0xabcd100000001, "\x3b\x00\x0a\xbc\xd1\x00\x00\x00\x00"},
{-1000000000001, "\x3b\x00\x00\x00\xe8\xd4\xa5\x10\x00"},
}
func TestAppendInt(t *testing.T) {
for _, tc := range integerTestCases {
s := AppendInt([]byte{}, tc.val)
got := string(s)
if got != tc.binary {
t.Errorf("AppendInt(0x%x)=0x%s, want: 0x%s",
tc.val, hex.EncodeToString(s),
hex.EncodeToString([]byte(tc.binary)))
}
}
}
var integerArrayTestCases = []struct {
val []int
binary string
json string
}{
{[]int{-1, 0, 200, 20}, "\x84\x20\x00\x18\xc8\x14", "[-1,0,200,20]"},
{[]int{-200, -10, 200, 400}, "\x84\x38\xc7\x29\x18\xc8\x19\x01\x90", "[-200,-10,200,400]"},
{[]int{1, 2, 3}, "\x83\x01\x02\x03", "[1,2,3]"},
{[]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25},
"\x98\x19\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x18\x18\x19",
"[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25]"},
}
func TestAppendIntArray(t *testing.T) {
for _, tc := range integerArrayTestCases {
s := AppendInts([]byte{}, tc.val)
got := string(s)
if got != tc.binary {
t.Errorf("AppendInts(%s)=0x%s, want: 0x%s",
tc.json, hex.EncodeToString(s),
hex.EncodeToString([]byte(tc.binary)))
}
}
}
var float32TestCases = []struct {
val float32
binary string
}{
{0.0, "\xfa\x00\x00\x00\x00"},
{-0.0, "\xfa\x00\x00\x00\x00"},
{1.0, "\xfa\x3f\x80\x00\x00"},
{1.5, "\xfa\x3f\xc0\x00\x00"},
{65504.0, "\xfa\x47\x7f\xe0\x00"},
{-4.0, "\xfa\xc0\x80\x00\x00"},
{0.00006103515625, "\xfa\x38\x80\x00\x00"},
}
func TestAppendFloat32(t *testing.T) {
for _, tc := range float32TestCases {
s := AppendFloat32([]byte{}, tc.val)
got := string(s)
if got != tc.binary {
t.Errorf("AppendFloat32(%f)=0x%s, want: 0x%s",
tc.val, hex.EncodeToString(s),
hex.EncodeToString([]byte(tc.binary)))
}
}
}
func BenchmarkAppendInt(b *testing.B) {
type st struct {
sz byte
val int64
}
tests := map[string]st{
"int-Positive": {sz: 0, val: 10000},
"int-Negative": {sz: 0, val: -10000},
"uint8": {sz: 1, val: 100},
"uint16": {sz: 2, val: 0xfff},
"uint32": {sz: 4, val: 0xffffff},
"uint64": {sz: 8, val: 0xffffffffff},
"int8": {sz: 21, val: -120},
"int16": {sz: 22, val: -1200},
"int32": {sz: 23, val: 32000},
"int64": {sz: 24, val: 0xffffffffff},
}
for name, str := range tests {
b.Run(name, func(b *testing.B) {
buf := make([]byte, 0, 100)
for i := 0; i < b.N; i++ {
switch str.sz {
case 0:
_ = AppendInt(buf, int(str.val))
case 1:
_ = AppendUint8(buf, uint8(str.val))
case 2:
_ = AppendUint16(buf, uint16(str.val))
case 4:
_ = AppendUint32(buf, uint32(str.val))
case 8:
_ = AppendUint64(buf, uint64(str.val))
case 21:
_ = AppendInt8(buf, int8(str.val))
case 22:
_ = AppendInt16(buf, int16(str.val))
case 23:
_ = AppendInt32(buf, int32(str.val))
case 24:
_ = AppendInt64(buf, int64(str.val))
}
}
})
}
}
func BenchmarkAppendFloat(b *testing.B) {
type st struct {
sz byte
val float64
}
tests := map[string]st{
"Float32": {sz: 4, val: 10000.12345},
"Float64": {sz: 8, val: -10000.54321},
}
for name, str := range tests {
b.Run(name, func(b *testing.B) {
buf := make([]byte, 0, 100)
for i := 0; i < b.N; i++ {
switch str.sz {
case 4:
_ = AppendFloat32(buf, float32(str.val))
case 8:
_ = AppendFloat64(buf, str.val)
}
}
})
}
}

View File

@ -2,7 +2,7 @@ package json
// AppendKey appends a new key to the output JSON.
func AppendKey(dst []byte, key string) []byte {
if len(dst) > 1 {
if len(dst) > 1 && dst[len(dst)-1] != '{' {
dst = append(dst, ',')
}
dst = AppendString(dst, key)

View File

@ -332,3 +332,16 @@ func AppendInterface(dst []byte, i interface{}) []byte {
}
return append(dst, marshaled...)
}
func AppendObjectData(dst []byte, o []byte) []byte {
// Two conditions we want to put a ',' between existing content and
// new content:
// 1. new content starts with '{' - which shd be dropped OR
// 2. existing content has already other fields
if o[0] == '{' {
o[0] = ','
} else if len(dst) > 1 {
dst = append(dst, ',')
}
return append(dst, o...)
}

7
log.go
View File

@ -358,11 +358,8 @@ func (l *Logger) newEvent(level Level, done func(string)) *Event {
if level != NoLevel {
e.Str(LevelFieldName, level.String())
}
if len(l.context) > 0 {
if len(e.buf) > 1 {
e.buf = append(e.buf, ',')
}
e.buf = append(e.buf, l.context...)
if l.context != nil && len(l.context) > 0 {
e.buf = appendObjectData(e.buf, l.context)
}
return e
}

View File

@ -1,3 +1,5 @@
// +build !binary_log
package log_test
import (

View File

@ -1,3 +1,5 @@
// +build !binary_log
package zerolog_test
import (
@ -13,7 +15,6 @@ func ExampleNew() {
log := zerolog.New(os.Stdout)
log.Info().Msg("hello world")
// Output: {"level":"info","message":"hello world"}
}

View File

@ -15,7 +15,7 @@ func TestLog(t *testing.T) {
out := &bytes.Buffer{}
log := New(out)
log.Log().Msg("")
if got, want := out.String(), "{}\n"; got != want {
if got, want := decodeIfBinaryToString(out.Bytes()), "{}\n"; got != want {
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
}
})
@ -24,7 +24,7 @@ func TestLog(t *testing.T) {
out := &bytes.Buffer{}
log := New(out)
log.Log().Str("foo", "bar").Msg("")
if got, want := out.String(), `{"foo":"bar"}`+"\n"; got != want {
if got, want := decodeIfBinaryToString(out.Bytes()), `{"foo":"bar"}`+"\n"; got != want {
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
}
})
@ -36,7 +36,7 @@ func TestLog(t *testing.T) {
Str("foo", "bar").
Int("n", 123).
Msg("")
if got, want := out.String(), `{"foo":"bar","n":123}`+"\n"; got != want {
if got, want := decodeIfBinaryToString(out.Bytes()), `{"foo":"bar","n":123}`+"\n"; got != want {
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
}
})
@ -47,7 +47,7 @@ func TestInfo(t *testing.T) {
out := &bytes.Buffer{}
log := New(out)
log.Info().Msg("")
if got, want := out.String(), `{"level":"info"}`+"\n"; got != want {
if got, want := decodeIfBinaryToString(out.Bytes()), `{"level":"info"}`+"\n"; got != want {
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
}
})
@ -56,7 +56,7 @@ func TestInfo(t *testing.T) {
out := &bytes.Buffer{}
log := New(out)
log.Info().Str("foo", "bar").Msg("")
if got, want := out.String(), `{"level":"info","foo":"bar"}`+"\n"; got != want {
if got, want := decodeIfBinaryToString(out.Bytes()), `{"level":"info","foo":"bar"}`+"\n"; got != want {
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
}
})
@ -68,7 +68,7 @@ func TestInfo(t *testing.T) {
Str("foo", "bar").
Int("n", 123).
Msg("")
if got, want := out.String(), `{"level":"info","foo":"bar","n":123}`+"\n"; got != want {
if got, want := decodeIfBinaryToString(out.Bytes()), `{"level":"info","foo":"bar","n":123}`+"\n"; got != want {
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
}
})
@ -101,7 +101,7 @@ func TestWith(t *testing.T) {
caller := fmt.Sprintf("%s:%d", file, line+3)
log := ctx.Caller().Logger()
log.Log().Msg("")
if got, want := out.String(), `{"string":"foo","bytes":"bar","hex":"12ef","json":{"some":"json"},"error":"some error","bool":true,"int":1,"int8":2,"int16":3,"int32":4,"int64":5,"uint":6,"uint8":7,"uint16":8,"uint32":9,"uint64":10,"float32":11,"float64":12,"time":"0001-01-01T00:00:00Z","caller":"`+caller+`"}`+"\n"; got != want {
if got, want := decodeIfBinaryToString(out.Bytes()), `{"string":"foo","bytes":"bar","hex":"12ef","json":{"some":"json"},"error":"some error","bool":true,"int":1,"int8":2,"int16":3,"int32":4,"int64":5,"uint":6,"uint8":7,"uint16":8,"uint32":9,"uint64":10,"float32":11,"float64":12,"time":"0001-01-01T00:00:00Z","caller":"`+caller+`"}`+"\n"; got != want {
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
}
}
@ -130,7 +130,7 @@ func TestFieldsMap(t *testing.T) {
"dur": 1 * time.Second,
"time": time.Time{},
}).Msg("")
if got, want := out.String(), `{"bool":true,"bytes":"bar","dur":1000,"error":"some error","float32":11,"float64":12,"int":1,"int16":3,"int32":4,"int64":5,"int8":2,"nil":null,"string":"foo","time":"0001-01-01T00:00:00Z","uint":6,"uint16":8,"uint32":9,"uint64":10,"uint8":7}`+"\n"; got != want {
if got, want := decodeIfBinaryToString(out.Bytes()), `{"bool":true,"bytes":"bar","dur":1000,"error":"some error","float32":11,"float64":12,"int":1,"int16":3,"int32":4,"int64":5,"int8":2,"nil":null,"string":"foo","time":"0001-01-01T00:00:00Z","uint":6,"uint16":8,"uint32":9,"uint64":10,"uint8":7}`+"\n"; got != want {
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
}
}
@ -156,7 +156,7 @@ func TestFieldsMapPnt(t *testing.T) {
"dur": new(time.Duration),
"time": new(time.Time),
}).Msg("")
if got, want := out.String(), `{"bool":false,"dur":0,"float32":0,"float64":0,"int":0,"int16":0,"int32":0,"int64":0,"int8":0,"string":"","time":"0001-01-01T00:00:00Z","uint":0,"uint16":0,"uint32":0,"uint64":0,"uint8":0}`+"\n"; got != want {
if got, want := decodeIfBinaryToString(out.Bytes()), `{"bool":false,"dur":0,"float32":0,"float64":0,"int":0,"int16":0,"int32":0,"int64":0,"int8":0,"string":"","time":"0001-01-01T00:00:00Z","uint":0,"uint16":0,"uint32":0,"uint64":0,"uint8":0}`+"\n"; got != want {
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
}
}
@ -192,7 +192,7 @@ func TestFields(t *testing.T) {
Time("time", time.Time{}).
TimeDiff("diff", now, now.Add(-10*time.Second)).
Msg("")
if got, want := out.String(), `{"caller":"`+caller+`","string":"foo","bytes":"bar","hex":"12ef","json":{"some":"json"},"error":"some error","bool":true,"int":1,"int8":2,"int16":3,"int32":4,"int64":5,"uint":6,"uint8":7,"uint16":8,"uint32":9,"uint64":10,"float32":11,"float64":12,"dur":1000,"time":"0001-01-01T00:00:00Z","diff":10000}`+"\n"; got != want {
if got, want := decodeIfBinaryToString(out.Bytes()), `{"caller":"`+caller+`","string":"foo","bytes":"bar","hex":"12ef","json":{"some":"json"},"error":"some error","bool":true,"int":1,"int8":2,"int16":3,"int32":4,"int64":5,"uint":6,"uint8":7,"uint16":8,"uint32":9,"uint64":10,"float32":11,"float64":12,"dur":1000,"time":"0001-01-01T00:00:00Z","diff":10000}`+"\n"; got != want {
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
}
}
@ -219,7 +219,7 @@ func TestFieldsArrayEmpty(t *testing.T) {
Durs("dur", []time.Duration{}).
Times("time", []time.Time{}).
Msg("")
if got, want := out.String(), `{"string":[],"err":[],"bool":[],"int":[],"int8":[],"int16":[],"int32":[],"int64":[],"uint":[],"uint8":[],"uint16":[],"uint32":[],"uint64":[],"float32":[],"float64":[],"dur":[],"time":[]}`+"\n"; got != want {
if got, want := decodeIfBinaryToString(out.Bytes()), `{"string":[],"err":[],"bool":[],"int":[],"int8":[],"int16":[],"int32":[],"int64":[],"uint":[],"uint8":[],"uint16":[],"uint32":[],"uint64":[],"float32":[],"float64":[],"dur":[],"time":[]}`+"\n"; got != want {
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
}
}
@ -246,7 +246,7 @@ func TestFieldsArraySingleElement(t *testing.T) {
Durs("dur", []time.Duration{1 * time.Second}).
Times("time", []time.Time{time.Time{}}).
Msg("")
if got, want := out.String(), `{"string":["foo"],"err":["some error"],"bool":[true],"int":[1],"int8":[2],"int16":[3],"int32":[4],"int64":[5],"uint":[6],"uint8":[7],"uint16":[8],"uint32":[9],"uint64":[10],"float32":[11],"float64":[12],"dur":[1000],"time":["0001-01-01T00:00:00Z"]}`+"\n"; got != want {
if got, want := decodeIfBinaryToString(out.Bytes()), `{"string":["foo"],"err":["some error"],"bool":[true],"int":[1],"int8":[2],"int16":[3],"int32":[4],"int64":[5],"uint":[6],"uint8":[7],"uint16":[8],"uint32":[9],"uint64":[10],"float32":[11],"float64":[12],"dur":[1000],"time":["0001-01-01T00:00:00Z"]}`+"\n"; got != want {
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
}
}
@ -273,7 +273,7 @@ func TestFieldsArrayMultipleElement(t *testing.T) {
Durs("dur", []time.Duration{1 * time.Second, 0}).
Times("time", []time.Time{time.Time{}, time.Time{}}).
Msg("")
if got, want := out.String(), `{"string":["foo","bar"],"err":["some error",null],"bool":[true,false],"int":[1,0],"int8":[2,0],"int16":[3,0],"int32":[4,0],"int64":[5,0],"uint":[6,0],"uint8":[7,0],"uint16":[8,0],"uint32":[9,0],"uint64":[10,0],"float32":[11,0],"float64":[12,0],"dur":[1000,0],"time":["0001-01-01T00:00:00Z","0001-01-01T00:00:00Z"]}`+"\n"; got != want {
if got, want := decodeIfBinaryToString(out.Bytes()), `{"string":["foo","bar"],"err":["some error",null],"bool":[true,false],"int":[1,0],"int8":[2,0],"int16":[3,0],"int32":[4,0],"int64":[5,0],"uint":[6,0],"uint8":[7,0],"uint16":[8,0],"uint32":[9,0],"uint64":[10,0],"float32":[11,0],"float64":[12,0],"dur":[1000,0],"time":["0001-01-01T00:00:00Z","0001-01-01T00:00:00Z"]}`+"\n"; got != want {
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
}
}
@ -305,7 +305,7 @@ func TestFieldsDisabled(t *testing.T) {
Time("time", time.Time{}).
TimeDiff("diff", now, now.Add(-10*time.Second)).
Msg("")
if got, want := out.String(), ""; got != want {
if got, want := decodeIfBinaryToString(out.Bytes()), ""; got != want {
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
}
}
@ -314,7 +314,7 @@ func TestMsgf(t *testing.T) {
out := &bytes.Buffer{}
log := New(out)
log.Log().Msgf("one %s %.1f %d %v", "two", 3.4, 5, errors.New("six"))
if got, want := out.String(), `{"message":"one two 3.4 5 six"}`+"\n"; got != want {
if got, want := decodeIfBinaryToString(out.Bytes()), `{"message":"one two 3.4 5 six"}`+"\n"; got != want {
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
}
}
@ -323,7 +323,7 @@ func TestWithAndFieldsCombined(t *testing.T) {
out := &bytes.Buffer{}
log := New(out).With().Str("f1", "val").Str("f2", "val").Logger()
log.Log().Str("f3", "val").Msg("")
if got, want := out.String(), `{"f1":"val","f2":"val","f3":"val"}`+"\n"; got != want {
if got, want := decodeIfBinaryToString(out.Bytes()), `{"f1":"val","f2":"val","f3":"val"}`+"\n"; got != want {
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
}
}
@ -333,7 +333,7 @@ func TestLevel(t *testing.T) {
out := &bytes.Buffer{}
log := New(out).Level(Disabled)
log.Info().Msg("test")
if got, want := out.String(), ""; got != want {
if got, want := decodeIfBinaryToString(out.Bytes()), ""; got != want {
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
}
})
@ -342,7 +342,7 @@ func TestLevel(t *testing.T) {
out := &bytes.Buffer{}
log := New(out).Level(Disabled)
log.Log().Msg("test")
if got, want := out.String(), ""; got != want {
if got, want := decodeIfBinaryToString(out.Bytes()), ""; got != want {
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
}
})
@ -351,7 +351,7 @@ func TestLevel(t *testing.T) {
out := &bytes.Buffer{}
log := New(out).Level(InfoLevel)
log.Log().Msg("test")
if got, want := out.String(), `{"message":"test"}`+"\n"; got != want {
if got, want := decodeIfBinaryToString(out.Bytes()), `{"message":"test"}`+"\n"; got != want {
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
}
})
@ -360,7 +360,7 @@ func TestLevel(t *testing.T) {
out := &bytes.Buffer{}
log := New(out).Level(PanicLevel)
log.Log().Msg("test")
if got, want := out.String(), `{"message":"test"}`+"\n"; got != want {
if got, want := decodeIfBinaryToString(out.Bytes()), `{"message":"test"}`+"\n"; got != want {
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
}
})
@ -369,7 +369,7 @@ func TestLevel(t *testing.T) {
out := &bytes.Buffer{}
log := New(out).Level(InfoLevel)
log.WithLevel(NoLevel).Msg("test")
if got, want := out.String(), `{"message":"test"}`+"\n"; got != want {
if got, want := decodeIfBinaryToString(out.Bytes()), `{"message":"test"}`+"\n"; got != want {
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
}
})
@ -378,7 +378,7 @@ func TestLevel(t *testing.T) {
out := &bytes.Buffer{}
log := New(out).Level(InfoLevel)
log.Info().Msg("test")
if got, want := out.String(), `{"level":"info","message":"test"}`+"\n"; got != want {
if got, want := decodeIfBinaryToString(out.Bytes()), `{"level":"info","message":"test"}`+"\n"; got != want {
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
}
})
@ -391,7 +391,7 @@ func TestSampling(t *testing.T) {
log.Log().Int("i", 2).Msg("")
log.Log().Int("i", 3).Msg("")
log.Log().Int("i", 4).Msg("")
if got, want := out.String(), "{\"i\":2}\n{\"i\":4}\n"; got != want {
if got, want := decodeIfBinaryToString(out.Bytes()), "{\"i\":2}\n{\"i\":4}\n"; got != want {
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
}
}
@ -408,6 +408,7 @@ func (lw *levelWriter) Write(p []byte) (int, error) {
}
func (lw *levelWriter) WriteLevel(lvl Level, p []byte) (int, error) {
p = decodeIfBinaryToBytes(p)
lw.ops = append(lw.ops, struct {
l Level
p string
@ -465,7 +466,7 @@ func TestContextTimestamp(t *testing.T) {
log := New(out).With().Timestamp().Str("foo", "bar").Logger()
log.Log().Msg("hello world")
if got, want := out.String(), `{"foo":"bar","time":"2001-02-03T04:05:06Z","message":"hello world"}`+"\n"; got != want {
if got, want := decodeIfBinaryToString(out.Bytes()), `{"foo":"bar","time":"2001-02-03T04:05:06Z","message":"hello world"}`+"\n"; got != want {
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
}
}
@ -481,7 +482,7 @@ func TestEventTimestamp(t *testing.T) {
log := New(out).With().Str("foo", "bar").Logger()
log.Log().Timestamp().Msg("hello world")
if got, want := out.String(), `{"foo":"bar","time":"2001-02-03T04:05:06Z","message":"hello world"}`+"\n"; got != want {
if got, want := decodeIfBinaryToString(out.Bytes()), `{"foo":"bar","time":"2001-02-03T04:05:06Z","message":"hello world"}`+"\n"; got != want {
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
}
}
@ -492,7 +493,7 @@ func TestOutputWithoutTimestamp(t *testing.T) {
log := New(ignoredOut).Output(out).With().Str("foo", "bar").Logger()
log.Log().Msg("hello world")
if got, want := out.String(), `{"foo":"bar","message":"hello world"}`+"\n"; got != want {
if got, want := decodeIfBinaryToString(out.Bytes()), `{"foo":"bar","message":"hello world"}`+"\n"; got != want {
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
}
}
@ -509,7 +510,7 @@ func TestOutputWithTimestamp(t *testing.T) {
log := New(ignoredOut).Output(out).With().Timestamp().Str("foo", "bar").Logger()
log.Log().Msg("hello world")
if got, want := out.String(), `{"foo":"bar","time":"2001-02-03T04:05:06Z","message":"hello world"}`+"\n"; got != want {
if got, want := decodeIfBinaryToString(out.Bytes()), `{"foo":"bar","time":"2001-02-03T04:05:06Z","message":"hello world"}`+"\n"; got != want {
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
}
}

View File

@ -1,3 +1,5 @@
// +build !binary_log
package zerolog
import (

View File

@ -1,8 +1,11 @@
// +build !windows
// +build !binary_log
package zerolog
import "io"
import (
"io"
)
// SyslogWriter is an interface matching a syslog.Writer struct.
type SyslogWriter interface {

View File

@ -1,3 +1,4 @@
// +build !binary_log
// +build !windows
package zerolog

View File

@ -1,3 +1,4 @@
// +build !binary_log
// +build !windows
package zerolog