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,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolarWindsAPM
4
+ module MetricsExporter
5
+ module Patch
6
+ # do not send metrics if no data_points present
7
+ def export(metrics, timeout: nil)
8
+ metrics.reject! { |m| m.data_points.empty? }
9
+ return ::OpenTelemetry::SDK::Metrics::Export::SUCCESS unless metrics.any? { |m| m.data_points.any? }
10
+
11
+ super
12
+ end
13
+ end
14
+ end
15
+ end
16
+
17
+ module SolarWindsAPM
18
+ module Span
19
+ module Patch
20
+ def finish(end_timestamp: nil)
21
+ @mutex.synchronize do
22
+ if @ended
23
+ ::OpenTelemetry.logger.warn('Calling finish on an ended Span.')
24
+ return self
25
+ end
26
+ end
27
+
28
+ @span_processors.each do |processor|
29
+ processor.on_finishing(self) if processor.respond_to?(:on_finishing)
30
+ end
31
+
32
+ @mutex.synchronize do
33
+ @end_timestamp = relative_timestamp(end_timestamp)
34
+ @attributes = validated_attributes(@attributes).freeze
35
+ @events.freeze
36
+ @links.freeze
37
+ @ended = true
38
+ end
39
+ @span_processors.each { |processor| processor.on_finish(self) }
40
+ self
41
+ end
42
+ end
43
+ end
44
+ end
45
+
46
+ OpenTelemetry::Exporter::OTLP::Metrics::MetricsExporter.prepend(SolarWindsAPM::MetricsExporter::Patch)
47
+
48
+ # issue: https://github.com/open-telemetry/opentelemetry-ruby/issues/1824
49
+ OpenTelemetry::SDK::Trace::Span.prepend(SolarWindsAPM::Span::Patch)
@@ -0,0 +1 @@
1
+ {"value"=>1000000, "flags"=>"SAMPLE_START,SAMPLE_THROUGH_ALWAYS,SAMPLE_BUCKET_ENABLED,TRIGGER_TRACE", "timestamp"=>1738523568, "ttl"=>120, "arguments"=>{"BucketCapacity"=>2, "BucketRate"=>1, "TriggerRelaxedBucketCapacity"=>20, "TriggerRelaxedBucketRate"=>1, "TriggerStrictBucketCapacity"=>6, "TriggerStrictBucketRate"=>0.1, "SignatureKey"=>"0sftj1EYX7JJp01DblJwkccYCIZ91fbU"}}
@@ -6,34 +6,23 @@
6
6
  #
7
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
8
 
9
- ####
10
- # noop version of SolarWindsAPM::Context
11
- #
12
- # module SolarWindsAPM
13
- # end
9
+ module SolarWindsAPM
10
+ module SamplingSettings
11
+ def self.merge(remote, local)
12
+ flags = local[:tracing_mode] || remote[:flags]
14
13
 
15
- module Oboe_metal # rubocop:disable Naming/ClassAndModuleCamelCase
16
- # Context for noop
17
- class Context
18
- ##
19
- # noop version of :toString
20
- # toString would return the current trace context as string
21
- #
22
- def self.toString
23
- '99-00000000000000000000000000000000-0000000000000000-00'
24
- end
14
+ if local[:trigger_mode] == :enabled
15
+ flags |= SolarWindsAPM::Flags::TRIGGERED_TRACE
16
+ elsif local[:trigger_mode] == :disabled
17
+ flags &= ~ SolarWindsAPM::Flags::TRIGGERED_TRACE
18
+ end
25
19
 
26
- def self.isReady(*)
27
- false
28
- end
20
+ if remote[:flags].anybits?(SolarWindsAPM::Flags::OVERRIDE)
21
+ flags &= remote[:flags]
22
+ flags |= SolarWindsAPM::Flags::OVERRIDE
23
+ end
29
24
 
30
- def self.getDecisions(*)
31
- [-1, -1, -1, 0, 0.0, 0.0, -1, -1, '', '', 4]
25
+ remote.dup.tap { |merged| merged[:flags] = flags }
32
26
  end
33
-
34
- ##
35
- # noop version of :clear
36
- #
37
- def self.clear; end
38
27
  end
