Class: DeadBro::Configuration

Inherits:
Object
  • Object
show all
Defined in:
lib/dead_bro/configuration.rb

Constant Summary collapse

HEARTBEAT_INTERVAL =

seconds

60
METRICS_BACKEND_SKIP_AFTER_507_SECONDS =

10 minutes

600
DEPLOY_REVISION_ENV_KEYS =

First non-empty ENV value wins for release/revision payloads and deploy grouping on the server. Order is roughly: DeadBro-native → common CI/hosting → observability tooling.

%w[
  DEAD_BRO_DEPLOY_ID
  dead_bro_DEPLOY_ID
  GIT_REV
  GIT_COMMIT
  GIT_COMMIT_SHA
  GIT_SHA
  CODEBUILD_RESOLVED_SOURCE_REVISION
  HEROKU_SLUG_COMMIT
  RENDER_GIT_COMMIT
  DD_VERSION
  APP_REVISION
  RELEASE_VERSION
  SOURCE_VERSION
].freeze
REMOTE_SETTING_KEYS =
%w[
  enabled sample_rate memory_tracking_enabled allocation_tracking_enabled allocation_sample_rate
  explain_analyze_enabled slow_query_threshold_ms max_sql_queries_to_send max_logs_to_send
  excluded_controllers excluded_jobs exclusive_controllers exclusive_jobs
  monitor_enabled enable_db_stats enable_process_stats enable_system_stats
  sample_rates_by_type
].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeConfiguration

Returns a new instance of Configuration.



75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/dead_bro/configuration.rb', line 75

def initialize
  @api_key = nil
  @open_timeout = 1.0
  @read_timeout = 1.0
  @enabled = true
  @ruby_dev = false
  @circuit_breaker_enabled = true
  @circuit_breaker_failure_threshold = 3
  @circuit_breaker_recovery_timeout = 60
  @circuit_breaker_retry_timeout = 300
  @explicit_deploy_revision = nil
  @disk_paths = ["/"]
  @interfaces_ignore = %w[lo lo0 docker0]

  # Remote-managed defaults (used until backend sends real values)
  @sample_rate = 100
  @memory_tracking_enabled = true
  @allocation_tracking_enabled = false
  # When allocation tracking is on, the heavy per-request work (object-space
  # sampling, allocation-source tracing) runs on this % of requests so the
  # ~2-5ms overhead can be capped without turning the feature fully off.
  @allocation_sample_rate = 100
  @explain_analyze_enabled = false
  # Remote kill switch for EXPLAIN capture. Defaults to true so a local
  # opt-in works against older backends that never send the key; any
  # response that sends explain_analyze_enabled: false turns capture off.
  @remote_explain_analyze_enabled = true
  @slow_query_threshold_ms = 500
  @max_sql_queries_to_send = 500
  @max_logs_to_send = 100
  self.excluded_controllers = []
  self.excluded_jobs = []
  self.exclusive_controllers = []
  self.exclusive_jobs = []
  self.sample_rates_by_type = {}
  @monitor_enabled = false
  @enable_db_stats = false
  @enable_process_stats = false
  @enable_system_stats = false

  @settings_received_at = nil
  @skip_until = nil
  @last_heartbeat_at = nil
  @last_heartbeat_attempt_at = nil
  @settings_mutex = Mutex.new
end

Instance Attribute Details

#allocation_sample_rateObject

Remote-managed settings (overwritten by backend JSON settings on successful API responses)



19
20
21
# File 'lib/dead_bro/configuration.rb', line 19

def allocation_sample_rate
  @allocation_sample_rate
end

#allocation_tracking_enabledObject

Remote-managed settings (overwritten by backend JSON settings on successful API responses)



19
20
21
# File 'lib/dead_bro/configuration.rb', line 19

def allocation_tracking_enabled
  @allocation_tracking_enabled
end

#api_keyObject

Local-only settings (not overwritten by API settings payloads). Note: enabled may still be updated remotely via apply_remote_settings when the backend returns it in a response; local configure() values apply until the next remote update.



