Skip to content

Commit 7acac04

Browse files
feat: contribute aws lambda resource detector (#1478)
* feat: contribute aws lambda resource detector * update error handling to use OpenTelemetry.handle_error API --------- Co-authored-by: Kayla Reopelle <[email protected]>
1 parent abde37a commit 7acac04

File tree

5 files changed

+228
-15
lines changed

5 files changed

+228
-15
lines changed

resources/aws/README.md

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,12 @@ require 'opentelemetry/resource/detector'
3131

3232
OpenTelemetry::SDK.configure do |c|
3333
# Specify which AWS resource detectors to use
34-
c.resource = OpenTelemetry::Resource::Detector::AWS.detect([:ec2, :ecs])
34+
c.resource = OpenTelemetry::Resource::Detector::AWS.detect([:ec2, :ecs, :lambda])
3535

3636
# Or use just one detector
3737
c.resource = OpenTelemetry::Resource::Detector::AWS.detect([:ec2])
3838
c.resource = OpenTelemetry::Resource::Detector::AWS.detect([:ecs])
39+
c.resource = OpenTelemetry::Resource::Detector::AWS.detect([:lambda])
3940
end
4041
```
4142

@@ -75,7 +76,19 @@ Populates `cloud`, `container`, and AWS ECS-specific attributes for processes ru
7576
| `aws.log.stream.names` | The CloudWatch log stream names (if awslogs driver is used) |
7677
| `aws.log.stream.arns` | The CloudWatch log stream ARNs (if awslogs driver is used) |
7778

78-
Additional AWS platforms (EKS, Lambda) will be supported in future versions.
79+
### AWS Lambda Detector
80+
Populates `cloud` and `faas` (Function as a Service) attributes for processes running on AWS Lambda.
81+
| Resource Attribute | Description |
82+
|--------------------|-------------|
83+
| `cloud.platform` | The cloud platform. In this context, it's always "aws_lambda" |
84+
| `cloud.provider` | The cloud provider. In this context, it's always "aws" |
85+
| `cloud.region` | The AWS region from the `AWS_REGION` environment variable |
86+
| `faas.name` | The Lambda function name from the `AWS_LAMBDA_FUNCTION_NAME` environment variable |
87+
| `faas.version` | The Lambda function version from the `AWS_LAMBDA_FUNCTION_VERSION` environment variable |
88+
| `faas.instance` | The Lambda function instance ID from the `AWS_LAMBDA_LOG_STREAM_NAME` environment variable |
89+
| `faas.max_memory` | The Lambda function memory size in MB from the `AWS_LAMBDA_FUNCTION_MEMORY_SIZE` environment variable |
90+
91+
Additional AWS platforms (EKS) will be supported in future versions.
7992

8093
## License
8194

resources/aws/lib/opentelemetry/resource/detector/aws.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
require 'opentelemetry/resource/detector/aws/ec2'
88
require 'opentelemetry/resource/detector/aws/ecs'
9+
require 'opentelemetry/resource/detector/aws/lambda'
910

1011
module OpenTelemetry
1112
module Resource
@@ -29,6 +30,8 @@ def detect(detectors = [])
2930
EC2.detect
3031
when :ecs
3132
ECS.detect
33+
when :lambda
34+
Lambda.detect
3235
else
3336
OpenTelemetry.logger.warn("Unknown AWS resource detector: #{detector}")
3437
OpenTelemetry::SDK::Resources::Resource.create({})
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# frozen_string_literal: true
2+
3+
# Copyright The OpenTelemetry Authors
4+
#
5+
# SPDX-License-Identifier: Apache-2.0
6+
7+
module OpenTelemetry
8+
module Resource
9+
module Detector
10+
module AWS
11+
# Lambda contains detect class method for determining Lambda resource attributes
12+
module Lambda
13+
extend self
14+
15+
# Create a constant for resource semantic conventions
16+
RESOURCE = OpenTelemetry::SemanticConventions::Resource
17+
18+
def detect
19+
# Return empty resource if not running on Lambda
20+
return OpenTelemetry::SDK::Resources::Resource.create({}) unless lambda_environment?
21+
22+
resource_attributes = {}
23+
24+
begin
25+
# Set Lambda-specific attributes from environment variables
26+
resource_attributes[RESOURCE::CLOUD_PROVIDER] = 'aws'
27+
resource_attributes[RESOURCE::CLOUD_PLATFORM] = 'aws_lambda'
28+
resource_attributes[RESOURCE::CLOUD_REGION] = ENV.fetch('AWS_REGION', nil)
29+
resource_attributes[RESOURCE::FAAS_NAME] = ENV.fetch('AWS_LAMBDA_FUNCTION_NAME', nil)
30+
resource_attributes[RESOURCE::FAAS_VERSION] = ENV.fetch('AWS_LAMBDA_FUNCTION_VERSION', nil)
31+
resource_attributes[RESOURCE::FAAS_INSTANCE] = ENV.fetch('AWS_LAMBDA_LOG_STREAM_NAME', nil)
32+
33+
# Convert memory size to integer
34+
resource_attributes[RESOURCE::FAAS_MAX_MEMORY] = ENV['AWS_LAMBDA_FUNCTION_MEMORY_SIZE'].to_i if ENV['AWS_LAMBDA_FUNCTION_MEMORY_SIZE']
35+
rescue StandardError => e
36+
OpenTelemetry.handle_error(exception: e, message: 'Lambda resource detection failed')
37+
return OpenTelemetry::SDK::Resources::Resource.create({})
38+
end
39+
40+
# Filter out nil or empty values
41+
# Note: we need to handle integers differently since they don't respond to empty?
42+
resource_attributes.delete_if do |_key, value|
43+
value.nil? || (value.respond_to?(:empty?) && value.empty?)
44+
end
45+
46+
OpenTelemetry::SDK::Resources::Resource.create(resource_attributes)
47+
end
48+
49+
private
50+
51+
# Determines if the current environment is AWS Lambda
52+
#
53+
# @return [Boolean] true if running on AWS Lambda
54+
def lambda_environment?
55+
# Check for Lambda-specific environment variables
56+
!ENV['AWS_LAMBDA_FUNCTION_NAME'].nil? &&
57+
!ENV['AWS_LAMBDA_FUNCTION_VERSION'].nil? &&
58+
!ENV['AWS_LAMBDA_LOG_STREAM_NAME'].nil?
59+
end
60+
end
61+
end
62+
end
63+
end
64+
end
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# frozen_string_literal: true
2+
3+
# Copyright The OpenTelemetry Authors
4+
#
5+
# SPDX-License-Identifier: Apache-2.0
6+
7+
require 'test_helper'
8+
9+
describe OpenTelemetry::Resource::Detector::AWS::Lambda do
10+
let(:detector) { OpenTelemetry::Resource::Detector::AWS::Lambda }
11+
12+
describe '.detect' do
13+
before do
14+
# Store original environment variables
15+
@original_env = ENV.to_hash
16+
ENV.clear
17+
end
18+
19+
after do
20+
# Restore original environment
21+
ENV.replace(@original_env)
22+
end
23+
24+
it 'returns empty resource when not running on Lambda' do
25+
resource = detector.detect
26+
_(resource).must_be_instance_of(OpenTelemetry::SDK::Resources::Resource)
27+
_(resource.attribute_enumerator.to_h).must_equal({})
28+
end
29+
30+
describe 'when running on Lambda' do
31+
before do
32+
# Set Lambda environment variables
33+
ENV['AWS_LAMBDA_FUNCTION_NAME'] = 'my-function'
34+
ENV['AWS_LAMBDA_FUNCTION_VERSION'] = '$LATEST'
35+
ENV['AWS_LAMBDA_LOG_STREAM_NAME'] = '2021/01/01/[$LATEST]abcdef123456'
36+
ENV['AWS_REGION'] = 'us-west-2'
37+
ENV['AWS_LAMBDA_FUNCTION_MEMORY_SIZE'] = '512'
38+
end
39+
40+
it 'detects Lambda resources' do
41+
resource = detector.detect
42+
43+
_(resource).must_be_instance_of(OpenTelemetry::SDK::Resources::Resource)
44+
attributes = resource.attribute_enumerator.to_h
45+
46+
# Check Lambda-specific attributes
47+
_(attributes[OpenTelemetry::SemanticConventions::Resource::CLOUD_PROVIDER]).must_equal('aws')
48+
_(attributes[OpenTelemetry::SemanticConventions::Resource::CLOUD_PLATFORM]).must_equal('aws_lambda')
49+
_(attributes[OpenTelemetry::SemanticConventions::Resource::CLOUD_REGION]).must_equal('us-west-2')
50+
_(attributes[OpenTelemetry::SemanticConventions::Resource::FAAS_NAME]).must_equal('my-function')
51+
_(attributes[OpenTelemetry::SemanticConventions::Resource::FAAS_VERSION]).must_equal('$LATEST')
52+
_(attributes[OpenTelemetry::SemanticConventions::Resource::FAAS_INSTANCE]).must_equal('2021/01/01/[$LATEST]abcdef123456')
53+
_(attributes[OpenTelemetry::SemanticConventions::Resource::FAAS_MAX_MEMORY]).must_equal(512)
54+
end
55+
56+
it 'handles missing memory size' do
57+
ENV.delete('AWS_LAMBDA_FUNCTION_MEMORY_SIZE')
58+
59+
resource = detector.detect
60+
attributes = resource.attribute_enumerator.to_h
61+
62+
_(attributes).wont_include(OpenTelemetry::SemanticConventions::Resource::FAAS_MAX_MEMORY)
63+
end
64+
end
65+
66+
describe 'when partial Lambda environment is detected' do
67+
before do
68+
# Set only some Lambda environment variables
69+
ENV['AWS_LAMBDA_FUNCTION_NAME'] = 'my-function'
70+
# Missing AWS_LAMBDA_FUNCTION_VERSION
71+
ENV['AWS_LAMBDA_LOG_STREAM_NAME'] = '2021/01/01/[$LATEST]abcdef123456'
72+
end
73+
74+
it 'returns empty resource' do
75+
resource = detector.detect
76+
_(resource).must_be_instance_of(OpenTelemetry::SDK::Resources::Resource)
77+
_(resource.attribute_enumerator.to_h).must_equal({})
78+
end
79+
end
80+
end
81+
end

resources/aws/test/opentelemetry/resource/detector/aws_test.rb

Lines changed: 65 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
describe OpenTelemetry::Resource::Detector::AWS do
1010
let(:detector) { OpenTelemetry::Resource::Detector::AWS }
1111

12+
RESOURCE = OpenTelemetry::SemanticConventions::Resource
13+
1214
describe '.detect' do
1315
before do
1416
WebMock.disable_net_connect!
@@ -22,10 +24,13 @@
2224
.with(headers: { 'Accept' => '*/*' })
2325
.to_return(status: 404, body: 'Not Found')
2426

25-
# Clear environment variables for ECS
27+
# Clear environment variables for ECS and Lambda
2628
@original_env = ENV.to_hash
2729
ENV.delete('ECS_CONTAINER_METADATA_URI')
2830
ENV.delete('ECS_CONTAINER_METADATA_URI_V4')
31+
ENV.delete('AWS_LAMBDA_FUNCTION_NAME')
32+
ENV.delete('AWS_LAMBDA_FUNCTION_VERSION')
33+
ENV.delete('AWS_LAMBDA_LOG_STREAM_NAME')
2934
end
3035

3136
after do
@@ -53,6 +58,10 @@ def assert_detection_result(detectors)
5358
assert_detection_result([:ecs])
5459
end
5560

61+
it 'returns an empty resource when Lambda detection fails' do
62+
assert_detection_result([:lambda])
63+
end
64+
5665
it 'returns an empty resource with unknown detector' do
5766
assert_detection_result([:unknown])
5867
end
@@ -93,14 +102,14 @@ def assert_detection_result(detectors)
93102
resource = detector.detect([:ec2])
94103
attributes = resource.attribute_enumerator.to_h
95104

96-
_(attributes[OpenTelemetry::SemanticConventions::Resource::CLOUD_PROVIDER]).must_equal('aws')
97-
_(attributes[OpenTelemetry::SemanticConventions::Resource::CLOUD_PLATFORM]).must_equal('aws_ec2')
98-
_(attributes[OpenTelemetry::SemanticConventions::Resource::CLOUD_ACCOUNT_ID]).must_equal('123456789012')
99-
_(attributes[OpenTelemetry::SemanticConventions::Resource::CLOUD_REGION]).must_equal('us-west-2')
100-
_(attributes[OpenTelemetry::SemanticConventions::Resource::CLOUD_AVAILABILITY_ZONE]).must_equal('us-west-2b')
101-
_(attributes[OpenTelemetry::SemanticConventions::Resource::HOST_ID]).must_equal('i-1234567890abcdef0')
102-
_(attributes[OpenTelemetry::SemanticConventions::Resource::HOST_TYPE]).must_equal('m5.xlarge')
103-
_(attributes[OpenTelemetry::SemanticConventions::Resource::HOST_NAME]).must_equal(hostname)
105+
_(attributes[RESOURCE::CLOUD_PROVIDER]).must_equal('aws')
106+
_(attributes[RESOURCE::CLOUD_PLATFORM]).must_equal('aws_ec2')
107+
_(attributes[RESOURCE::CLOUD_ACCOUNT_ID]).must_equal('123456789012')
108+
_(attributes[RESOURCE::CLOUD_REGION]).must_equal('us-west-2')
109+
_(attributes[RESOURCE::CLOUD_AVAILABILITY_ZONE]).must_equal('us-west-2b')
110+
_(attributes[RESOURCE::HOST_ID]).must_equal('i-1234567890abcdef0')
111+
_(attributes[RESOURCE::HOST_TYPE]).must_equal('m5.xlarge')
112+
_(attributes[RESOURCE::HOST_NAME]).must_equal(hostname)
104113
end
105114

106115
describe 'with succesefful ECS detection' do
@@ -147,8 +156,8 @@ def assert_detection_result(detectors)
147156
resource = detector.detect([:ecs])
148157
attributes = resource.attribute_enumerator.to_h
149158

150-
_(attributes[OpenTelemetry::SemanticConventions::Resource::CLOUD_PROVIDER]).must_equal('aws')
151-
_(attributes[OpenTelemetry::SemanticConventions::Resource::CLOUD_PLATFORM]).must_equal('aws_ecs')
159+
_(attributes[RESOURCE::CLOUD_PROVIDER]).must_equal('aws')
160+
_(attributes[RESOURCE::CLOUD_PLATFORM]).must_equal('aws_ecs')
152161
end
153162
end
154163
end
@@ -164,13 +173,56 @@ def assert_detection_result(detectors)
164173
attributes = resource.attribute_enumerator.to_h
165174

166175
# Should include attributes from both detectors
167-
_(attributes[OpenTelemetry::SemanticConventions::Resource::CLOUD_PROVIDER]).must_equal('aws')
168-
_(attributes[OpenTelemetry::SemanticConventions::Resource::CLOUD_PLATFORM]).must_equal('aws_ecs')
176+
_(attributes[RESOURCE::CLOUD_PROVIDER]).must_equal('aws')
177+
_(attributes[RESOURCE::CLOUD_PLATFORM]).must_equal('aws_ecs')
169178
_(attributes['ec2.instance.id']).must_equal('i-1234567890abcdef0')
170179
end
171180
end
172181
end
173182
end
183+
184+
describe 'with successful Lambda detection' do
185+
before do
186+
# Set Lambda environment variables
187+
ENV['AWS_LAMBDA_FUNCTION_NAME'] = 'my-function'
188+
ENV['AWS_LAMBDA_FUNCTION_VERSION'] = '$LATEST'
189+
ENV['AWS_LAMBDA_LOG_STREAM_NAME'] = '2025/01/01/[$LATEST]abcdef123456'
190+
ENV['AWS_REGION'] = 'us-east-1'
191+
ENV['AWS_LAMBDA_FUNCTION_MEMORY_SIZE'] = '512'
192+
end
193+
194+
it 'detects Lambda resources when specified' do
195+
resource = detector.detect([:lambda])
196+
attributes = resource.attribute_enumerator.to_h
197+
198+
_(attributes[RESOURCE::CLOUD_PROVIDER]).must_equal('aws')
199+
_(attributes[RESOURCE::CLOUD_PLATFORM]).must_equal('aws_lambda')
200+
_(attributes[RESOURCE::CLOUD_REGION]).must_equal('us-east-1')
201+
_(attributes[RESOURCE::FAAS_NAME]).must_equal('my-function')
202+
_(attributes[RESOURCE::FAAS_VERSION]).must_equal('$LATEST')
203+
_(attributes[RESOURCE::FAAS_INSTANCE]).must_equal('2025/01/01/[$LATEST]abcdef123456')
204+
_(attributes[RESOURCE::FAAS_MAX_MEMORY]).must_equal(512)
205+
end
206+
207+
it 'detects multiple resources when specified' do
208+
# Create a mock EC2 resource
209+
ec2_resource = OpenTelemetry::SDK::Resources::Resource.create({
210+
RESOURCE::HOST_ID => 'i-1234567890abcdef0'
211+
})
212+
213+
# Stub EC2 detection to return the mock resource
214+
OpenTelemetry::Resource::Detector::AWS::EC2.stub :detect, ec2_resource do
215+
resource = detector.detect(%i[ec2 lambda])
216+
attributes = resource.attribute_enumerator.to_h
217+
218+
# Should include attributes from both detectors
219+
_(attributes[RESOURCE::CLOUD_PROVIDER]).must_equal('aws')
220+
_(attributes[RESOURCE::CLOUD_PLATFORM]).must_equal('aws_lambda')
221+
_(attributes[RESOURCE::FAAS_NAME]).must_equal('my-function')
222+
_(attributes[RESOURCE::HOST_ID]).must_equal('i-1234567890abcdef0')
223+
end
224+
end
225+
end
174226
end
175227
end
176228
end

0 commit comments

Comments
 (0)