Skip to content

Commit 0abf927

Browse files
authored
Merge pull request #1054 from lisguo/appsignals-ec2-support
[awsappsignals] Add ASG Name, Instance Id, and host name to emf attributes for ec2
2 parents 6d5a21e + 72dc4b6 commit 0abf927

25 files changed

+488
-309
lines changed

plugins/processors/awsappsignals/config/config.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ func (cfg *Config) Validate() error {
6161
if resolver.Name == "" {
6262
return errors.New("name must not be empty for k8s resolver")
6363
}
64-
case PlatformGeneric:
64+
case PlatformEC2, PlatformGeneric:
6565
default:
6666
return errors.New("unknown resolver")
6767
}

plugins/processors/awsappsignals/config/config_test.go

+7-1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@ func TestValidatePassed(t *testing.T) {
2121
Rules: nil,
2222
}
2323
assert.Nil(t, config.Validate())
24+
25+
config = Config{
26+
Resolvers: []Resolver{NewEC2Resolver("test"), NewGenericResolver("")},
27+
Rules: nil,
28+
}
29+
assert.Nil(t, config.Validate())
2430
}
2531

2632
func TestValidateFailedOnEmptyResolver(t *testing.T) {
@@ -31,7 +37,7 @@ func TestValidateFailedOnEmptyResolver(t *testing.T) {
3137
assert.NotNil(t, config.Validate())
3238
}
3339

34-
func TestValidateFailedOnEmptyClusterName(t *testing.T) {
40+
func TestValidateFailedOnEmptyResolverName(t *testing.T) {
3541
config := Config{
3642
Resolvers: []Resolver{NewEKSResolver("")},
3743
Rules: nil,

plugins/processors/awsappsignals/config/resolvers.go

+9
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ const (
1010
PlatformEKS = "eks"
1111
// PlatformK8s Native Kubernetes
1212
PlatformK8s = "k8s"
13+
// PlatformEC2 Amazon EC2 platform
14+
PlatformEC2 = "ec2"
1315
)
1416

1517
type Resolver struct {
@@ -31,6 +33,13 @@ func NewK8sResolver(name string) Resolver {
3133
}
3234
}
3335

36+
func NewEC2Resolver(name string) Resolver {
37+
return Resolver{
38+
Name: name,
39+
Platform: PlatformEC2,
40+
}
41+
}
42+
3443
func NewGenericResolver(name string) Resolver {
3544
return Resolver{
3645
Name: name,

plugins/processors/awsappsignals/config/resolvers_test.go

+5
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ func TestK8sResolver(t *testing.T) {
1919
assert.Equal(t, "k8s", resolver.Platform)
2020
}
2121

22+
func TestEC2Resolver(t *testing.T) {
23+
resolver := NewEC2Resolver("test")
24+
assert.Equal(t, "ec2", resolver.Platform)
25+
}
26+
2227
func TestNewGenericResolver(t *testing.T) {
2328
resolver := NewGenericResolver("")
2429
assert.Equal(t, "generic", resolver.Platform)

plugins/processors/awsappsignals/internal/attributes/attributes.go

+10
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ const (
1212
AWSRemoteTarget = "aws.remote.target"
1313
AWSHostedInEnvironment = "aws.hostedin.environment"
1414

15+
// resource detection processor attributes
16+
ResourceDetectionHostId = "host.id"
17+
ResourceDetectionHostName = "host.name"
18+
ResourceDetectionASG = "ec2.tag.aws:autoscaling:groupName"
19+
1520
// kubernetes resource attributes
1621
K8SDeploymentName = "k8s.deployment.name"
1722
K8SStatefulSetName = "k8s.statefulset.name"
@@ -21,9 +26,14 @@ const (
2126
K8SPodName = "k8s.pod.name"
2227
K8SRemoteNamespace = "K8s.RemoteNamespace"
2328

29+
// ec2 resource attributes
30+
EC2AutoScalingGroupName = "EC2.AutoScalingGroupName"
31+
EC2InstanceId = "EC2.InstanceId"
32+
2433
// hosted in attribute names
2534
HostedInClusterNameEKS = "HostedIn.EKS.Cluster"
2635
HostedInClusterNameK8s = "HostedIn.K8s.Cluster"
2736
HostedInK8SNamespace = "HostedIn.K8s.Namespace"
37+
HostedInEC2Environment = "HostedIn.EC2.Environment"
2838
HostedInEnvironment = "HostedIn.Environment"
2939
)

plugins/processors/awsappsignals/internal/resolver/attributesresolver.go

+5-2
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,12 @@ func NewAttributesResolver(resolvers []appsignalsconfig.Resolver, logger *zap.Lo
3434
//TODO: Logic for native k8s needs to be implemented
3535
subResolvers := []subResolver{}
3636
for _, resolver := range resolvers {
37-
if resolver.Platform == appsignalsconfig.PlatformEKS || resolver.Platform == appsignalsconfig.PlatformK8s {
37+
switch resolver.Platform {
38+
case appsignalsconfig.PlatformEKS, appsignalsconfig.PlatformK8s:
3839
subResolvers = append(subResolvers, getKubernetesResolver(logger), newKubernetesHostedInAttributeResolver(resolver.Name))
39-
} else {
40+
case appsignalsconfig.PlatformEC2:
41+
subResolvers = append(subResolvers, newEC2HostedInAttributeResolver(resolver.Name))
42+
default:
4043
subResolvers = append(subResolvers, newHostedInAttributeResolver(resolver.Name, DefaultHostedInAttributes))
4144
}
4245
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package resolver
5+
6+
import (
7+
"context"
8+
9+
"go.opentelemetry.io/collector/pdata/pcommon"
10+
11+
attr "github.com/aws/amazon-cloudwatch-agent/plugins/processors/awsappsignals/internal/attributes"
12+
)
13+
14+
const AttributePlatformEC2 = "EC2"
15+
16+
// EC2HostedInAttributes is an allow-list that also renames attributes from the resource detection processor
17+
var EC2HostedInAttributes = map[string]string{
18+
attr.ResourceDetectionHostId: attr.EC2InstanceId,
19+
attr.ResourceDetectionHostName: attr.ResourceDetectionHostName,
20+
attr.ResourceDetectionASG: attr.EC2AutoScalingGroupName,
21+
}
22+
23+
type ec2HostedInAttributeResolver struct {
24+
name string
25+
attributeMap map[string]string
26+
}
27+
28+
func newEC2HostedInAttributeResolver(name string) *ec2HostedInAttributeResolver {
29+
if name == "" {
30+
name = AttributePlatformEC2
31+
}
32+
return &ec2HostedInAttributeResolver{
33+
name: name,
34+
attributeMap: EC2HostedInAttributes,
35+
}
36+
}
37+
func (h *ec2HostedInAttributeResolver) Process(attributes, resourceAttributes pcommon.Map) error {
38+
for attrKey, mappingKey := range h.attributeMap {
39+
if val, ok := resourceAttributes.Get(attrKey); ok {
40+
attributes.PutStr(mappingKey, val.AsString())
41+
}
42+
}
43+
44+
// If aws.hostedin.environment is populated, override HostedIn.EC2.Environment value
45+
// Otherwise, keep ASG name if it exists
46+
if val, ok := resourceAttributes.Get(attr.AWSHostedInEnvironment); ok {
47+
attributes.PutStr(attr.HostedInEC2Environment, val.AsString())
48+
} else if val, ok := resourceAttributes.Get(attr.ResourceDetectionASG); ok {
49+
attributes.PutStr(attr.HostedInEC2Environment, val.AsString())
50+
} else {
51+
attributes.PutStr(attr.HostedInEC2Environment, h.name)
52+
}
53+
54+
return nil
55+
}
56+
57+
func (h *ec2HostedInAttributeResolver) Stop(ctx context.Context) error {
58+
return nil
59+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package resolver
5+
6+
import (
7+
"testing"
8+
9+
"github.com/stretchr/testify/assert"
10+
"go.opentelemetry.io/collector/pdata/pcommon"
11+
12+
attr "github.com/aws/amazon-cloudwatch-agent/plugins/processors/awsappsignals/internal/attributes"
13+
)
14+
15+
func TestEC2HostedInAttributeResolverWithNoConfiguredName_NoASG_NoEnv(t *testing.T) {
16+
resolver := newEC2HostedInAttributeResolver("")
17+
18+
attributes := pcommon.NewMap()
19+
resourceAttributes := pcommon.NewMap()
20+
21+
resolver.Process(attributes, resourceAttributes)
22+
envAttr, ok := attributes.Get(attr.HostedInEC2Environment)
23+
assert.True(t, ok)
24+
assert.Equal(t, AttributePlatformEC2, envAttr.AsString())
25+
}
26+
27+
func TestEC2HostedInAttributeResolverWithNoConfiguredName_ASGExists_NoEnv(t *testing.T) {
28+
resolver := newEC2HostedInAttributeResolver("")
29+
30+
asgName := "ASG"
31+
attributes := pcommon.NewMap()
32+
resourceAttributes := pcommon.NewMap()
33+
resourceAttributes.PutStr(attr.ResourceDetectionASG, asgName)
34+
35+
resolver.Process(attributes, resourceAttributes)
36+
envAttr, ok := attributes.Get(attr.HostedInEC2Environment)
37+
assert.True(t, ok)
38+
assert.Equal(t, asgName, envAttr.AsString())
39+
}
40+
41+
func TestEC2HostedInAttributeResolverWithConfiguredName_NoASG_NoEnv(t *testing.T) {
42+
resolver := newEC2HostedInAttributeResolver("test")
43+
44+
attributes := pcommon.NewMap()
45+
resourceAttributes := pcommon.NewMap()
46+
47+
resolver.Process(attributes, resourceAttributes)
48+
envAttr, ok := attributes.Get(attr.HostedInEC2Environment)
49+
assert.True(t, ok)
50+
assert.Equal(t, "test", envAttr.AsString())
51+
}
52+
53+
func TestEC2HostedInAttributeResolverWithConfiguredName_ASGExists_NoEnv(t *testing.T) {
54+
resolver := newEC2HostedInAttributeResolver("test")
55+
56+
asgName := "ASG"
57+
attributes := pcommon.NewMap()
58+
resourceAttributes := pcommon.NewMap()
59+
resourceAttributes.PutStr(attr.ResourceDetectionASG, asgName)
60+
61+
resolver.Process(attributes, resourceAttributes)
62+
envAttr, ok := attributes.Get(attr.HostedInEC2Environment)
63+
assert.True(t, ok)
64+
assert.Equal(t, asgName, envAttr.AsString())
65+
}
66+
67+
func TestEC2HostedInAttributeResolverWithNoConfiguredName_NoASG_EnvExists(t *testing.T) {
68+
resolver := newEC2HostedInAttributeResolver("")
69+
70+
envName := "my-env"
71+
attributes := pcommon.NewMap()
72+
resourceAttributes := pcommon.NewMap()
73+
resourceAttributes.PutStr(attr.AWSHostedInEnvironment, envName)
74+
75+
resolver.Process(attributes, resourceAttributes)
76+
envAttr, ok := attributes.Get(attr.HostedInEC2Environment)
77+
assert.True(t, ok)
78+
assert.Equal(t, envName, envAttr.AsString())
79+
}
80+
81+
func TestEC2HostedInAttributeResolverWithConfiguredName_NoASG_EnvExists(t *testing.T) {
82+
resolver := newEC2HostedInAttributeResolver("test")
83+
84+
envName := "my-env"
85+
attributes := pcommon.NewMap()
86+
resourceAttributes := pcommon.NewMap()
87+
resourceAttributes.PutStr(attr.AWSHostedInEnvironment, envName)
88+
89+
resolver.Process(attributes, resourceAttributes)
90+
envAttr, ok := attributes.Get(attr.HostedInEC2Environment)
91+
assert.True(t, ok)
92+
assert.Equal(t, envName, envAttr.AsString())
93+
}
94+
95+
func TestEC2HostedInAttributeResolverWithNoConfiguredName_ASGExists_EnvExists(t *testing.T) {
96+
resolver := newEC2HostedInAttributeResolver("")
97+
98+
asgName := "ASG"
99+
envName := "my-env"
100+
attributes := pcommon.NewMap()
101+
resourceAttributes := pcommon.NewMap()
102+
resourceAttributes.PutStr(attr.EC2AutoScalingGroupName, asgName)
103+
resourceAttributes.PutStr(attr.AWSHostedInEnvironment, envName)
104+
105+
resolver.Process(attributes, resourceAttributes)
106+
envAttr, ok := attributes.Get(attr.HostedInEC2Environment)
107+
assert.True(t, ok)
108+
assert.Equal(t, envName, envAttr.AsString())
109+
}
110+
111+
func TestEC2HostedInAttributeResolverWithConfiguredName_ASGExists_EnvExists(t *testing.T) {
112+
resolver := newEC2HostedInAttributeResolver("test")
113+
114+
asgName := "ASG"
115+
envName := "my-env"
116+
attributes := pcommon.NewMap()
117+
resourceAttributes := pcommon.NewMap()
118+
resourceAttributes.PutStr(attr.EC2AutoScalingGroupName, asgName)
119+
resourceAttributes.PutStr(attr.AWSHostedInEnvironment, envName)
120+
121+
resolver.Process(attributes, resourceAttributes)
122+
envAttr, ok := attributes.Get(attr.HostedInEC2Environment)
123+
assert.True(t, ok)
124+
assert.Equal(t, envName, envAttr.AsString())
125+
}
126+
127+
func TestEC2HostedInAttributeResolverWithResourceDetectionAttributes(t *testing.T) {
128+
resolver := newEC2HostedInAttributeResolver("")
129+
130+
attributes := pcommon.NewMap()
131+
resourceAttributes := pcommon.NewMap()
132+
resourceAttributes.PutStr(attr.ResourceDetectionHostId, "hostid")
133+
resourceAttributes.PutStr(attr.ResourceDetectionHostName, "hostname")
134+
resourceAttributes.PutStr(attr.ResourceDetectionASG, "asg")
135+
136+
resolver.Process(attributes, resourceAttributes)
137+
expectedInstanceId, ok := attributes.Get(attr.EC2InstanceId)
138+
assert.True(t, ok)
139+
assert.Equal(t, "hostid", expectedInstanceId.AsString())
140+
141+
expectedHostName, ok := attributes.Get(attr.ResourceDetectionHostName)
142+
assert.True(t, ok)
143+
assert.Equal(t, "hostname", expectedHostName.AsString())
144+
145+
expectedASG, ok := attributes.Get(attr.EC2AutoScalingGroupName)
146+
assert.True(t, ok)
147+
assert.Equal(t, "asg", expectedASG.AsString())
148+
}

translator/tocwconfig/sampleConfig/appsignals_and_eks_config.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,7 @@ processors:
408408
enabled: true
409409
tags:
410410
- ^kubernetes.io/cluster/.*$
411+
- ^aws:autoscaling:groupName
411412
ecs:
412413
resource_attributes:
413414
aws.ecs.cluster.arn:

translator/tocwconfig/sampleConfig/base_appsignals_config.conf

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
debug = false
44
flush_interval = "1s"
55
flush_jitter = "0s"
6-
hostname = "host_name_from_env"
6+
hostname = ""
77
interval = "60s"
8-
logfile = ""
8+
logfile = "/opt/aws/amazon-cloudwatch-agent/logs/amazon-cloudwatch-agent.log"
99
logtarget = "lumberjack"
1010
metric_batch_size = 1000
1111
metric_buffer_limit = 10000

translator/tocwconfig/sampleConfig/base_appsignals_config.json

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"region": "us-east-1"
44
},
55
"logs": {
6+
"log_stream_name": "host_name_from_env",
67
"metrics_collected": {
78
"app_signals": {}
89
},

0 commit comments

Comments
 (0)