8
9
10
# File 'lib/dead_bro/configuration.rb', line 8

def api_key
  @api_key
end

#circuit_breaker_enabledObject

Local-only settings (not overwritten by API settings payloads). Note: enabled may still be updated remotely via apply_remote_settings when the backend returns it in a response; local configure() values apply until the next remote update.



8
9
10
# File 'lib/dead_bro/configuration.rb', line 8

def circuit_breaker_enabled
  @circuit_breaker_enabled
end

#circuit_breaker_failure_thresholdObject

Local-only settings (not overwritten by API settings payloads). Note: enabled may still be updated remotely via apply_remote_settings when the backend returns it in a response; local configure() values apply until the next remote update.



8
9
10
# File 'lib/dead_bro/configuration.rb', line 8

def circuit_breaker_failure_threshold
  @circuit_breaker_failure_threshold
end

#circuit_breaker_recovery_timeoutObject

Local-only settings (not overwritten by API settings payloads). Note: enabled may still be updated remotely via apply_remote_settings when the backend returns it in a response; local configure() values apply until the next remote update.



8
9
10
# File 'lib/dead_bro/configuration.rb', line 8

def circuit_breaker_recovery_timeout
  @circuit_breaker_recovery_timeout
end

#circuit_breaker_retry_timeoutObject

Local-only settings (not overwritten by API settings payloads). Note: enabled may still be updated remotely via apply_remote_settings when the backend returns it in a response; local configure() values apply until the next remote update.



8
9
10
# File 'lib/dead_bro/configuration.rb', line 8

def circuit_breaker_retry_timeout
  @circuit_breaker_retry_timeout
end

#disk_pathsObject

Local-only settings (not overwritten by API settings payloads). Note: enabled may still be updated remotely via apply_remote_settings when the backend returns it in a response; local configure() values apply until the next remote update.



8
9
10
# File 'lib/dead_bro/configuration.rb', line 8

def disk_paths
  @disk_paths
end

#enable_db_statsObject

Remote-managed settings (overwritten by backend JSON settings on successful API responses)



19
20
21
# File 'lib/dead_bro/configuration.rb', line 19

def enable_db_stats
  @enable_db_stats
end

#enable_process_statsObject

Remote-managed settings (overwritten by backend JSON settings on successful API responses)



19
20
21
# File 'lib/dead_bro/configuration.rb', line 19

def enable_process_stats
  @enable_process_stats
end

#enable_system_statsObject

Remote-managed settings (overwritten by backend JSON settings on successful API responses)



19
20
21
# File 'lib/dead_bro/configuration.rb', line 19

def enable_system_stats
  @enable_system_stats
end

#enabledObject

Local-only settings (not overwritten by API settings payloads). Note: enabled may still be updated remotely via apply_remote_settings when the backend returns it in a response; local configure() values apply until the next remote update.



8
9
10
# File 'lib/dead_bro/configuration.rb', line 8

def enabled
  @enabled
end

#excluded_controllersObject

Readers for exclusion lists. Writers are defined below so we can compile and cache the regex form once, instead of rebuilding it per request.



26
27
28
# File 'lib/dead_bro/configuration.rb', line 26

def excluded_controllers
  @excluded_controllers
end

#excluded_jobsObject

Readers for exclusion lists. Writers are defined below so we can compile and cache the regex form once, instead of rebuilding it per request.



26
27
28
# File 'lib/dead_bro/configuration.rb', line 26

def excluded_jobs
  @excluded_jobs
end

#exclusive_controllersObject

Readers for exclusion lists. Writers are defined below so we can compile and cache the regex form once, instead of rebuilding it per request.



26
27
28
# File 'lib/dead_bro/configuration.rb', line 26

def exclusive_controllers
  @exclusive_controllers
end

#exclusive_jobsObject

Readers for exclusion lists. Writers are defined below so we can compile and cache the regex form once, instead of rebuilding it per request.



26
27
28
# File 'lib/dead_bro/configuration.rb', line 26

def exclusive_jobs
  @exclusive_jobs
