solarwinds_apm 7.0.0.prev1 → 7.0.0.prev3

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: 2d66514bebfccf2f3c5a2b2e6924fe7fc7abe6a33fa90ce2dcc94de968e64018
4
- data.tar.gz: 55cd6df597c8db22130b6322ede1deae4911c1409674734cb11ccf9223734dcb
3
+ metadata.gz: 434e6f5c7d08a287fe5f6160eff2e5c901603b25f9aa31b8e247aa2c4ac28d5f
4
+ data.tar.gz: a4036a5696705e6cd1ea0ff0905ad93120ab95ff40d3b487ea286c42070ce191
5
5
  SHA512:
6
- metadata.gz: fa5dd5bb46399211d816833f257b69fec64a325cb6ee3c256d612d64be0e4cb50e5b1e195025afad69dee4d5c79726d9e5b6ff8f5a25d952724b01521e8d9331
7
- data.tar.gz: 6b9e3f5bc1e8e35b419d9a2c04c43bca23268ea9fed3832368d5f0c708925abd1e46d9f2e215833fae5e6154fa77c7ec696e3819a0ff37188c1c407aa4965522
6
+ metadata.gz: e1ab9ebd49e435da6e8cf90c255f53ea9d6e4a4f6308f39548ab3a3b4acec2462dc6679cb2cfbfaa137c4dc24f10e59e094b2484711cb3ff473435def6dc7759
7
+ data.tar.gz: cf251914d951aff610875e60edb959ebe6cf8a3a88ced6111e28d67bb99a59413bbb0a80234a9d87ad719101ebffd9484af1643d224103e7032817bd31a880bd
data/README.md CHANGED
@@ -158,7 +158,7 @@ By default, transaction names are constructed based on attributes such as the re
158
158
  result = SolarWindsAPM::API.set_transaction_name('my-custom-trace-name')
