event: changes to support context in start

Start now has a fluent builder, this makes constructing start events conceptually the same as all other types.
It also allows the span builder to carry the context, which is then passed to the handler.
Handler now has a method per kind rather than a single Handle method, and Kind has been removed from event.
This (amongst other things) allows Start to have a different signature so it can take and return a context.
It is also the fastest implementation so far across all benchmarks.
Disabled is now implemented by build tag instead of boolean.

Change-Id: Id00a22633a35bfaccf20ea5fcc1f918f849411fd
Reviewed-on: https://go-review.googlesource.com/c/exp/+/319070
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
new file mode 100644
index 0000000..3755514
--- /dev/null
+++ b/event/adapter/eventtest/eventtest.go
@@ -0,0 +1,76 @@
+// 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.
+
+// Package eventtest supports logging events to a test.
+// You can use NewContext to create a context that knows how to deliver
+// telemetry events back to the test.
+// You must use this context or a derived one anywhere you want telemetry to be
+// correctly routed back to the test it was constructed with.
+package eventtest
+
+import (
+	"context"
+	"strings"
+	"testing"
+	"time"
+
+	"golang.org/x/exp/event"
+	"golang.org/x/exp/event/adapter/logfmt"
+)
+
+// 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)
+	return event.WithExporter(ctx, event.NewExporter(h))
+}
+
+type testHandler struct {
+	tb  testing.TB
+	buf strings.Builder
+	p   event.Handler
+}
+
+func (h *testHandler) Log(ctx context.Context, ev *event.Event) {
+	h.p.Log(ctx, ev)
+	h.deliver()
+}
+
+func (h *testHandler) Metric(ctx context.Context, ev *event.Event) {
+	h.p.Metric(ctx, ev)
+	h.deliver()
+}
+
+func (h *testHandler) Annotate(ctx context.Context, ev *event.Event) {
+	h.p.Annotate(ctx, ev)
+	h.deliver()
+}
+
+func (h *testHandler) Start(ctx context.Context, ev *event.Event) context.Context {
+	ctx = h.p.Start(ctx, ev)
+	h.deliver()
+	return ctx
+}
+
+func (h *testHandler) End(ctx context.Context, ev *event.Event) {
+	h.p.End(ctx, ev)
+	h.deliver()
+}
+
+func (h *testHandler) deliver() {
+	if h.buf.Len() == 0 {
+		return
+	}
+	h.tb.Log(h.buf.String())
+	h.buf.Reset()
+}
+
+func TestNow() func() time.Time {
+	nextTime, _ := time.Parse(time.RFC3339Nano, "2020-03-05T14:27:48Z")
+	return func() time.Time {
+		thisTime := nextTime
+		nextTime = nextTime.Add(time.Second)
+		return thisTime
+	}
+}
diff --git a/event/logfmt.go b/event/adapter/logfmt/logfmt.go
similarity index 77%
rename from event/logfmt.go
rename to event/adapter/logfmt/logfmt.go
index 1ea8566..1d8cf23 100644
--- a/event/logfmt.go
+++ b/event/adapter/logfmt/logfmt.go
@@ -2,12 +2,15 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-package event
+package logfmt
 
 import (
+	"context"
 	"fmt"
 	"io"
 	"strconv"
+
+	"golang.org/x/exp/event"
 )
 
 //TODO: some actual research into what this arbritray optimization number should be
@@ -26,7 +29,7 @@
 
 // Printer 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) Handler {
+func Printer(to io.Writer) event.Handler {
 	return newPrinter(to)
 }
 
@@ -40,14 +43,33 @@
 	return p
 }
 
-// Handle makes printer implement the Handler interface so it can be used
-// directly to print all handled events.
-func (p *printer) Handle(ev *Event) {
-	p.Event(ev)
+func (p *printer) Log(ctx context.Context, ev *event.Event) {
+	p.Event("log", ev)
 	p.WriteString("\n")
 }
 
-func (p *printer) Event(ev *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) {
+	p.Event("annotate", ev)
+	p.WriteString("\n")
+}
+
+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) {
+	p.Event("end", ev)
+	p.WriteString("\n")
+}
+
+func (p *printer) Event(kind string, ev *event.Event) {
 	const timeFormat = "2006-01-02T15:04:05"
 	if !ev.At.IsZero() {
 		p.WriteString("time=")
@@ -63,7 +85,7 @@
 	}
 
 	p.WriteString(" kind=")
-	p.Kind(ev.Kind)
+	p.WriteString(kind)
 
 	if ev.Message != "" {
 		p.WriteString(" msg=")
@@ -79,30 +101,13 @@
 	}
 }
 