39
28
  end
@@ -0,0 +1,126 @@
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
+ # Bucket is used to consume that determine if capacity is enough
10
+ # capacity is updated through update_settings
11
+ module SolarWindsAPM
12
+ class TokenBucket
13
+ # Maximum value of a signed 32-bit integer
14
+ MAX_INTERVAL = (2**31) - 1
15
+
16
+ attr_reader :capacity, :rate, :interval, :tokens
17
+
18
+ def initialize(token_bucket_settings)
19
+ self.capacity = token_bucket_settings.capacity || 0
20
+ self.rate = token_bucket_settings.rate || 0
21
+ self.interval = token_bucket_settings.interval || MAX_INTERVAL
22
+ self.tokens = @capacity
23
+ @timer = nil
24
+ end
25
+
26
+ # used call from update_settings e.g. bucket.update(bucket_settings)
27
+ def update(settings)
28
+ settings.instance_of?(Hash) ? update_from_hash(settings) : update_from_token_bucket_settings(settings)
29
+ end
30
+
31
+ def update_from_hash(settings)
32
+ if settings[:capacity]
33
+ difference = settings[:capacity] - @capacity
34
+ self.capacity = settings[:capacity]
35
+ self.tokens = @tokens + difference
36
+ end
37
+
38
+ self.rate = settings[:rate] if settings[:rate]
39
+
40
+ return unless settings[:interval]
41
+
42
+ self.interval = settings[:interval]
43
+ return unless running
44
+
45
+ stop
46
+ start
47
+ end
48
+
49
+ def update_from_token_bucket_settings(settings)
50
+ if settings.capacity
51
+ difference = settings.capacity - @capacity
52
+ self.capacity = settings.capacity
53
+ self.tokens = @tokens + difference
54
+ end
55
+
56
+ self.rate = settings.rate if settings.rate
57
+
58
+ return unless settings.interval
59
+
60
+ self.interval = settings.interval
61
+ return unless running
62
+
63
+ stop
64
+ start
65
+ end
66
+
67
+ def capacity=(capacity)
68
+ @capacity = [0, capacity].max
69
+ end
70
+
71
+ def rate=(rate)
72
+ @rate = [0, rate].max
73
+ end
74
+
75
+ def interval=(interval)
76
+ @interval = interval.clamp(0, MAX_INTERVAL)
77
+ end
78
+
79
+ def tokens=(tokens)
80
+ @tokens = tokens.clamp(0, @capacity)
81
+ end
82
+
83
+ # Attempts to consume tokens from the bucket
84
+ # @param n [Integer] Number of tokens to consume
85
+ # @return [Boolean] Whether there were enough tokens
86
+ def consume(token = 1)
87
+ if @tokens >= token
88
+ self.tokens = @tokens - token
89
+ true
90
+ else
91
+ false
92
+ end
93
+ end
94
+
95
+ # Starts replenishing the bucket
96
+ def start
97
+ return if running
98
+
99
+ @timer = Thread.new do
100
+ loop do
101
+ task
102
+ sleep(@interval / 1000.0)
103
+ end
104
+ end
105
+ end
106
+
107
+ # Stops replenishing the bucket
108
+ def stop
109
+ return unless running
110
+
111
+ @timer.kill
112
+ @timer = nil
113
+ end
114
+
115
+ # Whether the bucket is actively being replenished
116
+ def running
117
+ !@timer.nil?
118
+ end
119
+
120
+ private
121
+
122
+ def task
123
+ self.tokens = tokens + @rate
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,100 @@
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
+ module SolarWindsAPM
10
+ class TraceOptions
11
+ TRIGGER_TRACE_KEY = 'trigger-trace'
12
+ TIMESTAMP_KEY = 'ts'
13
+ SW_KEYS_KEY = 'sw-keys'
14
+
15
+ CUSTOM_KEY_REGEX = /^custom-[^\s]*$/
16
+
17
+ def self.parse_trace_options(header, logger)
18
+ trace_options = TriggerTraceOptions.new(nil, nil, nil, {}, [], TraceOptionsResponse.new(nil, nil, []))
19
+
20
+ kvs = header.split(';').map do |kv|
21
+ key, *values = kv.split('=').map(&:strip)
22
+ value = values.any? ? values.join('=') : nil
23
+ [key, value]
24
+ end
25
+
26
+ kvs.reject! { |key, _| key.nil? || key.empty? }
27
+
28
+ kvs.each do |k, v|
29
+ case k
30
+ when TRIGGER_TRACE_KEY
31
+ if v || trace_options.trigger_trace
32
+ logger.debug { 'invalid trace option for trigger trace' }
33
+ trace_options.ignored << [k, v]
34
+ next
35
+ end
36
+ trace_options.trigger_trace = true
37
+ when TIMESTAMP_KEY
38
+ if v.nil? || trace_options.timestamp
39
+ logger.debug { 'invalid trace option for timestamp' }
40
+ trace_options.ignored << [k, v]
41
+ next
42
+ end
43
+
44
+ unless numeric_integer?(v)
45
+ logger.debug { 'invalid trace option for timestamp, should be an integer' }
46
+ trace_options.ignored << [k, v]
47
+ next
48
+ end
49
+ trace_options.timestamp = v.to_i
50
+ when SW_KEYS_KEY
51
+ if v.nil? || trace_options.sw_keys
52
+ logger.debug { 'invalid trace option for sw keys' }
53
+ trace_options.ignored << [k, v]
54
+ next
55
+ end
56
+ trace_options.sw_keys = v
57
+ when CUSTOM_KEY_REGEX
58
+ if v.nil? || trace_options.custom[k]
59
+ logger.debug { "invalid trace option for custom key #{k}" }
60
+ trace_options.ignored << [k, v]
61
+ next
62
+ end
63
+ trace_options.custom[k] = v
64
+ else
65
+ trace_options.ignored << [k, v]
66
+ end
67
+ end
68
+
69
+ trace_options
70
+ end
71
+
72
+ def self.numeric_integer?(str)
73
+ true if Integer(str)
74
+ rescue StandardError
75
+ false
76
+ end
77
+
78
+ # combine the array to string separate by ;
79
+ # tracestate doesn't accept value with k=v, here we use k:v
80
+ # but it will be replaced with = when inject in respond header
81
+ def self.stringify_trace_options_response(trace_options_response)
82
+ return if trace_options_response.nil?
83
+
84
+ kvs = {
85
+ auth: trace_options_response.auth,
86
+ 'trigger-trace': trace_options_response.trigger_trace,
87
+ ignored: trace_options_response.ignored.empty? ? nil : trace_options_response.ignored.join(',')
88
+ }
89
+ kvs.compact.map { |k, v| "#{k}:#{v}" }.join(';')
90
+ end
91
+
92
+ def self.validate_signature(header, signature, key, timestamp)
93
+ return Auth::NO_SIGNATURE_KEY unless key
94
+ return Auth::BAD_TIMESTAMP unless timestamp && (Time.now.to_i - timestamp).abs <= 5 * 60
95
+
96
+ digest = OpenSSL::HMAC.hexdigest('SHA1', key, header)
97
+ signature == digest ? Auth::OK : Auth::BAD_SIGNATURE
98
+ end
99
+ end
100
+ end
@@ -6,8 +6,24 @@
6
6
  #