end

#explain_analyze_enabledObject

Local-only opt-in for EXPLAIN plan capture on slow queries. The gem runs DB statements (plan-only EXPLAIN) when this is on, so it must be enabled in the app's own config — remote settings can only turn it OFF, never on (see apply_remote_settings). Effective state: #explain_analyze_active?



16
17
18
# File 'lib/dead_bro/configuration.rb', line 16

def explain_analyze_enabled
  @explain_analyze_enabled
end

#interfaces_ignoreObject

Local-only settings (not overwritten by API settings payloads). Note: enabled may still be updated remotely via apply_remote_settings when the backend returns it in a response; local configure() values apply until the next remote update.



8
9
10
# File 'lib/dead_bro/configuration.rb', line 8

def interfaces_ignore
  @interfaces_ignore
end

#last_heartbeat_atObject

Last successful heartbeat HTTP response time while disabled (in-memory only)



40
41
42
# File 'lib/dead_bro/configuration.rb', line 40

def last_heartbeat_at
  @last_heartbeat_at
end

#last_heartbeat_attempt_atObject

Throttles heartbeat attempts to HEARTBEAT_INTERVAL (set when a heartbeat request is started)



43
44
45
# File 'lib/dead_bro/configuration.rb', line 43

def last_heartbeat_attempt_at
  @last_heartbeat_attempt_at
end

#max_logs_to_sendObject

Remote-managed settings (overwritten by backend JSON settings on successful API responses)



19
20
21
# File 'lib/dead_bro/configuration.rb', line 19

def max_logs_to_send
  @max_logs_to_send
end

#max_sql_queries_to_sendObject

Remote-managed settings (overwritten by backend JSON settings on successful API responses)



19
20
21
# File 'lib/dead_bro/configuration.rb', line 19

def max_sql_queries_to_send
  @max_sql_queries_to_send
end

#memory_tracking_enabledObject

Remote-managed settings (overwritten by backend JSON settings on successful API responses)



19
20
21
# File 'lib/dead_bro/configuration.rb', line 19

def memory_tracking_enabled
  @memory_tracking_enabled
end

#monitor_enabledObject

Remote-managed settings (overwritten by backend JSON settings on successful API responses)



19
20
21
# File 'lib/dead_bro/configuration.rb', line 19

def monitor_enabled
  @monitor_enabled
end

#open_timeoutObject

Local-only settings (not overwritten by API settings payloads). Note: enabled may still be updated remotely via apply_remote_settings when the backend returns it in a response; local configure() values apply until the next remote update.



8
9
10
# File 'lib/dead_bro/configuration.rb', line 8

def open_timeout
  @open_timeout
end

#read_timeoutObject

Local-only settings (not overwritten by API settings payloads). Note: enabled may still be updated remotely via apply_remote_settings when the backend returns it in a response; local configure() values apply until the next remote update.



8
9
10
# File 'lib/dead_bro/configuration.rb', line 8

def read_timeout
  @read_timeout
end

#ruby_devObject

Local-only settings (not overwritten by API settings payloads). Note: enabled may still be updated remotely via apply_remote_settings when the backend returns it in a response; local configure() values apply until the next remote update.



8
9
10
# File 'lib/dead_bro/configuration.rb', line 8

def ruby_dev
  @ruby_dev
end

#sample_rateObject

Remote-managed settings (overwritten by backend JSON settings on successful API responses)



19
20
21
# File 'lib/dead_bro/configuration.rb', line 19

def sample_rate
  @sample_rate
end

#sample_rates_by_typeObject

Per-request-type sample rate overrides, keyed by exact "Controller#action" (or "JobClass#perform") strings — same identity ApmRequestType uses server-side.



30
31
32
# File 'lib/dead_bro/configuration.rb', line 30

def sample_rates_by_type
  @sample_rates_by_type
end

#settings_received_atObject

Tracks when we last received settings from the backend (in-memory only)



33
34
35
# File 'lib/dead_bro/configuration.rb', line 33

def settings_received_at
  @settings_received_at
end