-func (p *printer) Kind(k Kind) {
-	switch k {
-	case LogKind:
-		p.WriteString("log")
-	case StartKind:
-		p.WriteString("start")
-	case EndKind:
-		p.WriteString("end")
-	case MetricKind:
-		p.WriteString("metric")
-	case AnnotateKind:
-		p.WriteString("annotate")
-	default:
-		p.Write(strconv.AppendUint(p.buf[:0], uint64(k), 10))
-	}
-}
-
-func (p *printer) Label(l *Label) {
+func (p *printer) Label(l *event.Label) {
 	p.Ident(l.Name)
 	p.WriteString("=")
 	p.Value(&l.Value)
 }
 
-func (p *printer) Value(v *Value) {
+func (p *printer) Value(v *event.Value) {
 	switch {
 	case v.IsString():
 		p.Quote(v.String())
diff --git a/event/alloc_test.go b/event/alloc_test.go
index f66aa43..32430af 100644
--- a/event/alloc_test.go
+++ b/event/alloc_test.go
@@ -12,13 +12,14 @@
 	"testing"
 
 	"golang.org/x/exp/event"
+	"golang.org/x/exp/event/adapter/logfmt"
 )
 
 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))
+	e := event.NewExporter(logfmt.Printer(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_enabled_test.go b/event/bench/event_enabled_test.go
new file mode 100644
index 0000000..d9164ce
--- /dev/null
+++ b/event/bench/event_enabled_test.go
@@ -0,0 +1,53 @@
+// 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 bench_test
+
+import (
+	"testing"
+)
+
+func TestLogEventf(t *testing.T) {
+	testBenchmark(t, eventPrint, eventLogf, `
+time=2020-03-05T14:27:48 id=1 kind=log msg="a where A=0"
+time=2020-03-05T14:27:49 id=2 kind=log msg="b where B=\"A value\""
+time=2020-03-05T14:27:50 id=3 kind=log msg="a where A=1"
+time=2020-03-05T14:27:51 id=4 kind=log msg="b where B=\"Some other value\""
+time=2020-03-05T14:27:52 id=5 kind=log msg="a where A=22"
+time=2020-03-05T14:27:53 id=6 kind=log msg="b where B=\"Some other value\""
+time=2020-03-05T14:27:54 id=7 kind=log msg="a where A=333"
+time=2020-03-05T14:27:55 id=8 kind=log msg="b where B=\"\""
+time=2020-03-05T14:27:56 id=9 kind=log msg="a where A=4444"
+time=2020-03-05T14:27:57 id=10 kind=log msg="b where B=\"prime count of values\""
+time=2020-03-05T14:27:58 id=11 kind=log msg="a where A=55555"
+time=2020-03-05T14:27:59 id=12 kind=log msg="b where B=\"V\""
+time=2020-03-05T14:28:00 id=13 kind=log msg="a where A=666666"
+time=2020-03-05T14:28:01 id=14 kind=log msg="b where B=\"A value\""
+time=2020-03-05T14:28:02 id=15 kind=log msg="a where A=7777777"
+time=2020-03-05T14:28:03 id=16 kind=log msg="b where B=\"A value\""
+`)
+}
+
+func TestLogEvent(t *testing.T) {
+	testBenchmark(t, eventPrint, eventLog, `
+time=2020-03-05T14:27:48 id=1 kind=log msg=a A=0
+time=2020-03-05T14:27:49 id=2 kind=log msg=b B="A value"
+time=2020-03-05T14:27:50 id=3 kind=log msg=a A=1
+time=2020-03-05T14:27:51 id=4 kind=log msg=b B="Some other value"
+time=2020-03-05T14:27:52 id=5 kind=log msg=a A=22
+time=2020-03-05T14:27:53 id=6 kind=log msg=b B="Some other value"
+time=2020-03-05T14:27:54 id=7 kind=log msg=a A=333
+time=2020-03-05T14:27:55 id=8 kind=log msg=b B=""
+time=2020-03-05T14:27:56 id=9 kind=log msg=a A=4444
+time=2020-03-05T14:27:57 id=10 kind=log msg=b B="prime count of values"
+time=2020-03-05T14:27:58 id=11 kind=log msg=a A=55555
+time=2020-03-05T14:27:59 id=12 kind=log msg=b B=V
+time=2020-03-05T14:28:00 id=13 kind=log msg=a A=666666
+time=2020-03-05T14:28:01 id=14 kind=log msg=b B="A value"
+time=2020-03-05T14:28:02 id=15 kind=log msg=a A=7777777
+time=2020-03-05T14:28:03 id=16 kind=log msg=b B="A value"
+`)
+}
diff --git a/event/bench/event_test.go b/event/bench/event_test.go
index 4f0663c..29b8be3 100644
--- a/event/bench/event_test.go
+++ b/event/bench/event_test.go
@@ -10,7 +10,8 @@
 	"testing"
 
 	"golang.org/x/exp/event"
-	"golang.org/x/exp/event/eventtest"
+	"golang.org/x/exp/event/adapter/eventtest"
+	"golang.org/x/exp/event/adapter/logfmt"
 	"golang.org/x/exp/event/keys"
 )
 
@@ -50,20 +51,20 @@
 
 	eventTrace = Hooks{
 		AStart: func(ctx context.Context, a int) context.Context {
-			ctx, _ = event.Start(ctx, aMsg)
+			ctx, _ = event.Span(ctx).Start(aMsg)
 			event.To(ctx).With(aValue.Of(a)).Annotate()
 			return ctx
 		},
 		AEnd: func(ctx context.Context) {
-			event.To(ctx).Deliver(event.EndKind, "")
+			event.To(ctx).End()
 		},
 		BStart: func(ctx context.Context, b string) context.Context {
-			ctx, _ = event.Start(ctx, bMsg)
+			ctx, _ = event.Span(ctx).Start(bMsg)
 			event.To(ctx).With(bValue.Of(b)).Annotate()
 			return ctx
 		},
 		BEnd: func(ctx context.Context) {
-			event.To(ctx).Deliver(event.EndKind, "")
+			event.To(ctx).End()
 		},
 	}
 
@@ -94,17 +95,11 @@
 }
 
 func eventPrint(w io.Writer) context.Context {
-	e := event.NewExporter(event.Printer(w))
+	e := event.NewExporter(logfmt.Printer(w))
 	e.Now = eventtest.TestNow()
 	return event.WithExporter(context.Background(), e)
 }
 
-func BenchmarkLogEventDisabled(b *testing.B) {
-	event.SetEnabled(false)
-	defer event.SetEnabled(true)
-	runBenchmark(b, context.Background(), eventLog)
-}
-
 func BenchmarkLogEventNoExporter(b *testing.B) {
 	runBenchmark(b, eventNoExporter(), eventLog)
 }
@@ -121,48 +116,6 @@
 	runBenchmark(b, eventPrint(io.Discard), eventLogf)
 }
 
