Skip to content
This repository has been archived by the owner on Dec 20, 2021. It is now read-only.

Commit

Permalink
Transcribe attributes from span links to events
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
seh committed Oct 16, 2020
1 parent 961b435 commit db66e5d
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 22 deletions.
42 changes: 22 additions & 20 deletions honeycomb/honeycomb.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand Down Expand Up @@ -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"`
Expand Down Expand Up @@ -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{
Expand All @@ -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"`
Expand All @@ -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(),
Expand All @@ -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)
Expand Down
20 changes: 18 additions & 2 deletions honeycomb/honeycomb_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -347,15 +347,26 @@ 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{
SpanContext: apitrace.SpanContext{
TraceID: linkTraceID,
SpanID: linkSpanID,
},
Attributes: nil,
Attributes: []label.KeyValue{
label.Int("one", 1),
label.Int("two", 2),
},
}))

span.End()
Expand All @@ -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) {
Expand Down Expand Up @@ -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)
Expand Down

0 comments on commit db66e5d

Please sign in to comment.