blob: f90804f7696997db8140d0080385bc93179b36b2 [file] [log] [blame]
// Copyright 2020 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"
"fmt"
"sync"
"sync/atomic"
"time"
)
// Builder is a fluent builder for construction of new events.
type Builder struct {
builderCommon
}
type builderCommon struct {
ctx context.Context
data *builder
builderID uint64 // equals data.id if all is well
}
// preallocateLabels controls the space reserved for labels in a builder.
// Storing the first few labels directly in builders can avoid an allocation at
// all for the very common cases of simple events. The length needs to be large
// enough to cope with the majority of events but no so large as to cause undue
// stack pressure.
const preallocateLabels = 6
type builder struct {
exporter *Exporter
Event Event
labels [preallocateLabels]Label
id uint64
parentStart time.Time
}
var builderPool = sync.Pool{New: func() interface{} { return &builder{} }}
// To initializes a builder from the values stored in a context.
func To(ctx context.Context) Builder {
b := Builder{builderCommon{ctx: ctx}}
b.data = newBuilder(ctx)
if b.data != nil {
b.builderID = b.data.id
}
return b
}
var builderID uint64 // atomic
func newBuilder(ctx context.Context) *builder {
exporter, parent, parentStart := FromContext(ctx)
if exporter == nil {
return nil
}
b := allocBuilder()
b.exporter = exporter
b.parentStart = parentStart
b.Event.Labels = b.labels[:0]
b.Event.Parent = parent
return b
}
func allocBuilder() *builder {
b := builderPool.Get().(*builder)
b.id = atomic.AddUint64(&builderID, 1)
return b
}
// Clone returns a copy of this builder.
// The two copies can be independently delivered.
func (b Builder) Clone() Builder {
return Builder{b.clone()}
}
func (b builderCommon) clone() builderCommon {
if b.data == nil {
return b
}
bb := allocBuilder()
bbid := bb.id
clone := builderCommon{ctx: b.ctx, data: bb, builderID: bb.id}
*clone.data = *b.data
clone.data.id = bbid
if len(b.data.Event.Labels) == 0 || &b.data.labels[0] == &b.data.Event.Labels[0] {
clone.data.Event.Labels = clone.data.labels[:len(b.data.Event.Labels)]
} else {
clone.data.Event.Labels = make([]Label, len(b.data.Event.Labels))
copy(clone.data.Event.Labels, b.data.Event.Labels)
}
return clone
}
// With adds a new label to the event being constructed.
func (b Builder) With(label Label) Builder {
b.addLabel(label)
return b
}
func (b builderCommon) addLabel(label Label) {
if b.data != nil {
b.data.Event.Labels = append(b.data.Event.Labels, label)
checkValid(b.data, b.builderID)
}
}
// WithAll adds all the supplied labels to the event being constructed.
func (b Builder) WithAll(labels ...Label) Builder {
b.addLabels(labels)
return b
}
func (b builderCommon) addLabels(labels []Label) {
if b.data == nil || len(labels) == 0 {
return
}
checkValid(b.data, b.builderID)
if len(b.data.Event.Labels) == 0 {
b.data.Event.Labels = labels
} else {
b.data.Event.Labels = append(b.data.Event.Labels, labels...)
}
}
func (b Builder) At(t time.Time) Builder {
b.setAt(t)
return b
}
func (b builderCommon) setAt(t time.Time) {
if b.data != nil {
checkValid(b.data, b.builderID)
b.data.Event.At = t
}
}
func (b Builder) Namespace(ns string) Builder {
b.setNamespace(ns)
return b
}
func (b builderCommon) setNamespace(ns string) {
if b.data != nil {
checkValid(b.data, b.builderID)
b.data.Event.Namespace = ns
}
}
// Log is a helper that calls Deliver with LogKind.
func (b Builder) Log(message string) {
if b.data == nil {
return
}
checkValid(b.data, b.builderID)
if b.data.exporter.loggingEnabled() {
b.data.exporter.mu.Lock()
defer b.data.exporter.mu.Unlock()
b.data.Event.Labels = append(b.data.Event.Labels, Message.Of(message))
b.data.exporter.prepare(&b.data.Event)
b.data.exporter.handler.Log(b.ctx, &b.data.Event)
}
b.done()
}
// Logf is a helper that uses fmt.Sprint to build the message and then
// calls Deliver with LogKind.
func (b Builder) Logf(template string, args ...interface{}) {
if b.data == nil {
return
}
checkValid(b.data, b.builderID)
if b.data.exporter.loggingEnabled() {
message := fmt.Sprintf(template, args...)
// Duplicate code from Log so Exporter.deliver's invocation of runtime.Callers is correct.
b.data.exporter.mu.Lock()
defer b.data.exporter.mu.Unlock()
b.data.Event.Labels = append(b.data.Event.Labels, Message.Of(message))
b.data.exporter.prepare(&b.data.Event)
b.data.exporter.handler.Log(b.ctx, &b.data.Event)
}
b.done()
}
func (b Builder) Metric(mv MetricValue) {
if b.data == nil {
return
}
checkValid(b.data, b.builderID)
if b.data.exporter.metricsEnabled() {
b.data.exporter.mu.Lock()
defer b.data.exporter.mu.Unlock()
if b.data.Event.Namespace == "" {
b.data.Event.Namespace = mv.m.Descriptor().Namespace()
}
b.data.Event.Labels = append(b.data.Event.Labels, MetricVal.Of(mv.v), MetricKey.Of(mv.m))
b.data.exporter.prepare(&b.data.Event)
b.data.exporter.handler.Metric(b.ctx, &b.data.Event)
}
b.done()
}
// Annotate is a helper that calls Deliver with AnnotateKind.
func (b Builder) Annotate() {
if b.data == nil {
return
}
checkValid(b.data, b.builderID)
if b.data.exporter.annotationsEnabled() {
b.data.exporter.mu.Lock()
defer b.data.exporter.mu.Unlock()
b.data.exporter.prepare(&b.data.Event)
b.data.exporter.handler.Annotate(b.ctx, &b.data.Event)
}
b.done()
}
// End is a helper that calls Deliver with EndKind.
func (b Builder) End() {
if b.data == nil {
return
}
checkValid(b.data, b.builderID)
if b.data.exporter.tracingEnabled() {
// If there is a DurationMetric label, emit a Metric event
// with the time since Start was called.
if v, ok := DurationMetric.Find(&b.data.Event); ok {
m := v.(*Duration)
b.Clone().Metric(m.Record(time.Since(b.data.parentStart)))
}
b.data.exporter.mu.Lock()
defer b.data.exporter.mu.Unlock()
b.data.Event.Labels = append(b.data.Event.Labels, End.Value())
b.data.exporter.prepare(&b.data.Event)
b.data.exporter.handler.End(b.ctx, &b.data.Event)
}
b.done()
}
// Event returns a copy of the event currently being built.
func (b Builder) Event() *Event {
checkValid(b.data, b.builderID)
clone := b.data.Event
if len(b.data.Event.Labels) > 0 {
clone.Labels = make([]Label, len(b.data.Event.Labels))
copy(clone.Labels, b.data.Event.Labels)
}
return &clone
}
func (b builderCommon) done() {
*b.data = builder{}
builderPool.Put(b.data)
}
// Start delivers a start event with the given name and labels.
// Its second return value is a function that should be called to deliver the
// matching end event.
// All events created from the returned context will have this start event
// as their parent.
func (b Builder) Start(name string) (context.Context, func()) {
if b.data == nil {
return b.ctx, func() {}
}
checkValid(b.data, b.builderID)
ctx := b.ctx
end := func() {}
if b.data.exporter.tracingEnabled() {
b.data.exporter.mu.Lock()
defer b.data.exporter.mu.Unlock()
b.data.exporter.lastEvent++
traceID := b.data.exporter.lastEvent
// create the end builder
eb := b.Clone()
eb.data.Event.Parent = traceID
b.data.Event.Labels = append(b.data.Event.Labels, Trace.Of(traceID))
b.data.exporter.prepare(&b.data.Event)
// and now deliver the start event
b.data.Event.Labels = append(b.data.Event.Labels, Name.Of(name))
now := time.Now()
ctx = newContext(ctx, b.data.exporter, traceID, now)
ctx = b.data.exporter.handler.Start(ctx, &b.data.Event)
eb.data.parentStart = now
eb.ctx = ctx
end = eb.End
}
b.done()
return ctx, end
}
func checkValid(b *builder, wantID uint64) {
if b.exporter == nil || b.id != wantID {
panic("Builder already delivered an event; missing call to Clone")
}
}