blob: 5dc7f0291fa4abcfb63654cc9cbf94abdaebb1bb [file] [log] [blame]
// 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.
//go:build !disable_events
package event
import (
"context"
"sync"
"sync/atomic"
"time"
"unsafe"
)
// Exporter synchronizes the delivery of events to handlers.
type Exporter struct {
lastEvent uint64 // accessed using atomic, must be 64 bit aligned
opts ExporterOptions
mu sync.Mutex
handler Handler
sources sources
}
// target is a bound exporter.
// Normally you get a target by looking in the context using To.
type target struct {
exporter *Exporter
parent uint64
startTime time.Time // for trace latency
}
type ExporterOptions struct {
// If non-nil, sets zero Event.At on delivery.
Now func() time.Time
// Disable some event types, for better performance.
DisableLogging bool
DisableTracing bool
DisableAnnotations bool
DisableMetrics bool
// Enable automatically setting the event Namespace to the calling package's
// import path.
EnableNamespaces bool
}
// contextKeyType is used as the key for storing a contextValue on the context.
type contextKeyType struct{}
var contextKey interface{} = contextKeyType{}
var (
defaultTarget unsafe.Pointer
)
// NewExporter creates an Exporter using the supplied handler and options.
// Event delivery is serialized to enable safe atomic handling.
func NewExporter(handler Handler, opts *ExporterOptions) *Exporter {
if handler == nil {
panic("handler must not be nil")
}
e := &Exporter{
handler: handler,
sources: newCallers(),
}
if opts != nil {
e.opts = *opts
}
if e.opts.Now == nil {
e.opts.Now = time.Now
}
return e
}
func setDefaultExporter(e *Exporter) {
atomic.StorePointer(&defaultTarget, unsafe.Pointer(&target{exporter: e}))
}
func getDefaultTarget() *target {
return (*target)(atomic.LoadPointer(&defaultTarget))
}
func newContext(ctx context.Context, exporter *Exporter, parent uint64, start time.Time) context.Context {
var t *target
if exporter != nil {
t = &target{exporter: exporter, parent: parent, startTime: start}
}
return context.WithValue(ctx, contextKey, t)
}
// prepare events before delivering to the underlying handler.
// it is safe to call this more than once (trace events have to call it early)
// If the event does not have a timestamp, and the exporter has a Now function
// then the timestamp will be updated.
// If automatic namespaces are enabled and the event doesn't have a namespace,
// one based on the caller's import path will be provided.
func (ev *Event) prepare() {
e := ev.target.exporter
if ev.ID == 0 {
ev.ID = atomic.AddUint64(&e.lastEvent, 1)
}
if e.opts.Now != nil && ev.At.IsZero() {
ev.At = e.opts.Now()
}
if e.opts.EnableNamespaces && ev.Source.Space == "" {
ev.Source = e.sources.scanStack()
}
}
func (e *Exporter) loggingEnabled() bool { return !e.opts.DisableLogging }
func (e *Exporter) annotationsEnabled() bool { return !e.opts.DisableAnnotations }
func (e *Exporter) tracingEnabled() bool { return !e.opts.DisableTracing }
func (e *Exporter) metricsEnabled() bool { return !e.opts.DisableMetrics }