internal/telemetry: use tags instead of special event fields.

Change-Id: I0e6a26c62bd1f6eaa07c38a06152cb7a0f2eedc2
Reviewed-on: https://go-review.googlesource.com/c/tools/+/225579
Run-TryBot: Ian Cottrell <iancottrell@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Emmanuel Odeke <emm.odeke@gmail.com>
Reviewed-by: Robert Findley <rfindley@google.com>
diff --git a/internal/lsp/debug/serve.go b/internal/lsp/debug/serve.go
index a4cafbc..2004716 100644
--- a/internal/lsp/debug/serve.go
+++ b/internal/lsp/debug/serve.go
@@ -543,7 +543,7 @@
 func makeGlobalExporter(stderr io.Writer) event.Exporter {
 	return func(ctx context.Context, ev event.Event, tags event.TagMap) context.Context {
 		i := GetInstance(ctx)
-		if ev.IsLog() && (ev.Error != nil || i == nil) {
+		if ev.IsLog() && (event.Err.Get(ev.Map()) != nil || i == nil) {
 			fmt.Fprintf(stderr, "%v\n", ev)
 		}
 		ctx = protocol.LogEvent(ctx, ev, tags)
diff --git a/internal/lsp/protocol/context.go b/internal/lsp/protocol/context.go
index a1b8822..560de4d 100644
--- a/internal/lsp/protocol/context.go
+++ b/internal/lsp/protocol/context.go
@@ -27,7 +27,7 @@
 		return ctx
 	}
 	msg := &LogMessageParams{Type: Info, Message: fmt.Sprint(ev)}
-	if ev.Error != nil {
+	if event.Err.Get(tags) != nil {
 		msg.Type = Error
 	}
 	go client.LogMessage(xcontext.Detach(ctx), msg)
diff --git a/internal/telemetry/event/event.go b/internal/telemetry/event/event.go
index 3f222c4..23329cb 100644
--- a/internal/telemetry/event/event.go
+++ b/internal/telemetry/event/event.go
@@ -13,21 +13,32 @@
 type eventType uint8
 
 const (
-	LogType = eventType(iota)
-	StartSpanType
-	EndSpanType
-	LabelType
-	DetachType
-	RecordType
+	invalidType   = eventType(iota)
+	LogType       // an event that should be recorded in a log
+	StartSpanType // the start of a span of time
+	EndSpanType   // the end of a span of time
+	LabelType     // some values that should be noted for later events
+	DetachType    // an event that causes a context to detach
+	RecordType    // a value that should be tracked
 )
 
-type Event struct {
-	typ     eventType
-	At      time.Time
-	Message string
-	Error   error
+// sTags is used to hold a small number of tags inside an event whichout
+// requiring a separate allocation.
+// As tags are often on the stack, this avoids 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.
+// A log message with two values will use 3 tags (one for each value and
+// one for the message itself).
+type sTags [3]Tag
 
-	tags []Tag
+// Event holds the information about an event of note that ocurred.
+type Event struct {
+	At time.Time
+
+	typ     eventType
+	static  sTags // inline storage for the first few tags
+	dynamic []Tag // dynamically sized storage for remaining tags
 }
 
 func (ev Event) IsLog() bool       { return ev.typ == LogType }
@@ -38,15 +49,18 @@
 func (ev Event) IsRecord() bool    { return ev.typ == RecordType }
 
 func (ev Event) Format(f fmt.State, r rune) {
+	tagMap := ev.Map()
 	if !ev.At.IsZero() {
 		fmt.Fprint(f, ev.At.Format("2006/01/02 15:04:05 "))
 	}
-	fmt.Fprint(f, ev.Message)
-	if ev.Error != nil {
+	msg := Msg.Get(tagMap)
+	err := Err.Get(tagMap)
+	fmt.Fprint(f, msg)
+	if err != nil {
 		if f.Flag('+') {
-			fmt.Fprintf(f, ": %+v", ev.Error)
+			fmt.Fprintf(f, ": %+v", err)
 		} else {
-			fmt.Fprintf(f, ": %v", ev.Error)
+			fmt.Fprintf(f, ": %v", err)
 		}
 	}
 	for it := ev.Tags(); it.Valid(); it.Advance() {
@@ -56,12 +70,21 @@
 }
 
 func (ev Event) Tags() TagIterator {
-	if len(ev.tags) == 0 {
-		return TagIterator{}
-	}
-	return NewTagIterator(ev.tags...)
+	return ChainTagIterators(
+		NewTagIterator(ev.static[:]...),
+		NewTagIterator(ev.dynamic...))
 }
 
 func (ev Event) Map() TagMap {
-	return NewTagMap(ev.tags...)
+	return MergeTagMaps(
+		NewTagMap(ev.static[:]...),
+		NewTagMap(ev.dynamic...))
+}
+
+func makeEvent(typ eventType, static sTags, tags []Tag) Event {
+	return Event{
+		typ:     typ,
+		static:  static,
+		dynamic: tags,
+	}
 }
diff --git a/internal/telemetry/event/key.go b/internal/telemetry/event/key.go
index fddabcd..97572c3 100644
--- a/internal/telemetry/event/key.go
+++ b/internal/telemetry/event/key.go
@@ -4,11 +4,17 @@
 
 package event
 
-import "math"
+import (
+	"math"
+)
 
 var (
+	// Msg is a key used to add message strings to tag lists.
+	Msg = NewStringKey("message", "a readable message")
+	// Name is used for things like traces that have a name.
+	Name = NewStringKey("name", "an entity name")
 	// Err is a key used to add error values to tag lists.
-	Err = NewErrorKey("error", "")
+	Err = NewErrorKey("error", "an error that occurred")
 )
 
 // Key is the interface shared by all key implementations.
@@ -487,4 +493,7 @@
 }
 
 // From can be used to get a value from a Tag.
-func (k *ErrorKey) From(t Tag) error { return t.untyped.(error) }
+func (k *ErrorKey) From(t Tag) error {
+	err, _ := t.untyped.(error)
+	return err
+}
diff --git a/internal/telemetry/event/label.go b/internal/telemetry/event/label.go
index 9536ed2..ccdaba5 100644
--- a/internal/telemetry/event/label.go
+++ b/internal/telemetry/event/label.go
@@ -10,8 +10,5 @@
 
 // Label sends a label event to the exporter with the supplied tags.
 func Label(ctx context.Context, tags ...Tag) context.Context {
-	return dispatch(ctx, Event{
-		typ:  LabelType,
-		tags: tags,
-	})
+	return dispatch(ctx, makeEvent(LabelType, sTags{}, tags))
 }
diff --git a/internal/telemetry/event/log.go b/internal/telemetry/event/log.go
index 3367f9b..b989032 100644
--- a/internal/telemetry/event/log.go
+++ b/internal/telemetry/event/log.go
@@ -11,20 +11,13 @@
 
 // Log sends a log event with the supplied tag list to the exporter.
 func Log(ctx context.Context, tags ...Tag) {
-	dispatch(ctx, Event{
-		typ:  LogType,
-		tags: tags,
-	})
+	dispatch(ctx, makeEvent(LogType, sTags{}, tags))
 }
 
 // Print takes a message and a tag list and combines them into a single event
 // before delivering them to the exporter.
 func Print(ctx context.Context, message string, tags ...Tag) {
-	dispatch(ctx, Event{
-		typ:     LogType,
-		Message: message,
-		tags:    tags,
-	})
+	dispatch(ctx, makeEvent(LogType, sTags{Msg.Of(message)}, tags))
 }
 
 // Error takes a message and a tag list and combines them into a single event
@@ -35,10 +28,5 @@
 		err = errors.New(message)
 		message = ""
 	}
-	dispatch(ctx, Event{
-		typ:     LogType,
-		Message: message,
-		Error:   err,
-		tags:    tags,
-	})
+	dispatch(ctx, makeEvent(LogType, sTags{Msg.Of(message), Err.Of(err)}, tags))
 }
diff --git a/internal/telemetry/event/metric.go b/internal/telemetry/event/metric.go
index 48f030e..b00e870 100644
--- a/internal/telemetry/event/metric.go
+++ b/internal/telemetry/event/metric.go
@@ -9,8 +9,5 @@
 )
 
 func Record(ctx context.Context, tags ...Tag) {
-	dispatch(ctx, Event{
-		typ:  RecordType,
-		tags: tags,
-	})
+	dispatch(ctx, makeEvent(RecordType, sTags{}, tags))
 }
diff --git a/internal/telemetry/event/trace.go b/internal/telemetry/event/trace.go
index 85cdd53..0331aae 100644
--- a/internal/telemetry/event/trace.go
+++ b/internal/telemetry/event/trace.go
@@ -9,18 +9,12 @@
 )
 
 func StartSpan(ctx context.Context, name string, tags ...Tag) (context.Context, func()) {
-	ctx = dispatch(ctx, Event{
-		typ:     StartSpanType,
-		Message: name,
-		tags:    tags,
-	})
-	return ctx, func() {
-		dispatch(ctx, Event{typ: EndSpanType})
-	}
+	ctx = dispatch(ctx, makeEvent(StartSpanType, sTags{Name.Of(name)}, tags))
+	return ctx, func() { dispatch(ctx, makeEvent(EndSpanType, sTags{}, nil)) }
 }
 
 // Detach returns a context without an associated span.
 // This allows the creation of spans that are not children of the current span.
 func Detach(ctx context.Context) context.Context {
-	return dispatch(ctx, Event{typ: DetachType})
+	return dispatch(ctx, makeEvent(DetachType, sTags{}, nil))
 }
diff --git a/internal/telemetry/export/log.go b/internal/telemetry/export/log.go
index 5d2bc96..2458022 100644
--- a/internal/telemetry/export/log.go
+++ b/internal/telemetry/export/log.go
@@ -29,7 +29,7 @@
 func (w *logWriter) ProcessEvent(ctx context.Context, ev event.Event, tagMap event.TagMap) context.Context {
 	switch {
 	case ev.IsLog():
-		if w.onlyErrors && ev.Error == nil {
+		if w.onlyErrors && event.Err.Get(tagMap) == nil {
 			return ctx
 		}
 		fmt.Fprintf(w.writer, "%v\n", ev)
diff --git a/internal/telemetry/export/ocagent/ocagent.go b/internal/telemetry/export/ocagent/ocagent.go
index 86c2e8b..4c423eb 100644
--- a/internal/telemetry/export/ocagent/ocagent.go
+++ b/internal/telemetry/export/ocagent/ocagent.go
@@ -200,7 +200,7 @@
 		Kind:                    wire.UnspecifiedSpanKind,
 		StartTime:               convertTimestamp(span.Start.At),
 		EndTime:                 convertTimestamp(span.Finish.At),
-		Attributes:              convertAttributes(span.Start.Tags()),
+		Attributes:              convertAttributes(event.Filter(span.Start.Tags(), event.Name)),
 		TimeEvents:              convertEvents(span.Events),
 		SameProcessAsParentSpan: true,
 		//TODO: StackTrace?
@@ -295,19 +295,20 @@
 }
 
 func convertAnnotation(ev event.Event) *wire.Annotation {
-	description := ev.Message
-	if description == "" && ev.Error != nil {
-		description = ev.Error.Error()
-		ev.Error = nil
-	}
 	tags := ev.Tags()
-	if ev.Error != nil {
-		extra := event.NewTagIterator(event.Err.Of(ev.Error))
-		tags = event.ChainTagIterators(extra, tags)
-	}
-	if description == "" && !tags.Valid() {
+	if !tags.Valid() {
 		return nil
 	}
+	tagMap := ev.Map()
+	description := event.Msg.Get(tagMap)
+	tags = event.Filter(tags, event.Msg)
+	if description == "" {
+		err := event.Err.Get(tagMap)
+		tags = event.Filter(tags, event.Err)
+		if err != nil {
+			description = err.Error()
+		}
+	}
 	return &wire.Annotation{
 		Description: toTruncatableString(description),
 		Attributes:  convertAttributes(tags),
diff --git a/internal/telemetry/export/trace.go b/internal/telemetry/export/trace.go
index 3894b85..ffe86ad 100644
--- a/internal/telemetry/export/trace.go
+++ b/internal/telemetry/export/trace.go
@@ -54,7 +54,7 @@
 			}
 		case ev.IsStartSpan():
 			span := &Span{
-				Name:  ev.Message,
+				Name:  event.Name.Get(tagMap),
 				Start: ev,
 			}
 			if parent := GetSpan(ctx); parent != nil {