Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

pkg/trace/api: add support for OpenTelemetry Span Links. #15767

Merged
merged 12 commits into from
Mar 15, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions pkg/trace/api/otlp.go
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,49 @@ func marshalEvents(events ptrace.SpanEventSlice) string {
return str.String()
}

// marshalLinks marshals span links into JSON.
func marshalLinks(links ptrace.SpanLinkSlice) string {
var str strings.Builder
str.WriteString("[")
for i := 0; i < links.Len(); i++ {
l := links.At(i)
if i > 0 {
str.WriteString(",")
}
t := l.TraceID()
str.WriteString(`{"trace_id":"`)
str.WriteString(hex.EncodeToString(t[:]))
gbbr marked this conversation as resolved.
Show resolved Hide resolved
s := l.SpanID()
str.WriteString(`","span_id":"`)
str.WriteString(hex.EncodeToString(s[:]))
str.WriteString(`"`)
if l.Attributes().Len() > 0 {
str.WriteString(`,"attributes":{`)
dinooliva marked this conversation as resolved.
Show resolved Hide resolved
b := false
dinooliva marked this conversation as resolved.
Show resolved Hide resolved
l.Attributes().Range(func(k string, v pcommon.Value) bool {
if b {
str.WriteString(",")
}
b = true
str.WriteString(`"`)
str.WriteString(k)
str.WriteString(`":"`)
str.WriteString(v.AsString())
str.WriteString(`"`)
return true
})
str.WriteString("}")
}
if l.DroppedAttributesCount() > 0 {
gbbr marked this conversation as resolved.
Show resolved Hide resolved
str.WriteString(`,"dropped_attributes_count":`)
str.WriteString(strconv.FormatUint(uint64(l.DroppedAttributesCount()), 10))
}
str.WriteString("}")
}
str.WriteString("]")
return str.String()
}