#skip_untilObject

After HTTP 507 Insufficient Storage from the API, skip all tracking until this UTC time (in-memory only). Cleared on the next successful API response.



37
38
39
# File 'lib/dead_bro/configuration.rb', line 37

def skip_until
  @skip_until
end

#slow_query_threshold_msObject

Remote-managed settings (overwritten by backend JSON settings on successful API responses)



19
20
21
# File 'lib/dead_bro/configuration.rb', line 19

def slow_query_threshold_ms
  @slow_query_threshold_ms
end

Instance Method Details

#allocation_tracking_active?Boolean

Per-request decision: should this request pay for the heavy allocation tracking (object-space sampling + allocation-source tracing)? Combines the on/off flag with allocation_sample_rate. Decide once at request start and reuse the cached result for the matching stop, so start/stop agree.

Returns:

  • (Boolean)


292
293
294
295
296
297
298
299
300
# File 'lib/dead_bro/configuration.rb', line 292

def allocation_tracking_active?
  return false unless allocation_tracking_enabled

  rate = allocation_sample_rate.to_i
  return true if rate >= 100
  return false if rate <= 0

  rand(1..100) <= rate
end

#apply_remote_settings(hash) ⇒ Object

Apply a settings hash received from the backend response. Only known keys are applied; unknown keys are silently ignored. Serialized so concurrent HTTP threads do not interleave writes with request-thread reads.



165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
# File 'lib/dead_bro/configuration.rb', line 165

def apply_remote_settings(hash)
  return unless hash.is_a?(Hash)

  @settings_mutex.synchronize do
    hash.each do |key, value|
      k = key.to_s
      next unless REMOTE_SETTING_KEYS.include?(k)

      case k
      when "sample_rate", "allocation_sample_rate", "slow_query_threshold_ms", "max_sql_queries_to_send", "max_logs_to_send"
        send(:"#{k}=", value.to_i)
      when "explain_analyze_enabled"
        # EXPLAIN runs statements against the customer DB, so the backend
        # must never be able to switch it on — only off. The local opt-in
        # (explain_analyze_enabled) stays untouched; see #explain_analyze_active?
        @remote_explain_analyze_enabled = !!value
      when "enabled", "memory_tracking_enabled", "allocation_tracking_enabled",
           "monitor_enabled", "enable_db_stats", "enable_process_stats", "enable_system_stats"
        send(:"#{k}=", !!value)
      when "excluded_controllers", "excluded_jobs", "exclusive_controllers", "exclusive_jobs"
        send(:"#{k}=", Array(value).map(&:to_s))
      when "sample_rates_by_type"
        send(:"#{k}=", value)
      end
    end
  end
end

#deploy_idObject

Current release revision sent as revision on all API payloads — same semantics as #resolve_deploy_id.



123
124
125
# File 'lib/dead_bro/configuration.rb', line 123

def deploy_id
  resolve_deploy_id
end

#deploy_id=(value) ⇒ Object

Overrides ENV-based resolution when set to a non-empty string (or clears override when nil/blank).



128
129
130
131
# File 'lib/dead_bro/configuration.rb', line 128

def deploy_id=(value)
  s = value&.respond_to?(:to_s) ? value.to_s.strip : ""
  @explicit_deploy_revision = s.empty? ? nil : s
end

#excluded_controller?(controller_name, action_name = nil) ⇒ Boolean

Returns:

  • (Boolean)


231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
# File 'lib/dead_bro/configuration.rb', line 231

def excluded_controller?(controller_name, action_name = nil)
  compiled = @compiled_excluded_controllers
  return false if compiled.nil? || compiled.empty?

  if action_name
    target = "#{controller_name}##{action_name}"
    compiled.each do |entry|
      if entry[:has_hash]
        return true if match_compiled?(target, entry)
      elsif match_compiled?(controller_name, entry)
        return true
      end
    end
    return false
  end

  compiled.each do |entry|
    next if entry[:has_hash]
    return true if match_compiled?(controller_name, entry)
  end
  false
end

