Skip to content

Commit

Permalink
[receiver/awsxray] Set cloudwatch logs metadata in attributes (#32271)
Browse files Browse the repository at this point in the history
From the issue:

> X-Ray segments have a few [optional
fields](https://docs.aws.amazon.com/xray/latest/devguide/xray-api-segmentdocuments.html#api-segmentdocuments-aws)
that carry AWS resource data. As part of the translation of X-Ray
segments to spans, the translator [extracts AWS
data](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/receiver/awsxrayreceiver/internal/translator/aws.go#L15)
and puts them in the resource attributes. It does this for EC2, ECS,
Beanstalk, and EKS, but not for CloudWatch Logs. So, by the time the
span [reaches the
exporter](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/exporter/awsxrayexporter/internal/translator/aws.go#L109),
that information is lost.

This change adds the cloudwatch logs metadata to the attributes with the
keys `conventions.AttributeAWSLogGroupARNs` and
`conventions.AttributeAWSLogGroupNames`. Both of these are used by the
awsxrayexporter to restore the `LogGroupMetadata`.

https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/decc66f276b9bb3ca7b775d59a93340503668641/exporter/awsxrayexporter/internal/translator/aws.go#L109-L112

https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/decc66f276b9bb3ca7b775d59a93340503668641/exporter/awsxrayexporter/internal/translator/aws.go#L293-L295

Fixes #31784

**Testing:** Added unit tests to validate the translation to attributes.
  • Loading branch information
jefchien authored Apr 26, 2024
1 parent b0790c5 commit 5b1b8e6
Show file tree
Hide file tree
Showing 4 changed files with 166 additions and 2 deletions.
27 changes: 27 additions & 0 deletions .chloggen/fix-cwlogs-xray.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: bug_fix

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

# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
note: Retain CloudWatch Log Group when translating X-Ray segments

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

# (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]
12 changes: 12 additions & 0 deletions receiver/awsxrayreceiver/internal/translator/addtoattrs.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,15 @@ func addInt64(val *int64, attrKey string, attrs pcommon.Map) {
attrs.PutInt(attrKey, *val)
}
}

func addStringSlice(val *string, attrKey string, attrs pcommon.Map) {
if val != nil {
var slice pcommon.Slice
if attrVal, ok := attrs.Get(attrKey); ok && attrVal.Type() == pcommon.ValueTypeSlice {
slice = attrVal.Slice()
} else {
slice = attrs.PutEmptySlice(attrKey)
}
slice.AppendEmpty().SetStr(*val)
}
}
10 changes: 8 additions & 2 deletions receiver/awsxrayreceiver/internal/translator/aws.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,14 @@ func addAWSToResource(aws *awsxray.AWSData, attrs pcommon.Map) {
addString(aws.AccountID, conventions.AttributeCloudAccountID, attrs)

// based on https://docs.aws.amazon.com/xray/latest/devguide/xray-api-segmentdocuments.html#api-segmentdocuments-aws
// it's possible to have all ec2, ecs and beanstalk fields at the same time.
// it's possible to have all cloudwatch_logs, ec2, ecs and beanstalk fields at the same time.
if cwl := aws.CWLogs; cwl != nil {
for _, logGroupMetaData := range cwl {
addStringSlice(logGroupMetaData.Arn, conventions.AttributeAWSLogGroupARNs, attrs)
addStringSlice(logGroupMetaData.LogGroup, conventions.AttributeAWSLogGroupNames, attrs)
}
}

if ec2 := aws.EC2; ec2 != nil {
addString(ec2.AvailabilityZone, conventions.AttributeCloudAvailabilityZone, attrs)
addString(ec2.InstanceID, conventions.AttributeHostID, attrs)
Expand All @@ -51,7 +58,6 @@ func addAWSToResource(aws *awsxray.AWSData, attrs pcommon.Map) {
addString(eks.ContainerID, conventions.AttributeContainerID, attrs)
addString(eks.ClusterName, conventions.AttributeK8SClusterName, attrs)
addString(eks.Pod, conventions.AttributeK8SPodName, attrs)

}
}

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

package translator

import (
"testing"

"github.com/aws/aws-sdk-go/aws"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/pcommon"
conventions "go.opentelemetry.io/collector/semconv/v1.6.1"

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

func TestAddAWSToResource(t *testing.T) {
testCases := map[string]struct {
input *awsxray.AWSData
want map[string]any
}{
"WithNil": {
want: map[string]any{
conventions.AttributeCloudProvider: "unknown",
},
},
"WithCloudWatchLogs": {
input: &awsxray.AWSData{
CWLogs: []awsxray.LogGroupMetadata{
{
LogGroup: aws.String("<log-group-1>"),
Arn: aws.String("arn:aws:logs:<region>:<account>:log-group:<log-group-1>:*"),
},
{
LogGroup: aws.String("<log-group-2>"),
Arn: aws.String("arn:aws:logs:<region>:<account>:log-group:<log-group-2>:*"),
},
},
},
want: map[string]any{
conventions.AttributeCloudProvider: conventions.AttributeCloudProviderAWS,
conventions.AttributeAWSLogGroupARNs: []any{
"arn:aws:logs:<region>:<account>:log-group:<log-group-1>:*",
"arn:aws:logs:<region>:<account>:log-group:<log-group-2>:*",
},
conventions.AttributeAWSLogGroupNames: []any{"<log-group-1>", "<log-group-2>"},
},
},
"WithEC2": {
input: &awsxray.AWSData{
EC2: &awsxray.EC2Metadata{
InstanceID: aws.String("<instance-id>"),
AvailabilityZone: aws.String("<ec2-az>"),
InstanceSize: aws.String("<instance-size>"),
AmiID: aws.String("<ami>"),
},
},
want: map[string]any{
conventions.AttributeCloudProvider: conventions.AttributeCloudProviderAWS,
conventions.AttributeCloudAvailabilityZone: "<ec2-az>",
conventions.AttributeHostID: "<instance-id>",
conventions.AttributeHostType: "<instance-size>",
conventions.AttributeHostImageID: "<ami>",
},
},
"WithECS": {
input: &awsxray.AWSData{
ECS: &awsxray.ECSMetadata{
ContainerName: aws.String("<container-name>"),
ContainerID: aws.String("<ecs-container-id>"),
AvailabilityZone: aws.String("<ecs-az>"),
},
},
want: map[string]any{
conventions.AttributeCloudProvider: conventions.AttributeCloudProviderAWS,
conventions.AttributeCloudAvailabilityZone: "<ecs-az>",
conventions.AttributeContainerName: "<container-name>",
conventions.AttributeContainerID: "<ecs-container-id>",
},
},
"WithEKS": {
input: &awsxray.AWSData{
EKS: &awsxray.EKSMetadata{
ClusterName: aws.String("<cluster-name>"),
Pod: aws.String("<pod>"),
ContainerID: aws.String("<eks-container-id>"),
},
},
want: map[string]any{
conventions.AttributeCloudProvider: conventions.AttributeCloudProviderAWS,
conventions.AttributeK8SPodName: "<pod>",
conventions.AttributeK8SClusterName: "<cluster-name>",
conventions.AttributeContainerID: "<eks-container-id>",
},
},
"WithBeanstalk": {
input: &awsxray.AWSData{
Beanstalk: &awsxray.BeanstalkMetadata{
Environment: aws.String("<environment>"),
VersionLabel: aws.String("<version-label>"),
DeploymentID: aws.Int64(1),
},
},
want: map[string]any{
conventions.AttributeCloudProvider: conventions.AttributeCloudProviderAWS,
conventions.AttributeServiceNamespace: "<environment>",
conventions.AttributeServiceInstanceID: "1",
conventions.AttributeServiceVersion: "<version-label>",
},
},
}
for name, testCase := range testCases {
t.Run(name, func(t *testing.T) {
attrs := pcommon.NewMap()
addAWSToResource(testCase.input, attrs)
assert.Equal(t, testCase.want, attrs.AsRaw())
})
}
}

0 comments on commit 5b1b8e6

Please sign in to comment.