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

[exporter/awsxray] Adjust AwsXRay segment conversion logic #33000

Merged
Show file tree
Hide file tree
Changes from 7 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
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.
jj22ee marked this conversation as resolved.
Show resolved Hide resolved
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