internal/telemetry: change Exporter to be a function type.

Change-Id: Id410da20310baf4da6875de08e4449c7a6fb0250
Reviewed-on: https://go-review.googlesource.com/c/tools/+/224277
Run-TryBot: Ian Cottrell <iancottrell@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Emmanuel Odeke <emm.odeke@gmail.com>
diff --git a/internal/lsp/debug/serve.go b/internal/lsp/debug/serve.go
index 7835740..11b731a 100644
--- a/internal/lsp/debug/serve.go
+++ b/internal/lsp/debug/serve.go
@@ -36,10 +36,6 @@
 	"golang.org/x/tools/internal/telemetry/export/prometheus"
 )
 
-type exporter struct {
-	stderr io.Writer
-}
-
 type instanceKeyType int
 
 const instanceKey = instanceKeyType(0)
@@ -387,9 +383,7 @@
 }
 
 func init() {
-	event.SetExporter(&exporter{
-		stderr: os.Stderr,
-	})
+	event.SetExporter(makeExporter(os.Stderr))
 }
 
 func GetInstance(ctx context.Context) *Instance {
@@ -545,31 +539,33 @@
 	return nil
 }
 
-func (e *exporter) ProcessEvent(ctx context.Context, ev event.Event) (context.Context, event.Event) {
-	ctx, ev = export.ContextSpan(ctx, ev)
-	i := GetInstance(ctx)
-	if ev.IsLog() && (ev.Error != nil || i == nil) {
-		fmt.Fprintf(e.stderr, "%v\n", ev)
-	}
-	ctx, ev = protocol.LogEvent(ctx, ev)
-	if i == nil {
+func makeExporter(stderr io.Writer) event.Exporter {
+	return func(ctx context.Context, ev event.Event) (context.Context, event.Event) {
+		ctx, ev = export.ContextSpan(ctx, ev)
+		i := GetInstance(ctx)
+		if ev.IsLog() && (ev.Error != nil || i == nil) {
+			fmt.Fprintf(stderr, "%v\n", ev)
+		}
+		ctx, ev = protocol.LogEvent(ctx, ev)
+		if i == nil {
+			return ctx, ev
+		}
+		ctx, ev = export.Tag(ctx, ev)
+		ctx, ev = i.metrics.ProcessEvent(ctx, ev)
+		if i.ocagent != nil {
+			ctx, ev = i.ocagent.ProcessEvent(ctx, ev)
+		}
+		if i.prometheus != nil {
+			ctx, ev = i.prometheus.ProcessEvent(ctx, ev)
+		}
+		if i.rpcs != nil {
+			ctx, ev = i.rpcs.ProcessEvent(ctx, ev)
+		}
+		if i.traces != nil {
+			ctx, ev = i.traces.ProcessEvent(ctx, ev)
+		}
 		return ctx, ev
 	}
-	ctx, ev = export.Tag(ctx, ev)
-	ctx, ev = i.metrics.ProcessEvent(ctx, ev)
-	if i.ocagent != nil {
-		ctx, ev = i.ocagent.ProcessEvent(ctx, ev)
-	}
-	if i.prometheus != nil {
-		ctx, ev = i.prometheus.ProcessEvent(ctx, ev)
-	}
-	if i.rpcs != nil {
-		ctx, ev = i.rpcs.ProcessEvent(ctx, ev)
-	}
-	if i.traces != nil {
-		ctx, ev = i.traces.ProcessEvent(ctx, ev)
-	}
-	return ctx, ev
 }
 
 type dataFunc func(*http.Request) interface{}
diff --git a/internal/telemetry/bench_test.go b/internal/telemetry/bench_test.go
index 0ebdc2c..2568304 100644
--- a/internal/telemetry/bench_test.go
+++ b/internal/telemetry/bench_test.go
@@ -95,7 +95,7 @@
 	b.Run("TraceNoExporter", Trace.runBenchmark)
 	b.Run("StatsNoExporter", Stats.runBenchmark)
 
-	event.SetExporter(newExporter())
+	event.SetExporter(noopExporter)
 	b.Run("Log", Log.runBenchmark)
 	b.Run("Trace", Trace.runBenchmark)
 	b.Run("Stats", Stats.runBenchmark)
@@ -142,17 +142,9 @@
 	return len(b), nil
 }
 
-type loggingExporter struct {
-	logger event.Exporter
-}
+var noopLogger = export.LogWriter(new(noopWriter), false)
 
-func newExporter() *loggingExporter {
-	return &loggingExporter{
-		logger: export.LogWriter(new(noopWriter), false),
-	}
-}
-
-func (e *loggingExporter) ProcessEvent(ctx context.Context, ev event.Event) (context.Context, event.Event) {
+func noopExporter(ctx context.Context, ev event.Event) (context.Context, event.Event) {
 	ctx, ev = export.ContextSpan(ctx, ev)
-	return e.logger.ProcessEvent(ctx, ev)
+	return noopLogger(ctx, ev)
 }
diff --git a/internal/telemetry/event/export.go b/internal/telemetry/event/export.go
index 073ebef..362ba29 100644
--- a/internal/telemetry/event/export.go
+++ b/internal/telemetry/event/export.go
@@ -10,36 +10,35 @@
 	"unsafe"
 )
 
-type Exporter interface {
-	// ProcessEvent is a function that handles all events.
-	// This is called with all events that should be delivered to the exporter
-	// along with the context in which that event ocurred.
-	// This method is called synchronously from the event call site, so it should
-	// return quickly so as not to hold up user code.
-	ProcessEvent(context.Context, Event) (context.Context, Event)
-}
+// Exporter is a function that handles events.
+// It may return a modified context and event.
+type Exporter func(context.Context, Event) (context.Context, Event)
 
 var (
 	exporter unsafe.Pointer
 )
 
+// SetExporter sets the global exporter function that handles all events.
+// The exporter is called synchronously from the event call site, so it should
+// return quickly so as not to hold up user code.
 func SetExporter(e Exporter) {
 	p := unsafe.Pointer(&e)
 	if e == nil {
 		// &e is always valid, and so p is always valid, but for the early abort
 		// of ProcessEvent to be efficient it needs to make the nil check on the
-		// pointer without having to dereference it, so we make the nil interface
+		// pointer without having to dereference it, so we make the nil function
 		// also a nil pointer
 		p = nil
 	}
 	atomic.StorePointer(&exporter, p)
 }
 
+// ProcessEvent is called to deliver an event to the global exporter.
 func ProcessEvent(ctx context.Context, ev Event) (context.Context, Event) {
 	exporterPtr := (*Exporter)(atomic.LoadPointer(&exporter))
 	if exporterPtr == nil {
 		return ctx, ev
 	}
 	// and now also hand the event of to the current exporter
-	return (*exporterPtr).ProcessEvent(ctx, ev)
+	return (*exporterPtr)(ctx, ev)
 }
diff --git a/internal/telemetry/export/log.go b/internal/telemetry/export/log.go
index bb74805..1f53542 100644
--- a/internal/telemetry/export/log.go
+++ b/internal/telemetry/export/log.go
@@ -8,21 +8,17 @@
 	"context"
 	"fmt"
 	"io"
-	"os"
 
 	"golang.org/x/tools/internal/telemetry/event"
 )
 
