From fff2a3173012b6a2e35fd80dbb3e3aef16089084 Mon Sep 17 00:00:00 2001 From: Adam Shaw Date: Tue, 26 Sep 2023 16:14:13 -0700 Subject: [PATCH] Adding unit tests --- exporter/awsxrayexporter/awsxray.go | 2 +- .../internal/translator/segment.go | 186 ++++--- .../internal/translator/segment_test.go | 475 ++++++++++++++++++ 3 files changed, 589 insertions(+), 74 deletions(-) diff --git a/exporter/awsxrayexporter/awsxray.go b/exporter/awsxrayexporter/awsxray.go index ff3616eeb381..ac978cf52b75 100644 --- a/exporter/awsxrayexporter/awsxray.go +++ b/exporter/awsxrayexporter/awsxray.go @@ -105,7 +105,7 @@ 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++ { - documentsForSpan, localErr := translator.MakeSegmentDocumentStrings( + documentsForSpan, localErr := translator.MakeSegmentDocuments( spans.At(k), resource, config.(*Config).IndexedAttributes, config.(*Config).IndexAllAttributes, diff --git a/exporter/awsxrayexporter/internal/translator/segment.go b/exporter/awsxrayexporter/internal/translator/segment.go index 713b95829d46..f5a84d5ba622 100644 --- a/exporter/awsxrayexporter/internal/translator/segment.go +++ b/exporter/awsxrayexporter/internal/translator/segment.go @@ -69,8 +69,27 @@ var ( writers = newWriterPool(2048) ) -// MakeSegmentDocumentStrings converts one span into one or more JSON documents -func MakeSegmentDocumentStrings(span ptrace.Span, resource pcommon.Resource, indexedAttrs []string, indexAllAttrs bool, logGroupNames []string, skipTimestampValidation bool) ([]string, error) { +// MakeSegmentDocuments converts spans to json documents +func MakeSegmentDocuments(span ptrace.Span, resource pcommon.Resource, indexedAttrs []string, indexAllAttrs bool, logGroupNames []string, skipTimestampValidation bool) ([]string, error) { + if segments, err := MakeSegmentsFromSpan(span, resource, indexedAttrs, indexAllAttrs, logGroupNames, skipTimestampValidation); err != nil { + return nil, err + } else { + var documents []string + + for _, v := range segments { + if document, documentErr := MakeDocumentFromSegment(v); documentErr != nil { + return nil, documentErr + } else { + documents = append(documents, document) + } + } + + return documents, nil + } +} + +// 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) { var isLocalRoot = false if myAwsSpanKind, ok := span.Attributes().Get(awsSpanKind); ok { @@ -83,18 +102,17 @@ func MakeSegmentDocumentStrings(span ptrace.Span, resource pcommon.Resource, ind if segment, err := MakeSegment(span, resource, indexedAttrs, indexAllAttrs, logGroupNames, skipTimestampValidation); err != nil { return nil, err } else { - document, documentError := MakeSegmentJsonFromSegment(segment) - return []string{document}, documentError + return []*awsxray.Segment{segment}, nil } } else { - var isDependency = (span.Kind() != ptrace.SpanKindServer) && (span.Kind() != ptrace.SpanKindInternal) - var documents []string + var hasDependency = (span.Kind() != ptrace.SpanKindServer) && (span.Kind() != ptrace.SpanKindInternal) + var segments []*awsxray.Segment var serviceSegmentId = newSegmentID() // If it is a dependency, then we need to create a subsegment - if isDependency { - var dependencySpan ptrace.Span + if hasDependency { + var dependencySpan ptrace.Span = ptrace.NewSpan() span.CopyTo(dependencySpan) dependencySpan.SetParentSpanID(serviceSegmentId) @@ -115,84 +133,77 @@ func MakeSegmentDocumentStrings(span ptrace.Span, resource pcommon.Resource, ind dependencySubsegment.Links = nil } - // TODO: namespace will be aws or remote - but this is handled in a separate SIM - - if document, documentError := MakeSegmentJsonFromSegment(dependencySubsegment); documentError != nil { - return nil, documentError - } else { - documents = append(documents, document) - } + segments = append(segments, dependencySubsegment) } - } - // We always create a segment for the service - var serviceSpan ptrace.Span - span.CopyTo(serviceSpan) + // 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 - span.SetSpanID(serviceSegmentId) + // Set the span id to the one internally generated + serviceSpan.SetSpanID(serviceSegmentId) - serviceSegment, err := MakeSegment(serviceSpan, resource, indexedAttrs, indexAllAttrs, logGroupNames, skipTimestampValidation) + removeAnnotations := []string{ + "aws.remote.service", + "aws.remote.operation", + "remote.target", + "K8s.RemoteNamespace", + } - // Set the name - if myAwsLocalService, ok := span.Attributes().Get(awsLocalService); ok { - serviceSegment.Name = awsxray.String(myAwsLocalService.Str()) - } + for _, v := range removeAnnotations { + serviceSpan.Attributes().Remove(v) + } - // 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 - - // Remove remote annotations - removeAnnotations := []string{ - "aws_remote_service", - "aws_remote_operation", - "remote_target", - "K8s_RemoteNamespace", - } + serviceSegment, err := MakeSegment(serviceSpan, resource, indexedAttrs, indexAllAttrs, logGroupNames, skipTimestampValidation) - for _, v := range removeAnnotations { - delete(serviceSegment.Annotations, v) - } + // Set the name + if myAwsLocalService, ok := span.Attributes().Get(awsLocalService); ok { + serviceSegment.Name = awsxray.String(myAwsLocalService.Str()) + } - // Delete all metadata that does not start with 'otel.resource.' - for key, _ := range serviceSegment.Metadata { - if !strings.HasPrefix(key, "otel.resource.") { - delete(serviceSegment.Metadata, key) + // 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 + // Make it a segment + serviceSegment.Type = nil - // Remove span links from non-consumer spans - if span.Kind() != ptrace.SpanKindConsumer { - serviceSegment.Links = nil - } + // Remove span links from non-consumer spans + if span.Kind() != ptrace.SpanKindConsumer { + serviceSegment.Links = nil + } - document, err := MakeSegmentJsonFromSegment(serviceSegment) - documents = append(documents, document) - return documents, err - } -} + segments = append(segments, serviceSegment) -// MakeSegmentJsonFromSegment converts an OpenTelemetry Span to an X-Ray Segment and then serialzies to JSON -func MakeSegmentJsonFromSegment(segment *awsxray.Segment) (string, error) { - w := writers.borrow() - if err := w.Encode(*segment); err != nil { - return "", err + return segments, err + } else { + if segment, err := MakeSegment(span, resource, indexedAttrs, indexAllAttrs, logGroupNames, skipTimestampValidation); err != nil { + return nil, err + } else { + // Make internal spans a segment + segment.Type = nil + + return []*awsxray.Segment{segment}, nil + } + } } - jsonStr := w.String() - writers.release(w) - return jsonStr, nil } // MakeSegmentDocumentString converts an OpenTelemetry Span to an X-Ray Segment and then serialzies to JSON @@ -203,7 +214,18 @@ func MakeSegmentDocumentString(span ptrace.Span, resource pcommon.Resource, inde return "", err } - return MakeSegmentJsonFromSegment(segment) + 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 + } + jsonStr := w.String() + writers.release(w) + return jsonStr, nil } // MakeSegment converts an OpenTelemetry Span to an X-Ray Segment @@ -254,6 +276,14 @@ func MakeSegment(span ptrace.Span, resource pcommon.Resource, indexedAttrs []str 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 remoteServiceName, ok := attributes.Get(awsRemoteService); ok { name = remoteServiceName.Str() @@ -335,6 +365,16 @@ func MakeSegment(span ptrace.Span, resource pcommon.Resource, indexedAttrs []str namespace = "remote" } + if (span.Kind() == ptrace.SpanKindClient || + span.Kind() == ptrace.SpanKindConsumer || + span.Kind() == ptrace.SpanKindProducer) && + segmentType == "subsegment" && + namespace == "" { + if _, ok := span.Attributes().Get(awsRemoteService); ok { + namespace = "remote" + } + } + return &awsxray.Segment{ ID: awsxray.String(traceutil.SpanIDToHexOrEmptyString(span.SpanID())), TraceID: awsxray.String(traceID), diff --git a/exporter/awsxrayexporter/internal/translator/segment_test.go b/exporter/awsxrayexporter/internal/translator/segment_test.go index fcdfed6550a4..9e6ea125b8ff 100644 --- a/exporter/awsxrayexporter/internal/translator/segment_test.go +++ b/exporter/awsxrayexporter/internal/translator/segment_test.go @@ -1075,6 +1075,454 @@ func TestServerSpanWithAwsLocalServiceName(t *testing.T) { assert.False(t, strings.Contains(jsonStr, "user")) } +func TestLocalRootConsumer(t *testing.T) { + spanName := "destination receive" + resource := constructDefaultResource() + parentSpanID := newSegmentID() + + attributes := make(map[string]interface{}) + attributes[conventions.AttributeHTTPMethod] = "POST" + attributes[conventions.AttributeMessagingOperation] = "receive" + attributes["otel.resource.attributes"] = "service.name=myTest" + attributes["aws.span.kind"] = "LOCAL_ROOT" + attributes["aws.remote.service"] = "myRemoteService" + attributes["aws.local.service"] = "myLocalService" + attributes["myAnnotationKey"] = "myAnnotationValue" + attributes[awsxray.AWSOperationAttribute] = "UpdateItem" + + resource.Attributes().PutStr(conventions.AttributeTelemetrySDKName, "MySDK") + resource.Attributes().PutStr(conventions.AttributeTelemetrySDKVersion, "1.20.0") + resource.Attributes().PutStr(conventions.AttributeTelemetryAutoVersion, "1.2.3") + + span := constructConsumerSpan(parentSpanID, spanName, 0, "OK", attributes) + + spanLink := span.Links().AppendEmpty() + spanLink.SetTraceID(newTraceID()) + spanLink.SetSpanID(newSegmentID()) + + segments, err := MakeSegmentsFromSpan(span, resource, []string{"aws.remote.service", "myAnnotationKey"}, false, nil, false) + + assert.NotNil(t, segments) + assert.Equal(t, 2, len(segments)) + assert.Nil(t, err) + + tempTraceID := span.TraceID() + expectedTraceID := "1-" + fmt.Sprintf("%x", tempTraceID[0:4]) + "-" + fmt.Sprintf("%x", tempTraceID[4:16]) + + // Validate segment 1 (dependency subsegment) + assert.Equal(t, "subsegment", *segments[0].Type) + assert.Equal(t, "myRemoteService", *segments[0].Name) + assert.Equal(t, *segments[1].ID, *segments[0].ParentID) + assert.NotEqual(t, parentSpanID.String(), *segments[0].ID) + assert.Equal(t, span.SpanID().String(), *segments[0].ID) + assert.Nil(t, segments[0].Links) + assert.Equal(t, expectedTraceID, *segments[0].TraceID) + assert.NotNil(t, segments[0].HTTP) + assert.Equal(t, "POST", *segments[0].HTTP.Request.Method) + assert.Equal(t, 2, len(segments[0].Annotations)) + assert.Equal(t, "myRemoteService", segments[0].Annotations["aws_remote_service"]) + assert.Equal(t, "myAnnotationValue", segments[0].Annotations["myAnnotationKey"]) + assert.Equal(t, 4, len(segments[0].Metadata["default"])) + assert.Equal(t, "LOCAL_ROOT", segments[0].Metadata["default"]["aws.span.kind"]) + assert.Equal(t, "myLocalService", segments[0].Metadata["default"]["aws.local.service"]) + assert.Equal(t, "receive", segments[0].Metadata["default"]["messaging.operation"]) + assert.Equal(t, "service.name=myTest", segments[0].Metadata["default"]["otel.resource.attributes"]) + assert.Equal(t, "MySDK", *segments[0].AWS.XRay.SDK) + assert.Equal(t, "1.20.0", *segments[0].AWS.XRay.SDKVersion) + assert.Equal(t, true, *segments[0].AWS.XRay.AutoInstrumentation) + assert.Equal(t, "UpdateItem", *segments[0].AWS.Operation) + assert.Equal(t, "remote", *segments[0].Namespace) + + // Validate segment 2 (service segment) + assert.Nil(t, segments[1].Type) + assert.Equal(t, "myLocalService", *segments[1].Name) + assert.Equal(t, parentSpanID.String(), *segments[1].ParentID) + assert.NotEqual(t, *segments[0].ID, *segments[1].ParentID) + assert.NotEqual(t, *segments[0].ID, *segments[1].ID) + assert.Equal(t, 1, len(segments[1].Links)) + assert.Equal(t, spanLink.SpanID().String(), *segments[1].Links[0].SpanID) + assert.Equal(t, expectedTraceID, *segments[1].TraceID) + assert.Nil(t, segments[1].HTTP) + assert.Equal(t, 1, len(segments[1].Annotations)) + assert.Equal(t, "myAnnotationValue", segments[1].Annotations["myAnnotationKey"]) + assert.Equal(t, 1, len(segments[1].Metadata["default"])) + assert.Equal(t, "service.name=myTest", segments[1].Metadata["default"]["otel.resource.attributes"]) + assert.Equal(t, "MySDK", *segments[1].AWS.XRay.SDK) + assert.Equal(t, "1.20.0", *segments[1].AWS.XRay.SDKVersion) + assert.Equal(t, true, *segments[1].AWS.XRay.AutoInstrumentation) + assert.Nil(t, segments[1].AWS.Operation) + assert.Nil(t, segments[1].Namespace) + + // Checks these values are the same for both + assert.Equal(t, segments[0].StartTime, segments[1].StartTime) + assert.Equal(t, segments[0].EndTime, segments[1].EndTime) +} + +func TestLocalRootConsumerAWSNamespace(t *testing.T) { + spanName := "destination receive" + resource := constructDefaultResource() + parentSpanID := newSegmentID() + + attributes := make(map[string]interface{}) + attributes[conventions.AttributeHTTPMethod] = "POST" + attributes[conventions.AttributeMessagingOperation] = "receive" + attributes["otel.resource.attributes"] = "service.name=myTest" + attributes["aws.span.kind"] = "LOCAL_ROOT" + attributes["aws.remote.service"] = "myRemoteService" + attributes["aws.local.service"] = "myLocalService" + attributes["myAnnotationKey"] = "myAnnotationValue" + attributes[awsxray.AWSOperationAttribute] = "UpdateItem" + attributes[conventions.AttributeRPCSystem] = "aws-api" + + resource.Attributes().PutStr(conventions.AttributeTelemetrySDKName, "MySDK") + resource.Attributes().PutStr(conventions.AttributeTelemetrySDKVersion, "1.20.0") + resource.Attributes().PutStr(conventions.AttributeTelemetryAutoVersion, "1.2.3") + + span := constructConsumerSpan(parentSpanID, spanName, 0, "OK", attributes) + + spanLink := span.Links().AppendEmpty() + spanLink.SetTraceID(newTraceID()) + spanLink.SetSpanID(newSegmentID()) + + segments, err := MakeSegmentsFromSpan(span, resource, []string{"aws.remote.service", "myAnnotationKey"}, false, nil, false) + + assert.NotNil(t, segments) + assert.Equal(t, 2, len(segments)) + assert.Nil(t, err) + + // Ensure that AWS namespace is not overwritten to remote + assert.Equal(t, "aws", *segments[0].Namespace) +} + +func TestLocalRootClient(t *testing.T) { + spanName := "SQS Get" + resource := constructDefaultResource() + parentSpanID := newSegmentID() + + attributes := make(map[string]interface{}) + attributes[conventions.AttributeHTTPMethod] = "POST" + attributes["otel.resource.attributes"] = "service.name=myTest" + attributes["aws.span.kind"] = "LOCAL_ROOT" + attributes["aws.remote.service"] = "myRemoteService" + attributes["aws.local.service"] = "myLocalService" + attributes["myAnnotationKey"] = "myAnnotationValue" + attributes[awsxray.AWSOperationAttribute] = "UpdateItem" + + resource.Attributes().PutStr(conventions.AttributeTelemetrySDKName, "MySDK") + resource.Attributes().PutStr(conventions.AttributeTelemetrySDKVersion, "1.20.0") + resource.Attributes().PutStr(conventions.AttributeTelemetryAutoVersion, "1.2.3") + + span := constructClientSpan(parentSpanID, spanName, 0, "OK", attributes) + + spanLink := span.Links().AppendEmpty() + spanLink.SetTraceID(newTraceID()) + spanLink.SetSpanID(newSegmentID()) + + segments, err := MakeSegmentsFromSpan(span, resource, []string{"aws.remote.service", "myAnnotationKey"}, false, nil, false) + + assert.NotNil(t, segments) + assert.Equal(t, 2, len(segments)) + assert.Nil(t, err) + + tempTraceID := span.TraceID() + expectedTraceID := "1-" + fmt.Sprintf("%x", tempTraceID[0:4]) + "-" + fmt.Sprintf("%x", tempTraceID[4:16]) + + // Validate segment 1 (dependency subsegment) + assert.Equal(t, "subsegment", *segments[0].Type) + assert.Equal(t, "myRemoteService", *segments[0].Name) + assert.Equal(t, *segments[1].ID, *segments[0].ParentID) + assert.NotEqual(t, parentSpanID.String(), *segments[0].ID) + assert.Equal(t, span.SpanID().String(), *segments[0].ID) + assert.Equal(t, 1, len(segments[0].Links)) + assert.Equal(t, spanLink.SpanID().String(), *segments[0].Links[0].SpanID) + assert.Equal(t, expectedTraceID, *segments[0].TraceID) + assert.NotNil(t, segments[0].HTTP) + assert.Equal(t, "POST", *segments[0].HTTP.Request.Method) + assert.Equal(t, 2, len(segments[0].Annotations)) + assert.Equal(t, "myRemoteService", segments[0].Annotations["aws_remote_service"]) + assert.Equal(t, "myAnnotationValue", segments[0].Annotations["myAnnotationKey"]) + assert.Equal(t, 3, len(segments[0].Metadata["default"])) + assert.Equal(t, "LOCAL_ROOT", segments[0].Metadata["default"]["aws.span.kind"]) + assert.Equal(t, "myLocalService", segments[0].Metadata["default"]["aws.local.service"]) + assert.Equal(t, "service.name=myTest", segments[0].Metadata["default"]["otel.resource.attributes"]) + assert.Equal(t, "MySDK", *segments[0].AWS.XRay.SDK) + assert.Equal(t, "1.20.0", *segments[0].AWS.XRay.SDKVersion) + assert.Equal(t, true, *segments[0].AWS.XRay.AutoInstrumentation) + assert.Equal(t, "UpdateItem", *segments[0].AWS.Operation) + assert.Equal(t, "remote", *segments[0].Namespace) + + // Validate segment 2 (service segment) + assert.Nil(t, segments[1].Type) + assert.Equal(t, "myLocalService", *segments[1].Name) + assert.Equal(t, parentSpanID.String(), *segments[1].ParentID) + assert.NotEqual(t, *segments[0].ID, *segments[1].ParentID) + assert.NotEqual(t, *segments[0].ID, *segments[1].ID) + assert.Nil(t, segments[1].Links) + assert.Equal(t, expectedTraceID, *segments[1].TraceID) + assert.Nil(t, segments[1].HTTP) + assert.Equal(t, 1, len(segments[1].Annotations)) + assert.Equal(t, "myAnnotationValue", segments[1].Annotations["myAnnotationKey"]) + assert.Equal(t, 1, len(segments[1].Metadata["default"])) + assert.Equal(t, "service.name=myTest", segments[1].Metadata["default"]["otel.resource.attributes"]) + assert.Equal(t, "MySDK", *segments[1].AWS.XRay.SDK) + assert.Equal(t, "1.20.0", *segments[1].AWS.XRay.SDKVersion) + assert.Equal(t, true, *segments[1].AWS.XRay.AutoInstrumentation) + assert.Nil(t, segments[1].AWS.Operation) + assert.Equal(t, "remote", *segments[1].Namespace) + + // Checks these values are the same for both + assert.Equal(t, segments[0].StartTime, segments[1].StartTime) + assert.Equal(t, segments[0].EndTime, segments[1].EndTime) +} + +func TestLocalRootProducer(t *testing.T) { + spanName := "destination produce" + resource := constructDefaultResource() + parentSpanID := newSegmentID() + + attributes := make(map[string]interface{}) + attributes[conventions.AttributeHTTPMethod] = "POST" + attributes["otel.resource.attributes"] = "service.name=myTest" + attributes["aws.span.kind"] = "LOCAL_ROOT" + attributes["aws.remote.service"] = "myRemoteService" + attributes["aws.local.service"] = "myLocalService" + attributes["myAnnotationKey"] = "myAnnotationValue" + attributes[awsxray.AWSOperationAttribute] = "UpdateItem" + + resource.Attributes().PutStr(conventions.AttributeTelemetrySDKName, "MySDK") + resource.Attributes().PutStr(conventions.AttributeTelemetrySDKVersion, "1.20.0") + resource.Attributes().PutStr(conventions.AttributeTelemetryAutoVersion, "1.2.3") + + span := constructProducerSpan(parentSpanID, spanName, 0, "OK", attributes) + + spanLink := span.Links().AppendEmpty() + spanLink.SetTraceID(newTraceID()) + spanLink.SetSpanID(newSegmentID()) + + segments, err := MakeSegmentsFromSpan(span, resource, []string{"aws.remote.service", "myAnnotationKey"}, false, nil, false) + + assert.NotNil(t, segments) + assert.Equal(t, 2, len(segments)) + assert.Nil(t, err) + + tempTraceID := span.TraceID() + expectedTraceID := "1-" + fmt.Sprintf("%x", tempTraceID[0:4]) + "-" + fmt.Sprintf("%x", tempTraceID[4:16]) + + // Validate segment 1 (dependency subsegment) + assert.Equal(t, "subsegment", *segments[0].Type) + assert.Equal(t, "myRemoteService", *segments[0].Name) + assert.Equal(t, *segments[1].ID, *segments[0].ParentID) + assert.NotEqual(t, parentSpanID.String(), *segments[0].ID) + assert.Equal(t, span.SpanID().String(), *segments[0].ID) + assert.Equal(t, 1, len(segments[0].Links)) + assert.Equal(t, spanLink.SpanID().String(), *segments[0].Links[0].SpanID) + assert.Equal(t, expectedTraceID, *segments[0].TraceID) + assert.NotNil(t, segments[0].HTTP) + assert.Equal(t, "POST", *segments[0].HTTP.Request.Method) + assert.Equal(t, 2, len(segments[0].Annotations)) + assert.Equal(t, "myRemoteService", segments[0].Annotations["aws_remote_service"]) + assert.Equal(t, "myAnnotationValue", segments[0].Annotations["myAnnotationKey"]) + assert.Equal(t, 3, len(segments[0].Metadata["default"])) + assert.Equal(t, "LOCAL_ROOT", segments[0].Metadata["default"]["aws.span.kind"]) + assert.Equal(t, "myLocalService", segments[0].Metadata["default"]["aws.local.service"]) + assert.Equal(t, "service.name=myTest", segments[0].Metadata["default"]["otel.resource.attributes"]) + assert.Equal(t, "MySDK", *segments[0].AWS.XRay.SDK) + assert.Equal(t, "1.20.0", *segments[0].AWS.XRay.SDKVersion) + assert.Equal(t, true, *segments[0].AWS.XRay.AutoInstrumentation) + assert.Equal(t, "UpdateItem", *segments[0].AWS.Operation) + assert.Equal(t, "remote", *segments[0].Namespace) + + // Validate segment 2 (service segment) + assert.Nil(t, segments[1].Type) + assert.Equal(t, "myLocalService", *segments[1].Name) + assert.Equal(t, parentSpanID.String(), *segments[1].ParentID) + assert.NotEqual(t, *segments[0].ID, *segments[1].ParentID) + assert.NotEqual(t, *segments[0].ID, *segments[1].ID) + assert.Nil(t, segments[1].Links) + assert.Equal(t, expectedTraceID, *segments[1].TraceID) + assert.Nil(t, segments[1].HTTP) + assert.Equal(t, 1, len(segments[1].Annotations)) + assert.Equal(t, "myAnnotationValue", segments[1].Annotations["myAnnotationKey"]) + assert.Equal(t, 1, len(segments[1].Metadata["default"])) + assert.Equal(t, "service.name=myTest", segments[1].Metadata["default"]["otel.resource.attributes"]) + assert.Equal(t, "MySDK", *segments[1].AWS.XRay.SDK) + assert.Equal(t, "1.20.0", *segments[1].AWS.XRay.SDKVersion) + assert.Equal(t, true, *segments[1].AWS.XRay.AutoInstrumentation) + assert.Nil(t, segments[1].AWS.Operation) + assert.Nil(t, segments[1].Namespace) + + // Checks these values are the same for both + assert.Equal(t, segments[0].StartTime, segments[1].StartTime) + assert.Equal(t, segments[0].EndTime, segments[1].EndTime) +} + +func TestLocalRootServer(t *testing.T) { + spanName := "MyService" + resource := constructDefaultResource() + parentSpanID := newSegmentID() + + attributes := make(map[string]interface{}) + attributes[conventions.AttributeHTTPMethod] = "POST" + attributes["otel.resource.attributes"] = "service.name=myTest" + attributes["aws.span.kind"] = "LOCAL_ROOT" + attributes["aws.remote.service"] = "myRemoteService" + attributes["aws.local.service"] = "myLocalService" + attributes["myAnnotationKey"] = "myAnnotationValue" + attributes[awsxray.AWSOperationAttribute] = "UpdateItem" + + resource.Attributes().PutStr(conventions.AttributeTelemetrySDKName, "MySDK") + resource.Attributes().PutStr(conventions.AttributeTelemetrySDKVersion, "1.20.0") + resource.Attributes().PutStr(conventions.AttributeTelemetryAutoVersion, "1.2.3") + + span := constructServerSpan(parentSpanID, spanName, 0, "OK", attributes) + + spanLink := span.Links().AppendEmpty() + spanLink.SetTraceID(newTraceID()) + spanLink.SetSpanID(newSegmentID()) + + segments, err := MakeSegmentsFromSpan(span, resource, []string{"aws.remote.service", "myAnnotationKey"}, false, nil, false) + + assert.NotNil(t, segments) + assert.Equal(t, 1, len(segments)) + assert.Nil(t, err) + + tempTraceID := span.TraceID() + expectedTraceID := "1-" + fmt.Sprintf("%x", tempTraceID[0:4]) + "-" + fmt.Sprintf("%x", tempTraceID[4:16]) + + // Validate segment + assert.Nil(t, segments[0].Type) + assert.Equal(t, "myLocalService", *segments[0].Name) + assert.Equal(t, parentSpanID.String(), *segments[0].ParentID) + assert.Equal(t, 1, len(segments[0].Links)) + assert.Equal(t, expectedTraceID, *segments[0].TraceID) + assert.Equal(t, "POST", *segments[0].HTTP.Request.Method) + assert.Equal(t, 2, len(segments[0].Annotations)) + assert.Equal(t, "myRemoteService", segments[0].Annotations["aws_remote_service"]) + assert.Equal(t, "myAnnotationValue", segments[0].Annotations["myAnnotationKey"]) + assert.Equal(t, "service.name=myTest", segments[0].Metadata["default"]["otel.resource.attributes"]) + assert.Equal(t, "MySDK", *segments[0].AWS.XRay.SDK) + assert.Equal(t, "1.20.0", *segments[0].AWS.XRay.SDKVersion) + assert.Equal(t, true, *segments[0].AWS.XRay.AutoInstrumentation) + assert.Equal(t, "UpdateItem", *segments[0].AWS.Operation) + assert.Nil(t, segments[0].Namespace) +} + +func TestLocalRootInternal(t *testing.T) { + spanName := "MyInternalService" + resource := constructDefaultResource() + parentSpanID := newSegmentID() + + attributes := make(map[string]interface{}) + attributes[conventions.AttributeHTTPMethod] = "POST" + attributes["otel.resource.attributes"] = "service.name=myTest" + attributes["aws.span.kind"] = "LOCAL_ROOT" + attributes["aws.remote.service"] = "myRemoteService" + attributes["aws.local.service"] = "myLocalService" + attributes["myAnnotationKey"] = "myAnnotationValue" + attributes[awsxray.AWSOperationAttribute] = "UpdateItem" + + resource.Attributes().PutStr(conventions.AttributeTelemetrySDKName, "MySDK") + resource.Attributes().PutStr(conventions.AttributeTelemetrySDKVersion, "1.20.0") + resource.Attributes().PutStr(conventions.AttributeTelemetryAutoVersion, "1.2.3") + + span := constructInternalSpan(parentSpanID, spanName, 0, "OK", attributes) + + spanLink := span.Links().AppendEmpty() + spanLink.SetTraceID(newTraceID()) + spanLink.SetSpanID(newSegmentID()) + + segments, err := MakeSegmentsFromSpan(span, resource, []string{"aws.remote.service", "myAnnotationKey"}, false, nil, false) + + assert.NotNil(t, segments) + assert.Equal(t, 1, len(segments)) + assert.Nil(t, err) + + tempTraceID := span.TraceID() + expectedTraceID := "1-" + fmt.Sprintf("%x", tempTraceID[0:4]) + "-" + fmt.Sprintf("%x", tempTraceID[4:16]) + + // Validate segment + assert.Nil(t, segments[0].Type) + assert.Equal(t, "myLocalService", *segments[0].Name) + assert.Equal(t, parentSpanID.String(), *segments[0].ParentID) + assert.Equal(t, 1, len(segments[0].Links)) + assert.Equal(t, expectedTraceID, *segments[0].TraceID) + assert.Equal(t, "POST", *segments[0].HTTP.Request.Method) + assert.Equal(t, 2, len(segments[0].Annotations)) + assert.Equal(t, "myRemoteService", segments[0].Annotations["aws_remote_service"]) + assert.Equal(t, "myAnnotationValue", segments[0].Annotations["myAnnotationKey"]) + assert.Equal(t, "service.name=myTest", segments[0].Metadata["default"]["otel.resource.attributes"]) + assert.Equal(t, "MySDK", *segments[0].AWS.XRay.SDK) + assert.Equal(t, "1.20.0", *segments[0].AWS.XRay.SDKVersion) + assert.Equal(t, true, *segments[0].AWS.XRay.AutoInstrumentation) + assert.Equal(t, "UpdateItem", *segments[0].AWS.Operation) + assert.Nil(t, segments[0].Namespace) +} + +func TestNotLocalRootInternal(t *testing.T) { + spanName := "MyInternalService" + resource := constructDefaultResource() + parentSpanID := newSegmentID() + + attributes := make(map[string]interface{}) + attributes[conventions.AttributeHTTPMethod] = "POST" + attributes["otel.resource.attributes"] = "service.name=myTest" + attributes["aws.remote.service"] = "myRemoteService" + attributes["aws.local.service"] = "myLocalService" + attributes["myAnnotationKey"] = "myAnnotationValue" + attributes[awsxray.AWSOperationAttribute] = "UpdateItem" + + resource.Attributes().PutStr(conventions.AttributeTelemetrySDKName, "MySDK") + resource.Attributes().PutStr(conventions.AttributeTelemetrySDKVersion, "1.20.0") + resource.Attributes().PutStr(conventions.AttributeTelemetryAutoVersion, "1.2.3") + + span := constructInternalSpan(parentSpanID, spanName, 0, "OK", attributes) + + spanLink := span.Links().AppendEmpty() + spanLink.SetTraceID(newTraceID()) + spanLink.SetSpanID(newSegmentID()) + + segments, err := MakeSegmentsFromSpan(span, resource, []string{"aws.remote.service", "myAnnotationKey"}, false, nil, false) + + assert.NotNil(t, segments) + assert.Equal(t, 1, len(segments)) + assert.Nil(t, err) + + // Validate segment + assert.Equal(t, "subsegment", *segments[0].Type) + assert.Equal(t, "MyInternalService", *segments[0].Name) +} + +func TestNotLocalRoot(t *testing.T) { + spanName := "destination receive" + resource := constructDefaultResource() + parentSpanID := newSegmentID() + + attributes := make(map[string]interface{}) + attributes[conventions.AttributeHTTPMethod] = "POST" + attributes[conventions.AttributeMessagingOperation] = "receive" + attributes["otel.resource.attributes"] = "service.name=myTest" + attributes["aws.remote.service"] = "myRemoteService" + attributes["aws.local.service"] = "myLocalService" + attributes["myAnnotationKey"] = "myAnnotationValue" + + span := constructConsumerSpan(parentSpanID, spanName, 0, "OK", attributes) + + spanLink := span.Links().AppendEmpty() + spanLink.SetTraceID(newTraceID()) + spanLink.SetSpanID(newSegmentID()) + + segments, err := MakeSegmentsFromSpan(span, resource, nil, false, nil, false) + + assert.NotNil(t, segments) + assert.Equal(t, 1, len(segments)) + assert.Nil(t, err) + + assert.Equal(t, "subsegment", *segments[0].Type) + assert.Equal(t, "myLocalService", *segments[0].Name) + assert.Equal(t, parentSpanID.String(), *segments[0].ParentID) + assert.Equal(t, 1, len(segments[0].Links)) +} + func constructClientSpan(parentSpanID pcommon.SpanID, name string, code ptrace.StatusCode, message string, attributes map[string]interface{}) ptrace.Span { var ( traceID = newTraceID() @@ -1129,6 +1577,33 @@ func constructServerSpan(parentSpanID pcommon.SpanID, name string, code ptrace.S return span } +func constructInternalSpan(parentSpanID pcommon.SpanID, name string, code ptrace.StatusCode, message string, attributes map[string]interface{}) ptrace.Span { + var ( + traceID = newTraceID() + spanID = newSegmentID() + endTime = time.Now() + startTime = endTime.Add(-215 * time.Millisecond) + spanAttributes = constructSpanAttributes(attributes) + ) + + span := ptrace.NewSpan() + span.SetTraceID(traceID) + span.SetSpanID(spanID) + span.SetParentSpanID(parentSpanID) + span.SetName(name) + span.SetKind(ptrace.SpanKindInternal) + span.SetStartTimestamp(pcommon.NewTimestampFromTime(startTime)) + span.SetEndTimestamp(pcommon.NewTimestampFromTime(endTime)) + + status := ptrace.NewStatus() + status.SetCode(code) + status.SetMessage(message) + status.CopyTo(span.Status()) + + spanAttributes.CopyTo(span.Attributes()) + return span +} + func constructConsumerSpan(parentSpanID pcommon.SpanID, name string, code ptrace.StatusCode, message string, attributes map[string]interface{}) ptrace.Span { var ( traceID = newTraceID()