Skip to content

Commit

Permalink
[exporter/awsxray] Adjust AwsXRay segment conversion logic (#33000)
Browse files Browse the repository at this point in the history
**Description:** <Describe what has changed.>
<!--Ex. Fixing a bug - Describe the bug and how this fixes the issue.
Ex. Adding a feature - Explain what this achieves.-->
Cherry-picking from downstream:

amazon-contributing#111

amazon-contributing#115

amazon-contributing#127

- We are adjusting the segment creation to accommodate local root spans.
  If a span is a not a local root, then we keep existing behavior.
  If it is a local root then:
    - If it is an Internal or Server span, then promote it to a segment.
Else we will split it into a segment and subsegment. The segment will
represent the service operation and the subsegment will represent the
dependency (service A calls service B).
- Update the common logic for setting segment.Name, which previously
only looked at CLIENT/PRODUCER spans, to also look at CONSUMER spans.

**Link to tracking Issue:** <Issue number if applicable>

**Testing:** <Describe what testing was performed and which tests were
added.>
Unit Testing

**Documentation:** <Describe the documentation added.>

---------

Co-authored-by: atshaw43 <[email protected]>
Co-authored-by: Thomas Pierce <[email protected]>
Co-authored-by: John Knollmeyer <[email protected]>
  • Loading branch information
4 people authored May 22, 2024
1 parent a80d39b commit 902d846
Show file tree
Hide file tree
Showing 4 changed files with 825 additions and 21 deletions.
27 changes: 27 additions & 0 deletions .chloggen/awsxrayexporter_localrootspans.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Use this changelog template to create an entry for release notes.

# 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: AWS X-Ray exporter to make local root spans a segment for internal/service spans and subsegment + segment for client/producer/consumer spans.

# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists.
issues: [33000]

# (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:

# If your change doesn't affect end users or the exported elements of any package,
# you should instead start your pull request title with [chore] or use the "Skip Changelog" label.
# Optional: The change log or logs in which this entry should be included.
# e.g. '[user]' or '[user, api]'
# Include 'user' if the change is relevant to end users.
# Include 'api' if there is a change to a library API.
# Default: '[user]'
change_logs: [user]
8 changes: 6 additions & 2 deletions exporter/awsxrayexporter/awsxray.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,17 +105,21 @@ func extractResourceSpans(config component.Config, logger *zap.Logger, td ptrace
for j := 0; j < rspans.ScopeSpans().Len(); j++ {
spans := rspans.ScopeSpans().At(j).Spans()
for k := 0; k < spans.Len(); k++ {
document, localErr := translator.MakeSegmentDocumentString(
documentsForSpan, localErr := translator.MakeSegmentDocuments(
spans.At(k), resource,
config.(*Config).IndexedAttributes,
config.(*Config).IndexAllAttributes,
config.(*Config).LogGroupNames,
config.(*Config).skipTimestampValidation)

if localErr != nil {
logger.Debug("Error translating span.", zap.Error(localErr))
continue
}
documents = append(documents, &document)

for l := range documentsForSpan {
documents = append(documents, &documentsForSpan[l])
}
}
}
}
Expand Down
251 changes: 243 additions & 8 deletions exporter/awsxrayexporter/internal/translator/segment.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,13 @@ const (

// x-ray only span attributes - https://github.com/open-telemetry/opentelemetry-java-contrib/pull/802
const (
awsLocalService = "aws.local.service"
awsRemoteService = "aws.remote.service"
awsLocalService = "aws.local.service"
awsRemoteService = "aws.remote.service"
awsLocalOperation = "aws.local.operation"
awsRemoteOperation = "aws.remote.operation"
remoteTarget = "remoteTarget"
awsSpanKind = "aws.span.kind"
k8sRemoteNamespace = "K8s.RemoteNamespace"
)

var (
Expand Down Expand Up @@ -74,16 +79,233 @@ const (
identifierOffset = 11 // offset of identifier within traceID
)

const (
localRoot = "LOCAL_ROOT"
)

var removeAnnotationsFromServiceSegment = []string{
awsRemoteService,
awsRemoteOperation,
remoteTarget,
k8sRemoteNamespace,
}

var (
writers = newWriterPool(2048)
)

// MakeSegmentDocumentString converts an OpenTelemetry Span to an X-Ray Segment and then serialzies to JSON
// MakeSegmentDocuments converts spans to json documents
func MakeSegmentDocuments(span ptrace.Span, resource pcommon.Resource, indexedAttrs []string, indexAllAttrs bool, logGroupNames []string, skipTimestampValidation bool) ([]string, error) {
segments, err := MakeSegmentsFromSpan(span, resource, indexedAttrs, indexAllAttrs, logGroupNames, skipTimestampValidation)

if err == nil {
var documents []string

for _, v := range segments {
document, documentErr := MakeDocumentFromSegment(v)
if documentErr != nil {
return nil, documentErr
}

documents = append(documents, document)
}

return documents, nil
}

return nil, err
}

func isLocalRootSpanADependencySpan(span ptrace.Span) bool {
return span.Kind() != ptrace.SpanKindServer &&
span.Kind() != ptrace.SpanKindInternal
}

// isLocalRoot - we will move to using isRemote once the collector supports deserializing it. Until then, we will rely on aws.span.kind.
func isLocalRoot(span ptrace.Span) bool {
if myAwsSpanKind, ok := span.Attributes().Get(awsSpanKind); ok {
return localRoot == myAwsSpanKind.Str()
}

return false
}

func addNamespaceToSubsegmentWithRemoteService(span ptrace.Span, segment *awsxray.Segment) {
if (span.Kind() == ptrace.SpanKindClient ||
span.Kind() == ptrace.SpanKindConsumer ||
span.Kind() == ptrace.SpanKindProducer) &&
segment.Type != nil &&
segment.Namespace == nil {
if _, ok := span.Attributes().Get(awsRemoteService); ok {
segment.Namespace = awsxray.String("remote")
}
}
}

func MakeDependencySubsegmentForLocalRootDependencySpan(span ptrace.Span, resource pcommon.Resource, indexedAttrs []string, indexAllAttrs bool, logGroupNames []string, skipTimestampValidation bool, serviceSegmentID pcommon.SpanID) (*awsxray.Segment, error) {
var dependencySpan = ptrace.NewSpan()
span.CopyTo(dependencySpan)

dependencySpan.SetParentSpanID(serviceSegmentID)

dependencySubsegment, err := MakeSegment(dependencySpan, resource, indexedAttrs, indexAllAttrs, logGroupNames, skipTimestampValidation)

if err != nil {
return nil, err
}

// Make this a subsegment
dependencySubsegment.Type = awsxray.String("subsegment")

if dependencySubsegment.Namespace == nil {
dependencySubsegment.Namespace = awsxray.String("remote")
}

// Remove span links from consumer spans
if span.Kind() == ptrace.SpanKindConsumer {
dependencySubsegment.Links = nil
}

if myAwsRemoteService, ok := span.Attributes().Get(awsRemoteService); ok {
subsegmentName := myAwsRemoteService.Str()
dependencySubsegment.Name = awsxray.String(trimAwsSdkPrefix(subsegmentName, span))
}

return dependencySubsegment, err
}

func MakeServiceSegmentForLocalRootDependencySpan(span ptrace.Span, resource pcommon.Resource, indexedAttrs []string, indexAllAttrs bool, logGroupNames []string, skipTimestampValidation bool, serviceSegmentID pcommon.SpanID) (*awsxray.Segment, error) {
// We always create a segment for the service
var serviceSpan ptrace.Span = ptrace.NewSpan()
span.CopyTo(serviceSpan)

// Set the span id to the one internally generated
serviceSpan.SetSpanID(serviceSegmentID)

for _, v := range removeAnnotationsFromServiceSegment {
serviceSpan.Attributes().Remove(v)
}

serviceSegment, err := MakeSegment(serviceSpan, resource, indexedAttrs, indexAllAttrs, logGroupNames, skipTimestampValidation)

if err != nil {
return nil, err
}

// Set the name
if myAwsLocalService, ok := span.Attributes().Get(awsLocalService); ok {
serviceSegment.Name = awsxray.String(myAwsLocalService.Str())
}

// Remove the HTTP field
serviceSegment.HTTP = nil

// Remove AWS subsegment fields
serviceSegment.AWS.Operation = nil
serviceSegment.AWS.AccountID = nil
serviceSegment.AWS.RemoteRegion = nil
serviceSegment.AWS.RequestID = nil
serviceSegment.AWS.QueueURL = nil
serviceSegment.AWS.TableName = nil
serviceSegment.AWS.TableNames = nil

// Delete all metadata that does not start with 'otel.resource.'
for _, metaDataEntry := range serviceSegment.Metadata {
for key := range metaDataEntry {
if !strings.HasPrefix(key, "otel.resource.") {
delete(metaDataEntry, key)
}
}
}

// Make it a segment
serviceSegment.Type = nil

// Remote namespace
serviceSegment.Namespace = nil

// Remove span links from non-consumer spans
if span.Kind() != ptrace.SpanKindConsumer {
serviceSegment.Links = nil
}

return serviceSegment, nil
}

func MakeServiceSegmentForLocalRootSpanWithoutDependency(span ptrace.Span, resource pcommon.Resource, indexedAttrs []string, indexAllAttrs bool, logGroupNames []string, skipTimestampValidation bool) ([]*awsxray.Segment, error) {
segment, err := MakeSegment(span, resource, indexedAttrs, indexAllAttrs, logGroupNames, skipTimestampValidation)

if err != nil {
return nil, err
}

segment.Type = nil
segment.Namespace = nil

return []*awsxray.Segment{segment}, err
}

func MakeNonLocalRootSegment(span ptrace.Span, resource pcommon.Resource, indexedAttrs []string, indexAllAttrs bool, logGroupNames []string, skipTimestampValidation bool) ([]*awsxray.Segment, error) {
segment, err := MakeSegment(span, resource, indexedAttrs, indexAllAttrs, logGroupNames, skipTimestampValidation)

if err != nil {
return nil, err
}

addNamespaceToSubsegmentWithRemoteService(span, segment)

return []*awsxray.Segment{segment}, nil
}

func MakeServiceSegmentAndDependencySubsegment(span ptrace.Span, resource pcommon.Resource, indexedAttrs []string, indexAllAttrs bool, logGroupNames []string, skipTimestampValidation bool) ([]*awsxray.Segment, error) {
// If it is a local root span and a dependency span, we need to make a segment and subsegment representing the local service and remote service, respectively.
var serviceSegmentID = newSegmentID()
var segments []*awsxray.Segment

// Make Dependency Subsegment
dependencySubsegment, err := MakeDependencySubsegmentForLocalRootDependencySpan(span, resource, indexedAttrs, indexAllAttrs, logGroupNames, skipTimestampValidation, serviceSegmentID)
if err != nil {
return nil, err
}
segments = append(segments, dependencySubsegment)

// Make Service Segment
serviceSegment, err := MakeServiceSegmentForLocalRootDependencySpan(span, resource, indexedAttrs, indexAllAttrs, logGroupNames, skipTimestampValidation, serviceSegmentID)
if err != nil {
return nil, err
}
segments = append(segments, serviceSegment)

return segments, err
}

// MakeSegmentsFromSpan creates one or more segments from a span
func MakeSegmentsFromSpan(span ptrace.Span, resource pcommon.Resource, indexedAttrs []string, indexAllAttrs bool, logGroupNames []string, skipTimestampValidation bool) ([]*awsxray.Segment, error) {
if !isLocalRoot(span) {
return MakeNonLocalRootSegment(span, resource, indexedAttrs, indexAllAttrs, logGroupNames, skipTimestampValidation)
}

if !isLocalRootSpanADependencySpan(span) {
return MakeServiceSegmentForLocalRootSpanWithoutDependency(span, resource, indexedAttrs, indexAllAttrs, logGroupNames, skipTimestampValidation)
}

return MakeServiceSegmentAndDependencySubsegment(span, resource, indexedAttrs, indexAllAttrs, logGroupNames, skipTimestampValidation)
}

// MakeSegmentDocumentString converts an OpenTelemetry Span to an X-Ray Segment and then serializes to JSON
// MakeSegmentDocumentString will be deprecated in the future
func MakeSegmentDocumentString(span ptrace.Span, resource pcommon.Resource, indexedAttrs []string, indexAllAttrs bool, logGroupNames []string, skipTimestampValidation bool) (string, error) {
segment, err := MakeSegment(span, resource, indexedAttrs, indexAllAttrs, logGroupNames, skipTimestampValidation)

if err != nil {
return "", err
}

return MakeDocumentFromSegment(segment)
}

// MakeDocumentFromSegment converts a segment into a JSON document
func MakeDocumentFromSegment(segment *awsxray.Segment) (string, error) {
w := writers.borrow()
if err := w.Encode(*segment); err != nil {
return "", err
Expand Down Expand Up @@ -144,18 +366,24 @@ func MakeSegment(span ptrace.Span, resource pcommon.Resource, indexedAttrs []str
// 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
if span.Kind() == ptrace.SpanKindServer || span.Kind() == ptrace.SpanKindConsumer {
if span.Kind() == ptrace.SpanKindServer {
if localServiceName, ok := attributes.Get(awsLocalService); ok {
name = localServiceName.Str()
}
}

myAwsSpanKind, _ := span.Attributes().Get(awsSpanKind)
if span.Kind() == ptrace.SpanKindInternal && myAwsSpanKind.Str() == localRoot {
if localServiceName, ok := attributes.Get(awsLocalService); ok {
name = localServiceName.Str()
}
}
if span.Kind() == ptrace.SpanKindClient || span.Kind() == ptrace.SpanKindProducer {

if span.Kind() == ptrace.SpanKindClient || span.Kind() == ptrace.SpanKindProducer || span.Kind() == ptrace.SpanKindConsumer {
if remoteServiceName, ok := attributes.Get(awsRemoteService); ok {
name = remoteServiceName.Str()
// only strip the prefix for AWS spans
if isAwsSdkSpan(span) && strings.HasPrefix(name, "AWS.SDK.") {
name = strings.TrimPrefix(name, "AWS.SDK.")
}
name = trimAwsSdkPrefix(name, span)
}
}

Expand Down Expand Up @@ -537,3 +765,10 @@ func fixAnnotationKey(key string) string {
}
}, key)
}

func trimAwsSdkPrefix(name string, span ptrace.Span) string {
if isAwsSdkSpan(span) && strings.HasPrefix(name, "AWS.SDK.") {
return strings.TrimPrefix(name, "AWS.SDK.")
}
return name
}
Loading

0 comments on commit 902d846

Please sign in to comment.