Class: RailsErrorDashboard::Services::NotificationThrottler

Inherits:
Object
  • Object
show all
Defined in:
lib/rails_error_dashboard/services/notification_throttler.rb

Overview

Pure algorithm: Throttle error notifications to prevent alert fatigue

Checks severity minimum, per-error cooldown, and threshold milestones. Uses in-memory cache (same pattern as BaselineAlertThrottler). Thread-safe via Mutex. Fail-open: returns true on any error.

Constant Summary collapse

SEVERITY_RANK =

Severity levels ranked from lowest to highest

{ low: 0, medium: 1, high: 2, critical: 3 }.freeze

Class Method Summary collapse

Class Method Details

.cleanup!(max_age_hours: 24) ⇒ Object

Remove old entries to prevent memory growth

Parameters:

  • max_age_hours (Integer) (defaults to: 24)

    Remove entries older than this (default: 24)



80
81
82
83
84
85
86
# File 'lib/rails_error_dashboard/services/notification_throttler.rb', line 80

def cleanup!(max_age_hours: 24)
  cutoff_time = max_age_hours.hours.ago

  @mutex.synchronize do
    @last_notification_times.delete_if { |_, time| time < cutoff_time }
  end
end

.clear!Object

Clear all throttle state (for testing)



72
73
74
75
76
# File 'lib/rails_error_dashboard/services/notification_throttler.rb', line 72

def clear!
  @mutex.synchronize do
    @last_notification_times.clear
  end
end

.record_notification(error_log) ⇒ Object

Record that a notification was sent for this error

Parameters:

  • error_log (ErrorLog)

    The error that was notified about



61
62
63
64
65
66
67
68
69
# File 'lib/rails_error_dashboard/services/notification_throttler.rb', line 61

def record_notification(error_log)
  key = error_log.error_hash

  @mutex.synchronize do
    @last_notification_times[key] = Time.current
  end
rescue => e
  RailsErrorDashboard::Logger.debug("[RailsErrorDashboard] NotificationThrottler.record_notification failed: #{e.message}")
end

.severity_meets_minimum?(error_log) ⇒ Boolean

Does the error’s severity meet the configured minimum?

Parameters:

  • error_log (ErrorLog)

    The error to check

Returns:

  • (Boolean)

    true if severity is at or above minimum



35
36
37
38
39
40
41
42
43
44
# File 'lib/rails_error_dashboard/services/notification_throttler.rb', line 35

def severity_meets_minimum?(error_log)
  config = RailsErrorDashboard.configuration
  minimum = config.notification_minimum_severity || :low
  severity = SeverityClassifier.classify(error_log.error_type)

  (SEVERITY_RANK[severity] || 0) >= (SEVERITY_RANK[minimum] || 0)
rescue => e
  RailsErrorDashboard::Logger.debug("[RailsErrorDashboard] NotificationThrottler.severity_meets_minimum? failed: #{e.message}")
  true
end

.should_notify?(error_log) ⇒ Boolean

Should we send a notification for this error? Checks: severity minimum + cooldown period

Parameters:

  • error_log (ErrorLog)

    The error to check

Returns:

  • (Boolean)

    true if notification should be sent



22
23
24
25
26
27
28
29
30
# File 'lib/rails_error_dashboard/services/notification_throttler.rb', line 22

def should_notify?(error_log)
  return false unless severity_meets_minimum?(error_log)

  cooldown_ok?(error_log)
rescue => e
  # Fail-open: if throttler breaks, allow notification
  RailsErrorDashboard::Logger.debug("[RailsErrorDashboard] NotificationThrottler.should_notify? failed: #{e.message}")
  true
end

.threshold_reached?(error_log) ⇒ Boolean

Has the error’s occurrence count reached a configured threshold milestone?

Parameters:

  • error_log (ErrorLog)

    The error to check

Returns:

  • (Boolean)

    true if occurrence_count matches a threshold



49
50
51
52
53
54
55
56
57
# File 'lib/rails_error_dashboard/services/notification_throttler.rb', line 49

def threshold_reached?(error_log)
  thresholds = RailsErrorDashboard.configuration.notification_threshold_alerts
  return false if thresholds.nil? || thresholds.empty?

  thresholds.include?(error_log.occurrence_count)
rescue => e
  RailsErrorDashboard::Logger.debug("[RailsErrorDashboard] NotificationThrottler.threshold_reached? failed: #{e.message}")
  false
end