1
0
mirror of https://github.com/golang/go synced 2024-11-18 19:34:41 -07:00

internal/telemetry: allow ProcessEvent to modify the event

This allows early exporters to adjust the event for later ones.
This is used to lookup key values from the context if needed.
Also add a Query type event which is intended to perform all event
modifications but nothing else, and is used to lookup values from
the context. This cleans up a weirdness where the current lookup
presumes there will be an exporter with a matching mechanism.

Change-Id: I835d1e0b2511553c30f94b7becfe7b7b5462c111
Reviewed-on: https://go-review.googlesource.com/c/tools/+/223657
Run-TryBot: Ian Cottrell <iancottrell@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Emmanuel Odeke <emm.odeke@gmail.com>
This commit is contained in:
Ian Cottrell 2020-03-16 10:01:57 -04:00
parent 9fceb114c3
commit cb106d260e
17 changed files with 75 additions and 70 deletions

View File

@ -542,24 +542,24 @@ func (i *Instance) writeMemoryDebug(threshold uint64) error {
return nil return nil
} }
func (e *exporter) ProcessEvent(ctx context.Context, ev event.Event) context.Context { func (e *exporter) ProcessEvent(ctx context.Context, ev event.Event) (context.Context, event.Event) {
ctx = export.ContextSpan(ctx, ev) ctx, ev = export.ContextSpan(ctx, ev)
i := GetInstance(ctx) i := GetInstance(ctx)
if ev.IsLog() && (ev.Error != nil || i == nil) { if ev.IsLog() && (ev.Error != nil || i == nil) {
fmt.Fprintf(e.stderr, "%v\n", ev) fmt.Fprintf(e.stderr, "%v\n", ev)
} }
ctx = protocol.LogEvent(ctx, ev) ctx, ev = protocol.LogEvent(ctx, ev)
if i == nil { if i == nil {
return ctx return ctx, ev
} }
ctx = export.Tag(ctx, ev) ctx, ev = export.Tag(ctx, ev)
if i.ocagent != nil { if i.ocagent != nil {
ctx = i.ocagent.ProcessEvent(ctx, ev) ctx, ev = i.ocagent.ProcessEvent(ctx, ev)
} }
if i.traces != nil { if i.traces != nil {
ctx = i.traces.ProcessEvent(ctx, ev) ctx, ev = i.traces.ProcessEvent(ctx, ev)
} }
return ctx return ctx, ev
} }
func (e *exporter) Metric(ctx context.Context, data event.MetricData) { func (e *exporter) Metric(ctx context.Context, data event.MetricData) {

View File

@ -73,12 +73,12 @@ type traceEvent struct {
Tags string Tags string
} }
func (t *traces) ProcessEvent(ctx context.Context, ev event.Event) context.Context { func (t *traces) ProcessEvent(ctx context.Context, ev event.Event) (context.Context, event.Event) {
t.mu.Lock() t.mu.Lock()
defer t.mu.Unlock() defer t.mu.Unlock()
span := export.GetSpan(ctx) span := export.GetSpan(ctx)
if span == nil { if span == nil {
return ctx return ctx, ev
} }
switch { switch {
@ -99,13 +99,13 @@ func (t *traces) ProcessEvent(ctx context.Context, ev event.Event) context.Conte
t.unfinished[span.ID] = td t.unfinished[span.ID] = td
// and wire up parents if we have them // and wire up parents if we have them
if !span.ParentID.IsValid() { if !span.ParentID.IsValid() {
return ctx return ctx, ev
} }
parentID := export.SpanContext{TraceID: span.ID.TraceID, SpanID: span.ParentID} parentID := export.SpanContext{TraceID: span.ID.TraceID, SpanID: span.ParentID}
parent, found := t.unfinished[parentID] parent, found := t.unfinished[parentID]
if !found { if !found {
// trace had an invalid parent, so it cannot itself be valid // trace had an invalid parent, so it cannot itself be valid
return ctx return ctx, ev
} }
parent.Children = append(parent.Children, td) parent.Children = append(parent.Children, td)
@ -113,7 +113,7 @@ func (t *traces) ProcessEvent(ctx context.Context, ev event.Event) context.Conte
// finishing, must be already in the map // finishing, must be already in the map
td, found := t.unfinished[span.ID] td, found := t.unfinished[span.ID]
if !found { if !found {
return ctx // if this happens we are in a bad place return ctx, ev // if this happens we are in a bad place
} }
delete(t.unfinished, span.ID) delete(t.unfinished, span.ID)
@ -140,7 +140,7 @@ func (t *traces) ProcessEvent(ctx context.Context, ev event.Event) context.Conte
fillOffsets(td, td.Start) fillOffsets(td, td.Start)
} }
} }
return ctx return ctx, ev
} }
func (t *traces) getData(req *http.Request) interface{} { func (t *traces) getData(req *http.Request) interface{} {

View File

@ -201,7 +201,7 @@ func (s *Server) publishReports(ctx context.Context, snapshot source.Snapshot, r
Version: key.id.Version, Version: key.id.Version,
}); err != nil { }); err != nil {
if ctx.Err() == nil { if ctx.Err() == nil {
event.Error(ctx, "publishReports: failed to deliver diagnostic", err, telemetry.File.From(ctx)) event.Error(ctx, "publishReports: failed to deliver diagnostic", err, telemetry.URI.Of(key.id.URI))
} }
continue continue
} }