7
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
8
 
9
- # This file is for loading any customized patch for upstream
9
+ require 'json'
10
+ require 'fileutils'
11
+ require 'tempfile'
12
+ require 'uri'
13
+ require 'opentelemetry-sdk'
10
14
 
11
- # e.g.
12
- # require_relative './patch/dummy_patch'
13
- # OpenTelemetry::Instrumentation::Registry.prepend(SolarWindsAPM::Patch::DummyPatch) if defined? OpenTelemetry::Instrumentation::Registry && OpenTelemetry::Instrumentation::Registry::VERSION <= '0.3.0'
15
+ require_relative 'sampling/sampling_constants'
16
+ require_relative 'sampling/dice'
17
+ require_relative 'sampling/settings'
18
+ require_relative 'sampling/token_bucket'
19
+ require_relative 'sampling/trace_options'
20
+ require_relative 'sampling/metrics'
21
+
22
+ # HttpSampler/JsonSampler < Sampler < OboeSampler
23
+ require_relative 'sampling/oboe_sampler'
24
+ require_relative 'sampling/sampler'
25
+ require_relative 'sampling/http_sampler'
26
+ require_relative 'sampling/json_sampler'
27
+
28
+ # Patching
29
+ require_relative 'sampling/sampling_patch'
@@ -1,25 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # © 2023 SolarWinds Worldwide, LLC. All rights reserved.
3
+ # © 2025 SolarWinds Worldwide, LLC. All rights reserved.
4
4
  #