-func TestLogEventf(t *testing.T) {
-	testBenchmark(t, eventPrint, eventLogf, `
-time=2020-03-05T14:27:48 id=1 kind=log msg="a where A=0"
-time=2020-03-05T14:27:49 id=2 kind=log msg="b where B=\"A value\""
-time=2020-03-05T14:27:50 id=3 kind=log msg="a where A=1"
-time=2020-03-05T14:27:51 id=4 kind=log msg="b where B=\"Some other value\""
-time=2020-03-05T14:27:52 id=5 kind=log msg="a where A=22"
-time=2020-03-05T14:27:53 id=6 kind=log msg="b where B=\"Some other value\""
-time=2020-03-05T14:27:54 id=7 kind=log msg="a where A=333"
-time=2020-03-05T14:27:55 id=8 kind=log msg="b where B=\"\""
-time=2020-03-05T14:27:56 id=9 kind=log msg="a where A=4444"
-time=2020-03-05T14:27:57 id=10 kind=log msg="b where B=\"prime count of values\""
-time=2020-03-05T14:27:58 id=11 kind=log msg="a where A=55555"
-time=2020-03-05T14:27:59 id=12 kind=log msg="b where B=\"V\""
-time=2020-03-05T14:28:00 id=13 kind=log msg="a where A=666666"
-time=2020-03-05T14:28:01 id=14 kind=log msg="b where B=\"A value\""
-time=2020-03-05T14:28:02 id=15 kind=log msg="a where A=7777777"
-time=2020-03-05T14:28:03 id=16 kind=log msg="b where B=\"A value\""
-`)
-}
-
-func TestLogEvent(t *testing.T) {
-	testBenchmark(t, eventPrint, eventLog, `
-time=2020-03-05T14:27:48 id=1 kind=log msg=a A=0
-time=2020-03-05T14:27:49 id=2 kind=log msg=b B="A value"
-time=2020-03-05T14:27:50 id=3 kind=log msg=a A=1
-time=2020-03-05T14:27:51 id=4 kind=log msg=b B="Some other value"
-time=2020-03-05T14:27:52 id=5 kind=log msg=a A=22
-time=2020-03-05T14:27:53 id=6 kind=log msg=b B="Some other value"
-time=2020-03-05T14:27:54 id=7 kind=log msg=a A=333
-time=2020-03-05T14:27:55 id=8 kind=log msg=b B=""
-time=2020-03-05T14:27:56 id=9 kind=log msg=a A=4444
-time=2020-03-05T14:27:57 id=10 kind=log msg=b B="prime count of values"
-time=2020-03-05T14:27:58 id=11 kind=log msg=a A=55555
-time=2020-03-05T14:27:59 id=12 kind=log msg=b B=V
-time=2020-03-05T14:28:00 id=13 kind=log msg=a A=666666
-time=2020-03-05T14:28:01 id=14 kind=log msg=b B="A value"
-time=2020-03-05T14:28:02 id=15 kind=log msg=a A=7777777
-time=2020-03-05T14:28:03 id=16 kind=log msg=b B="A value"
-`)
-}
-
 func BenchmarkTraceEventNoop(b *testing.B) {
 	runBenchmark(b, eventPrint(io.Discard), eventTrace)
 }