View File

@ -18,18 +18,18 @@ func WithClient(ctx context.Context, client Client) context.Context {
return context.WithValue(ctx, clientKey, client) return context.WithValue(ctx, clientKey, client)
} }
func LogEvent(ctx context.Context, ev event.Event) context.Context { func LogEvent(ctx context.Context, ev event.Event) (context.Context, event.Event) {
if !ev.IsLog() { if !ev.IsLog() {
return ctx return ctx, ev
} }
client, ok := ctx.Value(clientKey).(Client) client, ok := ctx.Value(clientKey).(Client)
if !ok { if !ok {
return ctx return ctx, ev
} }
msg := &LogMessageParams{Type: Info, Message: fmt.Sprint(ev)} msg := &LogMessageParams{Type: Info, Message: fmt.Sprint(ev)}
if ev.Error != nil { if ev.Error != nil {
msg.Type = Error msg.Type = Error
} }
go client.LogMessage(xcontext.Detach(ctx), msg) go client.LogMessage(xcontext.Detach(ctx), msg)
return ctx return ctx, ev
} }

View File

@ -154,8 +154,8 @@ func newExporter() *loggingExporter {
} }
} }
func (e *loggingExporter) ProcessEvent(ctx context.Context, ev event.Event) context.Context { func (e *loggingExporter) ProcessEvent(ctx context.Context, ev event.Event) (context.Context, event.Event) {
export.ContextSpan(ctx, ev) ctx, ev = export.ContextSpan(ctx, ev)
return e.logger.ProcessEvent(ctx, ev) return e.logger.ProcessEvent(ctx, ev)
} }

View File

