From db66e5db3b080cf3f920fbbf94c2ecb4de248560 Mon Sep 17 00:00:00 2001 From: "Steven E. Harris" Date: Wed, 14 Oct 2020 12:25:36 -0400 Subject: [PATCH] Transcribe attributes from span links to events Links attached to OpenTelemetry spans carry attributes like spans and events. Since we publish links as Honeycomb events, and events all carry fields, transcribe attributes on links as fields on the corresponding Honeycomb events. Include OpenTelemetry resource attributes as the underlay, and shadow those resource attributes if necessary with the link-level attributes. --- honeycomb/honeycomb.go | 42 +++++++++++++++++++------------------ honeycomb/honeycomb_test.go | 20 ++++++++++++++++-- 2 files changed, 40 insertions(+), 22 deletions(-) diff --git a/honeycomb/honeycomb.go b/honeycomb/honeycomb.go index 73ba405..61abb1a 100644 --- a/honeycomb/honeycomb.go +++ b/honeycomb/honeycomb.go @@ -28,6 +28,7 @@ import ( "github.com/honeycombio/libhoney-go/transmission" "go.opentelemetry.io/otel/codes" + "go.opentelemetry.io/otel/label" "go.opentelemetry.io/otel/sdk/export/trace" ) @@ -328,6 +329,12 @@ const ( traceIDLongLength = 16 ) +func transcribeAttributesTo(ev *libhoney.Event, attrs []label.KeyValue) { + for _, kv := range attrs { + ev.AddField(string(kv.Key), kv.Value.AsInterface()) + } +} + // span is the format of trace events that Honeycomb accepts. type span struct { TraceID string `json:"trace.trace_id"` @@ -496,35 +503,29 @@ func (e *Exporter) exportSpan(ctx context.Context, data *trace.SpanData) { applyResourceAttributes := func(ev *libhoney.Event) { if data.Resource != nil { - for _, kv := range data.Resource.Attributes() { - ev.AddField(string(kv.Key), kv.Value.AsInterface()) - } + transcribeAttributesTo(ev, data.Resource.Attributes()) + } + if len(e.serviceName) != 0 { + ev.AddField("service_name", e.serviceName) } } + transcribeLayeredAttributesTo := func(ev *libhoney.Event, attrs []label.KeyValue) { + // Treat resource-defined attributes as underlays, with any same-keyed message event + // attributes taking precedence. Apply them first. + applyResourceAttributes(ev) + transcribeAttributesTo(ev, attrs) + } // Treat resource-defined attributes as underlays, with any same-keyed span attributes taking // precedence. Apply them first. applyResourceAttributes(ev) - if len(e.serviceName) != 0 { - ev.AddField("service_name", e.serviceName) - } - ev.Timestamp = data.StartTime ev.Add(honeycombSpan(data)) // We send these message events as zero-duration spans. for _, a := range data.MessageEvents { spanEv := e.client.NewEvent() - // Treat resource-defined attributes as underlays, with any same-keyed message event - // attributes taking precedence. Apply them first. - applyResourceAttributes(spanEv) - if len(e.serviceName) != 0 { - spanEv.AddField("service_name", e.serviceName) - } - - for _, kv := range a.Attributes { - spanEv.AddField(string(kv.Key), kv.Value.AsInterface()) - } + transcribeLayeredAttributesTo(spanEv, a.Attributes) spanEv.Timestamp = a.Time spanEv.Add(spanEvent{ @@ -540,7 +541,8 @@ func (e *Exporter) exportSpan(ctx context.Context, data *trace.SpanData) { } // link represents a link to a trace and span that lives elsewhere. - // TraceID and ParentID are used to identify the span with which the trace is associated + // + // TraceID and ParentID are used to identify the span with which the trace is associated. // We are modeling Links for now as child spans rather than properties of the event. type link struct { TraceID string `json:"trace.trace_id"` @@ -553,6 +555,8 @@ func (e *Exporter) exportSpan(ctx context.Context, data *trace.SpanData) { for _, spanLink := range data.Links { linkEv := e.client.NewEvent() + transcribeLayeredAttributesTo(linkEv, spanLink.Attributes) + linkEv.Add(link{ TraceID: getHoneycombTraceID(data.SpanContext.TraceID[:]), ParentID: data.SpanContext.SpanID.String(), @@ -562,8 +566,6 @@ func (e *Exporter) exportSpan(ctx context.Context, data *trace.SpanData) { // TODO(akvanhar): properly set the reference type when specs are defined // see https://github.com/open-telemetry/opentelemetry-specification/issues/65 RefType: spanRefTypeChildOf, - - // TODO(akvanhar) add support for link.Attributes }) if err := linkEv.Send(); err != nil { e.onError(err) diff --git a/honeycomb/honeycomb_test.go b/honeycomb/honeycomb_test.go index bcde981..e21ab7f 100644 --- a/honeycomb/honeycomb_test.go +++ b/honeycomb/honeycomb_test.go @@ -347,7 +347,15 @@ func TestHoneycombOutputWithLinks(t *testing.T) { mockHoneycomb := &transmission.MockSender{} assert := assert.New(t) - tr, err := setUpTestExporter(mockHoneycomb) + exporter, err := makeTestExporter(mockHoneycomb) + assert.Nil(err) + assert.NotNil(exporter) + + tr, err := setUpTestProvider(exporter, + sdktrace.WithResource(resource.New( + label.Int("zero", 0), + label.Int("one", 99), // NB: Deliberately not 1, to be overwritten later. + ))) assert.Nil(err) _, span := tr.Start(context.TODO(), "myTestSpan", apitrace.WithLinks(apitrace.Link{ @@ -355,7 +363,10 @@ func TestHoneycombOutputWithLinks(t *testing.T) { TraceID: linkTraceID, SpanID: linkSpanID, }, - Attributes: nil, + Attributes: []label.KeyValue{ + label.Int("one", 1), + label.Int("two", 2), + }, })) span.End() @@ -381,6 +392,10 @@ func TestHoneycombOutputWithLinks(t *testing.T) { assert.Equal("0102030405060709", hclinkSpanID) linkAnnotationType := linkFields["meta.annotation_type"] assert.Equal("link", linkAnnotationType) + + assert.Equal(int64(0), linkFields["zero"]) + assert.Equal(int64(1), linkFields["one"]) + assert.Equal(int64(2), linkFields["two"]) } func TestHoneycombConfigValidation(t *testing.T) { @@ -652,6 +667,7 @@ func TestHoneycombOutputWithResource(t *testing.T) { label.Int64("a", middle), label.Int64("c", middle), ))) + assert.Nil(err) ctx, span := tr.Start(context.TODO(), "myTestSpan") assert.Nil(err)