From 87aceba511b0eb509d8f6438e0f9c74b1ea9b92f Mon Sep 17 00:00:00 2001 From: Olivier Poitrey Date: Tue, 25 Jul 2017 16:55:47 -0700 Subject: [PATCH] Handle special values like Inf and NaN gracefuly --- benchmark_test.go | 35 +++++++++++++++++++++++++++++++++++ field.go | 31 +++++++++++++++++++++++++------ field_test.go | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 93 insertions(+), 6 deletions(-) create mode 100644 field_test.go diff --git a/benchmark_test.go b/benchmark_test.go index c0f1fb1..8464b66 100644 --- a/benchmark_test.go +++ b/benchmark_test.go @@ -71,3 +71,38 @@ func BenchmarkLogFields(b *testing.B) { } }) } + +func BenchmarkLogFieldType(b *testing.B) { + types := map[string]func(e *Event) *Event{ + "Int": func(e *Event) *Event { + return e.Int("int", 1) + }, + "Float32": func(e *Event) *Event { + return e.Float32("float", 1) + }, + "Str": func(e *Event) *Event { + return e.Str("str", "foo") + }, + "Err": func(e *Event) *Event { + return e.Err(errExample) + }, + "Time": func(e *Event) *Event { + return e.Time("time", time.Time{}) + }, + "Dur": func(e *Event) *Event { + return e.Dur("dur", 1*time.Millisecond) + }, + } + logger := New(ioutil.Discard) + b.ResetTimer() + for name := range types { + f := types[name] + b.Run(name, func(b *testing.B) { + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + f(logger.Info()).Msg("") + } + }) + }) + } +} diff --git a/field.go b/field.go index 31be55b..4aa6790 100644 --- a/field.go +++ b/field.go @@ -3,6 +3,7 @@ package zerolog import ( "encoding/json" "fmt" + "math" "sort" "strconv" "time" @@ -157,6 +158,9 @@ func appendErrorsKey(dst []byte, key string, errs []error) []byte { } func appendError(dst []byte, err error) []byte { + if err == nil { + return dst + } return appendErrorKey(dst, ErrorFieldName, err) } @@ -369,8 +373,23 @@ func appendUints64(dst []byte, key string, vals []uint64) []byte { return dst } +func appendFloat(dst []byte, val float64, bitSize int) []byte { + // JSON does not permit NaN or Infinity. A typical JSON encoder would fail + // with an error, but a logging library wants the data to get thru so we + // make a tradeoff and store those types as string. + switch { + case math.IsNaN(val): + return append(dst, `"NaN"`...) + case math.IsInf(val, 1): + return append(dst, `"+Inf"`...) + case math.IsInf(val, -1): + return append(dst, `"-Inf"`...) + } + return strconv.AppendFloat(dst, val, 'f', -1, bitSize) +} + func appendFloat32(dst []byte, key string, val float32) []byte { - return strconv.AppendFloat(appendKey(dst, key), float64(val), 'f', -1, 32) + return appendFloat(appendKey(dst, key), float64(val), 32) } func appendFloats32(dst []byte, key string, vals []float32) []byte { @@ -378,10 +397,10 @@ func appendFloats32(dst []byte, key string, vals []float32) []byte { return append(appendKey(dst, key), '[', ']') } dst = append(appendKey(dst, key), '[') - dst = strconv.AppendFloat(dst, float64(vals[0]), 'f', -1, 32) + dst = appendFloat(dst, float64(vals[0]), 32) if len(vals) > 1 { for _, val := range vals[1:] { - dst = strconv.AppendFloat(append(dst, ','), float64(val), 'f', -1, 32) + dst = appendFloat(append(dst, ','), float64(val), 32) } } dst = append(dst, ']') @@ -389,7 +408,7 @@ func appendFloats32(dst []byte, key string, vals []float32) []byte { } func appendFloat64(dst []byte, key string, val float64) []byte { - return strconv.AppendFloat(appendKey(dst, key), val, 'f', -1, 32) + return appendFloat(appendKey(dst, key), val, 64) } func appendFloats64(dst []byte, key string, vals []float64) []byte { @@ -397,10 +416,10 @@ func appendFloats64(dst []byte, key string, vals []float64) []byte { return append(appendKey(dst, key), '[', ']') } dst = append(appendKey(dst, key), '[') - dst = strconv.AppendFloat(dst, vals[0], 'f', -1, 32) + dst = appendFloat(dst, vals[0], 32) if len(vals) > 1 { for _, val := range vals[1:] { - dst = strconv.AppendFloat(append(dst, ','), val, 'f', -1, 32) + dst = appendFloat(append(dst, ','), val, 64) } } dst = append(dst, ']') diff --git a/field_test.go b/field_test.go new file mode 100644 index 0000000..e1d3f95 --- /dev/null +++ b/field_test.go @@ -0,0 +1,33 @@ +package zerolog + +import ( + "math" + "reflect" + "testing" +) + +func Test_appendFloat64(t *testing.T) { + tests := []struct { + name string + input float64 + want []byte + }{ + {"-Inf", math.Inf(-1), []byte(`"foo":"-Inf"`)}, + {"+Inf", math.Inf(1), []byte(`"foo":"+Inf"`)}, + {"NaN", math.NaN(), []byte(`"foo":"NaN"`)}, + {"0", 0, []byte(`"foo":0`)}, + {"-1.1", -1.1, []byte(`"foo":-1.1`)}, + {"1e20", 1e20, []byte(`"foo":100000000000000000000`)}, + {"1e21", 1e21, []byte(`"foo":1000000000000000000000`)}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := appendFloat32([]byte{}, "foo", float32(tt.input)); !reflect.DeepEqual(got, tt.want) { + t.Errorf("appendFloat32() = %s, want %s", got, tt.want) + } + if got := appendFloat64([]byte{}, "foo", tt.input); !reflect.DeepEqual(got, tt.want) { + t.Errorf("appendFloat32() = %s, want %s", got, tt.want) + } + }) + } +}