solarwinds_apm 6.1.2 → 7.0.0.prev1

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.
Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +4 -2
  3. data/lib/solarwinds_apm/api/current_trace_info.rb +10 -6
  4. data/lib/solarwinds_apm/api/custom_metrics.rb +8 -25
  5. data/lib/solarwinds_apm/api/tracing.rb +12 -27
  6. data/lib/solarwinds_apm/api/transaction_name.rb +6 -10
  7. data/lib/solarwinds_apm/config.rb +1 -1
  8. data/lib/solarwinds_apm/constants.rb +1 -0
  9. data/lib/solarwinds_apm/noop/api.rb +5 -2
  10. data/lib/solarwinds_apm/noop.rb +0 -24
  11. data/lib/solarwinds_apm/opentelemetry/otlp_processor.rb +90 -69
  12. data/lib/solarwinds_apm/opentelemetry/solarwinds_propagator.rb +0 -2
  13. data/lib/solarwinds_apm/opentelemetry/solarwinds_response_propagator.rb +5 -4
  14. data/lib/solarwinds_apm/opentelemetry.rb +5 -7
  15. data/lib/solarwinds_apm/otel_native_config.rb +177 -0
  16. data/lib/solarwinds_apm/patch/README.md +15 -0
  17. data/lib/solarwinds_apm/{noop/metadata.rb → sampling/dice.rb} +19 -17
  18. data/lib/solarwinds_apm/sampling/http_sampler.rb +87 -0
  19. data/lib/solarwinds_apm/sampling/json_sampler.rb +52 -0
  20. data/lib/solarwinds_apm/sampling/metrics.rb +38 -0
  21. data/lib/solarwinds_apm/sampling/oboe_sampler.rb +348 -0
  22. data/lib/solarwinds_apm/sampling/sampler.rb +197 -0
  23. data/lib/solarwinds_apm/sampling/sampling_constants.rb +127 -0
  24. data/lib/solarwinds_apm/sampling/sampling_patch.rb +49 -0
  25. data/lib/solarwinds_apm/sampling/setting_example.txt +1 -0
  26. data/lib/solarwinds_apm/{noop/context.rb → sampling/settings.rb} +14 -25
  27. data/lib/solarwinds_apm/sampling/token_bucket.rb +126 -0
  28. data/lib/solarwinds_apm/sampling/trace_options.rb +100 -0
  29. data/lib/solarwinds_apm/{patch.rb → sampling.rb} +20 -4
  30. data/lib/solarwinds_apm/{noop/span.rb → support/aws_resource_detector.rb} +5 -18
  31. data/lib/solarwinds_apm/support/logger_formatter.rb +1 -1
  32. data/lib/solarwinds_apm/support/logging_log_event.rb +1 -1
  33. data/lib/solarwinds_apm/support/lumberjack_formatter.rb +1 -1
  34. data/lib/solarwinds_apm/support/otlp_endpoint.rb +99 -0
  35. data/lib/solarwinds_apm/support/resource_detector/aws/beanstalk.rb +51 -0
  36. data/lib/solarwinds_apm/support/resource_detector/aws/ec2.rb +145 -0
  37. data/lib/solarwinds_apm/support/resource_detector/aws/ecs.rb +173 -0
  38. data/lib/solarwinds_apm/support/resource_detector/aws/eks.rb +174 -0
  39. data/lib/solarwinds_apm/support/resource_detector/aws/lambda.rb +66 -0
  40. data/lib/solarwinds_apm/support/resource_detector.rb +192 -0
  41. data/lib/solarwinds_apm/support/service_key_checker.rb +12 -6
  42. data/lib/solarwinds_apm/support/transaction_settings.rb +6 -0
  43. data/lib/solarwinds_apm/support/txn_name_manager.rb +54 -9
  44. data/lib/solarwinds_apm/support/utils.rb +9 -0
  45. data/lib/solarwinds_apm/support.rb +3 -4
  46. data/lib/solarwinds_apm/version.rb +4 -4
  47. data/lib/solarwinds_apm.rb +27 -73
  48. metadata +99 -40
  49. data/ext/oboe_metal/extconf.rb +0 -168
  50. data/ext/oboe_metal/lib/liboboe-1.0-aarch64.so.sha256 +0 -1
  51. data/ext/oboe_metal/lib/liboboe-1.0-alpine-aarch64.so.sha256 +0 -1
  52. data/ext/oboe_metal/lib/liboboe-1.0-alpine-x86_64.so.sha256 +0 -1
  53. data/ext/oboe_metal/lib/liboboe-1.0-lambda-aarch64.so.sha256 +0 -1
  54. data/ext/oboe_metal/lib/liboboe-1.0-lambda-x86_64.so.sha256 +0 -1
  55. data/ext/oboe_metal/lib/liboboe-1.0-x86_64.so.sha256 +0 -1
  56. data/ext/oboe_metal/src/VERSION +0 -1
  57. data/ext/oboe_metal/src/bson/bson.h +0 -220
  58. data/ext/oboe_metal/src/bson/platform_hacks.h +0 -91
  59. data/ext/oboe_metal/src/init_solarwinds_apm.cc +0 -18
  60. data/ext/oboe_metal/src/oboe.h +0 -930
  61. data/ext/oboe_metal/src/oboe_api.cpp +0 -793
  62. data/ext/oboe_metal/src/oboe_api.h +0 -621
  63. data/ext/oboe_metal/src/oboe_debug.h +0 -17
  64. data/ext/oboe_metal/src/oboe_swig_wrap.cc +0 -11045
  65. data/lib/oboe_metal.rb +0 -187
  66. data/lib/solarwinds_apm/cert/star.appoptics.com.issuer.crt +0 -24
  67. data/lib/solarwinds_apm/oboe_init_options.rb +0 -222
  68. data/lib/solarwinds_apm/opentelemetry/solarwinds_exporter.rb +0 -239
  69. data/lib/solarwinds_apm/opentelemetry/solarwinds_processor.rb +0 -174
  70. data/lib/solarwinds_apm/opentelemetry/solarwinds_sampler.rb +0 -333
  71. data/lib/solarwinds_apm/otel_config.rb +0 -174
  72. data/lib/solarwinds_apm/otel_lambda_config.rb +0 -56
  73. data/lib/solarwinds_apm/patch/dummy_patch.rb +0 -12
  74. data/lib/solarwinds_apm/support/oboe_tracing_mode.rb +0 -33
  75. data/lib/solarwinds_apm/support/support_report.rb +0 -99
  76. data/lib/solarwinds_apm/support/transaction_cache.rb +0 -57
  77. data/lib/solarwinds_apm/support/x_trace_options.rb +0 -138
@@ -0,0 +1,145 @@
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
@@ -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..] 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,174 @@
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
@@ -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