@@ -173,4 +126,10 @@
 
 type noopHandler struct{}
 
-func (noopHandler) Handle(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)      {}
+func (noopHandler) Start(ctx context.Context, ev *event.Event) context.Context {
+	return ctx
+}
diff --git a/event/bench/logrus_test.go b/event/bench/logrus_test.go
index 30344d4..3eefa40 100644
--- a/event/bench/logrus_test.go
+++ b/event/bench/logrus_test.go
@@ -11,7 +11,7 @@
 	"time"
 
 	"github.com/sirupsen/logrus"
-	"golang.org/x/exp/event/eventtest"
+	"golang.org/x/exp/event/adapter/eventtest"
 )
 
 var (
diff --git a/event/bench/stdlib_test.go b/event/bench/stdlib_test.go
index b91bf68..3542da9 100644
--- a/event/bench/stdlib_test.go
+++ b/event/bench/stdlib_test.go
@@ -12,7 +12,7 @@
 	"log"
 	"testing"
 
-	"golang.org/x/exp/event/eventtest"
+	"golang.org/x/exp/event/adapter/eventtest"
 )
 
 var (
diff --git a/event/bench/zap_test.go b/event/bench/zap_test.go
index 7878f35..18f41a6 100644
--- a/event/bench/zap_test.go
+++ b/event/bench/zap_test.go
@@ -12,7 +12,7 @@
 
 	"go.uber.org/zap"
 	"go.uber.org/zap/zapcore"
-	"golang.org/x/exp/event/eventtest"
+	"golang.org/x/exp/event/adapter/eventtest"
 )
 
 var (
diff --git a/event/bench/zerolog_test.go b/event/bench/zerolog_test.go
index a3d694b..65eb66d 100644
--- a/event/bench/zerolog_test.go
+++ b/event/bench/zerolog_test.go
@@ -10,7 +10,7 @@
 	"testing"
 
 	"github.com/rs/zerolog"
-	"golang.org/x/exp/event/eventtest"
+	"golang.org/x/exp/event/adapter/eventtest"
 )
 
 var (
diff --git a/event/builder.go b/event/builder.go
index 180245b..eaf23e3 100644
--- a/event/builder.go
+++ b/event/builder.go
@@ -2,98 +2,239 @@
 // 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"
 )
 
 // Builder is a fluent builder for construction of new events.
-//
-// Storing the first few labels directly 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.
 type Builder struct {
-	exporter *Exporter
-	Event    Event
-	labels   [4]Label
+	data *builder
 }
 
-var builderPool = sync.Pool{New: func() interface{} { return &Builder{} }}
+// SpanBuilder is a specialized Builder for construction of new span events.
+type SpanBuilder struct {
+	ctx  context.Context
+	data *spanBuilder
+}
+
+// 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 = 4
+
+type builder struct {
+	exporter *Exporter
+	ctx      context.Context
+	Event    Event
+	labels   [preallocateLabels]Label
+}
+
+var builderPool = sync.Pool{New: func() interface{} { return &builder{} }}
+
+type spanBuilder struct {
+	exporter *Exporter
+	Event    Event
+	labels   [preallocateLabels]Label
+}
+
+var spanBuilderPool = sync.Pool{New: func() interface{} { return &spanBuilder{} }}
+
+// To initializes a builder from the values stored in a context.
+func To(ctx context.Context) Builder {
+	return Builder{data: newBuilder(ctx)}
+}
+
+func newBuilder(ctx context.Context) *builder {
+	exporter, parent := fromContext(ctx)
+	if exporter == nil {
+		return nil
+	}
+	b := builderPool.Get().(*builder)
+	b.exporter = exporter
+	b.ctx = ctx
+	b.Event.Labels = b.labels[:0]
+	b.Event.Parent = parent
+	return b
+}
+
+// Span initializes a span builder from the values stored in a context.
+func Span(ctx context.Context) SpanBuilder {
+	b := SpanBuilder{ctx: ctx}
+	exporter, parent := fromContext(ctx)
+	if exporter == nil {
+		return b
+	}
+	b.data = spanBuilderPool.Get().(*spanBuilder)
+	b.data.exporter = exporter
+	b.data.Event.Labels = b.data.labels[:0]
+	b.data.Event.Parent = parent
+	return b
+}
 
 // Clone returns a copy of this builder.
 // The two copies can be independently delivered.
