opentelemetry-resource-detector-aws 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cd45e9862a275324dbb2e83785487d3b291efe7a0e085aa828d834c3df75d246
4
- data.tar.gz: 3854d519a3800a1872df7869b67bf2891b0d658bfec440202b3b0d425bb30a08
3
+ metadata.gz: 5a8ec0b68eb13782494f65cdafeed9f75447bb1762202bbbcde32cf795f4f3bb
4
+ data.tar.gz: 5039cfbeb483722204a6e7410881a675cbe7f8326c603a543e7ec233244616af
5
5
  SHA512:
6
- metadata.gz: edc2e5a3ab6d0e4f9f5342e3055ea5cae15d46833f31526e280c08148e19ac5fa5e95bb0625c836f43473a638735032b246743f82766617e8502ad53406b5f4d
7
- data.tar.gz: f5a4d7b2dae2e7bc1f04d2968f6404347a1e8ddf252bd9578ef6e98365b3328125016597f66b52151e06b869a718456702380ba01afcfe81da4a058c5f0917b6
6
+ metadata.gz: 0b0171c2e36d1c4c7ff95259b49699e5af069dd0fc34b8ec079682fb75b989190af36c5792e25e2f875499f50f4107c5461c45532a057cffadc283ce1c47dec3
7
+ data.tar.gz: 99ba3b6378c03a344f119329821c576cb78408bfa0e937dccb01f89e9503a79c02cd723e97e371789135f02529dd201720406f3c7f5697d119f904bdad6cc2c2
data/README.md CHANGED
@@ -30,7 +30,13 @@ require 'opentelemetry/sdk'
30
30
  require 'opentelemetry/resource/detector'
31
31
 
32
32
  OpenTelemetry::SDK.configure do |c|
33
- c.resource = OpenTelemetry::Resource::Detector::AWS.detect
33
+ # Specify which AWS resource detectors to use
34
+ c.resource = OpenTelemetry::Resource::Detector::AWS.detect([:ec2, :ecs, :lambda])
35
+
36
+ # Or use just one detector
37
+ c.resource = OpenTelemetry::Resource::Detector::AWS.detect([:ec2])
38
+ c.resource = OpenTelemetry::Resource::Detector::AWS.detect([:ecs])
39
+ c.resource = OpenTelemetry::Resource::Detector::AWS.detect([:lambda])
34
40
  end
