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)
+}