1
0
mirror of https://github.com/golang/go synced 2024-11-18 14:54:40 -07:00

internal/telemetry: unify the event handling to an event package

This is now the only package that is exposed to normal use, and should
be the only thing to appear in libraries.

Change-Id: I90ee47c6519f30db16ff5d5d2910be86e91e5df2
Reviewed-on: https://go-review.googlesource.com/c/tools/+/222557
Run-TryBot: Ian Cottrell <iancottrell@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
This commit is contained in:
Ian Cottrell 2020-03-07 18:02:27 -05:00
parent 3dc7fec788
commit d780ff7bdd
25 changed files with 418 additions and 363 deletions

View File

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

View File

@ -2,16 +2,13 @@ package telemetry_test
import (
"context"
stdlog "log"
"log"
"strings"
"testing"
"golang.org/x/tools/internal/telemetry"
"golang.org/x/tools/internal/telemetry/event"
"golang.org/x/tools/internal/telemetry/export"
tellog "golang.org/x/tools/internal/telemetry/log"
"golang.org/x/tools/internal/telemetry/stats"
"golang.org/x/tools/internal/telemetry/tag"
teltrace "golang.org/x/tools/internal/telemetry/trace"
"golang.org/x/tools/internal/telemetry/unit"
)
@ -37,40 +34,40 @@ var (
StdLog = Hooks{
A: func(ctx context.Context, a *int) (context.Context, func()) {
stdlog.Printf("start A where a=%d", *a)
log.Printf("start A where a=%d", *a)
return ctx, func() {
stdlog.Printf("end A where a=%d", *a)
log.Printf("end A where a=%d", *a)
}
},
B: func(ctx context.Context, b *string) (context.Context, func()) {
stdlog.Printf("start B where b=%q", *b)
log.Printf("start B where b=%q", *b)
return ctx, func() {
stdlog.Printf("end B where b=%q", *b)
log.Printf("end B where b=%q", *b)
}
},
}
Log = Hooks{
A: func(ctx context.Context, a *int) (context.Context, func()) {
tellog.Print(ctx, "start A", tag.Of("a", *a))
event.Print(ctx, "start A", event.TagOf("a", *a))
return ctx, func() {
tellog.Print(ctx, "end A", tag.Of("a", *a))
event.Print(ctx, "end A", event.TagOf("a", *a))
}
},
B: func(ctx context.Context, b *string) (context.Context, func()) {
tellog.Print(ctx, "start B", tag.Of("b", *b))
event.Print(ctx, "start B", event.TagOf("b", *b))
return ctx, func() {
tellog.Print(ctx, "end B", tag.Of("b", *b))
event.Print(ctx, "end B", event.TagOf("b", *b))
}
},
}
Trace = Hooks{
A: func(ctx context.Context, a *int) (context.Context, func()) {
return teltrace.StartSpan(ctx, "A")
return event.StartSpan(ctx, "A")
},
B: func(ctx context.Context, b *string) (context.Context, func()) {
return teltrace.StartSpan(ctx, "B")
return event.StartSpan(ctx, "B")
},
}
@ -93,12 +90,12 @@ var (
func Benchmark(b *testing.B) {
b.Run("Baseline", Baseline.runBenchmark)
b.Run("StdLog", StdLog.runBenchmark)
export.SetExporter(nil)
event.SetExporter(nil)
b.Run("LogNoExporter", Log.runBenchmark)
b.Run("TraceNoExporter", Trace.runBenchmark)
b.Run("StatsNoExporter", Stats.runBenchmark)
export.SetExporter(newExporter())
event.SetExporter(newExporter())
b.Run("Log", Log.runBenchmark)
b.Run("Trace", Trace.runBenchmark)
b.Run("Stats", Stats.runBenchmark)
@ -136,7 +133,7 @@ func (hooks Hooks) runBenchmark(b *testing.B) {
}
func init() {
stdlog.SetOutput(new(noopWriter))
log.SetOutput(new(noopWriter))
}
type noopWriter int
@ -146,7 +143,7 @@ func (nw *noopWriter) Write(b []byte) (int, error) {
}
type loggingExporter struct {
logger export.Exporter
logger event.Exporter
}
func newExporter() *loggingExporter {
@ -155,11 +152,11 @@ func newExporter() *loggingExporter {
}
}
func (e *loggingExporter) ProcessEvent(ctx context.Context, event telemetry.Event) context.Context {
export.ContextSpan(ctx, event)
return e.logger.ProcessEvent(ctx, event)
func (e *loggingExporter) ProcessEvent(ctx context.Context, ev event.Event) context.Context {
export.ContextSpan(ctx, ev)
return e.logger.ProcessEvent(ctx, ev)
}
func (e *loggingExporter) Metric(ctx context.Context, data telemetry.MetricData) {
func (e *loggingExporter) Metric(ctx context.Context, data event.MetricData) {
e.logger.Metric(ctx, data)
}

View File

@ -2,14 +2,10 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package telemetry provides an opinionated set of packages that cover the main
// Package telemetry provides a set of packages that cover the main
// concepts of telemetry in an implementation agnostic way.
// As a library author you should look at
// stats (for aggregatable measurements)
// trace (for scoped timing spans)
// log (for for time based events)
// As a binary author you might look at
// metric (for methods of aggregating stats)
// exporter (for methods of exporting the telemetry to external tools)
// debug (for serving internal http pages of some of the telemetry)
// The interface for libraries that want to expose telemetry is the event
// package.
// As a binary author you might look at exporter for methods of exporting the
// telemetry to external tools.
package telemetry

View File

@ -4,42 +4,17 @@
package telemetry
import (
"fmt"
"time"
)
import "golang.org/x/tools/internal/telemetry/event"
type EventType uint8
type Event = event.Event
type Tag = event.Tag
type TagList = event.TagList
type MetricData = event.MetricData
const (
EventLog = EventType(iota)
EventStartSpan
EventEndSpan
EventTag
EventDetach
EventLog = event.LogType
EventStartSpan = event.StartSpanType
EventEndSpan = event.EndSpanType
EventLabel = event.LabelType
EventDetach = event.DetachType
)
type Event struct {
Type EventType
At time.Time
Message string
Error error
Tags TagList
}
func (e Event) Format(f fmt.State, r rune) {
if !e.At.IsZero() {
fmt.Fprint(f, e.At.Format("2006/01/02 15:04:05 "))
}
fmt.Fprint(f, e.Message)
if e.Error != nil {
if f.Flag('+') {
fmt.Fprintf(f, ": %+v", e.Error)
} else {
fmt.Fprintf(f, ": %v", e.Error)
}
}
for _, tag := range e.Tags {
fmt.Fprintf(f, "\n\t%v = %v", tag.Key, tag.Value)
}
}

View File

@ -0,0 +1,52 @@
// 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 event provides support for event based telemetry.
package event
import (
"fmt"
"time"
)
type eventType uint8
const (
LogType = eventType(iota)
StartSpanType
EndSpanType
LabelType
DetachType
)
type Event struct {
Type eventType
At time.Time
Message string
Error error
Tags TagList
}
func (e Event) IsLog() bool { return e.Type == LogType }
func (e Event) IsEndSpan() bool { return e.Type == EndSpanType }
func (e Event) IsStartSpan() bool { return e.Type == StartSpanType }
func (e Event) IsTag() bool { return e.Type == LabelType }
func (e Event) IsDetach() bool { return e.Type == DetachType }
func (e Event) Format(f fmt.State, r rune) {
if !e.At.IsZero() {
fmt.Fprint(f, e.At.Format("2006/01/02 15:04:05 "))
}
fmt.Fprint(f, e.Message)
if e.Error != nil {
if f.Flag('+') {
fmt.Fprintf(f, ": %+v", e.Error)
} else {
fmt.Fprintf(f, ": %v", e.Error)
}
}
for _, tag := range e.Tags {
fmt.Fprintf(f, "\n\t%v = %v", tag.Key, tag.Value)
}
}

View File

@ -0,0 +1,55 @@
// 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 event
import (
"context"
"sync/atomic"
"unsafe"
)
type Exporter interface {
// ProcessEvent is a function that handles all events.
// This is called with all events that should be delivered to the exporter
// along with the context in which that event ocurred.
// This method is called synchronously from the event call site, so it should
// return quickly so as not to hold up user code.
ProcessEvent(context.Context, Event) context.Context
Metric(context.Context, MetricData)
}
var (
exporter unsafe.Pointer
)
func SetExporter(e Exporter) {
p := unsafe.Pointer(&e)
if e == nil {
// &e is always valid, and so p is always valid, but for the early abort
// of ProcessEvent to be efficient it needs to make the nil check on the
// pointer without having to dereference it, so we make the nil interface
// also a nil pointer
p = nil
}
atomic.StorePointer(&exporter, p)
}
func ProcessEvent(ctx context.Context, event Event) context.Context {
exporterPtr := (*Exporter)(atomic.LoadPointer(&exporter))
if exporterPtr == nil {
return ctx
}
// and now also hand the event of to the current exporter
return (*exporterPtr).ProcessEvent(ctx, event)
}
func Metric(ctx context.Context, data MetricData) {
exporterPtr := (*Exporter)(atomic.LoadPointer(&exporter))
if exporterPtr == nil {
return
}
(*exporterPtr).Metric(ctx, data)
}

View File

@ -2,13 +2,10 @@
// 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
package event
import (
"context"
"golang.org/x/tools/internal/telemetry"
)
// Key represents the key for a context tag.
@ -19,27 +16,24 @@ import (
// those values in the context.
type Key string
// Of returns a Tag for a key and value.
// TagOf returns a Tag for a key and value.
// This is a trivial helper that makes common logging easier to read.
func Of(key interface{}, value interface{}) telemetry.Tag {
return telemetry.Tag{Key: key, Value: value}
func TagOf(key interface{}, value interface{}) Tag {
return Tag{Key: key, Value: value}
}
// 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{}) telemetry.Tag {
return telemetry.Tag{Key: k, Value: v}
func (k Key) Of(v interface{}) Tag {
return Tag{Key: k, Value: v}
}
// Tag can be used to get a tag for the key from a context.
// It makes Key conform to the Tagger interface.
func (k Key) Tag(ctx context.Context) telemetry.Tag {
return telemetry.Tag{Key: k, Value: ctx.Value(k)}
// 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 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.
// 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 With(ctx, telemetry.Tag{Key: k, Value: v})
return Label(ctx, Tag{Key: k, Value: v})
}

View File

@ -0,0 +1,19 @@
// 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 event
import (
"context"
"time"
)
// Label sends a label event to the exporter with the supplied tags.
func Label(ctx context.Context, tags ...Tag) context.Context {
return ProcessEvent(ctx, Event{
Type: LabelType,
At: time.Now(),
Tags: tags,
})
}

View File

@ -0,0 +1,48 @@
// 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 event
import (
"context"
"errors"
"time"
)
// Log sends a log event with the supplied tag list to the exporter.
func Log(ctx context.Context, tags ...Tag) {
ProcessEvent(ctx, Event{
Type: LogType,
At: time.Now(),
Tags: 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) {
ProcessEvent(ctx, Event{
Type: LogType,
At: time.Now(),
Message: message,
Tags: tags,
})
}
// Error takes a message and a tag list and combines them into a single event
// before delivering them to the exporter. It captures the error in the
// delivered event.
func Error(ctx context.Context, message string, err error, tags ...Tag) {
if err == nil {
err = errors.New(message)
message = ""
}
ProcessEvent(ctx, Event{
Type: LogType,
At: time.Now(),
Message: message,
Error: err,
Tags: tags,
})
}

View File

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package telemetry
package event
// MetricData represents a single point in the time series of a metric.
// This provides the common interface to all metrics no matter their data

View File

@ -2,9 +2,10 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package telemetry
package event
import (
"context"
"fmt"
)
@ -25,6 +26,16 @@ func (t Tag) Format(f fmt.State, r rune) {
fmt.Fprintf(f, `%v="%v"`, t.Key, t.Value)
}
// Tags collects a set of values from the context and returns them as a
// tag list.
func Tags(ctx context.Context, keys ...interface{}) 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.
func (l TagList) Get(k interface{}) interface{} {
for _, t := range l {

View File

@ -0,0 +1,34 @@
// 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 event
import (
"context"
"time"
)
func StartSpan(ctx context.Context, name string, tags ...Tag) (context.Context, func()) {
ctx = ProcessEvent(ctx, Event{
Type: StartSpanType,
Message: name,
At: time.Now(),
Tags: tags,
})
return ctx, func() {
ProcessEvent(ctx, Event{
Type: EndSpanType,
At: time.Now(),
})
}
}
// 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 ProcessEvent(ctx, Event{
Type: DetachType,
At: time.Now(),
})
}

View File

@ -2,58 +2,12 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package export holds the definition of the telemetry Exporter interface,
// along with some simple implementations.
// Package export holds some exporter implementations.
// Larger more complex exporters are in sub packages of their own.
package export
import (
"context"
"os"
"sync/atomic"
"unsafe"
"golang.org/x/tools/internal/telemetry"
)
type Exporter interface {
// ProcessEvent is a function that handles all events.
// Exporters may use information in the context to decide what to do with a
// given event.
ProcessEvent(context.Context, telemetry.Event) context.Context
Metric(context.Context, telemetry.MetricData)
}
import "golang.org/x/tools/internal/telemetry/event"
var (
exporter unsafe.Pointer
SetExporter = event.SetExporter
)
func init() {
SetExporter(LogWriter(os.Stderr, true))
}
func SetExporter(e Exporter) {
p := unsafe.Pointer(&e)
if e == nil {
p = nil
}
atomic.StorePointer(&exporter, p)
}
func ProcessEvent(ctx context.Context, event telemetry.Event) context.Context {
exporterPtr := (*Exporter)(atomic.LoadPointer(&exporter))
if exporterPtr == nil {
return ctx
}
// and now also hand the event of to the current exporter
return (*exporterPtr).ProcessEvent(ctx, event)
}
func Metric(ctx context.Context, data telemetry.MetricData) {
exporterPtr := (*Exporter)(atomic.LoadPointer(&exporter))
if exporterPtr == nil {
return
}
(*exporterPtr).Metric(ctx, data)
}

View File

@ -8,15 +8,20 @@ import (
"context"
"fmt"
"io"
"os"
"golang.org/x/tools/internal/telemetry"
"golang.org/x/tools/internal/telemetry/event"
)
// LogWriter returns an observer that logs events to the supplied writer.
func init() {
event.SetExporter(LogWriter(os.Stderr, true))
}
// LogWriter returns an Exporter that logs events to the supplied writer.
// If onlyErrors is true it does not log any event that did not have an
// associated error.
// It ignores all telemetry other than log events.
func LogWriter(w io.Writer, onlyErrors bool) Exporter {
func LogWriter(w io.Writer, onlyErrors bool) event.Exporter {
return &logWriter{writer: w, onlyErrors: onlyErrors}
}
@ -25,25 +30,26 @@ type logWriter struct {
onlyErrors bool
}
func (w *logWriter) ProcessEvent(ctx context.Context, event telemetry.Event) context.Context {
switch event.Type {
case telemetry.EventLog:
if w.onlyErrors && event.Error == nil {
func (w *logWriter) ProcessEvent(ctx context.Context, ev event.Event) context.Context {
switch {
case ev.IsLog():
if w.onlyErrors && ev.Error == nil {
return ctx
}
fmt.Fprintf(w.writer, "%v\n", event)
case telemetry.EventStartSpan:
fmt.Fprintf(w.writer, "%v\n", ev)
case ev.IsStartSpan():
if span := GetSpan(ctx); span != nil {
fmt.Fprintf(w.writer, "start: %v %v", span.Name, span.ID)
if span.ParentID.IsValid() {
fmt.Fprintf(w.writer, "[%v]", span.ParentID)
}
}
case telemetry.EventEndSpan:
case ev.IsEndSpan():
if span := GetSpan(ctx); span != nil {
fmt.Fprintf(w.writer, "finish: %v %v", span.Name, span.ID)
}
}
return ctx
}
func (w *logWriter) Metric(context.Context, telemetry.MetricData) {}
func (w *logWriter) Metric(context.Context, event.MetricData) {}

View File

@ -8,13 +8,13 @@ import (
"fmt"
"time"
"golang.org/x/tools/internal/telemetry"
"golang.org/x/tools/internal/telemetry/event"
"golang.org/x/tools/internal/telemetry/export/ocagent/wire"
"golang.org/x/tools/internal/telemetry/metric"
)
// dataToMetricDescriptor return a *wire.MetricDescriptor based on data.
func dataToMetricDescriptor(data telemetry.MetricData) *wire.MetricDescriptor {
func dataToMetricDescriptor(data event.MetricData) *wire.MetricDescriptor {
if data == nil {
return nil
}
@ -30,7 +30,7 @@ func dataToMetricDescriptor(data telemetry.MetricData) *wire.MetricDescriptor {
}
// getDescription returns the description of data.
func getDescription(data telemetry.MetricData) string {
func getDescription(data event.MetricData) string {
switch d := data.(type) {
case *metric.Int64Data:
return d.Info.Description
@ -50,7 +50,7 @@ func getDescription(data telemetry.MetricData) string {
// getLabelKeys returns a slice of *wire.LabelKeys based on the keys
// in data.
func getLabelKeys(data telemetry.MetricData) []*wire.LabelKey {
func getLabelKeys(data event.MetricData) []*wire.LabelKey {
switch d := data.(type) {
case *metric.Int64Data:
return infoKeysToLabelKeys(d.Info.Keys)
@ -70,7 +70,7 @@ func getLabelKeys(data telemetry.MetricData) []*wire.LabelKey {
// dataToMetricDescriptorType returns a wire.MetricDescriptor_Type based on the
// underlying type of data.
func dataToMetricDescriptorType(data telemetry.MetricData) wire.MetricDescriptor_Type {
func dataToMetricDescriptorType(data event.MetricData) wire.MetricDescriptor_Type {
switch d := data.(type) {
case *metric.Int64Data:
if d.IsGauge {
@ -96,7 +96,7 @@ func dataToMetricDescriptorType(data telemetry.MetricData) wire.MetricDescriptor
// dataToTimeseries returns a slice of *wire.TimeSeries based on the
// points in data.
func dataToTimeseries(data telemetry.MetricData, start time.Time) []*wire.TimeSeries {
func dataToTimeseries(data event.MetricData, start time.Time) []*wire.TimeSeries {
if data == nil {
return nil
}
@ -117,7 +117,7 @@ func dataToTimeseries(data telemetry.MetricData, start time.Time) []*wire.TimeSe
}
// numRows returns the number of rows in data.
func numRows(data telemetry.MetricData) int {
func numRows(data event.MetricData) int {
switch d := data.(type) {
case *metric.Int64Data:
return len(d.Rows)
@ -134,7 +134,7 @@ func numRows(data telemetry.MetricData) int {
// dataToPoints returns an array of *wire.Points based on the point(s)
// in data at index i.
func dataToPoints(data telemetry.MetricData, i int) []*wire.Point {
func dataToPoints(data event.MetricData, i int) []*wire.Point {
switch d := data.(type) {
case *metric.Int64Data:
timestamp := convertTimestamp(*d.EndTime)

View File

@ -5,7 +5,7 @@ import (
"testing"
"time"
"golang.org/x/tools/internal/telemetry"
"golang.org/x/tools/internal/telemetry/event"
"golang.org/x/tools/internal/telemetry/metric"
)
@ -16,7 +16,7 @@ func TestEncodeMetric(t *testing.T) {
const suffix = `]}`
tests := []struct {
name string
data telemetry.MetricData
data event.MetricData
want string
}{
{

View File

@ -18,10 +18,9 @@ import (
"sync"
"time"
"golang.org/x/tools/internal/telemetry"
"golang.org/x/tools/internal/telemetry/event"
"golang.org/x/tools/internal/telemetry/export"
"golang.org/x/tools/internal/telemetry/export/ocagent/wire"
"golang.org/x/tools/internal/telemetry/tag"
)
type Config struct {
@ -47,7 +46,7 @@ type Exporter struct {
mu sync.Mutex
config Config
spans []*export.Span
metrics []telemetry.MetricData
metrics []event.MetricData
}
// Connect creates a process specific exporter with the specified
@ -85,8 +84,8 @@ func Connect(config *Config) *Exporter {
return exporter
}
func (e *Exporter) ProcessEvent(ctx context.Context, event telemetry.Event) context.Context {
if event.Type != telemetry.EventEndSpan {
func (e *Exporter) ProcessEvent(ctx context.Context, ev event.Event) context.Context {
if !ev.IsEndSpan() {
return ctx
}
e.mu.Lock()
@ -98,7 +97,7 @@ func (e *Exporter) ProcessEvent(ctx context.Context, event telemetry.Event) cont
return ctx
}
func (e *Exporter) Metric(ctx context.Context, data telemetry.MetricData) {
func (e *Exporter) Metric(ctx context.Context, data event.MetricData) {
e.mu.Lock()
defer e.mu.Unlock()
e.metrics = append(e.metrics, data)
@ -212,7 +211,7 @@ func convertSpan(span *export.Span) *wire.Span {
return result
}
func convertMetric(data telemetry.MetricData, start time.Time) *wire.Metric {
func convertMetric(data event.MetricData, start time.Time) *wire.Metric {
descriptor := dataToMetricDescriptor(data)
timeseries := dataToTimeseries(data, start)
@ -228,7 +227,7 @@ func convertMetric(data telemetry.MetricData, start time.Time) *wire.Metric {
}
}
func convertAttributes(tags telemetry.TagList) *wire.Attributes {
func convertAttributes(tags event.TagList) *wire.Attributes {
if len(tags) == 0 {
return nil
}
@ -274,7 +273,7 @@ func convertAttribute(v interface{}) wire.Attribute {
}
}
func convertEvents(events []telemetry.Event) *wire.TimeEvents {
func convertEvents(events []event.Event) *wire.TimeEvents {
//TODO: MessageEvents?
result := make([]wire.TimeEvent, len(events))
for i, event := range events {
@ -283,22 +282,22 @@ func convertEvents(events []telemetry.Event) *wire.TimeEvents {
return &wire.TimeEvents{TimeEvent: result}
}
func convertEvent(event telemetry.Event) wire.TimeEvent {
func convertEvent(ev event.Event) wire.TimeEvent {
return wire.TimeEvent{
Time: convertTimestamp(event.At),
Annotation: convertAnnotation(event),
Time: convertTimestamp(ev.At),
Annotation: convertAnnotation(ev),
}
}
func convertAnnotation(event telemetry.Event) *wire.Annotation {
description := event.Message
if description == "" && event.Error != nil {
description = event.Error.Error()
event.Error = nil
func convertAnnotation(ev event.Event) *wire.Annotation {
description := ev.Message
if description == "" && ev.Error != nil {
description = ev.Error.Error()
ev.Error = nil
}
tags := event.Tags
if event.Error != nil {
tags = append(tags, tag.Of("error", event.Error))
tags := ev.Tags
if ev.Error != nil {
tags = append(tags, event.TagOf("error", ev.Error))
}
if description == "" && len(tags) == 0 {
return nil

View File

@ -16,10 +16,9 @@ import (
"testing"
"time"
"golang.org/x/tools/internal/telemetry"
"golang.org/x/tools/internal/telemetry/event"
"golang.org/x/tools/internal/telemetry/export"
"golang.org/x/tools/internal/telemetry/export/ocagent"
"golang.org/x/tools/internal/telemetry/tag"
)
var (
@ -79,13 +78,13 @@ func TestEvents(t *testing.T) {
}`
tests := []struct {
name string
event func(ctx context.Context) telemetry.Event
event func(ctx context.Context) event.Event
want string
}{
{
name: "no tags",
event: func(ctx context.Context) telemetry.Event {
return telemetry.Event{
event: func(ctx context.Context) event.Event {
return event.Event{
At: at,
}
},
@ -95,12 +94,12 @@ func TestEvents(t *testing.T) {
},
{
name: "description no error",
event: func(ctx context.Context) telemetry.Event {
return telemetry.Event{
event: func(ctx context.Context) event.Event {
return event.Event{
At: at,
Message: "cache miss",
Tags: telemetry.TagList{
tag.Of("db", "godb"),
Tags: event.TagList{
event.TagOf("db", "godb"),
},
}
},
@ -116,13 +115,13 @@ func TestEvents(t *testing.T) {
{
name: "description and error",
event: func(ctx context.Context) telemetry.Event {
return telemetry.Event{
event: func(ctx context.Context) event.Event {
return event.Event{
At: at,
Message: "cache miss",
Error: errors.New("no network connectivity"),
Tags: telemetry.TagList{
tag.Of("db", "godb"), // must come before e
Tags: event.TagList{
event.TagOf("db", "godb"), // must come before e
},
}
},
@ -138,12 +137,12 @@ func TestEvents(t *testing.T) {
},
{
name: "no description, but error",
event: func(ctx context.Context) telemetry.Event {
return telemetry.Event{
event: func(ctx context.Context) event.Event {
return event.Event{
At: at,
Error: errors.New("no network connectivity"),
Tags: telemetry.TagList{
tag.Of("db", "godb"),
Tags: event.TagList{
event.TagOf("db", "godb"),
},
}
},
@ -158,31 +157,31 @@ func TestEvents(t *testing.T) {
},
{
name: "enumerate all attribute types",
event: func(ctx context.Context) telemetry.Event {
return telemetry.Event{
event: func(ctx context.Context) event.Event {
return event.Event{
At: at,
Message: "cache miss",
Tags: telemetry.TagList{
tag.Of("1_db", "godb"),
Tags: event.TagList{
event.TagOf("1_db", "godb"),
tag.Of("2a_age", 0.456), // Constant converted into "float64"
tag.Of("2b_ttl", float32(5000)),
tag.Of("2c_expiry_ms", float64(1e3)),
event.TagOf("2a_age", 0.456), // Constant converted into "float64"
event.TagOf("2b_ttl", float32(5000)),
event.TagOf("2c_expiry_ms", float64(1e3)),
tag.Of("3a_retry", false),
tag.Of("3b_stale", true),
event.TagOf("3a_retry", false),
event.TagOf("3b_stale", true),
tag.Of("4a_max", 0x7fff), // Constant converted into "int"
tag.Of("4b_opcode", int8(0x7e)),
tag.Of("4c_base", int16(1<<9)),
tag.Of("4e_checksum", int32(0x11f7e294)),
tag.Of("4f_mode", int64(0644)),
event.TagOf("4a_max", 0x7fff), // Constant converted into "int"
event.TagOf("4b_opcode", int8(0x7e)),
event.TagOf("4c_base", int16(1<<9)),
event.TagOf("4e_checksum", int32(0x11f7e294)),
event.TagOf("4f_mode", int64(0644)),
tag.Of("5a_min", uint(1)),
tag.Of("5b_mix", uint8(44)),
tag.Of("5c_port", uint16(55678)),
tag.Of("5d_min_hops", uint32(1<<9)),
tag.Of("5e_max_hops", uint64(0xffffff)),
event.TagOf("5a_min", uint(1)),
event.TagOf("5b_mix", uint8(44)),
event.TagOf("5c_port", uint16(55678)),
event.TagOf("5d_min_hops", uint32(1<<9)),
event.TagOf("5e_max_hops", uint64(0xffffff)),
},
}
},
@ -214,19 +213,19 @@ func TestEvents(t *testing.T) {
ctx := context.TODO()
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
startEvent := telemetry.Event{
Type: telemetry.EventStartSpan,
startEvent := event.Event{
Type: event.StartSpanType,
Message: "event span",
At: start,
}
endEvent := telemetry.Event{
Type: telemetry.EventEndSpan,
endEvent := event.Event{
Type: event.EndSpanType,
At: end,
}
ctx := export.ContextSpan(ctx, startEvent)
span := export.GetSpan(ctx)
span.ID = export.SpanContext{}
span.Events = []telemetry.Event{tt.event(ctx)}
span.Events = []event.Event{tt.event(ctx)}
exporter.ProcessEvent(ctx, startEvent)
export.ContextSpan(ctx, endEvent)
exporter.ProcessEvent(ctx, endEvent)

View File

@ -12,7 +12,7 @@ import (
"sort"
"sync"
"golang.org/x/tools/internal/telemetry"
"golang.org/x/tools/internal/telemetry/event"
"golang.org/x/tools/internal/telemetry/metric"
)
@ -22,10 +22,10 @@ func New() *Exporter {
type Exporter struct {
mu sync.Mutex
metrics []telemetry.MetricData
metrics []event.MetricData
}
func (e *Exporter) Metric(ctx context.Context, data telemetry.MetricData) {
func (e *Exporter) Metric(ctx context.Context, data event.MetricData) {
e.mu.Lock()
defer e.mu.Unlock()
name := data.Handle()
@ -38,7 +38,7 @@ func (e *Exporter) Metric(ctx context.Context, data telemetry.MetricData) {
if index >= len(e.metrics) || e.metrics[index].Handle() != name {
// we have a new metric, so we need to make a space for it
old := e.metrics
e.metrics = make([]telemetry.MetricData, len(old)+1)
e.metrics = make([]event.MetricData, len(old)+1)
copy(e.metrics, old[:index])
copy(e.metrics[index+1:], old[index:])
}
@ -57,7 +57,7 @@ func (e *Exporter) header(w http.ResponseWriter, name, description string, isGau
fmt.Fprintf(w, "# TYPE %s %s\n", name, kind)
}
func (e *Exporter) row(w http.ResponseWriter, name string, group telemetry.TagList, extra string, value interface{}) {
func (e *Exporter) row(w http.ResponseWriter, name string, group event.TagList, extra string, value interface{}) {
fmt.Fprint(w, name)
buf := &bytes.Buffer{}
fmt.Fprint(buf, group)

View File

@ -7,17 +7,16 @@ package export
import (
"context"
"golang.org/x/tools/internal/telemetry"
"golang.org/x/tools/internal/telemetry/event"
)
// Tag returns a context updated with tag values from the event.
// It ignores events that are not or type EventTag or EventStartSpan.
func Tag(ctx context.Context, event telemetry.Event) context.Context {
// It ignores events that are not or type IsTag or IsStartSpan.
func Tag(ctx context.Context, ev event.Event) context.Context {
//TODO: Do we need to do something more efficient than just store tags
//TODO: directly on the context?
switch event.Type {
case telemetry.EventTag, telemetry.EventStartSpan:
for _, t := range event.Tags {
if ev.IsTag() || ev.IsStartSpan() {
for _, t := range ev.Tags {
ctx = context.WithValue(ctx, t.Key, t.Value)
}
}

View File

@ -9,7 +9,7 @@ import (
"fmt"
"time"
"golang.org/x/tools/internal/telemetry"
"golang.org/x/tools/internal/telemetry/event"
)
type SpanContext struct {
@ -23,8 +23,8 @@ type Span struct {
ParentID SpanID
Start time.Time
Finish time.Time
Tags telemetry.TagList
Events []telemetry.Event
Tags event.TagList
Events []event.Event
}
type contextKeyType int
@ -46,17 +46,17 @@ func GetSpan(ctx context.Context) *Span {
// It creates new spans on EventStartSpan, adds events to the current span on
// EventLog or EventTag, and closes the span on EventEndSpan.
// The span structure can then be used by other exporters.
func ContextSpan(ctx context.Context, event telemetry.Event) context.Context {
switch event.Type {
case telemetry.EventLog, telemetry.EventTag:
func ContextSpan(ctx context.Context, ev event.Event) context.Context {
switch {
case ev.IsLog(), ev.IsTag():
if span := GetSpan(ctx); span != nil {
span.Events = append(span.Events, event)
span.Events = append(span.Events, ev)
}
case telemetry.EventStartSpan:
case ev.IsStartSpan():
span := &Span{
Name: event.Message,
Start: event.At,
Tags: event.Tags,
Name: ev.Message,
Start: ev.At,
Tags: ev.Tags,
}
if parent := GetSpan(ctx); parent != nil {
span.ID.TraceID = parent.ID.TraceID
@ -66,11 +66,11 @@ func ContextSpan(ctx context.Context, event telemetry.Event) context.Context {
}
span.ID.SpanID = newSpanID()
ctx = context.WithValue(ctx, spanContextKey, span)
case telemetry.EventEndSpan:
case ev.IsEndSpan():
if span := GetSpan(ctx); span != nil {
span.Finish = event.At
span.Finish = ev.At
}
case telemetry.EventDetach:
case ev.IsDetach():
return context.WithValue(ctx, spanContextKey, nil)
}
return ctx

View File

@ -6,54 +6,10 @@
// with both the lsp protocol and the other telemetry packages.
package log
import (
"context"
"time"
import "golang.org/x/tools/internal/telemetry/event"
"golang.org/x/tools/internal/telemetry"
"golang.org/x/tools/internal/telemetry/export"
var (
With = event.Log
Print = event.Print
Error = event.Error
)
type Event telemetry.Event
// With sends a tag list to the installed loggers.
func With(ctx context.Context, tags ...telemetry.Tag) {
export.ProcessEvent(ctx, telemetry.Event{
Type: telemetry.EventLog,
At: time.Now(),
Tags: tags,
})
}
// Print takes a message and a tag list and combines them into a single tag
// list before delivering them to the loggers.
func Print(ctx context.Context, message string, tags ...telemetry.Tag) {
export.ProcessEvent(ctx, telemetry.Event{
Type: telemetry.EventLog,
At: time.Now(),
Message: message,
Tags: tags,
})
}
// Error takes a message and a tag list and combines them into a single tag
// list before delivering them to the loggers. It captures the error in the
// delivered event.
func Error(ctx context.Context, message string, err error, tags ...telemetry.Tag) {
if err == nil {
err = errorString(message)
message = ""
}
export.ProcessEvent(ctx, telemetry.Event{
Type: telemetry.EventLog,
At: time.Now(),
Message: message,
Error: err,
Tags: tags,
})
}
type errorString string
// Error allows errorString to conform to the error interface.
func (err errorString) Error() string { return string(err) }

View File

@ -10,10 +10,8 @@ import (
"sort"
"time"
"golang.org/x/tools/internal/telemetry"
"golang.org/x/tools/internal/telemetry/export"
"golang.org/x/tools/internal/telemetry/event"
"golang.org/x/tools/internal/telemetry/stats"
"golang.org/x/tools/internal/telemetry/tag"
)
// Scalar represents the construction information for a scalar metric.
@ -134,7 +132,7 @@ type Int64Data struct {
// End is the last time this metric was updated.
EndTime *time.Time
groups []telemetry.TagList
groups []event.TagList
}
// Float64Data is a concrete implementation of Data for float64 scalar metrics.
@ -148,7 +146,7 @@ type Float64Data struct {
// End is the last time this metric was updated.
EndTime *time.Time
groups []telemetry.TagList
groups []event.TagList
}
// HistogramInt64Data is a concrete implementation of Data for int64 histogram metrics.
@ -160,7 +158,7 @@ type HistogramInt64Data struct {
// End is the last time this metric was updated.
EndTime *time.Time
groups []telemetry.TagList
groups []event.TagList
}
// HistogramInt64Row holds the values for a single row of a HistogramInt64Data.
@ -186,7 +184,7 @@ type HistogramFloat64Data struct {
// End is the last time this metric was updated.
EndTime *time.Time
groups []telemetry.TagList
groups []event.TagList
}
// HistogramFloat64Row holds the values for a single row of a HistogramFloat64Data.
@ -203,8 +201,8 @@ type HistogramFloat64Row struct {
Max float64
}
func getGroup(ctx context.Context, g *[]telemetry.TagList, keys []interface{}) (int, bool) {
group := tag.Get(ctx, keys...)
func getGroup(ctx context.Context, g *[]event.TagList, keys []interface{}) (int, bool) {
group := event.Tags(ctx, keys...)
old := *g
index := sort.Search(len(old), func(i int) bool {
return !old[i].Less(group)
@ -213,15 +211,15 @@ func getGroup(ctx context.Context, g *[]telemetry.TagList, keys []interface{}) (
// not a new group
return index, false
}
*g = make([]telemetry.TagList, len(old)+1)
*g = make([]event.TagList, len(old)+1)
copy(*g, old[:index])
copy((*g)[index+1:], old[index:])
(*g)[index] = group
return index, true
}
func (data *Int64Data) Handle() string { return data.Info.Name }
func (data *Int64Data) Groups() []telemetry.TagList { return data.groups }
func (data *Int64Data) Handle() string { return data.Info.Name }
func (data *Int64Data) Groups() []event.TagList { return data.groups }
func (data *Int64Data) modify(ctx context.Context, at time.Time, f func(v int64) int64) {
index, insert := getGroup(ctx, &data.groups, data.Info.Keys)
@ -237,7 +235,7 @@ func (data *Int64Data) modify(ctx context.Context, at time.Time, f func(v int64)
data.Rows[index] = f(data.Rows[index])
data.EndTime = &at
frozen := *data
export.Metric(ctx, &frozen)
event.Metric(ctx, &frozen)
}
func (data *Int64Data) countInt64(ctx context.Context, measure *stats.Int64Measure, value int64, at time.Time) {
@ -256,8 +254,8 @@ func (data *Int64Data) latest(ctx context.Context, measure *stats.Int64Measure,
data.modify(ctx, at, func(v int64) int64 { return value })
}
func (data *Float64Data) Handle() string { return data.Info.Name }
func (data *Float64Data) Groups() []telemetry.TagList { return data.groups }
func (data *Float64Data) Handle() string { return data.Info.Name }
func (data *Float64Data) Groups() []event.TagList { return data.groups }
func (data *Float64Data) modify(ctx context.Context, at time.Time, f func(v float64) float64) {
index, insert := getGroup(ctx, &data.groups, data.Info.Keys)
@ -273,7 +271,7 @@ func (data *Float64Data) modify(ctx context.Context, at time.Time, f func(v floa
data.Rows[index] = f(data.Rows[index])
data.EndTime = &at
frozen := *data
export.Metric(ctx, &frozen)
event.Metric(ctx, &frozen)
}
func (data *Float64Data) sum(ctx context.Context, measure *stats.Float64Measure, value float64, at time.Time) {
@ -284,8 +282,8 @@ func (data *Float64Data) latest(ctx context.Context, measure *stats.Float64Measu
data.modify(ctx, at, func(v float64) float64 { return value })
}
func (data *HistogramInt64Data) Handle() string { return data.Info.Name }
func (data *HistogramInt64Data) Groups() []telemetry.TagList { return data.groups }
func (data *HistogramInt64Data) Handle() string { return data.Info.Name }
func (data *HistogramInt64Data) Groups() []event.TagList { return data.groups }
func (data *HistogramInt64Data) modify(ctx context.Context, at time.Time, f func(v *HistogramInt64Row)) {
index, insert := getGroup(ctx, &data.groups, data.Info.Keys)
@ -307,7 +305,7 @@ func (data *HistogramInt64Data) modify(ctx context.Context, at time.Time, f func
data.Rows[index] = &v
data.EndTime = &at
frozen := *data
export.Metric(ctx, &frozen)
event.Metric(ctx, &frozen)
}
func (data *HistogramInt64Data) record(ctx context.Context, measure *stats.Int64Measure, value int64, at time.Time) {
@ -328,8 +326,8 @@ func (data *HistogramInt64Data) record(ctx context.Context, measure *stats.Int64
})
}
func (data *HistogramFloat64Data) Handle() string { return data.Info.Name }
func (data *HistogramFloat64Data) Groups() []telemetry.TagList { return data.groups }
func (data *HistogramFloat64Data) Handle() string { return data.Info.Name }
func (data *HistogramFloat64Data) Groups() []event.TagList { return data.groups }
func (data *HistogramFloat64Data) modify(ctx context.Context, at time.Time, f func(v *HistogramFloat64Row)) {
index, insert := getGroup(ctx, &data.groups, data.Info.Keys)
@ -351,7 +349,7 @@ func (data *HistogramFloat64Data) modify(ctx context.Context, at time.Time, f fu
data.Rows[index] = &v
data.EndTime = &at
frozen := *data
export.Metric(ctx, &frozen)
event.Metric(ctx, &frozen)
}
func (data *HistogramFloat64Data) record(ctx context.Context, measure *stats.Float64Measure, value float64, at time.Time) {

View File

@ -8,27 +8,13 @@
package tag
import (
"context"
"time"
"golang.org/x/tools/internal/telemetry"
"golang.org/x/tools/internal/telemetry/export"
"golang.org/x/tools/internal/telemetry/event"
)
// With delivers the tag list to the telemetry exporter.
func With(ctx context.Context, tags ...telemetry.Tag) context.Context {
return export.ProcessEvent(ctx, telemetry.Event{
Type: telemetry.EventTag,
At: time.Now(),
Tags: tags,
})
}
type Key = event.Key
// Get collects a set of values from the context and returns them as a tag list.
func Get(ctx context.Context, keys ...interface{}) telemetry.TagList {
tags := make(telemetry.TagList, len(keys))
for i, key := range keys {
tags[i] = telemetry.Tag{Key: key, Value: ctx.Value(key)}
}
return tags
}
var (
With = event.Label
Get = event.Tags
Of = event.TagOf
)

View File

@ -6,33 +6,10 @@
package trace
import (
"context"
"time"
"golang.org/x/tools/internal/telemetry"
"golang.org/x/tools/internal/telemetry/export"
"golang.org/x/tools/internal/telemetry/event"
)
func StartSpan(ctx context.Context, name string, tags ...telemetry.Tag) (context.Context, func()) {
ctx = export.ProcessEvent(ctx, telemetry.Event{
Type: telemetry.EventStartSpan,
Message: name,
At: time.Now(),
Tags: tags,
})
return ctx, func() {
export.ProcessEvent(ctx, telemetry.Event{
Type: telemetry.EventEndSpan,
At: time.Now(),
})
}
}
// 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 export.ProcessEvent(ctx, telemetry.Event{
Type: telemetry.EventDetach,
At: time.Now(),
})
}
var (
StartSpan = event.StartSpan
Detach = event.Detach
)