solarwinds_apm 7.0.0 → 7.1.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 +4 -4
- data/README.md +1 -0
- data/lib/solarwinds_apm/api/transaction_name.rb +7 -6
- data/lib/solarwinds_apm/config.rb +33 -24
- data/lib/solarwinds_apm/noop/README.md +24 -5
- data/lib/solarwinds_apm/noop/api.rb +12 -5
- data/lib/solarwinds_apm/opentelemetry/otlp_processor.rb +32 -19
- data/lib/solarwinds_apm/opentelemetry/solarwinds_propagator.rb +8 -2
- data/lib/solarwinds_apm/opentelemetry/solarwinds_response_propagator.rb +5 -15
- data/lib/solarwinds_apm/opentelemetry.rb +3 -0
- data/lib/solarwinds_apm/otel_config.rb +3 -2
- data/lib/solarwinds_apm/sampling/dice.rb +5 -3
- data/lib/solarwinds_apm/sampling/http_sampler.rb +58 -19
- data/lib/solarwinds_apm/sampling/json_sampler.rb +27 -12
- data/lib/solarwinds_apm/sampling/oboe_sampler.rb +50 -57
- data/lib/solarwinds_apm/sampling/sampler.rb +46 -58
- data/lib/solarwinds_apm/sampling/sampling_constants.rb +4 -3
- data/lib/solarwinds_apm/sampling/settings.rb +3 -1
- data/lib/solarwinds_apm/sampling/token_bucket.rb +12 -3
- data/lib/solarwinds_apm/sampling/trace_options.rb +33 -16
- data/lib/solarwinds_apm/sampling.rb +0 -1
- data/lib/solarwinds_apm/support/resource_detector.rb +20 -18
- data/lib/solarwinds_apm/support/transaction_settings.rb +12 -5
- data/lib/solarwinds_apm/support/txn_name_manager.rb +0 -2
- data/lib/solarwinds_apm/version.rb +1 -1
- metadata +44 -2
|
@@ -9,63 +9,66 @@
|
|
|
9
9
|
module SolarWindsAPM
|
|
10
10
|
class TraceOptions
|
|
11
11
|
TRIGGER_TRACE_KEY = 'trigger-trace'
|
|
12
|
-
TIMESTAMP_KEY
|
|
13
|
-
SW_KEYS_KEY
|
|
14
|
-
|
|
15
|
-
CUSTOM_KEY_REGEX = /^custom-[^\s]*$/
|
|
12
|
+
TIMESTAMP_KEY = 'ts'
|
|
13
|
+
SW_KEYS_KEY = 'sw-keys'
|
|
14
|
+
CUSTOM_KEY_REGEX = /^custom-[^\s]*$/
|
|
16
15
|
|
|
17
16
|
def self.parse_trace_options(header, logger)
|
|
17
|
+
logger.debug { "[#{self.class}/#{__method__}] Parsing trace options header: #{header&.slice(0, 100)}..." }
|
|
18
18
|
trace_options = TriggerTraceOptions.new(nil, nil, nil, {}, [], TraceOptionsResponse.new(nil, nil, []))
|
|
19
19
|
|
|
20
|
-
kvs = header.split(';').
|
|
20
|
+
kvs = header.split(';').filter_map do |kv|
|
|
21
21
|
key, *values = kv.split('=').map(&:strip)
|
|
22
|
+
next if key.nil? || key.empty?
|
|
23
|
+
|
|
22
24
|
value = values.any? ? values.join('=') : nil
|
|
23
25
|
[key, value]
|
|
24
26
|
end
|
|
25
27
|
|
|
26
|
-
|
|
28
|
+
logger.debug { "[#{self.class}/#{__method__}] Parsed kvs #{kvs.inspect}" }
|
|
27
29
|
|
|
28
30
|
kvs.each do |k, v|
|
|
29
31
|
case k
|
|
30
32
|
when TRIGGER_TRACE_KEY
|
|
31
33
|
if v || trace_options.trigger_trace
|
|
32
|
-
logger.debug {
|
|
34
|
+
logger.debug { "[#{self.class}/#{__method__}] invalid trace option for trigger trace: value=#{v}, already_set=#{trace_options.trigger_trace}" }
|
|
33
35
|
trace_options.ignored << [k, v]
|
|
34
36
|
next
|
|
35
37
|
end
|
|
36
38
|
trace_options.trigger_trace = true
|
|
37
39
|
when TIMESTAMP_KEY
|
|
38
40
|
if v.nil? || trace_options.timestamp
|
|
39
|
-
logger.debug {
|
|
41
|
+
logger.debug { "[#{self.class}/#{__method__}] invalid trace option for timestamp: value=#{v}, already_set=#{trace_options.timestamp}" }
|
|
40
42
|
trace_options.ignored << [k, v]
|
|
41
43
|
next
|
|
42
44
|
end
|
|
43
45
|
|
|
44
46
|
unless numeric_integer?(v)
|
|
45
|
-
logger.debug {
|
|
47
|
+
logger.debug { "[#{self.class}/#{__method__}] invalid trace option for timestamp, should be an integer: #{v}" }
|
|
46
48
|
trace_options.ignored << [k, v]
|
|
47
49
|
next
|
|
48
50
|
end
|
|
49
51
|
trace_options.timestamp = v.to_i
|
|
50
52
|
when SW_KEYS_KEY
|
|
51
53
|
if v.nil? || trace_options.sw_keys
|
|
52
|
-
logger.debug {
|
|
54
|
+
logger.debug { "[#{self.class}/#{__method__}] invalid trace option for sw keys: value=#{v}, already_set=#{trace_options.sw_keys}" }
|
|
53
55
|
trace_options.ignored << [k, v]
|
|
54
56
|
next
|
|
55
57
|
end
|
|
56
58
|
trace_options.sw_keys = v
|
|
57
59
|
when CUSTOM_KEY_REGEX
|
|
58
60
|
if v.nil? || trace_options.custom[k]
|
|
59
|
-
logger.debug { "invalid trace option for custom key #{k}" }
|
|
61
|
+
logger.debug { "[#{self.class}/#{__method__}] invalid trace option for custom key #{k}: value=#{v}, already_set=#{trace_options.custom[k]}" }
|
|
60
62
|
trace_options.ignored << [k, v]
|
|
61
63
|
next
|
|
62
64
|
end
|
|
63
65
|
trace_options.custom[k] = v
|
|
64
66
|
else
|
|
67
|
+
logger.debug { "[#{self.class}/#{__method__}] Unknown key ignored: #{k}=#{v}" }
|
|
65
68
|
trace_options.ignored << [k, v]
|
|
66
69
|
end
|
|
67
70
|
end
|
|
68
|
-
|
|
71
|
+
logger.debug { "[#{self.class}/#{__method__}] Parsing complete: trigger_trace=#{trace_options.trigger_trace}, timestamp=#{trace_options.timestamp}, sw_keys=#{trace_options.sw_keys}, custom_keys=#{trace_options.custom}, ignored=#{trace_options.ignored}" }
|
|
69
72
|
trace_options
|
|
70
73
|
end
|
|
71
74
|
|
|
@@ -86,15 +89,29 @@ module SolarWindsAPM
|
|
|
86
89
|
'trigger-trace': trace_options_response.trigger_trace,
|
|
87
90
|
ignored: trace_options_response.ignored.empty? ? nil : trace_options_response.ignored.join(',')
|
|
88
91
|
}
|
|
89
|
-
|
|
92
|
+
|
|
93
|
+
kvs.compact!
|
|
94
|
+
result = kvs.map { |k, v| "#{k}:#{v}" }.join(';')
|
|
95
|
+
SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] Stringified trace options response: #{result}" }
|
|
96
|
+
result
|
|
90
97
|
end
|
|
91
98
|
|
|
92
99
|
def self.validate_signature(header, signature, key, timestamp)
|
|
93
|
-
|
|
94
|
-
|
|
100
|
+
unless key
|
|
101
|
+
SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] Signature validation failed: no signature key available" }
|
|
102
|
+
return Auth::NO_SIGNATURE_KEY
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
unless timestamp && (Time.now.to_i - timestamp).abs <= 5 * 60
|
|
106
|
+
SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] Signature validation failed: bad timestamp (diff more than 300s)" }
|
|
107
|
+
return Auth::BAD_TIMESTAMP
|
|
108
|
+
end
|
|
95
109
|
|
|
96
110
|
digest = OpenSSL::HMAC.hexdigest('SHA1', key, header)
|
|
97
|
-
signature == digest
|
|
111
|
+
is_valid = signature == digest
|
|
112
|
+
|
|
113
|
+
SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] Signature validation result: #{is_valid ? 'valid' : 'invalid'}" }
|
|
114
|
+
is_valid ? Auth::OK : Auth::BAD_SIGNATURE
|
|
98
115
|
end
|
|
99
116
|
end
|
|
100
117
|
end
|
|
@@ -64,9 +64,7 @@ module SolarWindsAPM
|
|
|
64
64
|
|
|
65
65
|
response = nil
|
|
66
66
|
::OpenTelemetry::Common::Utilities.untraced do
|
|
67
|
-
|
|
68
|
-
request = Net::HTTP::Get.new(url)
|
|
69
|
-
response = http.request(request)
|
|
67
|
+
response = Net::HTTP.get_response(url)
|
|
70
68
|
end
|
|
71
69
|
|
|
72
70
|
raise 'Response returned non-200 status code' unless response&.code.to_i == 200
|
|
@@ -152,37 +150,41 @@ module SolarWindsAPM
|
|
|
152
150
|
end
|
|
153
151
|
|
|
154
152
|
def self.detect_ec2
|
|
155
|
-
|
|
156
|
-
SolarWindsAPM.logger.debug { "#{self.class}/#{__method__}] retrieved resource_attributes: #{attribute.instance_variable_get(:@attributes)}" }
|
|
157
|
-
attribute
|
|
158
|
-
rescue StandardError
|
|
159
|
-
::OpenTelemetry::SDK::Resources::Resource.create({})
|
|
153
|
+
run_opentelemetry_detector(::OpenTelemetry::Resource::Detector::AWS::EC2)
|
|
160
154
|
end
|
|
161
155
|
|
|
162
156
|
def self.detect_azure
|
|
163
|
-
|
|
164
|
-
SolarWindsAPM.logger.debug { "#{self.class}/#{__method__}] retrieved resource_attributes: #{attribute.instance_variable_get(:@attributes)}" }
|
|
165
|
-
attribute
|
|
166
|
-
rescue StandardError
|
|
167
|
-
::OpenTelemetry::SDK::Resources::Resource.create({})
|
|
157
|
+
run_opentelemetry_detector(::OpenTelemetry::Resource::Detector::Azure)
|
|
168
158
|
end
|
|
169
159
|
|
|
170
160
|
def self.detect_container
|
|
171
|
-
|
|
172
|
-
|
|
161
|
+
run_opentelemetry_detector(::OpenTelemetry::Resource::Detector::Container)
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def self.run_opentelemetry_detector(detector_class)
|
|
165
|
+
attribute = detector_class.detect
|
|
166
|
+
SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] Detector #{detector_class} retrieved: #{attribute.instance_variable_get(:@attributes)}" }
|
|
173
167
|
attribute
|
|
174
|
-
rescue StandardError
|
|
168
|
+
rescue StandardError => e
|
|
169
|
+
SolarWindsAPM.logger.error { "[#{self.class}/#{__method__}] Detector #{detector_class} failed. Error: #{e.message}." }
|
|
175
170
|
::OpenTelemetry::SDK::Resources::Resource.create({})
|
|
176
171
|
end
|
|
177
172
|
|
|
173
|
+
def self.number?(string)
|
|
174
|
+
true if Float(string)
|
|
175
|
+
rescue StandardError
|
|
176
|
+
false
|
|
177
|
+
end
|
|
178
|
+
|
|
178
179
|
def self.safe_integer?(number)
|
|
179
180
|
min_safe_integer = -((2**53) - 1)
|
|
180
181
|
max_safe_integer = (2**53) - 1
|
|
181
|
-
number
|
|
182
|
+
number = number.to_i if number?(number)
|
|
183
|
+
number.between?(min_safe_integer, max_safe_integer)
|
|
182
184
|
end
|
|
183
185
|
|
|
184
186
|
def self.windows?
|
|
185
|
-
|
|
187
|
+
/mswin|mingw|cygwin/.match?(RUBY_PLATFORM)
|
|
186
188
|
end
|
|
187
189
|
|
|
188
190
|
def self.random_uuid
|
|
@@ -37,17 +37,24 @@ module SolarWindsAPM
|
|
|
37
37
|
SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] enabled_regexps: #{enabled_regexps&.inspect}" }
|
|
38
38
|
SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] disabled_regexps: #{disabled_regexps&.inspect}" }
|
|
39
39
|
|
|
40
|
-
return false if
|
|
41
|
-
return true
|
|
42
|
-
return false if
|
|
43
|
-
return true
|
|
40
|
+
return false if matches_any?(disabled_regexps, @url_path)
|
|
41
|
+
return true if matches_any?(enabled_regexps, @url_path)
|
|
42
|
+
return false if matches_any?(disabled_regexps, span_layer)
|
|
43
|
+
return true if matches_any?(enabled_regexps, span_layer)
|
|
44
44
|
|
|
45
45
|
true
|
|
46
46
|
rescue StandardError => e
|
|
47
47
|
SolarWindsAPM.logger.warn do
|
|
48
|
-
"[#{self.class}/#{__method__}] Could not determine tracing status for #{kind}. #{e.inspect}. transaction_settings regexps/extensions igonred/unfiltered."
|
|
48
|
+
"[#{self.class}/#{__method__}] Could not determine tracing status for #{@kind}. #{e.inspect}. transaction_settings regexps/extensions igonred/unfiltered."
|
|
49
49
|
end
|
|
50
50
|
true
|
|
51
51
|
end
|
|
52
|
+
|
|
53
|
+
# Checks if a given string matches any regex in a list.
|
|
54
|
+
def matches_any?(regex_list, string_to_match)
|
|
55
|
+
return false unless regex_list.is_a?(Array)
|
|
56
|
+
|
|
57
|
+
regex_list.any? { |regex| regex.match?(string_to_match) }
|
|
58
|
+
end
|
|
52
59
|
end
|
|
53
60
|
end
|
|
@@ -52,9 +52,7 @@ module SolarWindsAPM
|
|
|
52
52
|
txn_name_exist = true
|
|
53
53
|
@transaction_name[txn_name].exit
|
|
54
54
|
end
|
|
55
|
-
end
|
|
56
55
|
|
|
57
|
-
@mutex.synchronize do
|
|
58
56
|
if txn_name_exist || @transaction_name.size < MAX_CARDINALITY
|
|
59
57
|
@cache[key] = txn_name
|
|
60
58
|
@transaction_name[txn_name] = Thread.new { cleanup_txn(key, txn_name) }
|
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.
|
|
4
|
+
version: 7.1.0
|
|
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-
|
|
14
|
+
date: 2025-10-21 00:00:00.000000000 Z
|
|
15
15
|
dependencies:
|
|
16
16
|
- !ruby/object:Gem::Dependency
|
|
17
17
|
name: opentelemetry-exporter-otlp
|
|
@@ -27,6 +27,20 @@ dependencies:
|
|
|
27
27
|
- - ">="
|
|
28
28
|
- !ruby/object:Gem::Version
|
|
29
29
|
version: 0.29.1
|
|
30
|
+
- !ruby/object:Gem::Dependency
|
|
31
|
+
name: opentelemetry-exporter-otlp-logs
|
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
|
33
|
+
requirements:
|
|
34
|
+
- - ">="
|
|
35
|
+
- !ruby/object:Gem::Version
|
|
36
|
+
version: 0.2.1
|
|
37
|
+
type: :runtime
|
|
38
|
+
prerelease: false
|
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
40
|
+
requirements:
|
|
41
|
+
- - ">="
|
|
42
|
+
- !ruby/object:Gem::Version
|
|
43
|
+
version: 0.2.1
|
|
30
44
|
- !ruby/object:Gem::Dependency
|
|
31
45
|
name: opentelemetry-exporter-otlp-metrics
|
|
32
46
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -55,6 +69,34 @@ dependencies:
|
|
|
55
69
|
- - ">="
|
|
56
70
|
- !ruby/object:Gem::Version
|
|
57
71
|
version: 0.31.0
|
|
72
|
+
- !ruby/object:Gem::Dependency
|
|
73
|
+
name: opentelemetry-instrumentation-logger
|
|
74
|
+
requirement: !ruby/object:Gem::Requirement
|
|
75
|
+
requirements:
|
|
76
|
+
- - ">="
|
|
77
|
+
- !ruby/object:Gem::Version
|
|
78
|
+
version: 0.1.0
|
|
79
|
+
type: :runtime
|
|
80
|
+
prerelease: false
|
|
81
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
82
|
+
requirements:
|
|
83
|
+
- - ">="
|
|
84
|
+
- !ruby/object:Gem::Version
|
|
85
|
+
version: 0.1.0
|
|
86
|
+
- !ruby/object:Gem::Dependency
|
|
87
|
+
name: opentelemetry-logs-sdk
|
|
88
|
+
requirement: !ruby/object:Gem::Requirement
|
|
89
|
+
requirements:
|
|
90
|
+
- - ">="
|
|
91
|
+
- !ruby/object:Gem::Version
|
|
92
|
+
version: 0.4.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.4.0
|
|
58
100
|
- !ruby/object:Gem::Dependency
|
|
59
101
|
name: opentelemetry-metrics-sdk
|
|
60
102
|
requirement: !ruby/object:Gem::Requirement
|