Skip to content

Commit a5cdd2f

Browse files
authored
[exporter/awsxray] Add span links and messenger field translation to x-ray exporter. (open-telemetry#20313)
Add link span link support and support for the messaging field. We are conforming to the OTel spec: - https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/api.md#specifying-links - https://github.com/open-telemetry/opentelemetry-specification/blob/main/semantic_conventions/trace/messaging.yaml#L110
1 parent 42bca6f commit a5cdd2f

File tree

6 files changed

+309
-4
lines changed

6 files changed

+309
-4
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
2+
change_type: enhancement
3+
4+
# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver)
5+
component: awsxrayexporter
6+
7+
# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
8+
note: Adding translation support for span links for the aws x-ray exporter
9+
10+
# One or more tracking issues related to the change
11+
issues: [20353]
12+
13+
# (Optional) One or more lines of additional information to render under the primary note.
14+
# These lines will be padded with 2 spaces and then inserted directly into the document.
15+
# Use pipe (|) for multiline entries.
16+
subtext:

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
local/
2+
vendor/
23

34
# GoLand IDEA
45
/.idea/

exporter/awsxrayexporter/internal/translator/segment.go

+6
Original file line numberDiff line numberDiff line change
@@ -107,10 +107,15 @@ func MakeSegment(span ptrace.Span, resource pcommon.Resource, indexedAttrs []str
107107
sqlfiltered, sql = makeSQL(span, awsfiltered)
108108
additionalAttrs = addSpecialAttributes(sqlfiltered, indexedAttrs, attributes)
109109
user, annotations, metadata = makeXRayAttributes(additionalAttrs, resource, storeResource, indexedAttrs, indexAllAttrs)
110+
spanLinks, makeSpanLinkErr = makeSpanLinks(span.Links())
110111
name string
111112
namespace string
112113
)
113114

115+
if makeSpanLinkErr != nil {
116+
return nil, makeSpanLinkErr
117+
}
118+
114119
// X-Ray segment names are service names, unlike span names which are methods. Try to find a service name.
115120

116121
// support x-ray specific service name attributes as segment name if it exists
@@ -221,6 +226,7 @@ func MakeSegment(span ptrace.Span, resource pcommon.Resource, indexedAttrs []str
221226
Annotations: annotations,
222227
Metadata: metadata,
223228
Type: awsxray.String(segmentType),
229+
Links: spanLinks,
224230
}, nil
225231
}
226232

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package translator // import "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/awsxrayexporter/internal/translator"
5+
6+
import (
7+
"go.opentelemetry.io/collector/pdata/pcommon"
8+
"go.opentelemetry.io/collector/pdata/ptrace"
9+
10+
awsxray "github.com/open-telemetry/opentelemetry-collector-contrib/internal/aws/xray"
11+
)
12+
13+
func makeSpanLinks(links ptrace.SpanLinkSlice) ([]awsxray.SpanLinkData, error) {
14+
var spanLinkDataArray []awsxray.SpanLinkData
15+
16+
for i := 0; i < links.Len(); i++ {
17+
var spanLinkData awsxray.SpanLinkData
18+
var link = links.At(i)
19+
20+
var spanID = link.SpanID().String()
21+
traceID, err := convertToAmazonTraceID(link.TraceID())
22+
23+
if err != nil {
24+
return nil, err
25+
}
26+
27+
spanLinkData.SpanID = &spanID
28+
spanLinkData.TraceID = &traceID
29+
30+
if link.Attributes().Len() > 0 {
31+
spanLinkData.Attributes = make(map[string]interface{})
32+
33+
link.Attributes().Range(func(k string, v pcommon.Value) bool {
34+
spanLinkData.Attributes[k] = v.AsRaw()
35+
return true
36+
})
37+
}
38+
39+
spanLinkDataArray = append(spanLinkDataArray, spanLinkData)
40+
}
41+
42+
return spanLinkDataArray, nil
43+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package translator // import "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/awsxrayexporter/internal/translator"
5+
6+
import (
7+
"encoding/binary"
8+
"strings"
9+
"testing"
10+
"time"
11+
12+
"github.com/stretchr/testify/assert"
13+
"go.opentelemetry.io/collector/pdata/ptrace"
14+
)
15+
16+
func TestSpanLinkSimple(t *testing.T) {
17+
spanName := "ProcessingMessage"
18+
parentSpanID := newSegmentID()
19+
attributes := make(map[string]interface{})
20+
resource := constructDefaultResource()
21+
span := constructServerSpan(parentSpanID, spanName, ptrace.StatusCodeOk, "OK", attributes)
22+
23+
var traceID = newTraceID()
24+
25+
spanLink := span.Links().AppendEmpty()
26+
spanLink.SetTraceID(traceID)
27+
spanLink.SetSpanID(newSegmentID())
28+
29+
segment, _ := MakeSegment(span, resource, nil, false, nil)
30+
31+
var convertedTraceID, _ = convertToAmazonTraceID(traceID)
32+
33+
assert.Equal(t, 1, len(segment.Links))
34+
assert.Equal(t, spanLink.SpanID().String(), *segment.Links[0].SpanID)
35+
assert.Equal(t, convertedTraceID, *segment.Links[0].TraceID)
36+
assert.Equal(t, 0, len(segment.Links[0].Attributes))
37+
38+
jsonStr, _ := MakeSegmentDocumentString(span, resource, nil, false, nil)
39+
40+
assert.True(t, strings.Contains(jsonStr, "links"))
41+
assert.False(t, strings.Contains(jsonStr, "attributes"))
42+
assert.True(t, strings.Contains(jsonStr, convertedTraceID))
43+
assert.True(t, strings.Contains(jsonStr, spanLink.SpanID().String()))
44+
}
45+
46+
func TestSpanLinkEmpty(t *testing.T) {
47+
spanName := "ProcessingMessage"
48+
parentSpanID := newSegmentID()
49+
attributes := make(map[string]interface{})
50+
resource := constructDefaultResource()
51+
span := constructServerSpan(parentSpanID, spanName, ptrace.StatusCodeOk, "OK", attributes)
52+
53+
segment, _ := MakeSegment(span, resource, nil, false, nil)
54+
55+
assert.Equal(t, 0, len(segment.Links))
56+
57+
jsonStr, _ := MakeSegmentDocumentString(span, resource, nil, false, nil)
58+
59+
assert.False(t, strings.Contains(jsonStr, "links"))
60+
}
61+
62+
func TestOldSpanLinkError(t *testing.T) {
63+
spanName := "ProcessingMessage"
64+
parentSpanID := newSegmentID()
65+
attributes := make(map[string]interface{})
66+
resource := constructDefaultResource()
67+
span := constructServerSpan(parentSpanID, spanName, ptrace.StatusCodeOk, "OK", attributes)
68+
69+
const maxAge = 60 * 60 * 24 * 30
70+
ExpiredEpoch := time.Now().Unix() - maxAge - 1
71+
72+
var traceID = newTraceID()
73+
binary.BigEndian.PutUint32(traceID[0:4], uint32(ExpiredEpoch))
74+
75+
spanLink := span.Links().AppendEmpty()
76+
spanLink.SetTraceID(traceID)
77+
spanLink.SetSpanID(newSegmentID())
78+
79+
_, error1 := MakeSegment(span, resource, nil, false, nil)
80+
81+
assert.NotNil(t, error1)
82+
83+
_, error2 := MakeSegmentDocumentString(span, resource, nil, false, nil)
84+
85+
assert.NotNil(t, error2)
86+
}
87+
88+
func TestTwoSpanLinks(t *testing.T) {
89+
spanName := "ProcessingMessage"
90+
parentSpanID := newSegmentID()
91+
attributes := make(map[string]interface{})
92+
resource := constructDefaultResource()
93+
span := constructServerSpan(parentSpanID, spanName, ptrace.StatusCodeOk, "OK", attributes)
94+
95+
var traceID1 = newTraceID()
96+
97+
spanLink1 := span.Links().AppendEmpty()
98+
spanLink1.SetTraceID(traceID1)
99+
spanLink1.SetSpanID(newSegmentID())
100+
spanLink1.Attributes().PutStr("myKey1", "ABC")
101+
102+
var traceID2 = newTraceID()
103+
104+
spanLink2 := span.Links().AppendEmpty()
105+
spanLink2.SetTraceID(traceID2)
106+
spanLink2.SetSpanID(newSegmentID())
107+
spanLink2.Attributes().PutInt("myKey2", 1234)
108+
109+
segment, _ := MakeSegment(span, resource, nil, false, nil)
110+
111+
var convertedTraceID1, _ = convertToAmazonTraceID(traceID1)
112+
var convertedTraceID2, _ = convertToAmazonTraceID(traceID2)
113+
114+
assert.Equal(t, 2, len(segment.Links))
115+
assert.Equal(t, spanLink1.SpanID().String(), *segment.Links[0].SpanID)
116+
assert.Equal(t, convertedTraceID1, *segment.Links[0].TraceID)
117+
118+
assert.Equal(t, 1, len(segment.Links[0].Attributes))
119+
assert.Equal(t, "ABC", segment.Links[0].Attributes["myKey1"])
120+
121+
assert.Equal(t, spanLink2.SpanID().String(), *segment.Links[1].SpanID)
122+
assert.Equal(t, convertedTraceID2, *segment.Links[1].TraceID)
123+
assert.Equal(t, 1, len(segment.Links[0].Attributes))
124+
assert.Equal(t, int64(1234), segment.Links[1].Attributes["myKey2"])
125+
126+
jsonStr, _ := MakeSegmentDocumentString(span, resource, nil, false, nil)
127+
128+
assert.True(t, strings.Contains(jsonStr, "attributes"))
129+
assert.True(t, strings.Contains(jsonStr, "links"))
130+
assert.True(t, strings.Contains(jsonStr, "myKey1"))
131+
assert.True(t, strings.Contains(jsonStr, "myKey2"))
132+
assert.True(t, strings.Contains(jsonStr, "ABC"))
133+
assert.True(t, strings.Contains(jsonStr, "1234"))
134+
assert.True(t, strings.Contains(jsonStr, convertedTraceID1))
135+
assert.True(t, strings.Contains(jsonStr, convertedTraceID2))
136+
}
137+
138+
func TestSpanLinkComplexAttributes(t *testing.T) {
139+
spanName := "ProcessingMessage"
140+
parentSpanID := newSegmentID()
141+
attributes := make(map[string]interface{})
142+
resource := constructDefaultResource()
143+
span := constructServerSpan(parentSpanID, spanName, ptrace.StatusCodeOk, "OK", attributes)
144+
145+
spanLink := span.Links().AppendEmpty()
146+
spanLink.SetTraceID(newTraceID())
147+
spanLink.SetSpanID(newSegmentID())
148+
spanLink.Attributes().PutStr("myKey1", "myValue")
149+
spanLink.Attributes().PutBool("myKey2", true)
150+
spanLink.Attributes().PutInt("myKey3", 112233)
151+
spanLink.Attributes().PutDouble("myKey4", 3.1415)
152+
153+
var slice1 = spanLink.Attributes().PutEmptySlice("myKey5")
154+
slice1.AppendEmpty().SetStr("apple")
155+
slice1.AppendEmpty().SetStr("pear")
156+
slice1.AppendEmpty().SetStr("banana")
157+
158+
var slice2 = spanLink.Attributes().PutEmptySlice("myKey6")
159+
slice2.AppendEmpty().SetBool(true)
160+
slice2.AppendEmpty().SetBool(false)
161+
slice2.AppendEmpty().SetBool(false)
162+
slice2.AppendEmpty().SetBool(true)
163+
164+
var slice3 = spanLink.Attributes().PutEmptySlice("myKey7")
165+
slice3.AppendEmpty().SetInt(1234)
166+
slice3.AppendEmpty().SetInt(5678)
167+
slice3.AppendEmpty().SetInt(9012)
168+
169+
var slice4 = spanLink.Attributes().PutEmptySlice("myKey8")
170+
slice4.AppendEmpty().SetDouble(2.718)
171+
slice4.AppendEmpty().SetDouble(1.618)
172+
173+
segment, _ := MakeSegment(span, resource, nil, false, nil)
174+
175+
assert.Equal(t, 1, len(segment.Links))
176+
assert.Equal(t, 8, len(segment.Links[0].Attributes))
177+
178+
assert.Equal(t, "myValue", segment.Links[0].Attributes["myKey1"])
179+
assert.Equal(t, true, segment.Links[0].Attributes["myKey2"])
180+
assert.Equal(t, int64(112233), segment.Links[0].Attributes["myKey3"])
181+
assert.Equal(t, 3.1415, segment.Links[0].Attributes["myKey4"])
182+
183+
assert.Equal(t, "apple", segment.Links[0].Attributes["myKey5"].([]interface{})[0])
184+
assert.Equal(t, "pear", segment.Links[0].Attributes["myKey5"].([]interface{})[1])
185+
assert.Equal(t, "banana", segment.Links[0].Attributes["myKey5"].([]interface{})[2])
186+
187+
assert.Equal(t, true, segment.Links[0].Attributes["myKey6"].([]interface{})[0])
188+
assert.Equal(t, false, segment.Links[0].Attributes["myKey6"].([]interface{})[1])
189+
assert.Equal(t, false, segment.Links[0].Attributes["myKey6"].([]interface{})[2])
190+
assert.Equal(t, true, segment.Links[0].Attributes["myKey6"].([]interface{})[0])
191+
192+
assert.Equal(t, int64(1234), segment.Links[0].Attributes["myKey7"].([]interface{})[0])
193+
assert.Equal(t, int64(5678), segment.Links[0].Attributes["myKey7"].([]interface{})[1])
194+
assert.Equal(t, int64(9012), segment.Links[0].Attributes["myKey7"].([]interface{})[2])
195+
196+
assert.Equal(t, 2.718, segment.Links[0].Attributes["myKey8"].([]interface{})[0])
197+
assert.Equal(t, 1.618, segment.Links[0].Attributes["myKey8"].([]interface{})[1])
198+
199+
jsonStr, _ := MakeSegmentDocumentString(span, resource, nil, false, nil)
200+
201+
assert.True(t, strings.Contains(jsonStr, "links"))
202+
203+
assert.True(t, strings.Contains(jsonStr, "myKey1"))
204+
assert.True(t, strings.Contains(jsonStr, "myValue"))
205+
206+
assert.True(t, strings.Contains(jsonStr, "myKey2"))
207+
assert.True(t, strings.Contains(jsonStr, "true"))
208+
209+
assert.True(t, strings.Contains(jsonStr, "myKey3"))
210+
assert.True(t, strings.Contains(jsonStr, "112233"))
211+
212+
assert.True(t, strings.Contains(jsonStr, "myKey4"))
213+
assert.True(t, strings.Contains(jsonStr, "3.1415"))
214+
215+
assert.True(t, strings.Contains(jsonStr, "myKey5"))
216+
assert.True(t, strings.Contains(jsonStr, "apple"))
217+
assert.True(t, strings.Contains(jsonStr, "pear"))
218+
assert.True(t, strings.Contains(jsonStr, "banana"))
219+
220+
assert.True(t, strings.Contains(jsonStr, "myKey6"))
221+
assert.True(t, strings.Contains(jsonStr, "false"))
222+
223+
assert.True(t, strings.Contains(jsonStr, "myKey7"))
224+
assert.True(t, strings.Contains(jsonStr, "1234"))
225+
assert.True(t, strings.Contains(jsonStr, "5678"))
226+
assert.True(t, strings.Contains(jsonStr, "9012"))
227+
228+
assert.True(t, strings.Contains(jsonStr, "myKey8"))
229+
assert.True(t, strings.Contains(jsonStr, "2.718"))
230+
assert.True(t, strings.Contains(jsonStr, "1.618"))
231+
}

internal/aws/xray/tracesegment.go

+12-4
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,11 @@ type Segment struct {
2929
StartTime *float64 `json:"start_time"`
3030

3131
// Segment-only optional fields
32-
Service *ServiceData `json:"service,omitempty"`
33-
Origin *string `json:"origin,omitempty"`
34-
User *string `json:"user,omitempty"`
35-
ResourceARN *string `json:"resource_arn,omitempty"`
32+
Service *ServiceData `json:"service,omitempty"`
33+
Origin *string `json:"origin,omitempty"`
34+
User *string `json:"user,omitempty"`
35+
ResourceARN *string `json:"resource_arn,omitempty"`
36+
Links []SpanLinkData `json:"links,omitempty"`
3637

3738
// Optional fields for both Segment and subsegments
3839
TraceID *string `json:"trace_id,omitempty"`
@@ -265,3 +266,10 @@ type ServiceData struct {
265266
CompilerVersion *string `json:"compiler_version,omitempty"`
266267
Compiler *string `json:"compiler,omitempty"`
267268
}
269+
270+
// SpanLinkData provides the shape for unmarshalling the span links in the span link field.
271+
type SpanLinkData struct {
272+
TraceID *string `json:"trace_id"`
273+
SpanID *string `json:"id"`
274+
Attributes map[string]interface{} `json:"attributes,omitempty"`
275+
}

0 commit comments

Comments
 (0)