#excluded_job?(job_class_name) ⇒ Boolean

Returns:

  • (Boolean)


254
255
256
257
258
# File 'lib/dead_bro/configuration.rb', line 254

def excluded_job?(job_class_name)
  compiled = @compiled_excluded_jobs
  return false if compiled.nil? || compiled.empty?
  compiled.any? { |entry| match_compiled?(job_class_name, entry) }
end

#exclusive_controller?(controller_name, action_name) ⇒ Boolean

Returns:

  • (Boolean)


266
267
268
269
270
271
# File 'lib/dead_bro/configuration.rb', line 266

def exclusive_controller?(controller_name, action_name)
  compiled = @compiled_exclusive_controllers
  return true if compiled.nil? || compiled.empty?
  target = "#{controller_name}##{action_name}"
  compiled.any? { |entry| match_compiled?(target, entry) }
end

#exclusive_job?(job_class_name) ⇒ Boolean

Returns:

  • (Boolean)


260
261
262
263
264
# File 'lib/dead_bro/configuration.rb', line 260

def exclusive_job?(job_class_name)
  compiled = @compiled_exclusive_jobs
  return true if compiled.nil? || compiled.empty?
  compiled.any? { |entry| match_compiled?(job_class_name, entry) }
end

#explain_analyze_active?Boolean

EXPLAIN capture requires BOTH the local opt-in and the remote flag — locally off means off no matter what the backend sends.

Returns:

  • (Boolean)


195
196
197
# File 'lib/dead_bro/configuration.rb', line 195

def explain_analyze_active?
  !!(@explain_analyze_enabled && @remote_explain_analyze_enabled)
end

#heartbeat_due?Boolean

Returns:

  • (Boolean)


199
200
201
202
# File 'lib/dead_bro/configuration.rb', line 199

def heartbeat_due?
  return false if api_key.nil?
  last_heartbeat_attempt_at.nil? || (Time.now.utc - last_heartbeat_attempt_at) >= HEARTBEAT_INTERVAL
end

#resolve_api_keyObject



307
308
309
310
311
# File 'lib/dead_bro/configuration.rb', line 307

def resolve_api_key
  return @api_key unless @api_key.nil?

  ENV["DEAD_BRO_API_KEY"]
end

#resolve_deploy_idObject



211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
# File 'lib/dead_bro/configuration.rb', line 211

def resolve_deploy_id
  explicit = @explicit_deploy_revision&.to_s&.strip
  return explicit unless explicit.nil? || explicit.empty?

  DEPLOY_REVISION_ENV_KEYS.each do |key|
    v = ENV[key]
    next unless v.respond_to?(:to_s)

    stripped = v.to_s.strip
    next if stripped.empty?

    return stripped
  end

  revision = read_revision_file
  return revision unless revision.nil? || revision.empty?

  DeadBro.process_deploy_id
end

#resolve_sample_rateObject

Returns the configured sample_rate only (no ENV fallback). Use DeadBro.configure or remote settings.



303
304
305
# File 'lib/dead_bro/configuration.rb', line 303

def resolve_sample_rate
  @sample_rate
end

#should_sample?(request_type_key = nil) ⇒ Boolean

Returns:

  • (Boolean)


273
274
275
276
277
278
279
280
281
282
283
284
285
286
# File 'lib/dead_bro/configuration.rb', line 273

def should_sample?(request_type_key = nil)
  sample_rate = if request_type_key && @sample_rates_by_type.key?(request_type_key.to_s)
    @sample_rates_by_type[request_type_key.to_s]
  else
    resolve_sample_rate
  end
  sample_rate = 100 if sample_rate.nil?

  return true if sample_rate >= 100
  return false if sample_rate <= 0

  # Generate random number 1-100 and check if it's within sample rate
  rand(1..100) <= sample_rate
end

#skip_tracking?Boolean

Returns:

  • (Boolean)


204
205
206
207
208
209
# File 'lib/dead_bro/configuration.rb', line 204

def skip_tracking?
  t = skip_until
  return false unless t

  Time.now.utc < t
end