5
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
6
  #
7
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
8
 
9
- ####
10
- # noop version of SolarWindsAPM::Span
11
- #
12
- module Oboe_metal # rubocop:disable Naming/ClassAndModuleCamelCase
13
- # Span
14
- class Span
15
- ##
16
- # noop version of :createHttpSpan
17
- #
18
- def self.createHttpSpan(*); end
19
-
20
- ##
21
- # noop version of :createSpan
22
- #
23
- def self.createSpan(*); end
24
- end
25
- end
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'
@@ -47,4 +47,4 @@ end
47
47
 
48
48
  # To use the trace context in log, ::Logger::Formatter.new must be defined
49
49
  # e.g. config.log_formatter = ::Logger::Formatter.new
50
- Logger::Formatter.prepend(SolarWindsAPM::Logger::Formatter) if SolarWindsAPM.loaded
50
+ Logger::Formatter.prepend(SolarWindsAPM::Logger::Formatter)
@@ -23,4 +23,4 @@ module SolarWindsAPM
23
23
  end
24
24
  end
25
25
 
26
- Logging::LogEvent.prepend(SolarWindsAPM::Logging::LogEvent) if SolarWindsAPM.loaded && defined?(Logging::LogEvent)
26
+ Logging::LogEvent.prepend(SolarWindsAPM::Logging::LogEvent) if defined?(Logging::LogEvent)
@@ -23,4 +23,4 @@ module SolarWindsAPM
23
23
  end
24
24
  end
25
25
 
