From 246c4f93de18b16256a95ce01da219633e84012f Mon Sep 17 00:00:00 2001 From: Dmitry Gubitskiy Date: Tue, 19 Jul 2016 00:40:56 +0300 Subject: [PATCH 1/3] Add the ability to specify min notification interval --- README.md | 11 ++++ lib/exception_notification/rack.rb | 8 ++- lib/exception_notifier.rb | 8 ++- lib/exception_notifier/modules/throttling.rb | 61 ++++++++++++++++++++ 4 files changed, 86 insertions(+), 2 deletions(-) create mode 100644 lib/exception_notifier/modules/throttling.rb diff --git a/README.md b/README.md index 4d35bca1..440d66ee 100644 --- a/README.md +++ b/README.md @@ -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* diff --git a/lib/exception_notification/rack.rb b/lib/exception_notification/rack.rb index 6ac907f6..fcd4abe7 100644 --- a/lib/exception_notification/rack.rb +++ b/lib/exception_notification/rack.rb @@ -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) diff --git a/lib/exception_notifier.rb b/lib/exception_notifier.rb index 5e403856..3cdabad4 100644 --- a/lib/exception_notifier.rb +++ b/lib/exception_notifier.rb @@ -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' @@ -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 diff --git a/lib/exception_notifier/modules/throttling.rb b/lib/exception_notifier/modules/throttling.rb new file mode 100644 index 00000000..bf3551e4 --- /dev/null +++ b/lib/exception_notifier/modules/throttling.rb @@ -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 From 15d2c241324bcfd51112927250a300a7c6cce478 Mon Sep 17 00:00:00 2001 From: Dmitry Gubitskiy Date: Fri, 26 Aug 2016 19:39:12 +0300 Subject: [PATCH 2/3] Depend on Rails >= 4.0 --- exception_notification.gemspec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/exception_notification.gemspec b/exception_notification.gemspec index 1b2bb77a..367b2126 100644 --- a/exception_notification.gemspec +++ b/exception_notification.gemspec @@ -15,8 +15,8 @@ Gem::Specification.new do |s| s.test_files = `git ls-files -- test`.split("\n") s.require_path = 'lib' - s.add_dependency("actionmailer", "~> 4.0") - s.add_dependency("activesupport", "~> 4.0") + s.add_dependency("actionmailer", ">= 4.0") + s.add_dependency("activesupport", ">= 4.0") s.add_development_dependency "rails", "~> 4.0" s.add_development_dependency "resque", "~> 1.2.0" From 03bd908854e176dc212b6e8493802691efbbe468 Mon Sep 17 00:00:00 2001 From: Dmitry Gubitskiy Date: Fri, 26 Aug 2016 19:44:28 +0300 Subject: [PATCH 3/3] Revert "Depend on Rails >= 4.0" This reverts commit 15d2c241324bcfd51112927250a300a7c6cce478. --- exception_notification.gemspec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/exception_notification.gemspec b/exception_notification.gemspec index 367b2126..1b2bb77a 100644 --- a/exception_notification.gemspec +++ b/exception_notification.gemspec @@ -15,8 +15,8 @@ Gem::Specification.new do |s| s.test_files = `git ls-files -- test`.split("\n") s.require_path = 'lib' - s.add_dependency("actionmailer", ">= 4.0") - s.add_dependency("activesupport", ">= 4.0") + s.add_dependency("actionmailer", "~> 4.0") + s.add_dependency("activesupport", "~> 4.0") s.add_development_dependency "rails", "~> 4.0" s.add_development_dependency "resque", "~> 1.2.0"