event: switch to kind specific handlers

This removes Handler and adds a handler per kind, and then detects which
kind specific handlers are supported when building an exporter.
This change makes it safe to add new kinds in the future, and is also faster
for kinds that are not handled.

Change-Id: Ic8f57a5da55cfa179d3e98e6e39112b8f87b8303
Reviewed-on: https://go-review.googlesource.com/c/exp/+/318832
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/eventtest/eventtest.go b/event/adapter/eventtest/eventtest.go
index 3755514..e9bd4be 100644
--- a/event/adapter/eventtest/eventtest.go
+++ b/event/adapter/eventtest/eventtest.go
@@ -22,14 +22,14 @@
 // 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.p = logfmt.Printer(&h.buf)
+	h.p = logfmt.NewPrinter(&h.buf)
 	return event.WithExporter(ctx, event.NewExporter(h))
 }
 
 type testHandler struct {
 	tb  testing.TB
 	buf strings.Builder
-	p   event.Handler
+	p   *logfmt.Printer
 }
 
 func (h *testHandler) Log(ctx context.Context, ev *event.Event) {
diff --git a/event/adapter/logfmt/logfmt.go b/event/adapter/logfmt/logfmt.go
index 1d8cf23..26b24f1 100644
--- a/event/adapter/logfmt/logfmt.go
+++ b/event/adapter/logfmt/logfmt.go
@@ -16,7 +16,7 @@
 //TODO: some actual research into what this arbritray optimization number should be
 const bufCap = 50
 
-type printer struct {
+type Printer struct {
 	io.Writer
 	io.StringWriter
 
@@ -27,14 +27,10 @@
 	io.Writer
 }
 
-// Printer returns a handler that prints the events to the supplied writer.
+// NewPrinter returns a handler that prints the events to the supplied writer.
 // Each event is printed in logfmt format on a single line.
-func Printer(to io.Writer) event.Handler {
-	return newPrinter(to)
-}
-
-func newPrinter(to io.Writer) *printer {
-	p := &printer{Writer: to}
+func NewPrinter(to io.Writer) *Printer {
+	p := &Printer{Writer: to}
 	ok := false
 	p.StringWriter, ok = to.(io.StringWriter)
 	if !ok {
@@ -43,33 +39,33 @@
 	return p
 }
 
-func (p *printer) Log(ctx context.Context, ev *event.Event) {
+func (p *Printer) Log(ctx context.Context, ev *event.Event) {
 	p.Event("log", ev)
 	p.WriteString("\n")
 }
 
-func (p *printer) Metric(ctx context.Context, ev *event.Event) {
+func (p *Printer) Metric(ctx context.Context, ev *event.Event) {
 	p.Event("metric", ev)
 	p.WriteString("\n")
 }
 
-func (p *printer) Annotate(ctx context.Context, ev *event.Event) {
+func (p *Printer) Annotate(ctx context.Context, ev *event.Event) {
 	p.Event("annotate", ev)
 	p.WriteString("\n")
 }
 
-func (p *printer) Start(ctx context.Context, ev *event.Event) context.Context {
+func (p *Printer) Start(ctx context.Context, ev *event.Event) context.Context {
 	p.Event("start", ev)
 	p.WriteString("\n")
 	return ctx
 }
 
-func (p *printer) End(ctx context.Context, ev *event.Event) {
+func (p *Printer) End(ctx context.Context, ev *event.Event) {
 	p.Event("end", ev)
 	p.WriteString("\n")
 }
 
-func (p *printer) Event(kind string, ev *event.Event) {
+func (p *Printer) Event(kind string, ev *event.Event) {
 	const timeFormat = "2006-01-02T15:04:05"
 	if !ev.At.IsZero() {
 		p.WriteString("time=")
@@ -101,13 +97,13 @@
 	}
 }
 
-func (p *printer) Label(l *event.Label) {
+func (p *Printer) Label(l *event.Label) {
 	p.Ident(l.Name)
 	p.WriteString("=")
 	p.Value(&l.Value)
 }
 
-func (p *printer) Value(v *event.Value) {
+func (p *Printer) Value(v *event.Value) {
 	switch {
 	case v.IsString():
 		p.Quote(v.String())
@@ -128,12 +124,12 @@
 	}
 }
 
-func (p *printer) Ident(s string) {
+func (p *Printer) Ident(s string) {
 	//TODO: this should also escape = if it occurs in an ident?
 	p.Quote(s)
 }
 
-func (p *printer) Quote(s string) {
+func (p *Printer) Quote(s string) {
 	if s == "" {
 		p.WriteString(`""`)
 		return
diff --git a/event/alloc_test.go b/event/alloc_test.go
index 32430af..8e9c8eb 100644
--- a/event/alloc_test.go
+++ b/event/alloc_test.go
@@ -19,7 +19,7 @@
 	anInt := event.Label{Name: "int", Value: event.Int64Of(4)}
 	aString := event.Label{Name: "string", Value: event.StringOf("value")}
 
-	e := event.NewExporter(logfmt.Printer(ioutil.Discard))
+	e := event.NewExporter(logfmt.NewPrinter(ioutil.Discard))
 	ctx := event.WithExporter(context.Background(), e)
 	allocs := int(testing.AllocsPerRun(5, func() {
 		event.To(ctx).With(aString).With(anInt).Log("message")
diff --git a/event/bench/event_test.go b/event/bench/event_test.go
index 29b8be3..7d171a5 100644
--- a/event/bench/event_test.go
+++ b/event/bench/event_test.go
@@ -51,7 +51,7 @@
 
 	eventTrace = Hooks{
 		AStart: func(ctx context.Context, a int) context.Context {
-			ctx, _ = event.Span(ctx).Start(aMsg)
+			ctx, _ = event.Trace(ctx).Start(aMsg)
 			event.To(ctx).With(aValue.Of(a)).Annotate()
 			return ctx
 		},
@@ -59,7 +59,7 @@
 			event.To(ctx).End()
 		},
 		BStart: func(ctx context.Context, b string) context.Context {
-			ctx, _ = event.Span(ctx).Start(bMsg)
+			ctx, _ = event.Trace(ctx).Start(bMsg)
 			event.To(ctx).With(bValue.Of(b)).Annotate()
 			return ctx
 		},
@@ -95,7 +95,7 @@
 }
 
 func eventPrint(w io.Writer) context.Context {
-	e := event.NewExporter(logfmt.Printer(w))
+	e := event.NewExporter(logfmt.NewPrinter(w))
 	e.Now = eventtest.TestNow()
 	return event.WithExporter(context.Background(), e)
 }
@@ -126,7 +126,7 @@
 
 type noopHandler struct{}
 
-func (noopHandler) Log(ctx context.Context, ev *event.Event)      {}
+func (noopHandler) Log_(ctx context.Context, ev *event.Event)     {}
 func (noopHandler) Metric(ctx context.Context, ev *event.Event)   {}
 func (noopHandler) Annotate(ctx context.Context, ev *event.Event) {}
 func (noopHandler) End(ctx context.Context, ev *event.Event)      {}
diff --git a/event/builder.go b/event/builder.go
index eaf23e3..68f9483 100644
--- a/event/builder.go
+++ b/event/builder.go
@@ -17,10 +17,10 @@
 	data *builder
 }
 
-// SpanBuilder is a specialized Builder for construction of new span events.
-type SpanBuilder struct {
+// TraceBuilder is a specialized Builder for construction of new trace events.
+type TraceBuilder struct {
 	ctx  context.Context
-	data *spanBuilder
+	data *traceBuilder
 }
 
 // preallocateLabels controls the space reserved for labels in a builder.
@@ -39,13 +39,13 @@
 
 var builderPool = sync.Pool{New: func() interface{} { return &builder{} }}
 
-type spanBuilder struct {
+type traceBuilder struct {
 	exporter *Exporter
 	Event    Event
 	labels   [preallocateLabels]Label
 }
 
-var spanBuilderPool = sync.Pool{New: func() interface{} { return &spanBuilder{} }}
+var traceBuilderPool = sync.Pool{New: func() interface{} { return &traceBuilder{} }}
 
 // To initializes a builder from the values stored in a context.
 func To(ctx context.Context) Builder {
@@ -65,14 +65,14 @@
 	return b
 }
 
-// Span initializes a span builder from the values stored in a context.
-func Span(ctx context.Context) SpanBuilder {
-	b := SpanBuilder{ctx: ctx}
+// Trace initializes a trace builder from the values stored in a context.
+func Trace(ctx context.Context) TraceBuilder {
+	b := TraceBuilder{ctx: ctx}
 	exporter, parent := fromContext(ctx)
 	if exporter == nil {
 		return b
 	}
-	b.data = spanBuilderPool.Get().(*spanBuilder)
+	b.data = traceBuilderPool.Get().(*traceBuilder)
 	b.data.exporter = exporter
 	b.data.Event.Labels = b.data.labels[:0]
 	b.data.Event.Parent = parent
@@ -122,11 +122,13 @@
 	if b.data == nil {
 		return
 	}
-	b.data.exporter.mu.Lock()
-	defer b.data.exporter.mu.Unlock()
-	b.data.Event.Message = message
-	b.data.exporter.prepare(&b.data.Event)
-	b.data.exporter.handler.Log(b.data.ctx, &b.data.Event)
+	if b.data.exporter.log != nil {
+		b.data.exporter.mu.Lock()
+		defer b.data.exporter.mu.Unlock()
+		b.data.Event.Message = message
+		b.data.exporter.prepare(&b.data.Event)
+		b.data.exporter.log.Log(b.data.ctx, &b.data.Event)
+	}
 	b.done()
 }
 
@@ -136,11 +138,13 @@
 	if b.data == nil {
 		return
 	}
-	b.data.exporter.mu.Lock()
-	defer b.data.exporter.mu.Unlock()
-	b.data.Event.Message = fmt.Sprintf(template, args...)
-	b.data.exporter.prepare(&b.data.Event)
-	b.data.exporter.handler.Log(b.data.ctx, &b.data.Event)
+	if b.data.exporter.log != nil {
+		b.data.exporter.mu.Lock()
+		defer b.data.exporter.mu.Unlock()
+		b.data.Event.Message = fmt.Sprintf(template, args...)
+		b.data.exporter.prepare(&b.data.Event)
+		b.data.exporter.log.Log(b.data.ctx, &b.data.Event)
+	}
 	b.done()
 }
 
@@ -149,10 +153,12 @@
 	if b.data == nil {
 		return
 	}
-	b.data.exporter.mu.Lock()
-	defer b.data.exporter.mu.Unlock()
-	b.data.exporter.prepare(&b.data.Event)
-	b.data.exporter.handler.Metric(b.data.ctx, &b.data.Event)
+	if b.data.exporter.metric != nil {
+		b.data.exporter.mu.Lock()
+		defer b.data.exporter.mu.Unlock()
+		b.data.exporter.prepare(&b.data.Event)
+		b.data.exporter.metric.Metric(b.data.ctx, &b.data.Event)
+	}
 	b.done()
 }
 
@@ -161,10 +167,12 @@
 	if b.data == nil {
 		return
 	}
-	b.data.exporter.mu.Lock()
-	defer b.data.exporter.mu.Unlock()
-	b.data.exporter.prepare(&b.data.Event)
-	b.data.exporter.handler.Annotate(b.data.ctx, &b.data.Event)
+	if b.data.exporter.annotate != nil {
+		b.data.exporter.mu.Lock()
+		defer b.data.exporter.mu.Unlock()
+		b.data.exporter.prepare(&b.data.Event)
+		b.data.exporter.annotate.Annotate(b.data.ctx, &b.data.Event)
+	}
 	b.done()
 }
 
@@ -173,10 +181,12 @@
 	if b.data == nil {
 		return
 	}
-	b.data.exporter.mu.Lock()
-	defer b.data.exporter.mu.Unlock()
-	b.data.exporter.prepare(&b.data.Event)
-	b.data.exporter.handler.End(b.data.ctx, &b.data.Event)
+	if b.data.exporter.trace != nil {
+		b.data.exporter.mu.Lock()
+		defer b.data.exporter.mu.Unlock()
+		b.data.exporter.prepare(&b.data.Event)
+		b.data.exporter.trace.End(b.data.ctx, &b.data.Event)
+	}
 	b.done()
 }
 
@@ -196,7 +206,7 @@
 }
 
 // WithAll adds all the supplied labels to the event being constructed.
-func (b SpanBuilder) WithAll(labels ...Label) SpanBuilder {
+func (b TraceBuilder) WithAll(labels ...Label) TraceBuilder {
 	if b.data != nil || len(labels) == 0 {
 		return b
 	}
@@ -213,28 +223,32 @@
 // matching end event.
 // All events created from the returned context will have this start event
 // as their parent.
-func (b SpanBuilder) Start(name string) (context.Context, func()) {
+func (b TraceBuilder) Start(name string) (context.Context, func()) {
 	if b.data == nil {
 		return b.ctx, func() {}
 	}
-	b.data.exporter.mu.Lock()
-	defer b.data.exporter.mu.Unlock()
-	b.data.exporter.prepare(&b.data.Event)
-	exporter, parent := b.data.exporter, b.data.Event.ID
-	b.data.Event.Message = name
-	ctx := newContext(b.ctx, exporter, parent)
-	ctx = b.data.exporter.handler.Start(ctx, &b.data.Event)
-	b.done()
-	return ctx, func() {
-		b := Builder{}
-		b.data = builderPool.Get().(*builder)
-		b.data.exporter = exporter
-		b.data.Event.Parent = parent
-		b.End()
+	ctx := b.ctx
+	end := func() {}
+	if b.data.exporter.trace != nil {
+		b.data.exporter.mu.Lock()
+		defer b.data.exporter.mu.Unlock()
+		b.data.exporter.prepare(&b.data.Event)
+		// create the end builder
+		eb := Builder{}
+		eb.data = builderPool.Get().(*builder)
+		eb.data.exporter = b.data.exporter
+		eb.data.Event.Parent = b.data.Event.ID
+		end = eb.End
+		// and now deliver the start event
+		b.data.Event.Message = name
+		ctx = newContext(ctx, b.data.exporter, b.data.Event.ID)
+		b.data.exporter.trace.Start(ctx, &b.data.Event)
 	}
+	b.done()
+	return ctx, end
 }
 
-func (b SpanBuilder) done() {
-	*b.data = spanBuilder{}
-	spanBuilderPool.Put(b.data)
+func (b TraceBuilder) done() {
+	*b.data = traceBuilder{}
+	traceBuilderPool.Put(b.data)
 }
diff --git a/event/builder_test.go b/event/builder_test.go
index e96c9e7..6cf7d69 100644
--- a/event/builder_test.go
+++ b/event/builder_test.go
@@ -16,23 +16,13 @@
 	"golang.org/x/exp/event/keys"
 )
 
-type testHandler struct{}
-
-func (testHandler) Log(ctx context.Context, ev *event.Event)      {}
-func (testHandler) Metric(ctx context.Context, ev *event.Event)   {}
-func (testHandler) Annotate(ctx context.Context, ev *event.Event) {}
-func (testHandler) End(ctx context.Context, ev *event.Event)      {}
-func (testHandler) Start(ctx context.Context, ev *event.Event) context.Context {
-	return ctx
-}
-
 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(testHandler{}))
+	ctx := event.WithExporter(context.Background(), event.NewExporter(nil))
 	b1 := event.To(ctx)
 	b1.With(labels[0]).With(labels[1])
 	check(t, b1, labels[:2])
diff --git a/event/disabled.go b/event/disabled.go
index cc55262..7de05ff 100644
--- a/event/disabled.go
+++ b/event/disabled.go
@@ -12,7 +12,7 @@
 )
 
 type Builder struct{}
-type SpanBuilder struct{ ctx context.Context }
+type TraceBuilder struct{ ctx context.Context }
 type Exporter struct {
 	Now func() time.Time
 }
@@ -20,7 +20,7 @@
 func NewExporter(h Handler) *Exporter { return &Exporter{} }
 
 func To(ctx context.Context) Builder                        { return Builder{} }
-func Span(ctx context.Context) SpanBuilder                  { return SpanBuilder{ctx: ctx} }
+func Trace(ctx context.Context) TraceBuilder                { return TraceBuilder{ctx: ctx} }
 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 }
@@ -30,10 +30,10 @@
 func (b Builder) Annotate()                                 {}
 func (b Builder) End()                                      {}
 func (b Builder) Event() *Event                             { return &Event{} }
-func (b SpanBuilder) With(label Label) SpanBuilder          { return b }
-func (b SpanBuilder) WithAll(labels ...Label) SpanBuilder   { return b }
+func (b TraceBuilder) With(label Label) TraceBuilder        { return b }
+func (b TraceBuilder) WithAll(labels ...Label) TraceBuilder { return b }
 
-func (b SpanBuilder) Start(name string) (context.Context, func()) {
+func (b TraceBuilder) Start(name string) (context.Context, func()) {
 	return b.ctx, func() {}
 }
 
diff --git a/event/event.go b/event/event.go
index 8437dda..3fcdde4 100644
--- a/event/event.go
+++ b/event/event.go
@@ -19,17 +19,32 @@
 	Labels  []Label
 }
 
-// Handler is a the type for something that handles events as they occur.
-type Handler interface {
+// LogHandler is a the type for something that handles log events as they occur.
+type LogHandler interface {
 	// Log indicates a logging event.
 	Log(context.Context, *Event)
+}
+
+// MetricHandler is a the type for something that handles metric events as they
+// occur.
+type MetricHandler interface {
 	// Metric indicates a metric record event.
 	Metric(context.Context, *Event)
+}
+
+// AnnotateHandler is a the type for something that handles annotate events as
+// they occur.
+type AnnotateHandler interface {
 	// Annotate reports label values at a point in time.
 	Annotate(context.Context, *Event)
-	// Start indicates a span start event.
+}
+
+// TraceHandler is a the type for something that handles start and end events as
+// they occur.
+type TraceHandler interface {
+	// Start indicates a trace start event.
 	Start(context.Context, *Event) context.Context
-	// End indicates a span end event.
+	// End indicates a trace end event.
 	End(context.Context, *Event)
 }
 
diff --git a/event/event_test.go b/event/event_test.go
index 265939b..2f796f3 100644
--- a/event/event_test.go
+++ b/event/event_test.go
@@ -49,7 +49,7 @@
 	}, {
 		name: "span",
 		events: func(ctx context.Context) {
-			ctx, end := event.Span(ctx).Start("span")
+			ctx, end := event.Trace(ctx).Start("span")
 			end()
 		},
 		expect: `
@@ -58,9 +58,9 @@
 `}, {
 		name: "span nested",
 		events: func(ctx context.Context) {
-			ctx, end := event.Span(ctx).Start("parent")
+			ctx, end := event.Trace(ctx).Start("parent")
 			defer end()
-			child, end2 := event.Span(ctx).Start("child")
+			child, end2 := event.Trace(ctx).Start("child")
 			defer end2()
 			event.To(child).Log("message")
 		},
@@ -98,8 +98,7 @@
 time=2020-03-05T14:27:49 id=2 kind=log msg="string event" myString="some string value"
 `}} {
 		buf := &strings.Builder{}
-		h := logfmt.Printer(buf)
-		e := event.NewExporter(h)
+		e := event.NewExporter(logfmt.NewPrinter(buf))
 		e.Now = eventtest.TestNow()
 		ctx := event.WithExporter(ctx, e)
 		test.events(ctx)
@@ -112,7 +111,7 @@
 }
 
 func ExampleLog() {
-	e := event.NewExporter(logfmt.Printer(os.Stdout))
+	e := event.NewExporter(logfmt.NewPrinter(os.Stdout))
 	e.Now = eventtest.TestNow()
 	ctx := event.WithExporter(context.Background(), e)
 	event.To(ctx).With(keys.Int("myInt").Of(6)).Log("my event")
diff --git a/event/export.go b/event/export.go
index dd64e6e..6f9636c 100644
--- a/event/export.go
+++ b/event/export.go
@@ -19,7 +19,10 @@
 	Now func() time.Time
 
 	mu        sync.Mutex
-	handler   Handler
+	log       LogHandler
+	metric    MetricHandler
+	annotate  AnnotateHandler
+	trace     TraceHandler
 	lastEvent uint64
 }
 
@@ -41,11 +44,13 @@
 
 // NewExporter creates an Exporter using the supplied handler.
 // Event delivery is serialized to enable safe atomic handling.
-func NewExporter(h Handler) *Exporter {
-	return &Exporter{
-		Now:     time.Now,
-		handler: h,
-	}
+func NewExporter(handler interface{}) *Exporter {
+	e := &Exporter{Now: time.Now}
+	e.log, _ = handler.(LogHandler)
+	e.metric, _ = handler.(MetricHandler)
+	e.annotate, _ = handler.(AnnotateHandler)
+	e.trace, _ = handler.(TraceHandler)
+	return e
 }
 
 func setDefaultExporter(e *Exporter) {
diff --git a/event/severity/severity_test.go b/event/severity/severity_test.go
index d602f28..11422cd 100644
--- a/event/severity/severity_test.go
+++ b/event/severity/severity_test.go
@@ -34,8 +34,7 @@
 		expect: `time=2020-03-05T14:27:48 id=1 kind=log msg="a message" level=info`},
 	} {
 		buf := &strings.Builder{}
-		h := logfmt.Printer(buf)
-		e := event.NewExporter(h)
+		e := event.NewExporter(logfmt.NewPrinter(buf))
 		e.Now = eventtest.TestNow()
 		ctx := event.WithExporter(ctx, e)
 		test.events(ctx)