diff --git a/exporter/awsxrayexporter/internal/translator/aws.go b/exporter/awsxrayexporter/internal/translator/aws.go index e29c7e4cbfdc..e312e27cf1ff 100644 --- a/exporter/awsxrayexporter/internal/translator/aws.go +++ b/exporter/awsxrayexporter/internal/translator/aws.go @@ -14,6 +14,10 @@ import ( awsxray "github.com/open-telemetry/opentelemetry-collector-contrib/internal/aws/xray" ) +const ( + AttributeTelemetryDistroVersion = "telemetry.distro.version" +) + func makeAws(attributes map[string]pcommon.Value, resource pcommon.Resource, logGroupNames []string) (map[string]pcommon.Value, *awsxray.AWSData) { var ( cloud string @@ -90,7 +94,7 @@ func makeAws(attributes map[string]pcommon.Value, resource pcommon.Resource, log sdkLanguage = value.Str() case conventions.AttributeTelemetrySDKVersion: sdkVersion = value.Str() - case conventions.AttributeTelemetryAutoVersion: + case conventions.AttributeTelemetryAutoVersion, AttributeTelemetryDistroVersion: autoVersion = value.Str() case conventions.AttributeContainerID: containerID = value.Str() diff --git a/exporter/awsxrayexporter/internal/translator/aws_test.go b/exporter/awsxrayexporter/internal/translator/aws_test.go index e5e7d2f585cb..4c8c78ae3c25 100644 --- a/exporter/awsxrayexporter/internal/translator/aws_test.go +++ b/exporter/awsxrayexporter/internal/translator/aws_test.go @@ -360,6 +360,23 @@ func TestJavaAutoInstrumentation(t *testing.T) { assert.True(t, *awsData.XRay.AutoInstrumentation) } +func TestJavaAutoInstrumentationStable(t *testing.T) { + attributes := make(map[string]pcommon.Value) + resource := pcommon.NewResource() + resource.Attributes().PutStr(conventions.AttributeTelemetrySDKName, "opentelemetry") + resource.Attributes().PutStr(conventions.AttributeTelemetrySDKLanguage, "java") + resource.Attributes().PutStr(conventions.AttributeTelemetrySDKVersion, "1.2.3") + resource.Attributes().PutStr(AttributeTelemetryDistroVersion, "3.4.5") + + filtered, awsData := makeAws(attributes, resource, nil) + + assert.NotNil(t, filtered) + assert.NotNil(t, awsData) + assert.Equal(t, "opentelemetry for java", *awsData.XRay.SDK) + assert.Equal(t, "1.2.3", *awsData.XRay.SDKVersion) + assert.True(t, *awsData.XRay.AutoInstrumentation) +} + func TestGoSDK(t *testing.T) { attributes := make(map[string]pcommon.Value) resource := pcommon.NewResource() diff --git a/exporter/awsxrayexporter/internal/translator/cause.go b/exporter/awsxrayexporter/internal/translator/cause.go index 3f26c84d7b75..1408983b20cd 100644 --- a/exporter/awsxrayexporter/internal/translator/cause.go +++ b/exporter/awsxrayexporter/internal/translator/cause.go @@ -150,6 +150,9 @@ func makeCause(span ptrace.Span, attributes map[string]pcommon.Value, resource p } val, ok := span.Attributes().Get(conventions.AttributeHTTPStatusCode) + if !ok { + val, ok = span.Attributes().Get(AwsIndividualHTTPErrorCodeAttr) + } switch { // The segment status for http spans will be based on their http.statuscode as we found some http diff --git a/exporter/awsxrayexporter/internal/translator/cause_test.go b/exporter/awsxrayexporter/internal/translator/cause_test.go index eabe8147369a..95dcb22681ec 100644 --- a/exporter/awsxrayexporter/internal/translator/cause_test.go +++ b/exporter/awsxrayexporter/internal/translator/cause_test.go @@ -196,6 +196,31 @@ func TestCauseWithStatusMessage(t *testing.T) { assert.True(t, strings.Contains(jsonStr, errorMsg)) } +func TestCauseWithStatusMessageStable(t *testing.T) { + errorMsg := "this is a test" + attributes := make(map[string]any) + attributes[conventions.AttributeHTTPMethod] = "POST" + attributes[conventions.AttributeHTTPURL] = "https://api.example.com/widgets" + attributes[AwsIndividualHTTPErrorCodeAttr] = 500 + span := constructExceptionServerSpan(attributes, ptrace.StatusCodeError) + span.Status().SetMessage(errorMsg) + filtered, _ := makeHTTP(span) + + res := pcommon.NewResource() + isError, isFault, isThrottle, filtered, cause := makeCause(span, filtered, res) + + assert.True(t, isFault) + assert.False(t, isError) + assert.False(t, isThrottle) + assert.NotNil(t, filtered) + assert.NotNil(t, cause) + w := testWriters.borrow() + require.NoError(t, w.Encode(cause)) + jsonStr := w.String() + testWriters.release(w) + assert.True(t, strings.Contains(jsonStr, errorMsg)) +} + func TestCauseWithHttpStatusMessage(t *testing.T) { errorMsg := "this is a test" attributes := make(map[string]any) @@ -221,6 +246,31 @@ func TestCauseWithHttpStatusMessage(t *testing.T) { assert.True(t, strings.Contains(jsonStr, errorMsg)) } +func TestCauseWithHttpStatusMessageStable(t *testing.T) { + errorMsg := "this is a test" + attributes := make(map[string]any) + attributes[conventions.AttributeHTTPMethod] = "POST" + attributes[conventions.AttributeHTTPURL] = "https://api.example.com/widgets" + attributes[AwsIndividualHTTPErrorCodeAttr] = 500 + attributes["http.status_text"] = errorMsg + span := constructExceptionServerSpan(attributes, ptrace.StatusCodeError) + filtered, _ := makeHTTP(span) + + res := pcommon.NewResource() + isError, isFault, isThrottle, filtered, cause := makeCause(span, filtered, res) + + assert.True(t, isFault) + assert.False(t, isError) + assert.False(t, isThrottle) + assert.NotNil(t, filtered) + assert.NotNil(t, cause) + w := testWriters.borrow() + require.NoError(t, w.Encode(cause)) + jsonStr := w.String() + testWriters.release(w) + assert.True(t, strings.Contains(jsonStr, errorMsg)) +} + func TestCauseWithZeroStatusMessageAndFaultHttpCode(t *testing.T) { errorMsg := "this is a test" attributes := make(map[string]any) @@ -245,6 +295,30 @@ func TestCauseWithZeroStatusMessageAndFaultHttpCode(t *testing.T) { assert.Nil(t, cause) } +func TestCauseWithZeroStatusMessageAndFaultHttpCodeStable(t *testing.T) { + errorMsg := "this is a test" + attributes := make(map[string]any) + attributes[conventions.AttributeHTTPMethod] = "POST" + attributes[conventions.AttributeHTTPURL] = "https://api.example.com/widgets" + attributes[AwsIndividualHTTPErrorCodeAttr] = 500 + attributes["http.status_text"] = errorMsg + + span := constructExceptionServerSpan(attributes, ptrace.StatusCodeUnset) + filtered, _ := makeHTTP(span) + // Status is used to determine whether an error or not. + // This span illustrates incorrect instrumentation, + // marking a success status with an error http status code, and status wins. + // We do not expect to see such spans in practice. + res := pcommon.NewResource() + isError, isFault, isThrottle, filtered, cause := makeCause(span, filtered, res) + + assert.False(t, isError) + assert.True(t, isFault) + assert.False(t, isThrottle) + assert.NotNil(t, filtered) + assert.Nil(t, cause) +} + func TestNonHttpUnsetCodeSpan(t *testing.T) { errorMsg := "this is a test" attributes := make(map[string]any) @@ -338,6 +412,30 @@ func TestCauseWithZeroStatusMessageAndFaultErrorCode(t *testing.T) { assert.Nil(t, cause) } +func TestCauseWithZeroStatusMessageAndFaultErrorCodeStable(t *testing.T) { + errorMsg := "this is a test" + attributes := make(map[string]any) + attributes[conventions.AttributeHTTPMethod] = "POST" + attributes[conventions.AttributeHTTPURL] = "https://api.example.com/widgets" + attributes[AwsIndividualHTTPErrorCodeAttr] = 400 + attributes["http.status_text"] = errorMsg + + span := constructExceptionServerSpan(attributes, ptrace.StatusCodeUnset) + filtered, _ := makeHTTP(span) + // Status is used to determine whether an error or not. + // This span illustrates incorrect instrumentation, + // marking a success status with an error http status code, and status wins. + // We do not expect to see such spans in practice. + res := pcommon.NewResource() + isError, isFault, isThrottle, filtered, cause := makeCause(span, filtered, res) + + assert.True(t, isError) + assert.False(t, isFault) + assert.False(t, isThrottle) + assert.NotNil(t, filtered) + assert.Nil(t, cause) +} + func TestCauseWithClientErrorMessage(t *testing.T) { errorMsg := "this is a test" attributes := make(map[string]any) @@ -359,6 +457,27 @@ func TestCauseWithClientErrorMessage(t *testing.T) { assert.NotNil(t, cause) } +func TestCauseWithClientErrorMessageStable(t *testing.T) { + errorMsg := "this is a test" + attributes := make(map[string]any) + attributes[conventions.AttributeHTTPMethod] = "POST" + attributes[conventions.AttributeHTTPURL] = "https://api.example.com/widgets" + attributes[AwsIndividualHTTPErrorCodeAttr] = 499 + attributes["http.status_text"] = errorMsg + + span := constructExceptionServerSpan(attributes, ptrace.StatusCodeError) + filtered, _ := makeHTTP(span) + + res := pcommon.NewResource() + isError, isFault, isThrottle, filtered, cause := makeCause(span, filtered, res) + + assert.True(t, isError) + assert.False(t, isFault) + assert.False(t, isThrottle) + assert.NotNil(t, filtered) + assert.NotNil(t, cause) +} + func TestCauseWithThrottled(t *testing.T) { errorMsg := "this is a test" attributes := make(map[string]any) @@ -380,6 +499,27 @@ func TestCauseWithThrottled(t *testing.T) { assert.NotNil(t, cause) } +func TestCauseWithThrottledStable(t *testing.T) { + errorMsg := "this is a test" + attributes := make(map[string]any) + attributes[conventions.AttributeHTTPMethod] = "POST" + attributes[conventions.AttributeHTTPURL] = "https://api.example.com/widgets" + attributes[AwsIndividualHTTPErrorCodeAttr] = 429 + attributes["http.status_text"] = errorMsg + + span := constructExceptionServerSpan(attributes, ptrace.StatusCodeError) + filtered, _ := makeHTTP(span) + + res := pcommon.NewResource() + isError, isFault, isThrottle, filtered, cause := makeCause(span, filtered, res) + + assert.True(t, isError) + assert.False(t, isFault) + assert.True(t, isThrottle) + assert.NotNil(t, filtered) + assert.NotNil(t, cause) +} + func constructExceptionServerSpan(attributes map[string]any, statuscode ptrace.StatusCode) ptrace.Span { endTime := time.Now().Round(time.Second) startTime := endTime.Add(-90 * time.Second) diff --git a/exporter/awsxrayexporter/internal/translator/http.go b/exporter/awsxrayexporter/internal/translator/http.go index 7aa31f83b777..475d5fc89aa7 100644 --- a/exporter/awsxrayexporter/internal/translator/http.go +++ b/exporter/awsxrayexporter/internal/translator/http.go @@ -25,6 +25,7 @@ const ( AttributeURLScheme = "url.scheme" AttributeURLFull = "url.full" AttributeURLPath = "url.path" + AttributeURLQuery = "url.query" AttributeUserAgentOriginal = "user_agent.original" ) @@ -71,8 +72,8 @@ func makeHTTP(span ptrace.Span) (map[string]pcommon.Value, *awsxray.HTTPData) { urlParts[key] = value.Str() hasHTTP = true hasHTTPRequestURLAttributes = true - case conventions.AttributeHTTPTarget: - urlParts[key] = value.Str() + case conventions.AttributeHTTPTarget, AttributeURLQuery: + urlParts[conventions.AttributeHTTPTarget] = value.Str() hasHTTP = true case conventions.AttributeHTTPServerName: urlParts[key] = value.Str() diff --git a/exporter/awsxrayexporter/internal/translator/http_test.go b/exporter/awsxrayexporter/internal/translator/http_test.go index a5fd43109ac8..f4b9f79c182b 100644 --- a/exporter/awsxrayexporter/internal/translator/http_test.go +++ b/exporter/awsxrayexporter/internal/translator/http_test.go @@ -73,6 +73,27 @@ func TestClientSpanWithSchemeHostTargetAttributes(t *testing.T) { assert.True(t, strings.Contains(jsonStr, "https://api.example.com/users/junit")) } +func TestClientSpanWithSchemeHostTargetAttributesStable(t *testing.T) { + attributes := make(map[string]any) + attributes[conventions.AttributeHTTPMethod] = "GET" + attributes[conventions.AttributeHTTPScheme] = "https" + attributes[conventions.AttributeHTTPHost] = "api.example.com" + attributes[AttributeURLQuery] = "/users/junit" + attributes[conventions.AttributeHTTPStatusCode] = 200 + attributes["user.id"] = "junit" + span := constructHTTPClientSpan(attributes) + + filtered, httpData := makeHTTP(span) + + assert.NotNil(t, httpData) + assert.NotNil(t, filtered) + w := testWriters.borrow() + require.NoError(t, w.Encode(httpData)) + jsonStr := w.String() + testWriters.release(w) + assert.True(t, strings.Contains(jsonStr, "https://api.example.com/users/junit")) +} + func TestClientSpanWithPeerAttributes(t *testing.T) { attributes := make(map[string]any) attributes[conventions.AttributeHTTPMethod] = "GET" @@ -106,7 +127,7 @@ func TestClientSpanWithPeerAttributesStable(t *testing.T) { attributes[conventions.AttributeNetPeerName] = "kb234.example.com" attributes[conventions.AttributeNetPeerPort] = 8080 attributes[conventions.AttributeNetPeerIP] = "10.8.17.36" - attributes[conventions.AttributeHTTPTarget] = "/users/junit" + attributes[AttributeURLQuery] = "/users/junit" attributes[conventions.AttributeHTTPStatusCode] = 200 span := constructHTTPClientSpan(attributes) @@ -257,7 +278,7 @@ func TestServerSpanWithSchemeHostTargetAttributesStable(t *testing.T) { attributes[AttributeHTTPRequestMethod] = "GET" attributes[AttributeURLScheme] = "https" attributes[AttributeServerAddress] = "api.example.com" - attributes[AttributeURLPath] = "/users/junit" + attributes[AttributeURLQuery] = "/users/junit" attributes[AttributeClientAddress] = "192.168.15.32" attributes[AttributeHTTPResponseStatusCode] = 200 span := constructHTTPServerSpan(attributes) @@ -301,7 +322,7 @@ func TestServerSpanWithSchemeServernamePortTargetAttributesStable(t *testing.T) attributes[AttributeURLScheme] = "https" attributes[AttributeServerAddress] = "api.example.com" attributes[AttributeServerPort] = 443 - attributes[AttributeURLPath] = "/users/junit" + attributes[AttributeURLQuery] = "/users/junit" attributes[AttributeClientAddress] = "192.168.15.32" attributes[AttributeHTTPResponseStatusCode] = 200 span := constructHTTPServerSpan(attributes)