blob: 85e87294bf5aa2e9427a0f9b0a285d05aa4b287a [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.
// +build !disable_events
package event
import (
"context"
"runtime"
"strings"
"sync"
"sync/atomic"
"time"
"unsafe"
)
// Exporter synchronizes the delivery of events to handlers.
type Exporter struct {
opts ExporterOptions
mu sync.Mutex
handler Handler
lastEvent uint64
pcToNamespace map[uintptr]string
}
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
}
// contextKey is used as the key for storing a contextValue on the context.
type contextKeyType struct{}
var contextKey interface{} = contextKeyType{}
// contextValue is stored by value in the context to track the exporter and
// current parent event.
type contextValue struct {
exporter *Exporter
parent uint64
}
var (
defaultExporter 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 {
e := &Exporter{handler: handler}
if opts != nil {
e.opts = *opts
}
if e.opts.Now == nil {
e.opts.Now = time.Now
}
return e
}
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})
}
// FromContext returns the exporter and current trace for the supplied context.
func FromContext(ctx context.Context) (*Exporter, uint64) {
if v, ok := ctx.Value(contextKey).(contextValue); ok {
return v.exporter, v.parent
}
return getDefaultExporter(), 0
}
// 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.
// If automatic namespaces are enabled and the event doesn't have a namespace,
// one based on the caller's import path will be provided.
// prepare must be called with the export mutex held.
func (e *Exporter) prepare(ev *Event) {
if e.opts.Now != nil && ev.At.IsZero() {
ev.At = e.opts.Now()
}
if e.opts.EnableNamespaces && ev.Namespace == "" {
// Get the pc of the user function that delivered the event.
// This is sensitive to the call stack.
// 0: runtime.Callers
// 1: Exporter.prepare (this function)
// 2: Builder.{Start,End,etc.}
// 3: user function
var pcs [1]uintptr
runtime.Callers(3, pcs[:])
pc := pcs[0]
if pc != 0 {
ns, ok := e.pcToNamespace[pc]
if !ok {
// If we call runtime.CallersFrames(pcs[:1]) in this function, the
// compiler will think the pcs array escapes and will allocate.
f := callerFrameFunction(pc)
ns = namespace(f)
if e.pcToNamespace == nil {
e.pcToNamespace = map[uintptr]string{}
}
e.pcToNamespace[pc] = ns
}
ev.Namespace = ns
}
}
}
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 }
func callerFrameFunction(pc uintptr) string {
frame, _ := runtime.CallersFrames([]uintptr{pc}).Next()
return frame.Function
}
func namespace(funcPath string) string {
// Function is the fully-qualified function name. The name itself may
// have dots (for a closure, for instance), but it can't have slashes.
// So the package path ends at the first dot after the last slash.
i := strings.LastIndexByte(funcPath, '/')
if i < 0 {
i = 0
}
end := strings.IndexByte(funcPath[i:], '.')
if end >= 0 {
end += i
} else {
end = len(funcPath)
}
return funcPath[:end]
}