// setMetaOTLP sets the k/v OTLP attribute pair as a tag on span s.
func setMetaOTLP(s *pb.Span, k, v string) {
switch k {
Expand Down Expand Up @@ -457,6 +500,9 @@ func (o *OTLPReceiver) convertSpan(rattr map[string]string, lib pcommon.Instrume
if in.Events().Len() > 0 {
setMetaOTLP(span, "events", marshalEvents(in.Events()))
}
if in.Links().Len() > 0 {
setMetaOTLP(span, "links", marshalLinks(in.Links()))
}
if svc, ok := in.Attributes().Get(semconv.AttributePeerService); ok {
// the span attribute "peer.service" takes precedence over any resource attributes,
// in the same way that "service.name" does as part of setMetaOTLP
Expand Down
110 changes: 110 additions & 0 deletions pkg/trace/api/otlp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,38 @@ var otlpTestSpanConfig = &testutil.OTLPSpan{
Dropped: 2,
},
},
Links: []testutil.OTLPSpanLink{
{
TraceID: "fedcba98765432100123456789abcdef",
SpanID: "abcdef0123456789",
Attributes: map[string]interface{}{
"a1": "v1",
"a2": "v2",
},
Dropped: 24,
},
{
TraceID: "abcdef0123456789abcdef0123456789",
SpanID: "fedcba9876543210",
Attributes: map[string]interface{}{
"a3": "v2",
"a4": "v4",
},
Dropped: 0,
},
{
TraceID: "abcdef0123456789abcdef0123456789",
SpanID: "fedcba9876543210",
Attributes: map[string]interface{}{},
Dropped: 2,
},
{
TraceID: "abcdef0123456789abcdef0123456789",
SpanID: "fedcba9876543210",
Attributes: map[string]interface{}{},
Dropped: 0,
},
},
StatusMsg: "Error",
StatusCode: ptrace.StatusCodeError,
}
Expand Down Expand Up @@ -825,6 +857,7 @@ func TestOTLPConvertSpan(t *testing.T) {
"w3c.tracestate": "state",
"version": "v1.2.3",
"events": `[{"time_unix_nano":123,"name":"boom","attributes":{"key":"Out of memory","accuracy":"2.4"},"dropped_attributes_count":2},{"time_unix_nano":456,"name":"exception","attributes":{"exception.message":"Out of memory","exception.type":"mem","exception.stacktrace":"1/2/3"},"dropped_attributes_count":2}]`,
"links": `[{"trace_id":"fedcba98765432100123456789abcdef","span_id":"abcdef0123456789","attributes":{"a1":"v1","a2":"v2"},"dropped_attributes_count":24},{"trace_id":"abcdef0123456789abcdef0123456789","span_id":"fedcba9876543210","attributes":{"a3":"v2","a4":"v4"}},{"trace_id":"abcdef0123456789abcdef0123456789","span_id":"fedcba9876543210","dropped_attributes_count":2},{"trace_id":"abcdef0123456789abcdef0123456789","span_id":"fedcba9876543210"}]`,
"error.msg": "Out of memory",
"error.type": "mem",
"error.stack": "1/2/3",
Expand Down Expand Up @@ -881,6 +914,38 @@ func TestOTLPConvertSpan(t *testing.T) {
Dropped: 2,
},
},
Links: []testutil.OTLPSpanLink{
{
TraceID: "fedcba98765432100123456789abcdef",
SpanID: "abcdef0123456789",
Attributes: map[string]interface{}{
"a1": "v1",
"a2": "v2",
},
Dropped: 24,
},
{
TraceID: "abcdef0123456789abcdef0123456789",
SpanID: "fedcba9876543210",
Attributes: map[string]interface{}{
"a3": "v2",
"a4": "v4",
},
Dropped: 0,
},
{
TraceID: "abcdef0123456789abcdef0123456789",
SpanID: "fedcba9876543210",
Attributes: map[string]interface{}{},
Dropped: 2,
},
{
TraceID: "abcdef0123456789abcdef0123456789",
SpanID: "fedcba9876543210",
Attributes: map[string]interface{}{},
Dropped: 0,
},
},
StatusMsg: "Error",
StatusCode: ptrace.StatusCodeError,
}),
Expand All @@ -907,6 +972,7 @@ func TestOTLPConvertSpan(t *testing.T) {
"w3c.tracestate": "state",
"version": "v1.2.3",
"events": "[{\"time_unix_nano\":123,\"name\":\"boom\",\"attributes\":{\"message\":\"Out of memory\",\"accuracy\":\"2.4\"},\"dropped_attributes_count\":2},{\"time_unix_nano\":456,\"name\":\"exception\",\"attributes\":{\"exception.message\":\"Out of memory\",\"exception.type\":\"mem\",\"exception.stacktrace\":\"1/2/3\"},\"dropped_attributes_count\":2}]",
"links": `[{"trace_id":"fedcba98765432100123456789abcdef","span_id":"abcdef0123456789","attributes":{"a1":"v1","a2":"v2"},"dropped_attributes_count":24},{"trace_id":"abcdef0123456789abcdef0123456789","span_id":"fedcba9876543210","attributes":{"a3":"v2","a4":"v4"}},{"trace_id":"abcdef0123456789abcdef0123456789","span_id":"fedcba9876543210","dropped_attributes_count":2},{"trace_id":"abcdef0123456789abcdef0123456789","span_id":"fedcba9876543210"}]`,
"error.msg": "Out of memory",
"error.type": "mem",
"error.stack": "1/2/3",
Expand Down Expand Up @@ -966,6 +1032,38 @@ func TestOTLPConvertSpan(t *testing.T) {
Dropped: 2,
},
},
Links: []testutil.OTLPSpanLink{
{
TraceID: "fedcba98765432100123456789abcdef",
SpanID: "abcdef0123456789",
Attributes: map[string]interface{}{
"a1": "v1",
"a2": "v2",
},
Dropped: 24,
},
{
TraceID: "abcdef0123456789abcdef0123456789",
SpanID: "fedcba9876543210",
Attributes: map[string]interface{}{
"a3": "v2",
"a4": "v4",
},
Dropped: 0,
},
{
TraceID: "abcdef0123456789abcdef0123456789",
SpanID: "fedcba9876543210",
Attributes: map[string]interface{}{},
Dropped: 2,
},
{
TraceID: "abcdef0123456789abcdef0123456789",
SpanID: "fedcba9876543210",
Attributes: map[string]interface{}{},
Dropped: 0,
},
},
StatusMsg: "Error",
StatusCode: ptrace.StatusCodeError,
}),
Expand All @@ -991,6 +1089,7 @@ func TestOTLPConvertSpan(t *testing.T) {
"version": "v1.2.3",
"otel.trace_id": "72df520af2bde7a5240031ead750e5f3",
"events": "[{\"time_unix_nano\":123,\"name\":\"boom\",\"attributes\":{\"message\":\"Out of memory\",\"accuracy\":\"2.4\"},\"dropped_attributes_count\":2},{\"time_unix_nano\":456,\"name\":\"exception\",\"attributes\":{\"exception.message\":\"Out of memory\",\"exception.type\":\"mem\",\"exception.stacktrace\":\"1/2/3\"},\"dropped_attributes_count\":2}]",
"links": `[{"trace_id":"fedcba98765432100123456789abcdef","span_id":"abcdef0123456789","attributes":{"a1":"v1","a2":"v2"},"dropped_attributes_count":24},{"trace_id":"abcdef0123456789abcdef0123456789","span_id":"fedcba9876543210","attributes":{"a3":"v2","a4":"v4"}},{"trace_id":"abcdef0123456789abcdef0123456789","span_id":"fedcba9876543210","dropped_attributes_count":2},{"trace_id":"abcdef0123456789abcdef0123456789","span_id":"fedcba9876543210"}]`,
"error.msg": "Out of memory",
"error.type": "mem",
"error.stack": "1/2/3",
Expand Down Expand Up @@ -1084,6 +1183,17 @@ func TestOTLPConvertSpan(t *testing.T) {
t.Fatalf("(%d) Error unmarshalling: %v", i, err)
}
assert.Equal(wante, gote)
case "links":
// links contain maps with no guaranteed order of
// traversal; best to unpack to compare
var gotl, wantl []testutil.OTLPSpanLink
if err := json.Unmarshal([]byte(v), &wantl); err != nil {
t.Fatalf("(%d) Error unmarshalling: %v", i, err)
}
if err := json.Unmarshal([]byte(got.Meta[k]), &gotl); err != nil {
t.Fatalf("(%d) Error unmarshalling: %v", i, err)
}
assert.Equal(wantl, gotl)
case "_dd.container_tags":
// order not guaranteed, so we need to unpack and sort to compare
gott := strings.Split(got.Meta[tagContainersTags], ",")
Expand Down
20 changes: 20 additions & 0 deletions pkg/trace/testutil/otlp.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
package testutil

import (
"encoding/hex"
"fmt"
"time"

Expand All @@ -29,6 +30,14 @@ type OTLPSpanEvent struct {
Dropped uint32 `json:"dropped_attributes_count"`
}

// OTLPSpanLink defines an OTLP test span link.
type OTLPSpanLink struct {
TraceID string `json:"trace_id"`
gbbr marked this conversation as resolved.
Show resolved Hide resolved
SpanID string `json:"span_id"`
Attributes map[string]interface{} `json:"attributes"`
Dropped uint32 `json:"dropped_attributes_count"`
}

// OTLPSpan defines an OTLP test span.
type OTLPSpan struct {
TraceID [16]byte
Expand All @@ -40,6 +49,7 @@ type OTLPSpan struct {
Start, End uint64
Attributes map[string]interface{}
Events []OTLPSpanEvent
Links []OTLPSpanLink
StatusMsg string
StatusCode ptrace.StatusCode
}
Expand Down Expand Up @@ -87,6 +97,16 @@ func setOTLPSpan(span ptrace.Span, s *OTLPSpan) {
insertAttributes(ev.Attributes(), e.Attributes)
ev.SetDroppedAttributesCount(e.Dropped)
}
links := span.Links()
for _, l := range s.Links {
li := links.AppendEmpty()
bytes, _ := hex.DecodeString(l.TraceID)
dinooliva marked this conversation as resolved.
Show resolved Hide resolved
dinooliva marked this conversation as resolved.
Show resolved Hide resolved
li.SetTraceID(*(*pcommon.TraceID)(bytes))
bytes, _ = hex.DecodeString(l.SpanID)
li.SetSpanID(*(*pcommon.SpanID)(bytes))
insertAttributes(li.Attributes(), l.Attributes)
li.SetDroppedAttributesCount(l.Dropped)
songy23 marked this conversation as resolved.
Show resolved Hide resolved
}
span.Status().SetCode(s.StatusCode)
span.Status().SetMessage(s.StatusMsg)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
enhancements:
- |
Adds support for OpenTelemetry span links to the Trace Agent otlp endpoint when converting OTLP spans (span links are added as metadata to the converted span).
dinooliva marked this conversation as resolved.
Show resolved Hide resolved