event: adding a global default exporter

Change-Id: Ibf0f051d7e07f4fad5f04e7d93eaf6b0b01f98a2
Reviewed-on: https://go-review.googlesource.com/c/exp/+/313989
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/bench/event_test.go b/event/bench/event_test.go
index a839ace..d98562b 100644
--- a/event/bench/event_test.go
+++ b/event/bench/event_test.go
@@ -83,15 +83,8 @@
 	}
 )
 
-func eventDisabled() context.Context {
-	event.Disable()
-	return context.Background()
-}
-
 func eventNoExporter() context.Context {
-	// register an exporter to turn the system on, but not in this context
-	event.NewExporter(noopHandler{})
-	return context.Background()
+	return event.WithExporter(context.Background(), nil)
 }
 
 func eventNoop() context.Context {
@@ -107,7 +100,9 @@
 }
 
 func BenchmarkLogEventDisabled(b *testing.B) {
-	runBenchmark(b, eventDisabled(), eventLog)
+	event.SetEnabled(false)
+	defer event.SetEnabled(true)
+	runBenchmark(b, context.Background(), eventLog)
 }
 
 func BenchmarkLogEventNoExporter(b *testing.B) {
diff --git a/event/export.go b/event/export.go
index 7e5f135..87c7c41 100644
--- a/event/export.go
+++ b/event/export.go
@@ -6,9 +6,12 @@
 
 import (
 	"context"
+	"fmt"
+	"os"
 	"sync"
 	"sync/atomic"
 	"time"
+	"unsafe"
 )
 
 // Handler is a the type for something that handles events as they occur.
@@ -27,7 +30,9 @@
 }
 
 // contextKey is used as the key for storing a contextValue on the context.
-type contextKey struct{}
+type contextKeyType struct{}
+
+var contextKey interface{} = contextKeyType{}
 
 // contextValue is stored by value in the context to track the exporter and
 // current parent event.
@@ -36,36 +41,70 @@
 	parent   uint64
 }
 
+type defaultHandler struct{}
+
 var (
-	activeExporters int32 // used atomically to shortcut the entire system
+	enabled         int32 = 1
+	defaultExporter       = unsafe.Pointer(&Exporter{
+		Now:     time.Now,
+		handler: defaultHandler{},
+	})
 )
 
 // 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 {
-	atomic.StoreInt32(&activeExporters, 1)
 	return &Exporter{
 		Now:     time.Now,
 		handler: h,
 	}
 }
 
+// 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))
+}
+
+func getDefaultExporter() *Exporter {
+	return (*Exporter)(atomic.LoadPointer(&defaultExporter))
+}
+
+func newContext(ctx context.Context, exporter *Exporter, parent uint64) context.Context {
+	return context.WithValue(ctx, contextKey, contextValue{exporter: exporter, parent: parent})
+}
+
+func fromContext(ctx context.Context) (*Exporter, uint64) {
+	if v, ok := ctx.Value(contextKey).(contextValue); ok {
+		return v.exporter, v.parent
+	}
+	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 {
-	atomic.StoreInt32(&activeExporters, 1)
-	return context.WithValue(ctx, contextKey{}, contextValue{exporter: e})
-}
-
-// Disable turns off the exporters, until the next WithExporter call.
-func Disable() {
-	atomic.StoreInt32(&activeExporters, 0)
+	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]
@@ -74,15 +113,14 @@
 
 // To initializes a builder from the values stored in a context.
 func To(ctx context.Context) *Builder {
-	if atomic.LoadInt32(&activeExporters) == 0 {
+	if isDisabled() {
 		return nil
 	}
-	v, ok := ctx.Value(contextKey{}).(contextValue)
-	if !ok || v.exporter == nil {
-		return nil
+	exporter, parent := fromContext(ctx)
+	b := exporter.Builder()
+	if b != nil {
+		b.Event.Parent = parent
 	}
-	b := v.exporter.Builder()
-	b.Event.Parent = v.parent
 	return b
 }
 
@@ -92,15 +130,20 @@
 // 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()) {
-	b := To(ctx)
-	if b == nil || b.exporter == nil {
+	if isDisabled() {
+		return nil, func() {}
+	}
+	exporter, parent := fromContext(ctx)
+	b := exporter.Builder()
+	if b == nil {
 		return ctx, func() {}
 	}
-	v := contextValue{exporter: b.exporter}
-	v.parent = b.WithAll(labels...).Deliver(StartKind, name)
-	return context.WithValue(ctx, contextKey{}, v), func() {
-		eb := v.exporter.Builder()
-		eb.Event.Parent = v.parent
+	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, "")
 	}
 }
@@ -111,6 +154,9 @@
 // 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()
 	e.lastEvent++
@@ -122,3 +168,11 @@
 	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)
+}