35
41
  ```
36
42
 
@@ -52,7 +58,37 @@ Populates `cloud` and `host` for processes running on Amazon EC2, including abst
52
58
  | `host.name` | Value of hostname from `/latest/meta-data/hostname` request |
53
59
  | `host.type` | Value of `instanceType` from `/latest/dynamic/instance-identity/document` request |
54
60
 
55
- Additional AWS platforms (ECS, EKS, Lambda) will be supported in future versions.
61
+ ### AWS ECS Detector
62
+
63
+ <!-- cspell:ignore launchtype awslogs -->
64
+ Populates `cloud`, `container`, and AWS ECS-specific attributes for processes running on Amazon ECS.
65
+ | Resource Attribute | Description |
66
+ |--------------------|-------------|
67
+ | `cloud.platform` | The cloud platform. In this context, it's always "aws_ecs" |
68
+ | `cloud.provider` | The cloud provider. In this context, it's always "aws" |
69
+ | `container.id` | The container ID from the `/proc/self/cgroup` file |
70
+ | `container.name` | The hostname of the container |
71
+ | `aws.ecs.container.arn` | The hostname of the container |
72
+ | `aws.ecs.cluster.arn` | The ARN of the ECS cluster |
73
+ | `aws.ecs.launchtype` | The launch type for the ECS task (e.g., "fargate" or "ec2") |
74
+ | `aws.ecs.task.arn` | The ARN of the ECS task |
75
+ | `aws.log.group.names` | The CloudWatch log group names (if awslogs driver is used) |
76
+ | `aws.log.stream.names` | The CloudWatch log stream names (if awslogs driver is used) |
77
+ | `aws.log.stream.arns` | The CloudWatch log stream ARNs (if awslogs driver is used) |
78
+
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.
56
92
 
57
93
  ## License
58
94
 
@@ -7,6 +7,7 @@
7
7
  require 'net/http'
8
8
  require 'json'
9
9
  require 'opentelemetry/common'
10
+ require 'opentelemetry/semantic_conventions/resource'
10
11
 
11
12
  module OpenTelemetry
12
13
  module Resource
@@ -59,7 +60,7 @@ module OpenTelemetry
59
60
  resource_attributes[RESOURCE::HOST_TYPE] = identity['instanceType']
60
61
  resource_attributes[RESOURCE::HOST_NAME] = hostname
61
62
  rescue StandardError => e
62
- OpenTelemetry.logger.debug("EC2 resource detection failed: #{e.message}")
63
+ OpenTelemetry.handle_error(exception: e, message: 'EC2 resource detection failed')
63
64
  return OpenTelemetry::SDK::Resources::Resource.create({})
64
65
  end
65
66
 
@@ -133,7 +134,7 @@ module OpenTelemetry
133
134
  http.request(request)
134
135
  end
135
136
  rescue StandardError => e
136
- OpenTelemetry.logger.debug("EC2 metadata service request failed: #{e.message}")
137
+ OpenTelemetry.handle_error(exception: e, message: 'EC2 metadata service request failed')
137
138
  nil
138
139
  end
139
140
  end
@@ -0,0 +1,173 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright The OpenTelemetry Authors
4
+ #
5
+ # SPDX-License-Identifier: Apache-2.0
6
+
7
+ require 'net/http'
8
+ require 'json'
9
+ require 'socket'
10
+ require 'opentelemetry/common'
11
+ require 'opentelemetry/semantic_conventions/resource'
12
+
13
+ module OpenTelemetry
14
+ module Resource
15
+ module Detector
16
+ module AWS
17
+ # ECS contains detect class method for determining the ECS resource attributes
18
+ module ECS
19
+ extend self
20
+
21
+ # Container ID length from cgroup file
22
+ CONTAINER_ID_LENGTH = 64
23
+
24
+ # HTTP request timeout in seconds
25
+ HTTP_TIMEOUT = 5
26
+
27
+ # Create a constant for resource semantic conventions
28
+ RESOURCE = OpenTelemetry::SemanticConventions::Resource
29
+
30
+ def detect
31
+ # Return empty resource if not running on ECS
32
+ metadata_uri = ENV.fetch('ECS_CONTAINER_METADATA_URI', nil)
33
+ metadata_uri_v4 = ENV.fetch('ECS_CONTAINER_METADATA_URI_V4', nil)
34
+
35
+ return OpenTelemetry::SDK::Resources::Resource.create({}) if metadata_uri.nil? && metadata_uri_v4.nil?
36
+
37
+ resource_attributes = {}
38
+ container_id = fetch_container_id
39
+
40
+ # Base ECS resource attributes
41
+ resource_attributes[RESOURCE::CLOUD_PROVIDER] = 'aws'
42
+ resource_attributes[RESOURCE::CLOUD_PLATFORM] = 'aws_ecs'
43
+ resource_attributes[RESOURCE::CONTAINER_NAME] = Socket.gethostname
44
+ resource_attributes[RESOURCE::CONTAINER_ID] = container_id unless container_id.empty?
45
+
46
+ # If v4 endpoint is not available, return basic resource
47
+ return OpenTelemetry::SDK::Resources::Resource.create(resource_attributes) if metadata_uri_v4.nil?
48
+
49
+ begin
50
+ # Fetch container and task metadata
51
+ container_metadata = JSON.parse(http_get(metadata_uri_v4.to_s))
52
+ task_metadata = JSON.parse(http_get("#{metadata_uri_v4}/task"))
53
+
54
+ task_arn = task_metadata['TaskARN']
55
+ base_arn = task_arn[0..task_arn.rindex(':') - 1]
56
+
57
+ cluster = task_metadata['Cluster']
58
+ cluster_arn = cluster.start_with?('arn:') ? cluster : "#{base_arn}:cluster/#{cluster}"
59
+
60
+ # Set ECS-specific attributes
61
+ resource_attributes[RESOURCE::AWS_ECS_CONTAINER_ARN] = container_metadata['ContainerARN']
62
+ resource_attributes[RESOURCE::AWS_ECS_CLUSTER_ARN] = cluster_arn
63
+ resource_attributes[RESOURCE::AWS_ECS_LAUNCHTYPE] = task_metadata['LaunchType'].downcase
64
+ resource_attributes[RESOURCE::AWS_ECS_TASK_ARN] = task_arn
65
+ resource_attributes[RESOURCE::AWS_ECS_TASK_FAMILY] = task_metadata['Family']
66
+ resource_attributes[RESOURCE::AWS_ECS_TASK_REVISION] = task_metadata['Revision']
67
+
68
+ # Add logging attributes if awslogs is used
69
+ logs_attributes = get_logs_resource(container_metadata)
70
+ resource_attributes.merge!(logs_attributes)
71
+ rescue StandardError => e
72
+ OpenTelemetry.handle_error(exception: e, message: 'ECS resource detection failed')
73
+ return OpenTelemetry::SDK::Resources::Resource.create({})
74
+ end
75
+
76
+ # Filter out nil or empty values
77
+ resource_attributes.delete_if { |_key, value| value.nil? || value.empty? }
78
+ OpenTelemetry::SDK::Resources::Resource.create(resource_attributes)
79
+ end
80
+
81
+ private
82
+
83
+ # Fetches container ID from /proc/self/cgroup file
84
+ #
85
+ # @return [String] The container ID or empty string if not found
86
+ def fetch_container_id
87
+ begin
88
+ File.open('/proc/self/cgroup', 'r') do |file|
89
+ file.each_line do |line|
90
+ line = line.strip
91
+ # Look for container ID (64 chars) at the end of the line
92
+ return line[-CONTAINER_ID_LENGTH..-1] if line.length > CONTAINER_ID_LENGTH
93
+ end
94
+ end
95
+ rescue Errno::ENOENT => e
96
+ OpenTelemetry.handle_error(exception: e, message: 'Failed to get container ID on ECS')
97
+ end
98
+
99
+ ''
100
+ end
101
+
102
+ # Extracting logging-related resource attributes
103
+ #
104
+ # @param container_metadata [Hash] Container metadata from ECS metadata endpoint
105
+ # @returhn [Hash] Resource attributes for logging configuration
106
+ def get_logs_resource(container_metadata)
107
+ log_attributes = {}
108
+
109
+ if container_metadata['LogDriver'] == 'awslogs'
110
+ log_options = container_metadata['LogOptions']
111
+
112
+ if log_options
113
+ logs_region = log_options['awslogs-region']
114
+ logs_group_name = log_options['awslogs-group']
115
+ logs_stream_name = log_options['awslogs-stream']
116
+
117
+ container_arn = container_metadata['ContainerARN']
118
+
119
+ # Parse region from ARN if not specified in log options
120
+ if logs_region.nil? || logs_region.empty?
121
+ region_match = container_arn.match(/arn:aws:ecs:([^:]+):.*/)
122
+ logs_region = region_match[1] if region_match
123
+ end
124
+
125
+ # Parse account ID from ARN
126
+ account_match = container_arn.match(/arn:aws:ecs:[^:]+:([^:]+):.*/)
127
+ aws_account = account_match[1] if account_match
128
+
129
+ logs_group_arn = nil
130
+ logs_stream_arn = nil
131
+
132
+ if logs_region && aws_account
133
+ logs_group_arn = "arn:aws:logs:#{logs_region}:#{aws_account}:log-group:#{logs_group_name}" if logs_group_name
134
+
135
+ logs_stream_arn = "arn:aws:logs:#{logs_region}:#{aws_account}:log-group:#{logs_group_name}:log-stream:#{logs_stream_name}" if logs_stream_name && logs_group_name
136
+ end
137
+
138
+ log_attributes[RESOURCE::AWS_LOG_GROUP_NAMES] = [logs_group_name].compact
139
+ log_attributes[RESOURCE::AWS_LOG_GROUP_ARNS] = [logs_group_arn].compact
140
+ log_attributes[RESOURCE::AWS_LOG_STREAM_NAMES] = [logs_stream_name].compact
141
+ log_attributes[RESOURCE::AWS_LOG_STREAM_ARNS] = [logs_stream_arn].compact
142
+ else
143
+ OpenTelemetry.handle_error(message: 'The metadata endpoint v4 has returned \'awslogs\' as \'LogDriver\', but there is no \'LogOptions\' data')
144
+ end
145
+ end
146
+
147
+ log_attributes
148
+ end
149
+
150
+ # Makes an HTTP GET request to the specified URL
151
+ #
152
+ # @param url [String] The URL to request
153
+ # @return [String] The response body
154
+ def http_get(url)
155
+ uri = URI.parse(url)
156
+ request = Net::HTTP::Get.new(uri)
157
+
158
+ http = Net::HTTP.new(uri.host, uri.port)
159
+ http.open_timeout = HTTP_TIMEOUT
160
+ http.read_timeout = HTTP_TIMEOUT
161
+
162
+ OpenTelemetry::Common::Utilities.untraced do
163
+ response = http.request(request)
164
+ raise "HTTP request failed with status #{response.code}" unless response.is_a?(Net::HTTPSuccess)
165
+
166
+ response.body
167
+ end
168
+ end
169
+ end
170
+ end
171
+ end
172
+ end
173
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright The OpenTelemetry Authors
4
+ #
5
+ # SPDX-License-Identifier: Apache-2.0
6
+
7
+ require 'opentelemetry/semantic_conventions/resource'
8
+
9
+ module OpenTelemetry
10
+ module Resource
11
+ module Detector
12
+ module AWS
13
+ # Lambda contains detect class method for determining Lambda resource attributes
14
+ module Lambda
15
+ extend self
16
+
17
+ # Create a constant for resource semantic conventions
18
+ RESOURCE = OpenTelemetry::SemanticConventions::Resource
19
+
20
+ def detect
21
+ # Return empty resource if not running on Lambda
22
+ return OpenTelemetry::SDK::Resources::Resource.create({}) unless lambda_environment?
23
+
24
+ resource_attributes = {}
25
+
26
+ begin
27
+ # Set Lambda-specific attributes from environment variables
28
+ resource_attributes[RESOURCE::CLOUD_PROVIDER] = 'aws'
29
+ resource_attributes[RESOURCE::CLOUD_PLATFORM] = 'aws_lambda'
30
+ resource_attributes[RESOURCE::CLOUD_REGION] = ENV.fetch('AWS_REGION', nil)
31
+ resource_attributes[RESOURCE::FAAS_NAME] = ENV.fetch('AWS_LAMBDA_FUNCTION_NAME', nil)
32
+ resource_attributes[RESOURCE::FAAS_VERSION] = ENV.fetch('AWS_LAMBDA_FUNCTION_VERSION', nil)
33
+ resource_attributes[RESOURCE::FAAS_INSTANCE] = ENV.fetch('AWS_LAMBDA_LOG_STREAM_NAME', nil)
34
+
35
+ # Convert memory size to integer
36
+ resource_attributes[RESOURCE::FAAS_MAX_MEMORY] = ENV['AWS_LAMBDA_FUNCTION_MEMORY_SIZE'].to_i if ENV['AWS_LAMBDA_FUNCTION_MEMORY_SIZE']
37
+ rescue StandardError => e
38
+ OpenTelemetry.handle_error(exception: e, message: 'Lambda resource detection failed')
39
+ return OpenTelemetry::SDK::Resources::Resource.create({})
40
+ end
41
+
42
+ # Filter out nil or empty values
43
+ # Note: we need to handle integers differently since they don't respond to empty?
44
+ resource_attributes.delete_if do |_key, value|
45
+ value.nil? || (value.respond_to?(:empty?) && value.empty?)
46
+ end
47
+
48
+ OpenTelemetry::SDK::Resources::Resource.create(resource_attributes)
49
+ end
50
+
51
+ private
52
+
53
+ # Determines if the current environment is AWS Lambda
54
+ #
55
+ # @return [Boolean] true if running on AWS Lambda
56
+ def lambda_environment?
57
+ # Check for Lambda-specific environment variables
58
+ !ENV['AWS_LAMBDA_FUNCTION_NAME'].nil? &&
59
+ !ENV['AWS_LAMBDA_FUNCTION_VERSION'].nil? &&
60
+ !ENV['AWS_LAMBDA_LOG_STREAM_NAME'].nil?
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -8,7 +8,7 @@ module OpenTelemetry
8
8
  module Resource
9
9
  module Detector
10
10
  module AWS
11
- VERSION = '0.1.0'
11
+ VERSION = '0.3.0'
12
12
  end
13
13
  end
14
14
  end
@@ -5,6 +5,8 @@
5
5
  # SPDX-License-Identifier: Apache-2.0
6
6
 
7
7
  require 'opentelemetry/resource/detector/aws/ec2'
8
+ require 'opentelemetry/resource/detector/aws/ecs'
9
+ require 'opentelemetry/resource/detector/aws/lambda'
8
10
 
9
11
  module OpenTelemetry
10
12
  module Resource
@@ -13,12 +15,33 @@ module OpenTelemetry
13
15
  module AWS
14
16
  extend self
15
17
 
16
- def detect
17
- # This will be a composite of all the AWS platform detectors
18
- EC2.detect
18
+ RESOURCE = OpenTelemetry::SDK::Resources::Resource
19
19
 
20
- # For now, return the EC2 resource directly
21
- # In the future, we'll implement detection for EC2, ECS, EKS, etc.
20
+ # Get resources from specified AWS resource detectors
21
+ #
22
+ # @param detectors [Array<Symbol>] List of detectors to use (e.g., :ec2)
23
+ # @return [OpenTelemetry::SDK::Resources::Resource] The detected AWS resources
24
+ def detect(detectors = [])
25
+ return RESOURCE.create({}) if detectors.empty?
26
+
27
+ resources = detectors.map do |detector|
28
+ case detector
29
+ when :ec2
30
+ EC2.detect
31
+ when :ecs
32
+ ECS.detect
33
+ when :lambda
34
+ Lambda.detect
35
+ else
36
+ OpenTelemetry.logger.warn("Unknown AWS resource detector: #{detector}")
37
+ OpenTelemetry::SDK::Resources::Resource.create({})
38
+ end
39
+ end
40
+
41
+ # Merge all resources into a single resource
42
+ resources.reduce(OpenTelemetry::SDK::Resources::Resource.create({})) do |merged, resource|
43
+ merged.merge(resource)
44
+ end
22
45
  end
23
46
  end
24
47
  end
@@ -4,4 +4,5 @@
4
4
  #
5
5
  # SPDX-License-Identifier: Apache-2.0
6
6
 
7
+ require 'opentelemetry/semantic_conventions/resource'
7
8
  require_relative 'opentelemetry/resource/detector'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: opentelemetry-resource-detector-aws
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - OpenTelemetry Authors
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-04-09 00:00:00.000000000 Z
11
+ date: 2025-05-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: opentelemetry-sdk
@@ -37,6 +37,8 @@ files:
37
37
  - lib/opentelemetry/resource/detector.rb
38
38
  - lib/opentelemetry/resource/detector/aws.rb
39
39
  - lib/opentelemetry/resource/detector/aws/ec2.rb
40
+ - lib/opentelemetry/resource/detector/aws/ecs.rb
41
+ - lib/opentelemetry/resource/detector/aws/lambda.rb
40
42
  - lib/opentelemetry/resource/detector/aws/version.rb
41
43
  homepage: https://github.com/open-telemetry/opentelemetry-ruby-contrib
42
44
  licenses: