Skip to content

Commit

Permalink
[exporter/awsxray] Add span links and messenger field translation to …
Browse files Browse the repository at this point in the history
  • Loading branch information
atshaw43 committed Jun 16, 2023
1 parent 42bca6f commit a5cdd2f
Show file tree
Hide file tree
Showing 6 changed files with 309 additions and 4 deletions.
16 changes: 16 additions & 0 deletions .chloggen/awsxray-support-span-links-and-messaging-field.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
change_type: enhancement

# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver)
component: awsxrayexporter

# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
note: Adding translation support for span links for the aws x-ray exporter

# One or more tracking issues related to the change
issues: [20353]

# (Optional) One or more lines of additional information to render under the primary note.
# These lines will be padded with 2 spaces and then inserted directly into the document.
# Use pipe (|) for multiline entries.
subtext:
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
local/
vendor/

# GoLand IDEA
/.idea/
Expand Down
6 changes: 6 additions & 0 deletions exporter/awsxrayexporter/internal/translator/segment.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,10 +107,15 @@ func MakeSegment(span ptrace.Span, resource pcommon.Resource, indexedAttrs []str
sqlfiltered, sql = makeSQL(span, awsfiltered)
additionalAttrs = addSpecialAttributes(sqlfiltered, indexedAttrs, attributes)
user, annotations, metadata = makeXRayAttributes(additionalAttrs, resource, storeResource, indexedAttrs, indexAllAttrs)
spanLinks, makeSpanLinkErr = makeSpanLinks(span.Links())
name string
namespace string
)

if makeSpanLinkErr != nil {
return nil, makeSpanLinkErr
}

// X-Ray segment names are service names, unlike span names which are methods. Try to find a service name.

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

Expand Down
43 changes: 43 additions & 0 deletions exporter/awsxrayexporter/internal/translator/span_links.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package translator // import "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/awsxrayexporter/internal/translator"

import (
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/ptrace"

awsxray "github.com/open-telemetry/opentelemetry-collector-contrib/internal/aws/xray"
)

func makeSpanLinks(links ptrace.SpanLinkSlice) ([]awsxray.SpanLinkData, error) {
var spanLinkDataArray []awsxray.SpanLinkData

for i := 0; i < links.Len(); i++ {
var spanLinkData awsxray.SpanLinkData
var link = links.At(i)

var spanID = link.SpanID().String()
traceID, err := convertToAmazonTraceID(link.TraceID())

if err != nil {
return nil, err
}

spanLinkData.SpanID = &spanID
spanLinkData.TraceID = &traceID

if link.Attributes().Len() > 0 {
spanLinkData.Attributes = make(map[string]interface{})

link.Attributes().Range(func(k string, v pcommon.Value) bool {
spanLinkData.Attributes[k] = v.AsRaw()
return true
})
}

spanLinkDataArray = append(spanLinkDataArray, spanLinkData)
}

return spanLinkDataArray, nil
}
231 changes: 231 additions & 0 deletions exporter/awsxrayexporter/internal/translator/span_links_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package translator // import "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/awsxrayexporter/internal/translator"

import (
"encoding/binary"
"strings"
"testing"
"time"

"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/ptrace"
)

func TestSpanLinkSimple(t *testing.T) {
spanName := "ProcessingMessage"
parentSpanID := newSegmentID()
attributes := make(map[string]interface{})
resource := constructDefaultResource()
span := constructServerSpan(parentSpanID, spanName, ptrace.StatusCodeOk, "OK", attributes)

var traceID = newTraceID()

spanLink := span.Links().AppendEmpty()
spanLink.SetTraceID(traceID)
spanLink.SetSpanID(newSegmentID())

segment, _ := MakeSegment(span, resource, nil, false, nil)

var convertedTraceID, _ = convertToAmazonTraceID(traceID)

assert.Equal(t, 1, len(segment.Links))
assert.Equal(t, spanLink.SpanID().String(), *segment.Links[0].SpanID)
assert.Equal(t, convertedTraceID, *segment.Links[0].TraceID)
assert.Equal(t, 0, len(segment.Links[0].Attributes))

jsonStr, _ := MakeSegmentDocumentString(span, resource, nil, false, nil)

assert.True(t, strings.Contains(jsonStr, "links"))
assert.False(t, strings.Contains(jsonStr, "attributes"))
assert.True(t, strings.Contains(jsonStr, convertedTraceID))
assert.True(t, strings.Contains(jsonStr, spanLink.SpanID().String()))
}

func TestSpanLinkEmpty(t *testing.T) {
spanName := "ProcessingMessage"
parentSpanID := newSegmentID()
attributes := make(map[string]interface{})
resource := constructDefaultResource()
span := constructServerSpan(parentSpanID, spanName, ptrace.StatusCodeOk, "OK", attributes)

segment, _ := MakeSegment(span, resource, nil, false, nil)

assert.Equal(t, 0, len(segment.Links))

jsonStr, _ := MakeSegmentDocumentString(span, resource, nil, false, nil)

assert.False(t, strings.Contains(jsonStr, "links"))
}