-func init() {
-	event.SetExporter(LogWriter(os.Stderr, true))
-}
-
 // LogWriter returns an Exporter that logs events to the supplied writer.
 // If onlyErrors is true it does not log any event that did not have an
 // associated error.
 // It ignores all telemetry other than log events.
 func LogWriter(w io.Writer, onlyErrors bool) event.Exporter {
-	return &logWriter{writer: w, onlyErrors: onlyErrors}
+	lw := &logWriter{writer: w, onlyErrors: onlyErrors}
+	return lw.ProcessEvent
 }
 
 type logWriter struct {
diff --git a/internal/telemetry/export/ocagent/ocagent_test.go b/internal/telemetry/export/ocagent/ocagent_test.go
index 329fc6a..ecd2612 100644
--- a/internal/telemetry/export/ocagent/ocagent_test.go
+++ b/internal/telemetry/export/ocagent/ocagent_test.go
@@ -116,11 +116,11 @@
 	metricBytesIn.Record(&exporter.metrics, bytesIn)
 	metricRecursiveCalls.SumInt64(&exporter.metrics, recursiveCalls)
 
-	event.SetExporter(exporter)
+	event.SetExporter(exporter.processEvent)
 	return exporter
 }
 
-func (e *testExporter) ProcessEvent(ctx context.Context, ev event.Event) (context.Context, event.Event) {
+func (e *testExporter) processEvent(ctx context.Context, ev event.Event) (context.Context, event.Event) {
 	switch {
 	case ev.IsStartSpan():
 		ev.At = e.start