159
159
  ```
160
160
 
161
- #### Send Custom Metrics
161
+ #### Send Custom Metrics (Depreciated)
162
162
 
163
163
  Service metrics are automatically collected by this library. In addition, the following methods support sending two types of custom metrics:
164
164
 
@@ -16,8 +16,7 @@
16
16
 
17
17
  if defined?(SolarWindsAPM::Config)
18
18
 
19
- # :service_key, :hostname_alias, :http_proxy, and :debug_level
20
- # are startup settings and can't be changed afterwards.
19
+ # :service_key and :debug_level are startup settings and can't be changed afterwards.
21
20
 
22
21
  #
23
22
  # Set SW_APM_SERVICE_KEY
@@ -29,34 +28,6 @@ if defined?(SolarWindsAPM::Config)
29
28
  #
30
29
  # SolarWindsAPM::Config[:service_key] = '0123456789abcde0123456789abcde0123456789abcde0123456789abcde1234:my_service'
31
30
 
32
- #
33
- # Set SW_APM_HOSTNAME_ALIAS
34
- # This setting will be overridden if SW_APM_HOSTNAME_ALIAS is set as an environment variable
35
- #
36
- # SolarWindsAPM::Config[:hostname_alias] = 'alias_name'
37
-
38
- #
39
- # Set Proxy for SolarWinds
40
- # This setting will be overridden if SW_APM_PROXY is set as an environment variable.
41
- #
42
- # Please configure http_proxy if a proxy needs to be used to communicate with
43
- # the SolarWinds APM collector.
44
- # The format should either be http://<proxyHost>:<proxyPort> for a proxy
45
- # server that does not require authentication, or
46
- # http://<username>:<password>@<proxyHost>:<proxyPort> for a proxy server that
47
- # requires basic authentication.
48
- #
49
- # Note that while HTTP is the only type of connection supported, the traffic
50
- # to SolarWinds is still encrypted using SSL/TLS.
51
- #
52
- # It is recommended to configure the proxy in this file or as SW_APM_PROXY
53
- # environment variable. However, the agent's underlying network library will
54
- # use a system-wide proxy defined in the environment variables grpc_proxy,
55
- # https_proxy or http_proxy if no SolarWindsAPM-specific configuration is set.
56
- # Please refer to gRPC environment variables for more information.
57
- #
58
- # SolarWindsAPM::Config[:http_proxy] = http://<proxyHost>:<proxyPort>
59
-
60
31
  #
61
32
  # Set SW_APM_DEBUG_LEVEL
62
33
  # This setting will be overridden if SW_APM_DEBUG_LEVEL is set as an environment variable.
@@ -226,6 +226,12 @@ module SolarWindsAPM
226
226
  when :tag_sql
227
227
  enable_disable_config('SW_APM_TAG_SQL', key, value, false, bool: true)
228
228
 
229
+ when :http_proxy
230
+ SolarWindsAPM.logger.warn { ':http_proxy is deprecated' }
231
+
232
+ when :hostname_alias
233
+ SolarWindsAPM.logger.warn { ':hostname_alias is deprecated' }
234
+
229
235
  else
230
236
  @@config[key.to_sym] = value
231
237
 
@@ -26,7 +26,7 @@ module SolarWindsAPM
26
26
  def initialize(txn_manager)
27
27
  @txn_manager = txn_manager
28
28
  @meters = { 'sw.apm.request.metrics' => ::OpenTelemetry.meter_provider.meter('sw.apm.request.metrics') }
29
- @metrics = { response_time: @meters['sw.apm.request.metrics'].create_histogram('trace.service.response_time', unit: 'ms', description: 'Duration of each entry span for the service, typically meaning the time taken to process an inbound request.') }
29
+ @metrics = init_response_time_metrics
30
30
  @transaction_name = nil
31
31
  end
32
32
 
@@ -45,6 +45,8 @@ module SolarWindsAPM
45
45
  end
46
46
 
47
47
  def on_finishing(span)
48
+ return if non_entry_span(span: span)
49
+
48
50
  @transaction_name = calculate_transaction_names(span)
49
51
  span.set_attribute(SW_TRANSACTION_NAME, @transaction_name)
50
52
  @txn_manager.delete_root_context_h(span.context.hex_trace_id)
@@ -67,8 +69,35 @@ module SolarWindsAPM
67
69
  SolarWindsAPM.logger.info { "[#{self.class}/#{__method__}] error processing span on_finish: #{e.message}" }
68
70
  end
69
71
 
72
+ # @param [optional Numeric] timeout An optional timeout in seconds.
73
+ # @return [Integer] Export::SUCCESS if no error occurred, Export::FAILURE if
74
+ # a non-specific failure occurred, Export::TIMEOUT if a timeout occurred.
75
+ def force_flush(timeout: nil) # rubocop:disable Lint/UnusedMethodArgument
76
+ ::OpenTelemetry::SDK::Trace::Export::SUCCESS
77
+ end
78
+
79
+ # @param [optional Numeric] timeout An optional timeout in seconds.
80
+ # @return [Integer] Export::SUCCESS if no error occurred, Export::FAILURE if
81
+ # a non-specific failure occurred, Export::TIMEOUT if a timeout occurred.
82
+ def shutdown(timeout: nil) # rubocop:disable Lint/UnusedMethodArgument
83
+ ::OpenTelemetry::SDK::Trace::Export::SUCCESS
84
+ end
85
+
70
86
  private
71
87
 
88
+ def init_response_time_metrics
89
+ # add the ExponentialBucketHistogram view
90
+ if defined? ::OpenTelemetry::Exporter::OTLP::Metrics && Gem::Version.new(::OpenTelemetry::Exporter::OTLP::Metrics::VERSION) >= Gem::Version.new('0.5.0')
91
+ ::OpenTelemetry.meter_provider.add_view('trace.service.response_time',
92
+ aggregation: ::OpenTelemetry::SDK::Metrics::Aggregation::ExponentialBucketHistogram.new(aggregation_temporality: :delta),
93
+ type: :histogram,
94
+ unit: 'ms')
95
+ end
96
+
97
+ instrument = @meters['sw.apm.request.metrics'].create_histogram('trace.service.response_time', unit: 'ms', description: 'Duration of each entry span for the service, typically meaning the time taken to process an inbound request.')
98
+ { response_time: instrument }
99
+ end
100
+
72
101
  def meter_attributes(span)
73
102
  meter_attrs = {
74
103
  SW_IS_ERROR => error?(span) == 1,
@@ -62,6 +62,9 @@ module SolarWindsAPM
62
62
  ENV["OTEL_EXPORTER_OTLP_#{signal}_COMPRESSION"] = 'gzip' if ENV["OTEL_EXPORTER_OTLP_#{signal}_COMPRESSION"].to_s.empty? && ENV['OTEL_EXPORTER_OTLP_COMPRESSION'].to_s.empty?
63
63
  end
64
64
 
65
+ # set http stable semconv
66
+ ENV['OTEL_SEMCONV_STABILITY_OPT_IN'] = 'http' if ENV['OTEL_SEMCONV_STABILITY_OPT_IN'].to_s.empty?
67
+
65
68
  # set delta temporality
66
69
  ENV['OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE'] = 'delta' if ENV['OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE'].to_s.empty?
67
70
 
@@ -81,6 +81,7 @@ module SolarWindsAPM
81
81
  end
82
82
  rescue StandardError => e
83
83
  @logger.warn { "Failed to retrieve sampling settings (#{e.message}), tracing will be disabled until valid ones are available." }
84
+ sleep(GET_SETTING_DURAION)
84
85
  end
85
86
  end
86
87
  end
@@ -337,7 +337,7 @@ module SolarWindsAPM
337
337
  time_now = Time.now.to_i * 1000
338
338
  if time_now > expiry
339
339
  @logger.debug { 'settings expired, removing' }
340
- @settings = nil
340
+ @settings = {}
341
341
  return
342
342
  end
343
343
  sampling_setting = SolarWindsAPM::SamplingSettings.merge(@settings, local_settings(params))
@@ -13,7 +13,7 @@ require 'socket'
13
13
  require 'securerandom'
14
14
  require 'opentelemetry/resource/detector/azure'
15
15
  require 'opentelemetry/resource/detector/container'
16
- require_relative 'resource_detector/aws/ec2'
16
+ require 'opentelemetry/resource/detector/aws/ec2'
17
17
 
18
18
  module SolarWindsAPM
19
19
  # ResourceDetector
@@ -16,4 +16,3 @@ require_relative 'support/txn_name_manager'
16
16
  require_relative 'support/utils'
17
17
  require_relative 'support/otlp_endpoint'
18
18
  require_relative 'support/resource_detector'
19
- require_relative 'support/aws_resource_detector'
@@ -14,7 +14,7 @@ module SolarWindsAPM
14
14
  MAJOR = 7 # breaking,
15
15
  MINOR = 0 # feature,
16
16
  PATCH = 0 # fix => BFF
17
- PRE = 'prev1'
17
+ PRE = 'prev3'
18
18
 
19
19
  STRING = [MAJOR, MINOR, PATCH, PRE].compact.join('.')
20
20
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: solarwinds_apm
3
3
  version: !ruby/object:Gem::Version
4
- version: 7.0.0.prev1
4
+ version: 7.0.0.prev3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Maia Engeli
@@ -11,7 +11,7 @@ authors:
11
11
  autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
- date: 2025-06-10 00:00:00.000000000 Z
14
+ date: 2025-07-07 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: opentelemetry-exporter-otlp
@@ -70,7 +70,7 @@ dependencies:
70
70
  - !ruby/object:Gem::Version
71
71
  version: 0.2.0
72
72
  - !ruby/object:Gem::Dependency
73
- name: opentelemetry-resource-detector-azure
73
+ name: opentelemetry-resource-detector-aws
74
74
  requirement: !ruby/object:Gem::Requirement
75
75
  requirements:
76
76
  - - ">="
@@ -83,20 +83,34 @@ dependencies:
83
83
  - - ">="
84
84
  - !ruby/object:Gem::Version
85
85
  version: 0.1.0
86
+ - !ruby/object:Gem::Dependency
87
+ name: opentelemetry-resource-detector-azure
88
+ requirement: !ruby/object:Gem::Requirement
89
+ requirements:
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ version: 0.2.0
93
+ type: :runtime
94
+ prerelease: false
95
+ version_requirements: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ version: 0.2.0
86
100
  - !ruby/object:Gem::Dependency
87
101
  name: opentelemetry-resource-detector-container
88
102
  requirement: !ruby/object:Gem::Requirement
89
103
  requirements:
90
104
  - - ">="
91
105
  - !ruby/object:Gem::Version
92
- version: 0.1.0
106
+ version: 0.2.0
93
107
  type: :runtime
94
108
  prerelease: false
95
109
  version_requirements: !ruby/object:Gem::Requirement
96
110
  requirements:
97
111
  - - ">="
98
112
  - !ruby/object:Gem::Version
99
- version: 0.1.0
113
+ version: 0.2.0
100
114
  - !ruby/object:Gem::Dependency
101
115
  name: opentelemetry-sdk
102
116
  requirement: !ruby/object:Gem::Requirement
@@ -161,17 +175,11 @@ files:
161
175
  - lib/solarwinds_apm/sampling/token_bucket.rb
162
176
  - lib/solarwinds_apm/sampling/trace_options.rb
163
177
  - lib/solarwinds_apm/support.rb
164
- - lib/solarwinds_apm/support/aws_resource_detector.rb
165
178
  - lib/solarwinds_apm/support/logger_formatter.rb
166
179
  - lib/solarwinds_apm/support/logging_log_event.rb
167
180
  - lib/solarwinds_apm/support/lumberjack_formatter.rb
168
181
  - lib/solarwinds_apm/support/otlp_endpoint.rb
169
182
  - lib/solarwinds_apm/support/resource_detector.rb
170
- - lib/solarwinds_apm/support/resource_detector/aws/beanstalk.rb
171
- - lib/solarwinds_apm/support/resource_detector/aws/ec2.rb
172
- - lib/solarwinds_apm/support/resource_detector/aws/ecs.rb
173
- - lib/solarwinds_apm/support/resource_detector/aws/eks.rb
174
- - lib/solarwinds_apm/support/resource_detector/aws/lambda.rb
175
183
  - lib/solarwinds_apm/support/service_key_checker.rb
176
184
  - lib/solarwinds_apm/support/transaction_settings.rb
177
185
  - lib/solarwinds_apm/support/txn_name_manager.rb
@@ -194,7 +202,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
194
202
  requirements:
195
203
  - - ">="
196
204
  - !ruby/object:Gem::Version
197
- version: 3.0.0
205
+ version: 3.1.0
198
206
  required_rubygems_version: !ruby/object:Gem::Requirement
199
207
  requirements:
200
208
  - - ">"
@@ -1,12 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # © 2025 SolarWinds Worldwide, LLC. All rights reserved.
4
- #
5
- # Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at:http://www.apache.org/licenses/LICENSE-2.0
6
- #
7
- # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
8
-
9
- require_relative 'resource_detector/aws/ecs'
10
- require_relative 'resource_detector/aws/eks'
11
- require_relative 'resource_detector/aws/lambda'
12
- require_relative 'resource_detector/aws/beanstalk'
@@ -1,51 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # © 2023 SolarWinds Worldwide, LLC. All rights reserved.
4
- #
5
- # Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at:http://www.apache.org/licenses/LICENSE-2.0
6
- #
7
- # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
8
-
9
- require 'net/http'
10
- require 'uri'
11
- require 'json'
12
- require 'socket'
13
-
14
- module SolarWindsAPM
15
- module ResourceDetector
16
- # Beanstalk
17
- module Beanstalk
18
- module_function
19
-
20
- DEFAULT_BEANSTALK_CONF_PATH = '/var/elasticbeanstalk/xray/environment.conf'
21
- WIN_OS_BEANSTALK_CONF_PATH = 'C:\\Program Files\\Amazon\\XRay\\environment.conf'
22
-
23
- def detect
24
- beanstalk_config_path = if RUBY_PLATFORM.include?('mingw32') || RUBY_PLATFORM.include?('mswin')
25
- WIN_OS_BEANSTALK_CONF_PATH
26
- else
27
- DEFAULT_BEANSTALK_CONF_PATH
28
- end
29
-
30
- attribute = gather_data(beanstalk_config_path)
31
- ::OpenTelemetry::SDK::Resources::Resource.create(attribute)
32
- end
33
-
34
- def gather_data(config_path)
35
- raw_data = File.read(config_path, encoding: 'utf-8')
36
- parsed_data = JSON.parse(raw_data)
37
- {
38
- ::OpenTelemetry::SemanticConventions::Resource::CLOUD_PROVIDER => 'aws',
39
- ::OpenTelemetry::SemanticConventions::Resource::CLOUD_PLATFORM => 'aws_elastic_beanstalk',
40
- ::OpenTelemetry::SemanticConventions::Resource::SERVICE_NAME => 'aws_elastic_beanstalk',
41
- ::OpenTelemetry::SemanticConventions::Resource::SERVICE_NAMESPACE => parsed_data['environment_name'],
42
- ::OpenTelemetry::SemanticConventions::Resource::SERVICE_VERSION => parsed_data['version_label'],
43
- ::OpenTelemetry::SemanticConventions::Resource::SERVICE_INSTANCE_ID => parsed_data['deployment_id']
44
- }
45
- rescue StandardError => e
46
- SolarWindsAPM.logger.debug { "Gather data for AWS Elastic Beanstalk resource detector failed: #{e.message}" }
47
- {}
48
- end
49
- end
50
- end
51
- end
@@ -1,145 +0,0 @@
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 'opentelemetry/common'
10
- require 'opentelemetry/semantic_conventions/resource'
11
-
12
- module OpenTelemetry
13
- module Resource
14
- module Detector
15
- module AWS
16
- # EC2 contains detect class method for determining EC2 resource attributes
17
- module EC2
18
- extend self
19
-
20
- # EC2 metadata service endpoints and constants
21
- EC2_METADATA_HOST = '169.254.169.254'
22
- TOKEN_ENDPOINT = '/latest/api/token'
23
- IDENTITY_DOCUMENT_ENDPOINT = '/latest/dynamic/instance-identity/document'
24
- HOSTNAME_ENDPOINT = '/latest/meta-data/hostname'
25
-
26
- TOKEN_HEADER = 'X-aws-ec2-metadata-token'
27
- TOKEN_TTL_HEADER = 'X-aws-ec2-metadata-token-ttl-seconds'
28
- TOKEN_TTL_VALUE = '60'
29
-
30
- # Timeout in seconds for HTTP requests
31
- HTTP_TIMEOUT = 1
32
-
33
- # Create a constant for resource semantic conventions
34
- RESOURCE = ::OpenTelemetry::SemanticConventions::Resource
35
-
36
- def detect
37
- # Implementation for EC2 detection supporting both IMDSv1 and IMDSv2
38
- resource_attributes = {}
39
-
40
- begin
41
- # Attempt to get IMDSv2 token - this will fail if IMDSv2 is not supported
42
- # but we'll still try IMDSv1 in that case
43
- token = fetch_token
44
-
45
- # Get instance identity document which contains most metadata
46
- # Will try with token (IMDSv2) or without token (IMDSv1)
47
- identity = fetch_identity_document(token) || {}
48
- return ::OpenTelemetry::SDK::Resources::Resource.create({}) if identity.empty?
49
-
50
- hostname = fetch_hostname(token)
51
-
52
- # Set resource attributes from the identity document
53
- resource_attributes[RESOURCE::CLOUD_PROVIDER] = 'aws'
54
- resource_attributes[RESOURCE::CLOUD_PLATFORM] = 'aws_ec2'
55
- resource_attributes[RESOURCE::CLOUD_ACCOUNT_ID] = identity['accountId']
56
- resource_attributes[RESOURCE::CLOUD_REGION] = identity['region']
57
- resource_attributes[RESOURCE::CLOUD_AVAILABILITY_ZONE] = identity['availabilityZone']
58
-
59
- resource_attributes[RESOURCE::HOST_ID] = identity['instanceId']
60
- resource_attributes[RESOURCE::HOST_TYPE] = identity['instanceType']
61
- resource_attributes[RESOURCE::HOST_NAME] = hostname
62
- rescue StandardError => e
63
- ::OpenTelemetry.handle_error(exception: e, message: 'EC2 resource detection failed')
64
- return ::OpenTelemetry::SDK::Resources::Resource.create({})
65
- end
66
-
67
- # Filter out nil or empty values
68
- resource_attributes.delete_if { |_key, value| value.nil? || value.empty? }
69
- ::OpenTelemetry::SDK::Resources::Resource.create(resource_attributes)
70
- end
71
-
72
- private
73
-
74
- # Fetches an IMDSv2 token from the EC2 metadata service
75
- #
76
- # @return [String, nil] The token or nil if the request failed
77
- def fetch_token
78
- uri = URI.parse("http://#{EC2_METADATA_HOST}#{TOKEN_ENDPOINT}")
79
- request = Net::HTTP::Put.new(uri)
80
- request[TOKEN_TTL_HEADER] = TOKEN_TTL_VALUE
81
-
82
- response = make_request(uri, request)
83
- return nil unless response.is_a?(Net::HTTPSuccess)
84
-
85
- response.body
86
- end
87
-
88
- # Fetches the instance identity document which contains EC2 instance metadata
89
- #
90
- # @param token [String, nil] IMDSv2 token (optional for IMDSv1)
91
- # @return [Hash, nil] Parsed identity document or nil if the request failed
92
- def fetch_identity_document(token)
93
- uri = URI.parse("http://#{EC2_METADATA_HOST}#{IDENTITY_DOCUMENT_ENDPOINT}")
94
- request = Net::HTTP::Get.new(uri)
95
- request[TOKEN_HEADER] = token if token
96
-
97
- response = make_request(uri, request)
98
- return nil unless response.is_a?(Net::HTTPSuccess)
99
-
100
- begin
101
- JSON.parse(response.body)
102
- rescue JSON::ParserError
103
- nil
104
- end
105
- end
106
-
107
- # Fetches the EC2 instance hostname
108
- #
109
- # @param token [String, nil] IMDSv2 token (optional for IMDSv1)
110
- # @return [String, nil] The hostname or nil if the request failed
111
- def fetch_hostname(token)
112
- uri = URI.parse("http://#{EC2_METADATA_HOST}#{HOSTNAME_ENDPOINT}")
113
- request = Net::HTTP::Get.new(uri)
114
- request[TOKEN_HEADER] = token if token
115
-
116
- response = make_request(uri, request)
117
- return nil unless response.is_a?(Net::HTTPSuccess)
118
-
119
- response.body
120
- end
121
-
122
- # Makes an HTTP request with timeout handling
123
- #
124
- # @param uri [URI] The request URI
125
- # @param request [Net::HTTP::Request] The request to perform
126
- # @return [Net::HTTPResponse, nil] The response or nil if the request failed
127
- def make_request(uri, request)
128
- http = Net::HTTP.new(uri.host, uri.port)
129
- http.open_timeout = HTTP_TIMEOUT
130
- http.read_timeout = HTTP_TIMEOUT
131
-
132
- begin
133
- ::OpenTelemetry::Common::Utilities.untraced do
134
- http.request(request)
135
- end
136
- rescue StandardError
137
- ::OpenTelemetry.logger.debug { 'EC2 metadata service request failed' }
138
- nil
139
- end
140
- end
141
- end
142
- end
143
- end
144
- end
145
- end
@@ -1,173 +0,0 @@
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..] 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
@@ -1,174 +0,0 @@
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 'openssl'
10
- require 'uri'
11
- require 'opentelemetry/common'
12
- require 'opentelemetry/semantic_conventions/resource'
13
-
14
- module OpenTelemetry
15
- module Resource
16
- module Detector
17
- module AWS
18
- # EKS contains detect class method for determining EKS resource attributes
19
- module EKS
20
- extend self
21
-
22
- # Container ID length from cgroup file
23
- CONTAINER_ID_LENGTH = 64
24
-
25
- # HTTP request timeout in seconds
26
- HTTP_TIMEOUT = 5
27
-
28
- # Kubernetes token and certificate paths
29
- TOKEN_PATH = '/var/run/secrets/kubernetes.io/serviceaccount/token'
30
- CERT_PATH = '/var/run/secrets/kubernetes.io/serviceaccount/ca.crt'
31
-
32
- # Kubernetes API paths
33
- AWS_AUTH_PATH = '/api/v1/namespaces/kube-system/configmaps/aws-auth'
34
- CLUSTER_INFO_PATH = '/api/v1/namespaces/amazon-cloudwatch/configmaps/cluster-info'
35
-
36
- # Create a constant for resource semantic conventions
37
- RESOURCE = ::OpenTelemetry::SemanticConventions::Resource
38
-
39
- def detect
40
- # Return empty resource if not running on K8s
41
- return ::OpenTelemetry::SDK::Resources::Resource.create({}) unless k8s?
42
-
43
- resource_attributes = {}
44
-
45
- begin
46
- # Get K8s credentials
47
- cred_value = k8s_cred_value
48
-
49
- # Verify this is an EKS cluster
50
- unless eks?(cred_value)
51
- ::OpenTelemetry.logger.debug('Could not confirm process is running on EKS')
52
- return ::OpenTelemetry::SDK::Resources::Resource.create({})
53
- end
54
-
55
- # Get cluster name and container ID
56
- cluster_name_val = cluster_name(cred_value)
57
- container_id_val = container_id
58
-
59
- if container_id_val.empty? && cluster_name_val.empty?
60
- ::OpenTelemetry.logger.debug('Neither cluster name nor container ID found on EKS process')
61
- return ::OpenTelemetry::SDK::Resources::Resource.create({})
62
- end
63
-
64
- # Set resource attributes
65
- resource_attributes[RESOURCE::CLOUD_PROVIDER] = 'aws'
66
- resource_attributes[RESOURCE::CLOUD_PLATFORM] = 'aws_eks'
67
- resource_attributes[RESOURCE::K8S_CLUSTER_NAME] = cluster_name_val unless cluster_name_val.empty?
68
- resource_attributes[RESOURCE::CONTAINER_ID] = container_id_val unless container_id_val.empty?
69
- rescue StandardError => e
70
- ::OpenTelemetry.logger.debug("EKS resource detection failed: #{e.message}")
71
- return ::OpenTelemetry::SDK::Resources::Resource.create({})
72
- end
73
-
74
- resource_attributes.delete_if { |_key, value| value.nil? || value.empty? }
75
- ::OpenTelemetry::SDK::Resources::Resource.create(resource_attributes)
76
- end
77
-
78
- private
79
-
80
- # Check if running on K8s
81
- #
82
- # @return [Boolean] true if running on K8s
83
- def k8s?
84
- File.exist?(TOKEN_PATH) && File.exist?(CERT_PATH)
85
- end
86
-
87
- # Get K8s token
88
- #
89
- # @return [String] K8s token
90
- # @raise [StandardError] if token could not be read
91
- def k8s_cred_value
92
- token = File.read(TOKEN_PATH).strip
93
- "Bearer #{token}"
94
- rescue StandardError => e
95
- ::OpenTelemetry.logger.debug("Failed to get k8s token: #{e.message}")
96
- raise e
97
- end
98
-
99
- # Check if running on EKS
100
- #
101
- # @param cred_value [String] K8s credentials
102
- # @return [Boolean] true if running on EKS
103
- def eks?(cred_value)
104
- # Just try to to access the aws-auth configmap
105
- # If it exists and we can access it, we're on EKS
106
- aws_http_request(AWS_AUTH_PATH, cred_value)
107
- true
108
- rescue StandardError
109
- false
110
- end
111
-
112
- # Get EKS cluster name
113
- #
114
- # @param cred_value [String] K8s credentials
115
- # @return [String] Cluster name or empty string if not found
116
- def cluster_name(cred_value)
117
- begin
118
- response = aws_http_request(CLUSTER_INFO_PATH, cred_value)
119
- cluster_info = JSON.parse(response)
120
- return cluster_info['data']['cluster.name'] if cluster_info['data'] && cluster_info['data']['cluster.name']
121
- rescue StandardError => e
122
- ::OpenTelemetry.logger.debug("Cannot get cluster name on EKS: #{e.message}")
123
- end
124
- ''
125
- end
126
-
127
- # Get container ID from cgroup file
128
- #
129
- # @return [String] Container ID or empty string if not found
130
- def container_id
131
- begin
132
- File.open('/proc/self/cgroup', 'r') do |file|
133
- file.each_line do |line|
134
- line = line.strip
135
- # Look for container ID (64 chars) at the end of the line
136
- return line[-CONTAINER_ID_LENGTH..] if line.length > CONTAINER_ID_LENGTH
137
- end
138
- end
139
- rescue StandardError => e
140
- ::OpenTelemetry.logger.debug("Failed to get container ID on EKS: #{e.message}")
141
- end
142
- ''
143
- end
144
-
145
- # Make HTTP GET request to K8s API
146
- #
147
- # @param path [String] API path
148
- # @param cred_value [String] Authorization header value
149
- # @return [String] Response body
150
- # @raise [StandardError] if request fails
151
- def aws_http_request(path, cred_value)
152
- uri = URI.parse("https://kubernetes.default.svc#{path}")
153
- http = Net::HTTP.new(uri.host, uri.port)
154
- http.use_ssl = true
155
- http.verify_mode = OpenSSL::SSL::VERIFY_PEER
156
- http.ca_file = CERT_PATH
157
- http.open_timeout = HTTP_TIMEOUT
158
- http.read_timeout = HTTP_TIMEOUT
159
-
160
- request = Net::HTTP::Get.new(uri)
161
- request['Authorization'] = cred_value
162
-
163
- ::OpenTelemetry::Common::Utilities.untraced do
164
- response = http.request(request)
165
- raise "HTTP request failed with status #{response.code}" unless response.is_a?(Net::HTTPSuccess)
166
-
167
- response.body
168
- end
169
- end
170
- end
171
- end
172
- end
173
- end
174
- end
@@ -1,66 +0,0 @@
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