internal/lsp: add the new form of the telemetry tagging package
This maps more directly to the basic telementery tagging requirements and uses
the context package in a way that is more idomatic.
Change-Id: If08c429b897bddfe014224ac2d92d7796a521ab9
Reviewed-on: https://go-review.googlesource.com/c/tools/+/184941
Run-TryBot: Ian Cottrell <iancottrell@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
diff --git a/internal/jsonrpc2/jsonrpc2.go b/internal/jsonrpc2/jsonrpc2.go
index f1c1a1f..52b0188 100644
--- a/internal/jsonrpc2/jsonrpc2.go
+++ b/internal/jsonrpc2/jsonrpc2.go
@@ -103,29 +103,24 @@
start: time.Now(),
}
ctx = context.WithValue(ctx, rpcStatsKey, s)
- tags := make([]tag.Mutator, 0, 4)
- tags = append(tags, tag.Upsert(telemetry.KeyMethod, method))
mode := telemetry.Outbound
- spanKind := trace.SpanKindClient
if server {
- spanKind = trace.SpanKindServer
mode = telemetry.Inbound
}
- tags = append(tags, tag.Upsert(telemetry.KeyRPCDirection, mode))
- if id != nil {
- tags = append(tags, tag.Upsert(telemetry.KeyRPCID, id.String()))
- }
- ctx, s.span = trace.StartSpan(ctx, method, trace.WithSpanKind(spanKind))
- ctx, _ = tag.New(ctx, tags...)
+ ctx, s.span = trace.StartSpan(ctx, method,
+ tag.Tag{Key: telemetry.Method, Value: method},
+ tag.Tag{Key: telemetry.RPCDirection, Value: mode},
+ tag.Tag{Key: telemetry.RPCID, Value: id},
+ )
stats.Record(ctx, telemetry.Started.M(1))
return ctx, s
}
func (s *rpcStats) end(ctx context.Context, err *error) {
if err != nil && *err != nil {
- ctx, _ = tag.New(ctx, tag.Upsert(telemetry.KeyStatus, "ERROR"))
+ ctx = telemetry.StatusCode.With(ctx, "ERROR")
} else {
- ctx, _ = tag.New(ctx, tag.Upsert(telemetry.KeyStatus, "OK"))
+ ctx = telemetry.StatusCode.With(ctx, "OK")
}
elapsedTime := time.Since(s.start)
latencyMillis := float64(elapsedTime) / float64(time.Millisecond)
@@ -309,7 +304,7 @@
if r.IsNotify() {
return fmt.Errorf("reply not invoked with a valid call")
}
- ctx, st := trace.StartSpan(ctx, r.Method+":reply", trace.WithSpanKind(trace.SpanKindClient))
+ ctx, st := trace.StartSpan(ctx, r.Method+":reply")
defer st.End()
// reply ends the handling phase of a call, so if we are not yet
diff --git a/internal/lsp/telemetry/tag/key.go b/internal/lsp/telemetry/tag/key.go
new file mode 100644
index 0000000..65a4ee0
--- /dev/null
+++ b/internal/lsp/telemetry/tag/key.go
@@ -0,0 +1,31 @@
+// 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 tag provides support for telemetry tagging.
+package tag
+
+import (
+ "context"
+)
+
+// Key represents the key for a context tag.
+// It is a helper to make use of context tagging slightly easier to read, it is
+// not strictly needed to use it at all.
+// It is intended that your common tagging keys are declared as constants of
+// this type, and then you can use the methods of this type to apply and find
+// those values in the context.
+type Key string
+
+// Of creates a new Tag with this key and the supplied value.
+// You can use this when building a tag list.
+func (k Key) Of(v interface{}) Tag {
+ return Tag{Key: k, Value: v}
+}
+
+// With applies sets this key to the supplied value on the context and
+// returns the new context generated.
+// It uses the With package level function so that observers are also notified.
+func (k Key) With(ctx context.Context, v interface{}) context.Context {
+ return With(ctx, Tag{Key: k, Value: v})
+}
diff --git a/internal/lsp/telemetry/tag/tag.go b/internal/lsp/telemetry/tag/tag.go
index a59559b..5a2b882 100644
--- a/internal/lsp/telemetry/tag/tag.go
+++ b/internal/lsp/telemetry/tag/tag.go
@@ -2,31 +2,118 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-// Package tag adds support for telemetry tags.
+// Package tag provides support for telemetry tagging.
+// This package is a thin shim over contexts with the main addition being the
+// the ability to observe when contexts get tagged with new values.
package tag
-import "context"
+import (
+ "context"
+ "fmt"
+ "time"
-type Map interface{}
-
-type Key interface {
- Name() string
-}
-
-type Mutator interface {
- Mutate(Map) (Map, error)
-}
-
-type nullMutator struct{}
-
-func (nullMutator) Mutate(Map) (Map, error) { return nil, nil }
-
-var (
- New = func(ctx context.Context, mutator ...Mutator) (context.Context, error) { return ctx, nil }
- NewContext = func(ctx context.Context, m Map) context.Context { return ctx }
- FromContext = func(ctx context.Context) Map { return nil }
- Delete = func(k Key) Mutator { return nullMutator{} }
- Insert = func(k Key, v string) Mutator { return nullMutator{} }
- Update = func(k Key, v string) Mutator { return nullMutator{} }
- Upsert = func(k Key, v string) Mutator { return nullMutator{} }
+ "golang.org/x/tools/internal/lsp/telemetry/worker"
)
+
+//TODO: Do we need to do something more efficient than just store tags
+//TODO: directly on the context?
+
+// Tag holds a key and value pair.
+// It is normally used when passing around lists of tags.
+type Tag struct {
+ Key interface{}
+ Value interface{}
+}
+
+// List is a way of passing around a collection of key value pairs.
+// It is an alternative to the less efficient and unordered method of using
+// maps.
+type List []Tag
+
+// Observer is the type for a function that wants to be notified when new tags
+// are set on a context.
+// If you use context.WithValue (or equivalent) it will bypass the observers,
+// you must use the setters in this package for tags that should be observed.
+// Register new observers with the Observe function.
+type Observer func(ctx context.Context, at time.Time, tags List)
+
+// With is roughly equivalent to context.WithValue except that it also notifies
+// registered observers.
+// Unlike WithValue, it takes a list of tags so that you can set many values
+// at once if needed. Each call to With results in one invocation of each
+// observer.
+func With(ctx context.Context, tags ...Tag) context.Context {
+ at := time.Now()
+ for _, t := range tags {
+ ctx = context.WithValue(ctx, t.Key, t.Value)
+ }
+ worker.Do(func() {
+ for i := len(observers) - 1; i >= 0; i-- {
+ observers[i](ctx, at, tags)
+ }
+ })
+ return ctx
+}
+
+// Get collects a set of values from the context and returns them as a tag list.
+func Get(ctx context.Context, keys ...interface{}) List {
+ tags := make(List, len(keys))
+ for i, key := range keys {
+ tags[i] = Tag{Key: key, Value: ctx.Value(key)}
+ }
+ return tags
+}
+
+var observers = []Observer{}
+
+// Observe adds a new tag observer to the registered set.
+// There is no way to ever unregister a observer.
+// Observers are free to use context information to control their behavior.
+func Observe(observer Observer) {
+ worker.Do(func() {
+ observers = append(observers, observer)
+ })
+}
+
+// Format is used for debug printing of tags.
+func (t Tag) Format(f fmt.State, r rune) {
+ fmt.Fprintf(f, `%v="%v"`, t.Key, t.Value)
+}
+
+// Get will get a single key's value from the list.
+func (l List) Get(k interface{}) interface{} {
+ for _, t := range l {
+ if t.Key == k {
+ return t.Value
+ }
+ }
+ return nil
+}
+
+// Format pretty prints a list.
+// It is intended only for debugging.
+func (l List) Format(f fmt.State, r rune) {
+ printed := false
+ for _, t := range l {
+ if t.Value == nil {
+ continue
+ }
+ if printed {
+ fmt.Fprint(f, ",")
+ }
+ fmt.Fprint(f, t)
+ printed = true
+ }
+}
+
+// Equal returns true if two lists are identical.
+func (l List) Equal(other List) bool {
+ //TODO: make this more efficient
+ return fmt.Sprint(l) == fmt.Sprint(other)
+}
+
+// Less is intended only for using tag lists as a sorting key.
+func (l List) Less(other List) bool {
+ //TODO: make this more efficient
+ return fmt.Sprint(l) < fmt.Sprint(other)
+}
diff --git a/internal/lsp/telemetry/telemetry.go b/internal/lsp/telemetry/telemetry.go
index 67b8036..f97c1e5 100644
--- a/internal/lsp/telemetry/telemetry.go
+++ b/internal/lsp/telemetry/telemetry.go
@@ -13,6 +13,17 @@
"golang.org/x/tools/internal/lsp/telemetry/tag"
)
+const (
+ // create the tag keys we use
+ Method = tag.Key("method")
+ StatusCode = tag.Key("status.code")
+ StatusMessage = tag.Key("status.message")
+ RPCID = tag.Key("id")
+ RPCDirection = tag.Key("direction")
+ File = tag.Key("file")
+ Package = tag.Key("package")
+)
+
var (
Handle = func(mux *http.ServeMux) {}
@@ -20,11 +31,6 @@
ReceivedBytes = stats.NullInt64Measure()
SentBytes = stats.NullInt64Measure()
Latency = stats.NullFloat64Measure()
-
- KeyRPCID tag.Key
- KeyMethod tag.Key
- KeyStatus tag.Key
- KeyRPCDirection tag.Key
)
const (