26
- Lumberjack::LogEntry.prepend(SolarWindsAPM::Lumberjack::LogEntry) if SolarWindsAPM.loaded && defined?(Lumberjack::LogEntry)
26
+ Lumberjack::LogEntry.prepend(SolarWindsAPM::Lumberjack::LogEntry) if defined?(Lumberjack::LogEntry)
@@ -0,0 +1,99 @@
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_relative 'service_key_checker'
10
+
11
+ module SolarWindsAPM
12
+ # OTLPEndPoint
13
+ class OTLPEndPoint
14
+ attr_reader :token, :service_name
15
+
16
+ SWO_APM_ENDPOINT_REGEX = /^apm\.collector\.([a-z]{2}-\d{2})\.([^.]+)\.solarwinds\.com(?::\d+)?$/
17
+ SWO_APM_ENDPOINT_DEFAULT = 'apm.collector.na-01.cloud.solarwinds.com:443'
18
+
19
+ SWO_OTLP_GENERAL_ENDPOINT_REGEX = %r{^https://otel\.collector\.[a-z0-9-]+\.[a-z0-9-]+\.solarwinds\.com(?::\d+)?$}
20
+ SWO_OTLP_SIGNAL_ENDPOINT_REGEX = %r{^https://otel\.collector\.[a-z0-9-]+\.[a-z0-9-]+\.solarwinds\.com(?::\d+)?/v1/(?:logs|metrics|traces)$}
21
+ SWO_OTLP_ENDPOINT_DEFAULT = 'https://otel.collector.na-01.cloud.solarwinds.com:443'
22
+
23
+ OTEL_SIGNAL_TYPE = %w[TRACES METRICS LOGS].freeze
24
+
25
+ def initialize
26
+ @token = nil
27
+ @service_name = nil
28
+ end
29
+
30
+ def config_otlp_token_and_endpoint
31
+ matches = ENV['SW_APM_COLLECTOR'].to_s.match(SWO_APM_ENDPOINT_REGEX)
32
+
33
+ resolve_get_setting_endpoint(matches)
34
+
35
+ service_key_checker = SolarWindsAPM::ServiceKeyChecker.new('ssl', SolarWindsAPM::Utils.determine_lambda)
36
+ @token = service_key_checker.token
37
+ @service_name = service_key_checker.service_name
38
+
39
+ OTEL_SIGNAL_TYPE.each do |data_type|
40
+ config_token(data_type)
41
+ configure_otlp_endpoint(data_type, matches)
42
+ end
43
+ end
44
+
45
+ # APM Libraries should only set the bearer token header as a convenience if:
46
+ # The OTEL config for exporter OTLP headers is not already set, i.e. explicitly configured by the end user, AND
47
+ # The OTLP export endpoint is SWO, i.e. host is otel.collector.*.*.solarwinds.com
48
+ def config_token(data_type)
49
+ data_type_upper = data_type.upcase
50
+
51
+ return unless @token
52
+
53
+ if ENV["OTEL_EXPORTER_OTLP_#{data_type_upper}_HEADERS"].to_s.empty? && ENV["OTEL_EXPORTER_OTLP_#{data_type_upper}_ENDPOINT"].to_s.match?(SWO_OTLP_SIGNAL_ENDPOINT_REGEX)
54
+ ENV["OTEL_EXPORTER_OTLP_#{data_type_upper}_HEADERS"] = "authorization=Bearer #{@token}"
55
+ elsif ENV['OTEL_EXPORTER_OTLP_HEADERS'].to_s.empty? && ENV['OTEL_EXPORTER_OTLP_ENDPOINT'].to_s.match?(SWO_OTLP_GENERAL_ENDPOINT_REGEX)
56
+ ENV['OTEL_EXPORTER_OTLP_HEADERS'] = "authorization=Bearer #{@token}"
57
+ end
58
+
59
+ ENV['OTEL_EXPORTER_OTLP_HEADERS'] = "authorization=Bearer #{@token}" if ENV['OTEL_EXPORTER_OTLP_HEADERS'].to_s.empty? && ENV["OTEL_EXPORTER_OTLP_#{data_type_upper}_HEADERS"].to_s.empty?
60
+ end
61
+
62
+ def configure_otlp_endpoint(data_type, matches)
63
+ data_type_upper = data_type.upcase
64
+ data_type_lower = data_type.downcase
65
+ otlp_endpoint_source = if ENV["OTEL_EXPORTER_OTLP_#{data_type_upper}_ENDPOINT"]
66
+ "#{data_type_lower}_endpoint"
67
+ elsif ENV['OTEL_EXPORTER_OTLP_ENDPOINT']
68
+ 'general_endpoint'
69
+ else
70
+ 'no_endpoint'
71
+ end
72
+
73
+ SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] otlp_endpoint_source: #{otlp_endpoint_source}" }
74
+
75
+ return unless otlp_endpoint_source == 'no_endpoint'
76
+
77
+ if matches&.size == 3
78
+ region = matches[1]
79
+ env = matches[2]
80
+ ENV["OTEL_EXPORTER_OTLP_#{data_type_upper}_ENDPOINT"] = "https://otel.collector.#{region}.#{env}.solarwinds.com:443/v1/#{data_type_lower}"
81
+ else
82
+ ENV["OTEL_EXPORTER_OTLP_#{data_type_upper}_ENDPOINT"] = "https://otel.collector.na-01.cloud.solarwinds.com:443/v1/#{data_type_lower}"
83
+ end
84
+ end
85
+
86
+ # only valid value is apm.collector.*.*.solarwinds.com
87
+ # If SW APM config for collector is not set, the fallback: apm.collector.na-01.cloud.solarwinds.com
88
+ def resolve_get_setting_endpoint(matches)
89
+ return if matches&.size == 3
90
+
91
+ unless ENV['SW_APM_COLLECTOR'].to_s.empty?
92
+ SolarWindsAPM.logger.warn do
93
+ "[#{self.class}/#{__method__}] SW_APM_COLLECTOR format invalid: #{ENV.fetch('SW_APM_COLLECTOR', nil)}. Valid format: apm.collector.*.*.solarwinds.com"
94
+ end
95
+ end
96
+ ENV['SW_APM_COLLECTOR'] = SWO_APM_ENDPOINT_DEFAULT
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,51 @@
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