event: Remove the builder concept
We no longer need it, the optimizer now manages to a avoid allocations on ... functions.
Change-Id: If6ac86cba8cabfc855b347fd597192d093c69663
Reviewed-on: https://go-review.googlesource.com/c/exp/+/329629
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/adapter/gokit/gokit.go b/event/adapter/gokit/gokit.go
index b51fab4..c442ed6 100644
--- a/event/adapter/gokit/gokit.go
+++ b/event/adapter/gokit/gokit.go
@@ -34,7 +34,10 @@
keyvals = keyvals[1:]
}
}
- b := event.To(ctx)
+ ev := event.New(ctx, event.LogKind)
+ if ev == nil {
+ return nil
+ }
var msg string
for i := 0; i < len(keyvals); i += 2 {
key := keyvals[i].(string)
@@ -42,9 +45,10 @@
if key == "msg" || key == "message" {
msg = fmt.Sprint(value)
} else {
- b.With(keys.Value(key).Of(value))
+ ev.Labels = append(ev.Labels, keys.Value(key).Of(value))
}
}
- b.Log(msg)
+ ev.Message = msg
+ ev.Deliver()
return nil
}
diff --git a/event/adapter/gokit/gokit_test.go b/event/adapter/gokit/gokit_test.go
index f8d6cad..9c84590 100644
--- a/event/adapter/gokit/gokit_test.go
+++ b/event/adapter/gokit/gokit_test.go
@@ -31,7 +31,7 @@
keys.Value("resource").Of("R"),
},
}}
- if diff := cmp.Diff(want, h.Got); diff != "" {
+ if diff := cmp.Diff(want, h.Got, eventtest.CmpOption()); diff != "" {
t.Errorf("mismatch (-want, +got):\n%s", diff)
}
}
diff --git a/event/adapter/logfmt/logfmt.go b/event/adapter/logfmt/logfmt.go
index 9735b38..cc8b297 100644
--- a/event/adapter/logfmt/logfmt.go
+++ b/event/adapter/logfmt/logfmt.go
@@ -40,27 +40,11 @@
return &Handler{to: to}
}
-func (h *Handler) Log(ctx context.Context, ev *event.Event) {
- h.Printer.Event(h.to, ev)
-}
-
-func (h *Handler) Metric(ctx context.Context, ev *event.Event) {
- h.Printer.Event(h.to, ev)
-}
-
-func (h *Handler) Annotate(ctx context.Context, ev *event.Event) {
- h.Printer.Event(h.to, ev)
-}
-
-func (h *Handler) Start(ctx context.Context, ev *event.Event) context.Context {
+func (h *Handler) Event(ctx context.Context, ev *event.Event) context.Context {
h.Printer.Event(h.to, ev)
return ctx
}
-func (h *Handler) End(ctx context.Context, ev *event.Event) {
- h.Printer.Event(h.to, ev)
-}
-
func (p *Printer) Event(w io.Writer, ev *event.Event) {
p.needSep = false
if !ev.At.IsZero() {
@@ -94,7 +78,7 @@
p.label(w, "name", event.StringOf(ev.Name))
}
- if ev.Kind == event.TraceKind && ev.TraceID == 0 {
+ if ev.Kind == event.EndKind {
p.label(w, "end", event.Value{})
}
diff --git a/event/adapter/logfmt/logfmt_test.go b/event/adapter/logfmt/logfmt_test.go
index ca764e3..be80ade 100644
--- a/event/adapter/logfmt/logfmt_test.go
+++ b/event/adapter/logfmt/logfmt_test.go
@@ -57,7 +57,7 @@
expect: `err="an error"`,
}, {
name: "end",
- event: event.Event{Kind: event.TraceKind},
+ event: event.Event{Kind: event.EndKind},
expect: `end`,
}, {
name: "string",
diff --git a/event/adapter/logr/logr.go b/event/adapter/logr/logr.go
index 2dfb62a..23bf172 100644
--- a/event/adapter/logr/logr.go
+++ b/event/adapter/logr/logr.go
@@ -15,8 +15,8 @@
)
type logger struct {
- exporter *event.Exporter
- builder event.Builder // never delivered, only cloned
+ ctx context.Context
+ labels []event.Label
nameSep string
name string
verbosity int
@@ -26,7 +26,7 @@
func NewLogger(ctx context.Context, nameSep string) logr.Logger {
return &logger{
- builder: event.To(ctx),
+ ctx: ctx,
nameSep: nameSep,
}
}
@@ -63,13 +63,6 @@
return true
}
-// addLabels adds labels for keysAndValues to b.
-func addLabels(b event.Builder, keysAndValues []interface{}) {
- for i := 0; i < len(keysAndValues); i += 2 {
- b.With(newLabel(keysAndValues[i], keysAndValues[i+1]))
- }
-}
-
// Info logs a non-error message with the given key/value pairs as context.
//
// The msg argument should be used to add some constant description to
@@ -77,7 +70,10 @@
// variable information. The key/value pairs should alternate string
// keys and arbitrary values.
func (l *logger) Info(msg string, keysAndValues ...interface{}) {
- l.log(msg, l.builder.Clone(), keysAndValues)
+ ev := event.New(l.ctx, event.LogKind)
+ if ev != nil {
+ l.log(ev, msg, keysAndValues)
+ }
}
// Error logs an error, with the given message and key/value pairs as context.
@@ -89,22 +85,35 @@
// while the err field should be used to attach the actual error that
// triggered this log line, if present.
func (l *logger) Error(err error, msg string, keysAndValues ...interface{}) {
- l.log(msg, l.builder.Clone().Error(err), keysAndValues)
+ ev := event.New(l.ctx, event.LogKind)
+ if ev != nil {
+ ev.Labels = append(ev.Labels, keys.Value("error").Of(err))
+ l.log(ev, msg, keysAndValues)
+ }
}
-func (l *logger) log(msg string, b event.Builder, keysAndValues []interface{}) {
- b.With(convertVerbosity(l.verbosity))
- b.Name(l.name)
- addLabels(b, keysAndValues)
- b.Log(msg)
+func (l *logger) log(ev *event.Event, msg string, keysAndValues []interface{}) {
+ ev.Labels = append(ev.Labels, convertVerbosity(l.verbosity).Label())
+ ev.Labels = append(ev.Labels, l.labels...)
+ for i := 0; i < len(keysAndValues); i += 2 {
+ ev.Labels = append(ev.Labels, newLabel(keysAndValues[i], keysAndValues[i+1]))
+ }
+ ev.Name = l.name
+ ev.Message = msg
+ ev.Deliver()
}
// WithValues adds some key-value pairs of context to a logger.
// See Info for documentation on how key/value pairs work.
func (l *logger) WithValues(keysAndValues ...interface{}) logr.Logger {
l2 := *l
- l2.builder = l2.builder.Clone()
- addLabels(l2.builder, keysAndValues)
+ if len(keysAndValues) > 0 {
+ l2.labels = make([]event.Label, len(l.labels), len(l.labels)+(len(keysAndValues)/2))
+ copy(l2.labels, l.labels)
+ for i := 0; i < len(keysAndValues); i += 2 {
+ l2.labels = append(l2.labels, newLabel(keysAndValues[i], keysAndValues[i+1]))
+ }
+ }
return &l2
}
@@ -112,7 +121,7 @@
return keys.Value(key.(string)).Of(value)
}
-func convertVerbosity(v int) event.Label {
+func convertVerbosity(v int) severity.Level {
//TODO: this needs to be more complicated, v decreases with increasing severity
- return severity.Of(severity.Level(v))
+ return severity.Level(v)
}
diff --git a/event/adapter/logr/logr_test.go b/event/adapter/logr/logr_test.go
index 8b897e4..6417755 100644
--- a/event/adapter/logr/logr_test.go
+++ b/event/adapter/logr/logr_test.go
@@ -19,7 +19,7 @@
func TestInfo(t *testing.T) {
ctx, th := eventtest.NewCapture()
- log := elogr.NewLogger(ctx, "/").WithName("n").V(int(severity.DebugLevel))
+ log := elogr.NewLogger(ctx, "/").WithName("n").V(int(severity.Debug))
log = log.WithName("m")
log.Info("mess", "traceID", 17, "resource", "R")
want := []event.Event{{
@@ -28,12 +28,12 @@
Message: "mess",
Name: "n/m",
Labels: []event.Label{
- severity.Debug,
+ severity.Debug.Label(),
keys.Value("traceID").Of(17),
keys.Value("resource").Of("R"),
},
}}
- if diff := cmp.Diff(want, th.Got); diff != "" {
+ if diff := cmp.Diff(want, th.Got, eventtest.CmpOption()); diff != "" {
t.Errorf("mismatch (-want, +got):\n%s", diff)
}
}
diff --git a/event/adapter/logrus/logrus.go b/event/adapter/logrus/logrus.go
index fe21fee..af41ce4 100644
--- a/event/adapter/logrus/logrus.go
+++ b/event/adapter/logrus/logrus.go
@@ -46,19 +46,24 @@
if ctx == nil {
ctx = context.Background()
}
- b := event.To(ctx).At(e.Time)
- b.With(convertLevel(e.Level))
- for k, v := range e.Data {
- b.With(keys.Value(k).Of(v))
+ ev := event.New(ctx, event.LogKind)
+ if ev == nil {
+ return nil, nil
}
- b.Log(e.Message)
+ ev.At = e.Time
+ ev.Labels = append(ev.Labels, convertLevel(e.Level).Label())
+ for k, v := range e.Data {
+ ev.Labels = append(ev.Labels, keys.Value(k).Of(v))
+ }
+ ev.Message = e.Message
+ ev.Deliver()
return nil, nil
}
-func convertLevel(level logrus.Level) event.Label {
+func convertLevel(level logrus.Level) severity.Level {
switch level {
case logrus.PanicLevel:
- return severity.Of(severity.FatalLevel + 1)
+ return severity.Fatal + 1
case logrus.FatalLevel:
return severity.Fatal
case logrus.ErrorLevel:
diff --git a/event/adapter/logrus/logrus_test.go b/event/adapter/logrus/logrus_test.go
index babc686..8b1f98a 100644
--- a/event/adapter/logrus/logrus_test.go
+++ b/event/adapter/logrus/logrus_test.go
@@ -32,7 +32,7 @@
Kind: event.LogKind,
Message: "mess",
Labels: []event.Label{
- severity.Info,
+ severity.Info.Label(),
keys.Value("traceID").Of(17),
keys.Value("resource").Of("R"),
},
@@ -40,7 +40,7 @@
// logrus fields are stored in a map, so we have to sort to overcome map
// iteration indeterminacy.
less := func(a, b event.Label) bool { return a.Name < b.Name }
- if diff := cmp.Diff(want, th.Got, cmpopts.SortSlices(less), cmpopts.IgnoreFields(event.Event{}, "At")); diff != "" {
+ if diff := cmp.Diff(want, th.Got, cmpopts.SortSlices(less), eventtest.CmpOption()); diff != "" {
t.Errorf("mismatch (-want, +got):\n%s", diff)
}
}
diff --git a/event/adapter/zap/zap.go b/event/adapter/zap/zap.go
index 22304dd..e73e45d 100644
--- a/event/adapter/zap/zap.go
+++ b/event/adapter/zap/zap.go
@@ -27,15 +27,14 @@
)
type core struct {
- builder event.Builder // never delivered, only cloned
+ ctx context.Context
+ labels []event.Label
}
var _ zapcore.Core = (*core)(nil)
func NewCore(ctx context.Context) zapcore.Core {
- return &core{
- builder: event.To(ctx),
- }
+ return &core{ctx: ctx}
}
func (c *core) Enabled(level zapcore.Level) bool {
@@ -44,25 +43,37 @@
func (c *core) With(fields []zapcore.Field) zapcore.Core {
c2 := *c
- c2.builder = c2.builder.Clone()
- addLabels(c2.builder, fields)
+ if len(fields) > 0 {
+ c2.labels = make([]event.Label, len(c.labels), len(c.labels)+len(fields))
+ copy(c2.labels, c.labels)
+ for _, f := range fields {
+ c2.labels = append(c2.labels, newLabel(f))
+ }
+ }
return &c2
}
func (c *core) Write(e zapcore.Entry, fs []zapcore.Field) error {
- b := c.builder.Clone().
- At(e.Time).
- With(convertLevel(e.Level)).
- Name(e.LoggerName)
+ ev := event.New(c.ctx, event.LogKind)
+ if ev == nil {
+ return nil
+ }
+ ev.At = e.Time
+ ev.Labels = append(ev.Labels, c.labels...)
+ ev.Labels = append(ev.Labels, convertLevel(e.Level).Label())
+ ev.Name = e.LoggerName
// TODO: add these additional labels more efficiently.
if e.Stack != "" {
- b.With(keys.String("stack").Of(e.Stack))
+ ev.Labels = append(ev.Labels, keys.String("stack").Of(e.Stack))
}
if e.Caller.Defined {
- b.With(keys.String("caller").Of(e.Caller.String()))
+ ev.Labels = append(ev.Labels, keys.String("caller").Of(e.Caller.String()))
}
- addLabels(b, fs)
- b.Log(e.Message)
+ for _, f := range fs {
+ ev.Labels = append(ev.Labels, newLabel(f))
+ }
+ ev.Message = e.Message
+ ev.Deliver()
return nil
}
@@ -72,14 +83,6 @@
func (c *core) Sync() error { return nil }
-// addLabels creates a new []event.Label with the given labels followed by the
-// labels constructed from fields.
-func addLabels(b event.Builder, fields []zap.Field) {
- for i := 0; i < len(fields); i++ {
- b.With(newLabel(fields[i]))
- }
-}
-
func newLabel(f zap.Field) event.Label {
switch f.Type {
case zapcore.ArrayMarshalerType, zapcore.ObjectMarshalerType, zapcore.BinaryType, zapcore.ByteStringType,
@@ -149,7 +152,7 @@
return stringer.(fmt.Stringer).String()
}
-func convertLevel(level zapcore.Level) event.Label {
+func convertLevel(level zapcore.Level) severity.Level {
switch level {
case zapcore.DebugLevel:
return severity.Debug
@@ -160,9 +163,9 @@
case zapcore.ErrorLevel:
return severity.Error
case zapcore.DPanicLevel:
- return severity.Of(severity.FatalLevel - 1)
+ return severity.Fatal - 1
case zapcore.PanicLevel:
- return severity.Of(severity.FatalLevel + 1)
+ return severity.Fatal + 1
case zapcore.FatalLevel:
return severity.Fatal
default:
diff --git a/event/adapter/zap/zap_test.go b/event/adapter/zap/zap_test.go
index 4ae028a..870fc56 100644
--- a/event/adapter/zap/zap_test.go
+++ b/event/adapter/zap/zap_test.go
@@ -10,7 +10,6 @@
"testing"
"github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
"go.uber.org/zap"
"golang.org/x/exp/event"
ezap "golang.org/x/exp/event/adapter/zap"
@@ -31,11 +30,11 @@
Labels: []event.Label{
keys.Int64("traceID").Of(17),
keys.String("resource").Of("R"),
- severity.Info,
+ severity.Info.Label(),
keys.Float64("pi").Of(3.14),
},
}}
- if diff := cmp.Diff(want, h.Got, cmpopts.IgnoreFields(event.Event{}, "At")); diff != "" {
+ if diff := cmp.Diff(want, h.Got, eventtest.CmpOption()); diff != "" {
t.Errorf("mismatch (-want, +got):\n%s", diff)
}
}
diff --git a/event/alloc_test.go b/event/alloc_test.go
index 070dfdf..80ce051 100644
--- a/event/alloc_test.go
+++ b/event/alloc_test.go
@@ -23,8 +23,7 @@
e := event.NewExporter(logfmt.NewHandler(ioutil.Discard), &event.ExporterOptions{EnableNamespaces: true})
ctx := event.WithExporter(context.Background(), e)
allocs := int(testing.AllocsPerRun(5, func() {
- event.To(ctx).With(aString).With(anInt).Log("message1")
- event.To(ctx).With(aString, anInt).Log("message2")
+ event.Log(ctx, "message", aString, anInt)
}))
if allocs != 0 {
t.Errorf("Got %d allocs, expect 0", allocs)
diff --git a/event/bench_test.go b/event/bench_test.go
index 0a07dea..8cc1206 100644
--- a/event/bench_test.go
+++ b/event/bench_test.go
@@ -26,12 +26,12 @@
eventLog = eventtest.Hooks{
AStart: func(ctx context.Context, a int) context.Context {
- event.To(ctx).With(severity.Info).With(aValue.Of(a)).Log(eventtest.A.Msg)
+ severity.Info.Log(ctx, eventtest.A.Msg, aValue.Of(a))
return ctx
},
AEnd: func(ctx context.Context) {},
BStart: func(ctx context.Context, b string) context.Context {
- event.To(ctx).With(severity.Info).With(bValue.Of(b)).Log(eventtest.B.Msg)
+ severity.Info.Log(ctx, eventtest.B.Msg, bValue.Of(b))
return ctx
},
BEnd: func(ctx context.Context) {},
@@ -39,12 +39,12 @@
eventLogf = eventtest.Hooks{
AStart: func(ctx context.Context, a int) context.Context {
- event.To(ctx).With(severity.Info).Logf(eventtest.A.Msgf, a)
+ severity.Info.Logf(ctx, eventtest.A.Msgf, a)
return ctx
},
AEnd: func(ctx context.Context) {},
BStart: func(ctx context.Context, b string) context.Context {
- event.To(ctx).With(severity.Info).Logf(eventtest.B.Msgf, b)
+ severity.Info.Logf(ctx, eventtest.B.Msgf, b)
return ctx
},
BEnd: func(ctx context.Context) {},
@@ -52,33 +52,31 @@
eventTrace = eventtest.Hooks{
AStart: func(ctx context.Context, a int) context.Context {
- ctx, _ = event.To(ctx).Start(eventtest.A.Msg)
- event.To(ctx).With(aValue.Of(a)).Annotate()
+ ctx = event.Start(ctx, eventtest.A.Msg, aValue.Of(a))
return ctx
},
AEnd: func(ctx context.Context) {
- event.To(ctx).End()
+ event.End(ctx)
},
BStart: func(ctx context.Context, b string) context.Context {
- ctx, _ = event.To(ctx).Start(eventtest.B.Msg)
- event.To(ctx).With(bValue.Of(b)).Annotate()
+ ctx = event.Start(ctx, eventtest.B.Msg, bValue.Of(b))
return ctx
},
BEnd: func(ctx context.Context) {
- event.To(ctx).End()
+ event.End(ctx)
},
}
eventMetric = eventtest.Hooks{
AStart: func(ctx context.Context, a int) context.Context {
- event.To(ctx).With(aStat.Of(a)).Metric(gauge.Record(1))
- event.To(ctx).With(aCount.Of(1)).Metric(gauge.Record(1))
+ gauge.Record(ctx, 1, aStat.Of(a))
+ gauge.Record(ctx, 1, aCount.Of(1))
return ctx
},
AEnd: func(ctx context.Context) {},
BStart: func(ctx context.Context, b string) context.Context {
- event.To(ctx).With(bLength.Of(len(b))).Metric(gauge.Record(1))
- event.To(ctx).With(bCount.Of(1)).Metric(gauge.Record(1))
+ gauge.Record(ctx, 1, bLength.Of(len(b)))
+ gauge.Record(ctx, 1, bCount.Of(1))
return ctx
},
BEnd: func(ctx context.Context) {},
@@ -90,7 +88,7 @@
}
func eventNoop() context.Context {
- return event.WithExporter(context.Background(), event.NewExporter(event.NopHandler{}, eventtest.ExporterOptions()))
+ return event.WithExporter(context.Background(), event.NewExporter(nopHandler{}, eventtest.ExporterOptions()))
}
func eventPrint(w io.Writer) context.Context {
@@ -103,6 +101,10 @@
return event.WithExporter(context.Background(), event.NewExporter(logfmt.NewHandler(w), opts))
}
+type nopHandler struct{}
+
+func (nopHandler) Event(ctx context.Context, _ *event.Event) context.Context { return ctx }
+
func BenchmarkEventLogNoExporter(b *testing.B) {
eventtest.RunBenchmark(b, eventNoExporter(), eventLog)
}
diff --git a/event/builder.go b/event/builder.go
deleted file mode 100644
index 1452dcf..0000000
--- a/event/builder.go
+++ /dev/null
@@ -1,303 +0,0 @@
-// 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 !disable_events
-
-package event
-
-import (
- "context"
- "fmt"
- "sync"
- "sync/atomic"
- "time"
-)
-
-// Builder is a fluent builder for construction of new events.
-type Builder struct {
- builderCommon
-}
-
-type builderCommon struct {
- ctx context.Context
- data *builder
- builderID uint64 // equals data.id if all is well
-}
-
-// preallocateLabels controls the space reserved for labels in a builder.
-// Storing the first few labels directly in builders can avoid an allocation at
-// all for the very common cases of simple events. The length needs to be large
-// enough to cope with the majority of events but no so large as to cause undue
-// stack pressure.
-const preallocateLabels = 6
-
-type builder struct {
- exporter *Exporter
- Event Event
- labels [preallocateLabels]Label
- id uint64
- parentStart time.Time
-}
-
-var builderPool = sync.Pool{New: func() interface{} { return &builder{} }}
-
-// To initializes a builder from the values stored in a context.
-func To(ctx context.Context) Builder {
- b := Builder{builderCommon{ctx: ctx}}
- b.data = newBuilder(ctx)
- if b.data != nil {
- b.builderID = b.data.id
- }
- return b
-}
-
-var builderID uint64 // atomic
-
-func newBuilder(ctx context.Context) *builder {
- exporter, parent, parentStart := FromContext(ctx)
- if exporter == nil {
- return nil
- }
- b := allocBuilder()
- b.exporter = exporter
- b.parentStart = parentStart
- b.Event.Labels = b.labels[:0]
- b.Event.Parent = parent
- return b
-}
-
-func allocBuilder() *builder {
- b := builderPool.Get().(*builder)
- b.id = atomic.AddUint64(&builderID, 1)
- return b
-}
-
-// Clone returns a copy of this builder.
-// The two copies can be independently delivered.
-func (b Builder) Clone() Builder {
- return Builder{b.clone()}
-}
-
-func (b builderCommon) clone() builderCommon {
- if b.data == nil {
- return b
- }
- bb := allocBuilder()
- bbid := bb.id
- clone := builderCommon{ctx: b.ctx, data: bb, builderID: bb.id}
- *clone.data = *b.data
- clone.data.id = bbid
- if len(b.data.Event.Labels) == 0 || &b.data.labels[0] == &b.data.Event.Labels[0] {
- clone.data.Event.Labels = clone.data.labels[:len(b.data.Event.Labels)]
- } else {
- clone.data.Event.Labels = make([]Label, len(b.data.Event.Labels))
- copy(clone.data.Event.Labels, b.data.Event.Labels)
- }
- return clone
-}
-
-func (b Builder) Name(name string) Builder {
- if b.data != nil {
- b.data.Event.Name = name
- }
- return b
-}
-
-func (b Builder) Error(err error) Builder {
- if b.data != nil {
- b.data.Event.Error = err
- }
- return b
-}
-
-func (b builderCommon) addLabel(label Label) {
- if b.data != nil {
- checkValid(b.data, b.builderID)
- }
-}
-
-// With adds all the supplied labels to the event being constructed.
-func (b Builder) With(labels ...Label) Builder {
- if b.data == nil || len(labels) == 0 {
- return b
- }
- checkValid(b.data, b.builderID)
- b.data.Event.Labels = append(b.data.Event.Labels, labels...)
- return b
-}
-
-func (b Builder) At(t time.Time) Builder {
- b.setAt(t)
- return b
-}
-
-func (b builderCommon) setAt(t time.Time) {
- if b.data != nil {
- checkValid(b.data, b.builderID)
- b.data.Event.At = t
- }
-}
-
-func (b Builder) Namespace(ns string) Builder {
- b.setNamespace(ns)
- return b
-}
-
-func (b builderCommon) setNamespace(ns string) {
- if b.data != nil {
- checkValid(b.data, b.builderID)
- b.data.Event.Namespace = ns
- }
-}
-
-// Log is a helper that calls Deliver with LogKind.
-func (b Builder) Log(message string) {
- if b.data == nil {
- return
- }
- checkValid(b.data, b.builderID)
- if b.data.exporter.loggingEnabled() {
- b.data.exporter.mu.Lock()
- defer b.data.exporter.mu.Unlock()
- b.data.Event.Kind = LogKind
- b.data.Event.Message = message
- b.data.exporter.prepare(&b.data.Event)
- b.data.exporter.handler.Log(b.ctx, &b.data.Event)
- }
- b.done()
-}
-
-// Logf is a helper that uses fmt.Sprint to build the message and then
-// calls Deliver with LogKind.
-func (b Builder) Logf(template string, args ...interface{}) {
- if b.data == nil {
- return
- }
- checkValid(b.data, b.builderID)
- if b.data.exporter.loggingEnabled() {
- message := fmt.Sprintf(template, args...)
- // Duplicate code from Log so Exporter.deliver's invocation of runtime.Callers is correct.
- b.data.exporter.mu.Lock()
- defer b.data.exporter.mu.Unlock()
- b.data.Event.Kind = LogKind
- b.data.Event.Message = message
- b.data.exporter.prepare(&b.data.Event)
- b.data.exporter.handler.Log(b.ctx, &b.data.Event)
- }
- b.done()
-}
-
-func (b Builder) Metric(mv MetricValue) {
- if b.data == nil {
- return
- }
- checkValid(b.data, b.builderID)
- if b.data.exporter.metricsEnabled() {
- b.data.exporter.mu.Lock()
- defer b.data.exporter.mu.Unlock()
- if b.data.Event.Namespace == "" {
- b.data.Event.Namespace = mv.m.Descriptor().Namespace()
- }
- b.data.Event.Labels = append(b.data.Event.Labels, MetricVal.Of(mv.v), MetricKey.Of(mv.m))
- b.data.Event.Kind = MetricKind
- b.data.exporter.prepare(&b.data.Event)
- b.data.exporter.handler.Metric(b.ctx, &b.data.Event)
- }
- b.done()
-}
-
-// Annotate is a helper that calls Deliver with AnnotateKind.
-func (b Builder) Annotate() {
- if b.data == nil {
- return
- }
- checkValid(b.data, b.builderID)
- if b.data.exporter.annotationsEnabled() {
- b.data.exporter.mu.Lock()
- defer b.data.exporter.mu.Unlock()
- b.data.exporter.prepare(&b.data.Event)
- b.data.exporter.handler.Annotate(b.ctx, &b.data.Event)
- }
- b.done()
-}
-
-// End is a helper that calls Deliver with EndKind.
-func (b Builder) End() {
- if b.data == nil {
- return
- }
- checkValid(b.data, b.builderID)
- if b.data.exporter.tracingEnabled() {
- // If there is a DurationMetric label, emit a Metric event
- // with the time since Start was called.
- if v, ok := DurationMetric.Find(&b.data.Event); ok {
- m := v.(*Duration)
- b.Clone().Metric(m.Record(time.Since(b.data.parentStart)))
- }
- b.data.exporter.mu.Lock()
- defer b.data.exporter.mu.Unlock()
- b.data.Event.Kind = TraceKind
- b.data.exporter.prepare(&b.data.Event)
- b.data.exporter.handler.End(b.ctx, &b.data.Event)
- }
- b.done()
-}
-
-// Event returns a copy of the event currently being built.
-func (b Builder) Event() *Event {
- checkValid(b.data, b.builderID)
- clone := b.data.Event
- if len(b.data.Event.Labels) > 0 {
- clone.Labels = make([]Label, len(b.data.Event.Labels))
- copy(clone.Labels, b.data.Event.Labels)
- }
- return &clone
-}
-
-func (b builderCommon) done() {
- *b.data = builder{}
- builderPool.Put(b.data)
-}
-
-// Start delivers a start event with the given name and labels.
-// Its second return value is a Builder whose End method can be
-// called to deliver the matching End event.
-// (It is also fine to ignore the Builder.)
-// All events created from the returned context will have this start event
-// as their parent.
-func (b Builder) Start(name string) (context.Context, Builder) {
- if b.data == nil {
- return b.ctx, Builder{}
- }
- checkValid(b.data, b.builderID)
- ctx := b.ctx
- var endBuilder Builder
- if b.data.exporter.tracingEnabled() {
- b.data.exporter.mu.Lock()
- defer b.data.exporter.mu.Unlock()
- b.data.exporter.lastEvent++
- traceID := b.data.exporter.lastEvent
- // create the end builder
- endBuilder = b.Clone()
- endBuilder.data.Event.Parent = traceID
- b.data.Event.Kind = TraceKind
- b.data.Event.TraceID = traceID
- b.data.exporter.prepare(&b.data.Event)
- // and now deliver the start event
- b.data.Event.Name = name
- now := time.Now()
- ctx = newContext(ctx, b.data.exporter, traceID, now)
- ctx = b.data.exporter.handler.Start(ctx, &b.data.Event)
- endBuilder.data.parentStart = now
- endBuilder.ctx = ctx
- }
- b.done()
- return ctx, endBuilder
-}
-
-func checkValid(b *builder, wantID uint64) {
- if b.exporter == nil || b.id != wantID {
- panic("Builder already delivered an event; missing call to Clone")
- }
-}
diff --git a/event/builder_test.go b/event/builder_test.go
deleted file mode 100644
index c467adb..0000000
--- a/event/builder_test.go
+++ /dev/null
@@ -1,194 +0,0 @@
-// 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 !disable_events
-
-package event_test
-
-import (
- "context"
- "fmt"
- "strings"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "golang.org/x/exp/event"
- "golang.org/x/exp/event/keys"
-)
-
-func TestClone(t *testing.T) {
- var labels []event.Label
- for i := 0; i < 5; i++ { // one greater than len(Builder.labels)
- labels = append(labels, keys.Int(fmt.Sprintf("l%d", i)).Of(i))
- }
-
- ctx := event.WithExporter(context.Background(), event.NewExporter(event.NopHandler{}, nil))
- b1 := event.To(ctx)
- b1.With(labels[0]).With(labels[1])
- check(t, b1, labels[:2])
- b2 := b1.Clone()
- check(t, b1, labels[:2])
- check(t, b2, labels[:2])
-
- b2.With(labels[2])
- check(t, b1, labels[:2])
- check(t, b2, labels[:3])
-
- // Force a new backing array for b.Event.Labels.
- for i := 3; i < len(labels); i++ {
- b2.With(labels[i])
- }
- check(t, b1, labels[:2])
- check(t, b2, labels)
-
- b2.Log("") // put b2 back in the pool.
- b2 = event.To(ctx)
- check(t, b1, labels[:2])
- check(t, b2, []event.Label{})
-
- b2.With(labels[3]).With(labels[4])
- check(t, b1, labels[:2])
- check(t, b2, labels[3:5])
-}
-
-func check(t *testing.T, b event.Builder, want []event.Label) {
- t.Helper()
- if got := b.Event().Labels; !cmp.Equal(got, want, cmp.Comparer(valueEqual)) {
- t.Fatalf("got %v, want %v", got, want)
- }
-}
-
-func valueEqual(l1, l2 event.Value) bool {
- return fmt.Sprint(l1) == fmt.Sprint(l2)
-}
-
-func TestTraceBuilder(t *testing.T) {
- // Verify that the context returned from the handler is also returned from Start,
- // and is the context passed to End.
- ctx := event.WithExporter(context.Background(), event.NewExporter(&testTraceHandler{t: t}, nil))
- ctx, eb := event.To(ctx).Start("s")
- val := ctx.Value("x")
- if val != 1 {
- t.Fatal("context not returned from Start")
- }
- eb.End()
-}
-
-type testTraceHandler struct {
- event.NopHandler
- t *testing.T
-}
-
-func (*testTraceHandler) Start(ctx context.Context, _ *event.Event) context.Context {
- return context.WithValue(ctx, "x", 1)
-}
-
-func (t *testTraceHandler) End(ctx context.Context, _ *event.Event) {
- val := ctx.Value("x")
- if val != 1 {
- t.t.Fatal("Start context not passed to End")
- }
-}
-
-func TestFailToClone(t *testing.T) {
- ctx := event.WithExporter(context.Background(), event.NewExporter(event.NopHandler{}, nil))
-
- catch := func(f func()) {
- defer func() {
- r := recover()
- if r == nil {
- t.Error("expected panic, did not get one")
- return
- }
- got, ok := r.(string)
- if !ok || !strings.Contains(got, "Clone") {
- t.Errorf("got panic(%v), want string with 'Clone'", r)
- }
- }()
-
- f()
- }
-
- catch(func() {
- b1 := event.To(ctx)
- b1.Log("msg1")
- // Reuse of Builder without Clone; b1.data has been cleared.
- b1.Log("msg2")
- })
-
- catch(func() {
- b1 := event.To(ctx)
- b1.Log("msg1")
- _ = event.To(ctx) // re-allocate the builder
- // b1.data is populated, but with the wrong information.
- b1.Log("msg2")
- })
-}
-
-func TestTraceDuration(t *testing.T) {
- // Verify that a trace can can emit a latency metric.
- dur := event.NewDuration("test", "")
- want := 200 * time.Millisecond
-
- check := func(t *testing.T, h *testTraceDurationHandler) {
- if !h.got.HasValue() {
- t.Fatal("no metric value")
- }
- got := h.got.Duration().Round(50 * time.Millisecond)
- if got != want {
- t.Fatalf("got %s, want %s", got, want)
- }
- }
-
- t.Run("returned builder", func(t *testing.T) {
- h := &testTraceDurationHandler{}
- ctx := event.WithExporter(context.Background(), event.NewExporter(h, nil))
- ctx, eb := event.To(ctx).With(event.DurationMetric.Of(dur)).Start("s")
- time.Sleep(want)
- eb.End()
- check(t, h)
- })
- t.Run("separate builder", func(t *testing.T) {
- h := &testTraceDurationHandler{}
- ctx := event.WithExporter(context.Background(), event.NewExporter(h, nil))
- ctx, _ = event.To(ctx).Start("s")
- time.Sleep(want)
- event.To(ctx).With(event.DurationMetric.Of(dur)).End()
- check(t, h)
- })
-}
-
-type testTraceDurationHandler struct {
- event.NopHandler
- got event.Value
-}
-
-func (t *testTraceDurationHandler) Metric(ctx context.Context, e *event.Event) {
- t.got, _ = event.MetricVal.Find(e)
-}
-
-func BenchmarkBuildContext(b *testing.B) {
- // How long does it take to deliver an event from a nested context?
- c := event.NewCounter("c", "")
- for _, depth := range []int{1, 5, 7, 10} {
- b.Run(fmt.Sprintf("depth %d", depth), func(b *testing.B) {
- ctx := event.WithExporter(context.Background(), event.NewExporter(event.NopHandler{}, nil))
- for i := 0; i < depth; i++ {
- ctx = context.WithValue(ctx, i, i)
- }
- b.Run("direct", func(b *testing.B) {
- for i := 0; i < b.N; i++ {
- event.To(ctx).Name("foo").Metric(c.Record(1))
- }
- })
- b.Run("cloned", func(b *testing.B) {
- bu := event.To(ctx)
- for i := 0; i < b.N; i++ {
- bu.Clone().Name("foo").Metric(c.Record(1))
- }
- })
- })
- }
-}
diff --git a/event/common.go b/event/common.go
index 4eb9ea0..b6876e3 100644
--- a/event/common.go
+++ b/event/common.go
@@ -4,6 +4,12 @@
package event
+import (
+ "context"
+ "fmt"
+ "sync/atomic"
+)
+
const (
MetricKey = interfaceKey("metric")
MetricVal = valueKey("metricValue")
@@ -17,7 +23,8 @@
LogKind
MetricKind
- TraceKind
+ StartKind
+ EndKind
)
type (
@@ -25,13 +32,60 @@
interfaceKey string
)
-func (k valueKey) Of(v Value) Label {
- return Label{Name: string(k), Value: v}
+func Log(ctx context.Context, msg string, labels ...Label) {
+ ev := New(ctx, LogKind)
+ if ev != nil {
+ ev.Labels = append(ev.Labels, labels...)
+ ev.Message = msg
+ ev.Deliver()
+ }
}
-func (k valueKey) Matches(ev *Event) bool {
- _, found := k.Find(ev)
- return found
+func Logf(ctx context.Context, msg string, args ...interface{}) {
+ ev := New(ctx, LogKind)
+ if ev != nil {
+ ev.Message = fmt.Sprintf(msg, args...)
+ ev.Deliver()
+ }
+}
+
+func Annotate(ctx context.Context, labels ...Label) {
+ ev := New(ctx, 0)
+ if ev != nil {
+ ev.Labels = append(ev.Labels, labels...)
+ ev.Deliver()
+ }
+}
+
+func Start(ctx context.Context, name string, labels ...Label) context.Context {
+ ev := New(ctx, StartKind)
+ if ev != nil {
+ ev.Labels = append(ev.Labels, labels...)
+ ev.Name = name
+ ev.TraceID = atomic.AddUint64(&ev.target.exporter.lastEvent, 1)
+ ev.target.exporter.prepare(ev)
+ ev.ctx = newContext(ev.ctx, ev.target.exporter, ev.TraceID, ev.At)
+ ctx = ev.Deliver()
+ }
+ return ctx
+}
+
+func End(ctx context.Context, labels ...Label) {
+ ev := New(ctx, EndKind)
+ if ev != nil {
+ ev.Labels = append(ev.Labels, labels...)
+ ev.target.exporter.prepare(ev)
+ // this was an end event, do we need to send a duration?
+ if v, ok := DurationMetric.Find(ev); ok {
+ //TODO: do we want the rest of the values from the end event?
+ v.(*Duration).Record(ctx, ev.At.Sub(ev.target.startTime))
+ }
+ ev.Deliver()
+ }
+}
+
+func (k valueKey) Of(v Value) Label {
+ return Label{Name: string(k), Value: v}
}
func (k valueKey) Find(ev *Event) (Value, bool) {
@@ -42,11 +96,6 @@
return Label{Name: string(k), Value: ValueOf(v)}
}
-func (k interfaceKey) Matches(ev *Event) bool {
- _, found := k.Find(ev)
- return found
-}
-
func (k interfaceKey) Find(ev *Event) (interface{}, bool) {
v, ok := lookupValue(string(k), ev.Labels)
if !ok {
diff --git a/event/common_test.go b/event/common_test.go
index d104024..852da43 100644
--- a/event/common_test.go
+++ b/event/common_test.go
@@ -11,6 +11,7 @@
"golang.org/x/exp/event"
"golang.org/x/exp/event/eventtest"
+ "golang.org/x/exp/event/keys"
)
func TestCommon(t *testing.T) {
@@ -20,27 +21,32 @@
const simple = "simple message"
const trace = "a trace"
- event.To(ctx).Log(simple)
+ event.Log(ctx, simple)
checkMessage(t, h, "Log", simple)
checkName(t, h, "Log", "")
h.Reset()
- event.To(ctx).Metric(m.Record(3))
+ event.Logf(ctx, "logf %s message", "to")
+ checkMessage(t, h, "Logf", "logf to message")
+ checkName(t, h, "Logf", "")
+ h.Reset()
+
+ m.Record(ctx, 3)
checkMessage(t, h, "Metric", "")
checkName(t, h, "Metric", "")
h.Reset()
- event.To(ctx).Annotate()
+ event.Annotate(ctx, keys.String("").Of(""))
checkMessage(t, h, "Annotate", "")
checkName(t, h, "Annotate", "")
h.Reset()
- _, eb := event.To(ctx).Start(trace)
+ ctx = event.Start(ctx, trace)
checkMessage(t, h, "Start", "")
checkName(t, h, "Start", trace)
h.Reset()
- eb.End()
+ event.End(ctx)
checkMessage(t, h, "End", "")
checkName(t, h, "End", "")
}
diff --git a/event/disabled.go b/event/disabled.go
index 77cbe75..20019ca 100644
--- a/event/disabled.go
+++ b/event/disabled.go
@@ -20,8 +20,8 @@
func To(ctx context.Context) Builder { return Builder{} }
func (b Builder) Clone() Builder { return b }
-func (b Builder) With(label Label) Builder { return b }
-func (b Builder) WithAll(labels ...Label) Builder { return b }
+func (b Builder) Label(label Label) Builder { return b }
+func (b Builder) Labels(labels ...Label) Builder { return b }
func (b Builder) Log(message string) {}
func (b Builder) Logf(template string, args ...interface{}) {}
func (b Builder) Metric() {}
@@ -35,6 +35,6 @@
func newContext(ctx context.Context, exporter *Exporter, parent uint64) context.Context {
return ctx
}
-func FromContext(ctx context.Context) (*Exporter, uint64) { return nil, 0 }
+func FromContext(ctx context.Context) *Target { return nil }
func setDefaultExporter(e *Exporter) {}
diff --git a/event/event.go b/event/event.go
index 369bcc5..b0718ee 100644
--- a/event/event.go
+++ b/event/event.go
@@ -6,6 +6,7 @@
import (
"context"
+ "sync"
"time"
)
@@ -21,27 +22,26 @@
Name string
Error error
Labels []Label
+
+ ctx context.Context
+ target *target
+ labels [preallocateLabels]Label
}
// Handler is a the type for something that handles events as they occur.
type Handler interface {
- // Log indicates a logging event.
- Log(context.Context, *Event)
- // Metric indicates a metric record event.
- Metric(context.Context, *Event)
- // Annotate reports label values at a point in time.
- Annotate(context.Context, *Event)
- // Start indicates a trace start event.
- Start(context.Context, *Event) context.Context
- // End indicates a trace end event.
- End(context.Context, *Event)
+ // Event is called with each event.
+ Event(context.Context, *Event) context.Context
}
-// Matcher is the interface to something that can check if an event matches
-// a condition.
-type Matcher interface {
- Matches(ev *Event) bool
-}
+// preallocateLabels controls the space reserved for labels in a builder.
+// Storing the first few labels directly in builders can avoid an allocation at
+// all for the very common cases of simple events. The length needs to be large
+// enough to cope with the majority of events but no so large as to cause undue
+// stack pressure.
+const preallocateLabels = 6
+
+var eventPool = sync.Pool{New: func() interface{} { return &Event{} }}
// WithExporter returns a context with the exporter attached.
// The exporter is called synchronously from the event call site, so it should
@@ -56,23 +56,58 @@
setDefaultExporter(e)
}
-// Is uses the matcher to check if the event is a match.
-// This is a simple helper to convert code like
-// event.End.Matches(ev)
-// to the more readable
-// ev.Is(event.End)
-func (ev *Event) Is(m Matcher) bool {
- return m.Matches(ev)
+// New prepares a new event.
+// This is intended to avoid allocations in the steady state case, to do this
+// it uses a pool of events.
+// Events are returned to the pool when Deliver is called. Failure to call
+// Deliver will exhaust the pool and cause allocations.
+// It returns nil if there is no active exporter for this kind of event.
+func New(ctx context.Context, kind Kind) *Event {
+ var t *target
+ if v, ok := ctx.Value(contextKey).(*target); ok {
+ t = v
+ } else {
+ t = getDefaultTarget()
+ }
+ if t == nil {
+ return nil
+ }
+ //TODO: we can change this to a much faster test
+ switch kind {
+ case LogKind:
+ if !t.exporter.loggingEnabled() {
+ return nil
+ }
+ case MetricKind:
+ if !t.exporter.metricsEnabled() {
+ return nil
+ }
+ case StartKind, EndKind:
+ if !t.exporter.tracingEnabled() {
+ return nil
+ }
+ }
+ ev := eventPool.Get().(*Event)
+ *ev = Event{
+ ctx: ctx,
+ target: t,
+ Kind: kind,
+ Parent: t.parent,
+ }
+ ev.Labels = ev.labels[:0]
+ return ev
}
-// NopHandler is a handler that does nothing. It can be used for tests, or
-// embedded in a struct to avoid having to implement all the Handler methods.
-type NopHandler struct{}
-
-func (NopHandler) Log(context.Context, *Event) {}
-func (NopHandler) Metric(context.Context, *Event) {}
-func (NopHandler) Annotate(context.Context, *Event) {}
-func (NopHandler) End(context.Context, *Event) {}
-func (NopHandler) Start(ctx context.Context, _ *Event) context.Context {
+// Deliver the event to the exporter that was found in New.
+// This also returns the event to the pool, it is an error to do anything
+// with the event after it is delivered.
+func (ev *Event) Deliver() context.Context {
+ // get the event ready to send
+ ev.target.exporter.prepare(ev)
+ // now hold the lock while we deliver the event
+ ev.target.exporter.mu.Lock()
+ defer ev.target.exporter.mu.Unlock()
+ ctx := ev.target.exporter.handler.Event(ev.ctx, ev)
+ eventPool.Put(ev)
return ctx
}
diff --git a/event/event_test.go b/event/event_test.go
index fe4501a..857c6c3 100644
--- a/event/event_test.go
+++ b/event/event_test.go
@@ -8,6 +8,7 @@
import (
"context"
+ "fmt"
"os"
"strings"
"testing"
@@ -36,25 +37,25 @@
expect string
}{{
name: "simple",
- events: func(ctx context.Context) { event.To(ctx).Log("a message") },
+ events: func(ctx context.Context) { event.Log(ctx, "a message") },
expect: `time="2020/03/05 14:27:48" msg="a message"
`}, {
name: "log 1",
- events: func(ctx context.Context) { event.To(ctx).With(l1).Log("a message") },
+ events: func(ctx context.Context) { event.Log(ctx, "a message", l1) },
expect: `time="2020/03/05 14:27:48" l1=1 msg="a message"`,
}, {
name: "log 2",
- events: func(ctx context.Context) { event.To(ctx).With(l1).With(l2).Log("a message") },
+ events: func(ctx context.Context) { event.Log(ctx, "a message", l1, l2) },
expect: `time="2020/03/05 14:27:48" l1=1 l2=2 msg="a message"`,
}, {
name: "log 3",
- events: func(ctx context.Context) { event.To(ctx).With(l1).With(l2).With(l3).Log("a message") },
+ events: func(ctx context.Context) { event.Log(ctx, "a message", l1, l2, l3) },
expect: `time="2020/03/05 14:27:48" l1=1 l2=2 l3=3 msg="a message"`,
}, {
name: "span",
events: func(ctx context.Context) {
- ctx, eb := event.To(ctx).Start("span")
- eb.End()
+ ctx = event.Start(ctx, "span")
+ event.End(ctx)
},
expect: `
time="2020/03/05 14:27:48" trace=1 name=span
@@ -62,11 +63,11 @@
`}, {
name: "span nested",
events: func(ctx context.Context) {
- ctx, eb := event.To(ctx).Start("parent")
- defer eb.End()
- child, eb2 := event.To(ctx).Start("child")
- defer eb2.End()
- event.To(child).Log("message")
+ ctx = event.Start(ctx, "parent")
+ defer event.End(ctx)
+ child := event.Start(ctx, "child")
+ defer event.End(child)
+ event.Log(child, "message")
},
expect: `
time="2020/03/05 14:27:48" trace=1 name=parent
@@ -76,32 +77,37 @@
time="2020/03/05 14:27:52" parent=1 end
`}, {
name: "counter",
- events: func(ctx context.Context) { event.To(ctx).With(l1).Metric(counter.Record(2)) },
- expect: `time="2020/03/05 14:27:48" in="golang.org/x/exp/event_test" l1=1 metricValue=2 metric="Metric(\"golang.org/x/exp/event_test/hits\")"`,
+ events: func(ctx context.Context) { counter.Record(ctx, 2, l1) },
+ expect: `time="2020/03/05 14:27:48" metricValue=2 metric="Metric(\"golang.org/x/exp/event_test/hits\")" l1=1`,
}, {
name: "gauge",
- events: func(ctx context.Context) { event.To(ctx).With(l1).Metric(gauge.Record(98.6)) },
- expect: `time="2020/03/05 14:27:48" in="golang.org/x/exp/event_test" l1=1 metricValue=98.6 metric="Metric(\"golang.org/x/exp/event_test/temperature\")"`,
+ events: func(ctx context.Context) { gauge.Record(ctx, 98.6, l1) },
+ expect: `time="2020/03/05 14:27:48" metricValue=98.6 metric="Metric(\"golang.org/x/exp/event_test/temperature\")" l1=1`,
}, {
name: "duration",
events: func(ctx context.Context) {
- event.To(ctx).With(l1).With(l2).Metric(latency.Record(3 * time.Second))
+ latency.Record(ctx, 3*time.Second, l1, l2)
},
- expect: `time="2020/03/05 14:27:48" in="golang.org/x/exp/event_test" l1=1 l2=2 metricValue=3s metric="Metric(\"golang.org/x/exp/event_test/latency\")"`,
+ expect: `time="2020/03/05 14:27:48" metricValue=3s metric="Metric(\"golang.org/x/exp/event_test/latency\")" l1=1 l2=2`,
}, {
name: "annotate",
- events: func(ctx context.Context) { event.To(ctx).With(l1).Annotate() },
+ events: func(ctx context.Context) { event.Annotate(ctx, l1) },
expect: `time="2020/03/05 14:27:48" l1=1`,
}, {
name: "annotate 2",
- events: func(ctx context.Context) { event.To(ctx).With(l1).With(l2).Annotate() },
+ events: func(ctx context.Context) { event.Annotate(ctx, l1, l2) },
expect: `time="2020/03/05 14:27:48" l1=1 l2=2`,
}, {
name: "multiple events",
events: func(ctx context.Context) {
- b := event.To(ctx)
- b.Clone().With(keys.Int("myInt").Of(6)).Log("my event")
- b.With(keys.String("myString").Of("some string value")).Log("string event")
+ /*TODO: this is supposed to be using a cached target
+ t := event.To(ctx)
+ p := event.Prototype{}.As(event.LogKind)
+ t.With(p).Int("myInt", 6).Message("my event").Send()
+ t.With(p).String("myString", "some string value").Message("string event").Send()
+ */
+ event.Log(ctx, "my event", keys.Int("myInt").Of(6))
+ event.Log(ctx, "string event", keys.String("myString").Of("some string value"))
},
expect: `
time="2020/03/05 14:27:48" myInt=6 msg="my event"
@@ -120,8 +126,8 @@
func ExampleLog() {
ctx := event.WithExporter(context.Background(), event.NewExporter(logfmt.NewHandler(os.Stdout), eventtest.ExporterOptions()))
- 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")
+ event.Log(ctx, "my event", keys.Int("myInt").Of(6))
+ event.Log(ctx, "error event", keys.String("myString").Of("some string value"))
// Output:
// time="2020/03/05 14:27:48" myInt=6 msg="my event"
// time="2020/03/05 14:27:49" myString="some string value" msg="error event"
@@ -134,3 +140,105 @@
func TestLogEvent(t *testing.T) {
eventtest.TestBenchmark(t, eventPrint, eventLog, eventtest.LogfmtOutput)
}
+
+func TestTraceBuilder(t *testing.T) {
+ // Verify that the context returned from the handler is also returned from Start,
+ // and is the context passed to End.
+ ctx := event.WithExporter(context.Background(), event.NewExporter(&testTraceHandler{t: t}, eventtest.ExporterOptions()))
+ ctx = event.Start(ctx, "s")
+ val := ctx.Value("x")
+ if val != 1 {
+ t.Fatal("context not returned from Start")
+ }
+ event.End(ctx)
+}
+
+type testTraceHandler struct {
+ t *testing.T
+}
+
+func (t *testTraceHandler) Event(ctx context.Context, ev *event.Event) context.Context {
+ switch ev.Kind {
+ case event.StartKind:
+ return context.WithValue(ctx, "x", 1)
+ case event.EndKind:
+ val := ctx.Value("x")
+ if val != 1 {
+ t.t.Fatal("Start context not passed to End")
+ }
+ return ctx
+ default:
+ return ctx
+ }
+}
+
+func TestTraceDuration(t *testing.T) {
+ // Verify that a trace can can emit a latency metric.
+ dur := event.NewDuration("test", "")
+ want := time.Second
+
+ check := func(t *testing.T, h *testTraceDurationHandler) {
+ if !h.got.HasValue() {
+ t.Fatal("no metric value")
+ }
+ got := h.got.Duration()
+ if got != want {
+ t.Fatalf("got %s, want %s", got, want)
+ }
+ }
+
+ t.Run("returned builder", func(t *testing.T) {
+ h := &testTraceDurationHandler{}
+ ctx := event.WithExporter(context.Background(), event.NewExporter(h, eventtest.ExporterOptions()))
+ ctx = event.Start(ctx, "s")
+ time.Sleep(want)
+ event.End(ctx, event.DurationMetric.Of(dur))
+ check(t, h)
+ })
+ //TODO: come back and fix this
+ t.Run("separate builder", func(t *testing.T) {
+ h := &testTraceDurationHandler{}
+ ctx := event.WithExporter(context.Background(), event.NewExporter(h, eventtest.ExporterOptions()))
+ ctx = event.Start(ctx, "s")
+ time.Sleep(want)
+ event.End(ctx, event.DurationMetric.Of(dur))
+ check(t, h)
+ })
+}
+
+type testTraceDurationHandler struct {
+ got event.Value
+}
+
+func (t *testTraceDurationHandler) Event(ctx context.Context, ev *event.Event) context.Context {
+ if ev.Kind == event.MetricKind {
+ t.got, _ = event.MetricVal.Find(ev)
+ }
+ return ctx
+}
+
+func BenchmarkBuildContext(b *testing.B) {
+ // How long does it take to deliver an event from a nested context?
+ c := event.NewCounter("c", "")
+ for _, depth := range []int{1, 5, 7, 10} {
+ b.Run(fmt.Sprintf("depth %d", depth), func(b *testing.B) {
+ ctx := event.WithExporter(context.Background(), event.NewExporter(nopHandler{}, eventtest.ExporterOptions()))
+ for i := 0; i < depth; i++ {
+ ctx = context.WithValue(ctx, i, i)
+ }
+ b.Run("direct", func(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ c.Record(ctx, 1)
+ }
+ })
+ /*TODO: work out how we do cached labels
+ b.Run("cloned", func(b *testing.B) {
+ bu := event.To(ctx)
+ for i := 0; i < b.N; i++ {
+ c.RecordTB(bu, 1).Name("foo").Send()
+ }
+ })
+ */
+ })
+ }
+}
diff --git a/event/eventtest/capture.go b/event/eventtest/capture.go
index 12a4615..9510b3e 100644
--- a/event/eventtest/capture.go
+++ b/event/eventtest/capture.go
@@ -10,40 +10,20 @@
Got []event.Event
}
-func (h *CaptureHandler) Log(ctx context.Context, ev *event.Event) {
- h.event(ctx, ev)
-}
-
-func (h *CaptureHandler) Metric(ctx context.Context, ev *event.Event) {
- h.event(ctx, ev)
-}
-
-func (h *CaptureHandler) Annotate(ctx context.Context, ev *event.Event) {
- h.event(ctx, ev)
-}
-
-func (h *CaptureHandler) Start(ctx context.Context, ev *event.Event) context.Context {
- h.event(ctx, ev)
+func (h *CaptureHandler) Event(ctx context.Context, ev *event.Event) context.Context {
+ h.Got = append(h.Got, *ev)
+ got := &h.Got[len(h.Got)-1]
+ got.Labels = make([]event.Label, len(ev.Labels))
+ copy(got.Labels, ev.Labels)
return ctx
}
-func (h *CaptureHandler) End(ctx context.Context, ev *event.Event) {
- h.event(ctx, ev)
-}
-
func (h *CaptureHandler) Reset() {
if len(h.Got) > 0 {
h.Got = h.Got[:0]
}
}
-func (h *CaptureHandler) event(ctx context.Context, ev *event.Event) {
- h.Got = append(h.Got, *ev)
- got := &h.Got[len(h.Got)-1]
- got.Labels = make([]event.Label, len(ev.Labels))
- copy(got.Labels, ev.Labels)
-}
-
func NewCapture() (context.Context, *CaptureHandler) {
h := &CaptureHandler{}
ctx := event.WithExporter(context.Background(), event.NewExporter(h, ExporterOptions()))
diff --git a/event/eventtest/eventtest.go b/event/eventtest/eventtest.go
index 8c1cdce..dd55a43 100644
--- a/event/eventtest/eventtest.go
+++ b/event/eventtest/eventtest.go
@@ -15,6 +15,8 @@
"testing"
"time"
+ "github.com/google/go-cmp/cmp"
+ "github.com/google/go-cmp/cmp/cmpopts"
"golang.org/x/exp/event"
"golang.org/x/exp/event/adapter/logfmt"
)
@@ -30,31 +32,11 @@
printer logfmt.Printer
}
-func (h *testHandler) Log(ctx context.Context, ev *event.Event) {
- h.event(ctx, ev)
-}
-
-func (h *testHandler) Metric(ctx context.Context, ev *event.Event) {
- h.event(ctx, ev)
-}
-
-func (h *testHandler) Annotate(ctx context.Context, ev *event.Event) {
- h.event(ctx, ev)
-}
-
-func (h *testHandler) Start(ctx context.Context, ev *event.Event) context.Context {
- h.event(ctx, ev)
- return ctx
-}
-
-func (h *testHandler) End(ctx context.Context, ev *event.Event) {
- h.event(ctx, ev)
-}
-
-func (h *testHandler) event(ctx context.Context, ev *event.Event) {
+func (h *testHandler) Event(ctx context.Context, ev *event.Event) context.Context {
//TODO: choose between stdout and stderr based on the event
//TODO: decide if we should be calling h.tb.Fail()
h.printer.Event(os.Stdout, ev)
+ return ctx
}
var InitialTime = func() time.Time {
@@ -72,3 +54,7 @@
},
}
}
+
+func CmpOption() cmp.Option {
+ return cmpopts.IgnoreFields(event.Event{}, "At", "ctx", "target", "labels")
+}
diff --git a/event/export.go b/event/export.go
index eee6a92..abfe673 100644
--- a/event/export.go
+++ b/event/export.go
@@ -16,12 +16,20 @@
// Exporter synchronizes the delivery of events to handlers.
type Exporter struct {
- opts ExporterOptions
+ lastEvent uint64 // accessed using atomic, must be 64 bit aligned
+ opts ExporterOptions
- mu sync.Mutex
- handler Handler
- lastEvent uint64
- sources sources
+ mu sync.Mutex
+ handler Handler
+ sources sources
+}
+
+// target is a bound exporter.
+// Normally you get a target by looking in the context using To.
+type target struct {
+ exporter *Exporter
+ parent uint64
+ startTime time.Time // for trace latency
}
type ExporterOptions struct {
@@ -44,16 +52,8 @@
var contextKey interface{} = contextKeyType{}
-// contextValue is stored by value in the context to track the exporter and
-// current parent event.
-type contextValue struct {
- exporter *Exporter
- parent uint64
- startTime time.Time // for trace latency
-}
-
var (
- defaultExporter unsafe.Pointer
+ defaultTarget unsafe.Pointer
)
// NewExporter creates an Exporter using the supplied handler and options.
@@ -76,32 +76,27 @@
}
func setDefaultExporter(e *Exporter) {
- atomic.StorePointer(&defaultExporter, unsafe.Pointer(e))
+ atomic.StorePointer(&defaultTarget, unsafe.Pointer(&target{exporter: e}))
}
-func getDefaultExporter() *Exporter {
- return (*Exporter)(atomic.LoadPointer(&defaultExporter))
+func getDefaultTarget() *target {
+ return (*target)(atomic.LoadPointer(&defaultTarget))
}
-func newContext(ctx context.Context, exporter *Exporter, parent uint64, t time.Time) context.Context {
- return context.WithValue(ctx, contextKey, contextValue{exporter: exporter, parent: parent, startTime: t})
-}
-
-// FromContext returns the exporter, parentID and parent start time for the supplied context.
-func FromContext(ctx context.Context) (*Exporter, uint64, time.Time) {
- if v, ok := ctx.Value(contextKey).(contextValue); ok {
- return v.exporter, v.parent, v.startTime
+func newContext(ctx context.Context, exporter *Exporter, parent uint64, start time.Time) context.Context {
+ var t *target
+ if exporter != nil {
+ t = &target{exporter: exporter, parent: parent, startTime: start}
}
- return getDefaultExporter(), 0, time.Time{}
+ return context.WithValue(ctx, contextKey, t)
}
// prepare events before delivering to the underlying handler.
-// The event will be assigned a new ID.
+// it is safe to call this more than once (trace events have to call it early)
// If the event does not have a timestamp, and the exporter has a Now function
// then the timestamp will be updated.
// If automatic namespaces are enabled and the event doesn't have a namespace,
// one based on the caller's import path will be provided.
-// prepare must be called with the export mutex held.
func (e *Exporter) prepare(ev *Event) {
if e.opts.Now != nil && ev.At.IsZero() {
ev.At = e.opts.Now()
diff --git a/event/label.go b/event/label.go
index 71c65fa..1593cf5 100644
--- a/event/label.go
+++ b/event/label.go
@@ -166,7 +166,7 @@
hdr := (*reflect.SliceHeader)(unsafe.Pointer(&buf))
hdr.Data = uintptr(bp)
hdr.Len = int(v.packed)
- hdr.Cap = hdr.Len // TODO: is this safe?
+ hdr.Cap = hdr.Len
return buf
}
diff --git a/event/metric.go b/event/metric.go
index a2611ac..143a5a3 100644
--- a/event/metric.go
+++ b/event/metric.go
@@ -5,6 +5,7 @@
package event
import (
+ "context"
"fmt"
"time"
)
@@ -37,8 +38,7 @@
panic("name cannot be empty")
}
return &MetricDescriptor{
- name: name,
- // TODO: use the global callers, will also need a lock
+ name: name,
namespace: scanStack().Space,
description: description,
}
@@ -84,8 +84,13 @@
// Record converts its argument into a Value and returns a MetricValue with the
// receiver and the value. It is intended to be used as an argument to
// Builder.Metric.
-func (c *Counter) Record(v uint64) MetricValue {
- return MetricValue{c, Uint64Of(v)}
+func (c *Counter) Record(ctx context.Context, v uint64, labels ...Label) {
+ ev := New(ctx, MetricKind)
+ if ev != nil {
+ record(ev, c, Uint64Of(v))
+ ev.Labels = append(ev.Labels, labels...)
+ ev.Deliver()
+ }
}
// A FloatGauge records a single floating-point value that may go up or down.
@@ -107,8 +112,13 @@
// Record converts its argument into a Value and returns a MetricValue with the
// receiver and the value. It is intended to be used as an argument to
// Builder.Metric.
-func (g *FloatGauge) Record(v float64) MetricValue {
- return MetricValue{g, Float64Of(v)}
+func (g *FloatGauge) Record(ctx context.Context, v float64, labels ...Label) {
+ ev := New(ctx, MetricKind)
+ if ev != nil {
+ record(ev, g, Float64Of(v))
+ ev.Labels = append(ev.Labels, labels...)
+ ev.Deliver()
+ }
}
// A Duration records a distribution of durations.
@@ -130,8 +140,13 @@
// Record converts its argument into a Value and returns a MetricValue with the
// receiver and the value. It is intended to be used as an argument to
// Builder.Metric.
-func (d *Duration) Record(v time.Duration) MetricValue {
- return MetricValue{d, DurationOf(v)}
+func (d *Duration) Record(ctx context.Context, v time.Duration, labels ...Label) {
+ ev := New(ctx, MetricKind)
+ if ev != nil {
+ record(ev, d, DurationOf(v))
+ ev.Labels = append(ev.Labels, labels...)
+ ev.Deliver()
+ }
}
// An IntDistribution records a distribution of int64s.
@@ -152,6 +167,15 @@
// Record converts its argument into a Value and returns a MetricValue with the
// receiver and the value. It is intended to be used as an argument to
// Builder.Metric.
-func (d *IntDistribution) Record(v int64) MetricValue {
- return MetricValue{d, Int64Of(v)}
+func (d *IntDistribution) Record(ctx context.Context, v int64, labels ...Label) {
+ ev := New(ctx, MetricKind)
+ if ev != nil {
+ record(ev, d, Int64Of(v))
+ ev.Labels = append(ev.Labels, labels...)
+ ev.Deliver()
+ }
+}
+
+func record(ev *Event, m Metric, v Value) {
+ ev.Labels = append(ev.Labels, MetricVal.Of(v), MetricKey.Of(ValueOf(m)))
}
diff --git a/event/namespace_test.go b/event/namespace_test.go
index d3789c9..abab523 100644
--- a/event/namespace_test.go
+++ b/event/namespace_test.go
@@ -17,7 +17,7 @@
func TestNamespace(t *testing.T) {
var h nsHandler
ctx := event.WithExporter(context.Background(), event.NewExporter(&h, &event.ExporterOptions{EnableNamespaces: true}))
- event.To(ctx).Log("msg")
+ event.Log(ctx, "msg")
if got, want := h.ns, thisImportPath; got != want {
t.Errorf("got namespace %q, want, %q", got, want)
}
@@ -27,31 +27,11 @@
ns string
}
-func (h *nsHandler) Log(ctx context.Context, ev *event.Event) {
- h.event(ctx, ev)
-}
-
-func (h *nsHandler) Metric(ctx context.Context, ev *event.Event) {
- h.event(ctx, ev)
-}
-
-func (h *nsHandler) Annotate(ctx context.Context, ev *event.Event) {
- h.event(ctx, ev)
-}
-
-func (h *nsHandler) Start(ctx context.Context, ev *event.Event) context.Context {
- h.event(ctx, ev)
+func (h *nsHandler) Event(ctx context.Context, ev *event.Event) context.Context {
+ h.ns = ev.Namespace
return ctx
}
-func (h *nsHandler) End(ctx context.Context, ev *event.Event) {
- h.event(ctx, ev)
-}
-
-func (h *nsHandler) event(event context.Context, e *event.Event) {
- h.ns = e.Namespace
-}
-
func BenchmarkRuntimeCallers(b *testing.B) {
for i := 0; i < b.N; i++ {
var pcs [1]uintptr
diff --git a/event/otel/trace.go b/event/otel/trace.go
index 14f8533..2c43edf 100644
--- a/event/otel/trace.go
+++ b/event/otel/trace.go
@@ -21,21 +21,22 @@
type spanKey struct{}
-func (t *TraceHandler) Log(ctx context.Context, ev *event.Event) {}
-func (t *TraceHandler) Annotate(ctx context.Context, ev *event.Event) {}
-func (t *TraceHandler) Metric(ctx context.Context, ev *event.Event) {}
-func (t *TraceHandler) Start(ctx context.Context, ev *event.Event) context.Context {
- opts := labelsToSpanOptions(ev.Labels)
- octx, span := t.tracer.Start(ctx, ev.Name, opts...)
- return context.WithValue(octx, spanKey{}, span)
-}
-
-func (t *TraceHandler) End(ctx context.Context, e *event.Event) {
- span, ok := ctx.Value(spanKey{}).(trace.Span)
- if !ok {
- panic("End called on context with no span")
+func (t *TraceHandler) Event(ctx context.Context, ev *event.Event) context.Context {
+ switch ev.Kind {
+ case event.StartKind:
+ opts := labelsToSpanOptions(ev.Labels)
+ octx, span := t.tracer.Start(ctx, ev.Name, opts...)
+ return context.WithValue(octx, spanKey{}, span)
+ case event.EndKind:
+ span, ok := ctx.Value(spanKey{}).(trace.Span)
+ if !ok {
+ panic("End called on context with no span")
+ }
+ span.End()
+ return ctx
+ default:
+ return ctx
}
- span.End()
}
func labelsToSpanOptions(ls []event.Label) []trace.SpanOption {
diff --git a/event/otel/trace_test.go b/event/otel/trace_test.go
index c29f392..787b1ed 100644
--- a/event/otel/trace_test.go
+++ b/event/otel/trace_test.go
@@ -95,9 +95,8 @@
ctx, span = s.tracer.Start(ctx, s.name)
defer span.End()
} else {
- var eb event.Builder
- ctx, eb = event.To(ctx).Start(s.name)
- defer eb.End()
+ ctx = event.Start(ctx, s.name)
+ defer event.End(ctx)
}
for _, c := range s.children {
c.apply(ctx)
diff --git a/event/severity/severity.go b/event/severity/severity.go
index 66892b5..64e1441 100644
--- a/event/severity/severity.go
+++ b/event/severity/severity.go
@@ -4,7 +4,12 @@
package severity
-import "golang.org/x/exp/event"
+import (
+ "context"
+ "fmt"
+
+ "golang.org/x/exp/event"
+)
// Level represents a severity level of an event.
// The basic severity levels are designed to match the levels used in open telemetry.
@@ -24,35 +29,20 @@
type Level uint64
const (
- TraceLevel = Level(1)
- DebugLevel = Level(5)
- InfoLevel = Level(9)
- WarningLevel = Level(13)
- ErrorLevel = Level(17)
- FatalLevel = Level(21)
- MaxLevel = Level(24)
+ Trace = Level(1)
+ Debug = Level(5)
+ Info = Level(9)
+ Warning = Level(13)
+ Error = Level(17)
+ Fatal = Level(21)
+ Max = Level(24)
)
const Key = "level"
-var (
- // Trace is an event.Label for trace level events.
- Trace = Of(TraceLevel)
- // Debug is an event.Label for debug level events.
- Debug = Of(DebugLevel)
- // Info is an event.Label for info level events.
- Info = Of(InfoLevel)
- // Warning is an event.Label for warning level events.
- Warning = Of(WarningLevel)
- // Error is an event.Label for error level events.
- Error = Of(ErrorLevel)
- // Fatal is an event.Label for fatal level events.
- Fatal = Of(FatalLevel)
-)
-
-// Of creates a new Label with this key and the supplied value.
-func Of(v Level) event.Label {
- return event.Label{Name: Key, Value: event.ValueOf(v)}
+// Of creates a label for the level.
+func (l Level) Label() event.Label {
+ return event.Label{Name: Key, Value: event.ValueOf(l)}
}
// From can be used to get a value from a Label.
@@ -60,22 +50,41 @@
return t.Value.Interface().(Level)
}
+func (l Level) Log(ctx context.Context, msg string, labels ...event.Label) {
+ ev := event.New(ctx, event.LogKind)
+ if ev != nil {
+ ev.Labels = append(ev.Labels, l.Label())
+ ev.Labels = append(ev.Labels, labels...)
+ ev.Message = msg
+ ev.Deliver()
+ }
+}
+
+func (l Level) Logf(ctx context.Context, msg string, args ...interface{}) {
+ ev := event.New(ctx, event.LogKind)
+ if ev != nil {
+ ev.Labels = append(ev.Labels, l.Label())
+ ev.Message = fmt.Sprintf(msg, args...)
+ ev.Deliver()
+ }
+}
+
func (l Level) Class() Level {
switch {
- case l > MaxLevel:
- return MaxLevel
- case l > FatalLevel:
- return FatalLevel
- case l > ErrorLevel:
- return ErrorLevel
- case l > WarningLevel:
- return WarningLevel
- case l > DebugLevel:
- return DebugLevel
- case l > InfoLevel:
- return InfoLevel
- case l > TraceLevel:
- return TraceLevel
+ case l > Max:
+ return Max
+ case l > Fatal:
+ return Fatal
+ case l > Error:
+ return Error
+ case l > Warning:
+ return Warning
+ case l > Debug:
+ return Debug
+ case l > Info:
+ return Info
+ case l > Trace:
+ return Trace
default:
return 0
}
@@ -86,58 +95,58 @@
case 0:
return "invalid"
- case TraceLevel:
+ case Trace:
return "trace"
- case TraceLevel + 1:
+ case Trace + 1:
return "trace2"
- case TraceLevel + 2:
+ case Trace + 2:
return "trace3"
- case TraceLevel + 3:
+ case Trace + 3:
return "trace4"
- case DebugLevel:
+ case Debug:
return "debug"
- case DebugLevel + 1:
+ case Debug + 1:
return "debug2"
- case DebugLevel + 2:
+ case Debug + 2:
return "debug3"
- case DebugLevel + 3:
+ case Debug + 3:
return "debug4"
- case InfoLevel:
+ case Info:
return "info"
- case InfoLevel + 1:
+ case Info + 1:
return "info2"
- case InfoLevel + 2:
+ case Info + 2:
return "info3"
- case InfoLevel + 3:
+ case Info + 3:
return "info4"
- case WarningLevel:
+ case Warning:
return "warning"
- case WarningLevel + 1:
+ case Warning + 1:
return "warning2"
- case WarningLevel + 2:
+ case Warning + 2:
return "warning3"
- case WarningLevel + 3:
+ case Warning + 3:
return "warning4"
- case ErrorLevel:
+ case Error:
return "error"
- case ErrorLevel + 1:
+ case Error + 1:
return "error2"
- case ErrorLevel + 2:
+ case Error + 2:
return "error3"
- case ErrorLevel + 3:
+ case Error + 3:
return "error4"
- case FatalLevel:
+ case Fatal:
return "fatal"
- case FatalLevel + 1:
+ case Fatal + 1:
return "fatal2"
- case FatalLevel + 2:
+ case Fatal + 2:
return "fatal3"
- case FatalLevel + 3:
+ case Fatal + 3:
return "fatal4"
default:
return "invalid"
diff --git a/event/severity/severity_test.go b/event/severity/severity_test.go
index f115052..8825f23 100644
--- a/event/severity/severity_test.go
+++ b/event/severity/severity_test.go
@@ -18,7 +18,6 @@
)
func TestPrint(t *testing.T) {
- //TODO: print the textual form of severity
ctx := context.Background()
for _, test := range []struct {
name string
@@ -26,11 +25,11 @@
expect string
}{{
name: "debug",
- events: func(ctx context.Context) { event.To(ctx).With(severity.Debug).Log("a message") },
+ events: func(ctx context.Context) { severity.Debug.Log(ctx, "a message") },
expect: `time="2020/03/05 14:27:48" level=debug msg="a message"`,
}, {
name: "info",
- events: func(ctx context.Context) { event.To(ctx).With(severity.Info).Log("a message") },
+ events: func(ctx context.Context) { severity.Info.Log(ctx, "a message") },
expect: `time="2020/03/05 14:27:48" level=info msg="a message"`},
} {
buf := &strings.Builder{}
diff --git a/jsonrpc2/conn.go b/jsonrpc2/conn.go
index cbc1558..1b26d3a 100644
--- a/jsonrpc2/conn.go
+++ b/jsonrpc2/conn.go
@@ -12,6 +12,7 @@
"sync/atomic"
"golang.org/x/exp/event"
+ "golang.org/x/exp/event/keys"
errors "golang.org/x/xerrors"
)
@@ -52,10 +53,10 @@
}
type AsyncCall struct {
- id ID
- response chan *Response // the channel a response will be delivered on
- resultBox chan asyncResult
- endBuilder event.Builder // close the tracing span when all processing for the message is complete
+ id ID
+ response chan *Response // the channel a response will be delivered on
+ resultBox chan asyncResult
+ ctx context.Context
}
type asyncResult struct {
@@ -65,11 +66,10 @@
// incoming is used to track an incoming request as it is being handled
type incoming struct {
- request *Request // the request being processed
- baseCtx context.Context // a base context for the message processing
- endBuilder event.Builder // builder's End method called when all processing for the message is complete
- handleCtx context.Context // the context for handling the message, child of baseCtx
- cancel func() // a function that cancels the handling context
+ request *Request // the request being processed
+ baseCtx context.Context // a base context for the message processing
+ handleCtx context.Context // the context for handling the message, child of baseCtx
+ cancel func() // a function that cancels the handling context
}
// Bind returns the options unmodified.
@@ -124,12 +124,14 @@
if err != nil {
return errors.Errorf("marshaling notify parameters: %v", err)
}
- b := event.To(ctx).With(Method.Of(method))
- b.Clone().Metric(Started.Record(1))
- ctx, endBuilder := b.With(RPCDirection.Of(Outbound)).Start(method)
- err = c.write(ctx, notify)
- event.To(ctx).With(Error.Of(err)).Metric(Finished.Record(1))
- endBuilder.End()
+ ctx = event.Start(ctx, method, RPCDirection.Of(Outbound))
+ Started.Record(ctx, 1, Method.Of(method))
+ var errLabel event.Label
+ if err = c.write(ctx, notify); err != nil {
+ errLabel = keys.Value("error").Of(err)
+ }
+ Finished.Record(ctx, 1, errLabel)
+ event.End(ctx)
return err
}
@@ -150,10 +152,11 @@
result.resultBox <- asyncResult{err: errors.Errorf("marshaling call parameters: %w", err)}
return result
}
- b := event.To(ctx).With(Method.Of(method))
- b.Clone().Metric(Started.Record(1))
- ctx, endBuilder := b.Clone().With(RPCDirection.Of(Outbound)).With(RPCID.Of(fmt.Sprintf("%q", result.id))).Start(method)
- result.endBuilder = endBuilder
+ //TODO: rewrite this using the new target/prototype stuff
+ ctx = event.Start(ctx, method,
+ Method.Of(method), RPCDirection.Of(Outbound), RPCID.Of(fmt.Sprintf("%q", result.id)))
+ Started.Record(ctx, 1, Method.Of(method))
+ result.ctx = ctx
// We have to add ourselves to the pending map before we send, otherwise we
// are racing the response.
// rchan is buffered in case the response arrives without a listener.
@@ -191,7 +194,7 @@
// The response will be unmarshaled from JSON into the result.
func (a *AsyncCall) Await(ctx context.Context, result interface{}) error {
status := "NONE"
- defer a.endBuilder.With(StatusCode.Of(status)).End()
+ defer event.End(a.ctx, StatusCode.Of(status))
var r asyncResult
select {
case response := <-a.response:
@@ -292,14 +295,14 @@
request: msg,
}
// add a span to the context for this request
- b := event.To(ctx).With(Method.Of(msg.Method)).With(RPCDirection.Of(Inbound))
+ var idLabel event.Label
if msg.IsCall() {
- b = b.With(RPCID.Of(fmt.Sprintf("%q", msg.ID)))
+ idLabel = RPCID.Of(fmt.Sprintf("%q", msg.ID))
}
- entry.baseCtx, entry.endBuilder = b.Start(msg.Method)
- b = event.To(entry.baseCtx).With(Method.Of(msg.Method))
- b.Clone().Metric(Started.Record(1))
- b.Metric(ReceivedBytes.Record(n))
+ entry.baseCtx = event.Start(ctx, msg.Method,
+ Method.Of(msg.Method), RPCDirection.Of(Inbound), idLabel)
+ Started.Record(entry.baseCtx, 1, Method.Of(msg.Method))
+ ReceivedBytes.Record(entry.baseCtx, n, Method.Of(msg.Method))
// in theory notifications cannot be cancelled, but we build them a cancel context anyway
entry.handleCtx, entry.cancel = context.WithCancel(entry.baseCtx)
// if the request is a call, add it to the incoming map so it can be
@@ -412,7 +415,7 @@
if err := c.respond(entry, result, rerr); err != nil {
// no way to propagate this error
//TODO: should we do more than just log it?
- event.To(entry.baseCtx).With(Error.Of(err)).Log("jsonrpc2 message delivery failed")
+ event.Log(entry.baseCtx, "jsonrpc2 message delivery failed", keys.Value("error").Of(err))
}
}
@@ -458,7 +461,7 @@
entry.cancel = nil
}
// mark the entire request processing as done
- entry.endBuilder.With(StatusCode.Of(status)).End()
+ event.End(entry.baseCtx, StatusCode.Of(status))
return err
}
@@ -469,6 +472,6 @@
defer func() { c.writerBox <- writer }()
n, err := writer.Write(ctx, msg)
// TODO: get a method label in here somehow.
- event.To(ctx).Metric(SentBytes.Record(n))
+ SentBytes.Record(ctx, n)
return err
}
diff --git a/jsonrpc2/defs.go b/jsonrpc2/defs.go
index f40800d..802a205 100644
--- a/jsonrpc2/defs.go
+++ b/jsonrpc2/defs.go
@@ -13,7 +13,6 @@
Method = keys.String("method")
RPCID = keys.String("id")
RPCDirection = keys.String("direction")
- Error = keys.Value("error")
StatusCode = keys.String("status.code")
)