-func (b *Builder) Clone() *Builder {
-	if b == nil {
-		return nil
+func (b Builder) Clone() Builder {
+	if b.data == nil {
+		return b
 	}
-	clone := builderPool.Get().(*Builder)
-	clone.exporter = b.exporter
-	clone.Event = b.Event
-	n := len(b.Event.Labels)
-	if n <= len(b.labels) {
-		clone.Event.Labels = clone.labels[:n]
+	clone := Builder{data: builderPool.Get().(*builder)}
+	*clone.data = *b.data
+	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.Event.Labels = make([]Label, n)
+		clone.data.Event.Labels = make([]Label, len(b.data.Event.Labels))
+		copy(clone.data.Event.Labels, b.data.Event.Labels)
 	}
-	copy(clone.Event.Labels, b.Event.Labels)
 	return clone
 }
 
 // With adds a new label to the event being constructed.
-func (b *Builder) With(label Label) *Builder {
-	if b == nil {
-		return nil
+func (b Builder) With(label Label) Builder {
+	if b.data != nil {
+		b.data.Event.Labels = append(b.data.Event.Labels, label)
 	}
-	b.Event.Labels = append(b.Event.Labels, label)
 	return b
 }
 
 // WithAll adds all the supplied labels to the event being constructed.
-func (b *Builder) WithAll(labels ...Label) *Builder {
-	if b == nil || len(labels) == 0 {
+func (b Builder) WithAll(labels ...Label) Builder {
+	if b.data != nil || len(labels) == 0 {
 		return b
 	}
-	if len(b.Event.Labels) == 0 {
-		b.Event.Labels = labels
-		return b
+	if len(b.data.Event.Labels) == 0 {
+		b.data.Event.Labels = labels
+	} else {
+		b.data.Event.Labels = append(b.data.Event.Labels, labels...)
 	}
-	b.Event.Labels = append(b.Event.Labels, labels...)
 	return b
 }
 
-// Deliver sends the constructed event to the exporter.
-func (b *Builder) Deliver(kind Kind, message string) uint64 {
-	if b == nil {
-		return 0
-	}
-	b.Event.Kind = kind
-	b.Event.Message = message
-	id := b.exporter.Deliver(&b.Event)
-	*b = Builder{}
-	builderPool.Put(b)
-	return id
-}
-
 // Log is a helper that calls Deliver with LogKind.
-func (b *Builder) Log(message string) {
-	b.Deliver(LogKind, message)
+func (b Builder) Log(message string) {
+	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)
+	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{}) {
-	b.Deliver(LogKind, fmt.Sprintf(template, args...))
+func (b Builder) Logf(template string, args ...interface{}) {
+	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)
+	b.done()
 }
 
 // Metric is a helper that calls Deliver with MetricKind.
-func (b *Builder) Metric() {
-	b.Deliver(MetricKind, "")
+func (b Builder) Metric() {
+	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)
+	b.done()
 }
 
 // Annotate is a helper that calls Deliver with AnnotateKind.
-func (b *Builder) Annotate() {
-	b.Deliver(AnnotateKind, "")
+func (b Builder) Annotate() {
+	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)
+	b.done()
+}
+
+// End is a helper that calls Deliver with EndKind.
+func (b Builder) End() {
+	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)
+	b.done()
+}
+
+// Event returns a copy of the event currently being built.
+func (b Builder) Event() *Event {
+	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 Builder) done() {
+	*b.data = builder{}
+	builderPool.Put(b.data)
+}
+
+// WithAll adds all the supplied labels to the event being constructed.
+func (b SpanBuilder) WithAll(labels ...Label) SpanBuilder {
+	if b.data != nil || len(labels) == 0 {
+		return b
+	}
+	if len(b.data.Event.Labels) == 0 {
+		b.data.Event.Labels = labels
+	} else {
+		b.data.Event.Labels = append(b.data.Event.Labels, labels...)
+	}
+	return b
+}
+
+// Start delivers a start event with the given name and labels.
+// Its second return value is a function that should be called to deliver the
+// 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()) {
+	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()
+	}
+}
+
+func (b SpanBuilder) done() {
+	*b.data = spanBuilder{}
+	spanBuilderPool.Put(b.data)
 }
diff --git a/event/builder_test.go b/event/builder_test.go
index ecd07ce..e96c9e7 100644
--- a/event/builder_test.go
+++ b/event/builder_test.go
@@ -2,9 +2,12 @@
 // 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"
 	"testing"
 
@@ -13,11 +16,15 @@
 	"golang.org/x/exp/event/keys"
 )
 
-// TODO: test WithAll (which will currently break the aliasing check).
-
 type testHandler struct{}
 
-func (testHandler) Handle(*event.Event) {}
+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
@@ -25,40 +32,40 @@
 		labels = append(labels, keys.Int(fmt.Sprintf("l%d", i)).Of(i))
 	}
 
-	check := func(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)
-		}
-	}
-
-	e := event.NewExporter(testHandler{})
-	b1 := e.Builder()
+	ctx := event.WithExporter(context.Background(), event.NewExporter(testHandler{}))
+	b1 := event.To(ctx)
 	b1.With(labels[0]).With(labels[1])
