Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add the ability to specify min notification interval #356

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -855,6 +855,17 @@ Rails.application.config.middleware.use ExceptionNotification::Rack,
}
```

### :min_notification_interval

*Something that responds to `call` or `to_i`*

Ignores repeated exceptions which happen in a set interval in seconds. Values of `0` or `nil` means do not ignore repeated exceptions. Only works if Rails cache is available.

```ruby
Rails.application.config.middleware.use ExceptionNotification::Rack,
:min_notification_interval = 600 # At most one exception every 10 minutes
```

### :ignore_if

*Lambda, default: nil*
Expand Down
8 changes: 7 additions & 1 deletion lib/exception_notification/rack.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,13 @@ class CascadePassException < Exception; end
def initialize(app, options = {})
@app = app

ExceptionNotifier.ignored_exceptions = options.delete(:ignore_exceptions) if options.key?(:ignore_exceptions)
if options.key?(:ignore_exceptions)
ExceptionNotifier.ignored_exceptions = options.delete(:ignore_exceptions)
end

if options.key?(:min_notification_interval)
ExceptionNotifier.min_notification_interval = options.delete(:min_notification_interval)
end

if options.key?(:ignore_if)
rack_ignore = options.delete(:ignore_if)
Expand Down
8 changes: 7 additions & 1 deletion lib/exception_notifier.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
module ExceptionNotifier

autoload :BacktraceCleaner, 'exception_notifier/modules/backtrace_cleaner'
autoload :Throttling, 'exception_notifier/modules/throttling'

autoload :Notifier, 'exception_notifier/notifier'
autoload :EmailNotifier, 'exception_notifier/email_notifier'
Expand Down Expand Up @@ -86,9 +87,14 @@ def clear_ignore_conditions!
@@ignores.clear
end

def min_notification_interval=(interval)
Throttling.min_notification_interval = interval
end

private
def ignored?(exception, options)
@@ignores.any?{ |condition| condition.call(exception, options) }
ignored = @@ignores.any? { |condition| condition.call(exception, options) }
ignored || Throttling.ignore_exception?(exception)
rescue Exception => e
raise e if @@testing_mode

Expand Down
61 changes: 61 additions & 0 deletions lib/exception_notifier/modules/throttling.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
require 'digest/sha1'

module ExceptionNotifier::Throttling

@min_notification_interval = 0

class << self

def ignore_exception?(exception)
return false unless rails_cache_available?

interval = min_notification_interval_for(exception)
return false if interval <= 0

key = ['exception-notification-throttling-', exception_signature(exception)].join('-')

if Rails.cache.read(key)
true
else
Rails.cache.write(key, true, expires_in: interval)
false
end
end

def min_notification_interval=(interval)
@min_notification_interval = normalize_min_notification_interval(interval)
end

private

attr_reader :min_notification_interval

def rails_cache_available?
defined?(Rails) && Rails.respond_to?(:cache)
end

def exception_signature(exception)
signature = [exception.class, exception.message, exception.backtrace].join('')
Digest::SHA1.hexdigest(signature)
end

def normalize_min_notification_interval(interval)
return interval if interval.respond_to?(:call)

begin
interval.to_i
rescue
raise "Invalid min notification interval: #{interval.inspect}"
end
end

def min_notification_interval_for(exception)
interval = min_notification_interval
if interval.respond_to?(:call)
normalize_min_notification_interval(interval.call(exception))
else
interval
end
end
end
end