diff --git a/.travis.yml b/.travis.yml index 9bde515..64d202a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -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 ./... diff --git a/array.go b/array.go index 4597187..9555369 100644 --- a/array.go +++ b/array.go @@ -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 } diff --git a/array_test.go b/array_test.go index 65ca343..952a579 100644 --- a/array_test.go +++ b/array_test.go @@ -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) } } diff --git a/binary_test.go b/binary_test.go new file mode 100644 index 0000000..f878532 --- /dev/null +++ b/binary_test.go @@ -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"} +} diff --git a/console.go b/console.go index 55a081a..ac3e3cd 100644 --- a/console.go +++ b/console.go @@ -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 diff --git a/console_test.go b/console_test.go new file mode 100644 index 0000000..a287f5b --- /dev/null +++ b/console_test.go @@ -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: |INFO| hello world +} diff --git a/context.go b/context.go index ce27d7e..25ef7cf 100644 --- a/context.go +++ b/context.go @@ -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 } diff --git a/diode/diode_example_test.go b/diode/diode_example_test.go new file mode 100644 index 0000000..e04f36b --- /dev/null +++ b/diode/diode_example_test.go @@ -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"} +} diff --git a/diode/diode_test.go b/diode/diode_test.go index f89ccf3..31df653 100644 --- a/diode/diode_test.go +++ b/diode/diode_test.go @@ -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) { diff --git a/encoder_cbor.go b/encoder_cbor.go new file mode 100644 index 0000000..6d001ac --- /dev/null +++ b/encoder_cbor.go @@ -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) +} diff --git a/encoder_json.go b/encoder_json.go new file mode 100644 index 0000000..1e5db80 --- /dev/null +++ b/encoder_json.go @@ -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) +} diff --git a/event.go b/event.go index aba156e..05ee4d8 100644 --- a/event.go +++ b/event.go @@ -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 } diff --git a/fields.go b/fields.go index be03edf..95e83ef 100644 --- a/fields.go +++ b/fields.go @@ -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 diff --git a/hlog/hlog_example_test.go b/hlog/hlog_example_test.go index a70240e..cae91e2 100644 --- a/hlog/hlog_example_test.go +++ b/hlog/hlog_example_test.go @@ -1,3 +1,5 @@ +// +build !binary_log + package hlog_test import ( diff --git a/hlog/hlog_test.go b/hlog/hlog_test.go index 5ac960a..975ce38 100644 --- a/hlog/hlog_test.go +++ b/hlog/hlog_test.go @@ -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) } } diff --git a/hook_test.go b/hook_test.go index 351500e..d051bdc 100644 --- a/hook_test.go +++ b/hook_test.go @@ -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) } }) diff --git a/internal/cbor/README.md b/internal/cbor/README.md new file mode 100644 index 0000000..ff71754 --- /dev/null +++ b/internal/cbor/README.md @@ -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> +``` diff --git a/internal/cbor/base.go b/internal/cbor/base.go new file mode 100644 index 0000000..6096183 --- /dev/null +++ b/internal/cbor/base.go @@ -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 +} diff --git a/internal/cbor/cbor.go b/internal/cbor/cbor.go new file mode 100644 index 0000000..fe216ce --- /dev/null +++ b/internal/cbor/cbor.go @@ -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 +} diff --git a/internal/cbor/decoder.go b/internal/cbor/decoder.go new file mode 100644 index 0000000..71e8648 --- /dev/null +++ b/internal/cbor/decoder.go @@ -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 +} diff --git a/internal/cbor/decoder_test.go b/internal/cbor/decoder_test.go new file mode 100644 index 0000000..b17ba11 --- /dev/null +++ b/internal/cbor/decoder_test.go @@ -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) + } + } +} diff --git a/internal/cbor/string.go b/internal/cbor/string.go new file mode 100644 index 0000000..b90edac --- /dev/null +++ b/internal/cbor/string.go @@ -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...) +} diff --git a/internal/cbor/string_test.go b/internal/cbor/string_test.go new file mode 100644 index 0000000..809b2a3 --- /dev/null +++ b/internal/cbor/string_test.go @@ -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) + } + }) + } +} diff --git a/internal/cbor/time.go b/internal/cbor/time.go new file mode 100644 index 0000000..c8513f2 --- /dev/null +++ b/internal/cbor/time.go @@ -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 +} diff --git a/internal/cbor/time_test.go b/internal/cbor/time_test.go new file mode 100644 index 0000000..507a6ba --- /dev/null +++ b/internal/cbor/time_test.go @@ -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") + } + }) + } +} diff --git a/internal/cbor/types.go b/internal/cbor/types.go new file mode 100644 index 0000000..b983ed7 --- /dev/null +++ b/internal/cbor/types.go @@ -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) +} diff --git a/internal/cbor/types_test.go b/internal/cbor/types_test.go new file mode 100644 index 0000000..010e2a0 --- /dev/null +++ b/internal/cbor/types_test.go @@ -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) + } + } + }) + } +} diff --git a/internal/json/base.go b/internal/json/base.go index 2e03e2e..722bf76 100644 --- a/internal/json/base.go +++ b/internal/json/base.go @@ -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) diff --git a/internal/json/types.go b/internal/json/types.go index 2f3ca24..927d1c8 100644 --- a/internal/json/types.go +++ b/internal/json/types.go @@ -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...) +} diff --git a/log.go b/log.go index 9a73f80..6ca93bb 100644 --- a/log.go +++ b/log.go @@ -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 } diff --git a/log/log_example_test.go b/log/log_example_test.go index 16fed27..4938435 100644 --- a/log/log_example_test.go +++ b/log/log_example_test.go @@ -1,3 +1,5 @@ +// +build !binary_log + package log_test import ( diff --git a/log_example_test.go b/log_example_test.go index 0570ad5..9a1b344 100644 --- a/log_example_test.go +++ b/log_example_test.go @@ -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"} } diff --git a/log_test.go b/log_test.go index 4c3bea6..339f459 100644 --- a/log_test.go +++ b/log_test.go @@ -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) } } diff --git a/sampler_test.go b/sampler_test.go index 35323a2..9010335 100644 --- a/sampler_test.go +++ b/sampler_test.go @@ -1,3 +1,5 @@ +// +build !binary_log + package zerolog import ( diff --git a/syslog.go b/syslog.go index 03be1d5..82b470e 100644 --- a/syslog.go +++ b/syslog.go @@ -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 { diff --git a/syslog_test.go b/syslog_test.go index 95aed65..94d15d9 100644 --- a/syslog_test.go +++ b/syslog_test.go @@ -1,3 +1,4 @@ +// +build !binary_log // +build !windows package zerolog diff --git a/writer_test.go b/writer_test.go index 63e26c7..7d06e4b 100644 --- a/writer_test.go +++ b/writer_test.go @@ -1,3 +1,4 @@ +// +build !binary_log // +build !windows package zerolog