func TestOldSpanLinkError(t *testing.T) {
spanName := "ProcessingMessage"
parentSpanID := newSegmentID()
attributes := make(map[string]interface{})
resource := constructDefaultResource()
span := constructServerSpan(parentSpanID, spanName, ptrace.StatusCodeOk, "OK", attributes)

const maxAge = 60 * 60 * 24 * 30
ExpiredEpoch := time.Now().Unix() - maxAge - 1

var traceID = newTraceID()
binary.BigEndian.PutUint32(traceID[0:4], uint32(ExpiredEpoch))

spanLink := span.Links().AppendEmpty()
spanLink.SetTraceID(traceID)
spanLink.SetSpanID(newSegmentID())

_, error1 := MakeSegment(span, resource, nil, false, nil)

assert.NotNil(t, error1)

_, error2 := MakeSegmentDocumentString(span, resource, nil, false, nil)

assert.NotNil(t, error2)
}

func TestTwoSpanLinks(t *testing.T) {
spanName := "ProcessingMessage"
parentSpanID := newSegmentID()
attributes := make(map[string]interface{})
resource := constructDefaultResource()
span := constructServerSpan(parentSpanID, spanName, ptrace.StatusCodeOk, "OK", attributes)

var traceID1 = newTraceID()

spanLink1 := span.Links().AppendEmpty()
spanLink1.SetTraceID(traceID1)
spanLink1.SetSpanID(newSegmentID())
spanLink1.Attributes().PutStr("myKey1", "ABC")

var traceID2 = newTraceID()

spanLink2 := span.Links().AppendEmpty()
spanLink2.SetTraceID(traceID2)
spanLink2.SetSpanID(newSegmentID())
spanLink2.Attributes().PutInt("myKey2", 1234)

segment, _ := MakeSegment(span, resource, nil, false, nil)

var convertedTraceID1, _ = convertToAmazonTraceID(traceID1)
var convertedTraceID2, _ = convertToAmazonTraceID(traceID2)

assert.Equal(t, 2, len(segment.Links))
assert.Equal(t, spanLink1.SpanID().String(), *segment.Links[0].SpanID)
assert.Equal(t, convertedTraceID1, *segment.Links[0].TraceID)

assert.Equal(t, 1, len(segment.Links[0].Attributes))
assert.Equal(t, "ABC", segment.Links[0].Attributes["myKey1"])

assert.Equal(t, spanLink2.SpanID().String(), *segment.Links[1].SpanID)
assert.Equal(t, convertedTraceID2, *segment.Links[1].TraceID)
assert.Equal(t, 1, len(segment.Links[0].Attributes))
assert.Equal(t, int64(1234), segment.Links[1].Attributes["myKey2"])

jsonStr, _ := MakeSegmentDocumentString(span, resource, nil, false, nil)

assert.True(t, strings.Contains(jsonStr, "attributes"))
assert.True(t, strings.Contains(jsonStr, "links"))
assert.True(t, strings.Contains(jsonStr, "myKey1"))
assert.True(t, strings.Contains(jsonStr, "myKey2"))
assert.True(t, strings.Contains(jsonStr, "ABC"))
assert.True(t, strings.Contains(jsonStr, "1234"))
assert.True(t, strings.Contains(jsonStr, convertedTraceID1))
assert.True(t, strings.Contains(jsonStr, convertedTraceID2))
}

