diff --git a/honeycomb/honeycomb.go b/honeycomb/honeycomb.go index c583ee0..ed80e11 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 4b7f865..3c62db0 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.NewWithAttributes( + 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) _, span := tr.Start(context.TODO(), "myTestSpan") assert.Nil(err)