| // 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. |
| |
| // Package trace adds support for telemetry tracing. |
| package trace |
| |
| import ( |
| "context" |
| "fmt" |
| "time" |
| |
| "golang.org/x/tools/internal/lsp/telemetry/log" |
| "golang.org/x/tools/internal/lsp/telemetry/tag" |
| "golang.org/x/tools/internal/lsp/telemetry/worker" |
| ) |
| |
| type Span struct { |
| Name string |
| TraceID TraceID |
| SpanID SpanID |
| ParentID SpanID |
| Start time.Time |
| Finish time.Time |
| Tags tag.List |
| Events []Event |
| |
| ready bool |
| } |
| |
| type Event struct { |
| Time time.Time |
| Tags tag.List |
| } |
| |
| type Observer func(*Span) |
| |
| func RegisterObservers(o ...Observer) { |
| worker.Do(func() { |
| if !registered { |
| registered = true |
| tag.Observe(tagObserver) |
| log.AddLogger(logger) |
| } |
| observers = append(observers, o...) |
| }) |
| } |
| |
| func StartSpan(ctx context.Context, name string, tags ...tag.Tag) (context.Context, func()) { |
| span := &Span{ |
| Name: name, |
| Start: time.Now(), |
| } |
| if parent := fromContext(ctx); parent != nil { |
| span.TraceID = parent.TraceID |
| span.ParentID = parent.SpanID |
| } else { |
| span.TraceID = newTraceID() |
| } |
| span.SpanID = newSpanID() |
| ctx = context.WithValue(ctx, contextKey, span) |
| if len(tags) > 0 { |
| ctx = tag.With(ctx, tags...) |
| } |
| worker.Do(func() { |
| span.ready = true |
| for _, o := range observers { |
| o(span) |
| } |
| }) |
| return ctx, span.close |
| } |
| |
| func (s *Span) close() { |
| now := time.Now() |
| worker.Do(func() { |
| s.Finish = now |
| for _, o := range observers { |
| o(s) |
| } |
| }) |
| } |
| |
| func (s *Span) Format(f fmt.State, r rune) { |
| fmt.Fprintf(f, "%v %v:%v", s.Name, s.TraceID, s.SpanID) |
| if s.ParentID.IsValid() { |
| fmt.Fprintf(f, "[%v]", s.ParentID) |
| } |
| fmt.Fprintf(f, " %v->%v", s.Start, s.Finish) |
| } |
| |
| type contextKeyType int |
| |
| var contextKey contextKeyType |
| |
| func fromContext(ctx context.Context) *Span { |
| v := ctx.Value(contextKey) |
| if v == nil { |
| return nil |
| } |
| return v.(*Span) |
| } |
| |
| var ( |
| observers []Observer |
| registered bool |
| ) |
| |
| func tagObserver(ctx context.Context, at time.Time, tags tag.List) { |
| span := fromContext(ctx) |
| if span == nil { |
| return |
| } |
| if !span.ready { |
| span.Tags = append(span.Tags, tags...) |
| return |
| } |
| span.Events = append(span.Events, Event{ |
| Time: at, |
| Tags: tags, |
| }) |
| } |
| |
| func logger(ctx context.Context, at time.Time, tags tag.List) bool { |
| span := fromContext(ctx) |
| if span == nil { |
| return false |
| } |
| span.Events = append(span.Events, Event{ |
| Time: at, |
| Tags: tags, |
| }) |
| return false |
| } |
| |
| // 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 context.WithValue(ctx, contextKey, nil) |
| } |