@ -17,6 +17,7 @@ const (
StartSpanType StartSpanType
EndSpanType EndSpanType
LabelType LabelType
QueryType
DetachType DetachType
) )
@ -31,7 +32,8 @@ type Event struct {
func (e Event) IsLog() bool { return e.Type == LogType } func (e Event) IsLog() bool { return e.Type == LogType }
func (e Event) IsEndSpan() bool { return e.Type == EndSpanType } func (e Event) IsEndSpan() bool { return e.Type == EndSpanType }
func (e Event) IsStartSpan() bool { return e.Type == StartSpanType } func (e Event) IsStartSpan() bool { return e.Type == StartSpanType }
func (e Event) IsTag() bool { return e.Type == LabelType } func (e Event) IsLabel() bool { return e.Type == LabelType }
func (e Event) IsQuery() bool { return e.Type == QueryType }
func (e Event) IsDetach() bool { return e.Type == DetachType } func (e Event) IsDetach() bool { return e.Type == DetachType }
func (e Event) Format(f fmt.State, r rune) { func (e Event) Format(f fmt.State, r rune) {

View File

@ -16,7 +16,7 @@ type Exporter interface {
// along with the context in which that event ocurred. // along with the context in which that event ocurred.
// This method is called synchronously from the event call site, so it should // This method is called synchronously from the event call site, so it should
// return quickly so as not to hold up user code. // return quickly so as not to hold up user code.
ProcessEvent(context.Context, Event) context.Context ProcessEvent(context.Context, Event) (context.Context, Event)
Metric(context.Context, MetricData) Metric(context.Context, MetricData)
} }
@ -37,13 +37,13 @@ func SetExporter(e Exporter) {
atomic.StorePointer(&exporter, p) atomic.StorePointer(&exporter, p)
} }
func ProcessEvent(ctx context.Context, event Event) context.Context { func ProcessEvent(ctx context.Context, ev Event) (context.Context, Event) {
exporterPtr := (*Exporter)(atomic.LoadPointer(&exporter)) exporterPtr := (*Exporter)(atomic.LoadPointer(&exporter))
if exporterPtr == nil { if exporterPtr == nil {
return ctx return ctx, ev
} }
// and now also hand the event of to the current exporter // and now also hand the event of to the current exporter
return (*exporterPtr).ProcessEvent(ctx, event) return (*exporterPtr).ProcessEvent(ctx, ev)
} }
func Metric(ctx context.Context, data MetricData) { func Metric(ctx context.Context, data MetricData) {

View File

@ -4,10 +4,6 @@
package event package event
import (
"context"
)
// Key is used as the identity of a Tag. // Key is used as the identity of a Tag.
// Keys are intended to be compared by pointer only, the name should be unique // Keys are intended to be compared by pointer only, the name should be unique
// for communicating with external systems, but it is not required or enforced. // for communicating with external systems, but it is not required or enforced.
@ -27,13 +23,3 @@ func TagOf(name string, value interface{}) Tag {
func (k *Key) Of(v interface{}) Tag { func (k *Key) Of(v interface{}) Tag {
return Tag{Key: k, Value: v} return Tag{Key: k, Value: v}
} }
// From can be used to get a tag for the key from a context.
func (k *Key) From(ctx context.Context) Tag {
return Tag{Key: k, Value: ctx.Value(k)}
}
// With is a wrapper over the Label package level function for just this key.
func (k *Key) With(ctx context.Context, v interface{}) context.Context {
return Label(ctx, Tag{Key: k, Value: v})
}

View File

@ -11,9 +11,24 @@ import (
// Label sends a label event to the exporter with the supplied tags. // Label sends a label event to the exporter with the supplied tags.
func Label(ctx context.Context, tags ...Tag) context.Context { func Label(ctx context.Context, tags ...Tag) context.Context {
return ProcessEvent(ctx, Event{ ctx, _ = ProcessEvent(ctx, Event{
Type: LabelType, Type: LabelType,
At: time.Now(), At: time.Now(),
Tags: tags, Tags: tags,
}) })
return ctx
}
// Query sends a query event to the exporter with the supplied keys.
// The returned tags will have up to date values if the exporter supports it.
func Query(ctx context.Context, keys ...*Key) TagList {
tags := make(TagList, len(keys))
for i, k := range keys {
tags[i].Key = k
}
_, ev := ProcessEvent(ctx, Event{
Type: QueryType,
Tags: tags,
})
return ev.Tags
} }

View File

@ -5,7 +5,6 @@
package event package event
import ( import (
"context"
"fmt" "fmt"
) )
@ -26,15 +25,6 @@ func (t Tag) Format(f fmt.State, r rune) {
fmt.Fprintf(f, `%v="%v"`, t.Key.Name, t.Value) fmt.Fprintf(f, `%v="%v"`, t.Key.Name, t.Value)
} }
// Tags collects a set of values from the context and returns them as a tag list.
func Tags(ctx context.Context, keys ...*Key) TagList {
tags := make(TagList, len(keys))
for i, key := range keys {
tags[i] = Tag{Key: key, Value: ctx.Value(key)}
}
return tags
}
// Get will get a single key's value from the list. // Get will get a single key's value from the list.
func (l TagList) Get(k interface{}) interface{} { func (l TagList) Get(k interface{}) interface{} {
for _, t := range l { for _, t := range l {

View File

@ -10,7 +10,7 @@ import (
) )
func StartSpan(ctx context.Context, name string, tags ...Tag) (context.Context, func()) { func StartSpan(ctx context.Context, name string, tags ...Tag) (context.Context, func()) {
ctx = ProcessEvent(ctx, Event{ ctx, _ = ProcessEvent(ctx, Event{
Type: StartSpanType, Type: StartSpanType,
Message: name, Message: name,
At: time.Now(), At: time.Now(),
@ -27,8 +27,9 @@ func StartSpan(ctx context.Context, name string, tags ...Tag) (context.Context,
// Detach returns a context without an associated span. // Detach returns a context without an associated span.
// This allows the creation of spans that are not children of the current span. // This allows the creation of spans that are not children of the current span.
func Detach(ctx context.Context) context.Context { func Detach(ctx context.Context) context.Context {
return ProcessEvent(ctx, Event{ ctx, _ = ProcessEvent(ctx, Event{
Type: DetachType, Type: DetachType,
At: time.Now(), At: time.Now(),
}) })
return ctx
} }

View File

@ -30,11 +30,11 @@ type logWriter struct {
onlyErrors bool onlyErrors bool
} }
func (w *logWriter) ProcessEvent(ctx context.Context, ev event.Event) context.Context { func (w *logWriter) ProcessEvent(ctx context.Context, ev event.Event) (context.Context, event.Event) {
switch { switch {
case ev.IsLog(): case ev.IsLog():
if w.onlyErrors && ev.Error == nil { if w.onlyErrors && ev.Error == nil {
return ctx return ctx, ev
} }
fmt.Fprintf(w.writer, "%v\n", ev) fmt.Fprintf(w.writer, "%v\n", ev)
case ev.IsStartSpan(): case ev.IsStartSpan():
@ -49,7 +49,7 @@ func (w *logWriter) ProcessEvent(ctx context.Context, ev event.Event) context.Co
fmt.Fprintf(w.writer, "finish: %v %v", span.Name, span.ID) fmt.Fprintf(w.writer, "finish: %v %v", span.Name, span.ID)
} }
} }
return ctx return ctx, ev
} }
func (w *logWriter) Metric(context.Context, event.MetricData) {} func (w *logWriter) Metric(context.Context, event.MetricData) {}

View File

@ -84,9 +84,9 @@ func Connect(config *Config) *Exporter {
return exporter return exporter
} }
func (e *Exporter) ProcessEvent(ctx context.Context, ev event.Event) context.Context { func (e *Exporter) ProcessEvent(ctx context.Context, ev event.Event) (context.Context, event.Event) {
if !ev.IsEndSpan() { if !ev.IsEndSpan() {
return ctx return ctx, ev
} }
e.mu.Lock() e.mu.Lock()
defer e.mu.Unlock() defer e.mu.Unlock()
@ -94,7 +94,7 @@ func (e *Exporter) ProcessEvent(ctx context.Context, ev event.Event) context.Con
if span != nil { if span != nil {
e.spans = append(e.spans, span) e.spans = append(e.spans, span)
} }
return ctx return ctx, ev
} }
func (e *Exporter) Metric(ctx context.Context, data event.MetricData) { func (e *Exporter) Metric(ctx context.Context, data event.MetricData) {

View File

@ -179,7 +179,7 @@ func TestTrace(t *testing.T) {
Type: event.EndSpanType, Type: event.EndSpanType,
At: end, At: end,
} }
ctx := export.ContextSpan(ctx, startEvent) ctx, _ := export.ContextSpan(ctx, startEvent)
span := export.GetSpan(ctx) span := export.GetSpan(ctx)
span.ID = export.SpanContext{} span.ID = export.SpanContext{}
span.Events = []event.Event{tt.event(ctx)} span.Events = []event.Event{tt.event(ctx)}

View File

@ -10,15 +10,26 @@ import (
"golang.org/x/tools/internal/telemetry/event" "golang.org/x/tools/internal/telemetry/event"
) )
// Tag returns a context updated with tag values from the event. // Tag manipulates the context using the event.
// It ignores events that are not or type IsTag or IsStartSpan. // If the event is type IsTag or IsStartSpan then it returns a context updated
func Tag(ctx context.Context, ev event.Event) context.Context { // with tag values from the event.
// For all other event types the event tags will be updated with values from the
// context if they are missing.
func Tag(ctx context.Context, ev event.Event) (context.Context, event.Event) {
//TODO: Do we need to do something more efficient than just store tags //TODO: Do we need to do something more efficient than just store tags
//TODO: directly on the context? //TODO: directly on the context?
if ev.IsTag() || ev.IsStartSpan() { switch {
case ev.IsLabel(), ev.IsStartSpan():
for _, t := range ev.Tags { for _, t := range ev.Tags {
ctx = context.WithValue(ctx, t.Key, t.Value) ctx = context.WithValue(ctx, t.Key, t.Value)
} }
default:
// all other types want the tags filled in if needed
for i := range ev.Tags {
if ev.Tags[i].Value == nil {
ev.Tags[i].Value = ctx.Value(ev.Tags[i].Key)
} }
return ctx }
}
return ctx, ev
} }

View File

@ -46,9 +46,9 @@ func GetSpan(ctx context.Context) *Span {
// It creates new spans on EventStartSpan, adds events to the current span on // It creates new spans on EventStartSpan, adds events to the current span on
// EventLog or EventTag, and closes the span on EventEndSpan. // EventLog or EventTag, and closes the span on EventEndSpan.
// The span structure can then be used by other exporters. // The span structure can then be used by other exporters.
func ContextSpan(ctx context.Context, ev event.Event) context.Context { func ContextSpan(ctx context.Context, ev event.Event) (context.Context, event.Event) {
switch { switch {
case ev.IsLog(), ev.IsTag(): case ev.IsLog(), ev.IsLabel():
if span := GetSpan(ctx); span != nil { if span := GetSpan(ctx); span != nil {
span.Events = append(span.Events, ev) span.Events = append(span.Events, ev)
} }
@ -71,9 +71,9 @@ func ContextSpan(ctx context.Context, ev event.Event) context.Context {
span.Finish = ev.At span.Finish = ev.At
} }
case ev.IsDetach(): case ev.IsDetach():
return context.WithValue(ctx, spanContextKey, nil) return context.WithValue(ctx, spanContextKey, nil), ev
} }
return ctx return ctx, ev
} }
func (s *SpanContext) Format(f fmt.State, r rune) { func (s *SpanContext) Format(f fmt.State, r rune) {

View File

@ -202,7 +202,7 @@ type HistogramFloat64Row struct {
} }
func getGroup(ctx context.Context, g *[]event.TagList, keys []*event.Key) (int, bool) { func getGroup(ctx context.Context, g *[]event.TagList, keys []*event.Key) (int, bool) {
group := event.Tags(ctx, keys...) group := event.Query(ctx, keys...)
old := *g old := *g
index := sort.Search(len(old), func(i int) bool { index := sort.Search(len(old), func(i int) bool {
return !old[i].Less(group) return !old[i].Less(group)