From 8eb285b62bc1d99c05266fb98f64a39084d8928f Mon Sep 17 00:00:00 2001 From: Dan Gillis Date: Fri, 9 Feb 2018 00:00:09 -0500 Subject: [PATCH 01/73] Updates to README.md to help with readability (#32) * Added missed >m< in github.com * Added backquotes to denote "code" where appropriate * Cleanup based on feedback * Added three examples from README * Removed Output for 3 examples * Updated Setting Global Log Level section w/ flags * Removed unnecessary blank lines * Moved flags from init into main * Update log_example_test.go * Cosmetic change based on Olivier's feedback * Pushed back to original * New Examples for Log package * Removed extra spaces * Reorganized file and added examples --- README.md | 165 ++++++++++++++++++++++++++++++---------- log/log_example_test.go | 141 ++++++++++++++++++++++++++++++++++ 2 files changed, 266 insertions(+), 40 deletions(-) create mode 100644 log/log_example_test.go diff --git a/README.md b/README.md index 6fac775..b387c5a 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ The zerolog package provides a fast and simple logger dedicated to JSON output. Zerolog's API is designed to provide both a great developer experience and stunning [performance](#benchmarks). Its unique chaining API allows zerolog to write JSON log events by avoiding allocations and reflection. -The uber's [zap](https://godoc.org/go.uber.org/zap) library pioneered this approach. Zerolog is taking this concept to the next level with simpler to use API and even better performance. +Uber's [zap](https://godoc.org/go.uber.org/zap) library pioneered this approach. Zerolog is taking this concept to the next level with a simpler to use API and even better performance. To keep the code base and the API simple, zerolog focuses on JSON logging only. Pretty logging on the console is made possible using the provided (but inefficient) `zerolog.ConsoleWriter`. @@ -24,43 +24,144 @@ To keep the code base and the API simple, zerolog focuses on JSON logging only. * `net/http` helpers * Pretty logging for development -## Usage +## Installation +```go +go get -u github.com/rs/zerolog/log +``` +## Getting Started +### Simple Logging Example +For simple logging, import the global logger package **github.com/rs/zerolog/log** +```go +package main + +import ( + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" +) + +func main() { + // UNIX Time is faster and smaller than most timestamps + // If you set zerolog.TimeFieldFormat to an empty string, + // logs will write with UNIX time + zerolog.TimeFieldFormat = "" + + log.Print("hello world") +} + +// Output: {"time":1516134303,"level":"debug","message":"hello world"} +``` +> Note: The default log level for `log.Print` is *debug* +---- +### Leveled Logging + +#### Simple Leveled Logging Example ```go -import "github.com/rs/zerolog/log" +package main + +import ( + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" +) + +func main() { + zerolog.TimeFieldFormat = "" + + log.Info().Msg("hello world") +} + +// Output: {"time":1516134303,"level":"info","message":"hello world"} ``` -### A global logger can be use for simple logging +**zerolog** allows for logging at the following levels (from highest to lowest): +- panic (`zerolog.PanicLevel`, 5) +- fatal (`zerolog.FatalLevel`, 4) +- error (`zerolog.ErrorLevel`, 3) +- warn (`zerolog.WarnLevel`, 2) +- info (`zerolog.InfoLevel`, 1) +- debug (`zerolog.DebugLevel`, 0) +You can set the Global logging level to any of these options using the `SetGlobalLevel` function in the zerolog package, passing in one of the given constants above, e.g. `zerolog.InfoLevel` would be the "info" level. Whichever level is chosen, all logs with a level greater than or equal to that level will be written. To turn off logging entirely, pass the `zerolog.Disabled` constant. + +#### Setting Global Log Level +This example uses command-line flags to demonstrate various outputs depending on the chosen log level. ```go -log.Print("hello world") +package main -// Output: {"level":"debug","time":1494567715,"message":"hello world"} +import ( + "flag" + + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" +) + +func main() { + zerolog.TimeFieldFormat = "" + debug := flag.Bool("debug", false, "sets log level to debug") + + flag.Parse() + + // Default level for this example is info, unless debug flag is present + zerolog.SetGlobalLevel(zerolog.InfoLevel) + if *debug { + zerolog.SetGlobalLevel(zerolog.DebugLevel) + } + + log.Debug().Msg("This message appears only when log level set to Debug") + log.Info().Msg("This message appears when log level set to Debug or Info") + + if e := log.Debug(); e.Enabled() { + // Compute log output only if enabled. + value := "bar" + e.Str("foo", value).Msg("some debug message") + } +} +``` +Info Output (no flag) +```bash +$ ./logLevelExample +{"time":1516387492,"level":"info","message":"This message appears when log level set to Debug or Info"} ``` +Debug Output (debug flag set) +```bash +$ ./logLevelExample -debug +{"time":1516387573,"level":"debug","message":"This message appears only when log level set to Debug"} +{"time":1516387573,"level":"info","message":"This message appears when log level set to Debug or Info"} +{"time":1516387573,"level":"debug","foo":"bar","message":"some debug message"} +``` +#### Logging Fatal Messages ```go -log.Info().Msg("hello world") +package main -// Output: {"level":"info","time":1494567715,"message":"hello world"} +import ( + "errors" + + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" +) + +func main() { + err := errors.New("A repo man spends his life getting into tense situations") + service := "myservice" + + zerolog.TimeFieldFormat = "" + + log.Fatal(). + Err(err). + Str("service", service). + Msgf("Cannot start %s", service) +} + +// Output: {"time":1516133263,"level":"fatal","error":"A repo man spends his life getting into tense situations","service":"myservice","message":"Cannot start myservice"} +// exit status 1 ``` +> NOTE: Using `Msgf` generates one allocation even when the logger is disabled. +---------------- +### Contextual Logging -NOTE: To import the global logger, import the `log` subpackage `github.com/rs/zerolog/log`. - -```go -log.Fatal(). - Err(err). - Str("service", service). - Msgf("Cannot start %s", service) - -// Output: {"level":"fatal","time":1494567715,"message":"Cannot start myservice","error":"some error","service":"myservice"} -// Exit 1 -``` - -NOTE: Using `Msgf` generates one allocation even when the logger is disabled. - -### Fields can be added to log messages - +#### Fields can be added to log messages ```go log.Info(). Str("foo", "bar"). @@ -91,22 +192,7 @@ sublogger.Info().Msg("hello world") // Output: {"level":"info","time":1494567715,"message":"hello world","component":"foo"} ``` -### Level logging -```go -zerolog.SetGlobalLevel(zerolog.InfoLevel) - -log.Debug().Msg("filtered out message") -log.Info().Msg("routed message") - -if e := log.Debug(); e.Enabled() { - // Compute log output only if enabled. - value := compute() - e.Str("foo": value).Msg("some debug message") -} - -// Output: {"level":"info","time":1494567715,"message":"routed message"} -``` ### Pretty logging @@ -375,4 +461,3 @@ Log a static string, without any context or `printf`-style templating: | logrus | 1244 ns/op | 1505 B/op | 27 allocs/op | | apex/log | 2751 ns/op | 584 B/op | 11 allocs/op | | log15 | 5181 ns/op | 1592 B/op | 26 allocs/op | - diff --git a/log/log_example_test.go b/log/log_example_test.go new file mode 100644 index 0000000..b52cb78 --- /dev/null +++ b/log/log_example_test.go @@ -0,0 +1,141 @@ +package log_test + +import ( + "errors" + "flag" + "os" + "time" + + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" +) + +// setup would normally be an init() function, however, there seems +// to be something awry with the testing framework when we set the +// global Logger from an init() +func setup() { + // UNIX Time is faster and smaller than most timestamps + // If you set zerolog.TimeFieldFormat to an empty string, + // logs will write with UNIX time + zerolog.TimeFieldFormat = "" + // In order to always output a static time to stdout for these + // examples to pass, we need to override zerolog.TimestampFunc + // and log.Logger globals -- you would not normally need to do this + zerolog.TimestampFunc = func() time.Time { + return time.Date(2008, 1, 8, 17, 5, 05, 0, time.UTC) + } + log.Logger = zerolog.New(os.Stdout).With().Timestamp().Logger() +} + +// Simple logging example using the Print function in the log package +// Note that both Print and Printf are at the debug log level by default +func ExamplePrint() { + setup() + + log.Print("hello world") + // Output: {"time":1199811905,"level":"debug","message":"hello world"} +} + +// Simple logging example using the Printf function in the log package +func ExamplePrintf() { + setup() + + log.Printf("hello %s", "world") + // Output: {"time":1199811905,"level":"debug","message":"hello world"} +} + +// Example of a log with no particular "level" +func ExampleLog() { + setup() + log.Log().Msg("hello world") + + // Output: {"time":1199811905,"message":"hello world"} +} + +// Example of a log at a particular "level" (in this case, "debug") +func ExampleDebug() { + setup() + log.Debug().Msg("hello world") + + // Output: {"time":1199811905,"level":"debug","message":"hello world"} +} + +// Example of a log at a particular "level" (in this case, "info") +func ExampleInfo() { + setup() + log.Info().Msg("hello world") + + // Output: {"time":1199811905,"level":"info","message":"hello world"} +} + +// Example of a log at a particular "level" (in this case, "warn") +func ExampleWarn() { + setup() + log.Warn().Msg("hello world") + + // Output: {"time":1199811905,"level":"warn","message":"hello world"} +} + +// Example of a log at a particular "level" (in this case, "error") +func ExampleError() { + setup() + log.Error().Msg("hello world") + + // Output: {"time":1199811905,"level":"error","message":"hello world"} +} + +// Example of a log at a particular "level" (in this case, "fatal") +func ExampleFatal() { + setup() + err := errors.New("A repo man spends his life getting into tense situations") + service := "myservice" + + log.Fatal(). + Err(err). + Str("service", service). + Msgf("Cannot start %s", service) + + // Outputs: {"time":1199811905,"level":"fatal","error":"A repo man spends his life getting into tense situations","service":"myservice","message":"Cannot start myservice"} +} + +// TODO: Panic + +// This example uses command-line flags to demonstrate various outputs +// depending on the chosen log level. +func ExampleLevelFlag() { + setup() + debug := flag.Bool("debug", false, "sets log level to debug") + + flag.Parse() + + // Default level for this example is info, unless debug flag is present + zerolog.SetGlobalLevel(zerolog.InfoLevel) + if *debug { + zerolog.SetGlobalLevel(zerolog.DebugLevel) + } + + log.Debug().Msg("This message appears only when log level set to Debug") + log.Info().Msg("This message appears when log level set to Debug or Info") + + if e := log.Debug(); e.Enabled() { + // Compute log output only if enabled. + value := "bar" + e.Str("foo", value).Msg("some debug message") + } + + // Output: {"time":1199811905,"level":"info","message":"This message appears when log level set to Debug or Info"} +} + +// TODO: Output + +// TODO: With + +// TODO: Level + +// TODO: Sample + +// TODO: Hook + +// TODO: WithLevel + +// TODO: Ctx From 7d8f9e5cf010402aa908d6d1b4f92094cf8b88cd Mon Sep 17 00:00:00 2001 From: Olivier Poitrey Date: Thu, 8 Feb 2018 21:29:16 -0800 Subject: [PATCH 02/73] Fix unit tests --- log/log_example_test.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/log/log_example_test.go b/log/log_example_test.go index b52cb78..d97a919 100644 --- a/log/log_example_test.go +++ b/log/log_example_test.go @@ -33,7 +33,7 @@ func ExamplePrint() { setup() log.Print("hello world") - // Output: {"time":1199811905,"level":"debug","message":"hello world"} + // Output: {"level":"debug","time":1199811905,"message":"hello world"} } // Simple logging example using the Printf function in the log package @@ -41,7 +41,7 @@ func ExamplePrintf() { setup() log.Printf("hello %s", "world") - // Output: {"time":1199811905,"level":"debug","message":"hello world"} + // Output: {"level":"debug","time":1199811905,"message":"hello world"} } // Example of a log with no particular "level" @@ -57,7 +57,7 @@ func ExampleDebug() { setup() log.Debug().Msg("hello world") - // Output: {"time":1199811905,"level":"debug","message":"hello world"} + // Output: {"level":"debug","time":1199811905,"message":"hello world"} } // Example of a log at a particular "level" (in this case, "info") @@ -65,7 +65,7 @@ func ExampleInfo() { setup() log.Info().Msg("hello world") - // Output: {"time":1199811905,"level":"info","message":"hello world"} + // Output: {"level":"info","time":1199811905,"message":"hello world"} } // Example of a log at a particular "level" (in this case, "warn") @@ -73,7 +73,7 @@ func ExampleWarn() { setup() log.Warn().Msg("hello world") - // Output: {"time":1199811905,"level":"warn","message":"hello world"} + // Output: {"level":"warn","time":1199811905,"message":"hello world"} } // Example of a log at a particular "level" (in this case, "error") @@ -81,7 +81,7 @@ func ExampleError() { setup() log.Error().Msg("hello world") - // Output: {"time":1199811905,"level":"error","message":"hello world"} + // Output: {"level":"error","time":1199811905,"message":"hello world"} } // Example of a log at a particular "level" (in this case, "fatal") @@ -95,7 +95,7 @@ func ExampleFatal() { Str("service", service). Msgf("Cannot start %s", service) - // Outputs: {"time":1199811905,"level":"fatal","error":"A repo man spends his life getting into tense situations","service":"myservice","message":"Cannot start myservice"} + // Outputs: {"level":"fatal","time":1199811905,"error":"A repo man spends his life getting into tense situations","service":"myservice","message":"Cannot start myservice"} } // TODO: Panic @@ -123,7 +123,7 @@ func ExampleLevelFlag() { e.Str("foo", value).Msg("some debug message") } - // Output: {"time":1199811905,"level":"info","message":"This message appears when log level set to Debug or Info"} + // Output: {"level":"info","time":1199811905,"message":"This message appears when log level set to Debug or Info"} } // TODO: Output From 9a92fd2536f399020c8b1270af747af2d369cbee Mon Sep 17 00:00:00 2001 From: Olivier Poitrey Date: Sat, 10 Feb 2018 18:57:53 -0800 Subject: [PATCH 03/73] Fix typoes --- hlog/hlog.go | 2 +- log/log.go | 4 ++-- log/log_example_test.go | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/hlog/hlog.go b/hlog/hlog.go index bbe8b12..2026214 100644 --- a/hlog/hlog.go +++ b/hlog/hlog.go @@ -124,7 +124,7 @@ func RefererHandler(fieldKey string) func(next http.Handler) http.Handler { type idKey struct{} -// IDFromRequest returns the unique id accociated to the request if any. +// IDFromRequest returns the unique id associated to the request if any. func IDFromRequest(r *http.Request) (id xid.ID, ok bool) { if r == nil { return diff --git a/log/log.go b/log/log.go index ad61913..dd92ab9 100644 --- a/log/log.go +++ b/log/log.go @@ -22,7 +22,7 @@ func With() zerolog.Context { return Logger.With() } -// Level crestes a child logger with the minium accepted level set to level. +// Level creates a child logger with the minimum accepted level set to level. func Level(level zerolog.Level) zerolog.Logger { return Logger.Level(level) } @@ -89,7 +89,7 @@ func WithLevel(level zerolog.Level) *zerolog.Event { } // Log starts a new message with no level. Setting zerolog.GlobalLevel to -// zerlog.Disabled will still disable events produced by this method. +// zerolog.Disabled will still disable events produced by this method. // // You must call Msg on the returned event in order to send the event. func Log() *zerolog.Event { diff --git a/log/log_example_test.go b/log/log_example_test.go index d97a919..32f3699 100644 --- a/log/log_example_test.go +++ b/log/log_example_test.go @@ -102,7 +102,7 @@ func ExampleFatal() { // This example uses command-line flags to demonstrate various outputs // depending on the chosen log level. -func ExampleLevelFlag() { +func Example_LevelFlag() { setup() debug := flag.Bool("debug", false, "sets log level to debug") From 56a970de510213e50dbaa39ad73ac07c9ec75606 Mon Sep 17 00:00:00 2001 From: Olivier Poitrey Date: Mon, 12 Feb 2018 16:05:27 -0800 Subject: [PATCH 04/73] Add RawJSON field type --- context.go | 9 +++++++++ event.go | 9 +++++++++ log_test.go | 6 ++++-- 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/context.go b/context.go index 4137287..7e86ea1 100644 --- a/context.go +++ b/context.go @@ -79,6 +79,15 @@ func (c Context) Bytes(key string, val []byte) Context { return c } +// RawJSON adds already encoded JSON to 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...) + 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 { diff --git a/event.go b/event.go index 66e0489..bb3bf69 100644 --- a/event.go +++ b/event.go @@ -218,6 +218,15 @@ func (e *Event) Bytes(key string, val []byte) *Event { return e } +// RawJSON adds already encoded JSON to the log line under key. +// +// No sanity check is performed on b; it must not contain carriage returns and +// be valid JSON. +func (e *Event) RawJSON(key string, b []byte) *Event { + e.buf = append(json.AppendKey(e.buf, key), b...) + return e +} + // AnErr adds the field key with err as a string to the *Event context. // If err is nil, no field is added. func (e *Event) AnErr(key string, err error) *Event { diff --git a/log_test.go b/log_test.go index a0aeb03..38ce7f1 100644 --- a/log_test.go +++ b/log_test.go @@ -78,6 +78,7 @@ func TestWith(t *testing.T) { out := &bytes.Buffer{} ctx := New(out).With(). Str("foo", "bar"). + RawJSON("json", []byte(`{"some":"json"}`)). AnErr("some_err", nil). Err(errors.New("some error")). Bool("bool", true). @@ -98,7 +99,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(), `{"foo":"bar","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 := out.String(), `{"foo":"bar","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) } } @@ -142,6 +143,7 @@ func TestFields(t *testing.T) { Caller(). Str("string", "foo"). Bytes("bytes", []byte("bar")). + RawJSON("json", []byte(`{"some":"json"}`)). AnErr("some_err", nil). Err(errors.New("some error")). Bool("bool", true). @@ -161,7 +163,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","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 := out.String(), `{"caller":"`+caller+`","string":"foo","bytes":"bar","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) } } From a717e7cbed40597b77cf385542a797dc048f9a69 Mon Sep 17 00:00:00 2001 From: Olivier Poitrey Date: Tue, 13 Feb 2018 00:20:42 -0800 Subject: [PATCH 05/73] Improve ConsoleWriter of non-scalar types --- console.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/console.go b/console.go index ca15045..55a081a 100644 --- a/console.go +++ b/console.go @@ -75,8 +75,15 @@ func (w ConsoleWriter) Write(p []byte) (n int, err error) { } else { buf.WriteString(value) } - default: + case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64: fmt.Fprint(buf, value) + default: + b, err := json.Marshal(value) + if err != nil { + fmt.Fprintf(buf, "[error: %v]", err) + } else { + fmt.Fprint(buf, string(b)) + } } } buf.WriteByte('\n') From 9ee98f91c4e05af131b20fe79acdade85dca08fe Mon Sep 17 00:00:00 2001 From: Giovanni Bajo Date: Tue, 13 Feb 2018 20:18:01 +0100 Subject: [PATCH 06/73] Remove allocations while logging an Array of Objects. (#38) --- array.go | 1 + benchmark_test.go | 16 ++++++++++++++++ event.go | 7 ++++--- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/array.go b/array.go index ca17b8e..cfce02c 100644 --- a/array.go +++ b/array.go @@ -48,6 +48,7 @@ func (a *Array) Object(obj LogObjectMarshaler) *Array { obj.MarshalZerologObject(e) e.buf = append(e.buf, '}') a.buf = append(a.buf, e.buf...) + eventPool.Put(e) return a } diff --git a/benchmark_test.go b/benchmark_test.go index 25e9225..4ae9d49 100644 --- a/benchmark_test.go +++ b/benchmark_test.go @@ -96,6 +96,22 @@ func (o obj) MarshalZerologObject(e *Event) { Int("priv", o.priv) } +func BenchmarkLogArrayObject(b *testing.B) { + obj1 := obj{"a", "b", 2} + obj2 := obj{"c", "d", 3} + obj3 := obj{"e", "f", 4} + logger := New(ioutil.Discard) + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + arr := Arr() + arr.Object(&obj1) + arr.Object(&obj2) + arr.Object(&obj3) + logger.Info().Array("objects", arr).Msg("test") + } +} + func BenchmarkLogFieldType(b *testing.B) { bools := []bool{true, false, true, false, true, false, true, false, true, false} ints := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} diff --git a/event.go b/event.go index bb3bf69..d49bea4 100644 --- a/event.go +++ b/event.go @@ -2,7 +2,6 @@ package zerolog import ( "fmt" - "io/ioutil" "os" "runtime" "strconv" @@ -61,7 +60,9 @@ func (e *Event) write() (err error) { return nil } e.buf = append(e.buf, '}', '\n') - _, err = e.w.WriteLevel(e.level, e.buf) + if e.w != nil { + _, err = e.w.WriteLevel(e.level, e.buf) + } eventPool.Put(e) return } @@ -142,7 +143,7 @@ func (e *Event) Dict(key string, dict *Event) *Event { // Call usual field methods like Str, Int etc to add fields to this // event and give it as argument the *Event.Dict method. func Dict() *Event { - return newEvent(levelWriterAdapter{ioutil.Discard}, 0, true) + return newEvent(nil, 0, true) } // Array adds the field key with an array to the event context. From 8c1c6a0cd7c081326ce0743ea905b1d85a89e63d Mon Sep 17 00:00:00 2001 From: Olivier Poitrey Date: Tue, 20 Feb 2018 01:52:12 -0800 Subject: [PATCH 07/73] Add diode.Writer, a thread-safe, lock-free, non-blocking writer wrapper --- README.md | 23 +++++++++--- diode/diode.go | 88 +++++++++++++++++++++++++++++++++++++++++++++ diode/diode_test.go | 44 +++++++++++++++++++++++ 3 files changed, 151 insertions(+), 4 deletions(-) create mode 100644 diode/diode.go create mode 100644 diode/diode_test.go diff --git a/README.md b/README.md index b387c5a..76c51b8 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ func main() { // Output: {"time":1516134303,"level":"debug","message":"hello world"} ``` > Note: The default log level for `log.Print` is *debug* ----- + ### Leveled Logging #### Simple Leveled Logging Example @@ -84,7 +84,9 @@ func main() { You can set the Global logging level to any of these options using the `SetGlobalLevel` function in the zerolog package, passing in one of the given constants above, e.g. `zerolog.InfoLevel` would be the "info" level. Whichever level is chosen, all logs with a level greater than or equal to that level will be written. To turn off logging entirely, pass the `zerolog.Disabled` constant. #### Setting Global Log Level + This example uses command-line flags to demonstrate various outputs depending on the chosen log level. + ```go package main @@ -158,7 +160,7 @@ func main() { // exit status 1 ``` > NOTE: Using `Msgf` generates one allocation even when the logger is disabled. ----------------- + ### Contextual Logging #### Fields can be added to log messages @@ -192,8 +194,6 @@ sublogger.Info().Msg("hello world") // Output: {"level":"info","time":1494567715,"message":"hello world","component":"foo"} ``` - - ### Pretty logging ```go @@ -245,6 +245,21 @@ log.Log().Str("foo","bar").Msg("") log.Logger = log.With().Str("foo", "bar").Logger() ``` +### Thread-safe, lock-free, non-blocking writer + +If your writer might be slow or not thread-safe and you need your log producers to never get slowed down by a slow writer, you can use a `diode.Writer` as follow: + +```go +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") +``` + +You will need to install `code.cloudfoundry.org/go-diodes` to use this feature. + ### Log Sampling ```go diff --git a/diode/diode.go b/diode/diode.go new file mode 100644 index 0000000..377a523 --- /dev/null +++ b/diode/diode.go @@ -0,0 +1,88 @@ +// Package diode provides a thread-safe, lock-free, non-blocking io.Writer +// wrapper. +package diode + +import ( + "context" + "io" + "sync" + "time" + + diodes "code.cloudfoundry.org/go-diodes" +) + +var bufPool = &sync.Pool{ + New: func() interface{} { + return make([]byte, 0, 500) + }, +} + +// Writer is a io.Writer wrapper that uses a diode to make Write lock-free, +// non-blocking and thread safe. +type Writer struct { + w io.Writer + d *diodes.ManyToOne + p *diodes.Poller + c context.CancelFunc + done chan struct{} +} + +// NewWriter creates a writer wrapping w with a many-to-one diode in order to +// never block log producers and drop events if the writer can't keep up with +// the flow of data. +// +// Use a diode.Writer when +// +// d := diodes.NewManyToOne(1000, diodes.AlertFunc(func(missed int) { +// log.Printf("Dropped %d messages", missed) +// })) +// w := diode.NewWriter(w, d, 10 * time.Millisecond) +// log := zerolog.New(w) +// +// See code.cloudfoundry.org/go-diodes for more info on diode. +func NewWriter(w io.Writer, manyToOneDiode *diodes.ManyToOne, poolInterval time.Duration) Writer { + ctx, cancel := context.WithCancel(context.Background()) + dw := Writer{ + w: w, + d: manyToOneDiode, + p: diodes.NewPoller(manyToOneDiode, + diodes.WithPollingInterval(poolInterval), + diodes.WithPollingContext(ctx)), + c: cancel, + done: make(chan struct{}), + } + go dw.poll() + return dw +} + +func (dw Writer) Write(p []byte) (n int, err error) { + // p is pooled in zerolog so we can't hold it passed this call, hence the + // copy. + p = append(bufPool.Get().([]byte), p...) + dw.d.Set(diodes.GenericDataType(&p)) + return len(p), nil +} + +// Close releases the diode poller and call Close on the wrapped writer if +// io.Closer is implemented. +func (dw Writer) Close() error { + dw.c() + <-dw.done + if w, ok := dw.w.(io.Closer); ok { + return w.Close() + } + return nil +} + +func (dw Writer) poll() { + defer close(dw.done) + for { + d := dw.p.Next() + if d == nil { + return + } + p := *(*[]byte)(d) + dw.w.Write(p) + bufPool.Put(p[:0]) + } +} diff --git a/diode/diode_test.go b/diode/diode_test.go new file mode 100644 index 0000000..f89ccf3 --- /dev/null +++ b/diode/diode_test.go @@ -0,0 +1,44 @@ +package diode_test + +import ( + "fmt" + "io/ioutil" + "log" + "os" + "testing" + "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"} +} + +func Benchmark(b *testing.B) { + log.SetOutput(ioutil.Discard) + defer log.SetOutput(os.Stderr) + d := diodes.NewManyToOne(100000, nil) + w := diode.NewWriter(ioutil.Discard, d, 10*time.Millisecond) + log := zerolog.New(w) + defer w.Close() + + b.SetParallelism(1000) + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + log.Print("test") + } + }) + +} From b62d797a8d78a41902a3beb2bc1acd008752c145 Mon Sep 17 00:00:00 2001 From: Kai Ren Date: Thu, 8 Mar 2018 17:41:28 +0200 Subject: [PATCH 08/73] Mention fields duplication caveat in documentation (#41) --- README.md | 15 +++++++++++++++ context.go | 2 ++ event.go | 3 +++ log.go | 14 ++++++++++++++ 4 files changed, 34 insertions(+) diff --git a/README.md b/README.md index 76c51b8..7a034b4 100644 --- a/README.md +++ b/README.md @@ -476,3 +476,18 @@ Log a static string, without any context or `printf`-style templating: | logrus | 1244 ns/op | 1505 B/op | 27 allocs/op | | apex/log | 2751 ns/op | 584 B/op | 11 allocs/op | | log15 | 5181 ns/op | 1592 B/op | 26 allocs/op | + +## Caveats + +There is no fields deduplication out-of-the-box. +Using the same key multiple times creates new key in final JSON each time. + +```go +logger := zerolog.New(os.Stderr).With().Timestamp().Logger() +logger.Info(). + Timestamp(). + Msg("dup") +// Output: {"level":"info","time":1494567715,"time":1494567715,"message":"dup"} +``` + +However, it’s not a big deal though as JSON accepts dup keys, the last one prevails. diff --git a/context.go b/context.go index 7e86ea1..26f8a2a 100644 --- a/context.go +++ b/context.go @@ -277,6 +277,8 @@ var th = timestampHook{} // Timestamp adds the current local time as UNIX timestamp to the logger context with the "time" key. // To customize the key name, change zerolog.TimestampFieldName. +// +// NOTE: It won't dedupe the "time" key if the *Context has one already. func (c Context) Timestamp() Context { c.l = c.l.Hook(th) return c diff --git a/event.go b/event.go index d49bea4..13358e1 100644 --- a/event.go +++ b/event.go @@ -499,6 +499,9 @@ func (e *Event) Floats64(key string, f []float64) *Event { // Timestamp adds the current local time as UNIX timestamp to the *Event context with the "time" key. // To customize the key name, change zerolog.TimestampFieldName. +// +// NOTE: It won't dedupe the "time" key if the *Event (or *Context) has one +// already. func (e *Event) Timestamp() *Event { if e == nil { return e diff --git a/log.go b/log.go index 843a94b..9a73f80 100644 --- a/log.go +++ b/log.go @@ -82,6 +82,20 @@ // log.Warn().Msg("") // // Output: {"level":"warn","severity":"warn"} // +// +// Caveats +// +// There is no fields deduplication out-of-the-box. +// Using the same key multiple times creates new key in final JSON each time. +// +// logger := zerolog.New(os.Stderr).With().Timestamp().Logger() +// logger.Info(). +// Timestamp(). +// Msg("dup") +// // Output: {"level":"info","time":1494567715,"time":1494567715,"message":"dup"} +// +// However, it’s not a big deal though as JSON accepts dup keys, +// the last one prevails. package zerolog import ( From 1c575db92819e737bf5f8751e6cc90d2ccd273e1 Mon Sep 17 00:00:00 2001 From: Max Wolter Date: Thu, 15 Mar 2018 18:29:26 +0100 Subject: [PATCH 09/73] Add support for hex-encoded of byte slice (#42) --- array.go | 6 ++++ array_test.go | 4 ++- context.go | 6 ++++ event.go | 9 ++++++ internal/json/base.go | 5 ++++ internal/json/string.go | 15 ++++++++++ internal/json/string_test.go | 28 +++++++++++++++--- internal/json/time.go | 8 ++++++ internal/json/types.go | 56 ++++++++++++++++++++++++++++++++++++ log_test.go | 10 +++++-- 10 files changed, 139 insertions(+), 8 deletions(-) diff --git a/array.go b/array.go index cfce02c..4597187 100644 --- a/array.go +++ b/array.go @@ -64,6 +64,12 @@ func (a *Array) Bytes(val []byte) *Array { 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) + 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) diff --git a/array_test.go b/array_test.go index 02fe9ae..65ca343 100644 --- a/array_test.go +++ b/array_test.go @@ -21,9 +21,11 @@ func TestArray(t *testing.T) { Float32(11). Float64(12). Str("a"). + Bytes([]byte("b")). + Hex([]byte{0x1f}). Time(time.Time{}). Dur(0) - want := `[true,1,2,3,4,5,6,7,8,9,10,11,12,"a","0001-01-01T00:00:00Z",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 { t.Errorf("Array.write()\ngot: %s\nwant: %s", got, want) } diff --git a/context.go b/context.go index 26f8a2a..ce27d7e 100644 --- a/context.go +++ b/context.go @@ -79,6 +79,12 @@ func (c Context) Bytes(key string, val []byte) Context { 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) + return c +} + // RawJSON adds already encoded JSON to context. // // No sanity check is performed on b; it must not contain carriage returns and diff --git a/event.go b/event.go index 13358e1..e451f5d 100644 --- a/event.go +++ b/event.go @@ -219,6 +219,15 @@ func (e *Event) Bytes(key string, val []byte) *Event { return e } +// Hex adds the field key with val as a hex string to the *Event context. +func (e *Event) Hex(key string, val []byte) *Event { + if e == nil { + return e + } + e.buf = json.AppendHex(json.AppendKey(e.buf, key), val) + return e +} + // RawJSON adds already encoded JSON to the log line under key. // // No sanity check is performed on b; it must not contain carriage returns and diff --git a/internal/json/base.go b/internal/json/base.go index 7baeec5..2e03e2e 100644 --- a/internal/json/base.go +++ b/internal/json/base.go @@ -1,5 +1,6 @@ package json +// AppendKey appends a new key to the output JSON. func AppendKey(dst []byte, key string) []byte { if len(dst) > 1 { dst = append(dst, ',') @@ -8,6 +9,8 @@ func AppendKey(dst []byte, key string) []byte { return append(dst, ':') } +// AppendError encodes the error string to json and appends +// the encoded string to the input byte slice. func AppendError(dst []byte, err error) []byte { if err == nil { return append(dst, `null`...) @@ -15,6 +18,8 @@ func AppendError(dst []byte, err error) []byte { return AppendString(dst, err.Error()) } +// AppendErrors encodes the error strings to json and +// appends the encoded string list to the input byte slice. func AppendErrors(dst []byte, errs []error) []byte { if len(errs) == 0 { return append(dst, '[', ']') diff --git a/internal/json/string.go b/internal/json/string.go index 8f8f4df..7f85ad6 100644 --- a/internal/json/string.go +++ b/internal/json/string.go @@ -4,6 +4,8 @@ import "unicode/utf8" const hex = "0123456789abcdef" +// AppendStrings encodes the input strings to json and +// appends the encoded string list to the input byte slice. func AppendStrings(dst []byte, vals []string) []byte { if len(vals) == 0 { return append(dst, '[', ']') @@ -123,6 +125,19 @@ func AppendBytes(dst, s []byte) []byte { return append(dst, '"') } +// AppendHex encodes the input bytes to a hex string and appends +// the encoded string to the input byte slice. +// +// The operation loops though each byte and encodes it as hex using +// the hex lookup table. +func AppendHex(dst, s []byte) []byte { + dst = append(dst, '"') + for _, v := range s { + dst = append(dst, hex[v>>4], hex[v&0x0f]) + } + return append(dst, '"') +} + // appendBytesComplex is a mirror of the appendStringComplex // with []byte arg func appendBytesComplex(dst, s []byte, i int) []byte { diff --git a/internal/json/string_test.go b/internal/json/string_test.go index b0c2c32..0d5fc6c 100644 --- a/internal/json/string_test.go +++ b/internal/json/string_test.go @@ -53,7 +53,18 @@ var encodeStringTests = []struct { {"emoji \u2764\ufe0f!", `"emoji ❤️!"`}, } -func TestappendString(t *testing.T) { +var encodeHexTests = []struct { + in byte + out string +}{ + {0x00, `"00"`}, + {0x0f, `"0f"`}, + {0x10, `"10"`}, + {0xf0, `"f0"`}, + {0xff, `"ff"`}, +} + +func TestAppendString(t *testing.T) { for _, tt := range encodeStringTests { b := AppendString([]byte{}, tt.in) if got, want := string(b), tt.out; got != want { @@ -62,7 +73,7 @@ func TestappendString(t *testing.T) { } } -func TestappendBytes(t *testing.T) { +func TestAppendBytes(t *testing.T) { for _, tt := range encodeStringTests { b := AppendBytes([]byte{}, []byte(tt.in)) if got, want := string(b), tt.out; got != want { @@ -71,6 +82,15 @@ func TestappendBytes(t *testing.T) { } } +func TestAppendHex(t *testing.T) { + for _, tt := range encodeHexTests { + b := AppendHex([]byte{}, []byte{tt.in}) + if got, want := string(b), tt.out; got != want { + t.Errorf("appendHex(%x) = %s, want %s", tt.in, got, want) + } + } +} + func TestStringBytes(t *testing.T) { t.Parallel() // Test that encodeState.stringBytes and encodeState.string use the same encoding. @@ -108,7 +128,7 @@ func TestStringBytes(t *testing.T) { } } -func BenchmarkappendString(b *testing.B) { +func BenchmarkAppendString(b *testing.B) { tests := map[string]string{ "NoEncoding": `aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa`, "EncodingFirst": `"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa`, @@ -128,7 +148,7 @@ func BenchmarkappendString(b *testing.B) { } } -func BenchmarkappendBytes(b *testing.B) { +func BenchmarkAppendBytes(b *testing.B) { tests := map[string]string{ "NoEncoding": `aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa`, "EncodingFirst": `"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa`, diff --git a/internal/json/time.go b/internal/json/time.go index 612438d..866cc4d 100644 --- a/internal/json/time.go +++ b/internal/json/time.go @@ -5,6 +5,8 @@ import ( "time" ) +// AppendTime formats the input time with the given format +// and appends the encoded string to the input byte slice. func AppendTime(dst []byte, t time.Time, format string) []byte { if format == "" { return AppendInt64(dst, t.Unix()) @@ -12,6 +14,8 @@ func AppendTime(dst []byte, t time.Time, format string) []byte { return append(t.AppendFormat(append(dst, '"'), format), '"') } +// AppendTimes converts the input times with the given format +// and appends the encoded string list to the input byte slice. func AppendTimes(dst []byte, vals []time.Time, format string) []byte { if format == "" { return appendUnixTimes(dst, vals) @@ -45,6 +49,8 @@ func appendUnixTimes(dst []byte, vals []time.Time) []byte { return dst } +// AppendDuration formats the input duration with the given unit & format +// and appends the encoded string to the input byte slice. func AppendDuration(dst []byte, d time.Duration, unit time.Duration, useInt bool) []byte { if useInt { return strconv.AppendInt(dst, int64(d/unit), 10) @@ -52,6 +58,8 @@ func AppendDuration(dst []byte, d time.Duration, unit time.Duration, useInt bool return AppendFloat64(dst, float64(d)/float64(unit)) } +// AppendDurations formats the input durations with the given unit & format +// and appends the encoded string list to the input byte slice. func AppendDurations(dst []byte, vals []time.Duration, unit time.Duration, useInt bool) []byte { if len(vals) == 0 { return append(dst, '[', ']') diff --git a/internal/json/types.go b/internal/json/types.go index bbc8e42..2f3ca24 100644 --- a/internal/json/types.go +++ b/internal/json/types.go @@ -7,10 +7,14 @@ import ( "strconv" ) +// AppendBool converts the input bool to a string and +// appends the encoded string to the input byte slice. func AppendBool(dst []byte, val bool) []byte { return strconv.AppendBool(dst, val) } +// AppendBools encodes the input bools to json and +// appends the encoded string list to the input byte slice. func AppendBools(dst []byte, vals []bool) []byte { if len(vals) == 0 { return append(dst, '[', ']') @@ -26,10 +30,14 @@ func AppendBools(dst []byte, vals []bool) []byte { return dst } +// AppendInt converts the input int to a string and +// appends the encoded string to the input byte slice. func AppendInt(dst []byte, val int) []byte { return strconv.AppendInt(dst, int64(val), 10) } +// AppendInts encodes the input ints to json and +// appends the encoded string list to the input byte slice. func AppendInts(dst []byte, vals []int) []byte { if len(vals) == 0 { return append(dst, '[', ']') @@ -45,10 +53,14 @@ func AppendInts(dst []byte, vals []int) []byte { return dst } +// AppendInt8 converts the input []int8 to a string and +// appends the encoded string to the input byte slice. func AppendInt8(dst []byte, val int8) []byte { return strconv.AppendInt(dst, int64(val), 10) } +// AppendInts8 encodes the input int8s to json and +// appends the encoded string list to the input byte slice. func AppendInts8(dst []byte, vals []int8) []byte { if len(vals) == 0 { return append(dst, '[', ']') @@ -64,10 +76,14 @@ func AppendInts8(dst []byte, vals []int8) []byte { return dst } +// AppendInt16 converts the input int16 to a string and +// appends the encoded string to the input byte slice. func AppendInt16(dst []byte, val int16) []byte { return strconv.AppendInt(dst, int64(val), 10) } +// AppendInts16 encodes the input int16s to json and +// appends the encoded string list to the input byte slice. func AppendInts16(dst []byte, vals []int16) []byte { if len(vals) == 0 { return append(dst, '[', ']') @@ -83,10 +99,14 @@ func AppendInts16(dst []byte, vals []int16) []byte { return dst } +// AppendInt32 converts the input int32 to a string and +// appends the encoded string to the input byte slice. func AppendInt32(dst []byte, val int32) []byte { return strconv.AppendInt(dst, int64(val), 10) } +// AppendInts32 encodes the input int32s to json and +// appends the encoded string list to the input byte slice. func AppendInts32(dst []byte, vals []int32) []byte { if len(vals) == 0 { return append(dst, '[', ']') @@ -102,10 +122,14 @@ func AppendInts32(dst []byte, vals []int32) []byte { return dst } +// AppendInt64 converts the input int64 to a string and +// appends the encoded string to the input byte slice. func AppendInt64(dst []byte, val int64) []byte { return strconv.AppendInt(dst, val, 10) } +// AppendInts64 encodes the input int64s to json and +// appends the encoded string list to the input byte slice. func AppendInts64(dst []byte, vals []int64) []byte { if len(vals) == 0 { return append(dst, '[', ']') @@ -121,10 +145,14 @@ func AppendInts64(dst []byte, vals []int64) []byte { return dst } +// AppendUint converts the input uint to a string and +// appends the encoded string to the input byte slice. func AppendUint(dst []byte, val uint) []byte { return strconv.AppendUint(dst, uint64(val), 10) } +// AppendUints encodes the input uints to json and +// appends the encoded string list to the input byte slice. func AppendUints(dst []byte, vals []uint) []byte { if len(vals) == 0 { return append(dst, '[', ']') @@ -140,10 +168,14 @@ func AppendUints(dst []byte, vals []uint) []byte { return dst } +// AppendUint8 converts the input uint8 to a string and +// appends the encoded string to the input byte slice. func AppendUint8(dst []byte, val uint8) []byte { return strconv.AppendUint(dst, uint64(val), 10) } +// AppendUints8 encodes the input uint8s to json and +// appends the encoded string list to the input byte slice. func AppendUints8(dst []byte, vals []uint8) []byte { if len(vals) == 0 { return append(dst, '[', ']') @@ -159,10 +191,14 @@ func AppendUints8(dst []byte, vals []uint8) []byte { return dst } +// AppendUint16 converts the input uint16 to a string and +// appends the encoded string to the input byte slice. func AppendUint16(dst []byte, val uint16) []byte { return strconv.AppendUint(dst, uint64(val), 10) } +// AppendUints16 encodes the input uint16s to json and +// appends the encoded string list to the input byte slice. func AppendUints16(dst []byte, vals []uint16) []byte { if len(vals) == 0 { return append(dst, '[', ']') @@ -178,10 +214,14 @@ func AppendUints16(dst []byte, vals []uint16) []byte { return dst } +// AppendUint32 converts the input uint32 to a string and +// appends the encoded string to the input byte slice. func AppendUint32(dst []byte, val uint32) []byte { return strconv.AppendUint(dst, uint64(val), 10) } +// AppendUints32 encodes the input uint32s to json and +// appends the encoded string list to the input byte slice. func AppendUints32(dst []byte, vals []uint32) []byte { if len(vals) == 0 { return append(dst, '[', ']') @@ -197,10 +237,14 @@ func AppendUints32(dst []byte, vals []uint32) []byte { return dst } +// AppendUint64 converts the input uint64 to a string and +// appends the encoded string to the input byte slice. func AppendUint64(dst []byte, val uint64) []byte { return strconv.AppendUint(dst, uint64(val), 10) } +// AppendUints64 encodes the input uint64s to json and +// appends the encoded string list to the input byte slice. func AppendUints64(dst []byte, vals []uint64) []byte { if len(vals) == 0 { return append(dst, '[', ']') @@ -216,6 +260,8 @@ func AppendUints64(dst []byte, vals []uint64) []byte { return dst } +// AppendFloat converts the input float to a string and +// appends the encoded string to the input byte slice. func AppendFloat(dst []byte, val float64, bitSize int) []byte { // JSON does not permit NaN or Infinity. A typical JSON encoder would fail // with an error, but a logging library wants the data to get thru so we @@ -231,10 +277,14 @@ func AppendFloat(dst []byte, val float64, bitSize int) []byte { return strconv.AppendFloat(dst, val, 'f', -1, bitSize) } +// AppendFloat32 converts the input float32 to a string and +// appends the encoded string to the input byte slice. func AppendFloat32(dst []byte, val float32) []byte { return AppendFloat(dst, float64(val), 32) } +// AppendFloats32 encodes the input float32s to json and +// appends the encoded string list to the input byte slice. func AppendFloats32(dst []byte, vals []float32) []byte { if len(vals) == 0 { return append(dst, '[', ']') @@ -250,10 +300,14 @@ func AppendFloats32(dst []byte, vals []float32) []byte { return dst } +// AppendFloat64 converts the input float64 to a string and +// appends the encoded string to the input byte slice. func AppendFloat64(dst []byte, val float64) []byte { return AppendFloat(dst, val, 64) } +// AppendFloats64 encodes the input float64s to json and +// appends the encoded string list to the input byte slice. func AppendFloats64(dst []byte, vals []float64) []byte { if len(vals) == 0 { return append(dst, '[', ']') @@ -269,6 +323,8 @@ func AppendFloats64(dst []byte, vals []float64) []byte { return dst } +// AppendInterface marshals the input interface to a string and +// appends the encoded string to the input byte slice. func AppendInterface(dst []byte, i interface{}) []byte { marshaled, err := json.Marshal(i) if err != nil { diff --git a/log_test.go b/log_test.go index 38ce7f1..0f5e4cc 100644 --- a/log_test.go +++ b/log_test.go @@ -77,7 +77,9 @@ func TestInfo(t *testing.T) { func TestWith(t *testing.T) { out := &bytes.Buffer{} ctx := New(out).With(). - Str("foo", "bar"). + Str("string", "foo"). + Bytes("bytes", []byte("bar")). + Hex("hex", []byte{0x12, 0xef}). RawJSON("json", []byte(`{"some":"json"}`)). AnErr("some_err", nil). Err(errors.New("some error")). @@ -99,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(), `{"foo":"bar","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 := 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 { t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want) } } @@ -143,6 +145,7 @@ func TestFields(t *testing.T) { Caller(). Str("string", "foo"). Bytes("bytes", []byte("bar")). + Hex("hex", []byte{0x12, 0xef}). RawJSON("json", []byte(`{"some":"json"}`)). AnErr("some_err", nil). Err(errors.New("some error")). @@ -163,7 +166,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","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 := 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 { t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want) } } @@ -256,6 +259,7 @@ func TestFieldsDisabled(t *testing.T) { log.Debug(). Str("string", "foo"). Bytes("bytes", []byte("bar")). + Hex("hex", []byte{0x12, 0xef}). AnErr("some_err", nil). Err(errors.New("some error")). Bool("bool", true). From be4b7c1474941a38644d6622bb382efc3b4131cf Mon Sep 17 00:00:00 2001 From: Ryan Boehning Date: Thu, 15 Mar 2018 10:50:30 -0700 Subject: [PATCH 10/73] Update for Go 1.10 (#39) * Add Go 1.10 to .travis.yml. * Add quotes to Go versions in .travis.yml, because unquoted 1.10 is interpreted as Go 1.1. * Change .travis.yml references from 'tip' to 'master'. 'tip' is a legacy reference coming from the days when the Go project used mercurial instead of git. --- .travis.yml | 11 ++++++----- log/log_example_test.go | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 19d1d80..9bde515 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,11 +1,12 @@ language: go go: -- 1.7 -- 1.8 -- 1.9 -- tip +- "1.7" +- "1.8" +- "1.9" +- "1.10" +- "master" matrix: allow_failures: - - go: tip + - go: "master" script: go test -v -race -cpu=1,2,4 -bench . -benchmem ./... diff --git a/log/log_example_test.go b/log/log_example_test.go index 32f3699..16fed27 100644 --- a/log/log_example_test.go +++ b/log/log_example_test.go @@ -102,7 +102,7 @@ func ExampleFatal() { // This example uses command-line flags to demonstrate various outputs // depending on the chosen log level. -func Example_LevelFlag() { +func Example() { setup() debug := flag.Bool("debug", false, "sets log level to debug") From 5250a1ba2d5ea1f951606293fdff0dc2ae265415 Mon Sep 17 00:00:00 2001 From: Johan Sim Jian An Date: Fri, 23 Mar 2018 12:08:22 +0800 Subject: [PATCH 11/73] Check nil in RawJSON. (#45) --- event.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/event.go b/event.go index e451f5d..aba156e 100644 --- a/event.go +++ b/event.go @@ -233,6 +233,9 @@ func (e *Event) Hex(key string, val []byte) *Event { // No sanity check is performed on b; it must not contain carriage returns and // be valid JSON. func (e *Event) RawJSON(key string, b []byte) *Event { + if e == nil { + return e + } e.buf = append(json.AppendKey(e.buf, key), b...) return e } From 4ea03de40d7ee400dfb3e97488df8f00b2a363b0 Mon Sep 17 00:00:00 2001 From: Olivier Poitrey Date: Fri, 23 Mar 2018 02:45:05 -0700 Subject: [PATCH 12/73] Optimize JSON string encoding using a lookup table MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit benchstat old new name old time/op new time/op delta AppendString/MultiBytesFirst-8 77.9ns ± 5% 70.2ns ± 1% -9.88% (p=0.008 n=5+5) AppendString/MultiBytesMiddle-8 64.2ns ± 1% 56.3ns ± 5% -12.19% (p=0.008 n=5+5) AppendString/MultiBytesLast-8 51.2ns ± 2% 45.2ns ± 4% -11.65% (p=0.008 n=5+5) AppendString/NoEncoding-8 36.2ns ± 4% 34.0ns ± 6% ~ (p=0.087 n=5+5) AppendString/EncodingFirst-8 67.7ns ± 2% 59.4ns ± 2% -12.26% (p=0.008 n=5+5) AppendString/EncodingMiddle-8 56.5ns ± 2% 50.6ns ± 5% -10.54% (p=0.008 n=5+5) AppendString/EncodingLast-8 41.3ns ± 1% 39.6ns ± 5% -4.11% (p=0.024 n=5+5) AppendBytes/MultiBytesLast-8 53.5ns ± 6% 45.6ns ± 4% -14.79% (p=0.008 n=5+5) AppendBytes/NoEncoding-8 36.3ns ± 3% 28.6ns ± 3% -21.10% (p=0.008 n=5+5) AppendBytes/EncodingFirst-8 67.3ns ± 4% 62.1ns ± 4% -7.75% (p=0.008 n=5+5) AppendBytes/EncodingMiddle-8 59.2ns ± 7% 51.0ns ± 6% -13.85% (p=0.008 n=5+5) AppendBytes/EncodingLast-8 43.7ns ± 6% 34.4ns ± 2% -21.32% (p=0.008 n=5+5) AppendBytes/MultiBytesFirst-8 77.7ns ± 2% 71.2ns ± 3% -8.37% (p=0.008 n=5+5) AppendBytes/MultiBytesMiddle-8 63.6ns ± 3% 57.8ns ± 5% -9.12% (p=0.008 n=5+5) --- internal/json/bytes.go | 85 ++++++++++++++++++++++++++++++++ internal/json/bytes_test.go | 82 +++++++++++++++++++++++++++++++ internal/json/string.go | 94 ++++-------------------------------- internal/json/string_test.go | 77 ----------------------------- 4 files changed, 177 insertions(+), 161 deletions(-) create mode 100644 internal/json/bytes.go create mode 100644 internal/json/bytes_test.go diff --git a/internal/json/bytes.go b/internal/json/bytes.go new file mode 100644 index 0000000..8f7d5fe --- /dev/null +++ b/internal/json/bytes.go @@ -0,0 +1,85 @@ +package json + +import "unicode/utf8" + +// AppendBytes is a mirror of appendString with []byte arg +func AppendBytes(dst, s []byte) []byte { + dst = append(dst, '"') + for i := 0; i < len(s); i++ { + if !noEscapeTable[s[i]] { + dst = appendBytesComplex(dst, s, i) + return append(dst, '"') + } + } + dst = append(dst, s...) + return append(dst, '"') +} + +// AppendHex encodes the input bytes to a hex string and appends +// the encoded string to the input byte slice. +// +// The operation loops though each byte and encodes it as hex using +// the hex lookup table. +func AppendHex(dst, s []byte) []byte { + dst = append(dst, '"') + for _, v := range s { + dst = append(dst, hex[v>>4], hex[v&0x0f]) + } + return append(dst, '"') +} + +// appendBytesComplex is a mirror of the appendStringComplex +// with []byte arg +func appendBytesComplex(dst, s []byte, i int) []byte { + start := 0 + for i < len(s) { + b := s[i] + if b >= utf8.RuneSelf { + r, size := utf8.DecodeRune(s[i:]) + if r == utf8.RuneError && size == 1 { + if start < i { + dst = append(dst, s[start:i]...) + } + dst = append(dst, `\ufffd`...) + i += size + start = i + continue + } + i += size + continue + } + if noEscapeTable[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 +} diff --git a/internal/json/bytes_test.go b/internal/json/bytes_test.go new file mode 100644 index 0000000..e33c1e0 --- /dev/null +++ b/internal/json/bytes_test.go @@ -0,0 +1,82 @@ +package json + +import ( + "testing" + "unicode" +) + +func TestAppendBytes(t *testing.T) { + for _, tt := range encodeStringTests { + b := AppendBytes([]byte{}, []byte(tt.in)) + if got, want := string(b), tt.out; got != want { + t.Errorf("appendBytes(%q) = %#q, want %#q", tt.in, got, want) + } + } +} + +func TestAppendHex(t *testing.T) { + for _, tt := range encodeHexTests { + b := AppendHex([]byte{}, []byte{tt.in}) + if got, want := string(b), tt.out; got != want { + t.Errorf("appendHex(%x) = %s, want %s", tt.in, got, want) + } + } +} + +func TestStringBytes(t *testing.T) { + t.Parallel() + // Test that encodeState.stringBytes and encodeState.string use the same encoding. + var r []rune + for i := '\u0000'; i <= unicode.MaxRune; i++ { + r = append(r, i) + } + s := string(r) + "\xff\xff\xffhello" // some invalid UTF-8 too + + enc := string(AppendString([]byte{}, s)) + encBytes := string(AppendBytes([]byte{}, []byte(s))) + + if enc != encBytes { + i := 0 + for i < len(enc) && i < len(encBytes) && enc[i] == encBytes[i] { + i++ + } + enc = enc[i:] + encBytes = encBytes[i:] + i = 0 + for i < len(enc) && i < len(encBytes) && enc[len(enc)-i-1] == encBytes[len(encBytes)-i-1] { + i++ + } + enc = enc[:len(enc)-i] + encBytes = encBytes[:len(encBytes)-i] + + if len(enc) > 20 { + enc = enc[:20] + "..." + } + if len(encBytes) > 20 { + encBytes = encBytes[:20] + "..." + } + + t.Errorf("encodings differ at %#q vs %#q", enc, encBytes) + } +} + +func BenchmarkAppendBytes(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 { + byt := []byte(str) + b.Run(name, func(b *testing.B) { + buf := make([]byte, 0, 100) + for i := 0; i < b.N; i++ { + _ = AppendBytes(buf, byt) + } + }) + } +} diff --git a/internal/json/string.go b/internal/json/string.go index 7f85ad6..bb606f0 100644 --- a/internal/json/string.go +++ b/internal/json/string.go @@ -4,6 +4,14 @@ import "unicode/utf8" const hex = "0123456789abcdef" +var noEscapeTable = [256]bool{} + +func init() { + for i := 0; i <= 0x7e; i++ { + noEscapeTable[i] = i >= 0x20 && i != '\\' && i != '"' + } +} + // AppendStrings encodes the input strings to json and // appends the encoded string list to the input byte slice. func AppendStrings(dst []byte, vals []string) []byte { @@ -38,7 +46,7 @@ func AppendString(dst []byte, s string) []byte { // 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 s[i] < 0x20 || s[i] > 0x7e || s[i] == '\\' || s[i] == '"' { + if !noEscapeTable[s[i]] { // We encountered a character that needs to be encoded. Switch // to complex version of the algorithm. dst = appendStringComplex(dst, s, i) @@ -76,89 +84,7 @@ func appendStringComplex(dst []byte, s string, i int) []byte { 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 -} - -// AppendBytes is a mirror of appendString with []byte arg -func AppendBytes(dst, s []byte) []byte { - dst = append(dst, '"') - for i := 0; i < len(s); i++ { - if s[i] < 0x20 || s[i] > 0x7e || s[i] == '\\' || s[i] == '"' { - dst = appendBytesComplex(dst, s, i) - return append(dst, '"') - } - } - dst = append(dst, s...) - return append(dst, '"') -} - -// AppendHex encodes the input bytes to a hex string and appends -// the encoded string to the input byte slice. -// -// The operation loops though each byte and encodes it as hex using -// the hex lookup table. -func AppendHex(dst, s []byte) []byte { - dst = append(dst, '"') - for _, v := range s { - dst = append(dst, hex[v>>4], hex[v&0x0f]) - } - return append(dst, '"') -} - -// appendBytesComplex is a mirror of the appendStringComplex -// with []byte arg -func appendBytesComplex(dst, s []byte, i int) []byte { - start := 0 - for i < len(s) { - b := s[i] - if b >= utf8.RuneSelf { - r, size := utf8.DecodeRune(s[i:]) - if r == utf8.RuneError && size == 1 { - 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 != '"' { + if noEscapeTable[b] { i++ continue } diff --git a/internal/json/string_test.go b/internal/json/string_test.go index 0d5fc6c..a30b124 100644 --- a/internal/json/string_test.go +++ b/internal/json/string_test.go @@ -2,7 +2,6 @@ package json import ( "testing" - "unicode" ) var encodeStringTests = []struct { @@ -73,61 +72,6 @@ func TestAppendString(t *testing.T) { } } -func TestAppendBytes(t *testing.T) { - for _, tt := range encodeStringTests { - b := AppendBytes([]byte{}, []byte(tt.in)) - if got, want := string(b), tt.out; got != want { - t.Errorf("appendBytes(%q) = %#q, want %#q", tt.in, got, want) - } - } -} - -func TestAppendHex(t *testing.T) { - for _, tt := range encodeHexTests { - b := AppendHex([]byte{}, []byte{tt.in}) - if got, want := string(b), tt.out; got != want { - t.Errorf("appendHex(%x) = %s, want %s", tt.in, got, want) - } - } -} - -func TestStringBytes(t *testing.T) { - t.Parallel() - // Test that encodeState.stringBytes and encodeState.string use the same encoding. - var r []rune - for i := '\u0000'; i <= unicode.MaxRune; i++ { - r = append(r, i) - } - s := string(r) + "\xff\xff\xffhello" // some invalid UTF-8 too - - enc := string(AppendString([]byte{}, s)) - encBytes := string(AppendBytes([]byte{}, []byte(s))) - - if enc != encBytes { - i := 0 - for i < len(enc) && i < len(encBytes) && enc[i] == encBytes[i] { - i++ - } - enc = enc[i:] - encBytes = encBytes[i:] - i = 0 - for i < len(enc) && i < len(encBytes) && enc[len(enc)-i-1] == encBytes[len(encBytes)-i-1] { - i++ - } - enc = enc[:len(enc)-i] - encBytes = encBytes[:len(encBytes)-i] - - if len(enc) > 20 { - enc = enc[:20] + "..." - } - if len(encBytes) > 20 { - encBytes = encBytes[:20] + "..." - } - - t.Errorf("encodings differ at %#q vs %#q", enc, encBytes) - } -} - func BenchmarkAppendString(b *testing.B) { tests := map[string]string{ "NoEncoding": `aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa`, @@ -147,24 +91,3 @@ func BenchmarkAppendString(b *testing.B) { }) } } - -func BenchmarkAppendBytes(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 { - byt := []byte(str) - b.Run(name, func(b *testing.B) { - buf := make([]byte, 0, 100) - for i := 0; i < b.N; i++ { - _ = AppendBytes(buf, byt) - } - }) - } -} From 24cc441b11a5f57af7d9ea86f0b07b27c129562f Mon Sep 17 00:00:00 2001 From: Olivier Poitrey Date: Fri, 23 Mar 2018 09:58:31 -0700 Subject: [PATCH 13/73] Add more json type tests --- internal/json/types_test.go | 58 ++++++++++++++++++++++++++++--------- 1 file changed, 44 insertions(+), 14 deletions(-) diff --git a/internal/json/types_test.go b/internal/json/types_test.go index 6a297a8..d90dac1 100644 --- a/internal/json/types_test.go +++ b/internal/json/types_test.go @@ -6,27 +6,57 @@ import ( "testing" ) -func Test_appendFloat64(t *testing.T) { +func TestAppendType(t *testing.T) { + w := map[string]func(interface{}) []byte{ + "AppendInt": func(v interface{}) []byte { return AppendInt([]byte{}, v.(int)) }, + "AppendInt8": func(v interface{}) []byte { return AppendInt8([]byte{}, v.(int8)) }, + "AppendInt16": func(v interface{}) []byte { return AppendInt16([]byte{}, v.(int16)) }, + "AppendInt32": func(v interface{}) []byte { return AppendInt32([]byte{}, v.(int32)) }, + "AppendInt64": func(v interface{}) []byte { return AppendInt64([]byte{}, v.(int64)) }, + "AppendUint": func(v interface{}) []byte { return AppendUint([]byte{}, v.(uint)) }, + "AppendUint8": func(v interface{}) []byte { return AppendUint8([]byte{}, v.(uint8)) }, + "AppendUint16": func(v interface{}) []byte { return AppendUint16([]byte{}, v.(uint16)) }, + "AppendUint32": func(v interface{}) []byte { return AppendUint32([]byte{}, v.(uint32)) }, + "AppendUint64": func(v interface{}) []byte { return AppendUint64([]byte{}, v.(uint64)) }, + "AppendFloat32": func(v interface{}) []byte { return AppendFloat32([]byte{}, v.(float32)) }, + "AppendFloat64": func(v interface{}) []byte { return AppendFloat64([]byte{}, v.(float64)) }, + } tests := []struct { name string - input float64 + fn string + input interface{} want []byte }{ - {"-Inf", math.Inf(-1), []byte(`"-Inf"`)}, - {"+Inf", math.Inf(1), []byte(`"+Inf"`)}, - {"NaN", math.NaN(), []byte(`"NaN"`)}, - {"0", 0, []byte(`0`)}, - {"-1.1", -1.1, []byte(`-1.1`)}, - {"1e20", 1e20, []byte(`100000000000000000000`)}, - {"1e21", 1e21, []byte(`1000000000000000000000`)}, + {"AppendInt8(math.MaxInt8)", "AppendInt8", int8(math.MaxInt8), []byte("127")}, + {"AppendInt16(math.MaxInt16)", "AppendInt16", int16(math.MaxInt16), []byte("32767")}, + {"AppendInt32(math.MaxInt32)", "AppendInt32", int32(math.MaxInt32), []byte("2147483647")}, + {"AppendInt64(math.MaxInt64)", "AppendInt64", int64(math.MaxInt64), []byte("9223372036854775807")}, + + {"AppendUint8(math.MaxUint8)", "AppendUint8", uint8(math.MaxUint8), []byte("255")}, + {"AppendUint16(math.MaxUint16)", "AppendUint16", uint16(math.MaxUint16), []byte("65535")}, + {"AppendUint32(math.MaxUint32)", "AppendUint32", uint32(math.MaxUint32), []byte("4294967295")}, + {"AppendUint64(math.MaxUint64)", "AppendUint64", uint64(math.MaxUint64), []byte("18446744073709551615")}, + + {"AppendFloat32(-Inf)", "AppendFloat32", float32(math.Inf(-1)), []byte(`"-Inf"`)}, + {"AppendFloat32(+Inf)", "AppendFloat32", float32(math.Inf(1)), []byte(`"+Inf"`)}, + {"AppendFloat32(NaN)", "AppendFloat32", float32(math.NaN()), []byte(`"NaN"`)}, + {"AppendFloat32(0)", "AppendFloat32", float32(0), []byte(`0`)}, + {"AppendFloat32(-1.1)", "AppendFloat32", float32(-1.1), []byte(`-1.1`)}, + {"AppendFloat32(1e20)", "AppendFloat32", float32(1e20), []byte(`100000000000000000000`)}, + {"AppendFloat32(1e21)", "AppendFloat32", float32(1e21), []byte(`1000000000000000000000`)}, + + {"AppendFloat64(-Inf)", "AppendFloat64", float64(math.Inf(-1)), []byte(`"-Inf"`)}, + {"AppendFloat64(+Inf)", "AppendFloat64", float64(math.Inf(1)), []byte(`"+Inf"`)}, + {"AppendFloat64(NaN)", "AppendFloat64", float64(math.NaN()), []byte(`"NaN"`)}, + {"AppendFloat64(0)", "AppendFloat64", float64(0), []byte(`0`)}, + {"AppendFloat64(-1.1)", "AppendFloat64", float64(-1.1), []byte(`-1.1`)}, + {"AppendFloat64(1e20)", "AppendFloat64", float64(1e20), []byte(`100000000000000000000`)}, + {"AppendFloat64(1e21)", "AppendFloat64", float64(1e21), []byte(`1000000000000000000000`)}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := AppendFloat32([]byte{}, float32(tt.input)); !reflect.DeepEqual(got, tt.want) { - t.Errorf("appendFloat32() = %s, want %s", got, tt.want) - } - if got := AppendFloat64([]byte{}, tt.input); !reflect.DeepEqual(got, tt.want) { - t.Errorf("appendFloat32() = %s, want %s", got, tt.want) + if got := w[tt.fn](tt.input); !reflect.DeepEqual(got, tt.want) { + t.Errorf("got %s, want %s", got, tt.want) } }) } From d0ca9bbceba337238110f05f26093f5750c0cbe3 Mon Sep 17 00:00:00 2001 From: Olivier Poitrey Date: Sun, 25 Mar 2018 20:18:47 -0700 Subject: [PATCH 14/73] Add support for pointer values in Fields Fixes #46 --- fields.go | 32 ++++++++++++++++++++++++++++++++ log_test.go | 26 ++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/fields.go b/fields.go index 6a19392..be03edf 100644 --- a/fields.go +++ b/fields.go @@ -54,6 +54,38 @@ func appendFields(dst []byte, fields map[string]interface{}) []byte { dst = json.AppendTime(dst, val, TimeFieldFormat) case time.Duration: dst = json.AppendDuration(dst, val, DurationFieldUnit, DurationFieldInteger) + case *string: + dst = json.AppendString(dst, *val) + case *bool: + dst = json.AppendBool(dst, *val) + case *int: + dst = json.AppendInt(dst, *val) + case *int8: + dst = json.AppendInt8(dst, *val) + case *int16: + dst = json.AppendInt16(dst, *val) + case *int32: + dst = json.AppendInt32(dst, *val) + case *int64: + dst = json.AppendInt64(dst, *val) + case *uint: + dst = json.AppendUint(dst, *val) + case *uint8: + dst = json.AppendUint8(dst, *val) + case *uint16: + dst = json.AppendUint16(dst, *val) + case *uint32: + dst = json.AppendUint32(dst, *val) + case *uint64: + dst = json.AppendUint64(dst, *val) + case *float32: + dst = json.AppendFloat32(dst, *val) + case *float64: + dst = json.AppendFloat64(dst, *val) + case *time.Time: + dst = json.AppendTime(dst, *val, TimeFieldFormat) + case *time.Duration: + dst = json.AppendDuration(dst, *val, DurationFieldUnit, DurationFieldInteger) case []string: dst = json.AppendStrings(dst, val) case []bool: diff --git a/log_test.go b/log_test.go index 0f5e4cc..4c3bea6 100644 --- a/log_test.go +++ b/log_test.go @@ -135,6 +135,32 @@ func TestFieldsMap(t *testing.T) { } } +func TestFieldsMapPnt(t *testing.T) { + out := &bytes.Buffer{} + log := New(out) + log.Log().Fields(map[string]interface{}{ + "string": new(string), + "bool": new(bool), + "int": new(int), + "int8": new(int8), + "int16": new(int16), + "int32": new(int32), + "int64": new(int64), + "uint": new(uint), + "uint8": new(uint8), + "uint16": new(uint16), + "uint32": new(uint32), + "uint64": new(uint64), + "float32": new(float32), + "float64": new(float64), + "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 { + t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want) + } +} + func TestFields(t *testing.T) { out := &bytes.Buffer{} log := New(out) From 3ab376bc30250ed8a432d68cc79ba462ebb9ce22 Mon Sep 17 00:00:00 2001 From: Olivier Poitrey Date: Sun, 25 Mar 2018 22:34:03 -0700 Subject: [PATCH 15/73] Add "Who uses zerolog" wiki page link --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 7a034b4..d9ea32b 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,10 @@ To keep the code base and the API simple, zerolog focuses on JSON logging only. ![](pretty.png) +## Who uses zerolog + +Find out [who uses zerolog](https://github.com/rs/zerolog/wiki/Who-uses-zerolog) and add your company / project to the list. + ## Features * Blazing fast From ddfae1b613b839c39633902368fb3933455ad62f Mon Sep 17 00:00:00 2001 From: Ravi Raju Date: Wed, 28 Mar 2018 11:49:41 -0700 Subject: [PATCH 16/73] Binary format support (#37) Adds support for binary logging (with cbor encoding) in addition to JSON. Use the binary_log compile tag to enable the feature. --- .travis.yml | 3 +- array.go | 60 ++-- array_test.go | 2 +- binary_test.go | 453 ++++++++++++++++++++++++++++ console.go | 1 + console_test.go | 14 + context.go | 89 +++--- diode/diode_example_test.go | 26 ++ diode/diode_test.go | 14 +- encoder_cbor.go | 218 ++++++++++++++ encoder_json.go | 216 ++++++++++++++ event.go | 114 ++++--- fields.go | 108 ++++--- hlog/hlog_example_test.go | 2 + hlog/hlog_test.go | 27 +- hook_test.go | 40 +-- internal/cbor/README.md | 74 +++++ internal/cbor/base.go | 43 +++ internal/cbor/cbor.go | 91 ++++++ internal/cbor/decoder.go | 548 ++++++++++++++++++++++++++++++++++ internal/cbor/decoder_test.go | 188 ++++++++++++ internal/cbor/string.go | 63 ++++ internal/cbor/string_test.go | 118 ++++++++ internal/cbor/time.go | 93 ++++++ internal/cbor/time_test.go | 99 ++++++ internal/cbor/types.go | 438 +++++++++++++++++++++++++++ internal/cbor/types_test.go | 253 ++++++++++++++++ internal/json/base.go | 2 +- internal/json/types.go | 13 + log.go | 7 +- log/log_example_test.go | 2 + log_example_test.go | 3 +- log_test.go | 55 ++-- sampler_test.go | 2 + syslog.go | 5 +- syslog_test.go | 1 + writer_test.go | 1 + 37 files changed, 3224 insertions(+), 262 deletions(-) create mode 100644 binary_test.go create mode 100644 console_test.go create mode 100644 diode/diode_example_test.go create mode 100644 encoder_cbor.go create mode 100644 encoder_json.go create mode 100644 internal/cbor/README.md create mode 100644 internal/cbor/base.go create mode 100644 internal/cbor/cbor.go create mode 100644 internal/cbor/decoder.go create mode 100644 internal/cbor/decoder_test.go create mode 100644 internal/cbor/string.go create mode 100644 internal/cbor/string_test.go create mode 100644 internal/cbor/time.go create mode 100644 internal/cbor/time_test.go create mode 100644 internal/cbor/types.go create mode 100644 internal/cbor/types_test.go 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 From 05eafee0eb17d0150591a8f30f0fa592cc9b7471 Mon Sep 17 00:00:00 2001 From: Olivier Poitrey Date: Wed, 28 Mar 2018 11:57:11 -0700 Subject: [PATCH 17/73] Update README with instruction about binary encoding --- README.md | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index d9ea32b..4f36996 100644 --- a/README.md +++ b/README.md @@ -4,11 +4,11 @@ The zerolog package provides a fast and simple logger dedicated to JSON output. -Zerolog's API is designed to provide both a great developer experience and stunning [performance](#benchmarks). Its unique chaining API allows zerolog to write JSON log events by avoiding allocations and reflection. +Zerolog's API is designed to provide both a great developer experience and stunning [performance](#benchmarks). Its unique chaining API allows zerolog to write JSON (or CBOR) log events by avoiding allocations and reflection. Uber's [zap](https://godoc.org/go.uber.org/zap) library pioneered this approach. Zerolog is taking this concept to the next level with a simpler to use API and even better performance. -To keep the code base and the API simple, zerolog focuses on JSON logging only. Pretty logging on the console is made possible using the provided (but inefficient) `zerolog.ConsoleWriter`. +To keep the code base and the API simple, zerolog focuses on efficient structured logging only. Pretty logging on the console is made possible using the provided (but inefficient) `zerolog.ConsoleWriter`. ![](pretty.png) @@ -26,14 +26,18 @@ Find out [who uses zerolog](https://github.com/rs/zerolog/wiki/Who-uses-zerolog) * Contextual fields * `context.Context` integration * `net/http` helpers +* JSON and CBOR encoding formats * Pretty logging for development ## Installation + ```go go get -u github.com/rs/zerolog/log ``` ## Getting Started + ### Simple Logging Example + For simple logging, import the global logger package **github.com/rs/zerolog/log** ```go package main @@ -138,6 +142,7 @@ $ ./logLevelExample -debug ``` #### Logging Fatal Messages + ```go package main @@ -168,6 +173,7 @@ func main() { ### Contextual Logging #### Fields can be added to log messages + ```go log.Info(). Str("foo", "bar"). @@ -422,6 +428,14 @@ Some settings can be changed and will by applied to all loggers: * `Dict`: Adds a sub-key/value as a field of the event. * `Interface`: Uses reflection to marshal the type. +## Binary Encoding + +In addition to the default JSON encoding, `zerolog` can produce binary logs using the [cbor](http://cbor.io) encoding. The choice of encoding can be decided at compile time using the build tag `binary_log` as follow: + +``` +go build -tags binary_log . +``` + ## Benchmarks All operations are allocation free (those numbers *include* JSON encoding): @@ -483,8 +497,7 @@ Log a static string, without any context or `printf`-style templating: ## Caveats -There is no fields deduplication out-of-the-box. -Using the same key multiple times creates new key in final JSON each time. +Note that zerolog does de-duplication fields. Using the same key multiple times creates multiple keys in final JSON: ```go logger := zerolog.New(os.Stderr).With().Timestamp().Logger() @@ -494,4 +507,4 @@ logger.Info(). // Output: {"level":"info","time":1494567715,"time":1494567715,"message":"dup"} ``` -However, it’s not a big deal though as JSON accepts dup keys, the last one prevails. +However, it’s not a big deal as JSON accepts dup keys; the last one prevails. From 2ccfab3e0738c1f880d71ccfb2d523d3abae4615 Mon Sep 17 00:00:00 2001 From: Ravi Raju Date: Tue, 3 Apr 2018 14:07:18 -0700 Subject: [PATCH 18/73] Support for adding IP Address/Prefix + stream based decoder (#49) * added IPAddr, IPPrefix and stream based cbor decoder * Update README with cbor decoder tool info * Update README in cbor with comparison data --- README.md | 3 + array.go | 19 + array_test.go | 4 +- context.go | 19 + encoder_cbor.go | 13 + encoder_json.go | 17 +- event.go | 28 ++ fields.go | 7 + internal/cbor/README.md | 122 +++---- internal/cbor/cbor.go | 25 +- internal/cbor/decode_stream.go | 610 +++++++++++++++++++++++++++++++ internal/cbor/decoder.go | 548 --------------------------- internal/cbor/decoder_test.go | 113 +++--- internal/cbor/examples/genLog.go | 55 +++ internal/cbor/examples/makefile | 10 + internal/cbor/string.go | 7 +- internal/cbor/types.go | 42 ++- internal/cbor/types_test.go | 67 ++++ internal/json/types.go | 19 + internal/json/types_test.go | 104 ++++++ log_example_test.go | 34 ++ log_test.go | 10 +- 22 files changed, 1191 insertions(+), 685 deletions(-) create mode 100644 internal/cbor/decode_stream.go delete mode 100644 internal/cbor/decoder.go create mode 100644 internal/cbor/examples/genLog.go create mode 100644 internal/cbor/examples/makefile diff --git a/README.md b/README.md index 4f36996..14b9577 100644 --- a/README.md +++ b/README.md @@ -436,6 +436,9 @@ In addition to the default JSON encoding, `zerolog` can produce binary logs usin go build -tags binary_log . ``` +To Decode binary encoded log files you can use any CBOR decoder. One has been tested to work +with zerolog library is CSD(https://github.com/toravir/csd/). + ## Benchmarks All operations are allocation free (those numbers *include* JSON encoding): diff --git a/array.go b/array.go index 9555369..66c2a4f 100644 --- a/array.go +++ b/array.go @@ -1,6 +1,7 @@ package zerolog import ( + "net" "sync" "time" ) @@ -174,3 +175,21 @@ func (a *Array) Interface(i interface{}) *Array { a.buf = appendInterface(appendArrayDelim(a.buf), i) return a } + +// IPAddr adds IPv4 or IPv6 address to the array +func (a *Array) IPAddr(ip net.IP) *Array { + a.buf = appendIPAddr(appendArrayDelim(a.buf), ip) + return a +} + +// IPPrefix adds IPv4 or IPv6 Prefix (IP + mask) to the array +func (a *Array) IPPrefix(pfx net.IPNet) *Array { + a.buf = appendIPPrefix(appendArrayDelim(a.buf), pfx) + return a +} + +// MACAddr adds a MAC (Ethernet) address to the array +func (a *Array) MACAddr(ha net.HardwareAddr) *Array { + a.buf = appendMACAddr(appendArrayDelim(a.buf), ha) + return a +} diff --git a/array_test.go b/array_test.go index 952a579..19bac56 100644 --- a/array_test.go +++ b/array_test.go @@ -1,6 +1,7 @@ package zerolog import ( + "net" "testing" "time" ) @@ -24,8 +25,9 @@ func TestArray(t *testing.T) { Bytes([]byte("b")). Hex([]byte{0x1f}). Time(time.Time{}). + IPAddr(net.IP{192, 168, 0, 10}). 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]` + want := `[true,1,2,3,4,5,6,7,8,9,10,11,12,"a","b","1f","0001-01-01T00:00:00Z","192.168.0.10",0]` if got := decodeObjectToStr(a.write([]byte{})); got != want { t.Errorf("Array.write()\ngot: %s\nwant: %s", got, want) } diff --git a/context.go b/context.go index 25ef7cf..1f92dae 100644 --- a/context.go +++ b/context.go @@ -2,6 +2,7 @@ package zerolog import ( "io/ioutil" + "net" "time" ) @@ -330,3 +331,21 @@ func (c Context) Caller() Context { c.l = c.l.Hook(ch) return c } + +// IPAddr adds IPv4 or IPv6 Address to the context +func (c Context) IPAddr(key string, ip net.IP) Context { + c.l.context = appendIPAddr(appendKey(c.l.context, key), ip) + return c +} + +// IPPrefix adds IPv4 or IPv6 Prefix (address and mask) to the context +func (c Context) IPPrefix(key string, pfx net.IPNet) Context { + c.l.context = appendIPPrefix(appendKey(c.l.context, key), pfx) + return c +} + +// MACAddr adds MAC address to the context +func (c Context) MACAddr(key string, ha net.HardwareAddr) Context { + c.l.context = appendMACAddr(appendKey(c.l.context, key), ha) + return c +} diff --git a/encoder_cbor.go b/encoder_cbor.go index 6d001ac..aa72013 100644 --- a/encoder_cbor.go +++ b/encoder_cbor.go @@ -5,6 +5,7 @@ package zerolog // This file contains bindings to do binary encoding. import ( + "net" "time" "github.com/rs/zerolog/internal/cbor" @@ -211,6 +212,18 @@ func decodeObjectToStr(in []byte) string { return cbor.DecodeObjectToStr(in) } +func appendIPAddr(dst []byte, ip net.IP) []byte { + return cbor.AppendIPAddr(dst, ip) +} + +func appendIPPrefix(dst []byte, pfx net.IPNet) []byte { + return cbor.AppendIPPrefix(dst, pfx) +} + +func appendMACAddr(dst []byte, ha net.HardwareAddr) []byte { + return cbor.AppendMACAddr(dst, ha) +} + // decodeIfBinaryToBytes - converts a binary formatted log msg to a // JSON formatted Bytes Log message. func decodeIfBinaryToBytes(in []byte) []byte { diff --git a/encoder_json.go b/encoder_json.go index 1e5db80..df15c7a 100644 --- a/encoder_json.go +++ b/encoder_json.go @@ -6,6 +6,7 @@ package zerolog // JSON encoded byte stream. import ( + "net" "strconv" "time" @@ -211,6 +212,18 @@ func decodeIfBinaryToBytes(in []byte) []byte { return in } -func appendHex(in []byte, val []byte) []byte { - return json.AppendHex(in, val) +func appendIPAddr(dst []byte, ip net.IP) []byte { + return json.AppendIPAddr(dst, ip) +} + +func appendIPPrefix(dst []byte, pfx net.IPNet) []byte { + return json.AppendIPPrefix(dst, pfx) +} + +func appendMACAddr(dst []byte, ha net.HardwareAddr) []byte { + return json.AppendMACAddr(dst, ha) +} + +func appendHex(in []byte, val []byte) []byte { + return json.AppendHex(in, val) } diff --git a/event.go b/event.go index 05ee4d8..dffaf3e 100644 --- a/event.go +++ b/event.go @@ -2,6 +2,7 @@ package zerolog import ( "fmt" + "net" "os" "runtime" "strconv" @@ -597,3 +598,30 @@ func (e *Event) caller(skip int) *Event { e.buf = appendString(appendKey(e.buf, CallerFieldName), file+":"+strconv.Itoa(line)) return e } + +// IPAddr adds IPv4 or IPv6 Address to the event +func (e *Event) IPAddr(key string, ip net.IP) *Event { + if e == nil { + return e + } + e.buf = appendIPAddr(appendKey(e.buf, key), ip) + return e +} + +// IPPrefix adds IPv4 or IPv6 Prefix (address and mask) to the event +func (e *Event) IPPrefix(key string, pfx net.IPNet) *Event { + if e == nil { + return e + } + e.buf = appendIPPrefix(appendKey(e.buf, key), pfx) + return e +} + +// MACAddr adds MAC address to the event +func (e *Event) MACAddr(key string, ha net.HardwareAddr) *Event { + if e == nil { + return e + } + e.buf = appendMACAddr(appendKey(e.buf, key), ha) + return e +} diff --git a/fields.go b/fields.go index 95e83ef..9d13f0c 100644 --- a/fields.go +++ b/fields.go @@ -1,6 +1,7 @@ package zerolog import ( + "net" "sort" "time" ) @@ -118,6 +119,12 @@ func appendFields(dst []byte, fields map[string]interface{}) []byte { dst = appendDurations(dst, val, DurationFieldUnit, DurationFieldInteger) case nil: dst = appendNil(dst) + case net.IP: + dst = appendIPAddr(dst, val) + case net.IPNet: + dst = appendIPPrefix(dst, val) + case net.HardwareAddr: + dst = appendMACAddr(dst, val) default: dst = appendInterface(dst, val) } diff --git a/internal/cbor/README.md b/internal/cbor/README.md index ff71754..92c2e8c 100644 --- a/internal/cbor/README.md +++ b/internal/cbor/README.md @@ -1,74 +1,56 @@ -Reference: - CBOR Encoding is described in RFC7049 https://tools.ietf.org/html/rfc7049 +## Reference: + CBOR Encoding is described in [RFC7049](https://tools.ietf.org/html/rfc7049) + +## Comparison of JSON vs CBOR + +Two main areas of reduction are: + +1. CPU usage to write a log msg +2. Size (in bytes) of log messages. -Tests and benchmark: - +CPU Usage savings are below: ``` -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> +name JSON time/op CBOR time/op delta +Info-32 15.3ns ± 1% 11.7ns ± 3% -23.78% (p=0.000 n=9+10) +ContextFields-32 16.2ns ± 2% 12.3ns ± 3% -23.97% (p=0.000 n=9+9) +ContextAppend-32 6.70ns ± 0% 6.20ns ± 0% -7.44% (p=0.000 n=9+9) +LogFields-32 66.4ns ± 0% 24.6ns ± 2% -62.89% (p=0.000 n=10+9) +LogArrayObject-32 911ns ±11% 768ns ± 6% -15.64% (p=0.000 n=10+10) +LogFieldType/Floats-32 70.3ns ± 2% 29.5ns ± 1% -57.98% (p=0.000 n=10+10) +LogFieldType/Err-32 14.0ns ± 3% 12.1ns ± 8% -13.20% (p=0.000 n=8+10) +LogFieldType/Dur-32 17.2ns ± 2% 13.1ns ± 1% -24.27% (p=0.000 n=10+9) +LogFieldType/Object-32 54.3ns ±11% 52.3ns ± 7% ~ (p=0.239 n=10+10) +LogFieldType/Ints-32 20.3ns ± 2% 15.1ns ± 2% -25.50% (p=0.000 n=9+10) +LogFieldType/Interfaces-32 642ns ±11% 621ns ± 9% ~ (p=0.118 n=10+10) +LogFieldType/Interface(Objects)-32 635ns ±13% 632ns ± 9% ~ (p=0.592 n=10+10) +LogFieldType/Times-32 294ns ± 0% 27ns ± 1% -90.71% (p=0.000 n=10+9) +LogFieldType/Durs-32 121ns ± 0% 33ns ± 2% -72.44% (p=0.000 n=9+9) +LogFieldType/Interface(Object)-32 56.6ns ± 8% 52.3ns ± 8% -7.54% (p=0.007 n=10+10) +LogFieldType/Errs-32 17.8ns ± 3% 16.1ns ± 2% -9.71% (p=0.000 n=10+9) +LogFieldType/Time-32 40.5ns ± 1% 12.7ns ± 6% -68.66% (p=0.000 n=8+9) +LogFieldType/Bool-32 12.0ns ± 5% 10.2ns ± 2% -15.18% (p=0.000 n=10+8) +LogFieldType/Bools-32 17.2ns ± 2% 12.6ns ± 4% -26.63% (p=0.000 n=10+10) +LogFieldType/Int-32 12.3ns ± 2% 11.2ns ± 4% -9.27% (p=0.000 n=9+10) +LogFieldType/Float-32 16.7ns ± 1% 12.6ns ± 2% -24.42% (p=0.000 n=7+9) +LogFieldType/Str-32 12.7ns ± 7% 11.3ns ± 7% -10.88% (p=0.000 n=10+9) +LogFieldType/Strs-32 20.3ns ± 3% 18.2ns ± 3% -10.25% (p=0.000 n=9+10) +LogFieldType/Interface-32 183ns ±12% 175ns ± 9% ~ (p=0.078 n=10+10) ``` + +Log message size savings is greatly dependent on the number and type of fields in the log message. +Assuming this log message (with an Integer, timestamp and string, in addition to level). + +`{"level":"error","Fault":41650,"time":"2018-04-01T15:18:19-07:00","message":"Some Message"}` + +Two measurements were done for the log file sizes - one without any compression, second +using [compress/zlib](https://golang.org/pkg/compress/zlib/). + +Results for 10,000 log messages: + +| Log Format | Plain File Size (in KB) | Compressed File Size (in KB) | +| :--- | :---: | :---: | +| JSON | 920 | 28 | +| CBOR | 550 | 28 | + +The example used to calculate the above data is available in [Examples](examples). diff --git a/internal/cbor/cbor.go b/internal/cbor/cbor.go index fe216ce..969f591 100644 --- a/internal/cbor/cbor.go +++ b/internal/cbor/cbor.go @@ -7,25 +7,34 @@ import "time" const ( majorOffset = 5 additionalMax = 23 - //Non Values + + // Non Values. additionalTypeBoolFalse byte = 20 additionalTypeBoolTrue byte = 21 additionalTypeNull byte = 22 - //Integer (+ve and -ve) Sub-types + + // Integer (+ve and -ve) Sub-types. additionalTypeIntUint8 byte = 24 additionalTypeIntUint16 byte = 25 additionalTypeIntUint32 byte = 26 additionalTypeIntUint64 byte = 27 - //Float Sub-types + + // 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 + + // Tag Sub-types. + additionalTypeTimestamp byte = 01 + + // Extended Tags - from https://www.iana.org/assignments/cbor-tags/cbor-tags.xhtml + additionalTypeTagNetworkAddr uint16 = 260 + additionalTypeTagNetworkPrefix uint16 = 261 + additionalTypeEmbeddedJSON uint16 = 262 + additionalTypeTagHexString uint16 = 263 + + // Unspecified number of elements. additionalTypeInfiniteCount byte = 31 ) const ( diff --git a/internal/cbor/decode_stream.go b/internal/cbor/decode_stream.go new file mode 100644 index 0000000..f1ff2a1 --- /dev/null +++ b/internal/cbor/decode_stream.go @@ -0,0 +1,610 @@ +package cbor + +// This file contains code to decode a stream of CBOR Data into JSON. + +import ( + "bufio" + "bytes" + "fmt" + "io" + "math" + "net" + "runtime" + "strconv" + "strings" + "time" + "unicode/utf8" +) + +var decodeTimeZone *time.Location + +const hexTable = "0123456789abcdef" + +func readNBytes(src *bufio.Reader, n int) []byte { + ret := make([]byte, n) + for i := 0; i < n; i++ { + ch, e := src.ReadByte() + if e != nil { + panic(fmt.Errorf("Tried to Read %d Bytes.. But hit end of file", n)) + } + ret[i] = ch + } + return ret +} + +func readByte(src *bufio.Reader) byte { + b, e := src.ReadByte() + if e != nil { + panic(fmt.Errorf("Tried to Read 1 Byte.. But hit end of file")) + } + return b +} + +func decodeIntAdditonalType(src *bufio.Reader, minor byte) int64 { + val := int64(0) + if minor <= 23 { + val = int64(minor) + } else { + bytesToRead := 0 + switch minor { + case additionalTypeIntUint8: + bytesToRead = 1 + case additionalTypeIntUint16: + bytesToRead = 2 + case additionalTypeIntUint32: + bytesToRead = 4 + case additionalTypeIntUint64: + bytesToRead = 8 + default: + panic(fmt.Errorf("Invalid Additional Type: %d in decodeInteger (expected <28)", minor)) + } + pb := readNBytes(src, bytesToRead) + for i := 0; i < bytesToRead; i++ { + val = val * 256 + val += int64(pb[i]) + } + } + return val +} + +func decodeInteger(src *bufio.Reader) int64 { + pb := readByte(src) + major := pb & maskOutAdditionalType + minor := pb & maskOutMajorType + if major != majorTypeUnsignedInt && major != majorTypeNegativeInt { + panic(fmt.Errorf("Major type is: %d in decodeInteger!! (expected 0 or 1)", major)) + } + val := decodeIntAdditonalType(src, minor) + if major == 0 { + return val + } + return (-1 - val) +} + +func decodeFloat(src *bufio.Reader) (float64, int) { + pb := readByte(src) + major := pb & maskOutAdditionalType + minor := pb & maskOutMajorType + if major != majorTypeSimpleAndFloat { + panic(fmt.Errorf("Incorrect Major type is: %d in decodeFloat", major)) + } + + switch minor { + case additionalTypeFloat16: + panic(fmt.Errorf("float16 is not suppported in decodeFloat")) + + case additionalTypeFloat32: + pb := readNBytes(src, 4) + switch string(pb) { + case float32Nan: + return math.NaN(), 4 + case float32PosInfinity: + return math.Inf(0), 4 + case float32NegInfinity: + return math.Inf(-1), 4 + } + n := uint32(0) + for i := 0; i < 4; i++ { + n = n * 256 + n += uint32(pb[i]) + } + val := math.Float32frombits(n) + return float64(val), 4 + case additionalTypeFloat64: + pb := readNBytes(src, 8) + switch string(pb) { + case float64Nan: + return math.NaN(), 8 + case float64PosInfinity: + return math.Inf(0), 8 + case float64NegInfinity: + return math.Inf(-1), 8 + } + n := uint64(0) + for i := 0; i < 8; i++ { + n = n * 256 + n += uint64(pb[i]) + } + val := math.Float64frombits(n) + return val, 8 + } + panic(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 *bufio.Reader, noQuotes bool) []byte { + pb := readByte(src) + major := pb & maskOutAdditionalType + minor := pb & maskOutMajorType + if major != majorTypeByteString { + panic(fmt.Errorf("Major type is: %d in decodeString", major)) + } + result := []byte{} + if !noQuotes { + result = append(result, '"') + } + length := decodeIntAdditonalType(src, minor) + len := int(length) + pbs := readNBytes(src, len) + result = append(result, pbs...) + if noQuotes { + return result + } + return append(result, '"') +} + +func decodeUTF8String(src *bufio.Reader) []byte { + pb := readByte(src) + major := pb & maskOutAdditionalType + minor := pb & maskOutMajorType + if major != majorTypeUtf8String { + panic(fmt.Errorf("Major type is: %d in decodeUTF8String", major)) + } + result := []byte{'"'} + length := decodeIntAdditonalType(src, minor) + len := int(length) + pbs := readNBytes(src, len) + + for i := 0; i < len; 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 pbs[i] < 0x20 || pbs[i] > 0x7e || pbs[i] == '\\' || pbs[i] == '"' { + // We encountered a character that needs to be encoded. Switch + // to complex version of the algorithm. + dst := []byte{'"'} + dst = decodeStringComplex(dst, string(pbs), uint(i)) + return append(dst, '"') + } + } + // The string has no need for encoding an therefore is directly + // appended to the byte slice. + result = append(result, pbs...) + return append(result, '"') +} + +func array2Json(src *bufio.Reader, dst io.Writer) { + dst.Write([]byte{'['}) + pb := readByte(src) + major := pb & maskOutAdditionalType + minor := pb & maskOutMajorType + if major != majorTypeArray { + panic(fmt.Errorf("Major type is: %d in array2Json", major)) + } + len := 0 + unSpecifiedCount := false + if minor == additionalTypeInfiniteCount { + unSpecifiedCount = true + } else { + length := decodeIntAdditonalType(src, minor) + len = int(length) + } + for i := 0; unSpecifiedCount || i < len; i++ { + if unSpecifiedCount { + pb, e := src.Peek(1) + if e != nil { + panic(e) + } + if pb[0] == byte(majorTypeSimpleAndFloat|additionalTypeBreak) { + readByte(src) + break + } + } + cbor2JsonOneObject(src, dst) + if unSpecifiedCount { + pb, e := src.Peek(1) + if e != nil { + panic(e) + } + if pb[0] == byte(majorTypeSimpleAndFloat|additionalTypeBreak) { + readByte(src) + break + } + dst.Write([]byte{','}) + } else if i+1 < len { + dst.Write([]byte{','}) + } + } + dst.Write([]byte{']'}) +} + +func map2Json(src *bufio.Reader, dst io.Writer) { + pb := readByte(src) + major := pb & maskOutAdditionalType + minor := pb & maskOutMajorType + if major != majorTypeMap { + panic(fmt.Errorf("Major type is: %d in map2Json", major)) + } + len := 0 + unSpecifiedCount := false + if minor == additionalTypeInfiniteCount { + unSpecifiedCount = true + } else { + length := decodeIntAdditonalType(src, minor) + len = int(length) + } + dst.Write([]byte{'{'}) + for i := 0; unSpecifiedCount || i < len; i++ { + if unSpecifiedCount { + pb, e := src.Peek(1) + if e != nil { + panic(e) + } + if pb[0] == byte(majorTypeSimpleAndFloat|additionalTypeBreak) { + readByte(src) + break + } + } + cbor2JsonOneObject(src, dst) + if i%2 == 0 { + // Even position values are keys. + dst.Write([]byte{':'}) + } else { + if unSpecifiedCount { + pb, e := src.Peek(1) + if e != nil { + panic(e) + } + if pb[0] == byte(majorTypeSimpleAndFloat|additionalTypeBreak) { + readByte(src) + break + } + dst.Write([]byte{','}) + } else if i+1 < len { + dst.Write([]byte{','}) + } + } + } + dst.Write([]byte{'}'}) +} + +func decodeTagData(src *bufio.Reader) []byte { + pb := readByte(src) + major := pb & maskOutAdditionalType + minor := pb & maskOutMajorType + if major != majorTypeTags { + panic(fmt.Errorf("Major type is: %d in decodeTagData", major)) + } + switch minor { + case additionalTypeTimestamp: + return decodeTimeStamp(src) + + // Tag value is larger than 256 (so uint16). + case additionalTypeIntUint16: + val := decodeIntAdditonalType(src, minor) + + switch uint16(val) { + case additionalTypeEmbeddedJSON: + pb := readByte(src) + dataMajor := pb & maskOutAdditionalType + if dataMajor != majorTypeByteString { + panic(fmt.Errorf("Unsupported embedded Type: %d in decodeEmbeddedJSON", dataMajor)) + } + src.UnreadByte() + return decodeString(src, true) + + case additionalTypeTagNetworkAddr: + octets := decodeString(src, true) + ss := []byte{'"'} + switch len(octets) { + case 6: // MAC address. + ha := net.HardwareAddr(octets) + ss = append(append(ss, ha.String()...), '"') + case 4: // IPv4 address. + fallthrough + case 16: // IPv6 address. + ip := net.IP(octets) + ss = append(append(ss, ip.String()...), '"') + default: + panic(fmt.Errorf("Unexpected Network Address length: %d (expected 4,6,16)", len(octets))) + } + return ss + + case additionalTypeTagNetworkPrefix: + pb := readByte(src) + if pb != byte(majorTypeMap|0x1) { + panic(fmt.Errorf("IP Prefix is NOT of MAP of 1 elements as expected")) + } + octets := decodeString(src, true) + val := decodeInteger(src) + ip := net.IP(octets) + var mask net.IPMask + pfxLen := int(val) + if len(octets) == 4 { + mask = net.CIDRMask(pfxLen, 32) + } else { + mask = net.CIDRMask(pfxLen, 128) + } + ipPfx := net.IPNet{IP: ip, Mask: mask} + ss := []byte{'"'} + ss = append(append(ss, ipPfx.String()...), '"') + return ss + + case additionalTypeTagHexString: + octets := decodeString(src, true) + ss := []byte{'"'} + for _, v := range octets { + ss = append(ss, hexTable[v>>4], hexTable[v&0x0f]) + } + return append(ss, '"') + + default: + panic(fmt.Errorf("Unsupported Additional Tag Type: %d in decodeTagData", val)) + } + } + panic(fmt.Errorf("Unsupported Additional Type: %d in decodeTagData", minor)) +} + +func decodeTimeStamp(src *bufio.Reader) []byte { + pb := readByte(src) + src.UnreadByte() + tsMajor := pb & maskOutAdditionalType + if tsMajor == majorTypeUnsignedInt || tsMajor == majorTypeNegativeInt { + n := decodeInteger(src) + 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 + } else if tsMajor == majorTypeSimpleAndFloat { + n, _ := decodeFloat(src) + 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 + } + panic(fmt.Errorf("TS format is neigther int nor float: %d", tsMajor)) +} + +func decodeSimpleFloat(src *bufio.Reader) []byte { + pb := readByte(src) + major := pb & maskOutAdditionalType + minor := pb & maskOutMajorType + if major != majorTypeSimpleAndFloat { + panic(fmt.Errorf("Major type is: %d in decodeSimpleFloat", major)) + } + switch minor { + case additionalTypeBoolTrue: + return []byte("true") + case additionalTypeBoolFalse: + return []byte("false") + case additionalTypeNull: + return []byte("null") + case additionalTypeFloat16: + fallthrough + case additionalTypeFloat32: + fallthrough + case additionalTypeFloat64: + src.UnreadByte() + v, bc := decodeFloat(src) + ba := []byte{} + switch { + case math.IsNaN(v): + return []byte("\"NaN\"") + case math.IsInf(v, 1): + return []byte("\"+Inf\"") + case math.IsInf(v, -1): + return []byte("\"-Inf\"") + } + if bc == 5 { + ba = strconv.AppendFloat(ba, v, 'f', -1, 32) + } else { + ba = strconv.AppendFloat(ba, v, 'f', -1, 64) + } + return ba + default: + panic(fmt.Errorf("Invalid Additional Type: %d in decodeSimpleFloat", minor)) + } +} + +func cbor2JsonOneObject(src *bufio.Reader, dst io.Writer) { + pb, e := src.Peek(1) + if e != nil { + panic(e) + } + major := (pb[0] & maskOutAdditionalType) + + switch major { + case majorTypeUnsignedInt: + fallthrough + case majorTypeNegativeInt: + n := decodeInteger(src) + dst.Write([]byte(strconv.Itoa(int(n)))) + + case majorTypeByteString: + s := decodeString(src, false) + dst.Write(s) + + case majorTypeUtf8String: + s := decodeUTF8String(src) + dst.Write(s) + + case majorTypeArray: + array2Json(src, dst) + + case majorTypeMap: + map2Json(src, dst) + + case majorTypeTags: + s := decodeTagData(src) + dst.Write(s) + + case majorTypeSimpleAndFloat: + s := decodeSimpleFloat(src) + dst.Write(s) + } +} + +func moreBytesToRead(src *bufio.Reader) bool { + _, e := src.ReadByte() + if e == nil { + src.UnreadByte() + return true + } + return false +} + +// Cbor2JsonManyObjects decodes all the CBOR Objects read from src +// reader. It keeps on decoding until reader returns EOF (error when reading). +// Decoded string is written to the dst. At the end of every CBOR Object +// newline is written to the output stream. +// +// Returns error (if any) that was encountered during decode. +// The child functions will generate a panic when error is encountered and +// this function will recover non-runtime Errors and return the reason as error. +func Cbor2JsonManyObjects(src io.Reader, dst io.Writer) (err error) { + defer func() { + if r := recover(); r != nil { + if _, ok := r.(runtime.Error); ok { + panic(r) + } + err = r.(error) + } + }() + bufRdr := bufio.NewReader(src) + for moreBytesToRead(bufRdr) { + cbor2JsonOneObject(bufRdr, dst) + dst.Write([]byte("\n")) + } + return 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 +} + +func getReader(str string) *bufio.Reader { + return bufio.NewReader(strings.NewReader(str)) +} + +// 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(strings.NewReader(string(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(getReader(string(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(bytes.NewReader(in), &b) + return b.Bytes() + } + return in +} diff --git a/internal/cbor/decoder.go b/internal/cbor/decoder.go deleted file mode 100644 index 71e8648..0000000 --- a/internal/cbor/decoder.go +++ /dev/null @@ -1,548 +0,0 @@ -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 index b17ba11..812e27f 100644 --- a/internal/cbor/decoder_test.go +++ b/internal/cbor/decoder_test.go @@ -9,8 +9,8 @@ import ( 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 { + gotv := decodeInteger(getReader(tc.binary)) + if gotv != int64(tc.val) { t.Errorf("decodeInteger(0x%s)=0x%d, want: 0x%d", hex.EncodeToString([]byte(tc.binary)), gotv, tc.val) } @@ -19,10 +19,7 @@ func TestDecodeInteger(t *testing.T) { 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))) - } + got := decodeUTF8String(getReader(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))) @@ -33,10 +30,7 @@ func TestDecodeString(t *testing.T) { func TestDecodeArray(t *testing.T) { for _, tc := range integerArrayTestCases { buf := bytes.NewBuffer([]byte{}) - _, err := array2Json([]byte(tc.binary), buf) - if err != nil { - panic(err) - } + array2Json(getReader(tc.binary), buf) if buf.String() != tc.json { t.Errorf("array2Json(0x%s)=%s, want: %s", hex.EncodeToString([]byte(tc.binary)), buf.String(), tc.json) } @@ -54,20 +48,14 @@ func TestDecodeArray(t *testing.T) { } for _, tc := range infiniteArrayTestCases { buf := bytes.NewBuffer([]byte{}) - _, err := array2Json([]byte(tc.in), buf) - if err != nil { - panic(err) - } + array2Json(getReader(tc.in), buf) 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()) - } + array2Json(getReader(tc.binary), buf) if buf.String() != tc.json { t.Errorf("array2Json(0x%s)=%s, want: %s", hex.EncodeToString([]byte(tc.binary)), buf.String(), tc.json) } @@ -94,20 +82,14 @@ var mapDecodeTestCases = []struct { 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) - } + map2Json(getReader(string(tc.bin)), buf) 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) - } + map2Json(getReader(string(tc.bin)), buf) if buf.String() != tc.json { t.Errorf("map2Json(0x%s)=%s, want: %s", hex.EncodeToString(tc.bin), buf.String(), tc.json) } @@ -116,10 +98,7 @@ func TestDecodeMap(t *testing.T) { 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()) - } + got := decodeSimpleFloat(getReader(tc.binary)) if string(got) != tc.json { t.Errorf("decodeSimpleFloat(0x%s)=%s, want:%s", hex.EncodeToString([]byte(tc.binary)), string(got), tc.json) } @@ -128,10 +107,7 @@ func TestDecodeBool(t *testing.T) { 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()) - } + got, _ := decodeFloat(getReader(tc.binary)) if got != float64(tc.val) { t.Errorf("decodeFloat(0x%s)=%f, want:%f", hex.EncodeToString([]byte(tc.binary)), got, tc.val) } @@ -141,19 +117,13 @@ func TestDecodeFloat(t *testing.T) { 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()) - } + tm := decodeTagData(getReader(tc.binary)) 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()) - } + tm := decodeTagData(getReader(tc.out)) //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) @@ -166,6 +136,33 @@ func TestDecodeTimestamp(t *testing.T) { } } +func TestDecodeNetworkAddr(t *testing.T) { + for _, tc := range ipAddrTestCases { + d1 := decodeTagData(getReader(tc.binary)) + if string(d1) != tc.text { + t.Errorf("decodeNetworkAddr(0x%s)=%s, want:%s", hex.EncodeToString([]byte(tc.binary)), d1, tc.text) + } + } +} + +func TestDecodeMACAddr(t *testing.T) { + for _, tc := range macAddrTestCases { + d1 := decodeTagData(getReader(tc.binary)) + if string(d1) != tc.text { + t.Errorf("decodeNetworkAddr(0x%s)=%s, want:%s", hex.EncodeToString([]byte(tc.binary)), d1, tc.text) + } + } +} + +func TestDecodeIPPrefix(t *testing.T) { + for _, tc := range IPPrefixTestCases { + d1 := decodeTagData(getReader(tc.binary)) + if string(d1) != tc.text { + t.Errorf("decodeIPPrefix(0x%s)=%s, want:%s", hex.EncodeToString([]byte(tc.binary)), d1, tc.text) + } + } +} + var compositeCborTestCases = []struct { binary []byte json string @@ -177,12 +174,32 @@ var compositeCborTestCases = []struct { 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) + err := Cbor2JsonManyObjects(getReader(string(tc.binary)), buf) + if buf.String() != tc.json || err != nil { + t.Errorf("cbor2JsonManyObjects(0x%s)=%s, want: %s, err:%s", hex.EncodeToString(tc.binary), buf.String(), tc.json, err.Error()) + } + } +} + +var negativeCborTestCases = []struct { + binary []byte + errStr string +}{ + {[]byte("\xb9\x64IETF\x20\x65Array\x9f\x20\x00\x18\xc8\x14"), "Tried to Read 18 Bytes.. But hit end of file"}, + {[]byte("\xbf\x64IETF\x20\x65Array\x9f\x20\x00\x18\xc8\x14"), "EOF"}, + {[]byte("\xbf\x14IETF\x20\x65Array\x9f\x20\x00\x18\xc8\x14"), "Tried to Read 40736 Bytes.. But hit end of file"}, + {[]byte("\xbf\x64IETF"), "EOF"}, + {[]byte("\xbf\x64IETF\x20\x65Array\x9f\x20\x00\x18\xc8\xff\xff\xff"), "Invalid Additional Type: 31 in decodeSimpleFloat"}, + {[]byte("\xbf\x64IETF\x20\x65Array"), "EOF"}, + {[]byte("\xbf\x64"), "Tried to Read 4 Bytes.. But hit end of file"}, +} + +func TestDecodeNegativeCbor2Json(t *testing.T) { + for _, tc := range negativeCborTestCases { + buf := bytes.NewBuffer([]byte{}) + err := Cbor2JsonManyObjects(getReader(string(tc.binary)), buf) + if err == nil || err.Error() != tc.errStr { + t.Errorf("Expected error got:%s, want:%s", err, tc.errStr) } } } diff --git a/internal/cbor/examples/genLog.go b/internal/cbor/examples/genLog.go new file mode 100644 index 0000000..43a2000 --- /dev/null +++ b/internal/cbor/examples/genLog.go @@ -0,0 +1,55 @@ +package main + +import ( + "compress/zlib" + "flag" + "io" + "log" + "os" + "time" + + "github.com/rs/zerolog" +) + +func writeLog(fname string, count int, useCompress bool) { + opFile := os.Stdout + if fname != "" { + fil, _ := os.Create(fname) + opFile = fil + defer func() { + if err := fil.Close(); err != nil { + log.Fatal(err) + } + }() + } + + var f io.WriteCloser = opFile + if useCompress { + f = zlib.NewWriter(f) + defer func() { + if err := f.Close(); err != nil { + log.Fatal(err) + } + }() + + } + + zerolog.TimestampFunc = func() time.Time { return time.Now().Round(time.Second) } + log := zerolog.New(f).With(). + Timestamp(). + Logger() + for i := 0; i < count; i++ { + log.Error(). + Int("Fault", 41650+i).Msg("Some Message") + } +} + +func main() { + outFile := flag.String("out", "", "Output File to which logs will be written to (WILL overwrite if already present).") + numLogs := flag.Int("num", 10, "Number of log messages to generate.") + doCompress := flag.Bool("compress", false, "Enable inline compressed writer") + + flag.Parse() + + writeLog(*outFile, *numLogs, *doCompress) +} diff --git a/internal/cbor/examples/makefile b/internal/cbor/examples/makefile new file mode 100644 index 0000000..d28075d --- /dev/null +++ b/internal/cbor/examples/makefile @@ -0,0 +1,10 @@ +all: genLogJSON genLogCBOR + +genLogJSON: genLog.go + go build -o genLogJSON genLog.go + +genLogCBOR: genLog.go + go build -tags binary_log -o genLogCBOR genLog.go + +clean: + rm -f genLogJSON genLogCBOR diff --git a/internal/cbor/string.go b/internal/cbor/string.go index b90edac..8b3ab96 100644 --- a/internal/cbor/string.go +++ b/internal/cbor/string.go @@ -48,8 +48,13 @@ func AppendBytes(dst, s []byte) []byte { func AppendEmbeddedJSON(dst, s []byte) []byte { major := majorTypeTags minor := additionalTypeEmbeddedJSON - dst = append(dst, byte(major|minor)) + // Append the TAG to indicate this is Embedded JSON. + dst = append(dst, byte(major|additionalTypeIntUint16)) + dst = append(dst, byte(minor>>8)) + dst = append(dst, byte(minor&0xff)) + + // Append the JSON Object as Byte String. major = majorTypeByteString l := len(s) diff --git a/internal/cbor/types.go b/internal/cbor/types.go index b983ed7..9b1216d 100644 --- a/internal/cbor/types.go +++ b/internal/cbor/types.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "math" + "net" ) // AppendNull inserts a 'Nil' object into the dst byte array. @@ -430,9 +431,40 @@ func AppendArrayDelim(dst []byte) []byte { 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) +// AppendIPAddr encodes and inserts an IP Address (IPv4 or IPv6). +func AppendIPAddr(dst []byte, ip net.IP) []byte { + dst = append(dst, byte(majorTypeTags|additionalTypeIntUint16)) + dst = append(dst, byte(additionalTypeTagNetworkAddr>>8)) + dst = append(dst, byte(additionalTypeTagNetworkAddr&0xff)) + return AppendBytes(dst, ip) +} + +// AppendIPPrefix encodes and inserts an IP Address Prefix (Address + Mask Length). +func AppendIPPrefix(dst []byte, pfx net.IPNet) []byte { + dst = append(dst, byte(majorTypeTags|additionalTypeIntUint16)) + dst = append(dst, byte(additionalTypeTagNetworkPrefix>>8)) + dst = append(dst, byte(additionalTypeTagNetworkPrefix&0xff)) + + // Prefix is a tuple (aka MAP of 1 pair of elements) - + // first element is prefix, second is mask length. + dst = append(dst, byte(majorTypeMap|0x1)) + dst = AppendBytes(dst, pfx.IP) + maskLen, _ := pfx.Mask.Size() + return AppendUint8(dst, uint8(maskLen)) +} + +// AppendMACAddr encodes and inserts an Hardware (MAC) address. +func AppendMACAddr(dst []byte, ha net.HardwareAddr) []byte { + dst = append(dst, byte(majorTypeTags|additionalTypeIntUint16)) + dst = append(dst, byte(additionalTypeTagNetworkAddr>>8)) + dst = append(dst, byte(additionalTypeTagNetworkAddr&0xff)) + return AppendBytes(dst, ha) +} + +// AppendHex adds a TAG and inserts a hex bytes as a string. +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 index 010e2a0..b3a1094 100644 --- a/internal/cbor/types_test.go +++ b/internal/cbor/types_test.go @@ -2,6 +2,7 @@ package cbor import ( "encoding/hex" + "net" "testing" ) @@ -182,6 +183,72 @@ func TestAppendFloat32(t *testing.T) { } } +var ipAddrTestCases = []struct { + ipaddr net.IP + text string // ASCII representation of ipaddr + binary string // CBOR representation of ipaddr +}{ + {net.IP{10, 0, 0, 1}, "\"10.0.0.1\"", "\xd9\x01\x04\x44\x0a\x00\x00\x01"}, + {net.IP{0x20, 0x01, 0x0d, 0xb8, 0x85, 0xa3, 0x0, 0x0, 0x0, 0x0, 0x8a, 0x2e, 0x03, 0x70, 0x73, 0x34}, + "\"2001:db8:85a3::8a2e:370:7334\"", + "\xd9\x01\x04\x50\x20\x01\x0d\xb8\x85\xa3\x00\x00\x00\x00\x8a\x2e\x03\x70\x73\x34"}, +} + +func TestAppendNetworkAddr(t *testing.T) { + for _, tc := range ipAddrTestCases { + s := AppendIPAddr([]byte{}, tc.ipaddr) + got := string(s) + if got != tc.binary { + t.Errorf("AppendIPAddr(%s)=0x%s, want: 0x%s", + tc.ipaddr, hex.EncodeToString(s), + hex.EncodeToString([]byte(tc.binary))) + } + } +} + +var macAddrTestCases = []struct { + macaddr net.HardwareAddr + text string // ASCII representation of macaddr + binary string // CBOR representation of macaddr +}{ + {net.HardwareAddr{0x12, 0x34, 0x56, 0x78, 0x90, 0xab}, "\"12:34:56:78:90:ab\"", "\xd9\x01\x04\x46\x12\x34\x56\x78\x90\xab"}, + {net.HardwareAddr{0x20, 0x01, 0x0d, 0xb8, 0x85, 0xa3}, "\"20:01:0d:b8:85:a3\"", "\xd9\x01\x04\x46\x20\x01\x0d\xb8\x85\xa3"}, +} + +func TestAppendMacAddr(t *testing.T) { + for _, tc := range macAddrTestCases { + s := AppendMACAddr([]byte{}, tc.macaddr) + got := string(s) + if got != tc.binary { + t.Errorf("AppendMACAddr(%s)=0x%s, want: 0x%s", + tc.macaddr.String(), hex.EncodeToString(s), + hex.EncodeToString([]byte(tc.binary))) + } + } +} + +var IPPrefixTestCases = []struct { + pfx net.IPNet + text string // ASCII representation of pfx + binary string // CBOR representation of pfx +}{ + {net.IPNet{IP: net.IP{0, 0, 0, 0}, Mask: net.CIDRMask(0, 32)}, "\"0.0.0.0/0\"", "\xd9\x01\x05\xa1\x44\x00\x00\x00\x00\x00"}, + {net.IPNet{IP: net.IP{192, 168, 0, 100}, Mask: net.CIDRMask(24, 32)}, "\"192.168.0.100/24\"", + "\xd9\x01\x05\xa1\x44\xc0\xa8\x00\x64\x18\x18"}, +} + +func TestAppendIPPrefix(t *testing.T) { + for _, tc := range IPPrefixTestCases { + s := AppendIPPrefix([]byte{}, tc.pfx) + got := string(s) + if got != tc.binary { + t.Errorf("AppendIPPrefix(%s)=0x%s, want: 0x%s", + tc.pfx.String(), hex.EncodeToString(s), + hex.EncodeToString([]byte(tc.binary))) + } + } +} + func BenchmarkAppendInt(b *testing.B) { type st struct { sz byte diff --git a/internal/json/types.go b/internal/json/types.go index 927d1c8..1beff1f 100644 --- a/internal/json/types.go +++ b/internal/json/types.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "math" + "net" "strconv" ) @@ -333,6 +334,8 @@ func AppendInterface(dst []byte, i interface{}) []byte { return append(dst, marshaled...) } +// AppendObjectData takes in an object that is already in a byte array +// and adds it to the dst. func AppendObjectData(dst []byte, o []byte) []byte { // Two conditions we want to put a ',' between existing content and // new content: @@ -345,3 +348,19 @@ func AppendObjectData(dst []byte, o []byte) []byte { } return append(dst, o...) } + +// AppendIPAddr adds IPv4 or IPv6 address to dst. +func AppendIPAddr(dst []byte, ip net.IP) []byte { + return AppendString(dst, ip.String()) +} + +// AppendIPPrefix adds IPv4 or IPv6 Prefix (address & mask) to dst. +func AppendIPPrefix(dst []byte, pfx net.IPNet) []byte { + return AppendString(dst, pfx.String()) + +} + +// AppendMACAddr adds MAC address to dst. +func AppendMACAddr(dst []byte, ha net.HardwareAddr) []byte { + return AppendString(dst, ha.String()) +} diff --git a/internal/json/types_test.go b/internal/json/types_test.go index d90dac1..719649d 100644 --- a/internal/json/types_test.go +++ b/internal/json/types_test.go @@ -2,6 +2,7 @@ package json import ( "math" + "net" "reflect" "testing" ) @@ -61,3 +62,106 @@ func TestAppendType(t *testing.T) { }) } } + +func Test_appendMAC(t *testing.T) { + MACtests := []struct { + input string + want []byte + }{ + {"01:23:45:67:89:ab", []byte(`"01:23:45:67:89:ab"`)}, + {"cd:ef:11:22:33:44", []byte(`"cd:ef:11:22:33:44"`)}, + } + for _, tt := range MACtests { + t.Run("MAC", func(t *testing.T) { + ha, _ := net.ParseMAC(tt.input) + if got := AppendMACAddr([]byte{}, ha); !reflect.DeepEqual(got, tt.want) { + t.Errorf("appendMACAddr() = %s, want %s", got, tt.want) + } + }) + } +} + +func Test_appendIP(t *testing.T) { + IPv4tests := []struct { + input net.IP + want []byte + }{ + {net.IP{0, 0, 0, 0}, []byte(`"0.0.0.0"`)}, + {net.IP{192, 0, 2, 200}, []byte(`"192.0.2.200"`)}, + } + + for _, tt := range IPv4tests { + t.Run("IPv4", func(t *testing.T) { + if got := AppendIPAddr([]byte{}, tt.input); !reflect.DeepEqual(got, tt.want) { + t.Errorf("appendIPAddr() = %s, want %s", got, tt.want) + } + }) + } + IPv6tests := []struct { + input net.IP + want []byte + }{ + {net.IPv6zero, []byte(`"::"`)}, + {net.IPv6linklocalallnodes, []byte(`"ff02::1"`)}, + {net.IP{0x20, 0x01, 0x0d, 0xb8, 0x85, 0xa3, 0x00, 0x00, 0x00, 0x00, 0x8a, 0x2e, 0x03, 0x70, 0x73, 0x34}, []byte(`"2001:db8:85a3::8a2e:370:7334"`)}, + } + for _, tt := range IPv6tests { + t.Run("IPv6", func(t *testing.T) { + if got := AppendIPAddr([]byte{}, tt.input); !reflect.DeepEqual(got, tt.want) { + t.Errorf("appendIPAddr() = %s, want %s", got, tt.want) + } + }) + } +} + +func Test_appendIPPrefix(t *testing.T) { + IPv4Prefixtests := []struct { + input net.IPNet + want []byte + }{ + {net.IPNet{IP: net.IP{0, 0, 0, 0}, Mask: net.IPv4Mask(0, 0, 0, 0)}, []byte(`"0.0.0.0/0"`)}, + {net.IPNet{IP: net.IP{192, 0, 2, 200}, Mask: net.IPv4Mask(255, 255, 255, 0)}, []byte(`"192.0.2.200/24"`)}, + } + for _, tt := range IPv4Prefixtests { + t.Run("IPv4", func(t *testing.T) { + if got := AppendIPPrefix([]byte{}, tt.input); !reflect.DeepEqual(got, tt.want) { + t.Errorf("appendIPPrefix() = %s, want %s", got, tt.want) + } + }) + } + IPv6Prefixtests := []struct { + input net.IPNet + want []byte + }{ + {net.IPNet{IP: net.IPv6zero, Mask: net.CIDRMask(0, 128)}, []byte(`"::/0"`)}, + {net.IPNet{IP: net.IPv6linklocalallnodes, Mask: net.CIDRMask(128, 128)}, []byte(`"ff02::1/128"`)}, + {net.IPNet{IP: net.IP{0x20, 0x01, 0x0d, 0xb8, 0x85, 0xa3, 0x00, 0x00, 0x00, 0x00, 0x8a, 0x2e, 0x03, 0x70, 0x73, 0x34}, + Mask: net.CIDRMask(64, 128)}, + []byte(`"2001:db8:85a3::8a2e:370:7334/64"`)}, + } + for _, tt := range IPv6Prefixtests { + t.Run("IPv6", func(t *testing.T) { + if got := AppendIPPrefix([]byte{}, tt.input); !reflect.DeepEqual(got, tt.want) { + t.Errorf("appendIPPrefix() = %s, want %s", got, tt.want) + } + }) + } +} + +func Test_appendMac(t *testing.T) { + MACtests := []struct { + input net.HardwareAddr + want []byte + }{ + {net.HardwareAddr{0x12, 0x34, 0x56, 0x78, 0x90, 0xab}, []byte(`"12:34:56:78:90:ab"`)}, + {net.HardwareAddr{0x12, 0x34, 0x00, 0x00, 0x90, 0xab}, []byte(`"12:34:00:00:90:ab"`)}, + } + + for _, tt := range MACtests { + t.Run("MAC", func(t *testing.T) { + if got := AppendMACAddr([]byte{}, tt.input); !reflect.DeepEqual(got, tt.want) { + t.Errorf("appendMAC() = %s, want %s", got, tt.want) + } + }) + } +} diff --git a/log_example_test.go b/log_example_test.go index 9a1b344..5d541b3 100644 --- a/log_example_test.go +++ b/log_example_test.go @@ -5,6 +5,7 @@ package zerolog_test import ( "errors" stdlog "log" + "net" "os" "time" @@ -395,3 +396,36 @@ func ExampleContext_Durs() { // Output: {"foo":"bar","durs":[10000,20000],"message":"hello world"} } + +func ExampleContext_IPAddr() { + hostIP := net.IP{192, 168, 0, 100} + log := zerolog.New(os.Stdout).With(). + IPAddr("HostIP", hostIP). + Logger() + + log.Log().Msg("hello world") + + // Output: {"HostIP":"192.168.0.100","message":"hello world"} +} + +func ExampleContext_IPPrefix() { + route := net.IPNet{IP: net.IP{192, 168, 0, 0}, Mask: net.CIDRMask(24, 32)} + log := zerolog.New(os.Stdout).With(). + IPPrefix("Route", route). + Logger() + + log.Log().Msg("hello world") + + // Output: {"Route":"192.168.0.0/24","message":"hello world"} +} + +func ExampleContext_MacAddr() { + mac := net.HardwareAddr{0x00, 0x14, 0x22, 0x01, 0x23, 0x45} + log := zerolog.New(os.Stdout).With(). + MACAddr("hostMAC", mac). + Logger() + + log.Log().Msg("hello world") + + // Output: {"hostMAC":"00:14:22:01:23:45","message":"hello world"} +} diff --git a/log_test.go b/log_test.go index 339f459..ebe716e 100644 --- a/log_test.go +++ b/log_test.go @@ -4,6 +4,7 @@ import ( "bytes" "errors" "fmt" + "net" "reflect" "runtime" "testing" @@ -127,10 +128,11 @@ func TestFieldsMap(t *testing.T) { "uint64": uint64(10), "float32": float32(11), "float64": float64(12), + "ipv6": net.IP{0x20, 0x01, 0x0d, 0xb8, 0x85, 0xa3, 0x00, 0x00, 0x00, 0x00, 0x8a, 0x2e, 0x03, 0x70, 0x73, 0x34}, "dur": 1 * time.Second, "time": time.Time{}, }).Msg("") - 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 { + 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,"ipv6":"2001:db8:85a3::8a2e:370:7334","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) } } @@ -186,13 +188,17 @@ func TestFields(t *testing.T) { Uint16("uint16", 8). Uint32("uint32", 9). Uint64("uint64", 10). + IPAddr("IPv4", net.IP{192, 168, 0, 100}). + IPAddr("IPv6", net.IP{0x20, 0x01, 0x0d, 0xb8, 0x85, 0xa3, 0x00, 0x00, 0x00, 0x00, 0x8a, 0x2e, 0x03, 0x70, 0x73, 0x34}). + MACAddr("Mac", net.HardwareAddr{0x00, 0x14, 0x22, 0x01, 0x23, 0x45}). + IPPrefix("Prefix", net.IPNet{IP: net.IP{192, 168, 0, 100}, Mask: net.CIDRMask(24, 32)}). Float32("float32", 11). Float64("float64", 12). Dur("dur", 1*time.Second). Time("time", time.Time{}). TimeDiff("diff", now, now.Add(-10*time.Second)). Msg("") - 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 { + 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,"IPv4":"192.168.0.100","IPv6":"2001:db8:85a3::8a2e:370:7334","Mac":"00:14:22:01:23:45","Prefix":"192.168.0.100/24","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) } } From 70bea47cc0d8e7cac9c98fdd2bf8d0254e602be0 Mon Sep 17 00:00:00 2001 From: Ravi Raju Date: Fri, 13 Apr 2018 00:13:41 -0700 Subject: [PATCH 19/73] Fix for a bug in cbor decodeFloat (#51) --- array_test.go | 6 +++--- internal/cbor/decode_stream.go | 28 ++++++++++++++++------------ log_test.go | 12 ++++++------ 3 files changed, 25 insertions(+), 21 deletions(-) diff --git a/array_test.go b/array_test.go index 19bac56..66c6668 100644 --- a/array_test.go +++ b/array_test.go @@ -19,15 +19,15 @@ func TestArray(t *testing.T) { Uint16(8). Uint32(9). Uint64(10). - Float32(11). - Float64(12). + Float32(11.98122). + Float64(12.987654321). Str("a"). Bytes([]byte("b")). Hex([]byte{0x1f}). Time(time.Time{}). IPAddr(net.IP{192, 168, 0, 10}). Dur(0) - want := `[true,1,2,3,4,5,6,7,8,9,10,11,12,"a","b","1f","0001-01-01T00:00:00Z","192.168.0.10",0]` + want := `[true,1,2,3,4,5,6,7,8,9,10,11.98122,12.987654321,"a","b","1f","0001-01-01T00:00:00Z","192.168.0.10",0]` if got := decodeObjectToStr(a.write([]byte{})); got != want { t.Errorf("Array.write()\ngot: %s\nwant: %s", got, want) } diff --git a/internal/cbor/decode_stream.go b/internal/cbor/decode_stream.go index f1ff2a1..e3cf3b7 100644 --- a/internal/cbor/decode_stream.go +++ b/internal/cbor/decode_stream.go @@ -20,6 +20,9 @@ var decodeTimeZone *time.Location const hexTable = "0123456789abcdef" +const isFloat32 = 4 +const isFloat64 = 8 + func readNBytes(src *bufio.Reader, n int) []byte { ret := make([]byte, n) for i := 0; i < n; i++ { @@ -97,11 +100,11 @@ func decodeFloat(src *bufio.Reader) (float64, int) { pb := readNBytes(src, 4) switch string(pb) { case float32Nan: - return math.NaN(), 4 + return math.NaN(), isFloat32 case float32PosInfinity: - return math.Inf(0), 4 + return math.Inf(0), isFloat32 case float32NegInfinity: - return math.Inf(-1), 4 + return math.Inf(-1), isFloat32 } n := uint32(0) for i := 0; i < 4; i++ { @@ -109,16 +112,16 @@ func decodeFloat(src *bufio.Reader) (float64, int) { n += uint32(pb[i]) } val := math.Float32frombits(n) - return float64(val), 4 + return float64(val), isFloat32 case additionalTypeFloat64: pb := readNBytes(src, 8) switch string(pb) { case float64Nan: - return math.NaN(), 8 + return math.NaN(), isFloat64 case float64PosInfinity: - return math.Inf(0), 8 + return math.Inf(0), isFloat64 case float64NegInfinity: - return math.Inf(-1), 8 + return math.Inf(-1), isFloat64 } n := uint64(0) for i := 0; i < 8; i++ { @@ -126,14 +129,13 @@ func decodeFloat(src *bufio.Reader) (float64, int) { n += uint64(pb[i]) } val := math.Float64frombits(n) - return val, 8 + return val, isFloat64 } panic(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) { @@ -180,7 +182,7 @@ func decodeStringComplex(dst []byte, s string, pos uint) []byte { case '\t': dst = append(dst, '\\', 't') default: - dst = append(dst, '\\', 'u', '0', '0', hex[b>>4], hex[b&0xF]) + dst = append(dst, '\\', 'u', '0', '0', hexTable[b>>4], hexTable[b&0xF]) } i++ start = i @@ -481,10 +483,12 @@ func decodeSimpleFloat(src *bufio.Reader) []byte { case math.IsInf(v, -1): return []byte("\"-Inf\"") } - if bc == 5 { + if bc == isFloat32 { ba = strconv.AppendFloat(ba, v, 'f', -1, 32) - } else { + } else if bc == isFloat64 { ba = strconv.AppendFloat(ba, v, 'f', -1, 64) + } else { + panic(fmt.Errorf("Invalid Float precision from decodeFloat: %d", bc)) } return ba default: diff --git a/log_test.go b/log_test.go index ebe716e..6503927 100644 --- a/log_test.go +++ b/log_test.go @@ -95,14 +95,14 @@ func TestWith(t *testing.T) { Uint16("uint16", 8). Uint32("uint32", 9). Uint64("uint64", 10). - Float32("float32", 11). - Float64("float64", 12). + Float32("float32", 11.101). + Float64("float64", 12.30303). Time("time", time.Time{}) _, file, line, _ := runtime.Caller(0) caller := fmt.Sprintf("%s:%d", file, line+3) log := ctx.Caller().Logger() log.Log().Msg("") - 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 { + 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.101,"float64":12.30303,"time":"0001-01-01T00:00:00Z","caller":"`+caller+`"}`+"\n"; got != want { t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want) } } @@ -192,13 +192,13 @@ func TestFields(t *testing.T) { IPAddr("IPv6", net.IP{0x20, 0x01, 0x0d, 0xb8, 0x85, 0xa3, 0x00, 0x00, 0x00, 0x00, 0x8a, 0x2e, 0x03, 0x70, 0x73, 0x34}). MACAddr("Mac", net.HardwareAddr{0x00, 0x14, 0x22, 0x01, 0x23, 0x45}). IPPrefix("Prefix", net.IPNet{IP: net.IP{192, 168, 0, 100}, Mask: net.CIDRMask(24, 32)}). - Float32("float32", 11). - Float64("float64", 12). + Float32("float32", 11.1234). + Float64("float64", 12.321321321). Dur("dur", 1*time.Second). Time("time", time.Time{}). TimeDiff("diff", now, now.Add(-10*time.Second)). Msg("") - 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,"IPv4":"192.168.0.100","IPv6":"2001:db8:85a3::8a2e:370:7334","Mac":"00:14:22:01:23:45","Prefix":"192.168.0.100/24","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,"IPv4":"192.168.0.100","IPv6":"2001:db8:85a3::8a2e:370:7334","Mac":"00:14:22:01:23:45","Prefix":"192.168.0.100/24","float32":11.1234,"float64":12.321321321,"dur":1000,"time":"0001-01-01T00:00:00Z","diff":10000}`+"\n"; got != want { t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want) } } From 1e2ce57d98f787b7b062608f0cacd9736a6a8dd8 Mon Sep 17 00:00:00 2001 From: Ilya Galimyanov Date: Wed, 18 Apr 2018 01:52:22 +0300 Subject: [PATCH 20/73] Make GlobalLevel a public function (#53) --- globals.go | 3 ++- log.go | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/globals.go b/globals.go index b65d0cb..5c2aa9c 100644 --- a/globals.go +++ b/globals.go @@ -49,7 +49,8 @@ func SetGlobalLevel(l Level) { atomic.StoreUint32(gLevel, uint32(l)) } -func globalLevel() Level { +// GlobalLevel returns the current global log level +func GlobalLevel() Level { return Level(atomic.LoadUint32(gLevel)) } diff --git a/log.go b/log.go index 6ca93bb..e446f2f 100644 --- a/log.go +++ b/log.go @@ -366,7 +366,7 @@ func (l *Logger) newEvent(level Level, done func(string)) *Event { // should returns true if the log event should be logged. func (l *Logger) should(lvl Level) bool { - if lvl < l.level || lvl < globalLevel() { + if lvl < l.level || lvl < GlobalLevel() { return false } if l.sampler != nil && !samplingDisabled() { From 711d95f5f15c629338eebfbb919cbe90c70600a1 Mon Sep 17 00:00:00 2001 From: Dan Gillis Date: Wed, 18 Apr 2018 23:29:59 -0400 Subject: [PATCH 21/73] Some new readability updates (#54) --- README.md | 198 ++++++++++++++++++++++++++++++++---------------------- 1 file changed, 118 insertions(+), 80 deletions(-) diff --git a/README.md b/README.md index 14b9577..0308a44 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Uber's [zap](https://godoc.org/go.uber.org/zap) library pioneered this approach. To keep the code base and the API simple, zerolog focuses on efficient structured logging only. Pretty logging on the console is made possible using the provided (but inefficient) `zerolog.ConsoleWriter`. -![](pretty.png) +![Pretty Logging Image](pretty.png) ## Who uses zerolog @@ -34,32 +34,61 @@ Find out [who uses zerolog](https://github.com/rs/zerolog/wiki/Who-uses-zerolog) ```go go get -u github.com/rs/zerolog/log ``` + ## Getting Started ### Simple Logging Example For simple logging, import the global logger package **github.com/rs/zerolog/log** + ```go package main import ( - "github.com/rs/zerolog" - "github.com/rs/zerolog/log" + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" ) func main() { - // UNIX Time is faster and smaller than most timestamps - // If you set zerolog.TimeFieldFormat to an empty string, - // logs will write with UNIX time - zerolog.TimeFieldFormat = "" + // UNIX Time is faster and smaller than most timestamps + // If you set zerolog.TimeFieldFormat to an empty string, + // logs will write with UNIX time + zerolog.TimeFieldFormat = "" - log.Print("hello world") + log.Print("hello world") } // Output: {"time":1516134303,"level":"debug","message":"hello world"} ``` + > Note: The default log level for `log.Print` is *debug* +### Contextual Logging + +**zerolog** allows data to be added to log messages in the form of key:value pairs. The data added to the message adds "context" about the log event that can be critical for debugging as well as myriad other purposes. An example of this is below: + +```go +package main + +import ( + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" +) + +func main() { + zerolog.TimeFieldFormat = "" + + log.Debug(). + Str("Scale", "833 cents"). + Float64("Interval", 833.09). + Msg("Fibonacci is everywhere") +} + +// Output: {"time":1524104936,"level":"debug","Scale":"833 cents","Interval":833.09,"message":"Fibonacci is everywhere"} +``` + +> You'll note in the above example that when adding contextual fields, the fields are strongly typed. You can find the full list of supported fields [here](#standard-types) + ### Leveled Logging #### Simple Leveled Logging Example @@ -68,26 +97,29 @@ func main() { package main import ( - "github.com/rs/zerolog" - "github.com/rs/zerolog/log" + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" ) func main() { - zerolog.TimeFieldFormat = "" + zerolog.TimeFieldFormat = "" - log.Info().Msg("hello world") + log.Info().Msg("hello world") } // Output: {"time":1516134303,"level":"info","message":"hello world"} ``` +> It is very important to note that when using the **zerolog** chaining API, as shown above (`log.Info().Msg("hello world"`), the chain must have either the `Msg` or `Msgf` method call. If you forget to add either of these, the log will not occur and there is no compile time error to alert you of this. + **zerolog** allows for logging at the following levels (from highest to lowest): -- panic (`zerolog.PanicLevel`, 5) -- fatal (`zerolog.FatalLevel`, 4) -- error (`zerolog.ErrorLevel`, 3) -- warn (`zerolog.WarnLevel`, 2) -- info (`zerolog.InfoLevel`, 1) -- debug (`zerolog.DebugLevel`, 0) + +* panic (`zerolog.PanicLevel`, 5) +* fatal (`zerolog.FatalLevel`, 4) +* error (`zerolog.ErrorLevel`, 3) +* warn (`zerolog.WarnLevel`, 2) +* info (`zerolog.InfoLevel`, 1) +* debug (`zerolog.DebugLevel`, 0) You can set the Global logging level to any of these options using the `SetGlobalLevel` function in the zerolog package, passing in one of the given constants above, e.g. `zerolog.InfoLevel` would be the "info" level. Whichever level is chosen, all logs with a level greater than or equal to that level will be written. To turn off logging entirely, pass the `zerolog.Disabled` constant. @@ -99,41 +131,44 @@ This example uses command-line flags to demonstrate various outputs depending on package main import ( - "flag" + "flag" - "github.com/rs/zerolog" - "github.com/rs/zerolog/log" + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" ) func main() { - zerolog.TimeFieldFormat = "" - debug := flag.Bool("debug", false, "sets log level to debug") + zerolog.TimeFieldFormat = "" + debug := flag.Bool("debug", false, "sets log level to debug") - flag.Parse() + flag.Parse() - // Default level for this example is info, unless debug flag is present - zerolog.SetGlobalLevel(zerolog.InfoLevel) - if *debug { - zerolog.SetGlobalLevel(zerolog.DebugLevel) - } + // Default level for this example is info, unless debug flag is present + zerolog.SetGlobalLevel(zerolog.InfoLevel) + if *debug { + zerolog.SetGlobalLevel(zerolog.DebugLevel) + } - log.Debug().Msg("This message appears only when log level set to Debug") - log.Info().Msg("This message appears when log level set to Debug or Info") + log.Debug().Msg("This message appears only when log level set to Debug") + log.Info().Msg("This message appears when log level set to Debug or Info") - if e := log.Debug(); e.Enabled() { - // Compute log output only if enabled. - value := "bar" - e.Str("foo", value).Msg("some debug message") - } + if e := log.Debug(); e.Enabled() { + // Compute log output only if enabled. + value := "bar" + e.Str("foo", value).Msg("some debug message") + } } ``` + Info Output (no flag) + ```bash $ ./logLevelExample {"time":1516387492,"level":"info","message":"This message appears when log level set to Debug or Info"} ``` Debug Output (debug flag set) + ```bash $ ./logLevelExample -debug {"time":1516387573,"level":"debug","message":"This message appears only when log level set to Debug"} @@ -141,48 +176,59 @@ $ ./logLevelExample -debug {"time":1516387573,"level":"debug","foo":"bar","message":"some debug message"} ``` +#### Logging without Level or Message + +You may choose to log without a specific level by using the `Log` method. You may also write without a message by setting an empty string in the `msg string` parameter of the `Msg` method. Both are demonstrated in the example below. + +```go +package main + +import ( + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" +) + +func main() { + zerolog.TimeFieldFormat = "" + + log.Log(). + Str("foo", "bar"). + Msg("") +} + +// Output: {"time":1494567715,"foo":"bar"} +``` + #### Logging Fatal Messages ```go package main import ( - "errors" + "errors" - "github.com/rs/zerolog" - "github.com/rs/zerolog/log" + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" ) func main() { - err := errors.New("A repo man spends his life getting into tense situations") - service := "myservice" + err := errors.New("A repo man spends his life getting into tense situations") + service := "myservice" - zerolog.TimeFieldFormat = "" + zerolog.TimeFieldFormat = "" - log.Fatal(). - Err(err). - Str("service", service). - Msgf("Cannot start %s", service) + log.Fatal(). + Err(err). + Str("service", service). + Msgf("Cannot start %s", service) } // Output: {"time":1516133263,"level":"fatal","error":"A repo man spends his life getting into tense situations","service":"myservice","message":"Cannot start myservice"} // exit status 1 ``` + > NOTE: Using `Msgf` generates one allocation even when the logger is disabled. -### Contextual Logging - -#### Fields can be added to log messages - -```go -log.Info(). - Str("foo", "bar"). - Int("n", 123). - Msg("hello world") - -// Output: {"level":"info","time":1494567715,"foo":"bar","n":123,"message":"hello world"} -``` - ### Create logger instance to manage different outputs ```go @@ -241,14 +287,6 @@ log.Info().Msg("hello world") // Output: {"l":"info","t":1494567715,"m":"hello world"} ``` -### Log with no level nor message - -```go -log.Log().Str("foo","bar").Msg("") - -// Output: {"time":1494567715,"foo":"bar"} -``` - ### Add contextual fields to the global logger ```go @@ -404,8 +442,8 @@ Some settings can be changed and will by applied to all loggers: * `zerolog.MessageFieldName`: Can be set to customize message field name. * `zerolog.ErrorFieldName`: Can be set to customize `Err` field name. * `zerolog.TimeFieldFormat`: Can be set to customize `Time` field value formatting. If set with an empty string, times are formated as UNIX timestamp. - // DurationFieldUnit defines the unit for time.Duration type fields added - // using the Dur method. + // DurationFieldUnit defines the unit for time.Duration type fields added + // using the Dur method. * `DurationFieldUnit`: Sets the unit of the fields added by `Dur` (default: `time.Millisecond`). * `DurationFieldInteger`: If set to true, `Dur` fields are formatted as integers instead of floats. @@ -430,31 +468,31 @@ Some settings can be changed and will by applied to all loggers: ## Binary Encoding -In addition to the default JSON encoding, `zerolog` can produce binary logs using the [cbor](http://cbor.io) encoding. The choice of encoding can be decided at compile time using the build tag `binary_log` as follow: +In addition to the default JSON encoding, `zerolog` can produce binary logs using [CBOR](http://cbor.io) encoding. The choice of encoding can be decided at compile time using the build tag `binary_log` as follows: -``` +```bash go build -tags binary_log . ``` To Decode binary encoded log files you can use any CBOR decoder. One has been tested to work -with zerolog library is CSD(https://github.com/toravir/csd/). +with zerolog library is [CSD](https://github.com/toravir/csd/). ## Benchmarks All operations are allocation free (those numbers *include* JSON encoding): -``` -BenchmarkLogEmpty-8 100000000 19.1 ns/op 0 B/op 0 allocs/op -BenchmarkDisabled-8 500000000 4.07 ns/op 0 B/op 0 allocs/op -BenchmarkInfo-8 30000000 42.5 ns/op 0 B/op 0 allocs/op -BenchmarkContextFields-8 30000000 44.9 ns/op 0 B/op 0 allocs/op -BenchmarkLogFields-8 10000000 184 ns/op 0 B/op 0 allocs/op +```text +BenchmarkLogEmpty-8 100000000 19.1 ns/op 0 B/op 0 allocs/op +BenchmarkDisabled-8 500000000 4.07 ns/op 0 B/op 0 allocs/op +BenchmarkInfo-8 30000000 42.5 ns/op 0 B/op 0 allocs/op +BenchmarkContextFields-8 30000000 44.9 ns/op 0 B/op 0 allocs/op +BenchmarkLogFields-8 10000000 184 ns/op 0 B/op 0 allocs/op ``` There are a few Go logging benchmarks and comparisons that include zerolog. -- [imkira/go-loggers-bench](https://github.com/imkira/go-loggers-bench) -- [uber-common/zap](https://github.com/uber-go/zap#performance) +* [imkira/go-loggers-bench](https://github.com/imkira/go-loggers-bench) +* [uber-common/zap](https://github.com/uber-go/zap#performance) Using Uber's zap comparison benchmark: From d2b7a51951c626ef9e7ca5ba8fe1cf1c4adefdba Mon Sep 17 00:00:00 2001 From: Olivier Poitrey Date: Thu, 19 Apr 2018 13:12:29 -0700 Subject: [PATCH 22/73] Do not print large ints using scientific notation with ConsoleWriter Fixes #55 --- console.go | 6 ++++-- console_test.go | 16 ++++++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/console.go b/console.go index ac3e3cd..69e08f5 100644 --- a/console.go +++ b/console.go @@ -40,7 +40,9 @@ 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) + d := json.NewDecoder(bytes.NewReader(p)) + d.UseNumber() + err = d.Decode(&event) if err != nil { return } @@ -76,7 +78,7 @@ func (w ConsoleWriter) Write(p []byte) (n int, err error) { } else { buf.WriteString(value) } - case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64: + case json.Number: fmt.Fprint(buf, value) default: b, err := json.Marshal(value) diff --git a/console_test.go b/console_test.go index a287f5b..a6e7e06 100644 --- a/console_test.go +++ b/console_test.go @@ -1,7 +1,10 @@ package zerolog_test import ( + "bytes" "os" + "strings" + "testing" "github.com/rs/zerolog" ) @@ -12,3 +15,16 @@ func ExampleConsoleWriter_Write() { log.Info().Msg("hello world") // Output: |INFO| hello world } + +func TestConsoleWriterNumbers(t *testing.T) { + buf := &bytes.Buffer{} + log := zerolog.New(zerolog.ConsoleWriter{Out: buf, NoColor: true}) + log.Info(). + Float64("float", 1.23). + Uint64("small", 123). + Uint64("big", 1152921504606846976). + Msg("msg") + if got, want := strings.TrimSpace(buf.String()), " |INFO| msg big=1152921504606846976 float=1.23 small=123"; got != want { + t.Errorf("\ngot:\n%s\nwant:\n%s", got, want) + } +} From 89162918d050fd4b6ec2e4e0da3d03d51e4b0e05 Mon Sep 17 00:00:00 2001 From: Olivier Poitrey Date: Thu, 26 Apr 2018 13:41:11 -0700 Subject: [PATCH 23/73] Add grpc-zerolog reference (fix #58) --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 0308a44..552146d 100644 --- a/README.md +++ b/README.md @@ -477,6 +477,10 @@ go build -tags binary_log . To Decode binary encoded log files you can use any CBOR decoder. One has been tested to work with zerolog library is [CSD](https://github.com/toravir/csd/). +## Related Projects + +* [grpc-zerolog](https://github.com/cheapRoc/grpc-zerolog): Implementation of `grpclog.LoggerV2` interface using `zerolog` + ## Benchmarks All operations are allocation free (those numbers *include* JSON encoding): From 57da509ee1873ad420b56633989c17bebc94e321 Mon Sep 17 00:00:00 2001 From: Ravi Raju Date: Thu, 26 Apr 2018 23:15:29 -0700 Subject: [PATCH 24/73] Add JournalD Writer (#57) JournalD writer decodes each log event and map fields to journald fields. The JSON payload is kept in the `JSON` field. --- encoder_cbor.go | 4 +- journald/journald.go | 112 ++++++++++++++++++++++++++++++++++++++ journald/journald_test.go | 44 +++++++++++++++ log.go | 22 ++++++++ 4 files changed, 180 insertions(+), 2 deletions(-) create mode 100644 journald/journald.go create mode 100644 journald/journald_test.go diff --git a/encoder_cbor.go b/encoder_cbor.go index aa72013..d345c8f 100644 --- a/encoder_cbor.go +++ b/encoder_cbor.go @@ -185,8 +185,8 @@ func appendArrayDelim(dst []byte) []byte { } 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. + // Map begin character is present in the src, which + // should not be copied when appending to existing data. return cbor.AppendObjectData(dst, src[1:]) } diff --git a/journald/journald.go b/journald/journald.go new file mode 100644 index 0000000..fecc99b --- /dev/null +++ b/journald/journald.go @@ -0,0 +1,112 @@ +// +build !windows + +package journald + +// This file provides a zerolog writer so that logs printed +// using zerolog library can be sent to a journalD. + +// Zerolog's Top level key/Value Pairs are translated to +// journald's args - all Values are sent to journald as strings. +// And all key strings are converted to uppercase before sending +// to journald (as required by journald). + +// In addition, entire log message (all Key Value Pairs), is also +// sent to journald under the key "JSON". + +import ( + "bytes" + "encoding/json" + "fmt" + "github.com/coreos/go-systemd/journal" + "github.com/rs/zerolog" + "github.com/rs/zerolog/internal/cbor" + "io" + "strings" +) + +const defaultJournalDPrio = journal.PriNotice + +// NewJournalDWriter returns a zerolog log destination +// to be used as parameter to New() calls. Writing logs +// to this writer will send the log messages to journalD +// running in this system. +func NewJournalDWriter() io.Writer { + return journalWriter{} +} + +type journalWriter struct { +} + +// levelToJPrio converts zerolog Level string into +// journalD's priority values. JournalD has more +// priorities than zerolog. +func levelToJPrio(zLevel string) journal.Priority { + lvl, _ := zerolog.ParseLevel(zLevel) + + switch lvl { + case zerolog.DebugLevel: + return journal.PriDebug + case zerolog.InfoLevel: + return journal.PriInfo + case zerolog.WarnLevel: + return journal.PriWarning + case zerolog.ErrorLevel: + return journal.PriErr + case zerolog.FatalLevel: + return journal.PriCrit + case zerolog.PanicLevel: + return journal.PriEmerg + case zerolog.NoLevel: + return journal.PriNotice + } + return defaultJournalDPrio +} + +func (w journalWriter) Write(p []byte) (n int, err error) { + if !journal.Enabled() { + err = fmt.Errorf("Cannot connect to journalD!!") + return + } + var event map[string]interface{} + p = cbor.DecodeIfBinaryToBytes(p) + d := json.NewDecoder(bytes.NewReader(p)) + d.UseNumber() + err = d.Decode(&event) + jPrio := defaultJournalDPrio + args := make(map[string]string, 0) + if err != nil { + return + } + if l, ok := event[zerolog.LevelFieldName].(string); ok { + jPrio = levelToJPrio(l) + } + + msg := "" + for key, value := range event { + jKey := strings.ToUpper(key) + switch key { + case zerolog.LevelFieldName, zerolog.TimestampFieldName: + continue + case zerolog.MessageFieldName: + msg, _ = value.(string) + continue + } + + switch value.(type) { + case string: + args[jKey], _ = value.(string) + case json.Number: + args[jKey] = fmt.Sprint(value) + default: + b, err := json.Marshal(value) + if err != nil { + args[jKey] = fmt.Sprintf("[error: %v]", err) + } else { + args[jKey] = string(b) + } + } + } + args["JSON"] = string(p) + err = journal.Send(msg, jPrio, args) + return +} diff --git a/journald/journald_test.go b/journald/journald_test.go new file mode 100644 index 0000000..7ea40b5 --- /dev/null +++ b/journald/journald_test.go @@ -0,0 +1,44 @@ +// +build !windows + +package journald_test + +import "github.com/rs/zerolog" +import "github.com/rs/zerolog/journald" + +func ExampleNewJournalDWriter() { + log := zerolog.New(journald.NewJournalDWriter()) + log.Info().Str("foo", "bar").Uint64("small", 123).Float64("float", 3.14).Uint64("big", 1152921504606846976).Msg("Journal Test") + // Output: +} + +/* + +There is no automated way to verify the output - since the output is sent +to journald process and method to retrieve is journalctl. Will find a way +to automate the process and fix this test. + +$ journalctl -o verbose -f + +Thu 2018-04-26 22:30:20.768136 PDT [s=3284d695bde946e4b5017c77a399237f;i=329f0;b=98c0dca0debc4b98a5b9534e910e7dd6;m=7a702e35dd4;t=56acdccd2ed0a;x=4690034cf0348614] + PRIORITY=6 + _AUDIT_LOGINUID=1000 + _BOOT_ID=98c0dca0debc4b98a5b9534e910e7dd6 + _MACHINE_ID=926ed67eb4744580948de70fb474975e + _HOSTNAME=sprint + _UID=1000 + _GID=1000 + _CAP_EFFECTIVE=0 + _SYSTEMD_SLICE=-.slice + _TRANSPORT=journal + _SYSTEMD_CGROUP=/ + _AUDIT_SESSION=2945 + MESSAGE=Journal Test + FOO=bar + BIG=1152921504606846976 + _COMM=journald.test + SMALL=123 + FLOAT=3.14 + JSON={"level":"info","foo":"bar","small":123,"float":3.14,"big":1152921504606846976,"message":"Journal Test"} + _PID=27103 + _SOURCE_REALTIME_TIMESTAMP=1524807020768136 +*/ diff --git a/log.go b/log.go index e446f2f..655954b 100644 --- a/log.go +++ b/log.go @@ -148,6 +148,28 @@ func (l Level) String() string { return "" } +// ParseLevel converts a level string into a zerolog Level value. +// returns an error if the input string does not match known values. +func ParseLevel(levelStr string) (Level, error) { + switch levelStr { + case DebugLevel.String(): + return DebugLevel, nil + case InfoLevel.String(): + return InfoLevel, nil + case WarnLevel.String(): + return WarnLevel, nil + case ErrorLevel.String(): + return ErrorLevel, nil + case FatalLevel.String(): + return FatalLevel, nil + case PanicLevel.String(): + return PanicLevel, nil + case NoLevel.String(): + return NoLevel, nil + } + return NoLevel, fmt.Errorf("Unknown Level String: '%s', defaulting to NoLevel", levelStr) +} + // A Logger represents an active logging object that generates lines // of JSON output to an io.Writer. Each logging operation makes a single // call to the Writer's Write method. There is no guaranty on access From 79281e4bf6cf29d46018ff8604c51c0056d5e45f Mon Sep 17 00:00:00 2001 From: Olivier Poitrey Date: Wed, 9 May 2018 03:51:07 -0700 Subject: [PATCH 25/73] Fix Event.Times when format is set to UNIX time --- internal/json/time.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/json/time.go b/internal/json/time.go index 866cc4d..6007d58 100644 --- a/internal/json/time.go +++ b/internal/json/time.go @@ -42,7 +42,7 @@ func appendUnixTimes(dst []byte, vals []time.Time) []byte { dst = strconv.AppendInt(dst, vals[0].Unix(), 10) if len(vals) > 1 { for _, t := range vals[1:] { - dst = strconv.AppendInt(dst, t.Unix(), 10) + dst = strconv.AppendInt(append(dst, ','), t.Unix(), 10) } } dst = append(dst, ']') From a572c9d1f674e774330ab0dda39e5902e184fab0 Mon Sep 17 00:00:00 2001 From: Olivier Poitrey Date: Wed, 9 May 2018 03:51:52 -0700 Subject: [PATCH 26/73] Add missing support for zerolog marshable objects to Fields --- context.go | 2 +- event.go | 7 ++----- fields.go | 11 ++++++++++- log.go | 2 +- log_test.go | 3 ++- 5 files changed, 16 insertions(+), 9 deletions(-) diff --git a/context.go b/context.go index 1f92dae..146a5df 100644 --- a/context.go +++ b/context.go @@ -52,7 +52,7 @@ func (c Context) Array(key string, arr LogArrayMarshaler) Context { // Object marshals an object that implement the LogObjectMarshaler interface. func (c Context) Object(key string, obj LogObjectMarshaler) Context { - e := newEvent(levelWriterAdapter{ioutil.Discard}, 0, true) + e := newEvent(levelWriterAdapter{ioutil.Discard}, 0) e.Object(key, obj) c.l.context = appendObjectData(c.l.context, e.buf) eventPool.Put(e) diff --git a/event.go b/event.go index dffaf3e..7481a40 100644 --- a/event.go +++ b/event.go @@ -41,10 +41,7 @@ type LogArrayMarshaler interface { MarshalZerologArray(a *Array) } -func newEvent(w LevelWriter, level Level, enabled bool) *Event { - if !enabled { - return &Event{} - } +func newEvent(w LevelWriter, level Level) *Event { e := eventPool.Get().(*Event) e.buf = e.buf[:0] e.h = e.h[:0] @@ -144,7 +141,7 @@ func (e *Event) Dict(key string, dict *Event) *Event { // Call usual field methods like Str, Int etc to add fields to this // event and give it as argument the *Event.Dict method. func Dict() *Event { - return newEvent(nil, 0, true) + return newEvent(nil, 0) } // Array adds the field key with an array to the event context. diff --git a/fields.go b/fields.go index 9d13f0c..8d8a62b 100644 --- a/fields.go +++ b/fields.go @@ -14,7 +14,16 @@ func appendFields(dst []byte, fields map[string]interface{}) []byte { sort.Strings(keys) for _, key := range keys { dst = appendKey(dst, key) - switch val := fields[key].(type) { + val := fields[key] + if val, ok := val.(LogObjectMarshaler); ok { + e := newEvent(nil, 0) + e.buf = e.buf[:0] + e.appendObject(val) + dst = append(dst, e.buf...) + eventPool.Put(e) + continue + } + switch val := val.(type) { case string: dst = appendString(dst, val) case []byte: diff --git a/log.go b/log.go index 655954b..528ac77 100644 --- a/log.go +++ b/log.go @@ -374,7 +374,7 @@ func (l *Logger) newEvent(level Level, done func(string)) *Event { if !enabled { return nil } - e := newEvent(l.w, level, true) + e := newEvent(l.w, level) e.done = done e.ch = l.hooks if level != NoLevel { diff --git a/log_test.go b/log_test.go index 6503927..1175af2 100644 --- a/log_test.go +++ b/log_test.go @@ -131,8 +131,9 @@ func TestFieldsMap(t *testing.T) { "ipv6": net.IP{0x20, 0x01, 0x0d, 0xb8, 0x85, 0xa3, 0x00, 0x00, 0x00, 0x00, 0x8a, 0x2e, 0x03, 0x70, 0x73, 0x34}, "dur": 1 * time.Second, "time": time.Time{}, + "obj": obj{"a", "b", 1}, }).Msg("") - 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,"ipv6":"2001:db8:85a3::8a2e:370:7334","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,"ipv6":"2001:db8:85a3::8a2e:370:7334","nil":null,"obj":{"Pub":"a","Tag":"b","priv":1},"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) } } From ea1184be2bbeb1b1b747028ec12d115985632214 Mon Sep 17 00:00:00 2001 From: Olivier Poitrey Date: Thu, 10 May 2018 15:01:41 -0700 Subject: [PATCH 27/73] Get back some ns by removing the extra inferance added by binary support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit benchstat old new name old time/op new time/op delta LogEmpty-8 15.2ns ±14% 13.4ns ± 3% -12.11% (p=0.008 n=5+5) Disabled-8 2.50ns ± 1% 2.28ns ± 6% -8.81% (p=0.008 n=5+5) Info-8 44.4ns ± 1% 36.4ns ± 4% -17.99% (p=0.008 n=5+5) ContextFields-8 47.6ns ± 1% 39.4ns ± 7% -17.30% (p=0.008 n=5+5) ContextAppend-8 18.9ns ± 4% 15.2ns ± 4% -19.68% (p=0.008 n=5+5) LogFields-8 181ns ± 2% 173ns ± 2% -4.63% (p=0.008 n=5+5) LogArrayObject-8 530ns ± 3% 487ns ± 3% -8.11% (p=0.008 n=5+5) LogFieldType/Int-8 29.5ns ± 3% 28.8ns ± 2% ~ (p=0.167 n=5+5) LogFieldType/Interface-8 180ns ± 7% 175ns ± 4% ~ (p=0.579 n=5+5) LogFieldType/Interface(Object)-8 87.8ns ± 3% 80.5ns ± 1% -8.29% (p=0.008 n=5+5) LogFieldType/Object-8 83.7ns ± 2% 77.2ns ± 3% -7.76% (p=0.008 n=5+5) LogFieldType/Bools-8 34.6ns ± 3% 32.3ns ± 6% -6.64% (p=0.032 n=5+5) LogFieldType/Float-8 43.0ns ± 4% 40.5ns ± 4% -5.86% (p=0.016 n=5+5) LogFieldType/Str-8 29.8ns ± 2% 26.5ns ± 5% -11.01% (p=0.008 n=5+5) LogFieldType/Err-8 32.8ns ± 2% 29.8ns ± 4% -9.21% (p=0.008 n=5+5) LogFieldType/Durs-8 309ns ± 3% 304ns ± 3% ~ (p=0.238 n=5+5) LogFieldType/Floats-8 175ns ± 2% 174ns ± 3% ~ (p=0.968 n=5+5) LogFieldType/Strs-8 51.0ns ± 3% 48.4ns ± 6% -5.06% (p=0.032 n=5+5) LogFieldType/Dur-8 44.5ns ± 3% 41.3ns ± 3% -7.11% (p=0.008 n=5+5) LogFieldType/Interface(Objects)-8 758ns ± 3% 760ns ± 6% ~ (p=1.000 n=5+5) LogFieldType/Interfaces-8 772ns ± 5% 762ns ± 4% ~ (p=0.794 n=5+5) LogFieldType/Bool-8 28.0ns ± 6% 26.5ns ± 9% ~ (p=0.143 n=5+5) LogFieldType/Ints-8 49.6ns ± 2% 46.2ns ± 2% -6.70% (p=0.008 n=5+5) LogFieldType/Errs-8 46.5ns ±11% 40.9ns ± 4% -11.92% (p=0.008 n=5+5) LogFieldType/Time-8 115ns ± 3% 113ns ± 3% ~ (p=0.167 n=5+5) LogFieldType/Times-8 810ns ± 1% 811ns ± 3% ~ (p=0.889 n=5+5) ContextFieldType/Errs-8 158ns ± 6% 156ns ±12% ~ (p=1.000 n=5+5) ContextFieldType/Times-8 165ns ±11% 173ns ± 9% ~ (p=0.651 n=5+5) ContextFieldType/Interface-8 289ns ±13% 287ns ±11% ~ (p=0.690 n=5+5) ContextFieldType/Interface(Object)-8 285ns ±12% 297ns ± 6% ~ (p=0.238 n=5+5) ContextFieldType/Interface(Objects)-8 941ns ± 6% 941ns ± 5% ~ (p=1.000 n=5+5) ContextFieldType/Object-8 201ns ± 5% 210ns ±12% ~ (p=0.262 n=5+5) ContextFieldType/Ints-8 173ns ±10% 165ns ± 9% ~ (p=0.198 n=5+5) ContextFieldType/Floats-8 297ns ± 6% 292ns ± 7% ~ (p=0.579 n=5+5) ContextFieldType/Timestamp-8 174ns ± 9% 174ns ±11% ~ (p=0.810 n=5+5) ContextFieldType/Durs-8 445ns ± 9% 425ns ± 3% ~ (p=0.151 n=5+5) ContextFieldType/Interfaces-8 944ns ± 6% 876ns ±10% ~ (p=0.095 n=5+5) ContextFieldType/Strs-8 179ns ±11% 165ns ±13% ~ (p=0.135 n=5+5) ContextFieldType/Dur-8 158ns ± 8% 160ns ±19% ~ (p=1.000 n=5+5) ContextFieldType/Time-8 152ns ±15% 148ns ±14% ~ (p=0.952 n=5+5) ContextFieldType/Str-8 146ns ±12% 147ns ±16% ~ (p=0.841 n=5+5) ContextFieldType/Err-8 138ns ±12% 145ns ±17% ~ (p=0.595 n=5+5) ContextFieldType/Int-8 145ns ±10% 146ns ±13% ~ (p=0.873 n=5+5) ContextFieldType/Float-8 181ns ± 9% 162ns ±12% ~ (p=0.151 n=5+5) ContextFieldType/Bool-8 153ns ±10% 131ns ±19% ~ (p=0.063 n=5+5) ContextFieldType/Bools-8 149ns ±11% 160ns ±16% ~ (p=0.500 n=5+5) --- array.go | 96 ++++++++-------- context.go | 92 ++++++++-------- encoder.go | 58 ++++++++++ encoder_cbor.go | 204 +--------------------------------- encoder_json.go | 205 +---------------------------------- event.go | 110 +++++++++---------- fields.go | 114 +++++++++---------- internal/cbor/base.go | 28 ++--- internal/cbor/string.go | 10 +- internal/cbor/string_test.go | 10 +- internal/cbor/time.go | 26 ++--- internal/cbor/time_test.go | 8 +- internal/cbor/types.go | 198 +++++++++++++++++---------------- internal/cbor/types_test.go | 44 ++++---- internal/json/base.go | 16 +-- internal/json/bytes.go | 4 +- internal/json/bytes_test.go | 28 ++--- internal/json/string.go | 8 +- internal/json/string_test.go | 4 +- internal/json/time.go | 16 +-- internal/json/types.go | 124 +++++++++++++-------- internal/json/types_test.go | 36 +++--- log.go | 2 +- 23 files changed, 578 insertions(+), 863 deletions(-) create mode 100644 encoder.go diff --git a/array.go b/array.go index 66c2a4f..47402ec 100644 --- a/array.go +++ b/array.go @@ -33,163 +33,163 @@ func (*Array) MarshalZerologArray(*Array) { } func (a *Array) write(dst []byte) []byte { - dst = appendArrayStart(dst) + dst = enc.AppendArrayStart(dst) if len(a.buf) > 0 { dst = append(append(dst, a.buf...)) } - dst = appendArrayEnd(dst) + dst = enc.AppendArrayEnd(dst) arrayPool.Put(a) return dst } // Object marshals an object that implement the LogObjectMarshaler -// interface and append it to the array. +// interface and enc.Append it to the array. func (a *Array) Object(obj LogObjectMarshaler) *Array { e := Dict() obj.MarshalZerologObject(e) - e.buf = appendEndMarker(e.buf) - a.buf = append(appendArrayDelim(a.buf), e.buf...) + e.buf = enc.AppendEndMarker(e.buf) + a.buf = append(enc.AppendArrayDelim(a.buf), e.buf...) eventPool.Put(e) return a } -// Str append the val as a string to the array. +// Str enc.Append the val as a string to the array. func (a *Array) Str(val string) *Array { - a.buf = appendString(appendArrayDelim(a.buf), val) + a.buf = enc.AppendString(enc.AppendArrayDelim(a.buf), val) return a } -// Bytes append the val as a string to the array. +// Bytes enc.Append the val as a string to the array. func (a *Array) Bytes(val []byte) *Array { - a.buf = appendBytes(appendArrayDelim(a.buf), val) + a.buf = enc.AppendBytes(enc.AppendArrayDelim(a.buf), val) return a } -// Hex append the val as a hex string to the array. +// Hex enc.Append the val as a hex string to the array. func (a *Array) Hex(val []byte) *Array { - a.buf = appendHex(appendArrayDelim(a.buf), val) + a.buf = enc.AppendHex(enc.AppendArrayDelim(a.buf), val) return a } -// Err append the err as a string to the array. +// Err enc.Append the err as a string to the array. func (a *Array) Err(err error) *Array { - a.buf = appendError(appendArrayDelim(a.buf), err) + a.buf = enc.AppendError(enc.AppendArrayDelim(a.buf), err) return a } -// Bool append the val as a bool to the array. +// Bool enc.Append the val as a bool to the array. func (a *Array) Bool(b bool) *Array { - a.buf = appendBool(appendArrayDelim(a.buf), b) + a.buf = enc.AppendBool(enc.AppendArrayDelim(a.buf), b) return a } -// Int append i as a int to the array. +// Int enc.Append i as a int to the array. func (a *Array) Int(i int) *Array { - a.buf = appendInt(appendArrayDelim(a.buf), i) + a.buf = enc.AppendInt(enc.AppendArrayDelim(a.buf), i) return a } -// Int8 append i as a int8 to the array. +// Int8 enc.Append i as a int8 to the array. func (a *Array) Int8(i int8) *Array { - a.buf = appendInt8(appendArrayDelim(a.buf), i) + a.buf = enc.AppendInt8(enc.AppendArrayDelim(a.buf), i) return a } -// Int16 append i as a int16 to the array. +// Int16 enc.Append i as a int16 to the array. func (a *Array) Int16(i int16) *Array { - a.buf = appendInt16(appendArrayDelim(a.buf), i) + a.buf = enc.AppendInt16(enc.AppendArrayDelim(a.buf), i) return a } -// Int32 append i as a int32 to the array. +// Int32 enc.Append i as a int32 to the array. func (a *Array) Int32(i int32) *Array { - a.buf = appendInt32(appendArrayDelim(a.buf), i) + a.buf = enc.AppendInt32(enc.AppendArrayDelim(a.buf), i) return a } -// Int64 append i as a int64 to the array. +// Int64 enc.Append i as a int64 to the array. func (a *Array) Int64(i int64) *Array { - a.buf = appendInt64(appendArrayDelim(a.buf), i) + a.buf = enc.AppendInt64(enc.AppendArrayDelim(a.buf), i) return a } -// Uint append i as a uint to the array. +// Uint enc.Append i as a uint to the array. func (a *Array) Uint(i uint) *Array { - a.buf = appendUint(appendArrayDelim(a.buf), i) + a.buf = enc.AppendUint(enc.AppendArrayDelim(a.buf), i) return a } -// Uint8 append i as a uint8 to the array. +// Uint8 enc.Append i as a uint8 to the array. func (a *Array) Uint8(i uint8) *Array { - a.buf = appendUint8(appendArrayDelim(a.buf), i) + a.buf = enc.AppendUint8(enc.AppendArrayDelim(a.buf), i) return a } -// Uint16 append i as a uint16 to the array. +// Uint16 enc.Append i as a uint16 to the array. func (a *Array) Uint16(i uint16) *Array { - a.buf = appendUint16(appendArrayDelim(a.buf), i) + a.buf = enc.AppendUint16(enc.AppendArrayDelim(a.buf), i) return a } -// Uint32 append i as a uint32 to the array. +// Uint32 enc.Append i as a uint32 to the array. func (a *Array) Uint32(i uint32) *Array { - a.buf = appendUint32(appendArrayDelim(a.buf), i) + a.buf = enc.AppendUint32(enc.AppendArrayDelim(a.buf), i) return a } -// Uint64 append i as a uint64 to the array. +// Uint64 enc.Append i as a uint64 to the array. func (a *Array) Uint64(i uint64) *Array { - a.buf = appendUint64(appendArrayDelim(a.buf), i) + a.buf = enc.AppendUint64(enc.AppendArrayDelim(a.buf), i) return a } -// Float32 append f as a float32 to the array. +// Float32 enc.Append f as a float32 to the array. func (a *Array) Float32(f float32) *Array { - a.buf = appendFloat32(appendArrayDelim(a.buf), f) + a.buf = enc.AppendFloat32(enc.AppendArrayDelim(a.buf), f) return a } -// Float64 append f as a float64 to the array. +// Float64 enc.Append f as a float64 to the array. func (a *Array) Float64(f float64) *Array { - a.buf = appendFloat64(appendArrayDelim(a.buf), f) + a.buf = enc.AppendFloat64(enc.AppendArrayDelim(a.buf), f) return a } -// Time append t formated as string using zerolog.TimeFieldFormat. +// Time enc.Append t formated as string using zerolog.TimeFieldFormat. func (a *Array) Time(t time.Time) *Array { - a.buf = appendTime(appendArrayDelim(a.buf), t, TimeFieldFormat) + a.buf = enc.AppendTime(enc.AppendArrayDelim(a.buf), t, TimeFieldFormat) return a } -// Dur append d to the array. +// Dur enc.Append d to the array. func (a *Array) Dur(d time.Duration) *Array { - a.buf = appendDuration(appendArrayDelim(a.buf), d, DurationFieldUnit, DurationFieldInteger) + a.buf = enc.AppendDuration(enc.AppendArrayDelim(a.buf), d, DurationFieldUnit, DurationFieldInteger) return a } -// Interface append i marshaled using reflection. +// Interface enc.Append i marshaled using reflection. func (a *Array) Interface(i interface{}) *Array { if obj, ok := i.(LogObjectMarshaler); ok { return a.Object(obj) } - a.buf = appendInterface(appendArrayDelim(a.buf), i) + a.buf = enc.AppendInterface(enc.AppendArrayDelim(a.buf), i) return a } // IPAddr adds IPv4 or IPv6 address to the array func (a *Array) IPAddr(ip net.IP) *Array { - a.buf = appendIPAddr(appendArrayDelim(a.buf), ip) + a.buf = enc.AppendIPAddr(enc.AppendArrayDelim(a.buf), ip) return a } // IPPrefix adds IPv4 or IPv6 Prefix (IP + mask) to the array func (a *Array) IPPrefix(pfx net.IPNet) *Array { - a.buf = appendIPPrefix(appendArrayDelim(a.buf), pfx) + a.buf = enc.AppendIPPrefix(enc.AppendArrayDelim(a.buf), pfx) return a } // MACAddr adds a MAC (Ethernet) address to the array func (a *Array) MACAddr(ha net.HardwareAddr) *Array { - a.buf = appendMACAddr(appendArrayDelim(a.buf), ha) + a.buf = enc.AppendMACAddr(enc.AppendArrayDelim(a.buf), ha) return a } diff --git a/context.go b/context.go index 146a5df..d9f9c22 100644 --- a/context.go +++ b/context.go @@ -24,8 +24,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 = appendEndMarker(dict.buf) - c.l.context = append(appendKey(c.l.context, key), dict.buf...) + dict.buf = enc.AppendEndMarker(dict.buf) + c.l.context = append(enc.AppendKey(c.l.context, key), dict.buf...) eventPool.Put(dict) return c } @@ -34,7 +34,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 = appendKey(c.l.context, key) + c.l.context = enc.AppendKey(c.l.context, key) if arr, ok := arr.(*Array); ok { c.l.context = arr.write(c.l.context) return c @@ -54,32 +54,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) e.Object(key, obj) - c.l.context = appendObjectData(c.l.context, e.buf) + c.l.context = enc.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 = appendString(appendKey(c.l.context, key), val) + c.l.context = enc.AppendString(enc.AppendKey(c.l.context, key), val) return c } // Strs adds the field key with val as a string to the logger context. func (c Context) Strs(key string, vals []string) Context { - c.l.context = appendStrings(appendKey(c.l.context, key), vals) + c.l.context = enc.AppendStrings(enc.AppendKey(c.l.context, key), vals) return c } // Bytes adds the field key with val as a []byte to the logger context. func (c Context) Bytes(key string, val []byte) Context { - c.l.context = appendBytes(appendKey(c.l.context, key), val) + c.l.context = enc.AppendBytes(enc.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 = appendHex(appendKey(c.l.context, key), val) + c.l.context = enc.AppendHex(enc.AppendKey(c.l.context, key), val) return c } @@ -88,21 +88,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 = appendJSON(appendKey(c.l.context, key), b) + c.l.context = appendJSON(enc.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 = appendError(appendKey(c.l.context, key), err) + c.l.context = enc.AppendError(enc.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 = appendErrors(appendKey(c.l.context, key), errs) + c.l.context = enc.AppendErrors(enc.AppendKey(c.l.context, key), errs) return c } @@ -110,164 +110,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 = appendError(appendKey(c.l.context, ErrorFieldName), err) + c.l.context = enc.AppendError(enc.AppendKey(c.l.context, ErrorFieldName), err) } return c } // Bool adds the field key with val as a bool to the logger context. func (c Context) Bool(key string, b bool) Context { - c.l.context = appendBool(appendKey(c.l.context, key), b) + c.l.context = enc.AppendBool(enc.AppendKey(c.l.context, key), b) return c } // Bools adds the field key with val as a []bool to the logger context. func (c Context) Bools(key string, b []bool) Context { - c.l.context = appendBools(appendKey(c.l.context, key), b) + c.l.context = enc.AppendBools(enc.AppendKey(c.l.context, key), b) return c } // Int adds the field key with i as a int to the logger context. func (c Context) Int(key string, i int) Context { - c.l.context = appendInt(appendKey(c.l.context, key), i) + c.l.context = enc.AppendInt(enc.AppendKey(c.l.context, key), i) return c } // Ints adds the field key with i as a []int to the logger context. func (c Context) Ints(key string, i []int) Context { - c.l.context = appendInts(appendKey(c.l.context, key), i) + c.l.context = enc.AppendInts(enc.AppendKey(c.l.context, key), i) return c } // Int8 adds the field key with i as a int8 to the logger context. func (c Context) Int8(key string, i int8) Context { - c.l.context = appendInt8(appendKey(c.l.context, key), i) + c.l.context = enc.AppendInt8(enc.AppendKey(c.l.context, key), i) return c } // Ints8 adds the field key with i as a []int8 to the logger context. func (c Context) Ints8(key string, i []int8) Context { - c.l.context = appendInts8(appendKey(c.l.context, key), i) + c.l.context = enc.AppendInts8(enc.AppendKey(c.l.context, key), i) return c } // Int16 adds the field key with i as a int16 to the logger context. func (c Context) Int16(key string, i int16) Context { - c.l.context = appendInt16(appendKey(c.l.context, key), i) + c.l.context = enc.AppendInt16(enc.AppendKey(c.l.context, key), i) return c } // Ints16 adds the field key with i as a []int16 to the logger context. func (c Context) Ints16(key string, i []int16) Context { - c.l.context = appendInts16(appendKey(c.l.context, key), i) + c.l.context = enc.AppendInts16(enc.AppendKey(c.l.context, key), i) return c } // Int32 adds the field key with i as a int32 to the logger context. func (c Context) Int32(key string, i int32) Context { - c.l.context = appendInt32(appendKey(c.l.context, key), i) + c.l.context = enc.AppendInt32(enc.AppendKey(c.l.context, key), i) return c } // Ints32 adds the field key with i as a []int32 to the logger context. func (c Context) Ints32(key string, i []int32) Context { - c.l.context = appendInts32(appendKey(c.l.context, key), i) + c.l.context = enc.AppendInts32(enc.AppendKey(c.l.context, key), i) return c } // Int64 adds the field key with i as a int64 to the logger context. func (c Context) Int64(key string, i int64) Context { - c.l.context = appendInt64(appendKey(c.l.context, key), i) + c.l.context = enc.AppendInt64(enc.AppendKey(c.l.context, key), i) return c } // Ints64 adds the field key with i as a []int64 to the logger context. func (c Context) Ints64(key string, i []int64) Context { - c.l.context = appendInts64(appendKey(c.l.context, key), i) + c.l.context = enc.AppendInts64(enc.AppendKey(c.l.context, key), i) return c } // Uint adds the field key with i as a uint to the logger context. func (c Context) Uint(key string, i uint) Context { - c.l.context = appendUint(appendKey(c.l.context, key), i) + c.l.context = enc.AppendUint(enc.AppendKey(c.l.context, key), i) return c } // Uints adds the field key with i as a []uint to the logger context. func (c Context) Uints(key string, i []uint) Context { - c.l.context = appendUints(appendKey(c.l.context, key), i) + c.l.context = enc.AppendUints(enc.AppendKey(c.l.context, key), i) return c } // Uint8 adds the field key with i as a uint8 to the logger context. func (c Context) Uint8(key string, i uint8) Context { - c.l.context = appendUint8(appendKey(c.l.context, key), i) + c.l.context = enc.AppendUint8(enc.AppendKey(c.l.context, key), i) return c } // Uints8 adds the field key with i as a []uint8 to the logger context. func (c Context) Uints8(key string, i []uint8) Context { - c.l.context = appendUints8(appendKey(c.l.context, key), i) + c.l.context = enc.AppendUints8(enc.AppendKey(c.l.context, key), i) return c } // Uint16 adds the field key with i as a uint16 to the logger context. func (c Context) Uint16(key string, i uint16) Context { - c.l.context = appendUint16(appendKey(c.l.context, key), i) + c.l.context = enc.AppendUint16(enc.AppendKey(c.l.context, key), i) return c } // Uints16 adds the field key with i as a []uint16 to the logger context. func (c Context) Uints16(key string, i []uint16) Context { - c.l.context = appendUints16(appendKey(c.l.context, key), i) + c.l.context = enc.AppendUints16(enc.AppendKey(c.l.context, key), i) return c } // Uint32 adds the field key with i as a uint32 to the logger context. func (c Context) Uint32(key string, i uint32) Context { - c.l.context = appendUint32(appendKey(c.l.context, key), i) + c.l.context = enc.AppendUint32(enc.AppendKey(c.l.context, key), i) return c } // Uints32 adds the field key with i as a []uint32 to the logger context. func (c Context) Uints32(key string, i []uint32) Context { - c.l.context = appendUints32(appendKey(c.l.context, key), i) + c.l.context = enc.AppendUints32(enc.AppendKey(c.l.context, key), i) return c } // Uint64 adds the field key with i as a uint64 to the logger context. func (c Context) Uint64(key string, i uint64) Context { - c.l.context = appendUint64(appendKey(c.l.context, key), i) + c.l.context = enc.AppendUint64(enc.AppendKey(c.l.context, key), i) return c } // Uints64 adds the field key with i as a []uint64 to the logger context. func (c Context) Uints64(key string, i []uint64) Context { - c.l.context = appendUints64(appendKey(c.l.context, key), i) + c.l.context = enc.AppendUints64(enc.AppendKey(c.l.context, key), i) return c } // Float32 adds the field key with f as a float32 to the logger context. func (c Context) Float32(key string, f float32) Context { - c.l.context = appendFloat32(appendKey(c.l.context, key), f) + c.l.context = enc.AppendFloat32(enc.AppendKey(c.l.context, key), f) return c } // Floats32 adds the field key with f as a []float32 to the logger context. func (c Context) Floats32(key string, f []float32) Context { - c.l.context = appendFloats32(appendKey(c.l.context, key), f) + c.l.context = enc.AppendFloats32(enc.AppendKey(c.l.context, key), f) return c } // Float64 adds the field key with f as a float64 to the logger context. func (c Context) Float64(key string, f float64) Context { - c.l.context = appendFloat64(appendKey(c.l.context, key), f) + c.l.context = enc.AppendFloat64(enc.AppendKey(c.l.context, key), f) return c } // Floats64 adds the field key with f as a []float64 to the logger context. func (c Context) Floats64(key string, f []float64) Context { - c.l.context = appendFloats64(appendKey(c.l.context, key), f) + c.l.context = enc.AppendFloats64(enc.AppendKey(c.l.context, key), f) return c } @@ -290,31 +290,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 = appendTime(appendKey(c.l.context, key), t, TimeFieldFormat) + c.l.context = enc.AppendTime(enc.AppendKey(c.l.context, key), t, TimeFieldFormat) return c } // Times adds the field key with t formated as string using zerolog.TimeFieldFormat. func (c Context) Times(key string, t []time.Time) Context { - c.l.context = appendTimes(appendKey(c.l.context, key), t, TimeFieldFormat) + c.l.context = enc.AppendTimes(enc.AppendKey(c.l.context, key), t, TimeFieldFormat) return c } // Dur adds the fields key with d divided by unit and stored as a float. func (c Context) Dur(key string, d time.Duration) Context { - c.l.context = appendDuration(appendKey(c.l.context, key), d, DurationFieldUnit, DurationFieldInteger) + c.l.context = enc.AppendDuration(enc.AppendKey(c.l.context, key), d, DurationFieldUnit, DurationFieldInteger) return c } // Durs adds the fields key with d divided by unit and stored as a float. func (c Context) Durs(key string, d []time.Duration) Context { - c.l.context = appendDurations(appendKey(c.l.context, key), d, DurationFieldUnit, DurationFieldInteger) + c.l.context = enc.AppendDurations(enc.AppendKey(c.l.context, key), d, DurationFieldUnit, DurationFieldInteger) return c } // Interface adds the field key with obj marshaled using reflection. func (c Context) Interface(key string, i interface{}) Context { - c.l.context = appendInterface(appendKey(c.l.context, key), i) + c.l.context = enc.AppendInterface(enc.AppendKey(c.l.context, key), i) return c } @@ -334,18 +334,18 @@ func (c Context) Caller() Context { // IPAddr adds IPv4 or IPv6 Address to the context func (c Context) IPAddr(key string, ip net.IP) Context { - c.l.context = appendIPAddr(appendKey(c.l.context, key), ip) + c.l.context = enc.AppendIPAddr(enc.AppendKey(c.l.context, key), ip) return c } // IPPrefix adds IPv4 or IPv6 Prefix (address and mask) to the context func (c Context) IPPrefix(key string, pfx net.IPNet) Context { - c.l.context = appendIPPrefix(appendKey(c.l.context, key), pfx) + c.l.context = enc.AppendIPPrefix(enc.AppendKey(c.l.context, key), pfx) return c } // MACAddr adds MAC address to the context func (c Context) MACAddr(key string, ha net.HardwareAddr) Context { - c.l.context = appendMACAddr(appendKey(c.l.context, key), ha) + c.l.context = enc.AppendMACAddr(enc.AppendKey(c.l.context, key), ha) return c } diff --git a/encoder.go b/encoder.go new file mode 100644 index 0000000..4bbe27a --- /dev/null +++ b/encoder.go @@ -0,0 +1,58 @@ +package zerolog + +import ( + "net" + "time" +) + +type encoder interface { + AppendArrayDelim(dst []byte) []byte + AppendArrayEnd(dst []byte) []byte + AppendArrayStart(dst []byte) []byte + AppendBeginMarker(dst []byte) []byte + AppendBool(dst []byte, val bool) []byte + AppendBools(dst []byte, vals []bool) []byte + AppendBytes(dst, s []byte) []byte + AppendDuration(dst []byte, d time.Duration, unit time.Duration, useInt bool) []byte + AppendDurations(dst []byte, vals []time.Duration, unit time.Duration, useInt bool) []byte + AppendEndMarker(dst []byte) []byte + AppendError(dst []byte, err error) []byte + AppendErrors(dst []byte, errs []error) []byte + AppendFloat32(dst []byte, val float32) []byte + AppendFloat64(dst []byte, val float64) []byte + AppendFloats32(dst []byte, vals []float32) []byte + AppendFloats64(dst []byte, vals []float64) []byte + AppendHex(dst, s []byte) []byte + AppendIPAddr(dst []byte, ip net.IP) []byte + AppendIPPrefix(dst []byte, pfx net.IPNet) []byte + AppendInt(dst []byte, val int) []byte + AppendInt16(dst []byte, val int16) []byte + AppendInt32(dst []byte, val int32) []byte + AppendInt64(dst []byte, val int64) []byte + AppendInt8(dst []byte, val int8) []byte + AppendInterface(dst []byte, i interface{}) []byte + AppendInts(dst []byte, vals []int) []byte + AppendInts16(dst []byte, vals []int16) []byte + AppendInts32(dst []byte, vals []int32) []byte + AppendInts64(dst []byte, vals []int64) []byte + AppendInts8(dst []byte, vals []int8) []byte + AppendKey(dst []byte, key string) []byte + AppendLineBreak(dst []byte) []byte + AppendMACAddr(dst []byte, ha net.HardwareAddr) []byte + AppendNil(dst []byte) []byte + AppendObjectData(dst []byte, o []byte) []byte + AppendString(dst []byte, s string) []byte + AppendStrings(dst []byte, vals []string) []byte + AppendTime(dst []byte, t time.Time, format string) []byte + AppendTimes(dst []byte, vals []time.Time, format string) []byte + AppendUint(dst []byte, val uint) []byte + AppendUint16(dst []byte, val uint16) []byte + AppendUint32(dst []byte, val uint32) []byte + AppendUint64(dst []byte, val uint64) []byte + AppendUint8(dst []byte, val uint8) []byte + AppendUints(dst []byte, vals []uint) []byte + AppendUints16(dst []byte, vals []uint16) []byte + AppendUints32(dst []byte, vals []uint32) []byte + AppendUints64(dst []byte, vals []uint64) []byte + AppendUints8(dst []byte, vals []uint8) []byte +} diff --git a/encoder_cbor.go b/encoder_cbor.go index d345c8f..f8d3fe9 100644 --- a/encoder_cbor.go +++ b/encoder_cbor.go @@ -5,203 +5,19 @@ package zerolog // This file contains bindings to do binary encoding. import ( - "net" - "time" - "github.com/rs/zerolog/internal/cbor" ) -func appendInterface(dst []byte, i interface{}) []byte { - return cbor.AppendInterface(dst, i) -} +var ( + _ encoder = (*cbor.Encoder)(nil) -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:]) -} + enc = cbor.Encoder{} +) 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 { @@ -212,18 +28,6 @@ func decodeObjectToStr(in []byte) string { return cbor.DecodeObjectToStr(in) } -func appendIPAddr(dst []byte, ip net.IP) []byte { - return cbor.AppendIPAddr(dst, ip) -} - -func appendIPPrefix(dst []byte, pfx net.IPNet) []byte { - return cbor.AppendIPPrefix(dst, pfx) -} - -func appendMACAddr(dst []byte, ha net.HardwareAddr) []byte { - return cbor.AppendMACAddr(dst, ha) -} - // decodeIfBinaryToBytes - converts a binary formatted log msg to a // JSON formatted Bytes Log message. func decodeIfBinaryToBytes(in []byte) []byte { diff --git a/encoder_json.go b/encoder_json.go index df15c7a..fe580f5 100644 --- a/encoder_json.go +++ b/encoder_json.go @@ -6,200 +6,19 @@ package zerolog // JSON encoded byte stream. import ( - "net" - "strconv" - "time" - "github.com/rs/zerolog/internal/json" ) -func appendInterface(dst []byte, i interface{}) []byte { - return json.AppendInterface(dst, i) -} +var ( + _ encoder = (*json.Encoder)(nil) -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) -} + enc = json.Encoder{} +) 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) } @@ -211,19 +30,3 @@ func decodeObjectToStr(in []byte) string { func decodeIfBinaryToBytes(in []byte) []byte { return in } - -func appendIPAddr(dst []byte, ip net.IP) []byte { - return json.AppendIPAddr(dst, ip) -} - -func appendIPPrefix(dst []byte, pfx net.IPNet) []byte { - return json.AppendIPPrefix(dst, pfx) -} - -func appendMACAddr(dst []byte, ha net.HardwareAddr) []byte { - return json.AppendMACAddr(dst, ha) -} - -func appendHex(in []byte, val []byte) []byte { - return json.AppendHex(in, val) -} diff --git a/event.go b/event.go index 7481a40..85e90ee 100644 --- a/event.go +++ b/event.go @@ -45,7 +45,7 @@ func newEvent(w LevelWriter, level Level) *Event { e := eventPool.Get().(*Event) e.buf = e.buf[:0] e.h = e.h[:0] - e.buf = appendBeginMarker(e.buf) + e.buf = enc.AppendBeginMarker(e.buf) e.w = w e.level = level return e @@ -55,8 +55,8 @@ func (e *Event) write() (err error) { if e == nil { return nil } - e.buf = appendEndMarker(e.buf) - e.buf = appendLineBreak(e.buf) + e.buf = enc.AppendEndMarker(e.buf) + e.buf = enc.AppendLineBreak(e.buf) if e.w != nil { _, err = e.w.WriteLevel(e.level, e.buf) } @@ -95,7 +95,7 @@ func (e *Event) Msg(msg string) { } } if msg != "" { - e.buf = appendString(appendKey(e.buf, MessageFieldName), msg) + e.buf = enc.AppendString(enc.AppendKey(e.buf, MessageFieldName), msg) } if e.done != nil { defer e.done(msg) @@ -131,8 +131,8 @@ func (e *Event) Dict(key string, dict *Event) *Event { if e == nil { return e } - dict.buf = appendEndMarker(dict.buf) - e.buf = append(appendKey(e.buf, key), dict.buf...) + dict.buf = enc.AppendEndMarker(dict.buf) + e.buf = append(enc.AppendKey(e.buf, key), dict.buf...) eventPool.Put(dict) return e } @@ -151,7 +151,7 @@ func (e *Event) Array(key string, arr LogArrayMarshaler) *Event { if e == nil { return e } - e.buf = appendKey(e.buf, key) + e.buf = enc.AppendKey(e.buf, key) var a *Array if aa, ok := arr.(*Array); ok { a = aa @@ -164,9 +164,9 @@ func (e *Event) Array(key string, arr LogArrayMarshaler) *Event { } func (e *Event) appendObject(obj LogObjectMarshaler) { - e.buf = appendBeginMarker(e.buf) + e.buf = enc.AppendBeginMarker(e.buf) obj.MarshalZerologObject(e) - e.buf = appendEndMarker(e.buf) + e.buf = enc.AppendEndMarker(e.buf) } // Object marshals an object that implement the LogObjectMarshaler interface. @@ -174,7 +174,7 @@ func (e *Event) Object(key string, obj LogObjectMarshaler) *Event { if e == nil { return e } - e.buf = appendKey(e.buf, key) + e.buf = enc.AppendKey(e.buf, key) e.appendObject(obj) return e } @@ -184,7 +184,7 @@ func (e *Event) Str(key, val string) *Event { if e == nil { return e } - e.buf = appendString(appendKey(e.buf, key), val) + e.buf = enc.AppendString(enc.AppendKey(e.buf, key), val) return e } @@ -193,7 +193,7 @@ func (e *Event) Strs(key string, vals []string) *Event { if e == nil { return e } - e.buf = appendStrings(appendKey(e.buf, key), vals) + e.buf = enc.AppendStrings(enc.AppendKey(e.buf, key), vals) return e } @@ -205,7 +205,7 @@ func (e *Event) Bytes(key string, val []byte) *Event { if e == nil { return e } - e.buf = appendBytes(appendKey(e.buf, key), val) + e.buf = enc.AppendBytes(enc.AppendKey(e.buf, key), val) return e } @@ -214,7 +214,7 @@ func (e *Event) Hex(key string, val []byte) *Event { if e == nil { return e } - e.buf = appendHex(appendKey(e.buf, key), val) + e.buf = enc.AppendHex(enc.AppendKey(e.buf, key), val) return e } @@ -226,7 +226,7 @@ func (e *Event) RawJSON(key string, b []byte) *Event { if e == nil { return e } - e.buf = appendJSON(appendKey(e.buf, key), b) + e.buf = appendJSON(enc.AppendKey(e.buf, key), b) return e } @@ -237,7 +237,7 @@ func (e *Event) AnErr(key string, err error) *Event { return e } if err != nil { - e.buf = appendError(appendKey(e.buf, key), err) + e.buf = enc.AppendError(enc.AppendKey(e.buf, key), err) } return e } @@ -248,7 +248,7 @@ func (e *Event) Errs(key string, errs []error) *Event { if e == nil { return e } - e.buf = appendErrors(appendKey(e.buf, key), errs) + e.buf = enc.AppendErrors(enc.AppendKey(e.buf, key), errs) return e } @@ -260,7 +260,7 @@ func (e *Event) Err(err error) *Event { return e } if err != nil { - e.buf = appendError(appendKey(e.buf, ErrorFieldName), err) + e.buf = enc.AppendError(enc.AppendKey(e.buf, ErrorFieldName), err) } return e } @@ -270,7 +270,7 @@ func (e *Event) Bool(key string, b bool) *Event { if e == nil { return e } - e.buf = appendBool(appendKey(e.buf, key), b) + e.buf = enc.AppendBool(enc.AppendKey(e.buf, key), b) return e } @@ -279,7 +279,7 @@ func (e *Event) Bools(key string, b []bool) *Event { if e == nil { return e } - e.buf = appendBools(appendKey(e.buf, key), b) + e.buf = enc.AppendBools(enc.AppendKey(e.buf, key), b) return e } @@ -288,7 +288,7 @@ func (e *Event) Int(key string, i int) *Event { if e == nil { return e } - e.buf = appendInt(appendKey(e.buf, key), i) + e.buf = enc.AppendInt(enc.AppendKey(e.buf, key), i) return e } @@ -297,7 +297,7 @@ func (e *Event) Ints(key string, i []int) *Event { if e == nil { return e } - e.buf = appendInts(appendKey(e.buf, key), i) + e.buf = enc.AppendInts(enc.AppendKey(e.buf, key), i) return e } @@ -306,7 +306,7 @@ func (e *Event) Int8(key string, i int8) *Event { if e == nil { return e } - e.buf = appendInt8(appendKey(e.buf, key), i) + e.buf = enc.AppendInt8(enc.AppendKey(e.buf, key), i) return e } @@ -315,7 +315,7 @@ func (e *Event) Ints8(key string, i []int8) *Event { if e == nil { return e } - e.buf = appendInts8(appendKey(e.buf, key), i) + e.buf = enc.AppendInts8(enc.AppendKey(e.buf, key), i) return e } @@ -324,7 +324,7 @@ func (e *Event) Int16(key string, i int16) *Event { if e == nil { return e } - e.buf = appendInt16(appendKey(e.buf, key), i) + e.buf = enc.AppendInt16(enc.AppendKey(e.buf, key), i) return e } @@ -333,7 +333,7 @@ func (e *Event) Ints16(key string, i []int16) *Event { if e == nil { return e } - e.buf = appendInts16(appendKey(e.buf, key), i) + e.buf = enc.AppendInts16(enc.AppendKey(e.buf, key), i) return e } @@ -342,7 +342,7 @@ func (e *Event) Int32(key string, i int32) *Event { if e == nil { return e } - e.buf = appendInt32(appendKey(e.buf, key), i) + e.buf = enc.AppendInt32(enc.AppendKey(e.buf, key), i) return e } @@ -351,7 +351,7 @@ func (e *Event) Ints32(key string, i []int32) *Event { if e == nil { return e } - e.buf = appendInts32(appendKey(e.buf, key), i) + e.buf = enc.AppendInts32(enc.AppendKey(e.buf, key), i) return e } @@ -360,7 +360,7 @@ func (e *Event) Int64(key string, i int64) *Event { if e == nil { return e } - e.buf = appendInt64(appendKey(e.buf, key), i) + e.buf = enc.AppendInt64(enc.AppendKey(e.buf, key), i) return e } @@ -369,7 +369,7 @@ func (e *Event) Ints64(key string, i []int64) *Event { if e == nil { return e } - e.buf = appendInts64(appendKey(e.buf, key), i) + e.buf = enc.AppendInts64(enc.AppendKey(e.buf, key), i) return e } @@ -378,7 +378,7 @@ func (e *Event) Uint(key string, i uint) *Event { if e == nil { return e } - e.buf = appendUint(appendKey(e.buf, key), i) + e.buf = enc.AppendUint(enc.AppendKey(e.buf, key), i) return e } @@ -387,7 +387,7 @@ func (e *Event) Uints(key string, i []uint) *Event { if e == nil { return e } - e.buf = appendUints(appendKey(e.buf, key), i) + e.buf = enc.AppendUints(enc.AppendKey(e.buf, key), i) return e } @@ -396,7 +396,7 @@ func (e *Event) Uint8(key string, i uint8) *Event { if e == nil { return e } - e.buf = appendUint8(appendKey(e.buf, key), i) + e.buf = enc.AppendUint8(enc.AppendKey(e.buf, key), i) return e } @@ -405,7 +405,7 @@ func (e *Event) Uints8(key string, i []uint8) *Event { if e == nil { return e } - e.buf = appendUints8(appendKey(e.buf, key), i) + e.buf = enc.AppendUints8(enc.AppendKey(e.buf, key), i) return e } @@ -414,7 +414,7 @@ func (e *Event) Uint16(key string, i uint16) *Event { if e == nil { return e } - e.buf = appendUint16(appendKey(e.buf, key), i) + e.buf = enc.AppendUint16(enc.AppendKey(e.buf, key), i) return e } @@ -423,7 +423,7 @@ func (e *Event) Uints16(key string, i []uint16) *Event { if e == nil { return e } - e.buf = appendUints16(appendKey(e.buf, key), i) + e.buf = enc.AppendUints16(enc.AppendKey(e.buf, key), i) return e } @@ -432,7 +432,7 @@ func (e *Event) Uint32(key string, i uint32) *Event { if e == nil { return e } - e.buf = appendUint32(appendKey(e.buf, key), i) + e.buf = enc.AppendUint32(enc.AppendKey(e.buf, key), i) return e } @@ -441,7 +441,7 @@ func (e *Event) Uints32(key string, i []uint32) *Event { if e == nil { return e } - e.buf = appendUints32(appendKey(e.buf, key), i) + e.buf = enc.AppendUints32(enc.AppendKey(e.buf, key), i) return e } @@ -450,7 +450,7 @@ func (e *Event) Uint64(key string, i uint64) *Event { if e == nil { return e } - e.buf = appendUint64(appendKey(e.buf, key), i) + e.buf = enc.AppendUint64(enc.AppendKey(e.buf, key), i) return e } @@ -459,7 +459,7 @@ func (e *Event) Uints64(key string, i []uint64) *Event { if e == nil { return e } - e.buf = appendUints64(appendKey(e.buf, key), i) + e.buf = enc.AppendUints64(enc.AppendKey(e.buf, key), i) return e } @@ -468,7 +468,7 @@ func (e *Event) Float32(key string, f float32) *Event { if e == nil { return e } - e.buf = appendFloat32(appendKey(e.buf, key), f) + e.buf = enc.AppendFloat32(enc.AppendKey(e.buf, key), f) return e } @@ -477,7 +477,7 @@ func (e *Event) Floats32(key string, f []float32) *Event { if e == nil { return e } - e.buf = appendFloats32(appendKey(e.buf, key), f) + e.buf = enc.AppendFloats32(enc.AppendKey(e.buf, key), f) return e } @@ -486,7 +486,7 @@ func (e *Event) Float64(key string, f float64) *Event { if e == nil { return e } - e.buf = appendFloat64(appendKey(e.buf, key), f) + e.buf = enc.AppendFloat64(enc.AppendKey(e.buf, key), f) return e } @@ -495,7 +495,7 @@ func (e *Event) Floats64(key string, f []float64) *Event { if e == nil { return e } - e.buf = appendFloats64(appendKey(e.buf, key), f) + e.buf = enc.AppendFloats64(enc.AppendKey(e.buf, key), f) return e } @@ -508,7 +508,7 @@ func (e *Event) Timestamp() *Event { if e == nil { return e } - e.buf = appendTime(appendKey(e.buf, TimestampFieldName), TimestampFunc(), TimeFieldFormat) + e.buf = enc.AppendTime(enc.AppendKey(e.buf, TimestampFieldName), TimestampFunc(), TimeFieldFormat) return e } @@ -517,7 +517,7 @@ func (e *Event) Time(key string, t time.Time) *Event { if e == nil { return e } - e.buf = appendTime(appendKey(e.buf, key), t, TimeFieldFormat) + e.buf = enc.AppendTime(enc.AppendKey(e.buf, key), t, TimeFieldFormat) return e } @@ -526,7 +526,7 @@ func (e *Event) Times(key string, t []time.Time) *Event { if e == nil { return e } - e.buf = appendTimes(appendKey(e.buf, key), t, TimeFieldFormat) + e.buf = enc.AppendTimes(enc.AppendKey(e.buf, key), t, TimeFieldFormat) return e } @@ -537,7 +537,7 @@ func (e *Event) Dur(key string, d time.Duration) *Event { if e == nil { return e } - e.buf = appendDuration(appendKey(e.buf, key), d, DurationFieldUnit, DurationFieldInteger) + e.buf = enc.AppendDuration(enc.AppendKey(e.buf, key), d, DurationFieldUnit, DurationFieldInteger) return e } @@ -548,7 +548,7 @@ func (e *Event) Durs(key string, d []time.Duration) *Event { if e == nil { return e } - e.buf = appendDurations(appendKey(e.buf, key), d, DurationFieldUnit, DurationFieldInteger) + e.buf = enc.AppendDurations(enc.AppendKey(e.buf, key), d, DurationFieldUnit, DurationFieldInteger) return e } @@ -563,7 +563,7 @@ func (e *Event) TimeDiff(key string, t time.Time, start time.Time) *Event { if t.After(start) { d = t.Sub(start) } - e.buf = appendDuration(appendKey(e.buf, key), d, DurationFieldUnit, DurationFieldInteger) + e.buf = enc.AppendDuration(enc.AppendKey(e.buf, key), d, DurationFieldUnit, DurationFieldInteger) return e } @@ -575,7 +575,7 @@ func (e *Event) Interface(key string, i interface{}) *Event { if obj, ok := i.(LogObjectMarshaler); ok { return e.Object(key, obj) } - e.buf = appendInterface(appendKey(e.buf, key), i) + e.buf = enc.AppendInterface(enc.AppendKey(e.buf, key), i) return e } @@ -592,7 +592,7 @@ func (e *Event) caller(skip int) *Event { if !ok { return e } - e.buf = appendString(appendKey(e.buf, CallerFieldName), file+":"+strconv.Itoa(line)) + e.buf = enc.AppendString(enc.AppendKey(e.buf, CallerFieldName), file+":"+strconv.Itoa(line)) return e } @@ -601,7 +601,7 @@ func (e *Event) IPAddr(key string, ip net.IP) *Event { if e == nil { return e } - e.buf = appendIPAddr(appendKey(e.buf, key), ip) + e.buf = enc.AppendIPAddr(enc.AppendKey(e.buf, key), ip) return e } @@ -610,7 +610,7 @@ func (e *Event) IPPrefix(key string, pfx net.IPNet) *Event { if e == nil { return e } - e.buf = appendIPPrefix(appendKey(e.buf, key), pfx) + e.buf = enc.AppendIPPrefix(enc.AppendKey(e.buf, key), pfx) return e } @@ -619,6 +619,6 @@ func (e *Event) MACAddr(key string, ha net.HardwareAddr) *Event { if e == nil { return e } - e.buf = appendMACAddr(appendKey(e.buf, key), ha) + e.buf = enc.AppendMACAddr(enc.AppendKey(e.buf, key), ha) return e } diff --git a/fields.go b/fields.go index 8d8a62b..25f0d47 100644 --- a/fields.go +++ b/fields.go @@ -13,7 +13,7 @@ func appendFields(dst []byte, fields map[string]interface{}) []byte { } sort.Strings(keys) for _, key := range keys { - dst = appendKey(dst, key) + dst = enc.AppendKey(dst, key) val := fields[key] if val, ok := val.(LogObjectMarshaler); ok { e := newEvent(nil, 0) @@ -25,117 +25,117 @@ func appendFields(dst []byte, fields map[string]interface{}) []byte { } switch val := val.(type) { case string: - dst = appendString(dst, val) + dst = enc.AppendString(dst, val) case []byte: - dst = appendBytes(dst, val) + dst = enc.AppendBytes(dst, val) case error: - dst = appendError(dst, val) + dst = enc.AppendError(dst, val) case []error: - dst = appendErrors(dst, val) + dst = enc.AppendErrors(dst, val) case bool: - dst = appendBool(dst, val) + dst = enc.AppendBool(dst, val) case int: - dst = appendInt(dst, val) + dst = enc.AppendInt(dst, val) case int8: - dst = appendInt8(dst, val) + dst = enc.AppendInt8(dst, val) case int16: - dst = appendInt16(dst, val) + dst = enc.AppendInt16(dst, val) case int32: - dst = appendInt32(dst, val) + dst = enc.AppendInt32(dst, val) case int64: - dst = appendInt64(dst, val) + dst = enc.AppendInt64(dst, val) case uint: - dst = appendUint(dst, val) + dst = enc.AppendUint(dst, val) case uint8: - dst = appendUint8(dst, val) + dst = enc.AppendUint8(dst, val) case uint16: - dst = appendUint16(dst, val) + dst = enc.AppendUint16(dst, val) case uint32: - dst = appendUint32(dst, val) + dst = enc.AppendUint32(dst, val) case uint64: - dst = appendUint64(dst, val) + dst = enc.AppendUint64(dst, val) case float32: - dst = appendFloat32(dst, val) + dst = enc.AppendFloat32(dst, val) case float64: - dst = appendFloat64(dst, val) + dst = enc.AppendFloat64(dst, val) case time.Time: - dst = appendTime(dst, val, TimeFieldFormat) + dst = enc.AppendTime(dst, val, TimeFieldFormat) case time.Duration: - dst = appendDuration(dst, val, DurationFieldUnit, DurationFieldInteger) + dst = enc.AppendDuration(dst, val, DurationFieldUnit, DurationFieldInteger) case *string: - dst = appendString(dst, *val) + dst = enc.AppendString(dst, *val) case *bool: - dst = appendBool(dst, *val) + dst = enc.AppendBool(dst, *val) case *int: - dst = appendInt(dst, *val) + dst = enc.AppendInt(dst, *val) case *int8: - dst = appendInt8(dst, *val) + dst = enc.AppendInt8(dst, *val) case *int16: - dst = appendInt16(dst, *val) + dst = enc.AppendInt16(dst, *val) case *int32: - dst = appendInt32(dst, *val) + dst = enc.AppendInt32(dst, *val) case *int64: - dst = appendInt64(dst, *val) + dst = enc.AppendInt64(dst, *val) case *uint: - dst = appendUint(dst, *val) + dst = enc.AppendUint(dst, *val) case *uint8: - dst = appendUint8(dst, *val) + dst = enc.AppendUint8(dst, *val) case *uint16: - dst = appendUint16(dst, *val) + dst = enc.AppendUint16(dst, *val) case *uint32: - dst = appendUint32(dst, *val) + dst = enc.AppendUint32(dst, *val) case *uint64: - dst = appendUint64(dst, *val) + dst = enc.AppendUint64(dst, *val) case *float32: - dst = appendFloat32(dst, *val) + dst = enc.AppendFloat32(dst, *val) case *float64: - dst = appendFloat64(dst, *val) + dst = enc.AppendFloat64(dst, *val) case *time.Time: - dst = appendTime(dst, *val, TimeFieldFormat) + dst = enc.AppendTime(dst, *val, TimeFieldFormat) case *time.Duration: - dst = appendDuration(dst, *val, DurationFieldUnit, DurationFieldInteger) + dst = enc.AppendDuration(dst, *val, DurationFieldUnit, DurationFieldInteger) case []string: - dst = appendStrings(dst, val) + dst = enc.AppendStrings(dst, val) case []bool: - dst = appendBools(dst, val) + dst = enc.AppendBools(dst, val) case []int: - dst = appendInts(dst, val) + dst = enc.AppendInts(dst, val) case []int8: - dst = appendInts8(dst, val) + dst = enc.AppendInts8(dst, val) case []int16: - dst = appendInts16(dst, val) + dst = enc.AppendInts16(dst, val) case []int32: - dst = appendInts32(dst, val) + dst = enc.AppendInts32(dst, val) case []int64: - dst = appendInts64(dst, val) + dst = enc.AppendInts64(dst, val) case []uint: - dst = appendUints(dst, val) + dst = enc.AppendUints(dst, val) // case []uint8: - // dst = appendUints8(dst, val) + // dst = enc.AppendUints8(dst, val) case []uint16: - dst = appendUints16(dst, val) + dst = enc.AppendUints16(dst, val) case []uint32: - dst = appendUints32(dst, val) + dst = enc.AppendUints32(dst, val) case []uint64: - dst = appendUints64(dst, val) + dst = enc.AppendUints64(dst, val) case []float32: - dst = appendFloats32(dst, val) + dst = enc.AppendFloats32(dst, val) case []float64: - dst = appendFloats64(dst, val) + dst = enc.AppendFloats64(dst, val) case []time.Time: - dst = appendTimes(dst, val, TimeFieldFormat) + dst = enc.AppendTimes(dst, val, TimeFieldFormat) case []time.Duration: - dst = appendDurations(dst, val, DurationFieldUnit, DurationFieldInteger) + dst = enc.AppendDurations(dst, val, DurationFieldUnit, DurationFieldInteger) case nil: - dst = appendNil(dst) + dst = enc.AppendNil(dst) case net.IP: - dst = appendIPAddr(dst, val) + dst = enc.AppendIPAddr(dst, val) case net.IPNet: - dst = appendIPPrefix(dst, val) + dst = enc.AppendIPPrefix(dst, val) case net.HardwareAddr: - dst = appendMACAddr(dst, val) + dst = enc.AppendMACAddr(dst, val) default: - dst = appendInterface(dst, val) + dst = enc.AppendInterface(dst, val) } } return dst diff --git a/internal/cbor/base.go b/internal/cbor/base.go index 6096183..1b13278 100644 --- a/internal/cbor/base.go +++ b/internal/cbor/base.go @@ -1,43 +1,45 @@ package cbor +type Encoder struct{} + // AppendKey adds a key (string) to the binary encoded log message -func AppendKey(dst []byte, key string) []byte { +func (e Encoder) AppendKey(dst []byte, key string) []byte { if len(dst) < 1 { - dst = AppendBeginMarker(dst) + dst = e.AppendBeginMarker(dst) } - return AppendString(dst, key) + return e.AppendString(dst, key) } // AppendError adds the Error to the log message if error is NOT nil -func AppendError(dst []byte, err error) []byte { +func (e Encoder) AppendError(dst []byte, err error) []byte { if err == nil { return append(dst, `null`...) } - return AppendString(dst, err.Error()) + return e.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 { +func (e Encoder) AppendErrors(dst []byte, errs []error) []byte { if len(errs) == 0 { - return AppendArrayEnd(AppendArrayStart(dst)) + return e.AppendArrayEnd(e.AppendArrayStart(dst)) } - dst = AppendArrayStart(dst) + dst = e.AppendArrayStart(dst) if errs[0] != nil { - dst = AppendString(dst, errs[0].Error()) + dst = e.AppendString(dst, errs[0].Error()) } else { - dst = AppendNull(dst) + dst = e.AppendNil(dst) } if len(errs) > 1 { for _, err := range errs[1:] { if err == nil { - dst = AppendNull(dst) + dst = e.AppendNil(dst) continue } - dst = AppendString(dst, err.Error()) + dst = e.AppendString(dst, err.Error()) } } - dst = AppendArrayEnd(dst) + dst = e.AppendArrayEnd(dst) return dst } diff --git a/internal/cbor/string.go b/internal/cbor/string.go index 8b3ab96..681640a 100644 --- a/internal/cbor/string.go +++ b/internal/cbor/string.go @@ -1,7 +1,7 @@ package cbor // AppendStrings encodes and adds an array of strings to the dst byte array. -func AppendStrings(dst []byte, vals []string) []byte { +func (e Encoder) AppendStrings(dst []byte, vals []string) []byte { major := majorTypeArray l := len(vals) if l <= additionalMax { @@ -11,13 +11,13 @@ func AppendStrings(dst []byte, vals []string) []byte { dst = appendCborTypePrefix(dst, major, uint64(l)) } for _, v := range vals { - dst = AppendString(dst, v) + dst = e.AppendString(dst, v) } return dst } // AppendString encodes and adds a string to the dst byte array. -func AppendString(dst []byte, s string) []byte { +func (Encoder) AppendString(dst []byte, s string) []byte { major := majorTypeUtf8String l := len(s) @@ -31,7 +31,7 @@ func AppendString(dst []byte, s string) []byte { } // AppendBytes encodes and adds an array of bytes to the dst byte array. -func AppendBytes(dst, s []byte) []byte { +func (Encoder) AppendBytes(dst, s []byte) []byte { major := majorTypeByteString l := len(s) @@ -45,7 +45,7 @@ func AppendBytes(dst, s []byte) []byte { } // AppendEmbeddedJSON adds a tag and embeds input JSON as such. -func AppendEmbeddedJSON(dst, s []byte) []byte { +func (Encoder) AppendEmbeddedJSON(dst, s []byte) []byte { major := majorTypeTags minor := additionalTypeEmbeddedJSON diff --git a/internal/cbor/string_test.go b/internal/cbor/string_test.go index 809b2a3..0e3e9ab 100644 --- a/internal/cbor/string_test.go +++ b/internal/cbor/string_test.go @@ -59,7 +59,7 @@ var encodeByteTests = []struct { func TestAppendString(t *testing.T) { for _, tt := range encodeStringTests { - b := AppendString([]byte{}, tt.plain) + b := enc.AppendString([]byte{}, tt.plain) if got, want := string(b), tt.binary; got != want { t.Errorf("appendString(%q) = %#q, want %#q", tt.plain, got, want) } @@ -72,7 +72,7 @@ func TestAppendString(t *testing.T) { } inp := buffer.String() want := "\x7a\x00\x01\x11\x70" + inp - b := AppendString([]byte{}, inp) + b := enc.AppendString([]byte{}, inp) if got := string(b); got != want { t.Errorf("appendString(%q) = %#q, want %#q", inp, got, want) } @@ -80,7 +80,7 @@ func TestAppendString(t *testing.T) { func TestAppendBytes(t *testing.T) { for _, tt := range encodeByteTests { - b := AppendBytes([]byte{}, tt.plain) + b := enc.AppendBytes([]byte{}, tt.plain) if got, want := string(b), tt.binary; got != want { t.Errorf("appendString(%q) = %#q, want %#q", tt.plain, got, want) } @@ -92,7 +92,7 @@ func TestAppendBytes(t *testing.T) { inp = append(inp, byte('a')) } want := "\x5a\x00\x01\x11\x70" + string(inp) - b := AppendBytes([]byte{}, inp) + b := enc.AppendBytes([]byte{}, inp) if got := string(b); got != want { t.Errorf("appendString(%q) = %#q, want %#q", inp, got, want) } @@ -111,7 +111,7 @@ func BenchmarkAppendString(b *testing.B) { b.Run(name, func(b *testing.B) { buf := make([]byte, 0, 120) for i := 0; i < b.N; i++ { - _ = AppendString(buf, str) + _ = enc.AppendString(buf, str) } }) } diff --git a/internal/cbor/time.go b/internal/cbor/time.go index c8513f2..12f6a1d 100644 --- a/internal/cbor/time.go +++ b/internal/cbor/time.go @@ -21,7 +21,7 @@ func appendIntegerTimestamp(dst []byte, t time.Time) []byte { return dst } -func appendFloatTimestamp(dst []byte, t time.Time) []byte { +func (e Encoder) appendFloatTimestamp(dst []byte, t time.Time) []byte { major := majorTypeTags minor := additionalTypeTimestamp dst = append(dst, byte(major|minor)) @@ -29,24 +29,24 @@ func appendFloatTimestamp(dst []byte, t time.Time) []byte { nanos := t.Nanosecond() var val float64 val = float64(secs)*1.0 + float64(nanos)*1E-9 - return AppendFloat64(dst, val) + return e.AppendFloat64(dst, val) } // AppendTime encodes and adds a timestamp to the dst byte array. -func AppendTime(dst []byte, t time.Time, unused string) []byte { +func (e Encoder) AppendTime(dst []byte, t time.Time, unused string) []byte { utc := t.UTC() if utc.Nanosecond() == 0 { return appendIntegerTimestamp(dst, utc) } - return appendFloatTimestamp(dst, utc) + return e.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 { +func (e Encoder) AppendTimes(dst []byte, vals []time.Time, unused string) []byte { major := majorTypeArray l := len(vals) if l == 0 { - return AppendArrayEnd(AppendArrayStart(dst)) + return e.AppendArrayEnd(e.AppendArrayStart(dst)) } if l <= additionalMax { lb := byte(l) @@ -56,7 +56,7 @@ func AppendTimes(dst []byte, vals []time.Time, unused string) []byte { } for _, t := range vals { - dst = AppendTime(dst, t, unused) + dst = e.AppendTime(dst, t, unused) } return dst } @@ -64,21 +64,21 @@ func AppendTimes(dst []byte, vals []time.Time, unused string) []byte { // 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 { +func (e Encoder) AppendDuration(dst []byte, d time.Duration, unit time.Duration, useInt bool) []byte { if useInt { - return AppendInt64(dst, int64(d/unit)) + return e.AppendInt64(dst, int64(d/unit)) } - return AppendFloat64(dst, float64(d)/float64(unit)) + return e.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 { +func (e Encoder) AppendDurations(dst []byte, vals []time.Duration, unit time.Duration, useInt bool) []byte { major := majorTypeArray l := len(vals) if l == 0 { - return AppendArrayEnd(AppendArrayStart(dst)) + return e.AppendArrayEnd(e.AppendArrayStart(dst)) } if l <= additionalMax { lb := byte(l) @@ -87,7 +87,7 @@ func AppendDurations(dst []byte, vals []time.Duration, unit time.Duration, useIn dst = appendCborTypePrefix(dst, major, uint64(l)) } for _, d := range vals { - dst = AppendDuration(dst, d, unit, useInt) + dst = e.AppendDuration(dst, d, unit, useInt) } return dst } diff --git a/internal/cbor/time_test.go b/internal/cbor/time_test.go index 507a6ba..d285e35 100644 --- a/internal/cbor/time_test.go +++ b/internal/cbor/time_test.go @@ -10,7 +10,7 @@ import ( func TestAppendTimeNow(t *testing.T) { tm := time.Now() - s := AppendTime([]byte{}, tm, "unused") + s := enc.AppendTime([]byte{}, tm, "unused") got := string(s) tm1 := float64(tm.Unix()) + float64(tm.Nanosecond())*1E-9 @@ -43,7 +43,7 @@ func TestAppendTimePastPresentInteger(t *testing.T) { fmt.Println("Cannot parse input", tt.txt, ".. Skipping!", err) continue } - b := AppendTime([]byte{}, tin, "unused") + b := enc.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), @@ -68,7 +68,7 @@ func TestAppendTimePastPresentFloat(t *testing.T) { fmt.Println("Cannot parse input", tt.rfcStr, ".. Skipping!") continue } - b := AppendTime([]byte{}, tin, "unused") + b := enc.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), @@ -92,7 +92,7 @@ func BenchmarkAppendTime(b *testing.B) { b.Run(name, func(b *testing.B) { buf := make([]byte, 0, 100) for i := 0; i < b.N; i++ { - _ = AppendTime(buf, t, "unused") + _ = enc.AppendTime(buf, t, "unused") } }) } diff --git a/internal/cbor/types.go b/internal/cbor/types.go index 9b1216d..2a8a716 100644 --- a/internal/cbor/types.go +++ b/internal/cbor/types.go @@ -7,23 +7,50 @@ import ( "net" ) -// AppendNull inserts a 'Nil' object into the dst byte array. -func AppendNull(dst []byte) []byte { +// AppendNil inserts a 'Nil' object into the dst byte array. +func (Encoder) AppendNil(dst []byte) []byte { return append(dst, byte(majorTypeSimpleAndFloat|additionalTypeNull)) } // AppendBeginMarker inserts a map start into the dst byte array. -func AppendBeginMarker(dst []byte) []byte { +func (Encoder) 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 { +func (Encoder) AppendEndMarker(dst []byte) []byte { return append(dst, byte(majorTypeSimpleAndFloat|additionalTypeBreak)) } +// AppendObjectData takes an object in form of a byte array and appends to dst. +func (Encoder) AppendObjectData(dst []byte, o []byte) []byte { + return append(dst, o...) +} + +// AppendArrayStart adds markers to indicate the start of an array. +func (Encoder) AppendArrayStart(dst []byte) []byte { + return append(dst, byte(majorTypeArray|additionalTypeInfiniteCount)) +} + +// AppendArrayEnd adds markers to indicate the end of an array. +func (Encoder) AppendArrayEnd(dst []byte) []byte { + return append(dst, byte(majorTypeSimpleAndFloat|additionalTypeBreak)) +} + +// AppendArrayDelim adds markers to indicate end of a particular array element. +func (Encoder) AppendArrayDelim(dst []byte) []byte { + //No delimiters needed in cbor + return dst +} + +// AppendLineBreak is a noop that keep API compat with json encoder. +func (Encoder) AppendLineBreak(dst []byte) []byte { + // No line breaks needed in binary format. + return dst +} + // AppendBool encodes and inserts a boolean value into the dst byte array. -func AppendBool(dst []byte, val bool) []byte { +func (Encoder) AppendBool(dst []byte, val bool) []byte { b := additionalTypeBoolFalse if val { b = additionalTypeBoolTrue @@ -32,11 +59,11 @@ func AppendBool(dst []byte, val bool) []byte { } // AppendBools encodes and inserts an array of boolean values into the dst byte array. -func AppendBools(dst []byte, vals []bool) []byte { +func (e Encoder) AppendBools(dst []byte, vals []bool) []byte { major := majorTypeArray l := len(vals) if l == 0 { - return AppendArrayEnd(AppendArrayStart(dst)) + return e.AppendArrayEnd(e.AppendArrayStart(dst)) } if l <= additionalMax { lb := byte(l) @@ -45,13 +72,13 @@ func AppendBools(dst []byte, vals []bool) []byte { dst = appendCborTypePrefix(dst, major, uint64(l)) } for _, v := range vals { - dst = AppendBool(dst, v) + dst = e.AppendBool(dst, v) } return dst } // AppendInt encodes and inserts an integer value into the dst byte array. -func AppendInt(dst []byte, val int) []byte { +func (Encoder) AppendInt(dst []byte, val int) []byte { major := majorTypeUnsignedInt contentVal := val if val < 0 { @@ -68,11 +95,11 @@ func AppendInt(dst []byte, val int) []byte { } // AppendInts encodes and inserts an array of integer values into the dst byte array. -func AppendInts(dst []byte, vals []int) []byte { +func (e Encoder) AppendInts(dst []byte, vals []int) []byte { major := majorTypeArray l := len(vals) if l == 0 { - return AppendArrayEnd(AppendArrayStart(dst)) + return e.AppendArrayEnd(e.AppendArrayStart(dst)) } if l <= additionalMax { lb := byte(l) @@ -81,22 +108,22 @@ func AppendInts(dst []byte, vals []int) []byte { dst = appendCborTypePrefix(dst, major, uint64(l)) } for _, v := range vals { - dst = AppendInt(dst, v) + dst = e.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)) +func (e Encoder) AppendInt8(dst []byte, val int8) []byte { + return e.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 { +func (e Encoder) AppendInts8(dst []byte, vals []int8) []byte { major := majorTypeArray l := len(vals) if l == 0 { - return AppendArrayEnd(AppendArrayStart(dst)) + return e.AppendArrayEnd(e.AppendArrayStart(dst)) } if l <= additionalMax { lb := byte(l) @@ -105,22 +132,22 @@ func AppendInts8(dst []byte, vals []int8) []byte { dst = appendCborTypePrefix(dst, major, uint64(l)) } for _, v := range vals { - dst = AppendInt(dst, int(v)) + dst = e.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)) +func (e Encoder) AppendInt16(dst []byte, val int16) []byte { + return e.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 { +func (e Encoder) AppendInts16(dst []byte, vals []int16) []byte { major := majorTypeArray l := len(vals) if l == 0 { - return AppendArrayEnd(AppendArrayStart(dst)) + return e.AppendArrayEnd(e.AppendArrayStart(dst)) } if l <= additionalMax { lb := byte(l) @@ -129,22 +156,22 @@ func AppendInts16(dst []byte, vals []int16) []byte { dst = appendCborTypePrefix(dst, major, uint64(l)) } for _, v := range vals { - dst = AppendInt(dst, int(v)) + dst = e.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)) +func (e Encoder) AppendInt32(dst []byte, val int32) []byte { + return e.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 { +func (e Encoder) AppendInts32(dst []byte, vals []int32) []byte { major := majorTypeArray l := len(vals) if l == 0 { - return AppendArrayEnd(AppendArrayStart(dst)) + return e.AppendArrayEnd(e.AppendArrayStart(dst)) } if l <= additionalMax { lb := byte(l) @@ -153,13 +180,13 @@ func AppendInts32(dst []byte, vals []int32) []byte { dst = appendCborTypePrefix(dst, major, uint64(l)) } for _, v := range vals { - dst = AppendInt(dst, int(v)) + dst = e.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 { +func (Encoder) AppendInt64(dst []byte, val int64) []byte { major := majorTypeUnsignedInt contentVal := val if val < 0 { @@ -176,11 +203,11 @@ func AppendInt64(dst []byte, val int64) []byte { } // AppendInts64 encodes and inserts an array of int64 values into the dst byte array. -func AppendInts64(dst []byte, vals []int64) []byte { +func (e Encoder) AppendInts64(dst []byte, vals []int64) []byte { major := majorTypeArray l := len(vals) if l == 0 { - return AppendArrayEnd(AppendArrayStart(dst)) + return e.AppendArrayEnd(e.AppendArrayStart(dst)) } if l <= additionalMax { lb := byte(l) @@ -189,22 +216,22 @@ func AppendInts64(dst []byte, vals []int64) []byte { dst = appendCborTypePrefix(dst, major, uint64(l)) } for _, v := range vals { - dst = AppendInt64(dst, v) + dst = e.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)) +func (e Encoder) AppendUint(dst []byte, val uint) []byte { + return e.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 { +func (e Encoder) AppendUints(dst []byte, vals []uint) []byte { major := majorTypeArray l := len(vals) if l == 0 { - return AppendArrayEnd(AppendArrayStart(dst)) + return e.AppendArrayEnd(e.AppendArrayStart(dst)) } if l <= additionalMax { lb := byte(l) @@ -213,22 +240,22 @@ func AppendUints(dst []byte, vals []uint) []byte { dst = appendCborTypePrefix(dst, major, uint64(l)) } for _, v := range vals { - dst = AppendUint(dst, v) + dst = e.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)) +func (e Encoder) AppendUint8(dst []byte, val uint8) []byte { + return e.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 { +func (e Encoder) AppendUints8(dst []byte, vals []uint8) []byte { major := majorTypeArray l := len(vals) if l == 0 { - return AppendArrayEnd(AppendArrayStart(dst)) + return e.AppendArrayEnd(e.AppendArrayStart(dst)) } if l <= additionalMax { lb := byte(l) @@ -237,22 +264,22 @@ func AppendUints8(dst []byte, vals []uint8) []byte { dst = appendCborTypePrefix(dst, major, uint64(l)) } for _, v := range vals { - dst = AppendUint8(dst, v) + dst = e.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)) +func (e Encoder) AppendUint16(dst []byte, val uint16) []byte { + return e.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 { +func (e Encoder) AppendUints16(dst []byte, vals []uint16) []byte { major := majorTypeArray l := len(vals) if l == 0 { - return AppendArrayEnd(AppendArrayStart(dst)) + return e.AppendArrayEnd(e.AppendArrayStart(dst)) } if l <= additionalMax { lb := byte(l) @@ -261,22 +288,22 @@ func AppendUints16(dst []byte, vals []uint16) []byte { dst = appendCborTypePrefix(dst, major, uint64(l)) } for _, v := range vals { - dst = AppendUint16(dst, v) + dst = e.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)) +func (e Encoder) AppendUint32(dst []byte, val uint32) []byte { + return e.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 { +func (e Encoder) AppendUints32(dst []byte, vals []uint32) []byte { major := majorTypeArray l := len(vals) if l == 0 { - return AppendArrayEnd(AppendArrayStart(dst)) + return e.AppendArrayEnd(e.AppendArrayStart(dst)) } if l <= additionalMax { lb := byte(l) @@ -285,13 +312,13 @@ func AppendUints32(dst []byte, vals []uint32) []byte { dst = appendCborTypePrefix(dst, major, uint64(l)) } for _, v := range vals { - dst = AppendUint32(dst, v) + dst = e.AppendUint32(dst, v) } return dst } // AppendUint64 encodes and inserts a uint64 value into the dst byte array. -func AppendUint64(dst []byte, val uint64) []byte { +func (Encoder) AppendUint64(dst []byte, val uint64) []byte { major := majorTypeUnsignedInt contentVal := val if contentVal <= additionalMax { @@ -304,11 +331,11 @@ func AppendUint64(dst []byte, val uint64) []byte { } // AppendUints64 encodes and inserts an array of uint64 values into the dst byte array. -func AppendUints64(dst []byte, vals []uint64) []byte { +func (e Encoder) AppendUints64(dst []byte, vals []uint64) []byte { major := majorTypeArray l := len(vals) if l == 0 { - return AppendArrayEnd(AppendArrayStart(dst)) + return e.AppendArrayEnd(e.AppendArrayStart(dst)) } if l <= additionalMax { lb := byte(l) @@ -317,13 +344,13 @@ func AppendUints64(dst []byte, vals []uint64) []byte { dst = appendCborTypePrefix(dst, major, uint64(l)) } for _, v := range vals { - dst = AppendUint64(dst, v) + dst = e.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 { +func (Encoder) AppendFloat32(dst []byte, val float32) []byte { switch { case math.IsNaN(float64(val)): return append(dst, "\xfa\x7f\xc0\x00\x00"...) @@ -343,11 +370,11 @@ func AppendFloat32(dst []byte, val float32) []byte { } // AppendFloats32 encodes and inserts an array of single precision float value into the dst byte array. -func AppendFloats32(dst []byte, vals []float32) []byte { +func (e Encoder) AppendFloats32(dst []byte, vals []float32) []byte { major := majorTypeArray l := len(vals) if l == 0 { - return AppendArrayEnd(AppendArrayStart(dst)) + return e.AppendArrayEnd(e.AppendArrayStart(dst)) } if l <= additionalMax { lb := byte(l) @@ -356,13 +383,13 @@ func AppendFloats32(dst []byte, vals []float32) []byte { dst = appendCborTypePrefix(dst, major, uint64(l)) } for _, v := range vals { - dst = AppendFloat32(dst, v) + dst = e.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 { +func (Encoder) AppendFloat64(dst []byte, val float64) []byte { switch { case math.IsNaN(val): return append(dst, "\xfb\x7f\xf8\x00\x00\x00\x00\x00\x00"...) @@ -383,11 +410,11 @@ func AppendFloat64(dst []byte, val float64) []byte { } // AppendFloats64 encodes and inserts an array of double precision float values into the dst byte array. -func AppendFloats64(dst []byte, vals []float64) []byte { +func (e Encoder) AppendFloats64(dst []byte, vals []float64) []byte { major := majorTypeArray l := len(vals) if l == 0 { - return AppendArrayEnd(AppendArrayStart(dst)) + return e.AppendArrayEnd(e.AppendArrayStart(dst)) } if l <= additionalMax { lb := byte(l) @@ -396,51 +423,30 @@ func AppendFloats64(dst []byte, vals []float64) []byte { dst = appendCborTypePrefix(dst, major, uint64(l)) } for _, v := range vals { - dst = AppendFloat64(dst, v) + dst = e.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 { +func (e Encoder) AppendInterface(dst []byte, i interface{}) []byte { marshaled, err := json.Marshal(i) if err != nil { - return AppendString(dst, fmt.Sprintf("marshaling error: %v", err)) + return e.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 + return e.AppendEmbeddedJSON(dst, marshaled) } // AppendIPAddr encodes and inserts an IP Address (IPv4 or IPv6). -func AppendIPAddr(dst []byte, ip net.IP) []byte { +func (e Encoder) AppendIPAddr(dst []byte, ip net.IP) []byte { dst = append(dst, byte(majorTypeTags|additionalTypeIntUint16)) dst = append(dst, byte(additionalTypeTagNetworkAddr>>8)) dst = append(dst, byte(additionalTypeTagNetworkAddr&0xff)) - return AppendBytes(dst, ip) + return e.AppendBytes(dst, ip) } // AppendIPPrefix encodes and inserts an IP Address Prefix (Address + Mask Length). -func AppendIPPrefix(dst []byte, pfx net.IPNet) []byte { +func (e Encoder) AppendIPPrefix(dst []byte, pfx net.IPNet) []byte { dst = append(dst, byte(majorTypeTags|additionalTypeIntUint16)) dst = append(dst, byte(additionalTypeTagNetworkPrefix>>8)) dst = append(dst, byte(additionalTypeTagNetworkPrefix&0xff)) @@ -448,23 +454,23 @@ func AppendIPPrefix(dst []byte, pfx net.IPNet) []byte { // Prefix is a tuple (aka MAP of 1 pair of elements) - // first element is prefix, second is mask length. dst = append(dst, byte(majorTypeMap|0x1)) - dst = AppendBytes(dst, pfx.IP) + dst = e.AppendBytes(dst, pfx.IP) maskLen, _ := pfx.Mask.Size() - return AppendUint8(dst, uint8(maskLen)) + return e.AppendUint8(dst, uint8(maskLen)) } // AppendMACAddr encodes and inserts an Hardware (MAC) address. -func AppendMACAddr(dst []byte, ha net.HardwareAddr) []byte { +func (e Encoder) AppendMACAddr(dst []byte, ha net.HardwareAddr) []byte { dst = append(dst, byte(majorTypeTags|additionalTypeIntUint16)) dst = append(dst, byte(additionalTypeTagNetworkAddr>>8)) dst = append(dst, byte(additionalTypeTagNetworkAddr&0xff)) - return AppendBytes(dst, ha) + return e.AppendBytes(dst, ha) } // AppendHex adds a TAG and inserts a hex bytes as a string. -func AppendHex(dst []byte, val []byte) []byte { +func (e Encoder) 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) + return e.AppendBytes(dst, val) } diff --git a/internal/cbor/types_test.go b/internal/cbor/types_test.go index b3a1094..9c31429 100644 --- a/internal/cbor/types_test.go +++ b/internal/cbor/types_test.go @@ -6,8 +6,10 @@ import ( "testing" ) -func TestAppendNull(t *testing.T) { - s := AppendNull([]byte{}) +var enc = Encoder{} + +func TestAppendNil(t *testing.T) { + s := enc.AppendNil([]byte{}) got := string(s) want := "\xf6" if got != want { @@ -27,7 +29,7 @@ var booleanTestCases = []struct { func TestAppendBool(t *testing.T) { for _, tc := range booleanTestCases { - s := AppendBool([]byte{}, tc.val) + s := enc.AppendBool([]byte{}, tc.val) got := string(s) if got != tc.binary { t.Errorf("AppendBool(%s)=0x%s, want: 0x%s", @@ -48,7 +50,7 @@ var booleanArrayTestCases = []struct { func TestAppendBoolArray(t *testing.T) { for _, tc := range booleanArrayTestCases { - s := AppendBools([]byte{}, tc.val) + s := enc.AppendBools([]byte{}, tc.val) got := string(s) if got != tc.binary { t.Errorf("AppendBools(%s)=0x%s, want: 0x%s", @@ -123,7 +125,7 @@ var integerTestCases = []struct { func TestAppendInt(t *testing.T) { for _, tc := range integerTestCases { - s := AppendInt([]byte{}, tc.val) + s := enc.AppendInt([]byte{}, tc.val) got := string(s) if got != tc.binary { t.Errorf("AppendInt(0x%x)=0x%s, want: 0x%s", @@ -148,7 +150,7 @@ var integerArrayTestCases = []struct { func TestAppendIntArray(t *testing.T) { for _, tc := range integerArrayTestCases { - s := AppendInts([]byte{}, tc.val) + s := enc.AppendInts([]byte{}, tc.val) got := string(s) if got != tc.binary { t.Errorf("AppendInts(%s)=0x%s, want: 0x%s", @@ -173,7 +175,7 @@ var float32TestCases = []struct { func TestAppendFloat32(t *testing.T) { for _, tc := range float32TestCases { - s := AppendFloat32([]byte{}, tc.val) + s := enc.AppendFloat32([]byte{}, tc.val) got := string(s) if got != tc.binary { t.Errorf("AppendFloat32(%f)=0x%s, want: 0x%s", @@ -196,7 +198,7 @@ var ipAddrTestCases = []struct { func TestAppendNetworkAddr(t *testing.T) { for _, tc := range ipAddrTestCases { - s := AppendIPAddr([]byte{}, tc.ipaddr) + s := enc.AppendIPAddr([]byte{}, tc.ipaddr) got := string(s) if got != tc.binary { t.Errorf("AppendIPAddr(%s)=0x%s, want: 0x%s", @@ -217,7 +219,7 @@ var macAddrTestCases = []struct { func TestAppendMacAddr(t *testing.T) { for _, tc := range macAddrTestCases { - s := AppendMACAddr([]byte{}, tc.macaddr) + s := enc.AppendMACAddr([]byte{}, tc.macaddr) got := string(s) if got != tc.binary { t.Errorf("AppendMACAddr(%s)=0x%s, want: 0x%s", @@ -239,7 +241,7 @@ var IPPrefixTestCases = []struct { func TestAppendIPPrefix(t *testing.T) { for _, tc := range IPPrefixTestCases { - s := AppendIPPrefix([]byte{}, tc.pfx) + s := enc.AppendIPPrefix([]byte{}, tc.pfx) got := string(s) if got != tc.binary { t.Errorf("AppendIPPrefix(%s)=0x%s, want: 0x%s", @@ -272,23 +274,23 @@ func BenchmarkAppendInt(b *testing.B) { for i := 0; i < b.N; i++ { switch str.sz { case 0: - _ = AppendInt(buf, int(str.val)) + _ = enc.AppendInt(buf, int(str.val)) case 1: - _ = AppendUint8(buf, uint8(str.val)) + _ = enc.AppendUint8(buf, uint8(str.val)) case 2: - _ = AppendUint16(buf, uint16(str.val)) + _ = enc.AppendUint16(buf, uint16(str.val)) case 4: - _ = AppendUint32(buf, uint32(str.val)) + _ = enc.AppendUint32(buf, uint32(str.val)) case 8: - _ = AppendUint64(buf, uint64(str.val)) + _ = enc.AppendUint64(buf, uint64(str.val)) case 21: - _ = AppendInt8(buf, int8(str.val)) + _ = enc.AppendInt8(buf, int8(str.val)) case 22: - _ = AppendInt16(buf, int16(str.val)) + _ = enc.AppendInt16(buf, int16(str.val)) case 23: - _ = AppendInt32(buf, int32(str.val)) + _ = enc.AppendInt32(buf, int32(str.val)) case 24: - _ = AppendInt64(buf, int64(str.val)) + _ = enc.AppendInt64(buf, int64(str.val)) } } }) @@ -310,9 +312,9 @@ func BenchmarkAppendFloat(b *testing.B) { for i := 0; i < b.N; i++ { switch str.sz { case 4: - _ = AppendFloat32(buf, float32(str.val)) + _ = enc.AppendFloat32(buf, float32(str.val)) case 8: - _ = AppendFloat64(buf, str.val) + _ = enc.AppendFloat64(buf, str.val) } } }) diff --git a/internal/json/base.go b/internal/json/base.go index 722bf76..bad1c74 100644 --- a/internal/json/base.go +++ b/internal/json/base.go @@ -1,32 +1,34 @@ package json +type Encoder struct{} + // AppendKey appends a new key to the output JSON. -func AppendKey(dst []byte, key string) []byte { +func (e Encoder) AppendKey(dst []byte, key string) []byte { if len(dst) > 1 && dst[len(dst)-1] != '{' { dst = append(dst, ',') } - dst = AppendString(dst, key) + dst = e.AppendString(dst, key) return append(dst, ':') } // AppendError encodes the error string to json and appends // the encoded string to the input byte slice. -func AppendError(dst []byte, err error) []byte { +func (e Encoder) AppendError(dst []byte, err error) []byte { if err == nil { return append(dst, `null`...) } - return AppendString(dst, err.Error()) + return e.AppendString(dst, err.Error()) } // AppendErrors encodes the error strings to json and // appends the encoded string list to the input byte slice. -func AppendErrors(dst []byte, errs []error) []byte { +func (e Encoder) AppendErrors(dst []byte, errs []error) []byte { if len(errs) == 0 { return append(dst, '[', ']') } dst = append(dst, '[') if errs[0] != nil { - dst = AppendString(dst, errs[0].Error()) + dst = e.AppendString(dst, errs[0].Error()) } else { dst = append(dst, "null"...) } @@ -36,7 +38,7 @@ func AppendErrors(dst []byte, errs []error) []byte { dst = append(dst, ",null"...) continue } - dst = AppendString(append(dst, ','), err.Error()) + dst = e.AppendString(append(dst, ','), err.Error()) } } dst = append(dst, ']') diff --git a/internal/json/bytes.go b/internal/json/bytes.go index 8f7d5fe..de64120 100644 --- a/internal/json/bytes.go +++ b/internal/json/bytes.go @@ -3,7 +3,7 @@ package json import "unicode/utf8" // AppendBytes is a mirror of appendString with []byte arg -func AppendBytes(dst, s []byte) []byte { +func (Encoder) AppendBytes(dst, s []byte) []byte { dst = append(dst, '"') for i := 0; i < len(s); i++ { if !noEscapeTable[s[i]] { @@ -20,7 +20,7 @@ func AppendBytes(dst, s []byte) []byte { // // The operation loops though each byte and encodes it as hex using // the hex lookup table. -func AppendHex(dst, s []byte) []byte { +func (Encoder) AppendHex(dst, s []byte) []byte { dst = append(dst, '"') for _, v := range s { dst = append(dst, hex[v>>4], hex[v&0x0f]) diff --git a/internal/json/bytes_test.go b/internal/json/bytes_test.go index e33c1e0..d1a370a 100644 --- a/internal/json/bytes_test.go +++ b/internal/json/bytes_test.go @@ -5,9 +5,11 @@ import ( "unicode" ) +var enc = Encoder{} + func TestAppendBytes(t *testing.T) { for _, tt := range encodeStringTests { - b := AppendBytes([]byte{}, []byte(tt.in)) + b := enc.AppendBytes([]byte{}, []byte(tt.in)) if got, want := string(b), tt.out; got != want { t.Errorf("appendBytes(%q) = %#q, want %#q", tt.in, got, want) } @@ -16,7 +18,7 @@ func TestAppendBytes(t *testing.T) { func TestAppendHex(t *testing.T) { for _, tt := range encodeHexTests { - b := AppendHex([]byte{}, []byte{tt.in}) + b := enc.AppendHex([]byte{}, []byte{tt.in}) if got, want := string(b), tt.out; got != want { t.Errorf("appendHex(%x) = %s, want %s", tt.in, got, want) } @@ -32,31 +34,31 @@ func TestStringBytes(t *testing.T) { } s := string(r) + "\xff\xff\xffhello" // some invalid UTF-8 too - enc := string(AppendString([]byte{}, s)) - encBytes := string(AppendBytes([]byte{}, []byte(s))) + encStr := string(enc.AppendString([]byte{}, s)) + encBytes := string(enc.AppendBytes([]byte{}, []byte(s))) - if enc != encBytes { + if encStr != encBytes { i := 0 - for i < len(enc) && i < len(encBytes) && enc[i] == encBytes[i] { + for i < len(encStr) && i < len(encBytes) && encStr[i] == encBytes[i] { i++ } - enc = enc[i:] + encStr = encStr[i:] encBytes = encBytes[i:] i = 0 - for i < len(enc) && i < len(encBytes) && enc[len(enc)-i-1] == encBytes[len(encBytes)-i-1] { + for i < len(encStr) && i < len(encBytes) && encStr[len(encStr)-i-1] == encBytes[len(encBytes)-i-1] { i++ } - enc = enc[:len(enc)-i] + encStr = encStr[:len(encStr)-i] encBytes = encBytes[:len(encBytes)-i] - if len(enc) > 20 { - enc = enc[:20] + "..." + if len(encStr) > 20 { + encStr = encStr[:20] + "..." } if len(encBytes) > 20 { encBytes = encBytes[:20] + "..." } - t.Errorf("encodings differ at %#q vs %#q", enc, encBytes) + t.Errorf("encodings differ at %#q vs %#q", encStr, encBytes) } } @@ -75,7 +77,7 @@ func BenchmarkAppendBytes(b *testing.B) { b.Run(name, func(b *testing.B) { buf := make([]byte, 0, 100) for i := 0; i < b.N; i++ { - _ = AppendBytes(buf, byt) + _ = enc.AppendBytes(buf, byt) } }) } diff --git a/internal/json/string.go b/internal/json/string.go index bb606f0..815906f 100644 --- a/internal/json/string.go +++ b/internal/json/string.go @@ -14,15 +14,15 @@ func init() { // AppendStrings encodes the input strings to json and // appends the encoded string list to the input byte slice. -func AppendStrings(dst []byte, vals []string) []byte { +func (e Encoder) AppendStrings(dst []byte, vals []string) []byte { if len(vals) == 0 { return append(dst, '[', ']') } dst = append(dst, '[') - dst = AppendString(dst, vals[0]) + dst = e.AppendString(dst, vals[0]) if len(vals) > 1 { for _, val := range vals[1:] { - dst = AppendString(append(dst, ','), val) + dst = e.AppendString(append(dst, ','), val) } } dst = append(dst, ']') @@ -38,7 +38,7 @@ func AppendStrings(dst []byte, vals []string) []byte { // entirety to the byte slice. // If we encounter a byte that does need encoding, switch up // the operation and perform a byte-by-byte read-encode-append. -func AppendString(dst []byte, s string) []byte { +func (Encoder) AppendString(dst []byte, s string) []byte { // Start with a double quote. dst = append(dst, '"') // Loop through each character in the string. diff --git a/internal/json/string_test.go b/internal/json/string_test.go index a30b124..10c1313 100644 --- a/internal/json/string_test.go +++ b/internal/json/string_test.go @@ -65,7 +65,7 @@ var encodeHexTests = []struct { func TestAppendString(t *testing.T) { for _, tt := range encodeStringTests { - b := AppendString([]byte{}, tt.in) + b := enc.AppendString([]byte{}, tt.in) if got, want := string(b), tt.out; got != want { t.Errorf("appendString(%q) = %#q, want %#q", tt.in, got, want) } @@ -86,7 +86,7 @@ func BenchmarkAppendString(b *testing.B) { b.Run(name, func(b *testing.B) { buf := make([]byte, 0, 100) for i := 0; i < b.N; i++ { - _ = AppendString(buf, str) + _ = enc.AppendString(buf, str) } }) } diff --git a/internal/json/time.go b/internal/json/time.go index 6007d58..739afff 100644 --- a/internal/json/time.go +++ b/internal/json/time.go @@ -7,16 +7,16 @@ import ( // AppendTime formats the input time with the given format // and appends the encoded string to the input byte slice. -func AppendTime(dst []byte, t time.Time, format string) []byte { +func (e Encoder) AppendTime(dst []byte, t time.Time, format string) []byte { if format == "" { - return AppendInt64(dst, t.Unix()) + return e.AppendInt64(dst, t.Unix()) } return append(t.AppendFormat(append(dst, '"'), format), '"') } // AppendTimes converts the input times with the given format // and appends the encoded string list to the input byte slice. -func AppendTimes(dst []byte, vals []time.Time, format string) []byte { +func (Encoder) AppendTimes(dst []byte, vals []time.Time, format string) []byte { if format == "" { return appendUnixTimes(dst, vals) } @@ -51,24 +51,24 @@ func appendUnixTimes(dst []byte, vals []time.Time) []byte { // AppendDuration formats the input duration with the given unit & format // and appends the encoded string to the input byte slice. -func AppendDuration(dst []byte, d time.Duration, unit time.Duration, useInt bool) []byte { +func (e Encoder) AppendDuration(dst []byte, d time.Duration, unit time.Duration, useInt bool) []byte { if useInt { return strconv.AppendInt(dst, int64(d/unit), 10) } - return AppendFloat64(dst, float64(d)/float64(unit)) + return e.AppendFloat64(dst, float64(d)/float64(unit)) } // AppendDurations formats the input durations with the given unit & format // and appends the encoded string list to the input byte slice. -func AppendDurations(dst []byte, vals []time.Duration, unit time.Duration, useInt bool) []byte { +func (e Encoder) AppendDurations(dst []byte, vals []time.Duration, unit time.Duration, useInt bool) []byte { if len(vals) == 0 { return append(dst, '[', ']') } dst = append(dst, '[') - dst = AppendDuration(dst, vals[0], unit, useInt) + dst = e.AppendDuration(dst, vals[0], unit, useInt) if len(vals) > 1 { for _, d := range vals[1:] { - dst = AppendDuration(append(dst, ','), d, unit, useInt) + dst = e.AppendDuration(append(dst, ','), d, unit, useInt) } } dst = append(dst, ']') diff --git a/internal/json/types.go b/internal/json/types.go index 1beff1f..f343c86 100644 --- a/internal/json/types.go +++ b/internal/json/types.go @@ -8,15 +8,53 @@ import ( "strconv" ) +// AppendNil inserts a 'Nil' object into the dst byte array. +func (Encoder) AppendNil(dst []byte) []byte { + return append(dst, "null"...) +} + +// AppendBeginMarker inserts a map start into the dst byte array. +func (Encoder) AppendBeginMarker(dst []byte) []byte { + return append(dst, '{') +} + +// AppendEndMarker inserts a map end into the dst byte array. +func (Encoder) AppendEndMarker(dst []byte) []byte { + return append(dst, '}') +} + +// AppendLineBreak appends a line break. +func (Encoder) AppendLineBreak(dst []byte) []byte { + return append(dst, '\n') +} + +// AppendArrayStart adds markers to indicate the start of an array. +func (Encoder) AppendArrayStart(dst []byte) []byte { + return append(dst, '[') +} + +// AppendArrayEnd adds markers to indicate the end of an array. +func (Encoder) AppendArrayEnd(dst []byte) []byte { + return append(dst, ']') +} + +// AppendArrayDelim adds markers to indicate end of a particular array element. +func (Encoder) AppendArrayDelim(dst []byte) []byte { + if len(dst) > 0 { + return append(dst, ',') + } + return dst +} + // AppendBool converts the input bool to a string and // appends the encoded string to the input byte slice. -func AppendBool(dst []byte, val bool) []byte { +func (Encoder) AppendBool(dst []byte, val bool) []byte { return strconv.AppendBool(dst, val) } // AppendBools encodes the input bools to json and // appends the encoded string list to the input byte slice. -func AppendBools(dst []byte, vals []bool) []byte { +func (Encoder) AppendBools(dst []byte, vals []bool) []byte { if len(vals) == 0 { return append(dst, '[', ']') } @@ -33,13 +71,13 @@ func AppendBools(dst []byte, vals []bool) []byte { // AppendInt converts the input int to a string and // appends the encoded string to the input byte slice. -func AppendInt(dst []byte, val int) []byte { +func (Encoder) AppendInt(dst []byte, val int) []byte { return strconv.AppendInt(dst, int64(val), 10) } // AppendInts encodes the input ints to json and // appends the encoded string list to the input byte slice. -func AppendInts(dst []byte, vals []int) []byte { +func (Encoder) AppendInts(dst []byte, vals []int) []byte { if len(vals) == 0 { return append(dst, '[', ']') } @@ -56,13 +94,13 @@ func AppendInts(dst []byte, vals []int) []byte { // AppendInt8 converts the input []int8 to a string and // appends the encoded string to the input byte slice. -func AppendInt8(dst []byte, val int8) []byte { +func (Encoder) AppendInt8(dst []byte, val int8) []byte { return strconv.AppendInt(dst, int64(val), 10) } // AppendInts8 encodes the input int8s to json and // appends the encoded string list to the input byte slice. -func AppendInts8(dst []byte, vals []int8) []byte { +func (Encoder) AppendInts8(dst []byte, vals []int8) []byte { if len(vals) == 0 { return append(dst, '[', ']') } @@ -79,13 +117,13 @@ func AppendInts8(dst []byte, vals []int8) []byte { // AppendInt16 converts the input int16 to a string and // appends the encoded string to the input byte slice. -func AppendInt16(dst []byte, val int16) []byte { +func (Encoder) AppendInt16(dst []byte, val int16) []byte { return strconv.AppendInt(dst, int64(val), 10) } // AppendInts16 encodes the input int16s to json and // appends the encoded string list to the input byte slice. -func AppendInts16(dst []byte, vals []int16) []byte { +func (Encoder) AppendInts16(dst []byte, vals []int16) []byte { if len(vals) == 0 { return append(dst, '[', ']') } @@ -102,13 +140,13 @@ func AppendInts16(dst []byte, vals []int16) []byte { // AppendInt32 converts the input int32 to a string and // appends the encoded string to the input byte slice. -func AppendInt32(dst []byte, val int32) []byte { +func (Encoder) AppendInt32(dst []byte, val int32) []byte { return strconv.AppendInt(dst, int64(val), 10) } // AppendInts32 encodes the input int32s to json and // appends the encoded string list to the input byte slice. -func AppendInts32(dst []byte, vals []int32) []byte { +func (Encoder) AppendInts32(dst []byte, vals []int32) []byte { if len(vals) == 0 { return append(dst, '[', ']') } @@ -125,13 +163,13 @@ func AppendInts32(dst []byte, vals []int32) []byte { // AppendInt64 converts the input int64 to a string and // appends the encoded string to the input byte slice. -func AppendInt64(dst []byte, val int64) []byte { +func (Encoder) AppendInt64(dst []byte, val int64) []byte { return strconv.AppendInt(dst, val, 10) } // AppendInts64 encodes the input int64s to json and // appends the encoded string list to the input byte slice. -func AppendInts64(dst []byte, vals []int64) []byte { +func (Encoder) AppendInts64(dst []byte, vals []int64) []byte { if len(vals) == 0 { return append(dst, '[', ']') } @@ -148,13 +186,13 @@ func AppendInts64(dst []byte, vals []int64) []byte { // AppendUint converts the input uint to a string and // appends the encoded string to the input byte slice. -func AppendUint(dst []byte, val uint) []byte { +func (Encoder) AppendUint(dst []byte, val uint) []byte { return strconv.AppendUint(dst, uint64(val), 10) } // AppendUints encodes the input uints to json and // appends the encoded string list to the input byte slice. -func AppendUints(dst []byte, vals []uint) []byte { +func (Encoder) AppendUints(dst []byte, vals []uint) []byte { if len(vals) == 0 { return append(dst, '[', ']') } @@ -171,13 +209,13 @@ func AppendUints(dst []byte, vals []uint) []byte { // AppendUint8 converts the input uint8 to a string and // appends the encoded string to the input byte slice. -func AppendUint8(dst []byte, val uint8) []byte { +func (Encoder) AppendUint8(dst []byte, val uint8) []byte { return strconv.AppendUint(dst, uint64(val), 10) } // AppendUints8 encodes the input uint8s to json and // appends the encoded string list to the input byte slice. -func AppendUints8(dst []byte, vals []uint8) []byte { +func (Encoder) AppendUints8(dst []byte, vals []uint8) []byte { if len(vals) == 0 { return append(dst, '[', ']') } @@ -194,13 +232,13 @@ func AppendUints8(dst []byte, vals []uint8) []byte { // AppendUint16 converts the input uint16 to a string and // appends the encoded string to the input byte slice. -func AppendUint16(dst []byte, val uint16) []byte { +func (Encoder) AppendUint16(dst []byte, val uint16) []byte { return strconv.AppendUint(dst, uint64(val), 10) } // AppendUints16 encodes the input uint16s to json and // appends the encoded string list to the input byte slice. -func AppendUints16(dst []byte, vals []uint16) []byte { +func (Encoder) AppendUints16(dst []byte, vals []uint16) []byte { if len(vals) == 0 { return append(dst, '[', ']') } @@ -217,13 +255,13 @@ func AppendUints16(dst []byte, vals []uint16) []byte { // AppendUint32 converts the input uint32 to a string and // appends the encoded string to the input byte slice. -func AppendUint32(dst []byte, val uint32) []byte { +func (Encoder) AppendUint32(dst []byte, val uint32) []byte { return strconv.AppendUint(dst, uint64(val), 10) } // AppendUints32 encodes the input uint32s to json and // appends the encoded string list to the input byte slice. -func AppendUints32(dst []byte, vals []uint32) []byte { +func (Encoder) AppendUints32(dst []byte, vals []uint32) []byte { if len(vals) == 0 { return append(dst, '[', ']') } @@ -240,13 +278,13 @@ func AppendUints32(dst []byte, vals []uint32) []byte { // AppendUint64 converts the input uint64 to a string and // appends the encoded string to the input byte slice. -func AppendUint64(dst []byte, val uint64) []byte { +func (Encoder) AppendUint64(dst []byte, val uint64) []byte { return strconv.AppendUint(dst, uint64(val), 10) } // AppendUints64 encodes the input uint64s to json and // appends the encoded string list to the input byte slice. -func AppendUints64(dst []byte, vals []uint64) []byte { +func (Encoder) AppendUints64(dst []byte, vals []uint64) []byte { if len(vals) == 0 { return append(dst, '[', ']') } @@ -261,9 +299,7 @@ func AppendUints64(dst []byte, vals []uint64) []byte { return dst } -// AppendFloat converts the input float to a string and -// appends the encoded string to the input byte slice. -func AppendFloat(dst []byte, val float64, bitSize int) []byte { +func appendFloat(dst []byte, val float64, bitSize int) []byte { // JSON does not permit NaN or Infinity. A typical JSON encoder would fail // with an error, but a logging library wants the data to get thru so we // make a tradeoff and store those types as string. @@ -280,21 +316,21 @@ func AppendFloat(dst []byte, val float64, bitSize int) []byte { // AppendFloat32 converts the input float32 to a string and // appends the encoded string to the input byte slice. -func AppendFloat32(dst []byte, val float32) []byte { - return AppendFloat(dst, float64(val), 32) +func (Encoder) AppendFloat32(dst []byte, val float32) []byte { + return appendFloat(dst, float64(val), 32) } // AppendFloats32 encodes the input float32s to json and // appends the encoded string list to the input byte slice. -func AppendFloats32(dst []byte, vals []float32) []byte { +func (Encoder) AppendFloats32(dst []byte, vals []float32) []byte { if len(vals) == 0 { return append(dst, '[', ']') } dst = append(dst, '[') - dst = AppendFloat(dst, float64(vals[0]), 32) + dst = appendFloat(dst, float64(vals[0]), 32) if len(vals) > 1 { for _, val := range vals[1:] { - dst = AppendFloat(append(dst, ','), float64(val), 32) + dst = appendFloat(append(dst, ','), float64(val), 32) } } dst = append(dst, ']') @@ -303,21 +339,21 @@ func AppendFloats32(dst []byte, vals []float32) []byte { // AppendFloat64 converts the input float64 to a string and // appends the encoded string to the input byte slice. -func AppendFloat64(dst []byte, val float64) []byte { - return AppendFloat(dst, val, 64) +func (Encoder) AppendFloat64(dst []byte, val float64) []byte { + return appendFloat(dst, val, 64) } // AppendFloats64 encodes the input float64s to json and // appends the encoded string list to the input byte slice. -func AppendFloats64(dst []byte, vals []float64) []byte { +func (Encoder) AppendFloats64(dst []byte, vals []float64) []byte { if len(vals) == 0 { return append(dst, '[', ']') } dst = append(dst, '[') - dst = AppendFloat(dst, vals[0], 32) + dst = appendFloat(dst, vals[0], 32) if len(vals) > 1 { for _, val := range vals[1:] { - dst = AppendFloat(append(dst, ','), val, 64) + dst = appendFloat(append(dst, ','), val, 64) } } dst = append(dst, ']') @@ -326,17 +362,17 @@ func AppendFloats64(dst []byte, vals []float64) []byte { // AppendInterface marshals the input interface to a string and // appends the encoded string to the input byte slice. -func AppendInterface(dst []byte, i interface{}) []byte { +func (e Encoder) AppendInterface(dst []byte, i interface{}) []byte { marshaled, err := json.Marshal(i) if err != nil { - return AppendString(dst, fmt.Sprintf("marshaling error: %v", err)) + return e.AppendString(dst, fmt.Sprintf("marshaling error: %v", err)) } return append(dst, marshaled...) } // AppendObjectData takes in an object that is already in a byte array // and adds it to the dst. -func AppendObjectData(dst []byte, o []byte) []byte { +func (Encoder) 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 @@ -350,17 +386,17 @@ func AppendObjectData(dst []byte, o []byte) []byte { } // AppendIPAddr adds IPv4 or IPv6 address to dst. -func AppendIPAddr(dst []byte, ip net.IP) []byte { - return AppendString(dst, ip.String()) +func (e Encoder) AppendIPAddr(dst []byte, ip net.IP) []byte { + return e.AppendString(dst, ip.String()) } // AppendIPPrefix adds IPv4 or IPv6 Prefix (address & mask) to dst. -func AppendIPPrefix(dst []byte, pfx net.IPNet) []byte { - return AppendString(dst, pfx.String()) +func (e Encoder) AppendIPPrefix(dst []byte, pfx net.IPNet) []byte { + return e.AppendString(dst, pfx.String()) } // AppendMACAddr adds MAC address to dst. -func AppendMACAddr(dst []byte, ha net.HardwareAddr) []byte { - return AppendString(dst, ha.String()) +func (e Encoder) AppendMACAddr(dst []byte, ha net.HardwareAddr) []byte { + return e.AppendString(dst, ha.String()) } diff --git a/internal/json/types_test.go b/internal/json/types_test.go index 719649d..f396299 100644 --- a/internal/json/types_test.go +++ b/internal/json/types_test.go @@ -9,18 +9,18 @@ import ( func TestAppendType(t *testing.T) { w := map[string]func(interface{}) []byte{ - "AppendInt": func(v interface{}) []byte { return AppendInt([]byte{}, v.(int)) }, - "AppendInt8": func(v interface{}) []byte { return AppendInt8([]byte{}, v.(int8)) }, - "AppendInt16": func(v interface{}) []byte { return AppendInt16([]byte{}, v.(int16)) }, - "AppendInt32": func(v interface{}) []byte { return AppendInt32([]byte{}, v.(int32)) }, - "AppendInt64": func(v interface{}) []byte { return AppendInt64([]byte{}, v.(int64)) }, - "AppendUint": func(v interface{}) []byte { return AppendUint([]byte{}, v.(uint)) }, - "AppendUint8": func(v interface{}) []byte { return AppendUint8([]byte{}, v.(uint8)) }, - "AppendUint16": func(v interface{}) []byte { return AppendUint16([]byte{}, v.(uint16)) }, - "AppendUint32": func(v interface{}) []byte { return AppendUint32([]byte{}, v.(uint32)) }, - "AppendUint64": func(v interface{}) []byte { return AppendUint64([]byte{}, v.(uint64)) }, - "AppendFloat32": func(v interface{}) []byte { return AppendFloat32([]byte{}, v.(float32)) }, - "AppendFloat64": func(v interface{}) []byte { return AppendFloat64([]byte{}, v.(float64)) }, + "AppendInt": func(v interface{}) []byte { return enc.AppendInt([]byte{}, v.(int)) }, + "AppendInt8": func(v interface{}) []byte { return enc.AppendInt8([]byte{}, v.(int8)) }, + "AppendInt16": func(v interface{}) []byte { return enc.AppendInt16([]byte{}, v.(int16)) }, + "AppendInt32": func(v interface{}) []byte { return enc.AppendInt32([]byte{}, v.(int32)) }, + "AppendInt64": func(v interface{}) []byte { return enc.AppendInt64([]byte{}, v.(int64)) }, + "AppendUint": func(v interface{}) []byte { return enc.AppendUint([]byte{}, v.(uint)) }, + "AppendUint8": func(v interface{}) []byte { return enc.AppendUint8([]byte{}, v.(uint8)) }, + "AppendUint16": func(v interface{}) []byte { return enc.AppendUint16([]byte{}, v.(uint16)) }, + "AppendUint32": func(v interface{}) []byte { return enc.AppendUint32([]byte{}, v.(uint32)) }, + "AppendUint64": func(v interface{}) []byte { return enc.AppendUint64([]byte{}, v.(uint64)) }, + "AppendFloat32": func(v interface{}) []byte { return enc.AppendFloat32([]byte{}, v.(float32)) }, + "AppendFloat64": func(v interface{}) []byte { return enc.AppendFloat64([]byte{}, v.(float64)) }, } tests := []struct { name string @@ -74,7 +74,7 @@ func Test_appendMAC(t *testing.T) { for _, tt := range MACtests { t.Run("MAC", func(t *testing.T) { ha, _ := net.ParseMAC(tt.input) - if got := AppendMACAddr([]byte{}, ha); !reflect.DeepEqual(got, tt.want) { + if got := enc.AppendMACAddr([]byte{}, ha); !reflect.DeepEqual(got, tt.want) { t.Errorf("appendMACAddr() = %s, want %s", got, tt.want) } }) @@ -92,7 +92,7 @@ func Test_appendIP(t *testing.T) { for _, tt := range IPv4tests { t.Run("IPv4", func(t *testing.T) { - if got := AppendIPAddr([]byte{}, tt.input); !reflect.DeepEqual(got, tt.want) { + if got := enc.AppendIPAddr([]byte{}, tt.input); !reflect.DeepEqual(got, tt.want) { t.Errorf("appendIPAddr() = %s, want %s", got, tt.want) } }) @@ -107,7 +107,7 @@ func Test_appendIP(t *testing.T) { } for _, tt := range IPv6tests { t.Run("IPv6", func(t *testing.T) { - if got := AppendIPAddr([]byte{}, tt.input); !reflect.DeepEqual(got, tt.want) { + if got := enc.AppendIPAddr([]byte{}, tt.input); !reflect.DeepEqual(got, tt.want) { t.Errorf("appendIPAddr() = %s, want %s", got, tt.want) } }) @@ -124,7 +124,7 @@ func Test_appendIPPrefix(t *testing.T) { } for _, tt := range IPv4Prefixtests { t.Run("IPv4", func(t *testing.T) { - if got := AppendIPPrefix([]byte{}, tt.input); !reflect.DeepEqual(got, tt.want) { + if got := enc.AppendIPPrefix([]byte{}, tt.input); !reflect.DeepEqual(got, tt.want) { t.Errorf("appendIPPrefix() = %s, want %s", got, tt.want) } }) @@ -141,7 +141,7 @@ func Test_appendIPPrefix(t *testing.T) { } for _, tt := range IPv6Prefixtests { t.Run("IPv6", func(t *testing.T) { - if got := AppendIPPrefix([]byte{}, tt.input); !reflect.DeepEqual(got, tt.want) { + if got := enc.AppendIPPrefix([]byte{}, tt.input); !reflect.DeepEqual(got, tt.want) { t.Errorf("appendIPPrefix() = %s, want %s", got, tt.want) } }) @@ -159,7 +159,7 @@ func Test_appendMac(t *testing.T) { for _, tt := range MACtests { t.Run("MAC", func(t *testing.T) { - if got := AppendMACAddr([]byte{}, tt.input); !reflect.DeepEqual(got, tt.want) { + if got := enc.AppendMACAddr([]byte{}, tt.input); !reflect.DeepEqual(got, tt.want) { t.Errorf("appendMAC() = %s, want %s", got, tt.want) } }) diff --git a/log.go b/log.go index 528ac77..ed7223f 100644 --- a/log.go +++ b/log.go @@ -381,7 +381,7 @@ func (l *Logger) newEvent(level Level, done func(string)) *Event { e.Str(LevelFieldName, level.String()) } if l.context != nil && len(l.context) > 0 { - e.buf = appendObjectData(e.buf, l.context) + e.buf = enc.AppendObjectData(e.buf, l.context) } return e } From 533ee32d5daba4d267593e79581afac50b57773c Mon Sep 17 00:00:00 2001 From: Ravi Raju Date: Thu, 10 May 2018 18:21:30 -0700 Subject: [PATCH 28/73] fix needed after calling encoder via interface (#60) --- internal/cbor/string.go | 2 +- internal/cbor/types.go | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/internal/cbor/string.go b/internal/cbor/string.go index 681640a..ff42afa 100644 --- a/internal/cbor/string.go +++ b/internal/cbor/string.go @@ -45,7 +45,7 @@ func (Encoder) AppendBytes(dst, s []byte) []byte { } // AppendEmbeddedJSON adds a tag and embeds input JSON as such. -func (Encoder) AppendEmbeddedJSON(dst, s []byte) []byte { +func AppendEmbeddedJSON(dst, s []byte) []byte { major := majorTypeTags minor := additionalTypeEmbeddedJSON diff --git a/internal/cbor/types.go b/internal/cbor/types.go index 2a8a716..eb4f697 100644 --- a/internal/cbor/types.go +++ b/internal/cbor/types.go @@ -24,7 +24,9 @@ func (Encoder) AppendEndMarker(dst []byte) []byte { // AppendObjectData takes an object in form of a byte array and appends to dst. func (Encoder) AppendObjectData(dst []byte, o []byte) []byte { - return append(dst, o...) + // BeginMarker is present in the dst, which + // should not be copied when appending to existing data. + return append(dst, o[1:]...) } // AppendArrayStart adds markers to indicate the start of an array. @@ -434,7 +436,7 @@ func (e Encoder) AppendInterface(dst []byte, i interface{}) []byte { if err != nil { return e.AppendString(dst, fmt.Sprintf("marshaling error: %v", err)) } - return e.AppendEmbeddedJSON(dst, marshaled) + return AppendEmbeddedJSON(dst, marshaled) } // AppendIPAddr encodes and inserts an IP Address (IPv4 or IPv6). From b5207c012d2c2d73bec68f517ef281fdad38114b Mon Sep 17 00:00:00 2001 From: Olivier Poitrey Date: Sat, 12 May 2018 22:12:35 -0700 Subject: [PATCH 29/73] Fix type in README (fix #62) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 552146d..6e402e5 100644 --- a/README.md +++ b/README.md @@ -269,7 +269,7 @@ log.Info(). Str("foo", "bar"). Dict("dict", zerolog.Dict(). Str("bar", "baz"). - Int("n", 1) + Int("n", 1), ).Msg("hello world") // Output: {"level":"info","time":1494567715,"foo":"bar","dict":{"bar":"baz","n":1},"message":"hello world"} From a025d45231e5359143134c3c73d0b0f3975ed705 Mon Sep 17 00:00:00 2001 From: Ravi Raju Date: Wed, 16 May 2018 18:42:33 -0700 Subject: [PATCH 30/73] EmbedObject() API and knob to change caller frames (#66) * Fix for a bug in cbor decodeFloat * Add EmbedObject() method * knob to change the depth of caller frames to skip * removed EmbedObj() for array - since it is same as Object() --- binary_test.go | 45 ++++++++++++++++++++++++++++++++++++++++++++ context.go | 12 +++++++++++- event.go | 11 ++++++++++- globals.go | 3 +++ journald/journald.go | 3 +++ log_example_test.go | 44 +++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 116 insertions(+), 2 deletions(-) diff --git a/binary_test.go b/binary_test.go index f878532..d02e3f9 100644 --- a/binary_test.go +++ b/binary_test.go @@ -283,6 +283,21 @@ func ExampleEvent_Object() { // Output: {"foo":"bar","user":{"name":"John","age":35,"created":"0001-01-01T00:00:00Z"},"message":"hello world"} } +func ExampleEvent_EmbedObject() { + price := Price{val: 6449, prec: 2, unit: "$"} + + dst := bytes.Buffer{} + log := New(&dst) + + log.Log(). + Str("foo", "bar"). + EmbedObject(price). + Msg("hello world") + + fmt.Println(decodeIfBinaryToString(dst.Bytes())) + // Output: {"foo":"bar","price":"$64.49","message":"hello world"} +} + func ExampleEvent_Interface() { dst := bytes.Buffer{} log := New(&dst) @@ -384,6 +399,36 @@ func ExampleContext_Array_object() { // 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"} } +type Price struct { + val uint64 + prec int + unit string +} + +func (p Price) MarshalZerologObject(e *Event) { + denom := uint64(1) + for i := 0; i < p.prec; i++ { + denom *= 10 + } + result := []byte(p.unit) + result = append(result, fmt.Sprintf("%d.%d", p.val/denom, p.val%denom)...) + e.Str("price", string(result)) +} + +func ExampleContext_EmbedObject() { + price := Price{val: 6449, prec: 2, unit: "$"} + + dst := bytes.Buffer{} + log := New(&dst).With(). + Str("foo", "bar"). + EmbedObject(price). + Logger() + + log.Log().Msg("hello world") + + fmt.Println(decodeIfBinaryToString(dst.Bytes())) + // Output: {"foo":"bar","price":"$64.49","message":"hello world"} +} func ExampleContext_Object() { // User implements LogObjectMarshaler u := User{"John", 35, time.Time{}} diff --git a/context.go b/context.go index d9f9c22..fd26d5d 100644 --- a/context.go +++ b/context.go @@ -59,6 +59,15 @@ func (c Context) Object(key string, obj LogObjectMarshaler) Context { return c } +// EmbedObject marshals and Embeds an object that implement the LogObjectMarshaler interface. +func (c Context) EmbedObject(obj LogObjectMarshaler) Context { + e := newEvent(levelWriterAdapter{ioutil.Discard}, 0) + e.EmbedObject(obj) + c.l.context = enc.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 = enc.AppendString(enc.AppendKey(c.l.context, key), val) @@ -321,7 +330,8 @@ func (c Context) Interface(key string, i interface{}) Context { type callerHook struct{} func (ch callerHook) Run(e *Event, level Level, msg string) { - e.caller(4) + //Two extra frames to skip (added by hook infra). + e.caller(CallerSkipFrameCount+2) } var ch = callerHook{} diff --git a/event.go b/event.go index 85e90ee..1e191a3 100644 --- a/event.go +++ b/event.go @@ -179,6 +179,15 @@ func (e *Event) Object(key string, obj LogObjectMarshaler) *Event { return e } +// Object marshals an object that implement the LogObjectMarshaler interface. +func (e *Event) EmbedObject(obj LogObjectMarshaler) *Event { + if e == nil { + return e + } + obj.MarshalZerologObject(e) + return e +} + // Str adds the field key with val as a string to the *Event context. func (e *Event) Str(key, val string) *Event { if e == nil { @@ -581,7 +590,7 @@ func (e *Event) Interface(key string, i interface{}) *Event { // Caller adds the file:line of the caller with the zerolog.CallerFieldName key. func (e *Event) Caller() *Event { - return e.caller(2) + return e.caller(CallerSkipFrameCount) } func (e *Event) caller(skip int) *Event { diff --git a/globals.go b/globals.go index 5c2aa9c..1a7dbcf 100644 --- a/globals.go +++ b/globals.go @@ -19,6 +19,9 @@ var ( // CallerFieldName is the field name used for caller field. CallerFieldName = "caller" + // CallerSkipFrameCount is the number of stack frames to skip to find the caller. + CallerSkipFrameCount = 2 + // TimeFieldFormat defines the time format of the Time field type. // If set to an empty string, the time is formatted as an UNIX timestamp // as integer. diff --git a/journald/journald.go b/journald/journald.go index fecc99b..dde4e3e 100644 --- a/journald/journald.go +++ b/journald/journald.go @@ -1,5 +1,8 @@ // +build !windows +// Package journald provides a io.Writer to send the logs +// to journalD component of systemd. + package journald // This file provides a zerolog writer so that logs printed diff --git a/log_example_test.go b/log_example_test.go index 5d541b3..56ee9c8 100644 --- a/log_example_test.go +++ b/log_example_test.go @@ -4,6 +4,7 @@ package zerolog_test import ( "errors" + "fmt" stdlog "log" "net" "os" @@ -195,6 +196,22 @@ func (u User) MarshalZerologObject(e *zerolog.Event) { Time("created", u.Created) } +type Price struct { + val uint64 + prec int + unit string +} + +func (p Price) MarshalZerologObject(e *zerolog.Event) { + denom := uint64(1) + for i := 0; i < p.prec; i++ { + denom *= 10 + } + result := []byte(p.unit) + result = append(result, fmt.Sprintf("%d.%d", p.val/denom, p.val%denom)...) + e.Str("price", string(result)) +} + type Users []User func (uu Users) MarshalZerologArray(a *zerolog.Array) { @@ -248,6 +265,19 @@ func ExampleEvent_Object() { // Output: {"foo":"bar","user":{"name":"John","age":35,"created":"0001-01-01T00:00:00Z"},"message":"hello world"} } +func ExampleEvent_EmbedObject() { + log := zerolog.New(os.Stdout) + + price := Price{val: 6449, prec: 2, unit: "$"} + + log.Log(). + Str("foo", "bar"). + EmbedObject(price). + Msg("hello world") + + // Output: {"foo":"bar","price":"$64.49","message":"hello world"} +} + func ExampleEvent_Interface() { log := zerolog.New(os.Stdout) @@ -351,6 +381,20 @@ func ExampleContext_Object() { // Output: {"foo":"bar","user":{"name":"John","age":35,"created":"0001-01-01T00:00:00Z"},"message":"hello world"} } +func ExampleContext_EmbedObject() { + + price := Price{val: 6449, prec: 2, unit: "$"} + + log := zerolog.New(os.Stdout).With(). + Str("foo", "bar"). + EmbedObject(price). + Logger() + + log.Log().Msg("hello world") + + // Output: {"foo":"bar","price":"$64.49","message":"hello world"} +} + func ExampleContext_Interface() { obj := struct { Name string `json:"name"` From fb469685aa8f19d742eff3f56a0bd51a23265829 Mon Sep 17 00:00:00 2001 From: Olivier Poitrey Date: Thu, 17 May 2018 16:09:39 -0700 Subject: [PATCH 31/73] Fix ConsoleWriter when zerolog is configured to use UNIX timestamp --- console.go | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/console.go b/console.go index 69e08f5..e9a0d84 100644 --- a/console.go +++ b/console.go @@ -9,6 +9,7 @@ import ( "strconv" "strings" "sync" + "time" ) const ( @@ -57,7 +58,7 @@ func (w ConsoleWriter) Write(p []byte) (n int, err error) { level = strings.ToUpper(l)[0:4] } fmt.Fprintf(buf, "%s |%s| %s", - colorize(event[TimestampFieldName], cDarkGray, !w.NoColor), + colorize(formatTime(event[TimestampFieldName]), cDarkGray, !w.NoColor), colorize(level, lvlColor, !w.NoColor), colorize(event[MessageFieldName], cReset, !w.NoColor)) fields := make([]string, 0, len(event)) @@ -95,6 +96,17 @@ func (w ConsoleWriter) Write(p []byte) (n int, err error) { return } +func formatTime(t interface{}) string { + switch t := t.(type) { + case string: + return t + case json.Number: + u, _ := t.Int64() + return time.Unix(u, 0).Format(time.RFC3339) + } + return "" +} + func colorize(s interface{}, color int, enabled bool) string { if !enabled { return fmt.Sprintf("%v", s) From c62533f76195c0f6c2b1c8985c3590895febfe57 Mon Sep 17 00:00:00 2001 From: Olivier Poitrey Date: Thu, 17 May 2018 16:35:57 -0700 Subject: [PATCH 32/73] Fix ConsoleWriter test --- console.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/console.go b/console.go index e9a0d84..c957571 100644 --- a/console.go +++ b/console.go @@ -104,7 +104,7 @@ func formatTime(t interface{}) string { u, _ := t.Int64() return time.Unix(u, 0).Format(time.RFC3339) } - return "" + return "" } func colorize(s interface{}, color int, enabled bool) string { From ea197802eb218ea675a8286a77c13e132aa9a21c Mon Sep 17 00:00:00 2001 From: Olivier Poitrey Date: Thu, 17 May 2018 16:48:29 -0700 Subject: [PATCH 33/73] Fix comments --- array.go | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/array.go b/array.go index 47402ec..b876033 100644 --- a/array.go +++ b/array.go @@ -43,7 +43,7 @@ func (a *Array) write(dst []byte) []byte { } // Object marshals an object that implement the LogObjectMarshaler -// interface and enc.Append it to the array. +// interface and Append it to the array. func (a *Array) Object(obj LogObjectMarshaler) *Array { e := Dict() obj.MarshalZerologObject(e) @@ -53,121 +53,121 @@ func (a *Array) Object(obj LogObjectMarshaler) *Array { return a } -// Str enc.Append the val as a string to the array. +// Str Append the val as a string to the array. func (a *Array) Str(val string) *Array { a.buf = enc.AppendString(enc.AppendArrayDelim(a.buf), val) return a } -// Bytes enc.Append the val as a string to the array. +// Bytes Append the val as a string to the array. func (a *Array) Bytes(val []byte) *Array { a.buf = enc.AppendBytes(enc.AppendArrayDelim(a.buf), val) return a } -// Hex enc.Append the val as a hex string to the array. +// Hex Append the val as a hex string to the array. func (a *Array) Hex(val []byte) *Array { a.buf = enc.AppendHex(enc.AppendArrayDelim(a.buf), val) return a } -// Err enc.Append the err as a string to the array. +// Err Append the err as a string to the array. func (a *Array) Err(err error) *Array { a.buf = enc.AppendError(enc.AppendArrayDelim(a.buf), err) return a } -// Bool enc.Append the val as a bool to the array. +// Bool Append the val as a bool to the array. func (a *Array) Bool(b bool) *Array { a.buf = enc.AppendBool(enc.AppendArrayDelim(a.buf), b) return a } -// Int enc.Append i as a int to the array. +// Int Append i as a int to the array. func (a *Array) Int(i int) *Array { a.buf = enc.AppendInt(enc.AppendArrayDelim(a.buf), i) return a } -// Int8 enc.Append i as a int8 to the array. +// Int8 Append i as a int8 to the array. func (a *Array) Int8(i int8) *Array { a.buf = enc.AppendInt8(enc.AppendArrayDelim(a.buf), i) return a } -// Int16 enc.Append i as a int16 to the array. +// Int16 Append i as a int16 to the array. func (a *Array) Int16(i int16) *Array { a.buf = enc.AppendInt16(enc.AppendArrayDelim(a.buf), i) return a } -// Int32 enc.Append i as a int32 to the array. +// Int32 Append i as a int32 to the array. func (a *Array) Int32(i int32) *Array { a.buf = enc.AppendInt32(enc.AppendArrayDelim(a.buf), i) return a } -// Int64 enc.Append i as a int64 to the array. +// Int64 Append i as a int64 to the array. func (a *Array) Int64(i int64) *Array { a.buf = enc.AppendInt64(enc.AppendArrayDelim(a.buf), i) return a } -// Uint enc.Append i as a uint to the array. +// Uint Append i as a uint to the array. func (a *Array) Uint(i uint) *Array { a.buf = enc.AppendUint(enc.AppendArrayDelim(a.buf), i) return a } -// Uint8 enc.Append i as a uint8 to the array. +// Uint8 Append i as a uint8 to the array. func (a *Array) Uint8(i uint8) *Array { a.buf = enc.AppendUint8(enc.AppendArrayDelim(a.buf), i) return a } -// Uint16 enc.Append i as a uint16 to the array. +// Uint16 Append i as a uint16 to the array. func (a *Array) Uint16(i uint16) *Array { a.buf = enc.AppendUint16(enc.AppendArrayDelim(a.buf), i) return a } -// Uint32 enc.Append i as a uint32 to the array. +// Uint32 Append i as a uint32 to the array. func (a *Array) Uint32(i uint32) *Array { a.buf = enc.AppendUint32(enc.AppendArrayDelim(a.buf), i) return a } -// Uint64 enc.Append i as a uint64 to the array. +// Uint64 Append i as a uint64 to the array. func (a *Array) Uint64(i uint64) *Array { a.buf = enc.AppendUint64(enc.AppendArrayDelim(a.buf), i) return a } -// Float32 enc.Append f as a float32 to the array. +// Float32 Append f as a float32 to the array. func (a *Array) Float32(f float32) *Array { a.buf = enc.AppendFloat32(enc.AppendArrayDelim(a.buf), f) return a } -// Float64 enc.Append f as a float64 to the array. +// Float64 Append f as a float64 to the array. func (a *Array) Float64(f float64) *Array { a.buf = enc.AppendFloat64(enc.AppendArrayDelim(a.buf), f) return a } -// Time enc.Append t formated as string using zerolog.TimeFieldFormat. +// Time Append t formated as string using zerolog.TimeFieldFormat. func (a *Array) Time(t time.Time) *Array { a.buf = enc.AppendTime(enc.AppendArrayDelim(a.buf), t, TimeFieldFormat) return a } -// Dur enc.Append d to the array. +// Dur Append d to the array. func (a *Array) Dur(d time.Duration) *Array { a.buf = enc.AppendDuration(enc.AppendArrayDelim(a.buf), d, DurationFieldUnit, DurationFieldInteger) return a } -// Interface enc.Append i marshaled using reflection. +// Interface Append i marshaled using reflection. func (a *Array) Interface(i interface{}) *Array { if obj, ok := i.(LogObjectMarshaler); ok { return a.Object(obj) From 80d6806aae988e40f96d74789a25df9c3a6616a5 Mon Sep 17 00:00:00 2001 From: Olivier Poitrey Date: Mon, 21 May 2018 11:01:34 -0700 Subject: [PATCH 34/73] Fix comments (bis) --- array.go | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/array.go b/array.go index b876033..4399c48 100644 --- a/array.go +++ b/array.go @@ -43,7 +43,7 @@ func (a *Array) write(dst []byte) []byte { } // Object marshals an object that implement the LogObjectMarshaler -// interface and Append it to the array. +// interface and append append it to the array. func (a *Array) Object(obj LogObjectMarshaler) *Array { e := Dict() obj.MarshalZerologObject(e) @@ -53,121 +53,121 @@ func (a *Array) Object(obj LogObjectMarshaler) *Array { return a } -// Str Append the val as a string to the array. +// Str append append the val as a string to the array. func (a *Array) Str(val string) *Array { a.buf = enc.AppendString(enc.AppendArrayDelim(a.buf), val) return a } -// Bytes Append the val as a string to the array. +// Bytes append append the val as a string to the array. func (a *Array) Bytes(val []byte) *Array { a.buf = enc.AppendBytes(enc.AppendArrayDelim(a.buf), val) return a } -// Hex Append the val as a hex string to the array. +// Hex append append the val as a hex string to the array. func (a *Array) Hex(val []byte) *Array { a.buf = enc.AppendHex(enc.AppendArrayDelim(a.buf), val) return a } -// Err Append the err as a string to the array. +// Err append append the err as a string to the array. func (a *Array) Err(err error) *Array { a.buf = enc.AppendError(enc.AppendArrayDelim(a.buf), err) return a } -// Bool Append the val as a bool to the array. +// Bool append append the val as a bool to the array. func (a *Array) Bool(b bool) *Array { a.buf = enc.AppendBool(enc.AppendArrayDelim(a.buf), b) return a } -// Int Append i as a int to the array. +// Int append append i as a int to the array. func (a *Array) Int(i int) *Array { a.buf = enc.AppendInt(enc.AppendArrayDelim(a.buf), i) return a } -// Int8 Append i as a int8 to the array. +// Int8 append append i as a int8 to the array. func (a *Array) Int8(i int8) *Array { a.buf = enc.AppendInt8(enc.AppendArrayDelim(a.buf), i) return a } -// Int16 Append i as a int16 to the array. +// Int16 append append i as a int16 to the array. func (a *Array) Int16(i int16) *Array { a.buf = enc.AppendInt16(enc.AppendArrayDelim(a.buf), i) return a } -// Int32 Append i as a int32 to the array. +// Int32 append append i as a int32 to the array. func (a *Array) Int32(i int32) *Array { a.buf = enc.AppendInt32(enc.AppendArrayDelim(a.buf), i) return a } -// Int64 Append i as a int64 to the array. +// Int64 append append i as a int64 to the array. func (a *Array) Int64(i int64) *Array { a.buf = enc.AppendInt64(enc.AppendArrayDelim(a.buf), i) return a } -// Uint Append i as a uint to the array. +// Uint append append i as a uint to the array. func (a *Array) Uint(i uint) *Array { a.buf = enc.AppendUint(enc.AppendArrayDelim(a.buf), i) return a } -// Uint8 Append i as a uint8 to the array. +// Uint8 append append i as a uint8 to the array. func (a *Array) Uint8(i uint8) *Array { a.buf = enc.AppendUint8(enc.AppendArrayDelim(a.buf), i) return a } -// Uint16 Append i as a uint16 to the array. +// Uint16 append append i as a uint16 to the array. func (a *Array) Uint16(i uint16) *Array { a.buf = enc.AppendUint16(enc.AppendArrayDelim(a.buf), i) return a } -// Uint32 Append i as a uint32 to the array. +// Uint32 append append i as a uint32 to the array. func (a *Array) Uint32(i uint32) *Array { a.buf = enc.AppendUint32(enc.AppendArrayDelim(a.buf), i) return a } -// Uint64 Append i as a uint64 to the array. +// Uint64 append append i as a uint64 to the array. func (a *Array) Uint64(i uint64) *Array { a.buf = enc.AppendUint64(enc.AppendArrayDelim(a.buf), i) return a } -// Float32 Append f as a float32 to the array. +// Float32 append append f as a float32 to the array. func (a *Array) Float32(f float32) *Array { a.buf = enc.AppendFloat32(enc.AppendArrayDelim(a.buf), f) return a } -// Float64 Append f as a float64 to the array. +// Float64 append append f as a float64 to the array. func (a *Array) Float64(f float64) *Array { a.buf = enc.AppendFloat64(enc.AppendArrayDelim(a.buf), f) return a } -// Time Append t formated as string using zerolog.TimeFieldFormat. +// Time append append t formated as string using zerolog.TimeFieldFormat. func (a *Array) Time(t time.Time) *Array { a.buf = enc.AppendTime(enc.AppendArrayDelim(a.buf), t, TimeFieldFormat) return a } -// Dur Append d to the array. +// Dur append append d to the array. func (a *Array) Dur(d time.Duration) *Array { a.buf = enc.AppendDuration(enc.AppendArrayDelim(a.buf), d, DurationFieldUnit, DurationFieldInteger) return a } -// Interface Append i marshaled using reflection. +// Interface append append i marshaled using reflection. func (a *Array) Interface(i interface{}) *Array { if obj, ok := i.(LogObjectMarshaler); ok { return a.Object(obj) From 64faaa6980a691db8af9a7c50d448c6dc8c6aedb Mon Sep 17 00:00:00 2001 From: Olivier Poitrey Date: Wed, 23 May 2018 09:50:46 -0700 Subject: [PATCH 35/73] Add go.mod file --- go.mod | 1 + 1 file changed, 1 insertion(+) create mode 100644 go.mod diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..ed79427 --- /dev/null +++ b/go.mod @@ -0,0 +1 @@ +module github.com/rs/zerolog From 77db4b4f350e31be66a57c332acb7721cf9ff9bb Mon Sep 17 00:00:00 2001 From: Olivier Poitrey Date: Thu, 24 May 2018 18:50:57 -0700 Subject: [PATCH 36/73] Embed the diode lib to avoid test dependencies This commit introduces a breaking change in the diode API in order to hide the diodes package interface. This removes a good number of dependencies introduced by the test framework used by the diodes package. --- diode/diode.go | 16 ++-- diode/diode_example_test.go | 6 +- diode/diode_test.go | 11 +-- diode/internal/diodes/README | 1 + diode/internal/diodes/many_to_one.go | 130 +++++++++++++++++++++++++++ diode/internal/diodes/one_to_one.go | 129 ++++++++++++++++++++++++++ diode/internal/diodes/poller.go | 80 +++++++++++++++++ diode/internal/diodes/waiter.go | 83 +++++++++++++++++ 8 files changed, 438 insertions(+), 18 deletions(-) create mode 100644 diode/internal/diodes/README create mode 100644 diode/internal/diodes/many_to_one.go create mode 100644 diode/internal/diodes/one_to_one.go create mode 100644 diode/internal/diodes/poller.go create mode 100644 diode/internal/diodes/waiter.go diff --git a/diode/diode.go b/diode/diode.go index 377a523..a720f0b 100644 --- a/diode/diode.go +++ b/diode/diode.go @@ -8,7 +8,7 @@ import ( "sync" "time" - diodes "code.cloudfoundry.org/go-diodes" + "github.com/rs/zerolog/diode/internal/diodes" ) var bufPool = &sync.Pool{ @@ -17,6 +17,8 @@ var bufPool = &sync.Pool{ }, } +type Alerter func(missed int) + // Writer is a io.Writer wrapper that uses a diode to make Write lock-free, // non-blocking and thread safe. type Writer struct { @@ -33,19 +35,19 @@ type Writer struct { // // Use a diode.Writer when // -// d := diodes.NewManyToOne(1000, diodes.AlertFunc(func(missed int) { +// w := diode.NewWriter(w, 1000, 10 * time.Millisecond, func(missed int) { // log.Printf("Dropped %d messages", missed) -// })) -// w := diode.NewWriter(w, d, 10 * time.Millisecond) +// }) // log := zerolog.New(w) // // See code.cloudfoundry.org/go-diodes for more info on diode. -func NewWriter(w io.Writer, manyToOneDiode *diodes.ManyToOne, poolInterval time.Duration) Writer { +func NewWriter(w io.Writer, size int, poolInterval time.Duration, f Alerter) Writer { ctx, cancel := context.WithCancel(context.Background()) + d := diodes.NewManyToOne(size, diodes.AlertFunc(f)) dw := Writer{ w: w, - d: manyToOneDiode, - p: diodes.NewPoller(manyToOneDiode, + d: d, + p: diodes.NewPoller(d, diodes.WithPollingInterval(poolInterval), diodes.WithPollingContext(ctx)), c: cancel, diff --git a/diode/diode_example_test.go b/diode/diode_example_test.go index e04f36b..a097c57 100644 --- a/diode/diode_example_test.go +++ b/diode/diode_example_test.go @@ -7,16 +7,14 @@ import ( "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) { + w := diode.NewWriter(os.Stdout, 1000, 10*time.Millisecond, 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") diff --git a/diode/diode_test.go b/diode/diode_test.go index 31df653..6171cb4 100644 --- a/diode/diode_test.go +++ b/diode/diode_test.go @@ -9,18 +9,16 @@ import ( "testing" "time" - diodes "code.cloudfoundry.org/go-diodes" "github.com/rs/zerolog" "github.com/rs/zerolog/diode" "github.com/rs/zerolog/internal/cbor" ) func TestNewWriter(t *testing.T) { - d := diodes.NewManyToOne(1000, diodes.AlertFunc(func(missed int) { - fmt.Printf("Dropped %d messages\n", missed) - })) buf := bytes.Buffer{} - w := diode.NewWriter(&buf, d, 10*time.Millisecond) + w := diode.NewWriter(&buf, 1000, 10*time.Millisecond, func(missed int) { + fmt.Printf("Dropped %d messages\n", missed) + }) log := zerolog.New(w) log.Print("test") @@ -35,8 +33,7 @@ func TestNewWriter(t *testing.T) { func Benchmark(b *testing.B) { log.SetOutput(ioutil.Discard) defer log.SetOutput(os.Stderr) - d := diodes.NewManyToOne(100000, nil) - w := diode.NewWriter(ioutil.Discard, d, 10*time.Millisecond) + w := diode.NewWriter(ioutil.Discard, 100000, 10*time.Millisecond, nil) log := zerolog.New(w) defer w.Close() diff --git a/diode/internal/diodes/README b/diode/internal/diodes/README new file mode 100644 index 0000000..6c4ec5f --- /dev/null +++ b/diode/internal/diodes/README @@ -0,0 +1 @@ +Copied from https://github.com/cloudfoundry/go-diodes to avoid test dependencies. diff --git a/diode/internal/diodes/many_to_one.go b/diode/internal/diodes/many_to_one.go new file mode 100644 index 0000000..0f562f7 --- /dev/null +++ b/diode/internal/diodes/many_to_one.go @@ -0,0 +1,130 @@ +package diodes + +import ( + "log" + "sync/atomic" + "unsafe" +) + +// ManyToOne diode is optimal for many writers (go-routines B-n) and a single +// reader (go-routine A). It is not thread safe for multiple readers. +type ManyToOne struct { + buffer []unsafe.Pointer + writeIndex uint64 + readIndex uint64 + alerter Alerter +} + +// NewManyToOne creates a new diode (ring buffer). The ManyToOne diode +// is optimzed for many writers (on go-routines B-n) and a single reader +// (on go-routine A). The alerter is invoked on the read's go-routine. It is +// called when it notices that the writer go-routine has passed it and wrote +// over data. A nil can be used to ignore alerts. +func NewManyToOne(size int, alerter Alerter) *ManyToOne { + if alerter == nil { + alerter = AlertFunc(func(int) {}) + } + + d := &ManyToOne{ + buffer: make([]unsafe.Pointer, size), + alerter: alerter, + } + + // Start write index at the value before 0 + // to allow the first write to use AddUint64 + // and still have a beginning index of 0 + d.writeIndex = ^d.writeIndex + return d +} + +// Set sets the data in the next slot of the ring buffer. +func (d *ManyToOne) Set(data GenericDataType) { + for { + writeIndex := atomic.AddUint64(&d.writeIndex, 1) + idx := writeIndex % uint64(len(d.buffer)) + old := atomic.LoadPointer(&d.buffer[idx]) + + if old != nil && + (*bucket)(old) != nil && + (*bucket)(old).seq > writeIndex-uint64(len(d.buffer)) { + log.Println("Diode set collision: consider using a larger diode") + continue + } + + newBucket := &bucket{ + data: data, + seq: writeIndex, + } + + if !atomic.CompareAndSwapPointer(&d.buffer[idx], old, unsafe.Pointer(newBucket)) { + log.Println("Diode set collision: consider using a larger diode") + continue + } + + return + } +} + +// TryNext will attempt to read from the next slot of the ring buffer. +// If there is not data available, it will return (nil, false). +func (d *ManyToOne) TryNext() (data GenericDataType, ok bool) { + // Read a value from the ring buffer based on the readIndex. + idx := d.readIndex % uint64(len(d.buffer)) + result := (*bucket)(atomic.SwapPointer(&d.buffer[idx], nil)) + + // When the result is nil that means the writer has not had the + // opportunity to write a value into the diode. This value must be ignored + // and the read head must not increment. + if result == nil { + return nil, false + } + + // When the seq value is less than the current read index that means a + // value was read from idx that was previously written but has since has + // been dropped. This value must be ignored and the read head must not + // increment. + // + // The simulation for this scenario assumes the fast forward occurred as + // detailed below. + // + // 5. The reader reads again getting seq 5. It then reads again expecting + // seq 6 but gets seq 2. This is a read of a stale value that was + // effectively "dropped" so the read fails and the read head stays put. + // `| 4 | 5 | 2 | 3 |` r: 7, w: 6 + // + if result.seq < d.readIndex { + return nil, false + } + + // When the seq value is greater than the current read index that means a + // value was read from idx that overwrote the value that was expected to + // be at this idx. This happens when the writer has lapped the reader. The + // reader needs to catch up to the writer so it moves its write head to + // the new seq, effectively dropping the messages that were not read in + // between the two values. + // + // Here is a simulation of this scenario: + // + // 1. Both the read and write heads start at 0. + // `| nil | nil | nil | nil |` r: 0, w: 0 + // 2. The writer fills the buffer. + // `| 0 | 1 | 2 | 3 |` r: 0, w: 4 + // 3. The writer laps the read head. + // `| 4 | 5 | 2 | 3 |` r: 0, w: 6 + // 4. The reader reads the first value, expecting a seq of 0 but reads 4, + // this forces the reader to fast forward to 5. + // `| 4 | 5 | 2 | 3 |` r: 5, w: 6 + // + if result.seq > d.readIndex { + dropped := result.seq - d.readIndex + d.readIndex = result.seq + d.alerter.Alert(int(dropped)) + } + + // Only increment read index if a regular read occurred (where seq was + // equal to readIndex) or a value was read that caused a fast forward + // (where seq was greater than readIndex). + // + d.readIndex++ + return result.data, true +} diff --git a/diode/internal/diodes/one_to_one.go b/diode/internal/diodes/one_to_one.go new file mode 100644 index 0000000..aaf66d1 --- /dev/null +++ b/diode/internal/diodes/one_to_one.go @@ -0,0 +1,129 @@ +package diodes + +import ( + "sync/atomic" + "unsafe" +) + +// GenericDataType is the data type the diodes operate on. +type GenericDataType unsafe.Pointer + +// Alerter is used to report how many values were overwritten since the +// last write. +type Alerter interface { + Alert(missed int) +} + +// AlertFunc type is an adapter to allow the use of ordinary functions as +// Alert handlers. +type AlertFunc func(missed int) + +// Alert calls f(missed) +func (f AlertFunc) Alert(missed int) { + f(missed) +} + +type bucket struct { + data GenericDataType + seq uint64 // seq is the recorded write index at the time of writing +} + +// OneToOne diode is meant to be used by a single reader and a single writer. +// It is not thread safe if used otherwise. +type OneToOne struct { + buffer []unsafe.Pointer + writeIndex uint64 + readIndex uint64 + alerter Alerter +} + +// NewOneToOne creates a new diode is meant to be used by a single reader and +// a single writer. The alerter is invoked on the read's go-routine. It is +// called when it notices that the writer go-routine has passed it and wrote +// over data. A nil can be used to ignore alerts. +func NewOneToOne(size int, alerter Alerter) *OneToOne { + if alerter == nil { + alerter = AlertFunc(func(int) {}) + } + + return &OneToOne{ + buffer: make([]unsafe.Pointer, size), + alerter: alerter, + } +} + +// Set sets the data in the next slot of the ring buffer. +func (d *OneToOne) Set(data GenericDataType) { + idx := d.writeIndex % uint64(len(d.buffer)) + + newBucket := &bucket{ + data: data, + seq: d.writeIndex, + } + d.writeIndex++ + + atomic.StorePointer(&d.buffer[idx], unsafe.Pointer(newBucket)) +} + +// TryNext will attempt to read from the next slot of the ring buffer. +// If there is no data available, it will return (nil, false). +func (d *OneToOne) TryNext() (data GenericDataType, ok bool) { + // Read a value from the ring buffer based on the readIndex. + idx := d.readIndex % uint64(len(d.buffer)) + result := (*bucket)(atomic.SwapPointer(&d.buffer[idx], nil)) + + // When the result is nil that means the writer has not had the + // opportunity to write a value into the diode. This value must be ignored + // and the read head must not increment. + if result == nil { + return nil, false + } + + // When the seq value is less than the current read index that means a + // value was read from idx that was previously written but has since has + // been dropped. This value must be ignored and the read head must not + // increment. + // + // The simulation for this scenario assumes the fast forward occurred as + // detailed below. + // + // 5. The reader reads again getting seq 5. It then reads again expecting + // seq 6 but gets seq 2. This is a read of a stale value that was + // effectively "dropped" so the read fails and the read head stays put. + // `| 4 | 5 | 2 | 3 |` r: 7, w: 6 + // + if result.seq < d.readIndex { + return nil, false + } + + // When the seq value is greater than the current read index that means a + // value was read from idx that overwrote the value that was expected to + // be at this idx. This happens when the writer has lapped the reader. The + // reader needs to catch up to the writer so it moves its write head to + // the new seq, effectively dropping the messages that were not read in + // between the two values. + // + // Here is a simulation of this scenario: + // + // 1. Both the read and write heads start at 0. + // `| nil | nil | nil | nil |` r: 0, w: 0 + // 2. The writer fills the buffer. + // `| 0 | 1 | 2 | 3 |` r: 0, w: 4 + // 3. The writer laps the read head. + // `| 4 | 5 | 2 | 3 |` r: 0, w: 6 + // 4. The reader reads the first value, expecting a seq of 0 but reads 4, + // this forces the reader to fast forward to 5. + // `| 4 | 5 | 2 | 3 |` r: 5, w: 6 + // + if result.seq > d.readIndex { + dropped := result.seq - d.readIndex + d.readIndex = result.seq + d.alerter.Alert(int(dropped)) + } + + // Only increment read index if a regular read occurred (where seq was + // equal to readIndex) or a value was read that caused a fast forward + // (where seq was greater than readIndex). + d.readIndex++ + return result.data, true +} diff --git a/diode/internal/diodes/poller.go b/diode/internal/diodes/poller.go new file mode 100644 index 0000000..d317a23 --- /dev/null +++ b/diode/internal/diodes/poller.go @@ -0,0 +1,80 @@ +package diodes + +import ( + "context" + "time" +) + +// Diode is any implementation of a diode. +type Diode interface { + Set(GenericDataType) + TryNext() (GenericDataType, bool) +} + +// Poller will poll a diode until a value is available. +type Poller struct { + Diode + interval time.Duration + ctx context.Context +} + +// PollerConfigOption can be used to setup the poller. +type PollerConfigOption func(*Poller) + +// WithPollingInterval sets the interval at which the diode is queried +// for new data. The default is 10ms. +func WithPollingInterval(interval time.Duration) PollerConfigOption { + return PollerConfigOption(func(c *Poller) { + c.interval = interval + }) +} + +// WithPollingContext sets the context to cancel any retrieval (Next()). It +// will not change any results for adding data (Set()). Default is +// context.Background(). +func WithPollingContext(ctx context.Context) PollerConfigOption { + return PollerConfigOption(func(c *Poller) { + c.ctx = ctx + }) +} + +// NewPoller returns a new Poller that wraps the given diode. +func NewPoller(d Diode, opts ...PollerConfigOption) *Poller { + p := &Poller{ + Diode: d, + interval: 10 * time.Millisecond, + ctx: context.Background(), + } + + for _, o := range opts { + o(p) + } + + return p +} + +// Next polls the diode until data is available or until the context is done. +// If the context is done, then nil will be returned. +func (p *Poller) Next() GenericDataType { + for { + data, ok := p.Diode.TryNext() + if !ok { + if p.isDone() { + return nil + } + + time.Sleep(p.interval) + continue + } + return data + } +} + +func (p *Poller) isDone() bool { + select { + case <-p.ctx.Done(): + return true + default: + return false + } +} diff --git a/diode/internal/diodes/waiter.go b/diode/internal/diodes/waiter.go new file mode 100644 index 0000000..a3770ff --- /dev/null +++ b/diode/internal/diodes/waiter.go @@ -0,0 +1,83 @@ +package diodes + +import ( + "context" + "sync" +) + +// Waiter will use a conditional mutex to alert the reader to when data is +// available. +type Waiter struct { + Diode + mu sync.Mutex + c *sync.Cond + ctx context.Context +} + +// WaiterConfigOption can be used to setup the waiter. +type WaiterConfigOption func(*Waiter) + +// WithWaiterContext sets the context to cancel any retrieval (Next()). It +// will not change any results for adding data (Set()). Default is +// context.Background(). +func WithWaiterContext(ctx context.Context) WaiterConfigOption { + return WaiterConfigOption(func(c *Waiter) { + c.ctx = ctx + }) +} + +// NewWaiter returns a new Waiter that wraps the given diode. +func NewWaiter(d Diode, opts ...WaiterConfigOption) *Waiter { + w := new(Waiter) + w.Diode = d + w.c = sync.NewCond(&w.mu) + w.ctx = context.Background() + + for _, opt := range opts { + opt(w) + } + + go func() { + <-w.ctx.Done() + w.c.Broadcast() + }() + + return w +} + +// Set invokes the wrapped diode's Set with the given data and uses Broadcast +// to wake up any readers. +func (w *Waiter) Set(data GenericDataType) { + w.Diode.Set(data) + w.c.Broadcast() +} + +// Next returns the next data point on the wrapped diode. If there is not any +// new data, it will Wait for set to be called or the context to be done. +// If the context is done, then nil will be returned. +func (w *Waiter) Next() GenericDataType { + w.mu.Lock() + defer w.mu.Unlock() + + for { + data, ok := w.Diode.TryNext() + if !ok { + if w.isDone() { + return nil + } + + w.c.Wait() + continue + } + return data + } +} + +func (w *Waiter) isDone() bool { + select { + case <-w.ctx.Done(): + return true + default: + return false + } +} From c19f1e5eed202e5e480fb05bc06764c0f92596b8 Mon Sep 17 00:00:00 2001 From: Rafael Passos Date: Fri, 25 May 2018 18:45:33 -0300 Subject: [PATCH 37/73] Diode module Documentation update (#71) * Updated Diode example to match new style * DOC: changed w to wr in assigment to reduce ambiguity --- README.md | 7 +++---- diode/diode.go | 5 +++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 6e402e5..082cd39 100644 --- a/README.md +++ b/README.md @@ -298,10 +298,9 @@ log.Logger = log.With().Str("foo", "bar").Logger() If your writer might be slow or not thread-safe and you need your log producers to never get slowed down by a slow writer, you can use a `diode.Writer` as follow: ```go -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) +wr := diode.NewWriter(os.Stdout, 1000, 10*time.Millisecond, func(missed int) { + fmt.Printf("Logger Dropped %d messages", missed) + }) log := zerolog.New(w) log.Print("test") ``` diff --git a/diode/diode.go b/diode/diode.go index a720f0b..8711ee5 100644 --- a/diode/diode.go +++ b/diode/diode.go @@ -35,10 +35,11 @@ type Writer struct { // // Use a diode.Writer when // -// w := diode.NewWriter(w, 1000, 10 * time.Millisecond, func(missed int) { +// wr := diode.NewWriter(w, 1000, 10 * time.Millisecond, func(missed int) { // log.Printf("Dropped %d messages", missed) // }) -// log := zerolog.New(w) +// log := zerolog.New(wr) +// // // See code.cloudfoundry.org/go-diodes for more info on diode. func NewWriter(w io.Writer, size int, poolInterval time.Duration, f Alerter) Writer { From dabc72c15b2adeeb6abc68bfdb798ebd29ea48a8 Mon Sep 17 00:00:00 2001 From: Pichugin Dmitry Date: Thu, 31 May 2018 20:33:44 +0300 Subject: [PATCH 38/73] fix README (#74) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 082cd39..5c008a6 100644 --- a/README.md +++ b/README.md @@ -243,7 +243,7 @@ logger.Info().Str("foo", "bar").Msg("hello world") ```go sublogger := log.With(). - Str("component": "foo"). + Str("component", "foo"). Logger() sublogger.Info().Msg("hello world") From 1a88fbfdd0e441d7f77f13647adb40548a8bb59b Mon Sep 17 00:00:00 2001 From: Josh Rendek Date: Mon, 4 Jun 2018 01:57:37 -0400 Subject: [PATCH 39/73] Update readme at example for Caller() (#76) * Update readme at example for Caller() --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 5c008a6..c9099b5 100644 --- a/README.md +++ b/README.md @@ -293,6 +293,16 @@ log.Info().Msg("hello world") log.Logger = log.With().Str("foo", "bar").Logger() ``` +### Add file and line number to log + +```go +log.Logger = log.With().Caller().Logger() +log.Info().Msg("hello world") + +// Output: {"level": "info", "message": "hello world", "caller": "/go/src/your_project/some_file:21"} +``` + + ### Thread-safe, lock-free, non-blocking writer If your writer might be slow or not thread-safe and you need your log producers to never get slowed down by a slow writer, you can use a `diode.Writer` as follow: From 1c6d99b45538810b3e120bc5a1c29fc019918225 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Du=C5=A1an=20Kasan?= Date: Mon, 2 Jul 2018 21:46:01 +0200 Subject: [PATCH 40/73] Add custom error serialization support and provide sane defaults (#78) As per https://github.com/rs/zerolog/issues/9 and to offer a different approach from https://github.com/rs/zerolog/pull/11 and https://github.com/rs/zerolog/pull/35 this PR introduces custom error serialization with sane defaults without breaking the existing APIs. This is just a first draft and is missing tests. Also, a bit of code duplication which I feel could be reduced but it serves to get the idea across. It provides global error marshalling by exposing a `var ErrorMarshalFunc func(error) interface{}` in zerolog package that by default is a function that returns the passed argument. It should be overriden if you require custom error marshalling. Then in every function that accept error or array of errors `ErrorMarshalFunc` is called on the error and then the result of it is processed like this: - if it implements `LogObjectMarshaler`, serialize it as an object - if it is a string serialize as a string - if it is an error, serialize as a string with the result of `Error()` - else serialize it as an interface The side effect of this change is that the encoders don't need the `AppendError/s` methods anymore, as the errors are serialized directly to other types. --- array.go | 19 ++++++++++++-- context.go | 46 ++++++++++++++++++++++++---------- encoder.go | 2 -- event.go | 57 ++++++++++++++++++++++++++++-------------- fields.go | 40 +++++++++++++++++++++++++++-- internal/cbor/base.go | 36 +-------------------------- internal/json/base.go | 36 +-------------------------- log_test.go | 58 +++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 186 insertions(+), 108 deletions(-) diff --git a/array.go b/array.go index 4399c48..01b3eca 100644 --- a/array.go +++ b/array.go @@ -71,9 +71,24 @@ func (a *Array) Hex(val []byte) *Array { return a } -// Err append append the err as a string to the array. +// Err serializes and appends the err to the array. func (a *Array) Err(err error) *Array { - a.buf = enc.AppendError(enc.AppendArrayDelim(a.buf), err) + marshaled := ErrorMarshalFunc(err) + switch m := marshaled.(type) { + case LogObjectMarshaler: + e := newEvent(nil, 0) + e.buf = e.buf[:0] + e.appendObject(m) + a.buf = append(enc.AppendArrayDelim(a.buf), e.buf...) + eventPool.Put(e) + case error: + a.buf = enc.AppendString(enc.AppendArrayDelim(a.buf), m.Error()) + case string: + a.buf = enc.AppendString(enc.AppendArrayDelim(a.buf), m) + default: + a.buf = enc.AppendInterface(enc.AppendArrayDelim(a.buf), m) + } + return a } diff --git a/context.go b/context.go index fd26d5d..9800eb6 100644 --- a/context.go +++ b/context.go @@ -101,27 +101,47 @@ func (c Context) RawJSON(key string, b []byte) Context { return c } -// AnErr adds the field key with err as a string to the logger context. +// AnErr adds the field key with serialized err to the logger context. func (c Context) AnErr(key string, err error) Context { - if err != nil { - c.l.context = enc.AppendError(enc.AppendKey(c.l.context, key), err) + marshaled := ErrorMarshalFunc(err) + switch m := marshaled.(type) { + case nil: + return c + case LogObjectMarshaler: + return c.Object(key,m) + case error: + return c.Str(key, m.Error()) + case string: + return c.Str(key, m) + default: + return c.Interface(key, m) } - return c } -// Errs adds the field key with errs as an array of strings to the logger context. +// Errs adds the field key with errs as an array of serialized errors to the +// logger context. func (c Context) Errs(key string, errs []error) Context { - c.l.context = enc.AppendErrors(enc.AppendKey(c.l.context, key), errs) - return c + arr := Arr() + for _, err := range errs { + marshaled := ErrorMarshalFunc(err) + switch m := marshaled.(type) { + case LogObjectMarshaler: + arr = arr.Object(m) + case error: + arr = arr.Str(m.Error()) + case string: + arr = arr.Str(m) + default: + arr = arr.Interface(m) + } + } + + return c.Array(key, arr) } -// Err adds the field "error" with err as a string to the logger context. -// To customize the key name, change zerolog.ErrorFieldName. +// Err adds the field "error" with serialized err to the logger context. func (c Context) Err(err error) Context { - if err != nil { - c.l.context = enc.AppendError(enc.AppendKey(c.l.context, ErrorFieldName), err) - } - return c + return c.AnErr(ErrorFieldName, err) } // Bool adds the field key with val as a bool to the logger context. diff --git a/encoder.go b/encoder.go index 4bbe27a..09b24e8 100644 --- a/encoder.go +++ b/encoder.go @@ -16,8 +16,6 @@ type encoder interface { AppendDuration(dst []byte, d time.Duration, unit time.Duration, useInt bool) []byte AppendDurations(dst []byte, vals []time.Duration, unit time.Duration, useInt bool) []byte AppendEndMarker(dst []byte) []byte - AppendError(dst []byte, err error) []byte - AppendErrors(dst []byte, errs []error) []byte AppendFloat32(dst []byte, val float32) []byte AppendFloat64(dst []byte, val float64) []byte AppendFloats32(dst []byte, vals []float32) []byte diff --git a/event.go b/event.go index 1e191a3..8a7abf0 100644 --- a/event.go +++ b/event.go @@ -18,6 +18,11 @@ var eventPool = &sync.Pool{ }, } +// ErrorMarshalFunc allows customization of global error marshaling +var ErrorMarshalFunc = func (err error) interface{} { + return err +} + // Event represents a log event. It is instanced by one of the level method of // Logger and finalized by the Msg or Msgf method. type Event struct { @@ -239,39 +244,53 @@ func (e *Event) RawJSON(key string, b []byte) *Event { return e } -// AnErr adds the field key with err as a string to the *Event context. +// AnErr adds the field key with serialized err to the *Event context. // If err is nil, no field is added. func (e *Event) AnErr(key string, err error) *Event { - if e == nil { + marshaled := ErrorMarshalFunc(err) + switch m := marshaled.(type) { + case nil: return e + case LogObjectMarshaler: + return e.Object(key, m) + case error: + return e.Str(key, m.Error()) + case string: + return e.Str(key, m) + default: + return e.Interface(key, m) } - if err != nil { - e.buf = enc.AppendError(enc.AppendKey(e.buf, key), err) - } - return e } - -// Errs adds the field key with errs as an array of strings to the *Event context. -// If err is nil, no field is added. +// Errs adds the field key with errs as an array of serialized errors to the +// *Event context. func (e *Event) Errs(key string, errs []error) *Event { if e == nil { return e } - e.buf = enc.AppendErrors(enc.AppendKey(e.buf, key), errs) - return e + + arr := Arr() + for _, err := range errs { + marshaled := ErrorMarshalFunc(err) + switch m := marshaled.(type) { + case LogObjectMarshaler: + arr = arr.Object(m) + case error: + arr = arr.Err(m) + case string: + arr = arr.Str(m) + default: + arr = arr.Interface(m) + } + } + + return e.Array(key, arr) } -// Err adds the field "error" with err as a string to the *Event context. +// Err adds the field "error" with serialized err to the *Event context. // If err is nil, no field is added. // To customize the key name, change zerolog.ErrorFieldName. func (e *Event) Err(err error) *Event { - if e == nil { - return e - } - if err != nil { - e.buf = enc.AppendError(enc.AppendKey(e.buf, ErrorFieldName), err) - } - return e + return e.AnErr(ErrorFieldName, err) } // Bool adds the field key with val as a bool to the *Event context. diff --git a/fields.go b/fields.go index 25f0d47..336ecbf 100644 --- a/fields.go +++ b/fields.go @@ -29,9 +29,45 @@ func appendFields(dst []byte, fields map[string]interface{}) []byte { case []byte: dst = enc.AppendBytes(dst, val) case error: - dst = enc.AppendError(dst, val) + marshaled := ErrorMarshalFunc(val) + switch m := marshaled.(type) { + case LogObjectMarshaler: + e := newEvent(nil, 0) + e.buf = e.buf[:0] + e.appendObject(m) + dst = append(dst, e.buf...) + eventPool.Put(e) + case error: + dst = enc.AppendString(dst, m.Error()) + case string: + dst = enc.AppendString(dst, m) + default: + dst = enc.AppendInterface(dst, m) + } case []error: - dst = enc.AppendErrors(dst, val) + dst = enc.AppendArrayStart(dst) + for i, err := range val { + marshaled := ErrorMarshalFunc(err) + switch m := marshaled.(type) { + case LogObjectMarshaler: + e := newEvent(nil, 0) + e.buf = e.buf[:0] + e.appendObject(m) + dst = append(dst, e.buf...) + eventPool.Put(e) + case error: + dst = enc.AppendString(dst, m.Error()) + case string: + dst = enc.AppendString(dst, m) + default: + dst = enc.AppendInterface(dst, m) + } + + if i < (len(val) - 1) { + enc.AppendArrayDelim(dst) + } + } + dst = enc.AppendArrayEnd(dst) case bool: dst = enc.AppendBool(dst, val) case int: diff --git a/internal/cbor/base.go b/internal/cbor/base.go index 1b13278..58cd082 100644 --- a/internal/cbor/base.go +++ b/internal/cbor/base.go @@ -8,38 +8,4 @@ func (e Encoder) AppendKey(dst []byte, key string) []byte { dst = e.AppendBeginMarker(dst) } return e.AppendString(dst, key) -} - -// AppendError adds the Error to the log message if error is NOT nil -func (e Encoder) AppendError(dst []byte, err error) []byte { - if err == nil { - return append(dst, `null`...) - } - return e.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 (e Encoder) AppendErrors(dst []byte, errs []error) []byte { - if len(errs) == 0 { - return e.AppendArrayEnd(e.AppendArrayStart(dst)) - } - dst = e.AppendArrayStart(dst) - if errs[0] != nil { - dst = e.AppendString(dst, errs[0].Error()) - } else { - dst = e.AppendNil(dst) - } - if len(errs) > 1 { - for _, err := range errs[1:] { - if err == nil { - dst = e.AppendNil(dst) - continue - } - dst = e.AppendString(dst, err.Error()) - } - } - dst = e.AppendArrayEnd(dst) - return dst -} +} \ No newline at end of file diff --git a/internal/json/base.go b/internal/json/base.go index bad1c74..d6f8839 100644 --- a/internal/json/base.go +++ b/internal/json/base.go @@ -9,38 +9,4 @@ func (e Encoder) AppendKey(dst []byte, key string) []byte { } dst = e.AppendString(dst, key) return append(dst, ':') -} - -// AppendError encodes the error string to json and appends -// the encoded string to the input byte slice. -func (e Encoder) AppendError(dst []byte, err error) []byte { - if err == nil { - return append(dst, `null`...) - } - return e.AppendString(dst, err.Error()) -} - -// AppendErrors encodes the error strings to json and -// appends the encoded string list to the input byte slice. -func (e Encoder) AppendErrors(dst []byte, errs []error) []byte { - if len(errs) == 0 { - return append(dst, '[', ']') - } - dst = append(dst, '[') - if errs[0] != nil { - dst = e.AppendString(dst, errs[0].Error()) - } else { - dst = append(dst, "null"...) - } - if len(errs) > 1 { - for _, err := range errs[1:] { - if err == nil { - dst = append(dst, ",null"...) - continue - } - dst = e.AppendString(append(dst, ','), err.Error()) - } - } - dst = append(dst, ']') - return dst -} +} \ No newline at end of file diff --git a/log_test.go b/log_test.go index 1175af2..e013606 100644 --- a/log_test.go +++ b/log_test.go @@ -521,3 +521,61 @@ func TestOutputWithTimestamp(t *testing.T) { t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want) } } + +type loggableError struct { + error +} + +func (l loggableError) MarshalZerologObject(e *Event) { + e.Str("message", l.error.Error() + ": loggableError") +} + +func TestErrorMarshalFunc(t *testing.T) { + out := &bytes.Buffer{} + log := New(out) + + // test default behaviour + log.Log().Err(errors.New("err")).Msg("msg") + if got, want := decodeIfBinaryToString(out.Bytes()), `{"error":"err","message":"msg"}`+"\n"; got != want { + t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want) + } + out.Reset() + + log.Log().Err(loggableError{errors.New("err")}).Msg("msg") + if got, want := decodeIfBinaryToString(out.Bytes()), `{"error":{"message":"err: loggableError"},"message":"msg"}`+"\n"; got != want { + t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want) + } + out.Reset() + + // test overriding the ErrorMarshalFunc + originalErrorMarshalFunc := ErrorMarshalFunc + defer func(){ + ErrorMarshalFunc = originalErrorMarshalFunc + }() + + ErrorMarshalFunc = func(err error) interface{} { + return err.Error() + ": marshaled string" + } + log.Log().Err(errors.New("err")).Msg("msg") + if got, want := decodeIfBinaryToString(out.Bytes()), `{"error":"err: marshaled string","message":"msg"}`+"\n"; got != want { + t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want) + } + + out.Reset() + ErrorMarshalFunc = func(err error) interface{} { + return errors.New(err.Error() + ": new error") + } + log.Log().Err(errors.New("err")).Msg("msg") + if got, want := decodeIfBinaryToString(out.Bytes()), `{"error":"err: new error","message":"msg"}`+"\n"; got != want { + t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want) + } + + out.Reset() + ErrorMarshalFunc = func(err error) interface{} { + return loggableError{err} + } + log.Log().Err(errors.New("err")).Msg("msg") + if got, want := decodeIfBinaryToString(out.Bytes()), `{"error":{"message":"err: loggableError"},"message":"msg"}`+"\n"; got != want { + t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want) + } +} From 9cd6f6eef2d7e9041629e2dd853778a9b2abddbd Mon Sep 17 00:00:00 2001 From: Olivier Poitrey Date: Mon, 2 Jul 2018 18:23:53 -0700 Subject: [PATCH 41/73] ctx: store logger in context when missing or different that stored one (#81) Current implementation stores a copy of the logger as a pointer and update its content, which is now unecessary since the introduction of WithContext. The new WithContext now takes the pointer and store it in the context if none is stored already or if the last one stored is a different pointer. This way it is still possible to update the context of a logger stored in the context, but it is also possible to store a copy of the logger in a sub-context. Fix #80 --- ctx.go | 23 ++++++++++++----------- ctx_test.go | 28 ++++++++++++++++++++++------ 2 files changed, 34 insertions(+), 17 deletions(-) diff --git a/ctx.go b/ctx.go index 04d8a11..2b7a682 100644 --- a/ctx.go +++ b/ctx.go @@ -2,38 +2,39 @@ package zerolog import ( "context" - "io/ioutil" ) var disabledLogger *Logger func init() { - l := New(ioutil.Discard).Level(Disabled) + l := Nop() disabledLogger = &l } type ctxKey struct{} // WithContext returns a copy of ctx with l associated. If an instance of Logger -// is already in the context, the pointer to this logger is updated with l. +// is already in the context, the context is not updated. // // For instance, to add a field to an existing logger in the context, use this // notation: // // ctx := r.Context() // l := zerolog.Ctx(ctx) -// ctx = l.With().Str("foo", "bar").WithContext(ctx) -func (l Logger) WithContext(ctx context.Context) context.Context { +// l.UpdateContext(func(c Context) Context { +// return c.Str("bar", "baz") +// }) +func (l *Logger) WithContext(ctx context.Context) context.Context { if lp, ok := ctx.Value(ctxKey{}).(*Logger); ok { - // Update existing pointer. - *lp = l - return ctx - } - if l.level == Disabled { + if lp == l { + // Do not store same logger. + return ctx + } + } else if l.level == Disabled { // Do not store disabled logger. return ctx } - return context.WithValue(ctx, ctxKey{}, &l) + return context.WithValue(ctx, ctxKey{}, l) } // Ctx returns the Logger associated with the ctx. If no logger diff --git a/ctx_test.go b/ctx_test.go index 942b723..646cd0f 100644 --- a/ctx_test.go +++ b/ctx_test.go @@ -30,18 +30,34 @@ func TestCtx(t *testing.T) { } func TestCtxDisabled(t *testing.T) { - ctx := disabledLogger.WithContext(context.Background()) + dl := New(ioutil.Discard).Level(Disabled) + ctx := dl.WithContext(context.Background()) if ctx != context.Background() { t.Error("WithContext stored a disabled logger") } - ctx = New(ioutil.Discard).WithContext(ctx) - if reflect.DeepEqual(Ctx(ctx), disabledLogger) { + l := New(ioutil.Discard).With().Str("foo", "bar").Logger() + ctx = l.WithContext(ctx) + if Ctx(ctx) != &l { t.Error("WithContext did not store logger") } - ctx = disabledLogger.WithContext(ctx) - if !reflect.DeepEqual(Ctx(ctx), disabledLogger) { - t.Error("WithContext did not update logger pointer with disabled logger") + l.UpdateContext(func(c Context) Context { + return c.Str("bar", "baz") + }) + ctx = l.WithContext(ctx) + if Ctx(ctx) != &l { + t.Error("WithContext did not store updated logger") + } + + l = l.Level(DebugLevel) + ctx = l.WithContext(ctx) + if Ctx(ctx) != &l { + t.Error("WithContext did not store copied logger") + } + + ctx = dl.WithContext(ctx) + if Ctx(ctx) != &dl { + t.Error("WithContext did not overide logger with a disabled logger") } } From 372015deb48c7445a077b889e4136daa51545a39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Du=C5=A1an=20Kasan?= Date: Fri, 20 Jul 2018 17:05:08 +0200 Subject: [PATCH 42/73] add linter to check for missing finishers (#85) --- cmd/lint/lint.go | 175 +++++++++++++++++++++++++++++++++++++++++++++ cmd/lint/readme.md | 37 ++++++++++ 2 files changed, 212 insertions(+) create mode 100644 cmd/lint/lint.go create mode 100644 cmd/lint/readme.md diff --git a/cmd/lint/lint.go b/cmd/lint/lint.go new file mode 100644 index 0000000..700371f --- /dev/null +++ b/cmd/lint/lint.go @@ -0,0 +1,175 @@ +package main + +import ( + "flag" + "fmt" + "go/ast" + "go/token" + "go/types" + "os" + "path/filepath" + "strings" + + "golang.org/x/tools/go/loader" +) + +var ( + recursivelyIgnoredPkgs arrayFlag + ignoredPkgs arrayFlag + ignoredFiles arrayFlag + allowedFinishers arrayFlag = []string{"Msg", "Msgf"} + rootPkg string +) + +// parse input flags and args +func init() { + flag.Var(&recursivelyIgnoredPkgs, "ignorePkgRecursively", "ignore the specified package and all subpackages recursively") + flag.Var(&ignoredPkgs, "ignorePkg", "ignore the specified package") + flag.Var(&ignoredFiles, "ignoreFile", "ignore the specified file by its path and/or go path (package/file.go)") + flag.Var(&allowedFinishers, "finisher", "allowed finisher for the event chain") + flag.Parse() + + // add zerolog to recursively ignored packages + recursivelyIgnoredPkgs = append(recursivelyIgnoredPkgs, "github.com/rs/zerolog") + args := flag.Args() + if len(args) != 1 { + fmt.Fprintln(os.Stderr, "you must provide exactly one package path") + os.Exit(1) + } + rootPkg = args[0] +} + +func main() { + // load the package and all its dependencies + conf := loader.Config{} + conf.Import(rootPkg) + p, err := conf.Load() + if err != nil { + fmt.Fprintf(os.Stderr, "Error: unable to load the root package. %s\n", err.Error()) + os.Exit(1) + } + + // get the github.com/rs/zerolog.Event type + event := getEvent(p) + if event == nil { + fmt.Fprintln(os.Stderr, "Error: github.com/rs/zerolog.Event declaration not found, maybe zerolog is not imported in the scanned package?") + os.Exit(1) + } + + // get all selections (function calls) with the github.com/rs/zerolog.Event (or pointer) receiver + selections := getSelectionsWithReceiverType(p, event) + + // print the violations (if any) + hasViolations := false + for _, s := range selections { + if hasBadFinisher(p, s) { + hasViolations = true + fmt.Printf("Error: missing or bad finisher for log chain, last call: %q at: %s:%v\n", s.fn.Name(), p.Fset.File(s.Pos()).Name(), p.Fset.Position(s.Pos()).Line) + } + } + + // if no violations detected, return normally + if !hasViolations { + fmt.Println("No violations found") + return + } + + // if violations were detected, return error code + os.Exit(1) +} + +func getEvent(p *loader.Program) types.Type { + for _, pkg := range p.AllPackages { + if strings.HasSuffix(pkg.Pkg.Path(), "github.com/rs/zerolog") { + for _, d := range pkg.Defs { + if d != nil && d.Name() == "Event" { + return d.Type() + } + } + } + } + + return nil +} + +func getSelectionsWithReceiverType(p *loader.Program, targetType types.Type) map[token.Pos]selection { + selections := map[token.Pos]selection{} + + for _, z := range p.AllPackages { + for i, t := range z.Selections { + switch o := t.Obj().(type) { + case *types.Func: + // this is not a bug, o.Type() is always *types.Signature, see docs + if vt := o.Type().(*types.Signature).Recv(); vt != nil { + typ := vt.Type() + if pointer, ok := typ.(*types.Pointer); ok { + typ = pointer.Elem() + } + + if typ == targetType { + if s, ok := selections[i.Pos()]; !ok || i.End() > s.End() { + selections[i.Pos()] = selection{i, o, z.Pkg} + } + } + } + default: + // skip + } + } + } + + return selections +} + +func hasBadFinisher(p *loader.Program, s selection) bool { + pkgPath := strings.TrimPrefix(s.pkg.Path(), rootPkg+"/vendor/") + absoluteFilePath := strings.TrimPrefix(p.Fset.File(s.Pos()).Name(), rootPkg+"/vendor/") + goFilePath := pkgPath + "/" + filepath.Base(p.Fset.Position(s.Pos()).Filename) + + for _, f := range allowedFinishers { + if f == s.fn.Name() { + return false + } + } + + for _, ignoredPkg := range recursivelyIgnoredPkgs { + if strings.HasPrefix(pkgPath, ignoredPkg) { + return false + } + } + + for _, ignoredPkg := range ignoredPkgs { + if pkgPath == ignoredPkg { + return false + } + } + + for _, ignoredFile := range ignoredFiles { + if absoluteFilePath == ignoredFile { + return false + } + + if goFilePath == ignoredFile { + return false + } + } + + return true +} + +type arrayFlag []string + +func (i *arrayFlag) String() string { + return fmt.Sprintf("%v", []string(*i)) +} + +func (i *arrayFlag) Set(value string) error { + *i = append(*i, value) + return nil +} + +type selection struct { + *ast.SelectorExpr + fn *types.Func + pkg *types.Package +} diff --git a/cmd/lint/readme.md b/cmd/lint/readme.md new file mode 100644 index 0000000..a15cba5 --- /dev/null +++ b/cmd/lint/readme.md @@ -0,0 +1,37 @@ +# Zerolog Lint + +This is a basic linter that checks for missing log event finishers. Finds errors like: `log.Error().Int64("userID": 5)` - missing the `Msg`/`Msgf` finishers. + +## Problem + +When using zerolog it's easy to forget to finish the log event chain by calling a finisher - the `Msg` or `Msgf` function that will schedule the event for writing. The problem with this is that it doesn't warn/panic during compilation and it's not easily found by grep or other general tools. It's even prominently mentioned in the project's readme, that: + +> It is very important to note that when using the **zerolog** chaining API, as shown above (`log.Info().Msg("hello world"`), the chain must have either the `Msg` or `Msgf` method call. If you forget to add either of these, the log will not occur and there is no compile time error to alert you of this. + +## Solution + +A basic linter like this one here that looks for method invocations on `zerolog.Event` can examine the last call in a method call chain and check if it is a finisher, thus pointing out these errors. + +## Usage + +Just compile this and then run it. Or just run it via `go run` command via something like `go run cmd/lint/lint.go`. + +The command accepts only one argument - the package to be inspected - and 4 optional flags, all of which can occur multiple times. The standard synopsis of the command is: + +`lint [-finisher value] [-ignoreFile value] [-ignorePkg value] [-ignorePkgRecursively value] package` + +#### Flags + +- finisher + - specify which finishers to accept, defaults to `Msg` and `Msgf` +- ignoreFile + - which files to ignore, either by full path or by go path (package/file.go) +- ignorePkg + - do not inspect the specified package if found in the dependecy tree +- ignorePkgRecursively + - do not inspect the specified package or its subpackages if found in the dependency tree + +## Drawbacks + +As it is, linter can generate a false positives in a specific case. These false positives come from the fact that if you have a method that returns a `zerolog.Event` the linter will flag it because you are obviously not finishing the event. This will be solved in later release. + From e8a8508f09b9d56fe915b9d928a8f2306e4fab78 Mon Sep 17 00:00:00 2001 From: su21 Date: Wed, 25 Jul 2018 17:48:22 +0800 Subject: [PATCH 43/73] fix caller file and line number in context hook (#89) * fix caller file and line number report in context hook * update comment * update comment --- context.go | 6 +++--- event.go | 29 +++++++++++++++++------------ 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/context.go b/context.go index 9800eb6..c277fd2 100644 --- a/context.go +++ b/context.go @@ -108,7 +108,7 @@ func (c Context) AnErr(key string, err error) Context { case nil: return c case LogObjectMarshaler: - return c.Object(key,m) + return c.Object(key, m) case error: return c.Str(key, m.Error()) case string: @@ -350,8 +350,8 @@ func (c Context) Interface(key string, i interface{}) Context { type callerHook struct{} func (ch callerHook) Run(e *Event, level Level, msg string) { - //Two extra frames to skip (added by hook infra). - e.caller(CallerSkipFrameCount+2) + // Three extra frames to skip (added by hook infra). + e.caller(CallerSkipFrameCount + 3) } var ch = callerHook{} diff --git a/event.go b/event.go index 8a7abf0..8468e41 100644 --- a/event.go +++ b/event.go @@ -19,7 +19,7 @@ var eventPool = &sync.Pool{ } // ErrorMarshalFunc allows customization of global error marshaling -var ErrorMarshalFunc = func (err error) interface{} { +var ErrorMarshalFunc = func(err error) interface{} { return err } @@ -83,6 +83,21 @@ func (e *Event) Msg(msg string) { if e == nil { return } + e.msg(msg) +} + +// Msgf sends the event with formated msg added as the message field if not empty. +// +// NOTICE: once this methid is called, the *Event should be disposed. +// Calling Msg twice can have unexpected result. +func (e *Event) Msgf(format string, v ...interface{}) { + if e == nil { + return + } + e.msg(fmt.Sprintf(format, v...)) +} + +func (e *Event) msg(msg string) { if len(e.ch) > 0 { e.ch[0].Run(e, e.level, msg) if len(e.ch) > 1 { @@ -110,17 +125,6 @@ func (e *Event) Msg(msg string) { } } -// Msgf sends the event with formated msg added as the message field if not empty. -// -// NOTICE: once this methid is called, the *Event should be disposed. -// Calling Msg twice can have unexpected result. -func (e *Event) Msgf(format string, v ...interface{}) { - if e == nil { - return - } - e.Msg(fmt.Sprintf(format, v...)) -} - // Fields is a helper function to use a map to set fields using type assertion. func (e *Event) Fields(fields map[string]interface{}) *Event { if e == nil { @@ -261,6 +265,7 @@ func (e *Event) AnErr(key string, err error) *Event { return e.Interface(key, m) } } + // Errs adds the field key with errs as an array of serialized errors to the // *Event context. func (e *Event) Errs(key string, errs []error) *Event { From bae001d86bbb77870a9576fb4e42ef285937bccb Mon Sep 17 00:00:00 2001 From: Dave McCormick Date: Wed, 25 Jul 2018 18:05:55 +0100 Subject: [PATCH 44/73] Allow devs to change the width of the logging level column in consolewriter (#87) * Allow user to change the width of the logging level column * Change default level width to 0 (no dynamic width) --- console.go | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/console.go b/console.go index c957571..98de5d7 100644 --- a/console.go +++ b/console.go @@ -31,6 +31,10 @@ var consoleBufPool = sync.Pool{ }, } +// LevelWidth defines the desired character width of the log level column. +// Default 0 does not trim or pad (variable width based level text, e.g. "INFO" or "ERROR") +var LevelWidth = 0 + // ConsoleWriter reads a JSON object per write operation and output an // optionally colored human readable version on the Out writer. type ConsoleWriter struct { @@ -55,7 +59,14 @@ func (w ConsoleWriter) Write(p []byte) (n int, err error) { if !w.NoColor { lvlColor = levelColor(l) } - level = strings.ToUpper(l)[0:4] + level = strings.ToUpper(l) + if LevelWidth > 0 { + if padding := LevelWidth - len(level); padding > 0 { + level += strings.Repeat(" ", padding) + } else { + level = level[0:LevelWidth] + } + } } fmt.Fprintf(buf, "%s |%s| %s", colorize(formatTime(event[TimestampFieldName]), cDarkGray, !w.NoColor), From 71e1f5e052d017a22320883199723057e89f3ec8 Mon Sep 17 00:00:00 2001 From: Olivier Poitrey Date: Thu, 26 Jul 2018 15:53:02 -0700 Subject: [PATCH 45/73] Add the ability to discard an event from a hook The Discard method has been added to the Event type so it can be called from a hook to prevent the event from behing printed. This new method works outside of the context of a hook too. Fixes #90 --- event.go | 18 ++- hook.go | 9 ++ hook_test.go | 319 ++++++++++++++++++++------------------------------- 3 files changed, 147 insertions(+), 199 deletions(-) diff --git a/event.go b/event.go index 8468e41..71f0a9a 100644 --- a/event.go +++ b/event.go @@ -60,10 +60,12 @@ func (e *Event) write() (err error) { if e == nil { return nil } - e.buf = enc.AppendEndMarker(e.buf) - e.buf = enc.AppendLineBreak(e.buf) - if e.w != nil { - _, err = e.w.WriteLevel(e.level, e.buf) + if e.level != Disabled { + e.buf = enc.AppendEndMarker(e.buf) + e.buf = enc.AppendLineBreak(e.buf) + if e.w != nil { + _, err = e.w.WriteLevel(e.level, e.buf) + } } eventPool.Put(e) return @@ -72,7 +74,13 @@ func (e *Event) write() (err error) { // Enabled return false if the *Event is going to be filtered out by // log level or sampling. func (e *Event) Enabled() bool { - return e != nil + return e != nil && e.level != Disabled +} + +// Discard disables the event so Msg(f) won't print it. +func (e *Event) Discard() *Event { + e.level = Disabled + return nil } // Msg sends the *Event with msg added as the message field if not empty. diff --git a/hook.go b/hook.go index 549d85a..08133ac 100644 --- a/hook.go +++ b/hook.go @@ -6,6 +6,15 @@ type Hook interface { Run(e *Event, level Level, message string) } +// HookFunc is an adaptor to allow the use of an ordinary function +// as a Hook. +type HookFunc func(e *Event, level Level, message string) + +// Run implements the Hook interface. +func (h HookFunc) Run(e *Event, level Level, message string) { + h(e, level, message) +} + // LevelHook applies a different hook for each level. type LevelHook struct { NoLevelHook, DebugHook, InfoHook, WarnHook, ErrorHook, FatalHook, PanicHook Hook diff --git a/hook_test.go b/hook_test.go index d051bdc..19bde9d 100644 --- a/hook_test.go +++ b/hook_test.go @@ -6,204 +6,135 @@ import ( "testing" ) -type LevelNameHook struct{} - -func (h LevelNameHook) Run(e *Event, level Level, msg string) { - levelName := level.String() - if level == NoLevel { - levelName = "nolevel" - } - e.Str("level_name", levelName) -} - -type SimpleHook struct{} - -func (h SimpleHook) Run(e *Event, level Level, msg string) { - e.Bool("has_level", level != NoLevel) - e.Str("test", "logged") -} - -type CopyHook struct{} - -func (h CopyHook) Run(e *Event, level Level, msg string) { - hasLevel := level != NoLevel - e.Bool("copy_has_level", hasLevel) - if hasLevel { - e.Str("copy_level", level.String()) - } - e.Str("copy_msg", msg) -} - -type NopHook struct{} - -func (h NopHook) Run(e *Event, level Level, msg string) { -} - var ( - levelNameHook LevelNameHook - simpleHook SimpleHook - copyHook CopyHook - nopHook NopHook + levelNameHook = HookFunc(func(e *Event, level Level, msg string) { + levelName := level.String() + if level == NoLevel { + levelName = "nolevel" + } + e.Str("level_name", levelName) + }) + simpleHook = HookFunc(func(e *Event, level Level, msg string) { + e.Bool("has_level", level != NoLevel) + e.Str("test", "logged") + }) + copyHook = HookFunc(func(e *Event, level Level, msg string) { + hasLevel := level != NoLevel + e.Bool("copy_has_level", hasLevel) + if hasLevel { + e.Str("copy_level", level.String()) + } + e.Str("copy_msg", msg) + }) + nopHook = HookFunc(func(e *Event, level Level, message string) { + }) + discardHook = HookFunc(func(e *Event, level Level, message string) { + e.Discard() + }) ) func TestHook(t *testing.T) { - t.Run("Message", func(t *testing.T) { - out := &bytes.Buffer{} - log := New(out).Hook(levelNameHook) - log.Log().Msg("test message") - 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) - } - }) - t.Run("NoLevel", func(t *testing.T) { - out := &bytes.Buffer{} - log := New(out).Hook(levelNameHook) - log.Log().Msg("") - if got, want := decodeIfBinaryToString(out.Bytes()), `{"level_name":"nolevel"}`+"\n"; got != want { - t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want) - } - }) - t.Run("Print", func(t *testing.T) { - out := &bytes.Buffer{} - log := New(out).Hook(levelNameHook) - log.Print("") - 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) - } - }) - t.Run("Error", func(t *testing.T) { - out := &bytes.Buffer{} - log := New(out).Hook(levelNameHook) - log.Error().Msg("") - 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) - } - }) - t.Run("Copy/1", func(t *testing.T) { - out := &bytes.Buffer{} - log := New(out).Hook(copyHook) - log.Log().Msg("") - 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) - } - }) - t.Run("Copy/2", func(t *testing.T) { - out := &bytes.Buffer{} - log := New(out).Hook(copyHook) - log.Info().Msg("a message") - 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) - } - }) - t.Run("Multi", func(t *testing.T) { - out := &bytes.Buffer{} - log := New(out).Hook(levelNameHook).Hook(simpleHook) - log.Error().Msg("") - 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) - } - }) - t.Run("Multi/Message", func(t *testing.T) { - out := &bytes.Buffer{} - log := New(out).Hook(levelNameHook).Hook(simpleHook) - log.Error().Msg("a message") - 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) - } - }) - t.Run("Output/single/pre", func(t *testing.T) { - ignored := &bytes.Buffer{} - out := &bytes.Buffer{} - log := New(ignored).Hook(levelNameHook).Output(out) - log.Error().Msg("") - 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) - } - }) - t.Run("Output/single/post", func(t *testing.T) { - ignored := &bytes.Buffer{} - out := &bytes.Buffer{} - log := New(ignored).Output(out).Hook(levelNameHook) - log.Error().Msg("") - 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) - } - }) - t.Run("Output/multi/pre", func(t *testing.T) { - ignored := &bytes.Buffer{} - out := &bytes.Buffer{} - log := New(ignored).Hook(levelNameHook).Hook(simpleHook).Output(out) - log.Error().Msg("") - 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) - } - }) - t.Run("Output/multi/post", func(t *testing.T) { - ignored := &bytes.Buffer{} - out := &bytes.Buffer{} - log := New(ignored).Output(out).Hook(levelNameHook).Hook(simpleHook) - log.Error().Msg("") - 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) - } - }) - t.Run("Output/mixed", func(t *testing.T) { - ignored := &bytes.Buffer{} - out := &bytes.Buffer{} - log := New(ignored).Hook(levelNameHook).Output(out).Hook(simpleHook) - log.Error().Msg("") - 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) - } - }) - t.Run("With/single/pre", func(t *testing.T) { - out := &bytes.Buffer{} - log := New(out).Hook(levelNameHook).With().Str("with", "pre").Logger() - log.Error().Msg("") - 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) - } - }) - t.Run("With/single/post", func(t *testing.T) { - out := &bytes.Buffer{} - log := New(out).With().Str("with", "post").Logger().Hook(levelNameHook) - log.Error().Msg("") - 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) - } - }) - t.Run("With/multi/pre", func(t *testing.T) { - out := &bytes.Buffer{} - log := New(out).Hook(levelNameHook).Hook(simpleHook).With().Str("with", "pre").Logger() - log.Error().Msg("") - 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) - } - }) - t.Run("With/multi/post", func(t *testing.T) { - out := &bytes.Buffer{} - log := New(out).With().Str("with", "post").Logger().Hook(levelNameHook).Hook(simpleHook) - log.Error().Msg("") - 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) - } - }) - t.Run("With/mixed", func(t *testing.T) { - out := &bytes.Buffer{} - log := New(out).Hook(levelNameHook).With().Str("with", "mixed").Logger().Hook(simpleHook) - log.Error().Msg("") - 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) - } - }) - t.Run("None", func(t *testing.T) { - out := &bytes.Buffer{} - log := New(out) - log.Error().Msg("") - if got, want := decodeIfBinaryToString(out.Bytes()), `{"level":"error"}`+"\n"; got != want { - t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want) - } - }) + tests := []struct { + name string + want string + test func(log Logger) + }{ + {"Message", `{"level_name":"nolevel","message":"test message"}` + "\n", func(log Logger) { + log = log.Hook(levelNameHook) + log.Log().Msg("test message") + }}, + {"NoLevel", `{"level_name":"nolevel"}` + "\n", func(log Logger) { + log = log.Hook(levelNameHook) + log.Log().Msg("") + }}, + {"Print", `{"level":"debug","level_name":"debug"}` + "\n", func(log Logger) { + log = log.Hook(levelNameHook) + log.Print("") + }}, + {"Error", `{"level":"error","level_name":"error"}` + "\n", func(log Logger) { + log = log.Hook(levelNameHook) + log.Error().Msg("") + }}, + {"Copy/1", `{"copy_has_level":false,"copy_msg":""}` + "\n", func(log Logger) { + log = log.Hook(copyHook) + log.Log().Msg("") + }}, + {"Copy/2", `{"level":"info","copy_has_level":true,"copy_level":"info","copy_msg":"a message","message":"a message"}` + "\n", func(log Logger) { + log = log.Hook(copyHook) + log.Info().Msg("a message") + }}, + {"Multi", `{"level":"error","level_name":"error","has_level":true,"test":"logged"}` + "\n", func(log Logger) { + log = log.Hook(levelNameHook).Hook(simpleHook) + log.Error().Msg("") + }}, + {"Multi/Message", `{"level":"error","level_name":"error","has_level":true,"test":"logged","message":"a message"}` + "\n", func(log Logger) { + log = log.Hook(levelNameHook).Hook(simpleHook) + log.Error().Msg("a message") + }}, + {"Output/single/pre", `{"level":"error","level_name":"error"}` + "\n", func(log Logger) { + ignored := &bytes.Buffer{} + log = New(ignored).Hook(levelNameHook).Output(log.w) + log.Error().Msg("") + }}, + {"Output/single/post", `{"level":"error","level_name":"error"}` + "\n", func(log Logger) { + ignored := &bytes.Buffer{} + log = New(ignored).Output(log.w).Hook(levelNameHook) + log.Error().Msg("") + }}, + {"Output/multi/pre", `{"level":"error","level_name":"error","has_level":true,"test":"logged"}` + "\n", func(log Logger) { + ignored := &bytes.Buffer{} + log = New(ignored).Hook(levelNameHook).Hook(simpleHook).Output(log.w) + log.Error().Msg("") + }}, + {"Output/multi/post", `{"level":"error","level_name":"error","has_level":true,"test":"logged"}` + "\n", func(log Logger) { + ignored := &bytes.Buffer{} + log = New(ignored).Output(log.w).Hook(levelNameHook).Hook(simpleHook) + log.Error().Msg("") + }}, + {"Output/mixed", `{"level":"error","level_name":"error","has_level":true,"test":"logged"}` + "\n", func(log Logger) { + ignored := &bytes.Buffer{} + log = New(ignored).Hook(levelNameHook).Output(log.w).Hook(simpleHook) + log.Error().Msg("") + }}, + {"With/single/pre", `{"level":"error","with":"pre","level_name":"error"}` + "\n", func(log Logger) { + log = log.Hook(levelNameHook).With().Str("with", "pre").Logger() + log.Error().Msg("") + }}, + {"With/single/post", `{"level":"error","with":"post","level_name":"error"}` + "\n", func(log Logger) { + log = log.With().Str("with", "post").Logger().Hook(levelNameHook) + log.Error().Msg("") + }}, + {"With/multi/pre", `{"level":"error","with":"pre","level_name":"error","has_level":true,"test":"logged"}` + "\n", func(log Logger) { + log = log.Hook(levelNameHook).Hook(simpleHook).With().Str("with", "pre").Logger() + log.Error().Msg("") + }}, + {"With/multi/post", `{"level":"error","with":"post","level_name":"error","has_level":true,"test":"logged"}` + "\n", func(log Logger) { + log = log.With().Str("with", "post").Logger().Hook(levelNameHook).Hook(simpleHook) + log.Error().Msg("") + }}, + {"With/mixed", `{"level":"error","with":"mixed","level_name":"error","has_level":true,"test":"logged"}` + "\n", func(log Logger) { + log = log.Hook(levelNameHook).With().Str("with", "mixed").Logger().Hook(simpleHook) + log.Error().Msg("") + }}, + {"Discard", "", func(log Logger) { + log = log.Hook(discardHook) + log.Log().Msg("test message") + }}, + {"None", `{"level":"error"}` + "\n", func(log Logger) { + log.Error().Msg("") + }}, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + out := &bytes.Buffer{} + log := New(out) + tt.test(log) + if got, want := decodeIfBinaryToString(out.Bytes()), tt.want; got != want { + t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want) + } + }) + } } func BenchmarkHooks(b *testing.B) { From 85255a5e26e0508529801070fa4ce521d9994d61 Mon Sep 17 00:00:00 2001 From: Marcelo Aymone Date: Tue, 14 Aug 2018 23:23:11 -0300 Subject: [PATCH 46/73] Fix typo on documentation (#94) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c9099b5..00fedae 100644 --- a/README.md +++ b/README.md @@ -444,7 +444,7 @@ if err := http.ListenAndServe(":8080", nil); err != nil { Some settings can be changed and will by applied to all loggers: * `log.Logger`: You can set this value to customize the global logger (the one used by package level methods). -* `zerolog.SetGlobalLevel`: Can raise the minimum level of all loggers. Set this to `zerolog.Disable` to disable logging altogether (quiet mode). +* `zerolog.SetGlobalLevel`: Can raise the minimum level of all loggers. Set this to `zerolog.Disabled` to disable logging altogether (quiet mode). * `zerolog.DisableSampling`: If argument is `true`, all sampled loggers will stop sampling and issue 100% of their log events. * `zerolog.TimestampFieldName`: Can be set to customize `Timestamp` field name. * `zerolog.LevelFieldName`: Can be set to customize level field name. From b6f076edc801b110475cbf68da701e2bea52096a Mon Sep 17 00:00:00 2001 From: Duncan Hall Date: Fri, 31 Aug 2018 17:46:32 +0100 Subject: [PATCH 47/73] Add note for default writing to os.Stderr (#97) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 00fedae..2383a0d 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ func main() { // Output: {"time":1516134303,"level":"debug","message":"hello world"} ``` - +> Note: By default log writes to `os.Stderr` > Note: The default log level for `log.Print` is *debug* ### Contextual Logging From 1dde226d4517f8f53b7d962a80b02cd73671607d Mon Sep 17 00:00:00 2001 From: Olivier Poitrey Date: Sun, 16 Sep 2018 15:53:13 -0700 Subject: [PATCH 48/73] BasicSampler prints first message (fix #104) --- log_test.go | 6 +++--- sampler.go | 2 +- sampler_test.go | 9 ++++++++- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/log_test.go b/log_test.go index e013606..32cf52f 100644 --- a/log_test.go +++ b/log_test.go @@ -398,7 +398,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 := decodeIfBinaryToString(out.Bytes()), "{\"i\":2}\n{\"i\":4}\n"; got != want { + if got, want := decodeIfBinaryToString(out.Bytes()), "{\"i\":1}\n{\"i\":3}\n"; got != want { t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want) } } @@ -527,7 +527,7 @@ type loggableError struct { } func (l loggableError) MarshalZerologObject(e *Event) { - e.Str("message", l.error.Error() + ": loggableError") + e.Str("message", l.error.Error()+": loggableError") } func TestErrorMarshalFunc(t *testing.T) { @@ -549,7 +549,7 @@ func TestErrorMarshalFunc(t *testing.T) { // test overriding the ErrorMarshalFunc originalErrorMarshalFunc := ErrorMarshalFunc - defer func(){ + defer func() { ErrorMarshalFunc = originalErrorMarshalFunc }() diff --git a/sampler.go b/sampler.go index 3f00e1f..2360f0d 100644 --- a/sampler.go +++ b/sampler.go @@ -47,7 +47,7 @@ type BasicSampler struct { // Sample implements the Sampler interface. func (s *BasicSampler) Sample(lvl Level) bool { c := atomic.AddUint32(&s.counter, 1) - return c%s.N == 0 + return c%s.N == s.N-1 } // BurstSampler lets Burst events pass per Period then pass the decision to diff --git a/sampler_test.go b/sampler_test.go index 9010335..e42ad3a 100644 --- a/sampler_test.go +++ b/sampler_test.go @@ -15,7 +15,14 @@ var samplers = []struct { wantMax int }{ { - "BasicSampler", + "BasicSampler_1", + func() Sampler { + return &BasicSampler{N: 1} + }, + 100, 100, 100, + }, + { + "BasicSampler_5", func() Sampler { return &BasicSampler{N: 5} }, From 470da8d0bbd751f052924ef1354455b164e48f71 Mon Sep 17 00:00:00 2001 From: Olivier Poitrey Date: Sun, 16 Sep 2018 18:53:09 -0700 Subject: [PATCH 49/73] Fix failing test introduced by last commit --- log_example_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/log_example_test.go b/log_example_test.go index 56ee9c8..7344745 100644 --- a/log_example_test.go +++ b/log_example_test.go @@ -48,8 +48,8 @@ func ExampleLogger_Sample() { log.Info().Msg("message 3") log.Info().Msg("message 4") - // Output: {"level":"info","message":"message 2"} - // {"level":"info","message":"message 4"} + // Output: {"level":"info","message":"message 1"} + // {"level":"info","message":"message 3"} } type LevelNameHook struct{} From 8aa660046f4659ee0b748d7bda26e30e55ddb09f Mon Sep 17 00:00:00 2001 From: jayven Date: Mon, 17 Sep 2018 09:11:09 +0800 Subject: [PATCH 50/73] Add hlog.IDFromCtx --- hlog/hlog.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/hlog/hlog.go b/hlog/hlog.go index 2026214..f7c3a5a 100644 --- a/hlog/hlog.go +++ b/hlog/hlog.go @@ -129,7 +129,12 @@ func IDFromRequest(r *http.Request) (id xid.ID, ok bool) { if r == nil { return } - id, ok = r.Context().Value(idKey{}).(xid.ID) + return IDFromCtx(r.Context()) +} + +// IDFromCtx returns the unique id associated to the context if any. +func IDFromCtx(ctx context.Context) (id xid.ID, ok bool) { + id, ok = ctx.Value(idKey{}).(xid.ID) return } From 2da253048d091a86df3829694deb3da4be875a66 Mon Sep 17 00:00:00 2001 From: Olivier Poitrey Date: Sun, 16 Sep 2018 19:15:25 -0700 Subject: [PATCH 51/73] Fix binary test too --- binary_test.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/binary_test.go b/binary_test.go index d02e3f9..b4b52d9 100644 --- a/binary_test.go +++ b/binary_test.go @@ -6,6 +6,7 @@ import ( "bytes" "errors" "fmt" + // "io/ioutil" stdlog "log" "time" @@ -54,8 +55,8 @@ func ExampleLogger_Sample() { log.Info().Msg("message 4") fmt.Println(decodeIfBinaryToString(dst.Bytes())) - // Output: {"level":"info","message":"message 2"} - // {"level":"info","message":"message 4"} + // Output: {"level":"info","message":"message 1"} + // {"level":"info","message":"message 3"} } type LevelNameHook1 struct{} From 1c8b5945b1d99d764e42637a187df72713ebf290 Mon Sep 17 00:00:00 2001 From: Olivier Poitrey Date: Sun, 16 Sep 2018 19:21:33 -0700 Subject: [PATCH 52/73] Set theme jekyll-theme-hacker --- _config.yml | 1 + 1 file changed, 1 insertion(+) create mode 100644 _config.yml diff --git a/_config.yml b/_config.yml new file mode 100644 index 0000000..fc24e7a --- /dev/null +++ b/_config.yml @@ -0,0 +1 @@ +theme: jekyll-theme-hacker \ No newline at end of file From fc5bbcd9d6531878cc834d384a11e9138ff29d20 Mon Sep 17 00:00:00 2001 From: Olivier Poitrey Date: Sun, 16 Sep 2018 19:21:46 -0700 Subject: [PATCH 53/73] Create CNAME --- CNAME | 1 + 1 file changed, 1 insertion(+) create mode 100644 CNAME diff --git a/CNAME b/CNAME new file mode 100644 index 0000000..9ce57a6 --- /dev/null +++ b/CNAME @@ -0,0 +1 @@ +zerolog.io \ No newline at end of file From 84794124e939bbaa35f5c280d91c694bbf5cc51d Mon Sep 17 00:00:00 2001 From: Olivier Poitrey Date: Sun, 16 Sep 2018 23:21:59 -0700 Subject: [PATCH 54/73] Use gh-readme template for zerolog.io --- _config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_config.yml b/_config.yml index fc24e7a..a1e896d 100644 --- a/_config.yml +++ b/_config.yml @@ -1 +1 @@ -theme: jekyll-theme-hacker \ No newline at end of file +remote_theme: rs/gh-readme From 785a567b105b1aee32793e11d741627ba716afbe Mon Sep 17 00:00:00 2001 From: Olivier Poitrey Date: Tue, 18 Sep 2018 02:18:32 -0700 Subject: [PATCH 55/73] Add a mention to logbench --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 2383a0d..19d87fa 100644 --- a/README.md +++ b/README.md @@ -492,6 +492,8 @@ with zerolog library is [CSD](https://github.com/toravir/csd/). ## Benchmarks +See [logbench](http://hackemist.com/logbench/) for more comprehensive and up-to-date benchmarks. + All operations are allocation free (those numbers *include* JSON encoding): ```text From 624b3116d88f1f603dd430e0cff7522f40a99371 Mon Sep 17 00:00:00 2001 From: Thiago Caiubi Date: Tue, 18 Sep 2018 11:57:53 -0300 Subject: [PATCH 56/73] Fix sub-logger by context example (#106) Not quite sure but looks like the example is using the wrong API. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 19d87fa..91c75f0 100644 --- a/README.md +++ b/README.md @@ -364,7 +364,7 @@ hooked.Warn().Msg("") ### Pass a sub-logger by context ```go -ctx := log.With("component", "module").Logger().WithContext(ctx) +ctx := log.With().Str("component", "module").Logger().WithContext(ctx) log.Ctx(ctx).Info().Msg("hello world") From 972f27185c243602b2f5d2a428eb60e6ede49c76 Mon Sep 17 00:00:00 2001 From: Olivier Poitrey Date: Wed, 19 Sep 2018 00:17:39 -0700 Subject: [PATCH 57/73] Remove unused hook field on event --- event.go | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/event.go b/event.go index 71f0a9a..c623bdd 100644 --- a/event.go +++ b/event.go @@ -31,7 +31,6 @@ type Event struct { level Level done func(msg string) ch []Hook // hooks from context - h []Hook } // LogObjectMarshaler provides a strongly-typed and encoding-agnostic interface @@ -49,7 +48,7 @@ type LogArrayMarshaler interface { func newEvent(w LevelWriter, level Level) *Event { e := eventPool.Get().(*Event) e.buf = e.buf[:0] - e.h = e.h[:0] + e.ch = nil e.buf = enc.AppendBeginMarker(e.buf) e.w = w e.level = level @@ -114,14 +113,6 @@ func (e *Event) msg(msg string) { } } } - if len(e.h) > 0 { - e.h[0].Run(e, e.level, msg) - if len(e.h) > 1 { - for _, hook := range e.h[1:] { - hook.Run(e, e.level, msg) - } - } - } if msg != "" { e.buf = enc.AppendString(enc.AppendKey(e.buf, MessageFieldName), msg) } From e0f8de6c359435b241b622af8e9a2e6d5a5e012c Mon Sep 17 00:00:00 2001 From: Olivier Poitrey Date: Wed, 19 Sep 2018 00:18:07 -0700 Subject: [PATCH 58/73] Fix usage of sync.Pool The current usage of sync.Pool is leaky because it stores an arbitrary sized buffer into the pool. However, sync.Pool assumes that all items in the pool are interchangeable from a memory cost perspective. Due to the unbounded size of a buffer that may be added, it is possible for the pool to eventually pin arbitrarily large amounts of memory in a live-lock situation. As a simple fix, we just set a maximum size that we permit back into the pool. --- array.go | 20 +++++++++++++++++--- context.go | 6 +++--- diode/diode.go | 12 +++++++++++- event.go | 18 ++++++++++++++++-- fields.go | 6 +++--- 5 files changed, 50 insertions(+), 12 deletions(-) diff --git a/array.go b/array.go index 01b3eca..aa4f623 100644 --- a/array.go +++ b/array.go @@ -20,6 +20,20 @@ type Array struct { buf []byte } +func putArray(a *Array) { + // Proper usage of a sync.Pool requires each entry to have approximately + // the same memory cost. To obtain this property when the stored type + // contains a variably-sized buffer, we add a hard limit on the maximum buffer + // to place back in the pool. + // + // See https://golang.org/issue/23199 + const maxSize = 1 << 16 // 64KiB + if cap(a.buf) > maxSize { + return + } + arrayPool.Put(a) +} + // Arr creates an array to be added to an Event or Context. func Arr() *Array { a := arrayPool.Get().(*Array) @@ -38,7 +52,7 @@ func (a *Array) write(dst []byte) []byte { dst = append(append(dst, a.buf...)) } dst = enc.AppendArrayEnd(dst) - arrayPool.Put(a) + putArray(a) return dst } @@ -49,7 +63,7 @@ func (a *Array) Object(obj LogObjectMarshaler) *Array { obj.MarshalZerologObject(e) e.buf = enc.AppendEndMarker(e.buf) a.buf = append(enc.AppendArrayDelim(a.buf), e.buf...) - eventPool.Put(e) + putEvent(e) return a } @@ -80,7 +94,7 @@ func (a *Array) Err(err error) *Array { e.buf = e.buf[:0] e.appendObject(m) a.buf = append(enc.AppendArrayDelim(a.buf), e.buf...) - eventPool.Put(e) + putEvent(e) case error: a.buf = enc.AppendString(enc.AppendArrayDelim(a.buf), m.Error()) case string: diff --git a/context.go b/context.go index c277fd2..b843179 100644 --- a/context.go +++ b/context.go @@ -26,7 +26,7 @@ func (c Context) Fields(fields map[string]interface{}) Context { func (c Context) Dict(key string, dict *Event) Context { dict.buf = enc.AppendEndMarker(dict.buf) c.l.context = append(enc.AppendKey(c.l.context, key), dict.buf...) - eventPool.Put(dict) + putEvent(dict) return c } @@ -55,7 +55,7 @@ func (c Context) Object(key string, obj LogObjectMarshaler) Context { e := newEvent(levelWriterAdapter{ioutil.Discard}, 0) e.Object(key, obj) c.l.context = enc.AppendObjectData(c.l.context, e.buf) - eventPool.Put(e) + putEvent(e) return c } @@ -64,7 +64,7 @@ func (c Context) EmbedObject(obj LogObjectMarshaler) Context { e := newEvent(levelWriterAdapter{ioutil.Discard}, 0) e.EmbedObject(obj) c.l.context = enc.AppendObjectData(c.l.context, e.buf) - eventPool.Put(e) + putEvent(e) return c } diff --git a/diode/diode.go b/diode/diode.go index 8711ee5..751cafe 100644 --- a/diode/diode.go +++ b/diode/diode.go @@ -86,6 +86,16 @@ func (dw Writer) poll() { } p := *(*[]byte)(d) dw.w.Write(p) - bufPool.Put(p[:0]) + + // Proper usage of a sync.Pool requires each entry to have approximately + // the same memory cost. To obtain this property when the stored type + // contains a variably-sized buffer, we add a hard limit on the maximum buffer + // to place back in the pool. + // + // See https://golang.org/issue/23199 + const maxSize = 1 << 16 // 64KiB + if cap(p) <= maxSize { + bufPool.Put(p[:0]) + } } } diff --git a/event.go b/event.go index c623bdd..c67bf23 100644 --- a/event.go +++ b/event.go @@ -33,6 +33,20 @@ type Event struct { ch []Hook // hooks from context } +func putEvent(e *Event) { + // Proper usage of a sync.Pool requires each entry to have approximately + // the same memory cost. To obtain this property when the stored type + // contains a variably-sized buffer, we add a hard limit on the maximum buffer + // to place back in the pool. + // + // See https://golang.org/issue/23199 + const maxSize = 1 << 16 // 64KiB + if cap(e.buf) > maxSize { + return + } + eventPool.Put(e) +} + // LogObjectMarshaler provides a strongly-typed and encoding-agnostic interface // to be implemented by types used with Event/Context's Object methods. type LogObjectMarshaler interface { @@ -66,7 +80,7 @@ func (e *Event) write() (err error) { _, err = e.w.WriteLevel(e.level, e.buf) } } - eventPool.Put(e) + putEvent(e) return } @@ -141,7 +155,7 @@ func (e *Event) Dict(key string, dict *Event) *Event { } dict.buf = enc.AppendEndMarker(dict.buf) e.buf = append(enc.AppendKey(e.buf, key), dict.buf...) - eventPool.Put(dict) + putEvent(e) return e } diff --git a/fields.go b/fields.go index 336ecbf..b65885d 100644 --- a/fields.go +++ b/fields.go @@ -20,7 +20,7 @@ func appendFields(dst []byte, fields map[string]interface{}) []byte { e.buf = e.buf[:0] e.appendObject(val) dst = append(dst, e.buf...) - eventPool.Put(e) + putEvent(e) continue } switch val := val.(type) { @@ -36,7 +36,7 @@ func appendFields(dst []byte, fields map[string]interface{}) []byte { e.buf = e.buf[:0] e.appendObject(m) dst = append(dst, e.buf...) - eventPool.Put(e) + putEvent(e) case error: dst = enc.AppendString(dst, m.Error()) case string: @@ -54,7 +54,7 @@ func appendFields(dst []byte, fields map[string]interface{}) []byte { e.buf = e.buf[:0] e.appendObject(m) dst = append(dst, e.buf...) - eventPool.Put(e) + putEvent(e) case error: dst = enc.AppendString(dst, m.Error()) case string: From 338f9bc14084d22cb8eeacd6492861f8449d715c Mon Sep 17 00:00:00 2001 From: Olivier Poitrey Date: Wed, 19 Sep 2018 07:40:00 -0700 Subject: [PATCH 59/73] Fix typo --- event.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/event.go b/event.go index c67bf23..eec1286 100644 --- a/event.go +++ b/event.go @@ -155,7 +155,7 @@ func (e *Event) Dict(key string, dict *Event) *Event { } dict.buf = enc.AppendEndMarker(dict.buf) e.buf = append(enc.AppendKey(e.buf, key), dict.buf...) - putEvent(e) + putEvent(dict) return e } From 20ad1708e7e26f1672fcc0f12e2e501fefd979a5 Mon Sep 17 00:00:00 2001 From: Olivier Poitrey Date: Wed, 26 Sep 2018 09:52:52 -0700 Subject: [PATCH 60/73] Fix nil pointer exception on Discard when called with nil logger Fixes #108 --- event.go | 3 +++ log_test.go | 15 +++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/event.go b/event.go index eec1286..eda2853 100644 --- a/event.go +++ b/event.go @@ -92,6 +92,9 @@ func (e *Event) Enabled() bool { // Discard disables the event so Msg(f) won't print it. func (e *Event) Discard() *Event { + if e == nil { + return e + } e.level = Disabled return nil } diff --git a/log_test.go b/log_test.go index 32cf52f..a4b66e4 100644 --- a/log_test.go +++ b/log_test.go @@ -403,6 +403,21 @@ func TestSampling(t *testing.T) { } } +func TestDiscard(t *testing.T) { + out := &bytes.Buffer{} + log := New(out) + log.Log().Discard().Str("a", "b").Msgf("one %s %.1f %d %v", "two", 3.4, 5, errors.New("six")) + if got, want := decodeIfBinaryToString(out.Bytes()), ""; got != want { + t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want) + } + + // Double call + log.Log().Discard().Discard().Str("a", "b").Msgf("one %s %.1f %d %v", "two", 3.4, 5, errors.New("six")) + if got, want := decodeIfBinaryToString(out.Bytes()), ""; got != want { + t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want) + } +} + type levelWriter struct { ops []struct { l Level From 8e36cbf881d3eadd0c6455d55588af60c7cc4736 Mon Sep 17 00:00:00 2001 From: Vojtech Vitek Date: Thu, 27 Sep 2018 21:11:43 -0400 Subject: [PATCH 61/73] Don't call panic() and os.Exit(1) on .WithLevel() (#110) * Don't call panic() and os.Exit(1) on .WithLevel() * Explain behavior of WithLevel(), compared to Panic() & Fatal() --- log.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/log.go b/log.go index ed7223f..8eb45a8 100644 --- a/log.go +++ b/log.go @@ -292,22 +292,24 @@ func (l *Logger) Error() *Event { } // Fatal starts a new message with fatal level. The os.Exit(1) function -// is called by the Msg method. +// is called by the Msg method, which terminates the program immediately. // // You must call Msg on the returned event in order to send the event. func (l *Logger) Fatal() *Event { return l.newEvent(FatalLevel, func(msg string) { os.Exit(1) }) } -// Panic starts a new message with panic level. The message is also sent -// to the panic function. +// Panic starts a new message with panic level. The panic() function +// is called by the Msg method, which stops the ordinary flow of a goroutine. // // You must call Msg on the returned event in order to send the event. func (l *Logger) Panic() *Event { return l.newEvent(PanicLevel, func(msg string) { panic(msg) }) } -// WithLevel starts a new message with level. +// WithLevel starts a new message with level. Unlike Fatal and Panic +// methods, WithLevel does not terminate the program or stop the ordinary +// flow of a gourotine when used with their respective levels. // // You must call Msg on the returned event in order to send the event. func (l *Logger) WithLevel(level Level) *Event { @@ -321,9 +323,9 @@ func (l *Logger) WithLevel(level Level) *Event { case ErrorLevel: return l.Error() case FatalLevel: - return l.Fatal() + return l.newEvent(FatalLevel, nil) case PanicLevel: - return l.Panic() + return l.newEvent(PanicLevel, nil) case NoLevel: return l.Log() case Disabled: From baa31cfa8565110ee2db8c5a483f928697f5305c Mon Sep 17 00:00:00 2001 From: anthony <31537659+anthony92717773@users.noreply.github.com> Date: Wed, 31 Oct 2018 18:40:46 +0100 Subject: [PATCH 62/73] Fix nil pointer dereference when call Fields with a typed nil value (#112) --- fields.go | 96 ++++++++++++++++++++++++++++++++++++++++++++--------- log_test.go | 46 +++++++++++++++++++++++++ 2 files changed, 126 insertions(+), 16 deletions(-) diff --git a/fields.go b/fields.go index b65885d..6b62ecc 100644 --- a/fields.go +++ b/fields.go @@ -99,37 +99,101 @@ func appendFields(dst []byte, fields map[string]interface{}) []byte { case time.Duration: dst = enc.AppendDuration(dst, val, DurationFieldUnit, DurationFieldInteger) case *string: - dst = enc.AppendString(dst, *val) + if val != nil { + dst = enc.AppendString(dst, *val) + } else { + dst = enc.AppendNil(dst) + } case *bool: - dst = enc.AppendBool(dst, *val) + if val != nil { + dst = enc.AppendBool(dst, *val) + } else { + dst = enc.AppendNil(dst) + } case *int: - dst = enc.AppendInt(dst, *val) + if val != nil { + dst = enc.AppendInt(dst, *val) + } else { + dst = enc.AppendNil(dst) + } case *int8: - dst = enc.AppendInt8(dst, *val) + if val != nil { + dst = enc.AppendInt8(dst, *val) + } else { + dst = enc.AppendNil(dst) + } case *int16: - dst = enc.AppendInt16(dst, *val) + if val != nil { + dst = enc.AppendInt16(dst, *val) + } else { + dst = enc.AppendNil(dst) + } case *int32: - dst = enc.AppendInt32(dst, *val) + if val != nil { + dst = enc.AppendInt32(dst, *val) + } else { + dst = enc.AppendNil(dst) + } case *int64: - dst = enc.AppendInt64(dst, *val) + if val != nil { + dst = enc.AppendInt64(dst, *val) + } else { + dst = enc.AppendNil(dst) + } case *uint: - dst = enc.AppendUint(dst, *val) + if val != nil { + dst = enc.AppendUint(dst, *val) + } else { + dst = enc.AppendNil(dst) + } case *uint8: - dst = enc.AppendUint8(dst, *val) + if val != nil { + dst = enc.AppendUint8(dst, *val) + } else { + dst = enc.AppendNil(dst) + } case *uint16: - dst = enc.AppendUint16(dst, *val) + if val != nil { + dst = enc.AppendUint16(dst, *val) + } else { + dst = enc.AppendNil(dst) + } case *uint32: - dst = enc.AppendUint32(dst, *val) + if val != nil { + dst = enc.AppendUint32(dst, *val) + } else { + dst = enc.AppendNil(dst) + } case *uint64: - dst = enc.AppendUint64(dst, *val) + if val != nil { + dst = enc.AppendUint64(dst, *val) + } else { + dst = enc.AppendNil(dst) + } case *float32: - dst = enc.AppendFloat32(dst, *val) + if val != nil { + dst = enc.AppendFloat32(dst, *val) + } else { + dst = enc.AppendNil(dst) + } case *float64: - dst = enc.AppendFloat64(dst, *val) + if val != nil { + dst = enc.AppendFloat64(dst, *val) + } else { + dst = enc.AppendNil(dst) + } case *time.Time: - dst = enc.AppendTime(dst, *val, TimeFieldFormat) + if val != nil { + dst = enc.AppendTime(dst, *val, TimeFieldFormat) + } else { + dst = enc.AppendNil(dst) + } case *time.Duration: - dst = enc.AppendDuration(dst, *val, DurationFieldUnit, DurationFieldInteger) + if val != nil { + dst = enc.AppendDuration(dst, *val, DurationFieldUnit, DurationFieldInteger) + } else { + dst = enc.AppendNil(dst) + } case []string: dst = enc.AppendStrings(dst, val) case []bool: diff --git a/log_test.go b/log_test.go index a4b66e4..3ae0238 100644 --- a/log_test.go +++ b/log_test.go @@ -164,6 +164,52 @@ func TestFieldsMapPnt(t *testing.T) { } } +func TestFieldsMapNilPnt(t *testing.T) { + var ( + stringPnt *string + boolPnt *bool + intPnt *int + int8Pnt *int8 + int16Pnt *int16 + int32Pnt *int32 + int64Pnt *int64 + uintPnt *uint + uint8Pnt *uint8 + uint16Pnt *uint16 + uint32Pnt *uint32 + uint64Pnt *uint64 + float32Pnt *float32 + float64Pnt *float64 + durPnt *time.Duration + timePnt *time.Time + ) + out := &bytes.Buffer{} + log := New(out) + fields := map[string]interface{}{ + "string": stringPnt, + "bool": boolPnt, + "int": intPnt, + "int8": int8Pnt, + "int16": int16Pnt, + "int32": int32Pnt, + "int64": int64Pnt, + "uint": uintPnt, + "uint8": uint8Pnt, + "uint16": uint16Pnt, + "uint32": uint32Pnt, + "uint64": uint64Pnt, + "float32": float32Pnt, + "float64": float64Pnt, + "dur": durPnt, + "time": timePnt, + } + + log.Log().Fields(fields).Msg("") + if got, want := decodeIfBinaryToString(out.Bytes()), `{"bool":null,"dur":null,"float32":null,"float64":null,"int":null,"int16":null,"int32":null,"int64":null,"int8":null,"string":null,"time":null,"uint":null,"uint16":null,"uint32":null,"uint64":null,"uint8":null}`+"\n"; got != want { + t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want) + } +} + func TestFields(t *testing.T) { out := &bytes.Buffer{} log := New(out) From e7627a4f73bfd66654c39d79386e5374cc36ffec Mon Sep 17 00:00:00 2001 From: Olivier Poitrey Date: Wed, 31 Oct 2018 16:57:15 -0700 Subject: [PATCH 63/73] diode: let use a waiter instead of a poller by using 0 as a poolInterval --- diode/diode.go | 33 +++++++++++++++++++++++---------- diode/diode_example_test.go | 3 +-- diode/diode_test.go | 29 ++++++++++++++++++----------- 3 files changed, 42 insertions(+), 23 deletions(-) diff --git a/diode/diode.go b/diode/diode.go index 751cafe..cb0c50d 100644 --- a/diode/diode.go +++ b/diode/diode.go @@ -19,12 +19,16 @@ var bufPool = &sync.Pool{ type Alerter func(missed int) +type diodeFetcher interface { + diodes.Diode + Next() diodes.GenericDataType +} + // Writer is a io.Writer wrapper that uses a diode to make Write lock-free, // non-blocking and thread safe. type Writer struct { w io.Writer - d *diodes.ManyToOne - p *diodes.Poller + d diodeFetcher c context.CancelFunc done chan struct{} } @@ -35,25 +39,34 @@ type Writer struct { // // Use a diode.Writer when // -// wr := diode.NewWriter(w, 1000, 10 * time.Millisecond, func(missed int) { +// wr := diode.NewWriter(w, 1000, 0, func(missed int) { // log.Printf("Dropped %d messages", missed) // }) // log := zerolog.New(wr) // +// If poolInterval is greater than 0, a poller is used otherwise a waiter is +// used. // // See code.cloudfoundry.org/go-diodes for more info on diode. func NewWriter(w io.Writer, size int, poolInterval time.Duration, f Alerter) Writer { ctx, cancel := context.WithCancel(context.Background()) - d := diodes.NewManyToOne(size, diodes.AlertFunc(f)) dw := Writer{ - w: w, - d: d, - p: diodes.NewPoller(d, - diodes.WithPollingInterval(poolInterval), - diodes.WithPollingContext(ctx)), + w: w, c: cancel, done: make(chan struct{}), } + if f == nil { + f = func(int) {} + } + d := diodes.NewManyToOne(size, diodes.AlertFunc(f)) + if poolInterval > 0 { + dw.d = diodes.NewPoller(d, + diodes.WithPollingInterval(poolInterval), + diodes.WithPollingContext(ctx)) + } else { + dw.d = diodes.NewWaiter(d, + diodes.WithWaiterContext(ctx)) + } go dw.poll() return dw } @@ -80,7 +93,7 @@ func (dw Writer) Close() error { func (dw Writer) poll() { defer close(dw.done) for { - d := dw.p.Next() + d := dw.d.Next() if d == nil { return } diff --git a/diode/diode_example_test.go b/diode/diode_example_test.go index a097c57..3540db6 100644 --- a/diode/diode_example_test.go +++ b/diode/diode_example_test.go @@ -5,14 +5,13 @@ package diode_test import ( "fmt" "os" - "time" "github.com/rs/zerolog" "github.com/rs/zerolog/diode" ) func ExampleNewWriter() { - w := diode.NewWriter(os.Stdout, 1000, 10*time.Millisecond, func(missed int) { + w := diode.NewWriter(os.Stdout, 1000, 0, func(missed int) { fmt.Printf("Dropped %d messages\n", missed) }) log := zerolog.New(w) diff --git a/diode/diode_test.go b/diode/diode_test.go index 6171cb4..098bd44 100644 --- a/diode/diode_test.go +++ b/diode/diode_test.go @@ -16,7 +16,7 @@ import ( func TestNewWriter(t *testing.T) { buf := bytes.Buffer{} - w := diode.NewWriter(&buf, 1000, 10*time.Millisecond, func(missed int) { + w := diode.NewWriter(&buf, 1000, 0, func(missed int) { fmt.Printf("Dropped %d messages\n", missed) }) log := zerolog.New(w) @@ -33,15 +33,22 @@ func TestNewWriter(t *testing.T) { func Benchmark(b *testing.B) { log.SetOutput(ioutil.Discard) defer log.SetOutput(os.Stderr) - w := diode.NewWriter(ioutil.Discard, 100000, 10*time.Millisecond, nil) - log := zerolog.New(w) - defer w.Close() - - b.SetParallelism(1000) - b.RunParallel(func(pb *testing.PB) { - for pb.Next() { - log.Print("test") - } - }) + benchs := map[string]time.Duration{ + "Waiter": 0, + "Pooler": 10 * time.Millisecond, + } + for name, interval := range benchs { + b.Run(name, func(b *testing.B) { + w := diode.NewWriter(ioutil.Discard, 100000, interval, nil) + log := zerolog.New(w) + defer w.Close() + b.SetParallelism(1000) + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + log.Print("test") + } + }) + }) + } } From 51c79ca476f01c3f6574665d041b66c8ae91fdf0 Mon Sep 17 00:00:00 2001 From: Olivier Poitrey Date: Fri, 2 Nov 2018 13:06:29 -0700 Subject: [PATCH 64/73] Fix com typo --- diode/diode.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/diode/diode.go b/diode/diode.go index cb0c50d..07224fa 100644 --- a/diode/diode.go +++ b/diode/diode.go @@ -44,7 +44,7 @@ type Writer struct { // }) // log := zerolog.New(wr) // -// If poolInterval is greater than 0, a poller is used otherwise a waiter is +// If pollInterval is greater than 0, a poller is used otherwise a waiter is // used. // // See code.cloudfoundry.org/go-diodes for more info on diode. From 96f91bb4f5dae9f5026ed08500dd8692f9cdf67b Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Mon, 5 Nov 2018 02:15:13 -0800 Subject: [PATCH 65/73] Refactored zerolog.ConsoleWriter to allow customization (#92) * Added a simple benchmarking test for the ConsoleWriter * Refactored `zerolog.ConsoleWriter` to allow customization Closes #84 --- .gitignore | 1 + README.md | 36 ++++- console.go | 402 +++++++++++++++++++++++++++++++++++++----------- console_test.go | 234 ++++++++++++++++++++++++++-- pretty.png | Bin 386254 -> 144694 bytes 5 files changed, 560 insertions(+), 113 deletions(-) diff --git a/.gitignore b/.gitignore index daf913b..8ebe58b 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ # Folders _obj _test +tmp # Architecture specific extensions/prefixes *.[568vq] diff --git a/README.md b/README.md index 91c75f0..6241b59 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Zerolog's API is designed to provide both a great developer experience and stunn Uber's [zap](https://godoc.org/go.uber.org/zap) library pioneered this approach. Zerolog is taking this concept to the next level with a simpler to use API and even better performance. -To keep the code base and the API simple, zerolog focuses on efficient structured logging only. Pretty logging on the console is made possible using the provided (but inefficient) `zerolog.ConsoleWriter`. +To keep the code base and the API simple, zerolog focuses on efficient structured logging only. Pretty logging on the console is made possible using the provided (but inefficient) [`zerolog.ConsoleWriter`](#pretty-logging). ![Pretty Logging Image](pretty.png) @@ -60,7 +60,7 @@ func main() { // Output: {"time":1516134303,"level":"debug","message":"hello world"} ``` -> Note: By default log writes to `os.Stderr` +> Note: By default log writes to `os.Stderr` > Note: The default log level for `log.Print` is *debug* ### Contextual Logging @@ -252,14 +252,38 @@ sublogger.Info().Msg("hello world") ### Pretty logging +To log a human-friendly, colorized output, use `zerolog.ConsoleWriter`: + ```go -if isConsole { - log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) -} +log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) log.Info().Str("foo", "bar").Msg("Hello world") -// Output: 1494567715 |INFO| Hello world foo=bar +// Output: 3:04PM INF Hello World foo=bar +``` + +To customize the configuration and formatting: + +```go +output := zerolog.ConsoleWriter{Out: os.Stdout, TimeFormat: time.RFC3339} +output.FormatLevel = func(i interface{}) string { + return strings.ToUpper(fmt.Sprintf("| %-6s|", i)) +} +output.FormatMessage = func(i interface{}) string { + return fmt.Sprintf("***%s****", i) +} +output.FormatFieldName = func(i interface{}) string { + return fmt.Sprintf("%s:", i) +} +output.FormatFieldValue = func(i interface{}) string { + return strings.ToUpper(fmt.Sprintf("%s", i)) +} + +log := zerolog.New(output).With().Timestamp().Logger() + +log.Info().Str("foo", "bar").Msg("Hello World") + +// Output: 2006-01-02T15:04:05Z07:00 | INFO | ***Hello World**** foo:BAR ``` ### Sub dictionary diff --git a/console.go b/console.go index 98de5d7..95bfd27 100644 --- a/console.go +++ b/console.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "io" + "os" "sort" "strconv" "strings" @@ -13,133 +14,260 @@ import ( ) const ( - cReset = 0 - cBold = 1 - cRed = 31 - cGreen = 32 - cYellow = 33 - cBlue = 34 - cMagenta = 35 - cCyan = 36 - cGray = 37 - cDarkGray = 90 + colorBold = iota + 1 + colorFaint ) -var consoleBufPool = sync.Pool{ - New: func() interface{} { - return bytes.NewBuffer(make([]byte, 0, 100)) - }, -} +const ( + colorBlack = iota + 30 + colorRed + colorGreen + colorYellow + colorBlue + colorMagenta + colorCyan + colorWhite +) -// LevelWidth defines the desired character width of the log level column. -// Default 0 does not trim or pad (variable width based level text, e.g. "INFO" or "ERROR") -var LevelWidth = 0 +var ( + consoleBufPool = sync.Pool{ + New: func() interface{} { + return bytes.NewBuffer(make([]byte, 0, 100)) + }, + } -// ConsoleWriter reads a JSON object per write operation and output an -// optionally colored human readable version on the Out writer. + consoleDefaultTimeFormat = time.Kitchen + consoleDefaultFormatter = func(i interface{}) string { return fmt.Sprintf("%s", i) } + consoleDefaultPartsOrder = []string{ + TimestampFieldName, + LevelFieldName, + CallerFieldName, + MessageFieldName, + } + + consoleNoColor = false + consoleTimeFormat = consoleDefaultTimeFormat +) + +// Formatter transforms the input into a formatted string. +type Formatter func(interface{}) string + +// ConsoleWriter parses the JSON input and writes it in an +// (optionally) colorized, human-friendly format to Out. type ConsoleWriter struct { - Out io.Writer + // Out is the output destination. + Out io.Writer + + // NoColor disables the colorized output. NoColor bool + + // TimeFormat specifies the format for timestamp in output. + TimeFormat string + + // PartsOrder defines the order of parts in output. + PartsOrder []string + + FormatTimestamp Formatter + FormatLevel Formatter + FormatCaller Formatter + FormatMessage Formatter + FormatFieldName Formatter + FormatFieldValue Formatter + FormatErrFieldName Formatter + FormatErrFieldValue Formatter } +// NewConsoleWriter creates and initializes a new ConsoleWriter. +func NewConsoleWriter(options ...func(w *ConsoleWriter)) ConsoleWriter { + w := ConsoleWriter{ + Out: os.Stdout, + TimeFormat: consoleDefaultTimeFormat, + PartsOrder: consoleDefaultPartsOrder, + } + + for _, opt := range options { + opt(&w) + } + + return w +} + +// Write transforms the JSON input with formatters and appends to w.Out. func (w ConsoleWriter) Write(p []byte) (n int, err error) { - var event map[string]interface{} + if w.PartsOrder == nil { + w.PartsOrder = consoleDefaultPartsOrder + } + if w.TimeFormat == "" && consoleTimeFormat != consoleDefaultTimeFormat { + consoleTimeFormat = consoleDefaultTimeFormat + } + if w.TimeFormat != "" && consoleTimeFormat != w.TimeFormat { + consoleTimeFormat = w.TimeFormat + } + if w.NoColor == false && consoleNoColor != false { + consoleNoColor = false + } + if w.NoColor == true && consoleNoColor != w.NoColor { + consoleNoColor = w.NoColor + } + + var buf = consoleBufPool.Get().(*bytes.Buffer) + defer consoleBufPool.Put(buf) + + var evt map[string]interface{} p = decodeIfBinaryToBytes(p) d := json.NewDecoder(bytes.NewReader(p)) d.UseNumber() - err = d.Decode(&event) + err = d.Decode(&evt) if err != nil { - return + return n, fmt.Errorf("cannot decode event: %s", err) } - buf := consoleBufPool.Get().(*bytes.Buffer) - defer consoleBufPool.Put(buf) - lvlColor := cReset - level := "????" - if l, ok := event[LevelFieldName].(string); ok { - if !w.NoColor { - lvlColor = levelColor(l) - } - level = strings.ToUpper(l) - if LevelWidth > 0 { - if padding := LevelWidth - len(level); padding > 0 { - level += strings.Repeat(" ", padding) - } else { - level = level[0:LevelWidth] - } - } + + for _, p := range w.PartsOrder { + w.writePart(buf, evt, p) } - fmt.Fprintf(buf, "%s |%s| %s", - colorize(formatTime(event[TimestampFieldName]), cDarkGray, !w.NoColor), - colorize(level, lvlColor, !w.NoColor), - colorize(event[MessageFieldName], cReset, !w.NoColor)) - fields := make([]string, 0, len(event)) - for field := range event { + + w.writeFields(evt, buf) + + buf.WriteByte('\n') + buf.WriteTo(w.Out) + return len(p), nil +} + +// writeFields appends formatted key-value pairs to buf. +func (w ConsoleWriter) writeFields(evt map[string]interface{}, buf *bytes.Buffer) { + var fields = make([]string, 0, len(evt)) + for field := range evt { switch field { - case LevelFieldName, TimestampFieldName, MessageFieldName: + case LevelFieldName, TimestampFieldName, MessageFieldName, CallerFieldName: continue } fields = append(fields, field) } sort.Strings(fields) - for _, field := range fields { - fmt.Fprintf(buf, " %s=", colorize(field, cCyan, !w.NoColor)) - switch value := event[field].(type) { - case string: - if needsQuote(value) { - buf.WriteString(strconv.Quote(value)) - } else { - buf.WriteString(value) + + if len(fields) > 0 { + buf.WriteByte(' ') + } + + // Move the "error" field to the front + ei := sort.Search(len(fields), func(i int) bool { return fields[i] >= ErrorFieldName }) + if ei < len(fields) && fields[ei] == ErrorFieldName { + fields[ei] = "" + fields = append([]string{ErrorFieldName}, fields...) + var xfields = make([]string, 0, len(fields)) + for _, field := range fields { + if field == "" { // Skip empty fields + continue } - case json.Number: - fmt.Fprint(buf, value) - default: - b, err := json.Marshal(value) - if err != nil { - fmt.Fprintf(buf, "[error: %v]", err) + xfields = append(xfields, field) + } + fields = xfields + } + + for i, field := range fields { + var fn Formatter + var fv Formatter + + if field == ErrorFieldName { + if w.FormatErrFieldName == nil { + fn = consoleDefaultFormatErrFieldName } else { - fmt.Fprint(buf, string(b)) + fn = w.FormatErrFieldName + } + + if w.FormatErrFieldValue == nil { + fv = consoleDefaultFormatErrFieldValue + } else { + fv = w.FormatErrFieldValue + } + } else { + if w.FormatFieldName == nil { + fn = consoleDefaultFormatFieldName + } else { + fn = w.FormatFieldName + } + + if w.FormatFieldValue == nil { + fv = consoleDefaultFormatFieldValue + } else { + fv = w.FormatFieldValue } } + + buf.WriteString(fn(field)) + + switch fValue := evt[field].(type) { + case string: + if needsQuote(fValue) { + buf.WriteString(fv(strconv.Quote(fValue))) + } else { + buf.WriteString(fv(fValue)) + } + case json.Number: + buf.WriteString(fv(fValue)) + default: + b, err := json.Marshal(fValue) + if err != nil { + fmt.Fprintf(buf, colorize("[error: %v]", colorRed, w.NoColor), err) + } else { + fmt.Fprint(buf, fv(b)) + } + } + + if i < len(fields)-1 { // Skip space for last field + buf.WriteByte(' ') + } } - buf.WriteByte('\n') - buf.WriteTo(w.Out) - n = len(p) - return } -func formatTime(t interface{}) string { - switch t := t.(type) { - case string: - return t - case json.Number: - u, _ := t.Int64() - return time.Unix(u, 0).Format(time.RFC3339) - } - return "" -} +// writePart appends a formatted part to buf. +func (w ConsoleWriter) writePart(buf *bytes.Buffer, evt map[string]interface{}, p string) { + var f Formatter -func colorize(s interface{}, color int, enabled bool) string { - if !enabled { - return fmt.Sprintf("%v", s) - } - return fmt.Sprintf("\x1b[%dm%v\x1b[0m", color, s) -} - -func levelColor(level string) int { - switch level { - case "debug": - return cMagenta - case "info": - return cGreen - case "warn": - return cYellow - case "error", "fatal", "panic": - return cRed + switch p { + case LevelFieldName: + if w.FormatLevel == nil { + f = consoleDefaultFormatLevel + } else { + f = w.FormatLevel + } + case TimestampFieldName: + if w.FormatTimestamp == nil { + f = consoleDefaultFormatTimestamp + } else { + f = w.FormatTimestamp + } + case MessageFieldName: + if w.FormatMessage == nil { + f = consoleDefaultFormatMessage + } else { + f = w.FormatMessage + } + case CallerFieldName: + if w.FormatCaller == nil { + f = consoleDefaultFormatCaller + } else { + f = w.FormatCaller + } default: - return cReset + if w.FormatFieldValue == nil { + f = consoleDefaultFormatFieldValue + } else { + f = w.FormatFieldValue + } + } + + var s = f(evt[p]) + + if len(s) > 0 { + buf.WriteString(s) + if p != w.PartsOrder[len(w.PartsOrder)-1] { // Skip space for last part + buf.WriteByte(' ') + } } } +// needsQuote returns true when the string s should be quoted in output. func needsQuote(s string) bool { for i := range s { if s[i] < 0x20 || s[i] > 0x7e || s[i] == ' ' || s[i] == '\\' || s[i] == '"' { @@ -148,3 +276,89 @@ func needsQuote(s string) bool { } return false } + +// colorize returns the string s wrapped in ANSI code c, unless disabled is true. +func colorize(s interface{}, c int, disabled bool) string { + if disabled { + return fmt.Sprintf("%s", s) + } + return fmt.Sprintf("\x1b[%dm%v\x1b[0m", c, s) +} + +// ----- DEFAULT FORMATTERS --------------------------------------------------- + +var ( + consoleDefaultFormatTimestamp = func(i interface{}) string { + t := "" + if tt, ok := i.(string); ok { + ts, err := time.Parse(time.RFC3339, tt) + if err != nil { + t = tt + } else { + t = ts.Format(consoleTimeFormat) + } + } + return colorize(t, colorFaint, consoleNoColor) + } + + consoleDefaultFormatLevel = func(i interface{}) string { + var l string + if ll, ok := i.(string); ok { + switch ll { + case "debug": + l = colorize("DBG", colorYellow, consoleNoColor) + case "info": + l = colorize("INF", colorGreen, consoleNoColor) + case "warn": + l = colorize("WRN", colorRed, consoleNoColor) + case "error": + l = colorize(colorize("ERR", colorRed, consoleNoColor), colorBold, consoleNoColor) + case "fatal": + l = colorize(colorize("FTL", colorRed, consoleNoColor), colorBold, consoleNoColor) + case "panic": + l = colorize(colorize("PNC", colorRed, consoleNoColor), colorBold, consoleNoColor) + default: + l = colorize("???", colorBold, consoleNoColor) + } + } else { + l = strings.ToUpper(fmt.Sprintf("%s", i))[0:3] + } + return l + } + + consoleDefaultFormatCaller = func(i interface{}) string { + var c string + if cc, ok := i.(string); ok { + c = cc + } + if len(c) > 0 { + cwd, err := os.Getwd() + if err == nil { + c = strings.TrimPrefix(c, cwd) + c = strings.TrimPrefix(c, "/") + } + c = colorize(c, colorBold, consoleNoColor) + colorize(" >", colorFaint, consoleNoColor) + } + return c + } + + consoleDefaultFormatMessage = func(i interface{}) string { + return fmt.Sprintf("%s", i) + } + + consoleDefaultFormatFieldName = func(i interface{}) string { + return colorize(fmt.Sprintf("%s=", i), colorFaint, consoleNoColor) + } + + consoleDefaultFormatFieldValue = func(i interface{}) string { + return fmt.Sprintf("%s", i) + } + + consoleDefaultFormatErrFieldName = func(i interface{}) string { + return colorize(fmt.Sprintf("%s=", i), colorRed, consoleNoColor) + } + + consoleDefaultFormatErrFieldValue = func(i interface{}) string { + return colorize(fmt.Sprintf("%s", i), colorRed, consoleNoColor) + } +) diff --git a/console_test.go b/console_test.go index a6e7e06..5bdfdaa 100644 --- a/console_test.go +++ b/console_test.go @@ -2,29 +2,237 @@ package zerolog_test import ( "bytes" + "fmt" + "io/ioutil" "os" "strings" "testing" + "time" "github.com/rs/zerolog" ) -func ExampleConsoleWriter_Write() { +func ExampleConsoleWriter() { log := zerolog.New(zerolog.ConsoleWriter{Out: os.Stdout, NoColor: true}) - log.Info().Msg("hello world") - // Output: |INFO| hello world + log.Info().Str("foo", "bar").Msg("Hello World") + // Output: INF Hello World foo=bar } -func TestConsoleWriterNumbers(t *testing.T) { - buf := &bytes.Buffer{} - log := zerolog.New(zerolog.ConsoleWriter{Out: buf, NoColor: true}) - log.Info(). - Float64("float", 1.23). - Uint64("small", 123). - Uint64("big", 1152921504606846976). - Msg("msg") - if got, want := strings.TrimSpace(buf.String()), " |INFO| msg big=1152921504606846976 float=1.23 small=123"; got != want { - t.Errorf("\ngot:\n%s\nwant:\n%s", got, want) +func ExampleConsoleWriter_customFormatters() { + out := zerolog.ConsoleWriter{Out: os.Stdout, NoColor: true} + out.FormatLevel = func(i interface{}) string { return strings.ToUpper(fmt.Sprintf("%-6s|", i)) } + out.FormatFieldName = func(i interface{}) string { return fmt.Sprintf("%s:", i) } + out.FormatFieldValue = func(i interface{}) string { return strings.ToUpper(fmt.Sprintf("%s", i)) } + log := zerolog.New(out) + + log.Info().Str("foo", "bar").Msg("Hello World") + // Output: INFO | Hello World foo:BAR +} + +func ExampleNewConsoleWriter() { + out := zerolog.NewConsoleWriter() + out.NoColor = true // For testing purposes only + log := zerolog.New(out) + + log.Debug().Str("foo", "bar").Msg("Hello World") + // Output: DBG Hello World foo=bar +} + +func ExampleNewConsoleWriter_customFormatters() { + out := zerolog.NewConsoleWriter( + func(w *zerolog.ConsoleWriter) { + // Customize time format + w.TimeFormat = time.RFC822 + // Customize level formatting + w.FormatLevel = func(i interface{}) string { return strings.ToUpper(fmt.Sprintf("[%-5s]", i)) } + }, + ) + out.NoColor = true // For testing purposes only + + log := zerolog.New(out) + + log.Info().Str("foo", "bar").Msg("Hello World") + // Output: [INFO ] Hello World foo=bar +} + +func TestConsoleLogger(t *testing.T) { + t.Run("Numbers", func(t *testing.T) { + buf := &bytes.Buffer{} + log := zerolog.New(zerolog.ConsoleWriter{Out: buf, NoColor: true}) + log.Info(). + Float64("float", 1.23). + Uint64("small", 123). + Uint64("big", 1152921504606846976). + Msg("msg") + if got, want := strings.TrimSpace(buf.String()), " INF msg big=1152921504606846976 float=1.23 small=123"; got != want { + t.Errorf("\ngot:\n%s\nwant:\n%s", got, want) + } + }) +} + +func TestConsoleWriter(t *testing.T) { + t.Run("Default field formatter", func(t *testing.T) { + buf := &bytes.Buffer{} + w := zerolog.ConsoleWriter{Out: buf, NoColor: true, PartsOrder: []string{"foo"}} + + _, err := w.Write([]byte(`{"foo" : "DEFAULT"}`)) + if err != nil { + t.Errorf("Unexpected error when writing output: %s", err) + } + + expectedOutput := "DEFAULT foo=DEFAULT\n" + actualOutput := buf.String() + if actualOutput != expectedOutput { + t.Errorf("Unexpected output %q, want: %q", actualOutput, expectedOutput) + } + }) + + t.Run("Write colorized", func(t *testing.T) { + buf := &bytes.Buffer{} + w := zerolog.ConsoleWriter{Out: buf, NoColor: false} + + _, err := w.Write([]byte(`{"level" : "warn", "message" : "Foobar"}`)) + if err != nil { + t.Errorf("Unexpected error when writing output: %s", err) + } + + expectedOutput := "\x1b[2m\x1b[0m \x1b[31mWRN\x1b[0m Foobar\n" + actualOutput := buf.String() + if actualOutput != expectedOutput { + t.Errorf("Unexpected output %q, want: %q", actualOutput, expectedOutput) + } + }) + + t.Run("Write fields", func(t *testing.T) { + buf := &bytes.Buffer{} + w := zerolog.ConsoleWriter{Out: buf, NoColor: true} + + d := time.Unix(0, 0).UTC().Format(time.RFC3339) + _, err := w.Write([]byte(`{"time" : "` + d + `", "level" : "debug", "message" : "Foobar", "foo" : "bar"}`)) + if err != nil { + t.Errorf("Unexpected error when writing output: %s", err) + } + + expectedOutput := "12:00AM DBG Foobar foo=bar\n" + actualOutput := buf.String() + if actualOutput != expectedOutput { + t.Errorf("Unexpected output %q, want: %q", actualOutput, expectedOutput) + } + }) + + t.Run("Write error field", func(t *testing.T) { + buf := &bytes.Buffer{} + w := zerolog.ConsoleWriter{Out: buf, NoColor: true} + + d := time.Unix(0, 0).UTC().Format(time.RFC3339) + evt := `{"time" : "` + d + `", "level" : "error", "message" : "Foobar", "aaa" : "bbb", "error" : "Error"}` + // t.Log(evt) + + _, err := w.Write([]byte(evt)) + if err != nil { + t.Errorf("Unexpected error when writing output: %s", err) + } + + expectedOutput := "12:00AM ERR Foobar error=Error aaa=bbb\n" + actualOutput := buf.String() + if actualOutput != expectedOutput { + t.Errorf("Unexpected output %q, want: %q", actualOutput, expectedOutput) + } + }) + + t.Run("Write caller field", func(t *testing.T) { + buf := &bytes.Buffer{} + w := zerolog.ConsoleWriter{Out: buf, NoColor: true} + + cwd, err := os.Getwd() + if err != nil { + t.Fatalf("Cannot get working directory: %s", err) + } + + d := time.Unix(0, 0).UTC().Format(time.RFC3339) + evt := `{"time" : "` + d + `", "level" : "debug", "message" : "Foobar", "foo" : "bar", "caller" : "` + cwd + `/foo/bar.go"}` + // t.Log(evt) + + _, err = w.Write([]byte(evt)) + if err != nil { + t.Errorf("Unexpected error when writing output: %s", err) + } + + expectedOutput := "12:00AM DBG foo/bar.go > Foobar foo=bar\n" + actualOutput := buf.String() + if actualOutput != expectedOutput { + t.Errorf("Unexpected output %q, want: %q", actualOutput, expectedOutput) + } + }) + + t.Run("Write JSON field", func(t *testing.T) { + buf := &bytes.Buffer{} + w := zerolog.ConsoleWriter{Out: buf, NoColor: true} + + evt := `{"level" : "debug", "message" : "Foobar", "foo" : [1, 2, 3], "bar" : true}` + // t.Log(evt) + + _, err := w.Write([]byte(evt)) + if err != nil { + t.Errorf("Unexpected error when writing output: %s", err) + } + + expectedOutput := " DBG Foobar bar=true foo=[1,2,3]\n" + actualOutput := buf.String() + if actualOutput != expectedOutput { + t.Errorf("Unexpected output %q, want: %q", actualOutput, expectedOutput) + } + }) +} + +func TestConsoleWriterConfiguration(t *testing.T) { + t.Run("Sets TimeFormat", func(t *testing.T) { + buf := &bytes.Buffer{} + w := zerolog.ConsoleWriter{Out: buf, NoColor: true, TimeFormat: time.RFC3339} + + d := time.Unix(0, 0).UTC().Format(time.RFC3339) + evt := `{"time" : "` + d + `", "level" : "info", "message" : "Foobar"}` + + _, err := w.Write([]byte(evt)) + if err != nil { + t.Errorf("Unexpected error when writing output: %s", err) + } + + expectedOutput := "1970-01-01T00:00:00Z INF Foobar\n" + actualOutput := buf.String() + if actualOutput != expectedOutput { + t.Errorf("Unexpected output %q, want: %q", actualOutput, expectedOutput) + } + }) + + t.Run("Sets PartsOrder", func(t *testing.T) { + buf := &bytes.Buffer{} + w := zerolog.ConsoleWriter{Out: buf, NoColor: true, PartsOrder: []string{"message", "level"}} + + evt := `{"level" : "info", "message" : "Foobar"}` + _, err := w.Write([]byte(evt)) + if err != nil { + t.Errorf("Unexpected error when writing output: %s", err) + } + + expectedOutput := "Foobar INF\n" + actualOutput := buf.String() + if actualOutput != expectedOutput { + t.Errorf("Unexpected output %q, want: %q", actualOutput, expectedOutput) + } + }) +} + +func BenchmarkConsoleWriter(b *testing.B) { + b.ResetTimer() + b.ReportAllocs() + + var msg = []byte(`{"level" : "info", "foo" : "bar", "message" : "HELLO", "time" : "1990-01-01"}`) + + w := zerolog.ConsoleWriter{Out: ioutil.Discard, NoColor: false} + + for i := 0; i < b.N; i++ { + w.Write(msg) } } diff --git a/pretty.png b/pretty.png index c18116496762d5519d3e59a2aeb7b885e7d15e68..34e43085ff315341b1902509f462b5e437f1304c 100644 GIT binary patch literal 144694 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

e|J9tMUg#4>R!uWvx3o}B=;~zVQ!`c? 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

    o5J=FUM|6qyZ;C<4y7G}@TYnfJn>Hj-+dI; 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 >7z=BjNxOjTVva7{W`XyYhun{AMD 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

    47$jXat}W>dhqvS=`cI90JIWkovbKF0`A+x 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%0AgsK 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 CI zOJT-3WcFaQ^DXcPK%bVUAv9B2ci<&=OnyrWL+V%^V%A6iNA%rnDD74``Mu(b&okS~ z;OVutp>?+K;1`7AY*NNnBIAiS-ij~6wn*Nq`Kc++nllksJfU8#am?NfSzcE_brz2H zz41JUtGcF6*n>>P>KKU0Xw2zuua5Kv%V_I*N2T=ZjXe=!^Si=lnOg`*K<8^0e%Wfu z@owQ-6O>Bgdh&~=heO`Sr(_w$kPl-dk-*zBiCTWLmp_PpX4uYvk>l-0QUmW^B|+59Q^` zt1IEi_p(mLwj_3$53Sc`RO!9io9S;viYO9N#vOmJ8653jNyph=+y-44X4|H-FuK!w z^K0(5p~Uj&VeeZ(ynysG$qdfyK-{o|Q`gUrd42din79q4cAJ9QD|Z z?V8%qAu|nFJ8)O}($5;y7z}bqXF8M^^iZ|?EN4G2&<$#}y<1_mz2wfK*J^#H{Ou_kwhYXUF%vg%?8& zNkaQ$Av5=J)Kr(xLSC6F++hWaT2kWd*ckU886HsC>4p zRhL-oHIH8i#T*+?*Bl3}QMwSChVQcJ9spFk*Y(188){LTEwMF!e);^{=4I)%?cUksgdHtomag!_zByX2 zvAn}dw($qu!VM*G_UkN>A8sK*ABOm;$H>Q;9NMcnj*F5p-5`&7i(^U1Z+?CbsR0%Z zVZ^b9SDf^Ep&dwfJ`kFP_cp*ziUsVhPY<^1?r=cP41Fk@>MN|aW)Up9f;4Xe26m2D zizx=n_4H(B7+5R$p&Ct1pdw^!=Hpop`rwu2Gx%rS7Mrj*t??;L<6ZO9qp!u$TD(mh z*>C!@^$mMaQynOPK9@jEL8@SOs)C6cV%f>KJ+!!MQS@XlryiU zUN!$Q;?a?+Q7~uJI621aDmXfV%G(`7x1+bsM9|7*db)fVUe}d$vBM0#c(!_$tqQ#V#k%IzDNf6N7o6;+FA7RcpYm(l3A1j~ zqP~wh_ndMCt*Cq8nmg@erg-tUylrMvjgBbaL`5|osCd1Upk-Az9LYWEPXx%VidUsv zPWJTQvCU5V-TGebV0*&X4Tr6VxMQ>IIdunX<6XX&dkw7_kI~_tsj+0Zl+1ejj(tO| z8by88P|K|9cp~(6KD-baKueF7yI&w-{|zX)_`qLZ7!eU~geP#^>joZwyb_mUv8VoX z1ZEVZP&C>*>RlhSwF0dDloC&;h^yN$R~D8hYK+(7ejZA#Y5dr4(Y4lMh2I3J5Pi&i zJOnr%=6nV+55+Ag*C!eon|Jb4`;%7rD*@(fpG#c|UbM2oA0r8~8ploQlk4%oEch3; z_CakUr9Nq4y6}g+K1dNi5!t(~#Qgg!!mhNIgJz-VY9FU)ZdOku(DZC=XOM2B*V~n) zB~Wx(eG0F{2dlmRl?M6|IKr`ELEI$23tR-49Qe$eWfw-nTUd`n6OU#`I~tP5vt21R z4tcb5o%tC?l2UG`M==&-?c73=?EqPpf`b=l$*>I5HjXBKmOpt0LRyksgCIxIIm~^ zkr+y#$4Ivm=HIvfi{HW*xC*$vX8AdJk7kZFeWVlb6e7uJD?BiAF4-l}O@ftvueO{2 z>GeYXr+$sP_%=j(%F*N-@$s(;{_I8l!BGufN|8<>7>Y*T-er z+e|(Dh0Ec2=`=F_HwI@v4z7j&jz3a%`rq1nudt@NZtq)fRBjtl5s?}Z0hK1b6AMLZ z6a}P4MWlmtLP_i>Qj{t+bm>TkP@+;o4?T3Gga82o1V~7IRhosrt}qfg9?WLUZzLPLInUD}6< zc&#%$kh7qNo9%#vPxZ~&LM&Y)^mK;+ZguFrX`{bpSj};gh;53a zV+dUg)DoxO(fg7x73|c zUCMWk?G!c|fsckQq+Im%sjv7Mhc}CHNYz8{ENF=q+%EmzUh(&Z?XhS;y#L~cZ9`{n z|K}6-i-kMi=437RI3~Q{ z|7O9pF>{xP0lO{POTnqf#FrBcaZ8-1)@EKJGs3q0rE1m;MP-PlrKzEY80oL3F{Q>5 z_r>}xElHk^P1Y4B^+yKc;;6&Byu;QCHXTJgHAtC{dKtKkAX=dze?ls5)LU2g+i*Om zUOR&4KDT-G3yPy6ZBsGFnr&F;RFurO6-*cn$TZuWJ?@^=DdJ zs^i0FvP*Nfq1*eF*A#uMk=^Xj5|6jmu>R0t0`o2kJ`cFBg)l4wsHYLsqL7Hd3;xd6 za$Ia@gMCg!rDTk83&FkF>A+c$E5jQ#qvj8>)nEC?JoEBU$al}BTDyOZDQ3E#vU{<1 zwgIESoWFT(&PHHJyWBU+v3ldS5I)XMb39bgL%-yte;PPo`!UJ8TBt&1#9Mo!{Y@Ww zFrW>24}I{A7oZH(pE^tU_X+=1tR&%6y>Lb^^9*+~a2m6)f#+J_OqUnAY;)>4{rY}) z7kL;YH2k3fF_3+>sT-G0xs_A$`T*Bt2IJ)c-CQcA$Ox!LYkOtT>AVl zO_Gh_h|fb?4Z8UBKbrdi&*?+7VdYsAje*rVR-|>Ou^4!PApu_SP{B`cB3(urq-E8XdQff*;wk`z4A-7b zW?fS70HrE;=+o|}i`jlFc`7IbdWiMb<_k@lbBqh}An(}NpPQ4Qzkp-k>u1xBgmqxB z#rKAItZVze&V3Z$axLScpSPO@s=#g{)3Yg4_I})6jB$~aLlgAW?ZL5lnkSI#6e_*z zE$&l%ZY{t}LVfqQ9(~9-jo~@z`I}M`iZUFXi}p;>x>h&oB-z)re%+e-v7A+C z?%Oa5r-q*!suxzMEw$1Mr2D9s#wYkk%b6e`e!m+)%Isx;Hlt>1)`C?sqjiNQkMTKA zjRb&L4~v>mk(0_ri)0<`n@KV7N4OYY^OZeUzwN6#xjg8vxfSWlBEs(`>RV1_L)Q8+R2n~A$%gqA}_S8QAJA+RT!$e zs**VD(tFA^@S%g}&Vyk8=^Mct9Mu^@aX%!rlPm?9pC!1UArGv)>jQlEnWhtJuumDG zQq4IISz!CTyu5^m8_DcGe{r?xwiZ*QE4*pp#lcf_x6a(&g8)H}Xe++#J<)NpyCXCOrEW_2ZKMgHUPcWq%oP?!rt_ zQ^%Ko(tdbqbJ+uMbT>mYX!?zJYXeICg8V>WDDp4M5;lZ(Td3?&lJr(pnAnnR|I7c$ zW#!QRqsR??WEYT$iD_9RakkJjV){?|6nK1o)k1$wAaTMvQ4E#j4at=|e}J;1FbXr1 zru$EIwCN>|4XvDwKU`1?GmG{S%*DWa<+A)%21H=UuZ@?fuXV+17WB){pF>2M01MFE zur3|Du0ycj{vx_DLT*LQi>bunSwYC!M`7kpYx}X&6cKqZqp?h=q?oCksThdO5zuqPO%1xOY(kl?#nhqdNbb_NV`0U z7>wg%2(=$t8m+#K6Lai{jjE&a{z~E;rgKSK)Rc{0@2bj4q=kc>VWKw z*8cqW^1c^6k}ZrzflVeFvy(bgDjw{BG(^{CE^@BYdFu0`-k>(yOWS)FIQ-zSMvRK} zJ4$|#C90@}AV;_MVGL*ph-a!eRQ&XsV0pGxT|rv+ zGR~{W71Osi#O0oj;WK1S6Hd9NHo{+U$&@UA{P8Lpc>dW_f0z%uL_%69$w~4BeURAO z17i7o0wrI%E_A;9eZSi$fX=L_vgxdgcq`cRosxWVTi|@4LE^k!tj}(U3oWtEWw_&X zwRfv{aM-A1^;1UULTCJ2M3t)@yPb6)T`_fgcMI~w2{x7!pmKWkvRm?Oj!ScSR+CaF zVxH`lk}I6ti{?U+GBc z0%+`@oqD{`QwD*aJ;g=7L^WHI#eP*+ZC>5=8c8mXnJ@ha9?%lz1k>Qqh7|_E6F0ok z-d^5w_Kn0~U<>q0>RPsR;db;Lnw3+G0rp9lMbe|B)BZoO9xF#$Hie!lpoaad=e`)g z*>o{~dpPjohh|qEuR?7ZM(BAqZchyz9$ycrBTyRkCN%dX#R|U~@e{`!o!$#LndHSi zi<(RB}N5z+?k zwsyN%$2U{d5*cqcxQXWb-cS+T$<9NTX`lVW)YW$!vxIn0{FxB3@fSMg0=AjWbp8iG z`G@EfDA`;Sy{KW^@&ludd4apSB-{6LL)HRt&;!gFTF23DGqqWaq^8At5|09Jy5E-o zWNO14WlpD4g5(dLb0bUzH*<9zlZKl78_ZRAwpjZo3%&xU8zOpbky;bxFWUg+PTNby zn+?`x=3${nJVHZlt4|mKt2!%(@C#h+(=a8kRi)2cXXV@uAxLr{HMaDoe`-Z< zTx1+HRxSlUqw6LK(KVZ);4hBXZ+L%mfQ{!Ft7*j9cuHJX)PgR?S<-~IRCD6pLhm1p0zQ)ZDFM`7) z)fCu#MUP7_V>_a?KBM-bOx&v-SjCK6;chIY`K`0)lqMd^g;$$Y`b@G_{_uQT_5EL_ z#btL^@mN26Lhbg$dLfi+ZTUBSPgJt7eY?$xWeaTsq2D%Y4X_5-%W%5-tWOD;ew9tF z*zL9juH8rhiD&58)I2^C6|$1vmeh8hq|+A0$PNs$D7U-G;k5;pH<-b573Uf?Cg{Yy z%{Jo7mAN+R1v0@#hkk%*23|LqLti)28hst`>g*Smv)xjao^e(&M)f+CKHl)m;l0YP7ezX|x$R z*Y`K*UXTY=W4XNRBCm%QHOHB7-wu)ZE~(@)o-)c~sn7n?Mvlcf%x^Y3RnhdtKY0BD zDXMYW*ys~gZPU|4qpd`PZc?ht94W8ZH`+YaIpus25_oOO{;RM65Sgf?vNb$CDd=L?!{)r%N;x-!YW~&g|8f^1tyeDG z)2I^p|Gr15&SAFJD?} ziZzu|avk|FYo#q4rqAn@6dt?}`~E(IzW7{U>l)D9NQ>yB6|>XW;%*>3PHMGKTc*W% z+^_ngiY`$as|NR|k2xh>+!)@$FD(A7hF)|h^p&Yb)?GJ=(KR`?JS$RAmpF>ny%HBm z&yboJtM=4rHq(xnrCee?ai3=18rgA&ktdH!42VeEn8lSV_LxX5m08)H_CS)=spY(+ zxkt`1psIxz@iYd6s`?H-Ho91VKHld{%-E&0$sCQ-u#TCG6jz3pUh(>HUnS3mp zd9_Ro96fgy9NlOks3+(8*R|y$&Yw*TXBSq*+vB1mrz3@rU=q7zE8KI`nsjFz8gxC( z0u>L>wpfiMjqMmF2;AVvDE=`6TOVl&;1D-!f46Z^0?qzL^|00?2&dGzJLbo9l-T>t z{kpZSW2QxH>QMrJJrtRLYvhFPHetU;(0h;4s_C8(8T>OTL5GL8E@sUdRgh-6wvv1) z-H8pJ0KtOUTr=Xr^?|P4creg6BU}eMCinZS(zmi^*xfm*wZ3|-zoh!^_vh6hYTQyx z!0_;cvivDTSBt|996EL+-4>rl!gq> z1fI%nRn0FbNJ%u|GaW{lNCbv$P}XA|Hijz|Sen`h3CUm3zbBiYh%lb-IM~DJi+>*^ z8>^o!AiRxKUX)|Jy~-Rl*w)F3dL{g1x1~MEhA<-lof?7UPUSyec;wQ}9X=x@IU-gz z^?iF}k-}jR5S!re<#O01^)hF=vF}@lsMqx>UG>3)@zq_^%@jw5cRanXiUc^jycHPX ztSVK_s%}=CTvJ$+qzjLqZ6Njoli%{ffY5GSJ;m_`d)`Xx78?K+2eoN0o;E)J5TmL8C`Ja6C z9fr7cP>j4E%2*)Hg4ohC4@>S}?`xWJJ&j6T@1O_9Wo>`z*sevSFY=uGWW>20Q83M+ zV;|*D6l(Ag#A^R2HWxl0L3;JTo)&cI8sNggldL8$x#0NjV*6uJO2m1y3bUB_rNjA) zKXcoz0M?-zs#e~rAQkUNSAo0+K$S_UT!w5K29S@L=NztFIAfErFuoLY$;m=c`zNu4 zYC`WX8iz}v7GG$h7C-wwB3bWn-Rc87Ds|PnDS5eB8<;l`F3EMi)53g(7>E07Ak-1Q z%0g0L-@J8-tUbI?^cc(vI! zx%Y6kPO(bss?W7;-KMBb!C5^^Qx4)5x5nCq_}KZa+OI0x&mM;)=gLoY;ee=;l%CSJ z#r6hy96btgkzcpsl*hIrS_Po(S@F~&3yn-~rNR%Q8N3 zFj5lvxn$<&!CLK3G)fw@g5O{Zu7QM0oP6gSU`Ly5!2MN1DBhaO!Lru}k<$@(D$wYW zXz{IxJJ8>z83xA$jh??woI_i86Yr14HGGIel5kJgd|(j%7f%A!ucr+D&SXb~ahA;2 z0jlNj6c#(E>DpuMatHr#m#$pJI03tL?cEm5_-iZCvhA>b^=sDpO#N4x7MO*P?a?~S zl;s?naZ6kJ#2RRpo^HrPGAc}O0wqas^{|1XeiFV^SON^xL+jnSd0Gp!A>r3k8JO)m zijGSA$WfVjzU|-{C4J*05VZc7Z|cn&QW5p+ikErtUnq1!keCYi2wOmxxu41oQul9O z;||xz;R!GI4sAxmPl2B%ZnNjuu5f4wpH={AeTlxlUDvQ2puXDnreX8)Y|-xXA)5qh zhq+pL7`t-SdAi+k*Y)sX-N-xo8UHLp*qrxqh>_NlQz$K1*Bzvg?BG;S{y@PhM(<#7 zZw!s~wisIkP^;QTQd!e#1hyK|!QvIxp{EvN{t>mdon`OG&_Z>EwLh0XI$3401tE+{ zMT7%QG?222f7ChWL}dLVR3fMFM|R{E7uDwN0f7pX|}=&Es!hgx%`_pqADHp1y`-*|NP1ftmBr%aB`gf zuw`~vYm5u7{%rN;8~3-)DTtDe35K{dP%W``IUH}*kJ^@5>zNwboDqQY4IQQ)OA5%< zQJy$bQE+%P44LA|uRPe0a_z)&pkd!qpkY^<&0dPG;tTgCvnx9|=E`D>3H=g98+Jyj zN`kfypIIs63?Dq3hF52o7YD)6PoA@DdXGPx(0~!K7PFPxMbn8<4f&#-UXT#o&0cPI z+!f!uH8zR&=iI8u$mP0)kI2G?kLsGgYik_+5)kQhj<>fQ@J`eAx>2A?^J^Br$0@Pe zp-gr!7g(w)g^ICsyU^pWRGj6ZI})|Iq*>>;WQBkWOFR5Fh-z>gNwl25z~k}lsObB1 zL}v0TD*1&)?C7MDKw&m(ulm=q@Al5LzDBNm1dEi+7SK+wUIRTJUJ75B45j;)HAcy- zvN@u%KDhI>@UtZT;jC~U^l!7eK#4GVmnXZBdB_5_e=(}0v&BjJgb#giBTxt`5v45( z17y3G+As%N^nLokMP4wOnJ^;H6)&fFgLxl&lOp1$E77L7QELaE;+mkLtqg+8PV98- zjADU(*95FNvP5B_uKq(m&ch7wyNr?qfPrJ88zltdo%IJ)#2K%n;~UheAMEK}&cSX@ z>Vs)-uZai^L$ke4fE8QH(%9>kv-n1$(3{Kh0obNnC;{_+x8xbzTg0r1MC}&>0#)N* zUJ*FdF7&BzY!Duiy{({_Ru^cAm4G6NHm|DM-Mz-^j&K)4VP~be03I zrEE$M(5ZDWA%hZy7ueKZEd5<-IHh~6+0m2#Ni;+wzJyR=^Sygdqu5W~E^O_|U_~cD zrz4msU^SRJm65E%54k~?4-PV7F!Y7qKjtTm z@xaGjhhEnIZDZGFqqHxY{8)-g(dF09y*xQ~R_=`?J8N|=nQyXZioCbDK|2M}x_3xl z{Y#=Q&{i%yxLV^g5VDzvtMk_C84hlf)%C3w40(W}JZpaf$}o;Sew00Z_N`uDAZR{9 zt-$?Zir(X&_VK6KUN%{^`b~}C@U13w9Bptd9@+Hq&Nh&d)8*HV1;i<2Zh&WvSq(2v zo64$5eALLaxt2am)7jtVfx2DOf|2NLvTtu=P^ZCH(%PmznAs^cTR@eXk!r$^!|1yV zh{i#4ge572XZWy{Z*?U1%f9SAc;aGt@gmQwrYKv945=aRb!QHLf_ghP9e4z=dd?(d zAfK)8Cxmi0@x4^HW^Vd`(?7bXaGo&9LRuNvo_`<(7%K1d_({<=Sq(d_)jIf_Hi_Av zlj&M3<+nE&gOpzBVb7(u3WryF6Svn)G`tI@jB$R1rb%*;SuFhJA>bJgXS)~>JHvmw z*4x{9VcwcVvDV>rqX8%=P(Sw+a&nP!Rd7UB4k zmS{`b2k?mnC40qJ=&b$r=`mAk-^E#r8)SRkwzgi1tosEAS@N5A4aXBYe-M3ALK~s03cEZ6$o5;4FdNpPY*;zDck)B@hi!X(}m`Ix|iuO&Xn5rP(1bEc*Ot*yM}g z7rV)W-IhKjZBbK19S5hx;jTeL>tw~Hg!f?8*~Ld&+Zx)e6I!rH%sI&J*=D~z=ioTn zC*hnN2hYK{I>n8?MOF4gNIxenP)$p7p-3R1i!cw-+)Ssj;>`%p(orX&-D z%TIMIiLQ-$tkRJC8+jvvOOZ{B6ypuj-W?5H-O|{8Z$ z@BiB#|H<<(iKtt+A^W+GuUWRPuR!yB2=QQ*oy&JD5dCTIK+L}-f#$yCC0m^NW!U11 z_SuNZ)Ap=l@_>R%Lf!NZ3O+;#p`@&P2cXWCAaN06<8ZC zoU?H&!`}l5FD+Z%HOSEY;RB>IubOQ67uz{u&)4hOt#~J_3oS}N575zK4 z!6j*U$x&k{-`A|XLV+TzsW?+yoLGg;&a~WHaDK6QGFw8 zm}dPez3f0NtQ#6DlhGD0_aw<3q`99}a7-vkvHv#x1!I5uj(0!iLr=DOkh2{*TECsb8xtS#?nUvzKH<{YG(G?;P zI7Au_*B}awta);x6VeZH=r3v?xa!t#71^g5fM-d5W=AP#J738dFM8|gmX{hC$+y9M z@b)yz+Xiph{7GWJz^=(;jBiEA;yLDEC*vbvPyBt_=O#iT*n|8Yy*lS<_Z4gU>62IX zFe>EKlo(*OEvwYX52B$RQp?q_IhRud=oh7_FG!&z%Y}u~ZM3?vjP?*`XU}*3$}**4 zjrPG}q&+H}Dp{)(H1bKhU^aH@4eb$umoez0l7G6|nQ}_!3&Es@6c$3Zdoi z0h;+=rCazVxGP5$!13qy~}rC=|i~J6CbVOe~+* z%U@+{O21=sGD@+KxHtGrc#R_i6d-;&GAV3}qqcOH24xvO0K%0QvKY1BGIZxO2qC-a zUVfPzP)Bn}O{L*qd1VW2*GMpD?2A~XGFhrVP zD_50TEH(6%4D*wUhD`n7MVuEzl?iBOwz=>jfAe;*bz16v5%@gb)e!>)ch=6!1ROPZ zwggIt!+?-iGKN^#Qyo7yDvJipCcuiI{@ z{iZ;q^C_`{I6PqUe3O)L-h#C^)9x>C06@v6E$&x^LQ0x?ia!LD!@t554&)N87N;kh zevIv|%dgEgI-J*Uocreg>B7*mQYuvFI#@&pP+#nV9RoMfzh0~psr1lycRpFb7IHs} zI6uFr_2#}^qw;Vz1d|Q&oX|B%QT+I{wz(m8wzOOO$2sP}lyD|cFxjTRHEur8r0G@0 z0#4z6qck{201i8yK9cSIkP{OwLnHhV%g+5 zA;iOhCVyUJCd6xPJ3Yt6#RZlO-dri+xN;2Mo@^Qza0uQo#Lc7t%na<-`}6} z+<=OW*cVMqecn%MH$*hu7CknnLtHMid?>>D7!3-PGJ|)${P+<1gf?#1{P6qTIcHW3&~ zW^NM6O~l5zuzn~MqlHh)r-num6^M|%hLv|3Zq#4kUdc_9PTE$Fr(5bJU(XD&a8F`v zJ9$PFRZl*=I$15c>jlSq`)s!lFSW_x2GJSjoAxD^7EX*c$Alh92y3gV2wZWi5@`Y>rpOOSY~vU?nFTEM_S#m?pf%;z`> z@KsE~^;!J$2IenJC#IMF&P^e}VR zyl7O-Rlz01RB56lG1jq;Ww?J@vorn6D(l9RM)$dUERN{f+MK1gUiRYUfk~`=47Pbk zqH<-;#t}@|1B9?nLD60MjVB1U{xNr0)K$nLdCrN|(K!w5OBgJl5VIL$meHpz%@MT6 z6Ig^Nmlo1>PyyEoXqz{_A z175jqGvX7}+j8&Z1BMKCTB-XOGzO5ds?KxB;C(u^L z4aR!wPS^}6`YFGk?y2UKel~FtLYhSMCnlG^VMv8LMl(B;S{(}+WCQ{p*g_}FxLISc z*YNh+F+TlrILDU^^UGzZE#j}>tfclG%@$1rgy`QYqXL2xg>g*$K^5URAsw%+Q~OFa6mp69Id~ zs6gjEsI{d~alBmt+^XoVccP5Y+FVwhih}!3JA#(yE(Dqi; z=2dwWD6v4q=cgsUCt1HqnI5jYzZol})wuMM<5=g*TO3!Ux~07!gdb1b=8wilfySlr z*OP;n2t{UYY7A-vt{QMv)(&69d=s(?07-M*43+~5cJhTYg?;N9LfC~igmiPGGF{3K z>xx^i0q&0r4Ypp@WP?Zeqy<$+_(O#rOgnDmj{yW-?f8JAaV^?*vjx%oL~l^a<`j#c z@5NT;8hR;RU};{~Rz!e7ENm9eS!(m@3ji5gzDo+&>&(!S{V4b%^OWjcp5W=^sHFZi z66{?Qv$mEC+LK_}wPK2sh0{E>?#DE~U7bT3eYL<4a=Ia5d}Wf_7jzx2z2oq`QzyCRaegBw!ZIOdQ%K_(!bV^cpxv3iX9ZLHlHu>u_YEW) z*!V`YiEH;TfR$;>r~&YjS4g&UEDc1`X7gpnv1+46jUmgHd@UbzWK8ETdlIavk#~G| z!TvENP@C1HVZ35*q#sfN+nhsLCNichpY<&DJX^a<&N4Q`uG zIAq>Fhc9`|pA02AU0Kt2Peuw)5%1xBIp$NJdjI}jkIvFKL>@TDN2xVfh$b}*GlG}e z?PyC%8=O6_N>L&&Bop16R^_3T7U?J3nz9#9lxDKtep;$_kuZdx2u`iX%%Havs&ITH zhY!Y-uW4!<&s>Bw{J@Pi_PexaY|Xd1x#q96PL8djn650eu!YoKp$5K_5m;ghV`}=J zg3dg)dl0s{Re>p5Fhgi;o|AwC*BGZh6uvvvLf1Q>SO!C8t2Qq^AU}Wa^GFMI*^WZt zQ0a8Cf$y4?Q*gb$O(=*;^8BG*0oYPjMg=_*ei-@3*K>tS2(iyUl>%9$) zksX2O^;MF^QJS#Nc=rg(k9=)E%1uiC2Y~z9op8WguYZ5^|AHl4$*OO6Pm0J8<(6gc z<2gqwXvcq6*W@iTQ=l-61B19@p6gy+bJSRwBJH=7|5!j?7lcH|$o@?Qf z9wXdtVoF*7%QC$%6Z~enr9|E41jx%wmb2_VE%@0YGNE!MQD8};NBJH3ZHH_*5Z(S%jpW-{vQug&(kM zX?8d%dNB7C5>V#NF&e~_0AtZNgQJIL_C8BBuQj&EOzz>!xwa~dA>^3aT2sD|d0qLP z&M%%5%tP6*Fb&7OG;ntjnzn-l{93_rzrm5rdxq3f&S@-7^I3MUqN8K?#||HY^XbC* z8p~KOoO0-yngV%s@Os-9?V!$h2d?9s&CMO4CRX9-r)NwaY&UzQ#IYP^6ZP@Jkz{4s zq7R*NXFB;+9;!Z4xFbyC+2{hIt7fjjJmVfhpeIg*+i^KbTZ|iUE#@ML+8lC? z0eiuNE!gtK_Dl|0Y2jOoV-8^*^F>q7yAo6ql9qKAPdiSpQr9Vnt(d!RR&2RmL}5uN)0p`NlAZyN(~ zicsuePK0BQBn|ao^M#X+FYykfICM`<#`TU~=gKYF&dEO9#Aj=w18Zg2YYwI1=9bm? ztOoJ7c6dCC0`83kXUJz^>Ac|Ujn7CC?K?BF_T0e^NsImuWWHYe*24IkPrEv&e)qWt ztmkM0oWb|8XK9&eEA|O7K*EFwNLS5->Bo4=Vo{GD+*&6Q>~UO?_Ey3daW&9S(f(Y3 z4%b({PHz!%w34UI(*d+o4Xo`4OU(#X-DB@^0&vu3I6iL3zbDjc`?3G~OncZPdv}IV zNNrjW^E{LRiFZ=xO|?b2_$8&a;A5?Jl;)N09XfR8o`KG-hmk6coIRv*FFNPa+ZdBC zB5%}OCngg&L#%~uZrE99!dud`7)uMi?H+++WZq!47DT{AqU4d~g9AZR5L}Nk4HXNf zJqVsTRSM0;;`<2&Ahuz)Xpnfh_MeNw4Q^xlIOLf4#pd?zIT7AoW^7vU(%*s4Yl!;9 zaQKV{f(mS4P|JHtdxFc|aFsJa^*&~0Q{c47TIP!eso6SAaBZnfqVFe-Lm4oW(S3M# zb$NIAoUhKt`cwP)%?*O%pb55{e4%us#zfBn@n#zP@m8NBeByAh!+ml@%CZm3hqbGD zL-cV;enVwQGj?#jyah3Q@&>s)B}o(H$ocj3r(lZ$7a6iF8I0jj_^ZP;MY7SdG=_4s zG|GDd67X{NQ7?m_WrLJ}!glB@&`tt)iL5}QCAnP;>n4Eh;r1H@@J?jw_t<&`*af5l z7X#4YHVxQOfInicDL>hvEsd)H5||;=KO4&Cy3(WZ zoIrXAO6AHzmv!$R9WK*@^r7TaH_M`nq|1iR03{Uem z_Xz!9MoUbhMVE_@smHgEytR3%p1J9cb8;k1j#e%)^;?tUsWm9ua>PM&^Vr32>=n=m z`_2u@}s|)w*;k zaO?X(%G)ZY%)@iq3D}$~{)+8j7UWBPl<}A*ZdwnoY(=_O4Sv}*O z8dQfGU#lH9a{P$Y^q+$zKGNS9lq>q*225l$z=O5@dL!FZUH|z%91O@QoLelw9dwa$ z@ORL?cO!EW@e-Jr41kb=s9dyl20|0`pPzs?zX4k8h>~Q!O}}-B@AXb3g3L=kKjd`e zKIitULHX9a77bC6Afk<00_ROWL0+{~NUX zzm0+aKhRLkEqx*Xc#Vo|Bt1cPq;2z@|H&4djQJXKZ5F7ScF?+W?&XT+{wn$PSgR}+ zG%(d9Rty5=-YKqAGsBPsUkI1ZE<0C&$g&u4Sn6jiqKWZ+$|x1T;WY6|n+)7E8~VnZ z6nlX^TD5H;Mw>V9n}<*nPv6dT9DHgj&L_B+48I#55aQsQ z3#BEXKvT&(RZsBk%TQQ1*V3LuIS9(=VzHiu{5`pm*H5|+SMUOn_J?daFC< z;Dhu%>!gS`s^|V|9yk)yh@R)G!XzHv{IoL6G|LNoNS2Qr|BHtnZP0#8@VJ?9pT7I6 zA>>2Bwl9}(gQ0B~u9h18`@sJnOrj3tbrQV&+Fod(RrYi0vPlASGrWH_v!ACE|UT|#0pMWW% ziOKGGYQsKw2UzUvi+X|9jc=$tF6v*;U7H~G2YWmHollLUS^8g6mXzz?j}`l;K?GX& zG^&!L!s?^ZeP67teMOE;euAfw^ZwBSg%WqRu8WpbSWZ2Z{2#00IlE*s2HTR!{ z$k2*q)eD7xqNxeABpRC6arufeccHbhz+xq8{pChfU-R`2923VhuaYxsfs5dp&H49$tNHcD+2E`U$=jvDHu+hgXs;kvk z$9iuV>Sl}K(V%^FlH9&CuNQK1#jP71Huq8s4q*^vUEEN9w zP*loGtMGX~g;ma+@@y;(l%xZ_s`v6~Py5yIX_Ln;jea{%Oe%F#AH$?Bu$?Rz^FEqH ze<*Z!PwbB?9IVP(bPZn98Vb!o1bg|gf>}I>^e>vAJ+77H!3vAKpsA^NnRX|*J$%o7 zMHK@Xef1Es^CtW;NkTEc^<)l z((;_{prOnnu3T#DpGRnAV!&HTYfsokn=KunVnXBXS8dB2R2hXIe?=(na#n1KWl(>J zXzdWm;4lhDIGgFrPtsq%PcW;nkH$WOXF#4`R#HgOCk%rA@zUbEX}M3U+0Q98t_}#L zC6j?r)=zDb8q9#^ Date: Wed, 7 Nov 2018 09:39:38 -0800 Subject: [PATCH 66/73] Fix the ConsoleWriter default parts order (#113) In order to prevent incorrect output when somebody uses a different name eg. for the "MessageFieldName", the `consoleDefaultPartsOrder` variable has been switched to a function, which is called in `Write()`, in order to pick up the custom name. Related: rs/zerolog#92 --- console.go | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/console.go b/console.go index 95bfd27..5730f75 100644 --- a/console.go +++ b/console.go @@ -38,11 +38,13 @@ var ( consoleDefaultTimeFormat = time.Kitchen consoleDefaultFormatter = func(i interface{}) string { return fmt.Sprintf("%s", i) } - consoleDefaultPartsOrder = []string{ - TimestampFieldName, - LevelFieldName, - CallerFieldName, - MessageFieldName, + consoleDefaultPartsOrder = func() []string { + return []string{ + TimestampFieldName, + LevelFieldName, + CallerFieldName, + MessageFieldName, + } } consoleNoColor = false @@ -82,7 +84,7 @@ func NewConsoleWriter(options ...func(w *ConsoleWriter)) ConsoleWriter { w := ConsoleWriter{ Out: os.Stdout, TimeFormat: consoleDefaultTimeFormat, - PartsOrder: consoleDefaultPartsOrder, + PartsOrder: consoleDefaultPartsOrder(), } for _, opt := range options { @@ -95,7 +97,7 @@ func NewConsoleWriter(options ...func(w *ConsoleWriter)) ConsoleWriter { // Write transforms the JSON input with formatters and appends to w.Out. func (w ConsoleWriter) Write(p []byte) (n int, err error) { if w.PartsOrder == nil { - w.PartsOrder = consoleDefaultPartsOrder + w.PartsOrder = consoleDefaultPartsOrder() } if w.TimeFormat == "" && consoleTimeFormat != consoleDefaultTimeFormat { consoleTimeFormat = consoleDefaultTimeFormat From 3f112dae87cc442ae64da296e41591b64a52529f Mon Sep 17 00:00:00 2001 From: Olivier Poitrey Date: Wed, 7 Nov 2018 15:20:33 -0800 Subject: [PATCH 67/73] Fix "could not write" error missing carriage return --- event.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/event.go b/event.go index eda2853..9b000fd 100644 --- a/event.go +++ b/event.go @@ -137,7 +137,8 @@ func (e *Event) msg(msg string) { defer e.done(msg) } if err := e.write(); err != nil { - fmt.Fprintf(os.Stderr, "zerolog: could not write event: %v", err) + fmt.Fprintf(os.Stderr, + `{"%s": "zerolog: could not write event: %v"}\n`, MessageFieldName, err) } } From 8e30c7136954547e35720dbc2e5a50fd140f60af Mon Sep 17 00:00:00 2001 From: Olivier Poitrey Date: Thu, 8 Nov 2018 14:49:44 -0800 Subject: [PATCH 68/73] Revert the wrapping of write errors All errors generated by Go libraries on stderr won't be json encoded anyway, so it does not make sense to have such a treatment for this one. --- event.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/event.go b/event.go index 9b000fd..eda2853 100644 --- a/event.go +++ b/event.go @@ -137,8 +137,7 @@ func (e *Event) msg(msg string) { defer e.done(msg) } if err := e.write(); err != nil { - fmt.Fprintf(os.Stderr, - `{"%s": "zerolog: could not write event: %v"}\n`, MessageFieldName, err) + fmt.Fprintf(os.Stderr, "zerolog: could not write event: %v", err) } } From 7bcaa1a99e9833acebc006258f934fd699d8bf6a Mon Sep 17 00:00:00 2001 From: Yongzheng Lai Date: Mon, 12 Nov 2018 16:50:17 +0800 Subject: [PATCH 69/73] fix: console ts with json number (#115) --- console.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/console.go b/console.go index 5730f75..f25bc32 100644 --- a/console.go +++ b/console.go @@ -292,13 +292,16 @@ func colorize(s interface{}, c int, disabled bool) string { var ( consoleDefaultFormatTimestamp = func(i interface{}) string { t := "" - if tt, ok := i.(string); ok { + switch tt := i.(type) { + case string: ts, err := time.Parse(time.RFC3339, tt) if err != nil { t = tt } else { t = ts.Format(consoleTimeFormat) } + case json.Number: + t = tt.String() } return colorize(t, colorFaint, consoleNoColor) } From 848482bc3dbeed6a47eb8f8cec0d4484795b1314 Mon Sep 17 00:00:00 2001 From: Olivier Poitrey Date: Mon, 12 Nov 2018 17:49:35 -0800 Subject: [PATCH 70/73] Fix "could not write" error missing carriage return (again) --- event.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/event.go b/event.go index eda2853..c08214b 100644 --- a/event.go +++ b/event.go @@ -137,7 +137,7 @@ func (e *Event) msg(msg string) { defer e.done(msg) } if err := e.write(); err != nil { - fmt.Fprintf(os.Stderr, "zerolog: could not write event: %v", err) + fmt.Fprintf(os.Stderr, "zerolog: could not write event: %v\n", err) } } From 8747b7b3a51b5d08ee7ac50eaf4869edaf9f714a Mon Sep 17 00:00:00 2001 From: Olivier Poitrey Date: Tue, 20 Nov 2018 10:54:42 -0800 Subject: [PATCH 71/73] Add ErrorHandler global to allow handling of write errors --- README.md | 1 + event.go | 6 +++++- globals.go | 5 +++++ log_test.go | 21 +++++++++++++++++++++ 4 files changed, 32 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6241b59..eefc1f6 100644 --- a/README.md +++ b/README.md @@ -479,6 +479,7 @@ Some settings can be changed and will by applied to all loggers: // using the Dur method. * `DurationFieldUnit`: Sets the unit of the fields added by `Dur` (default: `time.Millisecond`). * `DurationFieldInteger`: If set to true, `Dur` fields are formatted as integers instead of floats. +* `ErrorHandler`: Called whenever zerolog fails to write an event on its output. If not set, an error is printed on the stderr. This handler must be thread safe and non-blocking. ## Field Types diff --git a/event.go b/event.go index c08214b..a0e090a 100644 --- a/event.go +++ b/event.go @@ -137,7 +137,11 @@ func (e *Event) msg(msg string) { defer e.done(msg) } if err := e.write(); err != nil { - fmt.Fprintf(os.Stderr, "zerolog: could not write event: %v\n", err) + if ErrorHandler != nil { + ErrorHandler(err) + } else { + fmt.Fprintf(os.Stderr, "zerolog: could not write event: %v\n", err) + } } } diff --git a/globals.go b/globals.go index 1a7dbcf..1c66904 100644 --- a/globals.go +++ b/globals.go @@ -37,6 +37,11 @@ var ( // DurationFieldInteger renders Dur fields as integer instead of float if // set to true. DurationFieldInteger = false + + // ErrorHandler is called whenever zerolog fails to write an event on its + // output. If not set, an error is printed on the stderr. This handler must + // be thread safe and non-blocking. + ErrorHandler func(err error) ) var ( diff --git a/log_test.go b/log_test.go index 3ae0238..50cd1a6 100644 --- a/log_test.go +++ b/log_test.go @@ -640,3 +640,24 @@ func TestErrorMarshalFunc(t *testing.T) { t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want) } } + +type errWriter struct { + error +} + +func (w errWriter) Write(p []byte) (n int, err error) { + return 0, w.error +} + +func TestErrorHandler(t *testing.T) { + var got error + want := errors.New("write error") + ErrorHandler = func(err error) { + got = err + } + log := New(errWriter{want}) + log.Log().Msg("test") + if got != want { + t.Errorf("ErrorHandler err = %#v, want %#v", got, want) + } +} From 7179aeef58ebe0213606214702d2a7a059a3221e Mon Sep 17 00:00:00 2001 From: Ingmar Stein Date: Wed, 5 Dec 2018 08:46:12 +0100 Subject: [PATCH 72/73] ConsoleWriter: reset buffer before returning it to the pool (#119) The buffers put back into the pool should be equivalent to those generated by the `New` function. --- console.go | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/console.go b/console.go index f25bc32..1141ade 100644 --- a/console.go +++ b/console.go @@ -37,7 +37,6 @@ var ( } consoleDefaultTimeFormat = time.Kitchen - consoleDefaultFormatter = func(i interface{}) string { return fmt.Sprintf("%s", i) } consoleDefaultPartsOrder = func() []string { return []string{ TimestampFieldName, @@ -113,7 +112,10 @@ func (w ConsoleWriter) Write(p []byte) (n int, err error) { } var buf = consoleBufPool.Get().(*bytes.Buffer) - defer consoleBufPool.Put(buf) + defer func() { + buf.Reset() + consoleBufPool.Put(buf) + }() var evt map[string]interface{} p = decodeIfBinaryToBytes(p) @@ -130,9 +132,12 @@ func (w ConsoleWriter) Write(p []byte) (n int, err error) { w.writeFields(evt, buf) - buf.WriteByte('\n') - buf.WriteTo(w.Out) - return len(p), nil + err = buf.WriteByte('\n') + if err != nil { + return n, err + } + _, err = buf.WriteTo(w.Out) + return len(p), err } // writeFields appends formatted key-value pairs to buf. From c482b206231df1662b9e1895a9ba17a88b11220c Mon Sep 17 00:00:00 2001 From: Ingmar Stein Date: Fri, 7 Dec 2018 18:59:01 +0100 Subject: [PATCH 73/73] ConsoleWriter: fix race condition (#120) Alternative approach to #118: fix the race condition by replacing the package-level customization settings with curried functions. --- console.go | 115 +++++++++++++++++++++++++++-------------------------- 1 file changed, 58 insertions(+), 57 deletions(-) diff --git a/console.go b/console.go index 1141ade..e13d419 100644 --- a/console.go +++ b/console.go @@ -35,19 +35,10 @@ var ( return bytes.NewBuffer(make([]byte, 0, 100)) }, } +) +const ( consoleDefaultTimeFormat = time.Kitchen - consoleDefaultPartsOrder = func() []string { - return []string{ - TimestampFieldName, - LevelFieldName, - CallerFieldName, - MessageFieldName, - } - } - - consoleNoColor = false - consoleTimeFormat = consoleDefaultTimeFormat ) // Formatter transforms the input into a formatted string. @@ -98,18 +89,6 @@ func (w ConsoleWriter) Write(p []byte) (n int, err error) { if w.PartsOrder == nil { w.PartsOrder = consoleDefaultPartsOrder() } - if w.TimeFormat == "" && consoleTimeFormat != consoleDefaultTimeFormat { - consoleTimeFormat = consoleDefaultTimeFormat - } - if w.TimeFormat != "" && consoleTimeFormat != w.TimeFormat { - consoleTimeFormat = w.TimeFormat - } - if w.NoColor == false && consoleNoColor != false { - consoleNoColor = false - } - if w.NoColor == true && consoleNoColor != w.NoColor { - consoleNoColor = w.NoColor - } var buf = consoleBufPool.Get().(*bytes.Buffer) defer func() { @@ -177,19 +156,19 @@ func (w ConsoleWriter) writeFields(evt map[string]interface{}, buf *bytes.Buffer if field == ErrorFieldName { if w.FormatErrFieldName == nil { - fn = consoleDefaultFormatErrFieldName + fn = consoleDefaultFormatErrFieldName(w.NoColor) } else { fn = w.FormatErrFieldName } if w.FormatErrFieldValue == nil { - fv = consoleDefaultFormatErrFieldValue + fv = consoleDefaultFormatErrFieldValue(w.NoColor) } else { fv = w.FormatErrFieldValue } } else { if w.FormatFieldName == nil { - fn = consoleDefaultFormatFieldName + fn = consoleDefaultFormatFieldName(w.NoColor) } else { fn = w.FormatFieldName } @@ -234,13 +213,13 @@ func (w ConsoleWriter) writePart(buf *bytes.Buffer, evt map[string]interface{}, switch p { case LevelFieldName: if w.FormatLevel == nil { - f = consoleDefaultFormatLevel + f = consoleDefaultFormatLevel(w.NoColor) } else { f = w.FormatLevel } case TimestampFieldName: if w.FormatTimestamp == nil { - f = consoleDefaultFormatTimestamp + f = consoleDefaultFormatTimestamp(w.TimeFormat, w.NoColor) } else { f = w.FormatTimestamp } @@ -252,7 +231,7 @@ func (w ConsoleWriter) writePart(buf *bytes.Buffer, evt map[string]interface{}, } case CallerFieldName: if w.FormatCaller == nil { - f = consoleDefaultFormatCaller + f = consoleDefaultFormatCaller(w.NoColor) } else { f = w.FormatCaller } @@ -294,49 +273,65 @@ func colorize(s interface{}, c int, disabled bool) string { // ----- DEFAULT FORMATTERS --------------------------------------------------- -var ( - consoleDefaultFormatTimestamp = func(i interface{}) string { +func consoleDefaultPartsOrder() []string { + return []string{ + TimestampFieldName, + LevelFieldName, + CallerFieldName, + MessageFieldName, + } +} + +func consoleDefaultFormatTimestamp(timeFormat string, noColor bool) Formatter { + if timeFormat == "" { + timeFormat = consoleDefaultTimeFormat + } + return func(i interface{}) string { t := "" switch tt := i.(type) { case string: - ts, err := time.Parse(time.RFC3339, tt) + ts, err := time.Parse(TimeFieldFormat, tt) if err != nil { t = tt } else { - t = ts.Format(consoleTimeFormat) + t = ts.Format(timeFormat) } case json.Number: t = tt.String() } - return colorize(t, colorFaint, consoleNoColor) + return colorize(t, colorFaint, noColor) } +} - consoleDefaultFormatLevel = func(i interface{}) string { +func consoleDefaultFormatLevel(noColor bool) Formatter { + return func(i interface{}) string { var l string if ll, ok := i.(string); ok { switch ll { case "debug": - l = colorize("DBG", colorYellow, consoleNoColor) + l = colorize("DBG", colorYellow, noColor) case "info": - l = colorize("INF", colorGreen, consoleNoColor) + l = colorize("INF", colorGreen, noColor) case "warn": - l = colorize("WRN", colorRed, consoleNoColor) + l = colorize("WRN", colorRed, noColor) case "error": - l = colorize(colorize("ERR", colorRed, consoleNoColor), colorBold, consoleNoColor) + l = colorize(colorize("ERR", colorRed, noColor), colorBold, noColor) case "fatal": - l = colorize(colorize("FTL", colorRed, consoleNoColor), colorBold, consoleNoColor) + l = colorize(colorize("FTL", colorRed, noColor), colorBold, noColor) case "panic": - l = colorize(colorize("PNC", colorRed, consoleNoColor), colorBold, consoleNoColor) + l = colorize(colorize("PNC", colorRed, noColor), colorBold, noColor) default: - l = colorize("???", colorBold, consoleNoColor) + l = colorize("???", colorBold, noColor) } } else { l = strings.ToUpper(fmt.Sprintf("%s", i))[0:3] } return l } +} - consoleDefaultFormatCaller = func(i interface{}) string { +func consoleDefaultFormatCaller(noColor bool) Formatter { + return func(i interface{}) string { var c string if cc, ok := i.(string); ok { c = cc @@ -347,28 +342,34 @@ var ( c = strings.TrimPrefix(c, cwd) c = strings.TrimPrefix(c, "/") } - c = colorize(c, colorBold, consoleNoColor) + colorize(" >", colorFaint, consoleNoColor) + c = colorize(c, colorBold, noColor) + colorize(" >", colorFaint, noColor) } return c } +} - consoleDefaultFormatMessage = func(i interface{}) string { - return fmt.Sprintf("%s", i) - } +func consoleDefaultFormatMessage(i interface{}) string { + return fmt.Sprintf("%s", i) +} - consoleDefaultFormatFieldName = func(i interface{}) string { - return colorize(fmt.Sprintf("%s=", i), colorFaint, consoleNoColor) +func consoleDefaultFormatFieldName(noColor bool) Formatter { + return func(i interface{}) string { + return colorize(fmt.Sprintf("%s=", i), colorFaint, noColor) } +} - consoleDefaultFormatFieldValue = func(i interface{}) string { - return fmt.Sprintf("%s", i) - } +func consoleDefaultFormatFieldValue(i interface{}) string { + return fmt.Sprintf("%s", i) +} - consoleDefaultFormatErrFieldName = func(i interface{}) string { - return colorize(fmt.Sprintf("%s=", i), colorRed, consoleNoColor) +func consoleDefaultFormatErrFieldName(noColor bool) Formatter { + return func(i interface{}) string { + return colorize(fmt.Sprintf("%s=", i), colorRed, noColor) } +} - consoleDefaultFormatErrFieldValue = func(i interface{}) string { - return colorize(fmt.Sprintf("%s", i), colorRed, consoleNoColor) +func consoleDefaultFormatErrFieldValue(noColor bool) Formatter { + return func(i interface{}) string { + return colorize(fmt.Sprintf("%s", i), colorRed, noColor) } -) +}