-	check(b1, labels[:2])
+	check(t, b1, labels[:2])
 	b2 := b1.Clone()
-	check(b1, labels[:2])
-	check(b2, labels[:2])
+	check(t, b1, labels[:2])
+	check(t, b2, labels[:2])
 
 	b2.With(labels[2])
-	check(b1, labels[:2])
-	check(b2, labels[:3])
+	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(b1, labels[:2])
-	check(b2, labels)
+	check(t, b1, labels[:2])
+	check(t, b2, labels)
 
 	b2.Log("") // put b2 back in the pool.
-	b2 = e.Builder()
-	check(b1, labels[:2])
-	check(b2, []event.Label{})
+	b2 = event.To(ctx)
+	check(t, b1, labels[:2])
+	check(t, b2, []event.Label{})
 
 	b2.With(labels[3]).With(labels[4])
-	check(b1, labels[:2])
-	check(b2, labels[3:5])
+	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 {
diff --git a/event/disabled.go b/event/disabled.go
new file mode 100644
index 0000000..cc55262
--- /dev/null
+++ b/event/disabled.go
@@ -0,0 +1,44 @@
+// 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.
+
+// +build disable_events
+
+package event
+
+import (
+	"context"
+	"time"
+)
+
+type Builder struct{}
+type SpanBuilder struct{ ctx context.Context }
+type Exporter struct {
+	Now func() time.Time
+}
+
+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 (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) Log(message string)                        {}
+func (b Builder) Logf(template string, args ...interface{}) {}
+func (b Builder) Metric()                                   {}
+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 SpanBuilder) Start(name string) (context.Context, func()) {
+	return b.ctx, func() {}
+}
+
+func newContext(ctx context.Context, exporter *Exporter, parent uint64) context.Context {
+	return ctx
+}
+
+func setDefaultExporter(e *Exporter) {}
diff --git a/event/event.go b/event/event.go
index 7e71918..8437dda 100644
--- a/event/event.go
+++ b/event/event.go
@@ -5,14 +5,13 @@
 package event
 
 import (
-	"fmt"
+	"context"
 	"time"
 )
 
 // Event holds the information about an event that occurred.
 // It combines the event metadata with the user supplied labels.
 type Event struct {
-	Kind    Kind
 	ID      uint64    // unique for this process id of the event
 	Parent  uint64    // id of the parent event for this event
 	At      time.Time // time at which the event is delivered to the exporter.
@@ -20,29 +19,29 @@
 	Labels  []Label
 }
 
-// Kind indicates the type of event.
-type Kind byte
-
-const (
-	// UnknownKind is the default event kind, a real kind should always be chosen.
-	UnknownKind = Kind(iota)
-	// LogKind is a Labels kind that indicates a log event.
-	LogKind
-	// StartKind is a Labels kind that indicates a span start event.
-	StartKind
-	// EndKind is a Labels kind that indicates a span end event.
-	EndKind
-	// MetricKind is a Labels kind that indicates a metric record event.
-	MetricKind
-	// AnnotateKind is a Labels kind that reports label values at a point in time.
-	AnnotateKind
-)
-
-// Format prints the value in a standard form.
-func (e *Event) Format(f fmt.State, verb rune) {
-	newPrinter(f).Event(e)
+// 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 span start event.
+	Start(context.Context, *Event) context.Context
+	// End indicates a span end event.
+	End(context.Context, *Event)
 }
 
-func (k Kind) Format(f fmt.State, verb rune) {
-	newPrinter(f).Kind(k)
+// WithExporter returns a context with the exporter attached.
+// The exporter is called synchronously from the event call site, so it should
+// return quickly so as not to hold up user code.
+func WithExporter(ctx context.Context, e *Exporter) context.Context {
+	return newContext(ctx, e, 0)
+}
+
+// SetDefaultExporter sets an exporter that is used if no exporter can be
+// found on the context.
+func SetDefaultExporter(e *Exporter) {
+	setDefaultExporter(e)
 }
diff --git a/event/event_test.go b/event/event_test.go
index d26a6e4..265939b 100644
--- a/event/event_test.go
+++ b/event/event_test.go
@@ -2,6 +2,8 @@
 // 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 (
@@ -11,7 +13,8 @@
 	"testing"
 
 	"golang.org/x/exp/event"
-	"golang.org/x/exp/event/eventtest"
+	"golang.org/x/exp/event/adapter/eventtest"
+	"golang.org/x/exp/event/adapter/logfmt"
 	"golang.org/x/exp/event/keys"
 )
 
