event: a better version of labels
Change-Id: I29cf264140cca74687f4f1bf3331ee2d18a76bea
Reviewed-on: https://go-review.googlesource.com/c/exp/+/313430
Trust: Ian Cottrell <iancottrell@google.com>
Run-TryBot: Ian Cottrell <iancottrell@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Jonathan Amsterdam <jba@google.com>
diff --git a/event/alloc_test.go b/event/alloc_test.go
new file mode 100644
index 0000000..f66aa43
--- /dev/null
+++ b/event/alloc_test.go
@@ -0,0 +1,29 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build !race
+
+package event_test
+
+import (
+ "context"
+ "io/ioutil"
+ "testing"
+
+ "golang.org/x/exp/event"
+)
+
+func TestAllocs(t *testing.T) {
+ anInt := event.Label{Name: "int", Value: event.Int64Of(4)}
+ aString := event.Label{Name: "string", Value: event.StringOf("value")}
+
+ e := event.NewExporter(event.Printer(ioutil.Discard))
+ ctx := event.WithExporter(context.Background(), e)
+ allocs := int(testing.AllocsPerRun(5, func() {
+ event.To(ctx).With(aString).With(anInt).Log("message")
+ }))
+ if allocs != 0 {
+ t.Errorf("Got %d allocs, expect 0", allocs)
+ }
+}
diff --git a/event/bench/alloc_test.go b/event/bench/alloc_test.go
new file mode 100644
index 0000000..5996350
--- /dev/null
+++ b/event/bench/alloc_test.go
@@ -0,0 +1,15 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build !race
+
+package bench_test
+
+import (
+ "testing"
+)
+
+func TestLogEventAllocs(t *testing.T) {
+ testAllocs(t, eventPrint, eventLog, 0)
+}
diff --git a/event/bench/bench_test.go b/event/bench/bench_test.go
index fb50637..774bddb 100644
--- a/event/bench/bench_test.go
+++ b/event/bench/bench_test.go
@@ -35,11 +35,11 @@
const (
aName = "A"
- aMsg = "A"
- aMsgf = aMsg + " where a=%d"
+ aMsg = "a"
+ aMsgf = aMsg + " where " + aName + "=%d"
bName = "B"
bMsg = "b"
- bMsgf = bMsg + " where b=%q"
+ bMsgf = bMsg + " where " + bName + "=%q"
timeFormat = "2006/01/02 15:04:05"
)
@@ -61,29 +61,41 @@
return a + len(b)
}
-func runBenchmark(t testing.TB, ctx context.Context, hooks Hooks) {
+func runOnce(ctx context.Context, hooks Hooks) {
var acc int
- if b, ok := t.(*testing.B); ok {
- b.ReportAllocs()
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
- for _, value := range initialList {
- acc += benchA(ctx, hooks, value)
- }
- }
- return
- }
for _, value := range initialList {
acc += benchA(ctx, hooks, value)
}
}
-func testBenchmark(t testing.TB, f func(io.Writer) context.Context, hooks Hooks, expect string) {
+func runBenchmark(b *testing.B, ctx context.Context, hooks Hooks) {
+ b.ReportAllocs()
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ runOnce(ctx, hooks)
+ }
+}
+
+func testBenchmark(t *testing.T, f func(io.Writer) context.Context, hooks Hooks, expect string) {
buf := strings.Builder{}
- runBenchmark(t, f(&buf), hooks)
+ ctx := f(&buf)
+ runOnce(ctx, hooks)
got := strings.TrimSpace(buf.String())
expect = strings.TrimSpace(expect)
if diff := cmp.Diff(got, expect); diff != "" {
t.Error(diff)
}
}
+
+func testAllocs(t *testing.T, f func(io.Writer) context.Context, hooks Hooks, expect int) {
+ var acc int
+ ctx := f(io.Discard)
+ got := int(testing.AllocsPerRun(5, func() {
+ for _, value := range initialList {
+ acc += benchA(ctx, hooks, value)
+ }
+ }))
+ if got != expect {
+ t.Errorf("Got %d allocs, expect %d", got, expect)
+ }
+}
diff --git a/event/bench/event_test.go b/event/bench/event_test.go
index b8c020d..a839ace 100644
--- a/event/bench/event_test.go
+++ b/event/bench/event_test.go
@@ -101,7 +101,7 @@
}
func eventPrint(w io.Writer) context.Context {
- e := event.NewExporter(event.NewPrinter(w))
+ e := event.NewExporter(event.Printer(w))
e.Now = eventtest.TestNow()
return event.WithExporter(context.Background(), e)
}
@@ -128,22 +128,43 @@
func TestLogEventf(t *testing.T) {
testBenchmark(t, eventPrint, eventLogf, `
-2020/03/05 14:27:48 [log:1] A where a=0
-2020/03/05 14:27:49 [log:2] b where b="A value"
-2020/03/05 14:27:50 [log:3] A where a=1
-2020/03/05 14:27:51 [log:4] b where b="Some other value"
-2020/03/05 14:27:52 [log:5] A where a=22
-2020/03/05 14:27:53 [log:6] b where b="Some other value"
-2020/03/05 14:27:54 [log:7] A where a=333
-2020/03/05 14:27:55 [log:8] b where b=""
-2020/03/05 14:27:56 [log:9] A where a=4444
-2020/03/05 14:27:57 [log:10] b where b="prime count of values"
-2020/03/05 14:27:58 [log:11] A where a=55555
-2020/03/05 14:27:59 [log:12] b where b="V"
-2020/03/05 14:28:00 [log:13] A where a=666666
-2020/03/05 14:28:01 [log:14] b where b="A value"
-2020/03/05 14:28:02 [log:15] A where a=7777777
-2020/03/05 14:28:03 [log:16] b where b="A value"
+2020/03/05 14:27:48 [1] log a where A=0
+2020/03/05 14:27:49 [2] log b where B="A value"
+2020/03/05 14:27:50 [3] log a where A=1
+2020/03/05 14:27:51 [4] log b where B="Some other value"
+2020/03/05 14:27:52 [5] log a where A=22
+2020/03/05 14:27:53 [6] log b where B="Some other value"
+2020/03/05 14:27:54 [7] log a where A=333
+2020/03/05 14:27:55 [8] log b where B=""
+2020/03/05 14:27:56 [9] log a where A=4444
+2020/03/05 14:27:57 [10] log b where B="prime count of values"
+2020/03/05 14:27:58 [11] log a where A=55555
+2020/03/05 14:27:59 [12] log b where B="V"
+2020/03/05 14:28:00 [13] log a where A=666666
+2020/03/05 14:28:01 [14] log b where B="A value"
+2020/03/05 14:28:02 [15] log a where A=7777777
+2020/03/05 14:28:03 [16] log b where B="A value"
+`)
+}
+
+func TestLogEvent(t *testing.T) {
+ testBenchmark(t, eventPrint, eventLog, `
+2020/03/05 14:27:48 [1] log a {"A":0}
+2020/03/05 14:27:49 [2] log b {"B":"A value"}
+2020/03/05 14:27:50 [3] log a {"A":1}
+2020/03/05 14:27:51 [4] log b {"B":"Some other value"}
+2020/03/05 14:27:52 [5] log a {"A":22}
+2020/03/05 14:27:53 [6] log b {"B":"Some other value"}
+2020/03/05 14:27:54 [7] log a {"A":333}
+2020/03/05 14:27:55 [8] log b {"B":""}
+2020/03/05 14:27:56 [9] log a {"A":4444}
+2020/03/05 14:27:57 [10] log b {"B":"prime count of values"}
+2020/03/05 14:27:58 [11] log a {"A":55555}
+2020/03/05 14:27:59 [12] log b {"B":"V"}
+2020/03/05 14:28:00 [13] log a {"A":666666}
+2020/03/05 14:28:01 [14] log b {"B":"A value"}
+2020/03/05 14:28:02 [15] log a {"A":7777777}
+2020/03/05 14:28:03 [16] log b {"B":"A value"}
`)
}
diff --git a/event/bench/logrus_test.go b/event/bench/logrus_test.go
index 735e4d2..30344d4 100644
--- a/event/bench/logrus_test.go
+++ b/event/bench/logrus_test.go
@@ -84,21 +84,21 @@
func TestLogrusf(t *testing.T) {
testBenchmark(t, logrusPrint, logrusLogf, `
-time="2020/03/05 14:27:48" level=info msg="A where a=0"
-time="2020/03/05 14:27:49" level=info msg="b where b=\"A value\""
-time="2020/03/05 14:27:50" level=info msg="A where a=1"
-time="2020/03/05 14:27:51" level=info msg="b where b=\"Some other value\""
-time="2020/03/05 14:27:52" level=info msg="A where a=22"
-time="2020/03/05 14:27:53" level=info msg="b where b=\"Some other value\""
-time="2020/03/05 14:27:54" level=info msg="A where a=333"
-time="2020/03/05 14:27:55" level=info msg="b where b=\"\""
-time="2020/03/05 14:27:56" level=info msg="A where a=4444"
-time="2020/03/05 14:27:57" level=info msg="b where b=\"prime count of values\""
-time="2020/03/05 14:27:58" level=info msg="A where a=55555"
-time="2020/03/05 14:27:59" level=info msg="b where b=\"V\""
-time="2020/03/05 14:28:00" level=info msg="A where a=666666"
-time="2020/03/05 14:28:01" level=info msg="b where b=\"A value\""
-time="2020/03/05 14:28:02" level=info msg="A where a=7777777"
-time="2020/03/05 14:28:03" level=info msg="b where b=\"A value\""
+time="2020/03/05 14:27:48" level=info msg="a where A=0"
+time="2020/03/05 14:27:49" level=info msg="b where B=\"A value\""
+time="2020/03/05 14:27:50" level=info msg="a where A=1"
+time="2020/03/05 14:27:51" level=info msg="b where B=\"Some other value\""
+time="2020/03/05 14:27:52" level=info msg="a where A=22"
+time="2020/03/05 14:27:53" level=info msg="b where B=\"Some other value\""
+time="2020/03/05 14:27:54" level=info msg="a where A=333"
+time="2020/03/05 14:27:55" level=info msg="b where B=\"\""
+time="2020/03/05 14:27:56" level=info msg="a where A=4444"
+time="2020/03/05 14:27:57" level=info msg="b where B=\"prime count of values\""
+time="2020/03/05 14:27:58" level=info msg="a where A=55555"
+time="2020/03/05 14:27:59" level=info msg="b where B=\"V\""
+time="2020/03/05 14:28:00" level=info msg="a where A=666666"
+time="2020/03/05 14:28:01" level=info msg="b where B=\"A value\""
+time="2020/03/05 14:28:02" level=info msg="a where A=7777777"
+time="2020/03/05 14:28:03" level=info msg="b where B=\"A value\""
`)
}
diff --git a/event/bench/stdlib_test.go b/event/bench/stdlib_test.go
index 410963d..b91bf68 100644
--- a/event/bench/stdlib_test.go
+++ b/event/bench/stdlib_test.go
@@ -103,42 +103,42 @@
func TestLogStdlib(t *testing.T) {
testBenchmark(t, stdlibLoggerNoTime, stdlibLog, `
-A where a=0
-b where b="A value"
-A where a=1
-b where b="Some other value"
-A where a=22
-b where b="Some other value"
-A where a=333
-b where b=""
-A where a=4444
-b where b="prime count of values"
-A where a=55555
-b where b="V"
-A where a=666666
-b where b="A value"
-A where a=7777777
-b where b="A value"
+a where A=0
+b where B="A value"
+a where A=1
+b where B="Some other value"
+a where A=22
+b where B="Some other value"
+a where A=333
+b where B=""
+a where A=4444
+b where B="prime count of values"
+a where A=55555
+b where B="V"
+a where A=666666
+b where B="A value"
+a where A=7777777
+b where B="A value"
`)
}
func TestLogPrintf(t *testing.T) {
testBenchmark(t, stdlibWriter, stdlibPrintf, `
-2020/03/05 14:27:48 A where a=0
-2020/03/05 14:27:49 b where b="A value"
-2020/03/05 14:27:50 A where a=1
-2020/03/05 14:27:51 b where b="Some other value"
-2020/03/05 14:27:52 A where a=22
-2020/03/05 14:27:53 b where b="Some other value"
-2020/03/05 14:27:54 A where a=333
-2020/03/05 14:27:55 b where b=""
-2020/03/05 14:27:56 A where a=4444
-2020/03/05 14:27:57 b where b="prime count of values"
-2020/03/05 14:27:58 A where a=55555
-2020/03/05 14:27:59 b where b="V"
-2020/03/05 14:28:00 A where a=666666
-2020/03/05 14:28:01 b where b="A value"
-2020/03/05 14:28:02 A where a=7777777
-2020/03/05 14:28:03 b where b="A value"
+2020/03/05 14:27:48 a where A=0
+2020/03/05 14:27:49 b where B="A value"
+2020/03/05 14:27:50 a where A=1
+2020/03/05 14:27:51 b where B="Some other value"
+2020/03/05 14:27:52 a where A=22
+2020/03/05 14:27:53 b where B="Some other value"
+2020/03/05 14:27:54 a where A=333
+2020/03/05 14:27:55 b where B=""
+2020/03/05 14:27:56 a where A=4444
+2020/03/05 14:27:57 b where B="prime count of values"
+2020/03/05 14:27:58 a where A=55555
+2020/03/05 14:27:59 b where B="V"
+2020/03/05 14:28:00 a where A=666666
+2020/03/05 14:28:01 b where B="A value"
+2020/03/05 14:28:02 a where A=7777777
+2020/03/05 14:28:03 b where B="A value"
`)
}
diff --git a/event/bench/zap_test.go b/event/bench/zap_test.go
index e66642f..7878f35 100644
--- a/event/bench/zap_test.go
+++ b/event/bench/zap_test.go
@@ -23,7 +23,7 @@
},
AEnd: func(ctx context.Context) {},
BStart: func(ctx context.Context, b string) context.Context {
- zapCtx(ctx).Info(aMsg, zap.String(bName, b))
+ zapCtx(ctx).Info(bMsg, zap.String(bName, b))
return ctx
},
BEnd: func(ctx context.Context) {},
@@ -75,21 +75,41 @@
func TestLogZapf(t *testing.T) {
testBenchmark(t, zapPrint, zapLogf, `
-2020/03/05 14:27:48 info A where a=0
-2020/03/05 14:27:49 info b where b="A value"
-2020/03/05 14:27:50 info A where a=1
-2020/03/05 14:27:51 info b where b="Some other value"
-2020/03/05 14:27:52 info A where a=22
-2020/03/05 14:27:53 info b where b="Some other value"
-2020/03/05 14:27:54 info A where a=333
-2020/03/05 14:27:55 info b where b=""
-2020/03/05 14:27:56 info A where a=4444
-2020/03/05 14:27:57 info b where b="prime count of values"
-2020/03/05 14:27:58 info A where a=55555
-2020/03/05 14:27:59 info b where b="V"
-2020/03/05 14:28:00 info A where a=666666
-2020/03/05 14:28:01 info b where b="A value"
-2020/03/05 14:28:02 info A where a=7777777
-2020/03/05 14:28:03 info b where b="A value"
+2020/03/05 14:27:48 info a where A=0
+2020/03/05 14:27:49 info b where B="A value"
+2020/03/05 14:27:50 info a where A=1
+2020/03/05 14:27:51 info b where B="Some other value"
+2020/03/05 14:27:52 info a where A=22
+2020/03/05 14:27:53 info b where B="Some other value"
+2020/03/05 14:27:54 info a where A=333
+2020/03/05 14:27:55 info b where B=""
+2020/03/05 14:27:56 info a where A=4444
+2020/03/05 14:27:57 info b where B="prime count of values"
+2020/03/05 14:27:58 info a where A=55555
+2020/03/05 14:27:59 info b where B="V"
+2020/03/05 14:28:00 info a where A=666666
+2020/03/05 14:28:01 info b where B="A value"
+2020/03/05 14:28:02 info a where A=7777777
+2020/03/05 14:28:03 info b where B="A value"
+`)
+}
+func TestLogZap(t *testing.T) {
+ testBenchmark(t, zapPrint, zapLog, `
+2020/03/05 14:27:48 info a {"A": 0}
+2020/03/05 14:27:49 info b {"B": "A value"}
+2020/03/05 14:27:50 info a {"A": 1}
+2020/03/05 14:27:51 info b {"B": "Some other value"}
+2020/03/05 14:27:52 info a {"A": 22}
+2020/03/05 14:27:53 info b {"B": "Some other value"}
+2020/03/05 14:27:54 info a {"A": 333}
+2020/03/05 14:27:55 info b {"B": ""}
+2020/03/05 14:27:56 info a {"A": 4444}
+2020/03/05 14:27:57 info b {"B": "prime count of values"}
+2020/03/05 14:27:58 info a {"A": 55555}
+2020/03/05 14:27:59 info b {"B": "V"}
+2020/03/05 14:28:00 info a {"A": 666666}
+2020/03/05 14:28:01 info b {"B": "A value"}
+2020/03/05 14:28:02 info a {"A": 7777777}
+2020/03/05 14:28:03 info b {"B": "A value"}
`)
}
diff --git a/event/bench/zerolog_test.go b/event/bench/zerolog_test.go
index f5a048d..a3d694b 100644
--- a/event/bench/zerolog_test.go
+++ b/event/bench/zerolog_test.go
@@ -58,21 +58,21 @@
func TestLogZerologf(t *testing.T) {
testBenchmark(t, zerologPrint, zerologMsgf, `
-{"level":"info","time":"2020/03/05 14:27:48","message":"A where a=0"}
-{"level":"info","time":"2020/03/05 14:27:49","message":"b where b=\"A value\""}
-{"level":"info","time":"2020/03/05 14:27:50","message":"A where a=1"}
-{"level":"info","time":"2020/03/05 14:27:51","message":"b where b=\"Some other value\""}
-{"level":"info","time":"2020/03/05 14:27:52","message":"A where a=22"}
-{"level":"info","time":"2020/03/05 14:27:53","message":"b where b=\"Some other value\""}
-{"level":"info","time":"2020/03/05 14:27:54","message":"A where a=333"}
-{"level":"info","time":"2020/03/05 14:27:55","message":"b where b=\"\""}
-{"level":"info","time":"2020/03/05 14:27:56","message":"A where a=4444"}
-{"level":"info","time":"2020/03/05 14:27:57","message":"b where b=\"prime count of values\""}
-{"level":"info","time":"2020/03/05 14:27:58","message":"A where a=55555"}
-{"level":"info","time":"2020/03/05 14:27:59","message":"b where b=\"V\""}
-{"level":"info","time":"2020/03/05 14:28:00","message":"A where a=666666"}
-{"level":"info","time":"2020/03/05 14:28:01","message":"b where b=\"A value\""}
-{"level":"info","time":"2020/03/05 14:28:02","message":"A where a=7777777"}
-{"level":"info","time":"2020/03/05 14:28:03","message":"b where b=\"A value\""}
+{"level":"info","time":"2020/03/05 14:27:48","message":"a where A=0"}
+{"level":"info","time":"2020/03/05 14:27:49","message":"b where B=\"A value\""}
+{"level":"info","time":"2020/03/05 14:27:50","message":"a where A=1"}
+{"level":"info","time":"2020/03/05 14:27:51","message":"b where B=\"Some other value\""}
+{"level":"info","time":"2020/03/05 14:27:52","message":"a where A=22"}
+{"level":"info","time":"2020/03/05 14:27:53","message":"b where B=\"Some other value\""}
+{"level":"info","time":"2020/03/05 14:27:54","message":"a where A=333"}
+{"level":"info","time":"2020/03/05 14:27:55","message":"b where B=\"\""}
+{"level":"info","time":"2020/03/05 14:27:56","message":"a where A=4444"}
+{"level":"info","time":"2020/03/05 14:27:57","message":"b where B=\"prime count of values\""}
+{"level":"info","time":"2020/03/05 14:27:58","message":"a where A=55555"}
+{"level":"info","time":"2020/03/05 14:27:59","message":"b where B=\"V\""}
+{"level":"info","time":"2020/03/05 14:28:00","message":"a where A=666666"}
+{"level":"info","time":"2020/03/05 14:28:01","message":"b where B=\"A value\""}
+{"level":"info","time":"2020/03/05 14:28:02","message":"a where A=7777777"}
+{"level":"info","time":"2020/03/05 14:28:03","message":"b where B=\"A value\""}
`)
}
diff --git a/event/builder_test.go b/event/builder_test.go
index a683b2b..ecd07ce 100644
--- a/event/builder_test.go
+++ b/event/builder_test.go
@@ -5,7 +5,6 @@
package event_test
import (
- "bytes"
"fmt"
"testing"
@@ -28,7 +27,7 @@
check := func(b *event.Builder, want []event.Label) {
t.Helper()
- if got := b.Event.Labels; !cmp.Equal(got, want, cmp.Comparer(labelEqual)) {
+ if got := b.Event.Labels; !cmp.Equal(got, want, cmp.Comparer(valueEqual)) {
t.Fatalf("got %v, want %v", got, want)
}
}
@@ -62,13 +61,6 @@
check(b2, labels[3:5])
}
-func labelEqual(l1, l2 event.Label) bool {
- return labelString(l1) == labelString(l2)
-}
-
-func labelString(l event.Label) string {
- var buf bytes.Buffer
- p := event.NewPrinter(&buf)
- p.Label(l)
- return buf.String()
+func valueEqual(l1, l2 event.Value) bool {
+ return fmt.Sprint(l1) == fmt.Sprint(l2)
}
diff --git a/event/event.go b/event/event.go
index ec28174..5c26cd6 100644
--- a/event/event.go
+++ b/event/event.go
@@ -6,6 +6,9 @@
import (
"fmt"
+ "io"
+ "strconv"
+ "sync"
"time"
)
@@ -38,31 +41,101 @@
AnnotateKind
)
-// Find searches the labels of an event to see if one of them has the
-// supplied key.
-func (ev Event) Find(key string) Label {
- for _, l := range ev.Labels {
- if l.Key() == key {
- return l
- }
- }
- return Label{}
+// Format prints the value in a standard form.
+func (e *Event) Format(f fmt.State, verb rune) {
+ buf := bufPool.Get().(*buffer)
+ e.format(f.(writer), buf.data[:0])
+ bufPool.Put(buf)
}
-// String returns a string representation of the kind for printing.
-func (k Kind) String() string {
+// Format prints the value in a standard form.
+func (e *Event) format(w writer, buf []byte) {
+ const timeFormat = "2006/01/02 15:04:05"
+ if !e.At.IsZero() {
+ w.Write(e.At.AppendFormat(buf[:0], timeFormat))
+ w.WriteString("\t")
+ }
+ //TODO: pick a standard format for the event id and parent
+ w.WriteString("[")
+ w.Write(strconv.AppendUint(buf[:0], e.ID, 10))
+ if e.Parent != 0 {
+ w.WriteString(":")
+ w.Write(strconv.AppendUint(buf[:0], e.Parent, 10))
+ }
+ w.WriteString("]")
+
+ //TODO: pick a standard format for the kind
+ w.WriteString("\t")
+ e.Kind.format(w, buf)
+
+ if e.Message != "" {
+ w.WriteString("\t")
+ w.WriteString(e.Message)
+ }
+
+ first := true
+ for _, l := range e.Labels {
+ if l.Name == "" {
+ continue
+ }
+ if first {
+ w.WriteString("\t{")
+ first = false
+ } else {
+ w.WriteString(", ")
+ }
+ l.format(w, buf)
+ }
+ if !first {
+ w.WriteString("}")
+ }
+}
+
+func (k Kind) Format(f fmt.State, verb rune) {
+ buf := bufPool.Get().(*buffer)
+ k.format(f.(writer), buf.data[:0])
+ bufPool.Put(buf)
+}
+
+func (k Kind) format(w writer, buf []byte) {
switch k {
case LogKind:
- return "log"
+ w.WriteString("log")
case StartKind:
- return "start"
+ w.WriteString("start")
case EndKind:
- return "end"
+ w.WriteString("end")
case MetricKind:
- return "metric"
+ w.WriteString("metric")
case AnnotateKind:
- return "annotate"
+ w.WriteString("annotate")
default:
- return fmt.Sprint(byte(k))
+ w.Write(strconv.AppendUint(buf[:0], uint64(k), 10))
}
}
+
+// Printer returns a handler that prints the events to the supplied writer.
+// Each event is printed in normal %v mode on its own line.
+func Printer(to io.Writer) Handler {
+ return &printHandler{to: to}
+}
+
+type printHandler struct {
+ to io.Writer
+}
+
+func (h *printHandler) Handle(ev *Event) {
+ fmt.Fprintln(h.to, ev)
+}
+
+//TODO: some actual research into what this arbritray optimization number should be
+const bufCap = 50
+
+type buffer struct{ data [bufCap]byte }
+
+var bufPool = sync.Pool{New: func() interface{} { return new(buffer) }}
+
+type writer interface {
+ io.Writer
+ io.StringWriter
+}
diff --git a/event/event_test.go b/event/event_test.go
index 665f31a..689858d 100644
--- a/event/event_test.go
+++ b/event/event_test.go
@@ -21,15 +21,6 @@
l3 = keys.Int("l3").Of(3)
)
-type captureHandler struct {
- printer event.Printer
- buf strings.Builder
-}
-
-func (e *captureHandler) Handle(ev *event.Event) {
- e.printer.Handle(ev)
-}
-
func TestPrint(t *testing.T) {
ctx := context.Background()
for _, test := range []struct {
@@ -40,37 +31,27 @@
name: "simple",
events: func(ctx context.Context) { event.To(ctx).Log("a message") },
expect: `
-2020/03/05 14:27:48 [log:1] a message
+2020/03/05 14:27:48 [1] log a message
`}, {
name: "log 1",
events: func(ctx context.Context) { event.To(ctx).With(l1).Log("a message") },
- expect: `
-2020/03/05 14:27:48 [log:1] a message
- l1=1
-`}, {
+ expect: `2020/03/05 14:27:48 [1] log a message {"l1":1}`}, {
name: "simple",
events: func(ctx context.Context) { event.To(ctx).With(l1).With(l2).Log("a message") },
- expect: `
-2020/03/05 14:27:48 [log:1] a message
- l1=1
- l2=2
-`}, {
+ expect: `2020/03/05 14:27:48 [1] log a message {"l1":1, "l2":2}`,
+ }, {
name: "simple",
events: func(ctx context.Context) { event.To(ctx).With(l1).With(l2).With(l3).Log("a message") },
- expect: `
-2020/03/05 14:27:48 [log:1] a message
- l1=1
- l2=2
- l3=3
-`}, {
+ expect: `2020/03/05 14:27:48 [1] log a message {"l1":1, "l2":2, "l3":3}`,
+ }, {
name: "span",
events: func(ctx context.Context) {
ctx, end := event.Start(ctx, "span")
end()
},
expect: `
-2020/03/05 14:27:48 [start:1] span
-2020/03/05 14:27:49 [end:2:1]
+2020/03/05 14:27:48 [1] start span
+2020/03/05 14:27:49 [2:1] end
`}, {
name: "span nested",
events: func(ctx context.Context) {
@@ -81,38 +62,28 @@
event.To(child).Log("message")
},
expect: `
-2020/03/05 14:27:48 [start:1] parent
-2020/03/05 14:27:49 [start:2:1] child
-2020/03/05 14:27:50 [log:3:2] message
-2020/03/05 14:27:51 [end:4:2]
-2020/03/05 14:27:52 [end:5:1]
+2020/03/05 14:27:48 [1] start parent
+2020/03/05 14:27:49 [2:1] start child
+2020/03/05 14:27:50 [3:2] log message
+2020/03/05 14:27:51 [4:2] end
+2020/03/05 14:27:52 [5:1] end
`}, {
name: "metric",
events: func(ctx context.Context) { event.To(ctx).With(l1).Metric() },
- expect: `
-2020/03/05 14:27:48 [metric:1]
- l1=1
-`}, {
+ expect: `2020/03/05 14:27:48 [1] metric {"l1":1}`,
+ }, {
name: "metric 2",
events: func(ctx context.Context) { event.To(ctx).With(l1).With(l2).Metric() },
- expect: `
-2020/03/05 14:27:48 [metric:1]
- l1=1
- l2=2
-`}, {
+ expect: `2020/03/05 14:27:48 [1] metric {"l1":1, "l2":2}`,
+ }, {
name: "annotate",
events: func(ctx context.Context) { event.To(ctx).With(l1).Annotate() },
- expect: `
-2020/03/05 14:27:48 [annotate:1]
- l1=1
-`}, {
+ expect: `2020/03/05 14:27:48 [1] annotate {"l1":1}`,
+ }, {
name: "annotate 2",
events: func(ctx context.Context) { event.To(ctx).With(l1).With(l2).Annotate() },
- expect: `
-2020/03/05 14:27:48 [annotate:1]
- l1=1
- l2=2
-`}, {
+ expect: `2020/03/05 14:27:48 [1] annotate {"l1":1, "l2":2}`,
+ }, {
name: "multiple events",
events: func(ctx context.Context) {
b := event.To(ctx)
@@ -120,34 +91,30 @@
b.With(keys.String("myString").Of("some string value")).Log("string event")
},
expect: `
-2020/03/05 14:27:48 [log:1] my event
- myInt=6
-2020/03/05 14:27:49 [log:2] string event
- myString="some string value"
+2020/03/05 14:27:48 [1] log my event {"myInt":6}
+2020/03/05 14:27:49 [2] log string event {"myString":"some string value"}
`}} {
- h := &captureHandler{}
- h.printer = event.NewPrinter(&h.buf)
+ buf := &strings.Builder{}
+ h := event.Printer(buf)
e := event.NewExporter(h)
e.Now = eventtest.TestNow()
ctx := event.WithExporter(ctx, e)
test.events(ctx)
- got := strings.TrimSpace(h.buf.String())
+ got := strings.TrimSpace(buf.String())
expect := strings.TrimSpace(test.expect)
if got != expect {
- t.Errorf("%s failed\ngot : %q\nexpect: %q", test.name, got, expect)
+ t.Errorf("%s failed\ngot : %s\nexpect: %s", test.name, got, expect)
}
}
}
func ExampleLog() {
- e := event.NewExporter(event.NewPrinter(os.Stdout))
+ e := event.NewExporter(event.Printer(os.Stdout))
e.Now = eventtest.TestNow()
ctx := event.WithExporter(context.Background(), e)
event.To(ctx).With(keys.Int("myInt").Of(6)).Log("my event")
event.To(ctx).With(keys.String("myString").Of("some string value")).Log("error event")
// Output:
- // 2020/03/05 14:27:48 [log:1] my event
- // myInt=6
- // 2020/03/05 14:27:49 [log:2] error event
- // myString="some string value"
+ // 2020/03/05 14:27:48 [1] log my event {"myInt":6}
+ // 2020/03/05 14:27:49 [2] log error event {"myString":"some string value"}
}
diff --git a/event/eventtest/eventtest.go b/event/eventtest/eventtest.go
index badd861..9e0a8de 100644
--- a/event/eventtest/eventtest.go
+++ b/event/eventtest/eventtest.go
@@ -11,6 +11,7 @@
import (
"context"
+ "fmt"
"strings"
"testing"
"time"
@@ -21,20 +22,18 @@
// NewContext returns a context you should use for the active test.
func NewContext(ctx context.Context, tb testing.TB) context.Context {
h := &testHandler{tb: tb}
- h.printer = event.NewPrinter(&h.buf)
return event.WithExporter(ctx, event.NewExporter(h))
}
type testHandler struct {
- tb testing.TB
- printer event.Printer
- buf strings.Builder
+ tb testing.TB
+ buf strings.Builder
}
func (w *testHandler) Handle(ev *event.Event) {
// build our log message in buffer
w.buf.Reset()
- w.printer.Handle(ev)
+ fmt.Fprint(&w.buf, ev)
// log to the testing.TB
msg := w.buf.String()
if len(msg) > 0 {
diff --git a/event/keys/keys.go b/event/keys/keys.go
index b18b0b7..574d696 100644
--- a/event/keys/keys.go
+++ b/event/keys/keys.go
@@ -5,22 +5,18 @@
package keys
import (
- "math"
-
"golang.org/x/exp/event"
)
// Value represents a key for untyped values.
type Value string
-func (k Value) Name() string { return string(k) }
-
// From can be used to get a value from a Label.
-func (k Value) From(t event.Label) interface{} { return t.UnpackValue() }
+func (k Value) From(l event.Label) interface{} { return l.Value.Interface() }
// Of creates a new Label with this key and the supplied value.
-func (k Value) Of(value interface{}) event.Label {
- return event.OfValue(string(k), dispatchValue, value)
+func (k Value) Of(v interface{}) event.Label {
+ return event.Label{Name: string(k), Value: event.ValueOf(v)}
}
// Tag represents a key for tagging labels that have no value.
@@ -29,189 +25,163 @@
// package.
type Tag string
-func (k Tag) Name() string { return string(k) }
-
// New creates a new Label with this key.
-func (k Tag) New() event.Label { return event.OfValue(string(k), nil, nil) }
+func (k Tag) New() event.Label {
+ return event.Label{Name: string(k)}
+}
// Int represents a key
type Int string
-func (k Int) Name() string { return string(k) }
-
// Of creates a new Label with this key and the supplied value.
-func (k Int) Of(v int) event.Label { return event.Of64(string(k), dispatchInt, uint64(v)) }
+func (k Int) Of(v int) event.Label {
+ return event.Label{Name: string(k), Value: event.Int64Of(int64(v))}
+}
// From can be used to get a value from a Label.
-func (k Int) From(l event.Label) int { return int(l.Unpack64()) }
+func (k Int) From(l event.Label) int { return int(l.Value.Int64()) }
// Int8 represents a key
type Int8 string
-func (k Int8) Name() string { return string(k) }
-func (k Int8) Print(p event.Printer, l event.Label) { p.Int(int64(k.From(l))) }
-
// Of creates a new Label with this key and the supplied value.
-func (k Int8) Of(v int8) event.Label { return event.Of64(string(k), dispatchInt, uint64(v)) }
+func (k Int8) Of(v int8) event.Label {
+ return event.Label{Name: string(k), Value: event.Int64Of(int64(v))}
+}
// From can be used to get a value from a Label.
-func (k Int8) From(t event.Label) int8 { return int8(t.Unpack64()) }
+func (k Int8) From(l event.Label) int8 { return int8(l.Value.Int64()) }
// Int16 represents a key
type Int16 string
-func (k Int16) Name() string { return string(k) }
-
// Of creates a new Label with this key and the supplied value.
-func (k Int16) Of(v int16) event.Label { return event.Of64(string(k), dispatchInt, uint64(v)) }
+func (k Int16) Of(v int16) event.Label {
+ return event.Label{Name: string(k), Value: event.Int64Of(int64(v))}
+}
// From can be used to get a value from a Label.
-func (k Int16) From(t event.Label) int16 { return int16(t.Unpack64()) }
+func (k Int16) From(l event.Label) int16 { return int16(l.Value.Int64()) }
// Int32 represents a key
type Int32 string
-func (k Int32) Name() string { return string(k) }
-
// Of creates a new Label with this key and the supplied value.
-func (k Int32) Of(v int32) event.Label { return event.Of64(string(k), dispatchInt, uint64(v)) }
+func (k Int32) Of(v int32) event.Label {
+ return event.Label{Name: string(k), Value: event.Int64Of(int64(v))}
+}
// From can be used to get a value from a Label.
-func (k Int32) From(t event.Label) int32 { return int32(t.Unpack64()) }
+func (k Int32) From(l event.Label) int32 { return int32(l.Value.Int64()) }
// Int64 represents a key
type Int64 string
-func (k Int64) Name() string { return string(k) }
-
// Of creates a new Label with this key and the supplied value.
-func (k Int64) Of(v int64) event.Label { return event.Of64(string(k), dispatchInt, uint64(v)) }
+func (k Int64) Of(v int64) event.Label {
+ return event.Label{Name: string(k), Value: event.Int64Of(v)}
+}
// From can be used to get a value from a Label.
-func (k Int64) From(t event.Label) int64 { return int64(t.Unpack64()) }
+func (k Int64) From(l event.Label) int64 { return l.Value.Int64() }
// UInt represents a key
type UInt string
-func (k UInt) Name() string { return string(k) }
-
// Of creates a new Label with this key and the supplied value.
-func (k UInt) Of(v uint) event.Label { return event.Of64(string(k), dispatchUint, uint64(v)) }
+func (k UInt) Of(v uint) event.Label {
+ return event.Label{Name: string(k), Value: event.Uint64Of(uint64(v))}
+}
// From can be used to get a value from a Label.
-func (k UInt) From(t event.Label) uint { return uint(t.Unpack64()) }
+func (k UInt) From(l event.Label) uint { return uint(l.Value.Uint64()) }
// UInt8 represents a key
type UInt8 string
-func (k UInt8) Name() string { return string(k) }
-
// Of creates a new Label with this key and the supplied value.
-func (k UInt8) Of(v uint8) event.Label { return event.Of64(string(k), dispatchUint, uint64(v)) }
+func (k UInt8) Of(v uint8) event.Label {
+ return event.Label{Name: string(k), Value: event.Uint64Of(uint64(v))}
+}
// From can be used to get a value from a Label.
-func (k UInt8) From(t event.Label) uint8 { return uint8(t.Unpack64()) }
+func (k UInt8) From(l event.Label) uint8 { return uint8(l.Value.Uint64()) }
// UInt16 represents a key
type UInt16 string
-func (k UInt16) Name() string { return string(k) }
-
// Of creates a new Label with this key and the supplied value.
-func (k UInt16) Of(v uint16) event.Label { return event.Of64(string(k), dispatchUint, uint64(v)) }
+func (k UInt16) Of(v uint16) event.Label {
+ return event.Label{Name: string(k), Value: event.Uint64Of(uint64(v))}
+}
// From can be used to get a value from a Label.
-func (k UInt16) From(t event.Label) uint16 { return uint16(t.Unpack64()) }
+func (k UInt16) From(l event.Label) uint16 { return uint16(l.Value.Uint64()) }
// UInt32 represents a key
type UInt32 string
-func (k UInt32) Name() string { return string(k) }
-
// Of creates a new Label with this key and the supplied value.
-func (k UInt32) Of(v uint32) event.Label { return event.Of64(string(k), dispatchUint, uint64(v)) }
+func (k UInt32) Of(v uint32) event.Label {
+ return event.Label{Name: string(k), Value: event.Uint64Of(uint64(v))}
+}
// From can be used to get a value from a Label.
-func (k UInt32) From(t event.Label) uint32 { return uint32(t.Unpack64()) }
+func (k UInt32) From(l event.Label) uint32 { return uint32(l.Value.Uint64()) }
// UInt64 represents a key
type UInt64 string
-func (k UInt64) Name() string { return string(k) }
-
// Of creates a new Label with this key and the supplied value.
-func (k UInt64) Of(v uint64) event.Label { return event.Of64(string(k), dispatchUint, v) }
+func (k UInt64) Of(v uint64) event.Label {
+ return event.Label{Name: string(k), Value: event.Uint64Of(v)}
+}
// From can be used to get a value from a Label.
-func (k UInt64) From(t event.Label) uint64 { return t.Unpack64() }
+func (k UInt64) From(l event.Label) uint64 { return l.Value.Uint64() }
// Float32 represents a key
type Float32 string
-func (k Float32) Name() string { return string(k) }
-
// Of creates a new Label with this key and the supplied value.
func (k Float32) Of(v float32) event.Label {
- return event.Of64(string(k), dispatchFloat, uint64(math.Float32bits(v)))
+ return event.Label{Name: string(k), Value: event.Float64Of(float64(v))}
}
// From can be used to get a value from a Label.
-func (k Float32) From(t event.Label) float32 {
- return math.Float32frombits(uint32(t.Unpack64()))
-}
+func (k Float32) From(l event.Label) float32 { return float32(l.Value.Float64()) }
// Float64 represents a key
type Float64 string
-func (k Float64) Name() string { return string(k) }
-
// Of creates a new Label with this key and the supplied value.
func (k Float64) Of(v float64) event.Label {
- return event.Of64(string(k), dispatchFloat, math.Float64bits(v))
+ return event.Label{Name: string(k), Value: event.Float64Of(v)}
}
// From can be used to get a value from a Label.
-func (k Float64) From(t event.Label) float64 {
- return math.Float64frombits(t.Unpack64())
+func (k Float64) From(l event.Label) float64 {
+ return l.Value.Float64()
}
// String represents a key
type String string
-func (k String) Name() string { return string(k) }
-
// Of creates a new Label with this key and the supplied value.
-func (k String) Of(v string) event.Label { return event.OfString(string(k), dispatchString, v) }
-
-// From can be used to get a value from a Label.
-func (k String) From(t event.Label) string { return t.UnpackString() }
-
-// Boolean represents a key
-type Boolean string
-
-func (k Boolean) Name() string { return string(k) }
-
-// Of creates a new Label with this key and the supplied value.
-func (k Boolean) Of(v bool) event.Label {
- if v {
- return event.Of64(string(k), dispatchBoolean, 1)
- }
- return event.Of64(string(k), dispatchBoolean, 0)
+func (k String) Of(v string) event.Label {
+ return event.Label{Name: string(k), Value: event.StringOf(v)}
}
// From can be used to get a value from a Label.
-func (k Boolean) From(t event.Label) bool { return t.Unpack64() > 0 }
+func (k String) From(l event.Label) string { return l.Value.String() }
-func dispatchValue(h event.ValueHandler, l event.Label) { h.Value(l.UnpackValue()) }
-func dispatchInt(h event.ValueHandler, l event.Label) { h.Int(int64(l.Unpack64())) }
-func dispatchUint(h event.ValueHandler, l event.Label) { h.Uint(l.Unpack64()) }
-func dispatchString(h event.ValueHandler, l event.Label) { h.Quote(l.UnpackString()) }
-func dispatchFloat(h event.ValueHandler, l event.Label) { h.Float(math.Float64frombits(l.Unpack64())) }
+// Bool represents a key
+type Bool string
-func dispatchBoolean(h event.ValueHandler, l event.Label) {
- if l.Unpack64() > 0 {
- h.String("true")
- } else {
- h.String("false")
- }
+// Of creates a new Label with this key and the supplied value.
+func (k Bool) Of(v bool) event.Label {
+ return event.Label{Name: string(k), Value: event.BoolOf(v)}
}
+
+// From can be used to get a value from a Label.
+func (k Bool) From(l event.Label) bool { return l.Value.Bool() }
diff --git a/event/label.go b/event/label.go
index d867dd4..6fab1e8 100644
--- a/event/label.go
+++ b/event/label.go
@@ -6,135 +6,223 @@
import (
"fmt"
+ "math"
"reflect"
"strconv"
+ "strings"
"unsafe"
)
-// ValueHandler is used to safely unpack unknown labels.
-type ValueHandler interface {
- String(v string)
- Quote(v string)
- Int(v int64)
- Uint(v uint64)
- Float(v float64)
- Value(v interface{})
+// Value holds any value in an efficient way that avoids allocations for
+// most types.
+type Value struct {
+ packed uint64
+ untyped interface{}
}
-// LabelDispatcher is used as the identity of a Label.
-type LabelDispatcher func(h ValueHandler, l Label)
-
-// Label holds a key and value pair.
-// It is normally used when passing around lists of labels.
+// Label is a named value.
type Label struct {
- key string
- dispatch LabelDispatcher
- packed uint64
- untyped interface{}
+ Name string
+ Value Value
}
-// OfValue creates a new label from the key and value.
-// This method is for implementing new key types, label creation should
-// normally be done with the Of method of the key.
-func OfValue(k string, d LabelDispatcher, value interface{}) Label {
- return Label{key: k, dispatch: d, untyped: value}
-}
-
-// UnpackValue assumes the label was built using LabelOfValue and returns the value
-// that was passed to that constructor.
-// This method is for implementing new key types, for type safety normal
-// access should be done with the From method of the key.
-func (l Label) UnpackValue() interface{} { return l.untyped }
-
-// Of64 creates a new label from a key and a uint64. This is often
-// used for non uint64 values that can be packed into a uint64.
-// This method is for implementing new key types, label creation should
-// normally be done with the Of method of the key.
-func Of64(k string, d LabelDispatcher, v uint64) Label {
- return Label{key: k, dispatch: d, packed: v}
-}
-
-// Unpack64 assumes the label was built using LabelOf64 and returns the value that
-// was passed to that constructor.
-// This method is for implementing new key types, for type safety normal
-// access should be done with the From method of the key.
-func (l Label) Unpack64() uint64 { return l.packed }
-
+// stringptr is used in untyped when the Value is a string
type stringptr unsafe.Pointer
-// OfString creates a new label from a key and a string.
-// This method is for implementing new key types, label creation should
-// normally be done with the Of method of the key.
-func OfString(k string, d LabelDispatcher, v string) Label {
- hdr := (*reflect.StringHeader)(unsafe.Pointer(&v))
- return Label{
- key: k,
- dispatch: d,
- packed: uint64(hdr.Len),
- untyped: stringptr(hdr.Data),
+// int64Kind is used in untyped when the Value is a signed integer
+type int64Kind struct{}
+
+// uint64Kind is used in untyped when the Value is an unsigned integer
+type uint64Kind struct{}
+
+// float64Kind is used in untyped when the Value is a floating point number
+type float64Kind struct{}
+
+// boolKind is used in untyped when the Value is a boolean
+type boolKind struct{}
+
+// Format prints the label in a standard form.
+func (l *Label) Format(f fmt.State, verb rune) {
+ buf := bufPool.Get().(*buffer)
+ l.format(f.(writer), buf.data[:0])
+ bufPool.Put(buf)
+}
+
+func (l *Label) format(w writer, buf []byte) {
+ w.Write(strconv.AppendQuote(buf[:0], l.Name))
+ w.WriteString(":")
+ l.Value.format(w, buf)
+}
+
+// Format prints the value in a standard form.
+func (v *Value) Format(f fmt.State, verb rune) {
+ buf := bufPool.Get().(*buffer)
+ v.format(f.(writer), buf.data[:0])
+ bufPool.Put(buf)
+}
+
+func (v *Value) format(w writer, buf []byte) {
+ switch {
+ case v.IsString():
+ w.Write(strconv.AppendQuote(buf[:0], v.String()))
+ case v.IsInt64():
+ w.Write(strconv.AppendInt(buf[:0], v.Int64(), 10))
+ case v.IsUint64():
+ w.Write(strconv.AppendUint(buf[:0], v.Uint64(), 10))
+ case v.IsFloat64():
+ w.Write(strconv.AppendFloat(buf[:0], v.Float64(), 'g', -1, 64))
+ case v.IsBool():
+ if v.Bool() {
+ w.WriteString("true")
+ } else {
+ w.WriteString("false")
+ }
+ default:
+ fmt.Fprint(w, v.Interface())
}
}
-// UnpackString assumes the label was built using LabelOfString and returns the
-// value that was passed to that constructor.
-// This method is for implementing new key types, for type safety normal
-// access should be done with the From method of the key.
-func (l Label) UnpackString() string {
- var v string
- hdr := (*reflect.StringHeader)(unsafe.Pointer(&v))
- hdr.Data = uintptr(l.untyped.(stringptr))
- hdr.Len = int(l.packed)
- return v
+// HasValue returns true if the value is set to any type.
+func (v *Value) HasValue() bool { return v.untyped != nil }
+
+// ValueOf returns a Value for the supplied value.
+func ValueOf(value interface{}) Value {
+ return Value{untyped: value}
}
-// Valid returns true if the Label is a valid one (it has a key).
-func (l Label) Valid() bool { return l.key != "" }
-
-// Key returns the key of this Label.
-func (l Label) Key() string { return l.key }
-
-// Apply calls the appropriate method of h on the label's value.
-func (l Label) Apply(h ValueHandler) {
- if l.dispatch != nil {
- l.dispatch(h, l)
+// Interface returns the value.
+// This will never panic, things that were not set using SetInterface will be
+// unpacked and returned anyway.
+func (v Value) Interface() interface{} {
+ switch {
+ case v.IsString():
+ return v.String()
+ case v.IsInt64():
+ return v.Int64()
+ case v.IsUint64():
+ return v.Uint64()
+ case v.IsFloat64():
+ return v.Float64()
+ case v.IsBool():
+ return v.Bool()
+ default:
+ return v.untyped
}
}
-//////////////////////////////////////////////////////////////////////
-
-// These are more demos of what Apply can do, rather than things we'd
-// necessarily want here.
-
-// Value is an expensive but general way to get a label's value.
-func (l Label) Value() interface{} {
- var v interface{}
- l.Apply(vhandler{&v})
- return v
+// StringOf returns a new Value for a string.
+func StringOf(s string) Value {
+ hdr := (*reflect.StringHeader)(unsafe.Pointer(&s))
+ return Value{packed: uint64(hdr.Len), untyped: stringptr(hdr.Data)}
}
-type vhandler struct {
- pv *interface{}
+// String returns the value as a string.
+// This does not panic if v's Kind is not String, instead, it returns a string
+// representation of the value in all cases.
+func (v Value) String() string {
+ if sp, ok := v.untyped.(stringptr); ok {
+ var s string
+ hdr := (*reflect.StringHeader)(unsafe.Pointer(&s))
+ hdr.Data = uintptr(sp)
+ hdr.Len = int(v.packed)
+ return s
+ }
+ // not a string, so invoke the formatter to build one
+ w := &strings.Builder{}
+ buf := bufPool.Get().(*buffer)
+ v.format(w, buf.data[:0])
+ bufPool.Put(buf)
+ return w.String()
}
-func (h vhandler) String(v string) { *h.pv = v }
-func (h vhandler) Quote(v string) { *h.pv = strconv.Quote(v) }
-func (h vhandler) Int(v int64) { *h.pv = v }
-func (h vhandler) Uint(v uint64) { *h.pv = v }
-func (h vhandler) Float(v float64) { *h.pv = v }
-func (h vhandler) Value(v interface{}) { *h.pv = v }
-
-// AppendValue appends the value of l to *dst as text.
-func (l Label) AppendValue(dst *[]byte) {
- l.Apply(ahandler{dst})
+// IsString returns true if the value was built with SetString.
+func (v Value) IsString() bool {
+ _, ok := v.untyped.(stringptr)
+ return ok
}
-type ahandler struct {
- b *[]byte
+// Int64Of returns a new Value for a signed integer.
+func Int64Of(u int64) Value {
+ return Value{packed: uint64(u), untyped: int64Kind{}}
}
-func (h ahandler) String(v string) { *h.b = append(*h.b, v...) }
-func (h ahandler) Quote(v string) { *h.b = strconv.AppendQuote(*h.b, v) }
-func (h ahandler) Int(v int64) { *h.b = strconv.AppendInt(*h.b, v, 10) }
-func (h ahandler) Uint(v uint64) { *h.b = strconv.AppendUint(*h.b, v, 10) }
-func (h ahandler) Float(v float64) { *h.b = strconv.AppendFloat(*h.b, v, 'E', -1, 32) }
-func (h ahandler) Value(v interface{}) { *h.b = append(*h.b, fmt.Sprint(v)...) }
+// Int64 returns the int64 from a value that was set with SetInt64.
+// It will panic for any value for which IsInt64 is not true.
+func (v Value) Int64() int64 {
+ if !v.IsInt64() {
+ panic("Int64 called on non int64 value")
+ }
+ return int64(v.packed)
+}
+
+// IsInt64 returns true if the value was built with SetInt64.
+func (v Value) IsInt64() bool {
+ _, ok := v.untyped.(int64Kind)
+ return ok
+}
+
+// Uint64Of returns a new Value for an unsigned integer.
+func Uint64Of(u uint64) Value {
+ return Value{packed: u, untyped: uint64Kind{}}
+}
+
+// Uint64 returns the uint64 from a value that was set with SetUint64.
+// It will panic for any value for which IsUint64 is not true.
+func (v Value) Uint64() uint64 {
+ if !v.IsUint64() {
+ panic("Uint64 called on non uint64 value")
+ }
+ return v.packed
+}
+
+// IsUint64 returns true if the value was built with SetUint64.
+func (v Value) IsUint64() bool {
+ _, ok := v.untyped.(uint64Kind)
+ return ok
+}
+
+// Float64Of returns a new Value for a floating point number.
+func Float64Of(f float64) Value {
+ return Value{packed: math.Float64bits(f), untyped: float64Kind{}}
+}
+
+// Float64 returns the float64 from a value that was set with SetFloat64.
+// It will panic for any value for which IsFloat64 is not true.
+func (v Value) Float64() float64 {
+ if !v.IsFloat64() {
+ panic("Float64 called on non float64 value")
+ }
+ return math.Float64frombits(v.packed)
+}
+
+// IsFloat64 returns true if the value was built with SetFloat64.
+func (v Value) IsFloat64() bool {
+ _, ok := v.untyped.(float64Kind)
+ return ok
+}
+
+// BoolOf returns a new Value for a bool.
+func BoolOf(b bool) Value {
+ if b {
+ return Value{packed: 1, untyped: boolKind{}}
+ }
+ return Value{packed: 0, untyped: boolKind{}}
+}
+
+// Bool returns the bool from a value that was set with SetBool.
+// It will panic for any value for which IsBool is not true.
+func (v Value) Bool() bool {
+ if !v.IsBool() {
+ panic("Bool called on non bool value")
+ }
+ if v.packed != 0 {
+ return true
+ }
+ return false
+}
+
+// IsBool returns true if the value was built with SetBool.
+func (v Value) IsBool() bool {
+ _, ok := v.untyped.(boolKind)
+ return ok
+}
diff --git a/event/label_test.go b/event/label_test.go
new file mode 100644
index 0000000..3a5c549
--- /dev/null
+++ b/event/label_test.go
@@ -0,0 +1,120 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package event_test
+
+import (
+ "testing"
+ "time"
+
+ "golang.org/x/exp/event"
+)
+
+func TestOfAs(t *testing.T) {
+ const i = 3
+ var v event.Value
+ v = event.Int64Of(i)
+ if got := v.Int64(); got != i {
+ t.Errorf("got %v, want %v", got, i)
+ }
+ v = event.Uint64Of(i)
+ if got := v.Uint64(); got != i {
+ t.Errorf("got %v, want %v", got, i)
+ }
+ v = event.Float64Of(i)
+ if got := v.Float64(); got != i {
+ t.Errorf("got %v, want %v", got, i)
+ }
+ v = event.BoolOf(true)
+ if got := v.Bool(); got != true {
+ t.Errorf("got %v, want %v", got, true)
+ }
+ const s = "foo"
+ v = event.StringOf(s)
+ if got := v.String(); got != s {
+ t.Errorf("got %v, want %v", got, s)
+ }
+ tm := time.Now()
+ v = event.ValueOf(tm)
+ if got := v.Interface(); got != tm {
+ t.Errorf("got %v, want %v", got, tm)
+ }
+ var vnil event.Value
+ if got := vnil.Interface(); got != nil {
+ t.Errorf("got %v, want nil", got)
+ }
+}
+
+func panics(f func()) (b bool) {
+ defer func() {
+ if x := recover(); x != nil {
+ b = true
+ }
+ }()
+ f()
+ return false
+}
+
+func TestPanics(t *testing.T) {
+ for _, test := range []struct {
+ name string
+ f func()
+ }{
+ {"int64", func() { event.Float64Of(3).Int64() }},
+ {"uint64", func() { event.Int64Of(3).Uint64() }},
+ {"float64", func() { event.Uint64Of(3).Float64() }},
+ {"bool", func() { event.Int64Of(3).Bool() }},
+ } {
+ if !panics(test.f) {
+ t.Errorf("%s: got no panic, want panic", test.name)
+ }
+ }
+}
+
+func TestString(t *testing.T) {
+ for _, test := range []struct {
+ v event.Value
+ want string
+ }{
+ {event.Int64Of(-3), "-3"},
+ {event.Uint64Of(3), "3"},
+ {event.Float64Of(.15), "0.15"},
+ {event.BoolOf(true), "true"},
+ {event.StringOf("foo"), "foo"},
+ {event.ValueOf(time.Duration(3 * time.Second)), "3s"},
+ } {
+ if got := test.v.String(); got != test.want {
+ t.Errorf("%#v: got %q, want %q", test.v, got, test.want)
+ }
+ }
+}
+
+func TestNoAlloc(t *testing.T) {
+ // Assign values just to make sure the compiler doesn't optimize away the statements.
+ var (
+ i int64
+ u uint64
+ f float64
+ b bool
+ s string
+ x interface{}
+ p = &i
+ )
+ a := int(testing.AllocsPerRun(5, func() {
+ i = event.Int64Of(1).Int64()
+ u = event.Uint64Of(1).Uint64()
+ f = event.Float64Of(1).Float64()
+ b = event.BoolOf(true).Bool()
+ s = event.StringOf("foo").String()
+ x = event.ValueOf(p).Interface()
+ }))
+ if a != 0 {
+ t.Errorf("got %d allocs, want zero", a)
+ }
+ _ = u
+ _ = f
+ _ = b
+ _ = s
+ _ = x
+}
diff --git a/event/printer.go b/event/printer.go
deleted file mode 100644
index f3e5388..0000000
--- a/event/printer.go
+++ /dev/null
@@ -1,96 +0,0 @@
-// Copyright 2019 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package event
-
-import (
- "fmt"
- "io"
- "strconv"
-)
-
-// Printer is the interface to something capable of printing standard types
-// used in events.
-// It is used to enable zero allocation printing support.
-// Implementations can be directly used as an Exporter.
-type Printer interface {
- Handler
- Label(l Label)
- String(v string)
- Quote(v string)
- Int(v int64)
- Uint(v uint64)
- Float(v float64)
- Value(v interface{})
-}
-
-// NewPrinter returns a Printer that prints to the supplied writer.
-func NewPrinter(w io.Writer) Printer {
- return &printer{writer: w}
-}
-
-type printer struct {
- buf [128]byte
- writer io.Writer
-}
-
-func (p *printer) Handle(ev *Event) {
- const timeFormat = "2006/01/02 15:04:05"
- if !ev.At.IsZero() {
- p.writer.Write(ev.At.AppendFormat(p.buf[:0], timeFormat))
- }
- io.WriteString(p.writer, " [")
- io.WriteString(p.writer, ev.Kind.String())
- io.WriteString(p.writer, ":")
- p.writer.Write(strconv.AppendUint(p.buf[:0], ev.ID, 10))
- if ev.Parent != 0 {
- io.WriteString(p.writer, ":")
- p.writer.Write(strconv.AppendUint(p.buf[:0], ev.Parent, 10))
- }
- io.WriteString(p.writer, "]")
- if ev.Message != "" {
- io.WriteString(p.writer, " ")
- io.WriteString(p.writer, ev.Message)
- }
- for _, l := range ev.Labels {
- if !l.Valid() {
- continue
- }
- io.WriteString(p.writer, "\n\t")
- p.Label(l)
- }
- io.WriteString(p.writer, "\n")
-}
-
-func (p *printer) Label(l Label) {
- io.WriteString(p.writer, l.key)
- if l.dispatch != nil {
- io.WriteString(p.writer, "=")
- l.dispatch(p, l)
- }
-}
-
-func (p *printer) String(v string) {
- io.WriteString(p.writer, v)
-}
-
-func (p *printer) Quote(v string) {
- p.writer.Write(strconv.AppendQuote(p.buf[:0], v))
-}
-
-func (p *printer) Int(v int64) {
- p.writer.Write(strconv.AppendInt(p.buf[:0], v, 10))
-}
-
-func (p *printer) Uint(v uint64) {
- p.writer.Write(strconv.AppendUint(p.buf[:0], v, 10))
-}
-
-func (p *printer) Float(v float64) {
- p.writer.Write(strconv.AppendFloat(p.buf[:0], v, 'E', -1, 32))
-}
-
-func (p *printer) Value(v interface{}) {
- fmt.Fprint(p.writer, v)
-}
diff --git a/event/severity/severity.go b/event/severity/severity.go
index 06dfc96..66892b5 100644
--- a/event/severity/severity.go
+++ b/event/severity/severity.go
@@ -50,18 +50,14 @@
Fatal = Of(FatalLevel)
)
-func dispatchSeverity(h event.ValueHandler, l event.Label) {
- h.String(Level(l.Unpack64()).String())
-}
-
// Of creates a new Label with this key and the supplied value.
-func Of(l Level) event.Label {
- return event.Of64(Key, dispatchSeverity, uint64(l))
+func Of(v Level) event.Label {
+ return event.Label{Name: Key, Value: event.ValueOf(v)}
}
// From can be used to get a value from a Label.
func From(t event.Label) Level {
- return Level(t.Unpack64())
+ return t.Value.Interface().(Level)
}
func (l Level) Class() Level {
diff --git a/event/severity/severity_test.go b/event/severity/severity_test.go
index e90cac2..01c1b05 100644
--- a/event/severity/severity_test.go
+++ b/event/severity/severity_test.go
@@ -15,6 +15,7 @@
)
func TestPrint(t *testing.T) {
+ //TODO: print the textual form of severity
ctx := context.Background()
for _, test := range []struct {
name string
@@ -23,35 +24,22 @@
}{{
name: "debug",
events: func(ctx context.Context) { event.To(ctx).With(severity.Debug).Log("a message") },
- expect: `
-2020/03/05 14:27:48 [log:1] a message
- level=debug
-`}, {
+ expect: `2020/03/05 14:27:48 [1] log a message {"level":debug}`,
+ }, {
name: "info",
events: func(ctx context.Context) { event.To(ctx).With(severity.Info).Log("a message") },
- expect: `
-2020/03/05 14:27:48 [log:1] a message
- level=info
-`}} {
- h := &captureHandler{}
- h.printer = event.NewPrinter(&h.buf)
+ expect: `2020/03/05 14:27:48 [1] log a message {"level":info}`},
+ } {
+ buf := &strings.Builder{}
+ h := event.Printer(buf)
e := event.NewExporter(h)
e.Now = eventtest.TestNow()
ctx := event.WithExporter(ctx, e)
test.events(ctx)
- got := strings.TrimSpace(h.buf.String())
+ got := strings.TrimSpace(buf.String())
expect := strings.TrimSpace(test.expect)
if got != expect {
t.Errorf("%s failed\ngot : %q\nexpect: %q", test.name, got, expect)
}
}
}
-
-type captureHandler struct {
- printer event.Printer
- buf strings.Builder
-}
-
-func (e *captureHandler) Handle(ev *event.Event) {
- e.printer.Handle(ev)
-}