solarwinds_apm 7.0.1 → 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 +31 -12
- 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 +1 -1
- data/lib/solarwinds_apm/sampling/http_sampler.rb +17 -10
- 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 +2 -0
- 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 +11 -16
- data/lib/solarwinds_apm/support/transaction_settings.rb +12 -5
- data/lib/solarwinds_apm/version.rb +2 -2
- metadata +44 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d565e746144994b5c3a81ee19665ee9b2d4e81fbad2e21da567465455c37e6c0
|
|
4
|
+
data.tar.gz: 949351c94748745d2ba19268a531ba5b4f805568132772dcfb9749b0af3698a8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 209a50b78aa94dc85805ef8c6c08db7584966ca62293c53aa9744852170c3c84ac4ef24b2e43e508bad5d1e505f4cd82d72d984a00823232b9a9e13bddb061c6
|
|
7
|
+
data.tar.gz: '0962ab7325e66d3f330361b9f0187f75c07eabcd2d0d400db0d55e75c2124aefa2fe630cbe2eef506f48f58f6e6d3cb6ad1d36f1a816845618b6bd2649e8b951'
|
data/README.md
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
The `solarwinds_apm` gem starting from version 6.0.0 is an [OpenTelemetry Ruby](https://opentelemetry.io/docs/instrumentation/ruby/) distribution. It provides automatic instrumentation and custom SolarWinds Observability features for Ruby applications.
|
|
4
4
|
|
|
5
5
|
## Requirements
|
|
6
|
+
>
|
|
6
7
|
> [!NOTE]
|
|
7
8
|
> Versions before 7.0.0 only support Linux and will go into no-op mode on other platforms.
|
|
8
9
|
|
|
@@ -21,15 +21,16 @@ module SolarWindsAPM
|
|
|
21
21
|
#
|
|
22
22
|
# === Example:
|
|
23
23
|
#
|
|
24
|
-
# class
|
|
24
|
+
# class OrdersController < ApplicationController
|
|
25
25
|
#
|
|
26
26
|
# def create
|
|
27
|
-
# @
|
|
28
|
-
# @
|
|
27
|
+
# @order = Order.new(params.permit(:item, :quantity))
|
|
28
|
+
# if @order.save
|
|
29
|
+
# custom_name = "orderscontroller.create_for_#{params[:item]}"
|
|
30
|
+
# SolarWindsAPM::API.set_transaction_name(custom_name)
|
|
31
|
+
# end
|
|
29
32
|
#
|
|
30
|
-
#
|
|
31
|
-
#
|
|
32
|
-
# redirect_to @dogfood
|
|
33
|
+
# redirect_to @order
|
|
33
34
|
# end
|
|
34
35
|
#
|
|
35
36
|
# end
|
|
@@ -87,21 +87,32 @@ module SolarWindsAPM
|
|
|
87
87
|
SolarWindsAPM.logger.level = SW_LOG_LEVEL_MAPPING.dig(log_level, :stdlib) || ::Logger::INFO # default log level info
|
|
88
88
|
end
|
|
89
89
|
|
|
90
|
+
# e.g. enable_disable_config('STRING', :key, value, false, bool: true)
|
|
90
91
|
def self.enable_disable_config(env_var, key, value, default, bool: false)
|
|
91
|
-
|
|
92
|
+
raw_env_value = ENV.fetch(env_var, '')
|
|
93
|
+
env_value = raw_env_value.downcase
|
|
92
94
|
valid_env_values = bool ? %w[true false] : %w[enabled disabled]
|
|
93
95
|
|
|
94
|
-
if env_var && valid_env_values.include?(env_value)
|
|
96
|
+
if !env_var.empty? && valid_env_values.include?(env_value)
|
|
95
97
|
value = bool ? true?(env_value) : env_value.to_sym
|
|
96
|
-
elsif env_var && !
|
|
97
|
-
SolarWindsAPM.logger.warn
|
|
98
|
-
|
|
98
|
+
elsif !env_var.empty? && !raw_env_value.empty?
|
|
99
|
+
SolarWindsAPM.logger.warn do
|
|
100
|
+
"[#{name}/#{__method__}] #{env_var} must be #{valid_env_values.join('/')} (current setting is #{raw_env_value}). Using default value: #{default}."
|
|
101
|
+
end
|
|
102
|
+
return @@config[key] = default
|
|
99
103
|
end
|
|
100
104
|
|
|
101
|
-
|
|
105
|
+
# Validate final value efficiently
|
|
106
|
+
valid = bool ? boolean?(value) : symbol?(value)
|
|
107
|
+
|
|
108
|
+
unless valid
|
|
109
|
+
SolarWindsAPM.logger.warn do
|
|
110
|
+
"[#{name}/#{__method__}] :#{key} must be #{valid_env_values.join('/')}. Using default value: #{default}."
|
|
111
|
+
end
|
|
112
|
+
return @@config[key] = default
|
|
113
|
+
end
|
|
102
114
|
|
|
103
|
-
|
|
104
|
-
@@config[key.to_sym] = default
|
|
115
|
+
@@config[key] = value
|
|
105
116
|
end
|
|
106
117
|
|
|
107
118
|
def self.true?(obj)
|
|
@@ -211,7 +222,7 @@ module SolarWindsAPM
|
|
|
211
222
|
enable_disable_config('SW_APM_TRIGGER_TRACING_MODE', key, value, :enabled)
|
|
212
223
|
|
|
213
224
|
when :tracing_mode
|
|
214
|
-
enable_disable_config(
|
|
225
|
+
enable_disable_config('', key, value, :enabled)
|
|
215
226
|
|
|
216
227
|
when :tag_sql
|
|
217
228
|
enable_disable_config('SW_APM_TAG_SQL', key, value, false, bool: true)
|
|
@@ -242,9 +253,17 @@ module SolarWindsAPM
|
|
|
242
253
|
return
|
|
243
254
|
end
|
|
244
255
|
|
|
245
|
-
# `tracing: disabled` is the default
|
|
246
|
-
|
|
247
|
-
|
|
256
|
+
# `tracing: disabled` is the default; below only separate the enabled and disabled settings
|
|
257
|
+
result = settings.each_with_object({ enabled: [], disabled: [] }) do |setting, acc|
|
|
258
|
+
if setting[:tracing] == :enabled
|
|
259
|
+
acc[:enabled] << setting
|
|
260
|
+
elsif !setting.key?(:tracing) || setting[:tracing] == :disabled
|
|
261
|
+
acc[:disabled] << setting
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
enabled = result[:enabled]
|
|
266
|
+
disabled = result[:disabled]
|
|
248
267
|
|
|
249
268
|
SolarWindsAPM::Config[:enabled_regexps] = compile_regexp(enabled)
|
|
250
269
|
SolarWindsAPM::Config[:disabled_regexps] = compile_regexp(disabled)
|
|
@@ -21,12 +21,15 @@ module SolarWindsAPM
|
|
|
21
21
|
HTTP_STATUS_CODE = 'http.status_code'
|
|
22
22
|
HTTP_URL = 'http.url'
|
|
23
23
|
|
|
24
|
+
HTTP_RESPONSE_STATUS_CODE = 'http.response.status_code'
|
|
25
|
+
HTTP_REQUEST_METHOD = 'http.request.method'
|
|
26
|
+
|
|
24
27
|
INVALID_HTTP_STATUS_CODE = 0
|
|
25
28
|
|
|
26
29
|
def initialize(txn_manager)
|
|
27
30
|
@txn_manager = txn_manager
|
|
28
|
-
@meters = { 'sw.apm.request.metrics' => ::OpenTelemetry.meter_provider.meter('sw.apm.request.metrics') }
|
|
29
31
|
@metrics = init_response_time_metrics
|
|
32
|
+
@is_lambda = SolarWindsAPM::Utils.determine_lambda
|
|
30
33
|
@transaction_name = nil
|
|
31
34
|
end
|
|
32
35
|
|
|
@@ -40,21 +43,24 @@ module SolarWindsAPM
|
|
|
40
43
|
trace_flags = span.context.trace_flags.sampled? ? '01' : '00'
|
|
41
44
|
@txn_manager&.set_root_context_h(span.context.hex_trace_id, "#{span.context.hex_span_id}-#{trace_flags}")
|
|
42
45
|
span.add_attributes({ SW_IS_ENTRY_SPAN => true })
|
|
46
|
+
SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] processor on_start end" }
|
|
43
47
|
rescue StandardError => e
|
|
44
48
|
SolarWindsAPM.logger.info { "[#{self.class}/#{__method__}] processor on_start error: #{e.message}" }
|
|
45
49
|
end
|
|
46
50
|
|
|
47
51
|
def on_finishing(span)
|
|
52
|
+
SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] processor on_finishing span attributes: #{span.attributes}" }
|
|
48
53
|
return if non_entry_span(span: span)
|
|
49
54
|
|
|
50
55
|
@transaction_name = calculate_transaction_names(span)
|
|
51
56
|
span.set_attribute(SW_TRANSACTION_NAME, @transaction_name)
|
|
52
57
|
@txn_manager.delete_root_context_h(span.context.hex_trace_id)
|
|
58
|
+
SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] processor on_finishing end" }
|
|
53
59
|
end
|
|
54
60
|
|
|
55
61
|
# @param [Span] span the (immutable) {Span} that just ended.
|
|
56
62
|
def on_finish(span)
|
|
57
|
-
SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] processor on_finish span: #{span.
|
|
63
|
+
SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] processor on_finish span attributes: #{span.attributes}" }
|
|
58
64
|
return if non_entry_span(span: span)
|
|
59
65
|
|
|
60
66
|
record_request_metrics(span)
|
|
@@ -63,10 +69,9 @@ module SolarWindsAPM
|
|
|
63
69
|
::OpenTelemetry.meter_provider.metric_readers.each do |reader|
|
|
64
70
|
reader.pull if reader.respond_to? :pull
|
|
65
71
|
end
|
|
66
|
-
|
|
67
|
-
SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] processor on_finish succeed" }
|
|
72
|
+
SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] processor on_finish end" }
|
|
68
73
|
rescue StandardError => e
|
|
69
|
-
SolarWindsAPM.logger.info { "[#{self.class}/#{__method__}]
|
|
74
|
+
SolarWindsAPM.logger.info { "[#{self.class}/#{__method__}] processor on_finish error: #{e.message}" }
|
|
70
75
|
end
|
|
71
76
|
|
|
72
77
|
# @param [optional Numeric] timeout An optional timeout in seconds.
|
|
@@ -94,7 +99,9 @@ module SolarWindsAPM
|
|
|
94
99
|
unit: 'ms')
|
|
95
100
|
end
|
|
96
101
|
|
|
97
|
-
|
|
102
|
+
meter = ::OpenTelemetry.meter_provider.meter('sw.apm.request.metrics')
|
|
103
|
+
instrument = meter.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.')
|
|
104
|
+
SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] Adding ExponentialBucketHistogram for response time metrics: #{instrument.inspect}" }
|
|
98
105
|
{ response_time: instrument }
|
|
99
106
|
end
|
|
100
107
|
|
|
@@ -104,37 +111,43 @@ module SolarWindsAPM
|
|
|
104
111
|
SW_TRANSACTION_NAME => @transaction_name
|
|
105
112
|
}
|
|
106
113
|
|
|
107
|
-
|
|
114
|
+
is_http_span = span_http?(span)
|
|
115
|
+
|
|
116
|
+
if is_http_span
|
|
108
117
|
http_status_code = get_http_status_code(span)
|
|
109
|
-
meter_attrs[
|
|
110
|
-
meter_attrs[
|
|
118
|
+
meter_attrs[HTTP_STATUS_CODE] = http_status_code if http_status_code != 0
|
|
119
|
+
meter_attrs[HTTP_METHOD] = span.attributes[HTTP_METHOD] if span.attributes[HTTP_METHOD]
|
|
120
|
+
meter_attrs[HTTP_METHOD] = span.attributes[HTTP_REQUEST_METHOD] if span.attributes[HTTP_REQUEST_METHOD]
|
|
111
121
|
end
|
|
112
|
-
|
|
122
|
+
|
|
123
|
+
SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] is_http_span: #{is_http_span}; meter_attrs: #{meter_attrs.inspect}" }
|
|
113
124
|
meter_attrs.compact!
|
|
114
125
|
meter_attrs
|
|
115
126
|
end
|
|
116
127
|
|
|
117
128
|
def calculate_lambda_transaction_name(span_name)
|
|
118
|
-
(ENV['SW_APM_TRANSACTION_NAME'] || ENV['AWS_LAMBDA_FUNCTION_NAME'] || span_name || 'unknown').slice(0, SolarWindsAPM::Constants::MAX_TXN_NAME_LENGTH)
|
|
129
|
+
txn_name = (ENV['SW_APM_TRANSACTION_NAME'] || ENV['AWS_LAMBDA_FUNCTION_NAME'] || span_name || 'unknown').slice(0, SolarWindsAPM::Constants::MAX_TXN_NAME_LENGTH)
|
|
130
|
+
SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] Lambda transaction name: #{txn_name} (from env_txn=#{ENV.fetch('SW_APM_TRANSACTION_NAME', nil)}, lambda_func=#{ENV.fetch('AWS_LAMBDA_FUNCTION_NAME', nil)}, span_name=#{span_name})" }
|
|
131
|
+
txn_name
|
|
119
132
|
end
|
|
120
133
|
|
|
121
134
|
# Get trans_name and url_tran of this span instance.
|
|
122
135
|
# Predecessor order: custom SDK > env var SW_APM_TRANSACTION_NAME > automatic naming
|
|
123
136
|
def calculate_transaction_names(span)
|
|
124
|
-
return calculate_lambda_transaction_name(span.name) if
|
|
137
|
+
return calculate_lambda_transaction_name(span.name) if @is_lambda
|
|
125
138
|
|
|
126
139
|
trace_span_id = "#{span.context.hex_trace_id}-#{span.context.hex_span_id}"
|
|
127
140
|
trans_name = @txn_manager.get(trace_span_id)
|
|
128
141
|
if trans_name
|
|
129
|
-
SolarWindsAPM.logger.debug
|
|
130
|
-
"[#{self.class}/#{__method__}] found trans name from txn_manager: #{trans_name} by #{trace_span_id}"
|
|
131
|
-
end
|
|
142
|
+
SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] Using transaction name from txn_manager: #{trans_name} (#{trace_span_id})" }
|
|
132
143
|
@txn_manager.del(trace_span_id)
|
|
133
144
|
elsif !ENV['SW_APM_TRANSACTION_NAME'].to_s.empty?
|
|
134
145
|
trans_name = ENV.fetch('SW_APM_TRANSACTION_NAME', nil)
|
|
146
|
+
SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] Using transaction name from env var: #{trans_name}" }
|
|
135
147
|
else
|
|
136
|
-
trans_name = span.attributes[HTTP_ROUTE]
|
|
137
|
-
trans_name = span.name if trans_name.to_s.empty?
|
|
148
|
+
trans_name = span.attributes[HTTP_ROUTE]
|
|
149
|
+
trans_name = span.name if trans_name.to_s.empty?
|
|
150
|
+
SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] Using transaction name from span.attributes: #{span.attributes[HTTP_ROUTE]} or span.name: #{span.name}" }
|
|
138
151
|
end
|
|
139
152
|
trans_name.to_s.slice(0, SolarWindsAPM::Constants::MAX_TXN_NAME_LENGTH)
|
|
140
153
|
end
|
|
@@ -163,14 +176,14 @@ module SolarWindsAPM
|
|
|
163
176
|
|
|
164
177
|
# This span from inbound HTTP request if from a SERVER by some http.method
|
|
165
178
|
def span_http?(span)
|
|
166
|
-
span.kind == ::OpenTelemetry::Trace::SpanKind::SERVER
|
|
179
|
+
(!span.attributes[HTTP_METHOD].nil? || !span.attributes[HTTP_REQUEST_METHOD].nil?) && span.kind == ::OpenTelemetry::Trace::SpanKind::SERVER
|
|
167
180
|
end
|
|
168
181
|
|
|
169
182
|
# Calculate HTTP status_code from span or default to UNAVAILABLE
|
|
170
183
|
# Something went wrong in OTel or instrumented service crashed early
|
|
171
184
|
# if no status_code in attributes of HTTP span
|
|
172
185
|
def get_http_status_code(span)
|
|
173
|
-
span.attributes[HTTP_STATUS_CODE] || INVALID_HTTP_STATUS_CODE
|
|
186
|
+
span.attributes[HTTP_RESPONSE_STATUS_CODE] || span.attributes[HTTP_STATUS_CODE] || INVALID_HTTP_STATUS_CODE
|
|
174
187
|
end
|
|
175
188
|
|
|
176
189
|
# check if it's entry span based on no parent or parent is remote
|
|
@@ -40,6 +40,9 @@ module SolarWindsAPM
|
|
|
40
40
|
context = ::OpenTelemetry::Context.new({}) if context.nil?
|
|
41
41
|
context = inject_extracted_header(carrier, context, getter, XTRACEOPTIONS_HEADER_NAME, INTL_SWO_X_OPTIONS_KEY)
|
|
42
42
|
inject_extracted_header(carrier, context, getter, XTRACEOPTIONS_SIGNATURE_HEADER_NAME, INTL_SWO_SIGNATURE_KEY)
|
|
43
|
+
rescue StandardError => e
|
|
44
|
+
SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] Extraction failed: #{e.message}" }
|
|
45
|
+
context || ::OpenTelemetry::Context.current
|
|
43
46
|
end
|
|
44
47
|
|
|
45
48
|
# Inject trace context into the supplied carrier.
|
|
@@ -80,10 +83,13 @@ module SolarWindsAPM
|
|
|
80
83
|
end
|
|
81
84
|
setter.set(carrier, TRACESTATE_HEADER_NAME, Utils.trace_state_header(trace_state))
|
|
82
85
|
end
|
|
86
|
+
rescue StandardError => e
|
|
87
|
+
SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] Injection failed: #{e.message}" }
|
|
83
88
|
end
|
|
84
89
|
|
|
85
|
-
# Returns the predefined propagation fields
|
|
86
|
-
# should delete the fields returned by
|
|
90
|
+
# Returns the predefined propagation fields, required by upstream.
|
|
91
|
+
# If your carrier is reused, you should delete the fields returned by
|
|
92
|
+
# this method before calling +inject+.
|
|
87
93
|
#
|
|
88
94
|
# @return [Array<String>] a list of fields that will be used by this propagator.
|
|
89
95
|
def fields
|
|
@@ -56,31 +56,21 @@ module SolarWindsAPM
|
|
|
56
56
|
xtraceoptions_response)
|
|
57
57
|
end
|
|
58
58
|
setter.set(carrier, HTTP_HEADER_ACCESS_CONTROL_EXPOSE_HEADERS, exposed_headers.join(','))
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
# Returns the predefined propagation fields. If your carrier is reused, you
|
|
62
|
-
# should delete the fields returned by this method before calling +inject+.
|
|
63
|
-
#
|
|
64
|
-
# @return [Array<String>] a list of fields that will be used by this propagator.
|
|
65
|
-
def fields
|
|
66
|
-
TRACESTATE_HEADER_NAME
|
|
59
|
+
rescue StandardError => e
|
|
60
|
+
SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] Injection failed: #{e.message}" }
|
|
67
61
|
end
|
|
68
62
|
|
|
69
63
|
private
|
|
70
64
|
|
|
71
|
-
#
|
|
65
|
+
# SW_XTRACEOPTIONS_RESPONSE_KEY -> xtrace_options_response
|
|
72
66
|
def recover_response_from_tracestate(span_context)
|
|
73
67
|
sanitized = span_context.tracestate.value(SW_XTRACEOPTIONS_RESPONSE_KEY)
|
|
74
68
|
sanitized = '' if sanitized.nil?
|
|
75
69
|
sanitized = sanitized.gsub(SolarWindsAPM::Constants::INTL_SWO_EQUALS_W3C_SANITIZED,
|
|
76
70
|
SolarWindsAPM::Constants::INTL_SWO_EQUALS)
|
|
77
71
|
sanitized = sanitized.gsub(':', SolarWindsAPM::Constants::INTL_SWO_EQUALS)
|
|
78
|
-
sanitized
|
|
79
|
-
|
|
80
|
-
SolarWindsAPM.logger.debug do
|
|
81
|
-
"[#{self.class}/#{__method__}] recover_response_from_tracestate sanitized: #{sanitized.inspect}"
|
|
82
|
-
end
|
|
83
|
-
sanitized
|
|
72
|
+
sanitized.gsub(SolarWindsAPM::Constants::INTL_SWO_COMMA_W3C_SANITIZED,
|
|
73
|
+
SolarWindsAPM::Constants::INTL_SWO_COMMA)
|
|
84
74
|
end
|
|
85
75
|
end
|
|
86
76
|
end
|
|
@@ -11,6 +11,9 @@ require 'opentelemetry-metrics-sdk'
|
|
|
11
11
|
require 'opentelemetry-exporter-otlp'
|
|
12
12
|
require 'opentelemetry-exporter-otlp-metrics'
|
|
13
13
|
require 'opentelemetry-instrumentation-all'
|
|
14
|
+
require 'opentelemetry-logs-sdk'
|
|
15
|
+
require 'opentelemetry-exporter-otlp-logs'
|
|
16
|
+
require 'opentelemetry-instrumentation-logger'
|
|
14
17
|
|
|
15
18
|
require_relative 'opentelemetry/solarwinds_propagator'
|
|
16
19
|
require_relative 'opentelemetry/solarwinds_response_propagator'
|
|
@@ -25,8 +25,6 @@ module SolarWindsAPM
|
|
|
25
25
|
|
|
26
26
|
is_lambda = SolarWindsAPM::Utils.determine_lambda
|
|
27
27
|
|
|
28
|
-
ENV['OTEL_TRACES_EXPORTER'] = ENV['OTEL_TRACES_EXPORTER'].to_s.split(',').tap { |e| e << 'otlp' unless e.include?('otlp') }.join(',')
|
|
29
|
-
|
|
30
28
|
# add response propagator to rack instrumentation
|
|
31
29
|
resolve_response_propagator
|
|
32
30
|
|
|
@@ -74,6 +72,9 @@ module SolarWindsAPM
|
|
|
74
72
|
ENV['OTEL_LOG_LEVEL'] = SolarWindsAPM::Config::SW_LOG_LEVEL_MAPPING.dig(log_level, :otel)
|
|
75
73
|
end
|
|
76
74
|
|
|
75
|
+
# disable log bridge by default
|
|
76
|
+
ENV['OTEL_RUBY_INSTRUMENTATION_LOGGER_ENABLED'] = 'false' unless %w[true false].include?(ENV['OTEL_RUBY_INSTRUMENTATION_LOGGER_ENABLED'].to_s)
|
|
77
|
+
|
|
77
78
|
::OpenTelemetry::SDK.configure do |c|
|
|
78
79
|
c.resource = final_attributes
|
|
79
80
|
c.use_all(@@config_map)
|
|
@@ -25,6 +25,7 @@ module SolarWindsAPM
|
|
|
25
25
|
@pid = nil
|
|
26
26
|
@thread = nil
|
|
27
27
|
|
|
28
|
+
@logger.debug { "[#{self.class}/#{__method__}] HttpSampler initialized: url=#{@url}, service=#{@service}, hostname=#{@hostname}, setting_url=#{@setting_url}" }
|
|
28
29
|
reset_on_fork
|
|
29
30
|
end
|
|
30
31
|
|
|
@@ -57,7 +58,6 @@ module SolarWindsAPM
|
|
|
57
58
|
def fetch_with_timeout(url, timeout_seconds = nil)
|
|
58
59
|
uri = url
|
|
59
60
|
timeout = timeout_seconds || REQUEST_TIMEOUT
|
|
60
|
-
|
|
61
61
|
deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + timeout
|
|
62
62
|
|
|
63
63
|
remaining = lambda {
|
|
@@ -94,26 +94,33 @@ module SolarWindsAPM
|
|
|
94
94
|
|
|
95
95
|
# a endless loop within a thread (non-blocking)
|
|
96
96
|
def settings_request
|
|
97
|
+
@logger.debug { "[#{self.class}/#{__method__}] Starting settings request loop" }
|
|
98
|
+
sleep_duration = GET_SETTING_DURATION
|
|
97
99
|
loop do
|
|
98
|
-
@logger.debug { "Retrieving sampling settings from #{@setting_url}" }
|
|
99
|
-
|
|
100
100
|
response = fetch_with_timeout(@setting_url)
|
|
101
|
-
parsed = response.nil? ? nil : JSON.parse(response.body)
|
|
102
101
|
|
|
103
|
-
|
|
102
|
+
# Check for nil response from timeout
|
|
103
|
+
unless response.is_a?(Net::HTTPSuccess)
|
|
104
|
+
@logger.warn { "[#{self.class}/#{__method__}] Failed to retrieve settings due to timeout." }
|
|
105
|
+
next
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
parsed = JSON.parse(response.body)
|
|
104
109
|
|
|
105
110
|
if update_settings(parsed)
|
|
106
111
|
# update the settings before the previous ones expire with some time to spare
|
|
107
112
|
expiry = (parsed['timestamp'].to_i + parsed['ttl'].to_i)
|
|
108
113
|
expiry_timeout = expiry - REQUEST_TIMEOUT - Time.now.to_i
|
|
109
|
-
|
|
114
|
+
sleep_duration = [0, expiry_timeout].max
|
|
110
115
|
else
|
|
111
|
-
@logger.warn {
|
|
112
|
-
sleep(GET_SETTING_DURATION)
|
|
116
|
+
@logger.warn { "[#{self.class}/#{__method__}] Retrieved sampling settings are invalid. Ensure proper configuration." }
|
|
113
117
|
end
|
|
118
|
+
rescue JSON::ParserError => e
|
|
119
|
+
@logger.warn { "[#{self.class}/#{__method__}] JSON parsing error: #{e.message}" }
|
|
114
120
|
rescue StandardError => e
|
|
115
|
-
@logger.warn { "Failed to retrieve sampling settings (#{e.message}), tracing will be disabled until valid ones are available." }
|
|
116
|
-
|
|
121
|
+
@logger.warn { "[#{self.class}/#{__method__}] Failed to retrieve sampling settings (#{e.message}), tracing will be disabled until valid ones are available." }
|
|
122
|
+
ensure
|
|
123
|
+
sleep(sleep_duration)
|
|
117
124
|
end
|
|
118
125
|
end
|
|
119
126
|
end
|
|
@@ -15,11 +15,12 @@ module SolarWindsAPM
|
|
|
15
15
|
|
|
16
16
|
@path = path || DEFAULT_PATH
|
|
17
17
|
@expiry = Time.now.to_i
|
|
18
|
+
@last_mtime = nil
|
|
19
|
+
@logger.debug { "[#{self.class}/#{__method__}] JsonSampler initialized: path=#{@path}, initial_expiry=#{@expiry}" }
|
|
18
20
|
loop_check
|
|
19
21
|
end
|
|
20
22
|
|
|
21
23
|
# only json sampler will need to check if the settings.json file
|
|
22
|
-
# updated or not from collector extention
|
|
23
24
|
def should_sample?(params)
|
|
24
25
|
loop_check
|
|
25
26
|
super
|
|
@@ -27,26 +28,40 @@ module SolarWindsAPM
|
|
|
27
28
|
|
|
28
29
|
private
|
|
29
30
|
|
|
31
|
+
# multi-thread is rare in lambda environment,
|
|
32
|
+
# here we don't use mutex to guard the execution
|
|
30
33
|
def loop_check
|
|
31
|
-
|
|
32
|
-
return if Time.now.to_i + 10 < @expiry
|
|
34
|
+
return if Time.now.to_i < @expiry - 10
|
|
33
35
|
|
|
36
|
+
# 1. Read and parse settings from the file.
|
|
34
37
|
begin
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
38
|
+
current_mtime = File.mtime(@path)
|
|
39
|
+
return if @last_mtime && current_mtime == @last_mtime
|
|
40
|
+
|
|
41
|
+
settings_data = JSON.parse(File.read(@path))
|
|
42
|
+
@last_mtime = current_mtime
|
|
43
|
+
rescue Errno::ENOENT
|
|
44
|
+
# File doesn't exist due to timing, missing collector, etc
|
|
45
|
+
@logger.error { "[#{self.class}##{__method__}] Settings file not found at #{@path}." }
|
|
46
|
+
return
|
|
47
|
+
rescue JSON::ParserError => e
|
|
48
|
+
@logger.error { "[#{self.class}##{__method__}] JSON parsing error in #{@path}: #{e.message}" }
|
|
39
49
|
return
|
|
40
50
|
end
|
|
41
51
|
|
|
42
|
-
|
|
43
|
-
|
|
52
|
+
# 2. Validate the structure of the parsed settings.
|
|
53
|
+
unless settings_data.is_a?(Array) && settings_data.length == 1
|
|
54
|
+
@logger.error { "[#{self.class}##{__method__}] Invalid settings file content: #{settings_data.inspect}" }
|
|
44
55
|
return
|
|
45
56
|
end
|
|
46
57
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
58
|
+
# 3. Attempt to update the settings.
|
|
59
|
+
if (new_settings = update_settings(settings_data.first))
|
|
60
|
+
@expiry = new_settings[:timestamp].to_i + new_settings[:ttl].to_i
|
|
61
|
+
@logger.debug { "[#{self.class}##{__method__}] Settings #{new_settings} updated successfully. New expiry: #{@expiry}" }
|
|
62
|
+
else
|
|
63
|
+
@logger.debug { "[#{self.class}##{__method__}] Settings update failed, keeping current expiry: #{@expiry}" }
|
|
64
|
+
end
|
|
50
65
|
end
|
|
51
66
|
end
|
|
52
67
|
end
|