@@ -46,7 +49,7 @@
 	}, {
 		name: "span",
 		events: func(ctx context.Context) {
-			ctx, end := event.Start(ctx, "span")
+			ctx, end := event.Span(ctx).Start("span")
 			end()
 		},
 		expect: `
@@ -55,9 +58,9 @@
 `}, {
 		name: "span nested",
 		events: func(ctx context.Context) {
-			ctx, end := event.Start(ctx, "parent")
+			ctx, end := event.Span(ctx).Start("parent")
 			defer end()
-			child, end2 := event.Start(ctx, "child")
+			child, end2 := event.Span(ctx).Start("child")
 			defer end2()
 			event.To(child).Log("message")
 		},
@@ -95,7 +98,7 @@
 time=2020-03-05T14:27:49 id=2 kind=log msg="string event" myString="some string value"
 `}} {
 		buf := &strings.Builder{}
-		h := event.Printer(buf)
+		h := logfmt.Printer(buf)
 		e := event.NewExporter(h)
 		e.Now = eventtest.TestNow()
 		ctx := event.WithExporter(ctx, e)
@@ -109,7 +112,7 @@
 }
 
 func ExampleLog() {
-	e := event.NewExporter(event.Printer(os.Stdout))
+	e := event.NewExporter(logfmt.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")
diff --git a/event/eventtest/eventtest.go b/event/eventtest/eventtest.go
deleted file mode 100644
index 9e0a8de..0000000
--- a/event/eventtest/eventtest.go
+++ /dev/null
@@ -1,51 +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.
-
-// Package eventtest supports logging events to a test.
-// You can use NewContext to create a context that knows how to deliver
-// telemetry events back to the test.
-// You must use this context or a derived one anywhere you want telemetry to be
-// correctly routed back to the test it was constructed with.
-package eventtest
-
-import (
-	"context"
-	"fmt"
-	"strings"
-	"testing"
-	"time"
-
-	"golang.org/x/exp/event"
-)
-
-// 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}
-	return event.WithExporter(ctx, event.NewExporter(h))
-}
-
-type testHandler struct {
-	tb  testing.TB
-	buf strings.Builder
-}
-
-func (w *testHandler) Handle(ev *event.Event) {
-	// build our log message in buffer
-	w.buf.Reset()
-	fmt.Fprint(&w.buf, ev)
-	// log to the testing.TB
-	msg := w.buf.String()
-	if len(msg) > 0 {
-		w.tb.Log(msg)
-	}
-}
-
-func TestNow() func() time.Time {
-	nextTime, _ := time.Parse(time.RFC3339Nano, "2020-03-05T14:27:48Z")
-	return func() time.Time {
-		thisTime := nextTime
-		nextTime = nextTime.Add(time.Second)
-		return thisTime
-	}
-}
diff --git a/event/export.go b/event/export.go
index 87c7c41..dd64e6e 100644
--- a/event/export.go
+++ b/event/export.go
@@ -2,24 +2,18 @@
 // 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"
-	"os"
 	"sync"
 	"sync/atomic"
 	"time"
 	"unsafe"
 )
 
-// Handler is a the type for something that handles events as they occur.
-type Handler interface {
-	// Handle is called for each event delivered to the system.
-	Handle(*Event)
-}
-
 // Exporter synchronizes the delivery of events to handlers.
 type Exporter struct {
 	Now func() time.Time
@@ -41,19 +35,12 @@
 	parent   uint64
 }
 
-type defaultHandler struct{}
-
 var (
-	enabled         int32 = 1
-	defaultExporter       = unsafe.Pointer(&Exporter{
-		Now:     time.Now,
-		handler: defaultHandler{},
-	})
+	defaultExporter unsafe.Pointer
 )
 
 // NewExporter creates an Exporter using the supplied handler.
 // Event delivery is serialized to enable safe atomic handling.
-// It also marks the event system as active.
 func NewExporter(h Handler) *Exporter {
 	return &Exporter{
 		Now:     time.Now,
@@ -61,19 +48,6 @@
 	}
 }
 
-// SetEnabled can be used to enable or disable the entire event system.
-func SetEnabled(value bool) {
-	if value {
-		atomic.StoreInt32(&enabled, 1)
-	} else {
-		atomic.StoreInt32(&enabled, 0)
-	}
-}
-
-func isDisabled() bool {
-	return atomic.LoadInt32(&enabled) == 0
-}
-
 func setDefaultExporter(e *Exporter) {
 	atomic.StorePointer(&defaultExporter, unsafe.Pointer(e))
 }
@@ -93,86 +67,16 @@
 	return getDefaultExporter(), 0
 }
 
-// WithExporter returns a context with the exporter attached.
-// The exporter is called synchronously from the event call site, so it should
-// return quickly so as not to hold up user code.
-func WithExporter(ctx context.Context, e *Exporter) context.Context {
-	return newContext(ctx, e, 0)
-}
-
-// Builder returns a new builder for the exporter.
-func (e *Exporter) Builder() *Builder {
-	if e == nil {
-		return nil
-	}
-	b := builderPool.Get().(*Builder)
-	b.exporter = e
-	b.Event.Labels = b.labels[:0]
-	return b
-}
-
-// To initializes a builder from the values stored in a context.
-func To(ctx context.Context) *Builder {
-	if isDisabled() {
-		return nil
-	}
-	exporter, parent := fromContext(ctx)
-	b := exporter.Builder()
-	if b != nil {
-		b.Event.Parent = parent
-	}
-	return b
-}
-
-// Start delivers a start event with the given name and labels.
-// Its second return value is a function that should be called to deliver the
-// matching end event.
-// All events created from the returned context will have this start event
-// as their parent.
-func Start(ctx context.Context, name string, labels ...Label) (_ context.Context, end func()) {
-	if isDisabled() {
-		return nil, func() {}
-	}
-	exporter, parent := fromContext(ctx)
-	b := exporter.Builder()
-	if b == nil {
-		return ctx, func() {}
-	}
-	b.Event.Parent = parent
-	span := b.WithAll(labels...).Deliver(StartKind, name)
-	ctx = newContext(ctx, exporter, span)
-	return ctx, func() {
-		eb := exporter.Builder()
-		eb.Event.Parent = span
-		eb.Deliver(EndKind, "")
-	}
-}
-
-// Deliver events to the underlying handler.
-// The event will be assigned a new ID before being delivered, and the new ID
-// will be returned.
+// prepare events before delivering to the underlying handler.
+// The event will be assigned a new ID.
 // If the event does not have a timestamp, and the exporter has a Now function
 // then the timestamp will be updated.
-func (e *Exporter) Deliver(ev *Event) uint64 {
-	if e == nil {
-		return 0
-	}
-	e.mu.Lock()
-	defer e.mu.Unlock()
+// prepare must be called with the export mutex held.
+func (e *Exporter) prepare(ev *Event) {
 	e.lastEvent++
 	id := e.lastEvent
 	ev.ID = id
 	if e.Now != nil && ev.At.IsZero() {
 		ev.At = e.Now()
 	}
-	e.handler.Handle(ev)
-	return id
-}
-
-func (defaultHandler) Handle(ev *Event) {
-	if ev.Kind != LogKind {
-		return
-	}
-	//TODO: split between stdout and stderr?
-	fmt.Fprintln(os.Stdout, ev)
 }
diff --git a/event/label.go b/event/label.go
index a6819da..736b3f5 100644
--- a/event/label.go
+++ b/event/label.go
@@ -8,7 +8,7 @@
 	"fmt"
 	"math"
 	"reflect"
-	"strings"
+	"strconv"
 	"unsafe"
 )
 
@@ -45,16 +45,6 @@
 // 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) {
-	newPrinter(f).Label(l)
-}
-
-// Format prints the value in a standard form.
-func (v *Value) Format(f fmt.State, verb rune) {
-	newPrinter(f).Value(v)
-}
-
 // HasValue returns true if the value is set to any type.
 func (v *Value) HasValue() bool { return v.untyped != nil }
 
@@ -124,10 +114,23 @@
 		hdr.Len = int(v.packed)
 		return s
 	}
-	// not a string, so invoke the formatter to build one
-	w := &strings.Builder{}
-	newPrinter(w).Value(&v)
-	return w.String()
+	// not a string, convert to one
+	switch {
+	case v.IsInt64():
+		return strconv.FormatInt(v.Int64(), 10)
+	case v.IsUint64():
+		return strconv.FormatUint(v.Uint64(), 10)
+	case v.IsFloat64():
+		return strconv.FormatFloat(v.Float64(), 'g', -1, 64)
+	case v.IsBool():
+		if v.Bool() {
+			return "true"
+		} else {
+			return "false"
+		}
+	default:
+		return fmt.Sprint(v.Interface())
+	}
 }
 
 // IsString returns true if the value was built with SetString.
diff --git a/event/severity/severity_test.go b/event/severity/severity_test.go
index 22662a8..d602f28 100644
--- a/event/severity/severity_test.go
+++ b/event/severity/severity_test.go
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
+// +build !disable_events
+
 package severity_test
 
 import (
@@ -10,7 +12,8 @@
 	"testing"
 
 	"golang.org/x/exp/event"
-	"golang.org/x/exp/event/eventtest"
+	"golang.org/x/exp/event/adapter/eventtest"
+	"golang.org/x/exp/event/adapter/logfmt"
 	"golang.org/x/exp/event/severity"
 )
 
@@ -31,7 +34,7 @@
 		expect: `time=2020-03-05T14:27:48 id=1 kind=log msg="a message" level=info`},
 	} {
 		buf := &strings.Builder{}
-		h := event.Printer(buf)
+		h := logfmt.Printer(buf)
 		e := event.NewExporter(h)
 		e.Now = eventtest.TestNow()
 		ctx := event.WithExporter(ctx, e)