func TestSpanLinkComplexAttributes(t *testing.T) {
spanName := "ProcessingMessage"
parentSpanID := newSegmentID()
attributes := make(map[string]interface{})
resource := constructDefaultResource()
span := constructServerSpan(parentSpanID, spanName, ptrace.StatusCodeOk, "OK", attributes)

spanLink := span.Links().AppendEmpty()
spanLink.SetTraceID(newTraceID())
spanLink.SetSpanID(newSegmentID())
spanLink.Attributes().PutStr("myKey1", "myValue")
spanLink.Attributes().PutBool("myKey2", true)
spanLink.Attributes().PutInt("myKey3", 112233)
spanLink.Attributes().PutDouble("myKey4", 3.1415)

var slice1 = spanLink.Attributes().PutEmptySlice("myKey5")
slice1.AppendEmpty().SetStr("apple")
slice1.AppendEmpty().SetStr("pear")
slice1.AppendEmpty().SetStr("banana")

var slice2 = spanLink.Attributes().PutEmptySlice("myKey6")
slice2.AppendEmpty().SetBool(true)
slice2.AppendEmpty().SetBool(false)
slice2.AppendEmpty().SetBool(false)
slice2.AppendEmpty().SetBool(true)

var slice3 = spanLink.Attributes().PutEmptySlice("myKey7")
slice3.AppendEmpty().SetInt(1234)
slice3.AppendEmpty().SetInt(5678)
slice3.AppendEmpty().SetInt(9012)

var slice4 = spanLink.Attributes().PutEmptySlice("myKey8")
slice4.AppendEmpty().SetDouble(2.718)
slice4.AppendEmpty().SetDouble(1.618)

segment, _ := MakeSegment(span, resource, nil, false, nil)

assert.Equal(t, 1, len(segment.Links))
assert.Equal(t, 8, len(segment.Links[0].Attributes))

assert.Equal(t, "myValue", segment.Links[0].Attributes["myKey1"])
assert.Equal(t, true, segment.Links[0].Attributes["myKey2"])
assert.Equal(t, int64(112233), segment.Links[0].Attributes["myKey3"])
assert.Equal(t, 3.1415, segment.Links[0].Attributes["myKey4"])

assert.Equal(t, "apple", segment.Links[0].Attributes["myKey5"].([]interface{})[0])
assert.Equal(t, "pear", segment.Links[0].Attributes["myKey5"].([]interface{})[1])
assert.Equal(t, "banana", segment.Links[0].Attributes["myKey5"].([]interface{})[2])

assert.Equal(t, true, segment.Links[0].Attributes["myKey6"].([]interface{})[0])
assert.Equal(t, false, segment.Links[0].Attributes["myKey6"].([]interface{})[1])
assert.Equal(t, false, segment.Links[0].Attributes["myKey6"].([]interface{})[2])
assert.Equal(t, true, segment.Links[0].Attributes["myKey6"].([]interface{})[0])

assert.Equal(t, int64(1234), segment.Links[0].Attributes["myKey7"].([]interface{})[0])
assert.Equal(t, int64(5678), segment.Links[0].Attributes["myKey7"].([]interface{})[1])
assert.Equal(t, int64(9012), segment.Links[0].Attributes["myKey7"].([]interface{})[2])

assert.Equal(t, 2.718, segment.Links[0].Attributes["myKey8"].([]interface{})[0])
assert.Equal(t, 1.618, segment.Links[0].Attributes["myKey8"].([]interface{})[1])

jsonStr, _ := MakeSegmentDocumentString(span, resource, nil, false, nil)

assert.True(t, strings.Contains(jsonStr, "links"))

assert.True(t, strings.Contains(jsonStr, "myKey1"))
assert.True(t, strings.Contains(jsonStr, "myValue"))

assert.True(t, strings.Contains(jsonStr, "myKey2"))
assert.True(t, strings.Contains(jsonStr, "true"))

assert.True(t, strings.Contains(jsonStr, "myKey3"))
assert.True(t, strings.Contains(jsonStr, "112233"))

assert.True(t, strings.Contains(jsonStr, "myKey4"))
assert.True(t, strings.Contains(jsonStr, "3.1415"))

assert.True(t, strings.Contains(jsonStr, "myKey5"))
assert.True(t, strings.Contains(jsonStr, "apple"))
assert.True(t, strings.Contains(jsonStr, "pear"))
assert.True(t, strings.Contains(jsonStr, "banana"))

assert.True(t, strings.Contains(jsonStr, "myKey6"))
assert.True(t, strings.Contains(jsonStr, "false"))

assert.True(t, strings.Contains(jsonStr, "myKey7"))
assert.True(t, strings.Contains(jsonStr, "1234"))
assert.True(t, strings.Contains(jsonStr, "5678"))
assert.True(t, strings.Contains(jsonStr, "9012"))

assert.True(t, strings.Contains(jsonStr, "myKey8"))
assert.True(t, strings.Contains(jsonStr, "2.718"))
assert.True(t, strings.Contains(jsonStr, "1.618"))
}
16 changes: 12 additions & 4 deletions internal/aws/xray/tracesegment.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,11 @@ type Segment struct {
StartTime *float64 `json:"start_time"`

// Segment-only optional fields
Service *ServiceData `json:"service,omitempty"`
Origin *string `json:"origin,omitempty"`
User *string `json:"user,omitempty"`
ResourceARN *string `json:"resource_arn,omitempty"`
Service *ServiceData `json:"service,omitempty"`
Origin *string `json:"origin,omitempty"`
User *string `json:"user,omitempty"`
ResourceARN *string `json:"resource_arn,omitempty"`
Links []SpanLinkData `json:"links,omitempty"`

// Optional fields for both Segment and subsegments
TraceID *string `json:"trace_id,omitempty"`
Expand Down Expand Up @@ -265,3 +266,10 @@ type ServiceData struct {
CompilerVersion *string `json:"compiler_version,omitempty"`
Compiler *string `json:"compiler,omitempty"`
}

// SpanLinkData provides the shape for unmarshalling the span links in the span link field.
type SpanLinkData struct {
TraceID *string `json:"trace_id"`
SpanID *string `json:"id"`
Attributes map[string]interface{} `json:"attributes,omitempty"`
}

0 comments on commit a5cdd2f

Please sign in to comment.