Hello community,
here is the log from the commit of package rubygem-exception_notification for openSUSE:Factory checked in at 2017-09-04 12:36:54
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/rubygem-exception_notification (Old)
and /work/SRC/openSUSE:Factory/.rubygem-exception_notification.new (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "rubygem-exception_notification"
Mon Sep 4 12:36:54 2017 rev:17 rq:520442 version:4.2.2
Changes:
--------
--- /work/SRC/openSUSE:Factory/rubygem-exception_notification/rubygem-exception_notification.changes 2016-07-30 00:27:30.000000000 +0200
+++ /work/SRC/openSUSE:Factory/.rubygem-exception_notification.new/rubygem-exception_notification.changes 2017-09-04 12:36:55.882372182 +0200
@@ -1,0 +2,13 @@
+Sun Sep 3 09:00:32 UTC 2017 - coolo@suse.com
+
+- updated to version 4.2.2
+ see installed CHANGELOG.rdoc
+
+ == 4.2.2
+
+ * enhancements
+ * Error groupiong (by @Martin91)
+ * Additional fields for Slack support (by @schurig)
+ * Enterprise HipChat support (by @seanhuber)
+
+-------------------------------------------------------------------
Old:
----
exception_notification-4.2.1.gem
New:
----
exception_notification-4.2.2.gem
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ rubygem-exception_notification.spec ++++++
--- /var/tmp/diff_new_pack.ORpWOh/_old 2017-09-04 12:36:56.786245015 +0200
+++ /var/tmp/diff_new_pack.ORpWOh/_new 2017-09-04 12:36:56.786245015 +0200
@@ -1,7 +1,7 @@
#
# spec file for package rubygem-exception_notification
#
-# Copyright (c) 2016 SUSE LINUX GmbH, Nuernberg, Germany.
+# Copyright (c) 2017 SUSE LINUX GmbH, Nuernberg, Germany.
#
# All modifications and additions to the file contributed by third parties
# remain the property of their copyright owners, unless otherwise agreed
@@ -24,7 +24,7 @@
#
Name: rubygem-exception_notification
-Version: 4.2.1
+Version: 4.2.2
Release: 0
%define mod_name exception_notification
%define mod_full_name %{mod_name}-%{version}
++++++ exception_notification-4.2.1.gem -> exception_notification-4.2.2.gem ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/CHANGELOG.rdoc new/CHANGELOG.rdoc
--- old/CHANGELOG.rdoc 2016-07-18 01:26:10.000000000 +0200
+++ new/CHANGELOG.rdoc 2017-08-12 22:52:29.000000000 +0200
@@ -1,3 +1,10 @@
+== 4.2.2
+
+* enhancements
+ * Error groupiong (by @Martin91)
+ * Additional fields for Slack support (by @schurig)
+ * Enterprise HipChat support (by @seanhuber)
+
== 4.2.1
* enhancements
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/CONTRIBUTING.md new/CONTRIBUTING.md
--- old/CONTRIBUTING.md 2016-07-18 01:26:10.000000000 +0200
+++ new/CONTRIBUTING.md 2017-08-12 22:52:29.000000000 +0200
@@ -30,8 +30,9 @@
bundle
cd test/dummy
bundle
- bundle exec rake db:reset
- bundle exec rake db:test:prepare
+ bundle exec rake db:reset db:test:prepare
+ cd ../..
+ bundle exec rake test
```
* Create a topic branch from where you want to base your work.
* Add a test for your change. Only refactoring and documentation changes
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/README.md new/README.md
--- old/README.md 2016-07-18 01:26:10.000000000 +0200
+++ new/README.md 2017-08-12 22:52:29.000000000 +0200
@@ -82,7 +82,7 @@
## Notifiers
-ExceptionNotification relies on notifiers to deliver notifications when errors occur in your applications. By default, six notifiers are available:
+ExceptionNotification relies on notifiers to deliver notifications when errors occur in your applications. By default, 7 notifiers are available:
* [Campfire notifier](#campfire-notifier)
* [Email notifier](#email-notifier)
@@ -279,6 +279,12 @@
If enabled, remove numbers from subject so they thread as a single one. Use `:normalize_subject => true` to enable it.
+##### include_controller_and_action_names_in_subject
+
+*Boolean, default: true*
+
+If enabled, include the controller and action names in the subject. Use `:include_controller_and_action_names_in_subject => false` to exclude them.
+
##### email_format
*Symbol, default: :text*
@@ -393,6 +399,12 @@
Message will appear from this nickname. Default : 'Exception'.
+##### server_url
+
+*String, optional*
+
+Custom Server URL for self-hosted, Enterprise HipChat Server
+
For all options & possible values see [Hipchat API](https://www.hipchat.com/docs/api/method/rooms/message).
### IRC notifier
@@ -590,6 +602,12 @@
Contains additional payload for a message (e.g avatar, attachments, etc). See [slack-notifier](https://github.com/stevenosloan/slack-notifier#additional-parameters) for more information.. Default: '{}'
+##### additional_fields
+
+*Array of Hashes, optional*
+
+Contains additional fields that will be added to the attachement. See [Slack documentation](https://api.slack.com/docs/message-attachments).
+
## Mattermost notifier
Post notification in a mattermost channel via [incoming webhook](http://docs.mattermost.com/developer/webhooks-incoming.html)
@@ -600,7 +618,7 @@
gem 'httparty'
```
-To configure it, you **need** to set the `webhook_url` option.
+To configure it, you **need** to set the `webhook_url` option.
You can also specify an other channel with `channel` option.
```ruby
@@ -616,7 +634,7 @@
}
```
-If you are using Github or Gitlab for issues tracking, you can specify `git_url` as follow to add a *Create issue* link in you notification.
+If you are using Github or Gitlab for issues tracking, you can specify `git_url` as follow to add a *Create issue* link in you notification.
By default this will use your Rails application name to match the git repository. If yours differ you can specify `app_name`.
@@ -810,6 +828,33 @@
}
```
+## Error Grouping
+In general, exception notification will send every notification when an error occured, which may result in a problem: if your site has a high throughput and an same error raised frequently, you will receive too many notifications during a short period time, your mail box may be full of thousands of exception mails or even your mail server will be slow. To prevent this, you can choose to error errors by using `:error_grouping` option and set it to `true`.
+
+Error grouping has a default formula `log2(errors_count)` to determine if it is needed to send the notification based on the accumulated errors count for specified exception, this makes the notifier only send notification when count is: 1, 2, 4, 8, 16, 32, 64, 128, ... (2**n). You can use `:notification_trigger` to override this default formula.
+
+The below shows options used to enable error grouping:
+
+```ruby
+Rails.application.config.middleware.use ExceptionNotification::Rack,
+ :ignore_exceptions => ['ActionView::TemplateError'] + ExceptionNotifier.ignored_exceptions,
+ :email => {
+ :email_prefix => "[PREFIX] ",
+ :sender_address => %{"notifier" },
+ :exception_recipients => %w{exceptions@example.com}
+ },
+ :error_grouping => true,
+ # :error_grouping_period => 5.minutes, # the time before an error is regarded as fixed
+ # :error_grouping_cache => Rails.cache, # for other applications such as Sinatra, use one instance of ActiveSupport::Cache::Store
+ #
+ # notification_trigger: specify a callback to determine when a notification should be sent,
+ # the callback will be invoked with two arguments:
+ # exception: the exception raised
+ # count: accumulated errors count for this exception
+ #
+ # :notification_trigger => lambda { |exception, count| count % 10 == 0 }
+```
+
## Ignore Exceptions
You can choose to ignore certain exceptions, which will make ExceptionNotification avoid sending notifications for those specified. There are three ways of specifying which exceptions to ignore:
Binary files old/checksums.yaml.gz and new/checksums.yaml.gz differ
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/exception_notification.gemspec new/exception_notification.gemspec
--- old/exception_notification.gemspec 2016-07-18 01:26:10.000000000 +0200
+++ new/exception_notification.gemspec 2017-08-12 22:52:29.000000000 +0200
@@ -1,8 +1,8 @@
Gem::Specification.new do |s|
s.name = 'exception_notification'
- s.version = '4.2.1'
+ s.version = '4.2.2'
s.authors = ["Jamis Buck", "Josh Peek"]
- s.date = %q{2016-07-17}
+ s.date = %q{2017-08-12}
s.summary = "Exception notification for Rails apps"
s.homepage = "https://smartinez87.github.io/exception_notification/"
s.email = "smartinez87@gmail.com"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/exception_notification/rack.rb new/lib/exception_notification/rack.rb
--- old/lib/exception_notification/rack.rb 2016-07-18 01:26:10.000000000 +0200
+++ new/lib/exception_notification/rack.rb 2017-08-12 22:52:29.000000000 +0200
@@ -6,6 +6,15 @@
@app = app
ExceptionNotifier.ignored_exceptions = options.delete(:ignore_exceptions) if options.key?(:ignore_exceptions)
+ ExceptionNotifier.error_grouping = options.delete(:error_grouping) if options.key?(:error_grouping)
+ ExceptionNotifier.error_grouping_period = options.delete(:error_grouping_period) if options.key?(:error_grouping_period)
+ ExceptionNotifier.notification_trigger = options.delete(:notification_trigger) if options.key?(:notification_trigger)
+
+ if options.key?(:error_grouping_cache)
+ ExceptionNotifier.error_grouping_cache = options.delete(:error_grouping_cache)
+ elsif defined?(Rails)
+ ExceptionNotifier.error_grouping_cache = Rails.cache
+ end
if options.key?(:ignore_if)
rack_ignore = options.delete(:ignore_if)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/exception_notification/rails.rb new/lib/exception_notification/rails.rb
--- old/lib/exception_notification/rails.rb 2016-07-18 01:26:10.000000000 +0200
+++ new/lib/exception_notification/rails.rb 2017-08-12 22:52:29.000000000 +0200
@@ -2,6 +2,7 @@
class Engine < ::Rails::Engine
config.exception_notification = ExceptionNotifier
config.exception_notification.logger = Rails.logger
+ config.exception_notification.error_grouping_cache = Rails.cache
config.app_middleware.use ExceptionNotification::Rack
end
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/exception_notifier/campfire_notifier.rb new/lib/exception_notifier/campfire_notifier.rb
--- old/lib/exception_notifier/campfire_notifier.rb 2016-07-18 01:26:10.000000000 +0200
+++ new/lib/exception_notifier/campfire_notifier.rb 2017-08-12 22:52:29.000000000 +0200
@@ -19,7 +19,11 @@
def call(exception, options={})
if active?
- message = "A new exception occurred: '#{exception.message}'"
+ message = if options[:accumulated_errors_count].to_i > 1
+ "The exception occurred #{options[:accumulated_errors_count]} times: '#{exception.message}'"
+ else
+ "A new exception occurred: '#{exception.message}'"
+ end
message += " on '#{exception.backtrace.first}'" if exception.backtrace
send_notice(exception, options, message) do |msg, _|
@room.paste msg
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/exception_notifier/email_notifier.rb new/lib/exception_notifier/email_notifier.rb
--- old/lib/exception_notifier/email_notifier.rb 2016-07-18 01:26:10.000000000 +0200
+++ new/lib/exception_notifier/email_notifier.rb 2017-08-12 22:52:29.000000000 +0200
@@ -9,8 +9,8 @@
attr_accessor(:sender_address, :exception_recipients,
:pre_callback, :post_callback,
:email_prefix, :email_format, :sections, :background_sections,
- :verbose_subject, :normalize_subject, :delivery_method, :mailer_settings,
- :email_headers, :mailer_parent, :template_path, :deliver_with)
+ :verbose_subject, :normalize_subject, :include_controller_and_action_names_in_subject,
+ :delivery_method, :mailer_settings, :email_headers, :mailer_parent, :template_path, :deliver_with)
module Mailer
class MissingController
@@ -46,7 +46,7 @@
load_custom_views
@exception = exception
- @options = options.reverse_merge(default_options)
+ @options = options.reverse_merge(default_options).symbolize_keys
@backtrace = exception.backtrace || []
@timestamp = Time.current
@sections = @options[:background_sections]
@@ -60,7 +60,8 @@
def compose_subject
subject = "#{@options[:email_prefix]}"
- subject << "#{@kontroller.controller_name}##{@kontroller.action_name}" if @kontroller
+ subject << "(#{@options[:accumulated_errors_count]} times) " if @options[:accumulated_errors_count].to_i > 1
+ subject << "#{@kontroller.controller_name}##{@kontroller.action_name}" if @kontroller && @options[:include_controller_and_action_names_in_subject]
subject << " (#{@exception.class})"
subject << " #{@exception.message.inspect}" if @options[:verbose_subject]
subject = EmailNotifier.normalize_digits(subject) if @options[:normalize_subject]
@@ -74,13 +75,17 @@
end
helper_method :inspect_object
-
+
+ def truncate(string, max)
+ string.length > max ? "#{string[0...max]}..." : string
+ end
+
def inspect_object(object)
case object
when Hash, Array
- object.inspect
+ truncate(object.inspect, 300)
else
- object.to_s
+ object.to_s
end
end
@@ -138,10 +143,10 @@
options[:mailer_settings] = options.delete(mailer_settings_key)
options.reverse_merge(EmailNotifier.default_options).select{|k,v|[
- :sender_address, :exception_recipients,
- :pre_callback, :post_callback,
- :email_prefix, :email_format, :sections, :background_sections,
- :verbose_subject, :normalize_subject, :delivery_method, :mailer_settings,
+ :sender_address, :exception_recipients, :pre_callback,
+ :post_callback, :email_prefix, :email_format,
+ :sections, :background_sections, :verbose_subject, :normalize_subject,
+ :include_controller_and_action_names_in_subject, :delivery_method, :mailer_settings,
:email_headers, :mailer_parent, :template_path, :deliver_with].include?(k)}.each{|k,v| send("#{k}=", v)}
end
@@ -197,6 +202,7 @@
:background_sections => %w(backtrace data),
:verbose_subject => true,
:normalize_subject => false,
+ :include_controller_and_action_names_in_subject => true,
:delivery_method => nil,
:mailer_settings => nil,
:email_headers => {},
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/exception_notifier/hipchat_notifier.rb new/lib/exception_notifier/hipchat_notifier.rb
--- old/lib/exception_notifier/hipchat_notifier.rb 2016-07-18 01:26:10.000000000 +0200
+++ new/lib/exception_notifier/hipchat_notifier.rb 2017-08-12 22:52:29.000000000 +0200
@@ -13,10 +13,15 @@
opts = {
:api_version => options.delete(:api_version) || 'v1'
}
+ opts[:server_url] = options.delete(:server_url) if options[:server_url]
@from = options.delete(:from) || 'Exception'
@room = HipChat::Client.new(api_token, opts)[room_name]
- @message_template = options.delete(:message_template) || ->(exception) {
- msg = "A new exception occurred: '#{Rack::Utils.escape_html(exception.message)}'"
+ @message_template = options.delete(:message_template) || ->(exception, errors_count) {
+ msg = if errors_count > 1
+ "The exception occurred #{errors_count} times: '#{Rack::Utils.escape_html(exception.message)}'"
+ else
+ "A new exception occurred: '#{Rack::Utils.escape_html(exception.message)}'"
+ end
msg += " on '#{exception.backtrace.first}'" if exception.backtrace
msg
}
@@ -30,7 +35,7 @@
def call(exception, options={})
return if !active?
- message = @message_template.call(exception)
+ message = @message_template.call(exception, options[:accumulated_errors_count].to_i)
send_notice(exception, options, message, @message_options) do |msg, message_opts|
@room.send(@from, msg, message_opts)
end
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/exception_notifier/irc_notifier.rb new/lib/exception_notifier/irc_notifier.rb
--- old/lib/exception_notifier/irc_notifier.rb 2016-07-18 01:26:10.000000000 +0200
+++ new/lib/exception_notifier/irc_notifier.rb 2017-08-12 22:52:29.000000000 +0200
@@ -7,7 +7,11 @@
end
def call(exception, options={})
+ errors_count = options[:accumulated_errors_count].to_i
+
message = "'#{exception.message}'"
+ message.prepend("(#{errors_count} times)") if errors_count > 1
+
message += " on '#{exception.backtrace.first}'" if exception.backtrace
if active?
send_notice(exception, options, message) do |msg, _|
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/exception_notifier/mattermost_notifier.rb new/lib/exception_notifier/mattermost_notifier.rb
--- old/lib/exception_notifier/mattermost_notifier.rb 2016-07-18 01:26:10.000000000 +0200
+++ new/lib/exception_notifier/mattermost_notifier.rb 2017-08-12 22:52:29.000000000 +0200
@@ -91,8 +91,9 @@
def message_header
text = []
+ errors_count = @options[:accumulated_errors_count].to_i
text << "### :warning: Error 500 in #{Rails.env} :warning:"
- text << "An *#{@exception.class}* occured" + if @controller then " in *#{controller_and_method}*." else "." end
+ text << "#{errors_count > 1 ? errors_count : 'An'} *#{@exception.class}* occured" + if @controller then " in *#{controller_and_method}*." else "." end
text << "*#{@exception.message}*"
text
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/exception_notifier/modules/error_grouping.rb new/lib/exception_notifier/modules/error_grouping.rb
--- old/lib/exception_notifier/modules/error_grouping.rb 1970-01-01 01:00:00.000000000 +0100
+++ new/lib/exception_notifier/modules/error_grouping.rb 2017-08-12 22:52:29.000000000 +0200
@@ -0,0 +1,77 @@
+require 'active_support/core_ext/numeric/time'
+require 'active_support/concern'
+
+module ExceptionNotifier
+ module ErrorGrouping
+ extend ActiveSupport::Concern
+
+ included do
+ mattr_accessor :error_grouping
+ self.error_grouping = false
+
+ mattr_accessor :error_grouping_period
+ self.error_grouping_period = 5.minutes
+
+ mattr_accessor :notification_trigger
+
+ mattr_accessor :error_grouping_cache
+ end
+
+ module ClassMethods
+ # Fallback to the memory store while the specified cache store doesn't work
+ #
+ def fallback_cache_store
+ @fallback_cache_store ||= ActiveSupport::Cache::MemoryStore.new
+ end
+
+ def error_count(error_key)
+ count = begin
+ error_grouping_cache.read(error_key)
+ rescue => e
+ ExceptionNotifier.logger.warn("#{error_grouping_cache.inspect} failed to read, reason: #{e.message}. Falling back to memory cache store.")
+ fallback_cache_store.read(error_key)
+ end
+
+ count.to_i if count
+ end
+
+ def save_error_count(error_key, count)
+ error_grouping_cache.write(error_key, count, expires_in: error_grouping_period)
+ rescue => e
+ ExceptionNotifier.logger.warn("#{error_grouping_cache.inspect} failed to write, reason: #{e.message}. Falling back to memory cache store.")
+ fallback_cache_store.write(error_key, count, expires_in: error_grouping_period)
+ end
+
+ def group_error!(exception, options)
+ message_based_key = "exception:#{Zlib.crc32("#{exception.class.name}\nmessage:#{exception.message}")}"
+ accumulated_errors_count = 1
+
+ if count = error_count(message_based_key)
+ accumulated_errors_count = count + 1
+ save_error_count(message_based_key, accumulated_errors_count)
+ else
+ backtrace_based_key = "exception:#{Zlib.crc32("#{exception.class.name}\npath:#{exception.backtrace.try(:first)}")}"
+
+ if count = Rails.cache.read(backtrace_based_key)
+ accumulated_errors_count = count + 1
+ save_error_count(backtrace_based_key, accumulated_errors_count)
+ else
+ save_error_count(backtrace_based_key, accumulated_errors_count)
+ save_error_count(message_based_key, accumulated_errors_count)
+ end
+ end
+
+ options[:accumulated_errors_count] = accumulated_errors_count
+ end
+
+ def send_notification?(exception, count)
+ if notification_trigger.respond_to?(:call)
+ notification_trigger.call(exception, count)
+ else
+ factor = Math.log2(count)
+ factor.to_i == factor
+ end
+ end
+ end
+ end
+end
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/exception_notifier/slack_notifier.rb new/lib/exception_notifier/slack_notifier.rb
--- old/lib/exception_notifier/slack_notifier.rb 2016-07-18 01:26:10.000000000 +0200
+++ new/lib/exception_notifier/slack_notifier.rb 2017-08-12 22:52:29.000000000 +0200
@@ -8,10 +8,12 @@
super
begin
@ignore_data_if = options[:ignore_data_if]
- @backtrace_lines = options[:backtrace_lines]
+ @backtrace_lines = options.fetch(:backtrace_lines, 10)
+ @additional_fields = options[:additional_fields]
webhook_url = options.fetch(:webhook_url)
@message_opts = options.fetch(:additional_parameters, {})
+ @color = @message_opts.delete(:color) { 'danger' }
@notifier = Slack::Notifier.new webhook_url, options
rescue
@notifier = nil
@@ -19,7 +21,9 @@
end
def call(exception, options={})
- exception_name = "*#{exception.class.to_s =~ /^[aeiou]/i ? 'An' : 'A'}* `#{exception.class.to_s}`"
+ errors_count = options[:accumulated_errors_count].to_i
+ measure_word = errors_count > 1 ? errors_count : (exception.class.to_s =~ /^[aeiou]/i ? 'An' : 'A')
+ exception_name = "*#{measure_word}* `#{exception.class.to_s}`"
if options[:env].nil?
data = options[:data] || {}
@@ -30,18 +34,18 @@
kontroller = env['action_controller.instance']
request = "#{env['REQUEST_METHOD']} <#{env['REQUEST_URI']}>"
- text = "#{exception_name} *occurred while* `#{env['REQUEST_METHOD']} <#{env['REQUEST_URI']}>`"
+ text = "#{exception_name} *occurred while* `#{request}`"
text += " *was processed by* `#{kontroller.controller_name}##{kontroller.action_name}`" if kontroller
text += "\n"
end
clean_message = exception.message.gsub("`", "'")
- fields = [ { title: 'Exception', value: clean_message} ]
+ fields = [ { title: 'Exception', value: clean_message } ]
fields.push({ title: 'Hostname', value: Socket.gethostname })
if exception.backtrace
- formatted_backtrace = @backtrace_lines ? "```#{exception.backtrace.first(@backtrace_lines).join("\n")}```" : "```#{exception.backtrace.join("\n")}```"
+ formatted_backtrace = "```#{exception.backtrace.first(@backtrace_lines).join("\n")}```"
fields.push({ title: 'Backtrace', value: formatted_backtrace })
end
@@ -51,7 +55,9 @@
fields.push({ title: 'Data', value: "```#{data_string}```" })
end
- attchs = [color: 'danger', text: text, fields: fields, mrkdwn_in: %w(text fields)]
+ fields.concat(@additional_fields) if @additional_fields
+
+ attchs = [color: @color, text: text, fields: fields, mrkdwn_in: %w(text fields)]
if valid?
send_notice(exception, options, clean_message, @message_opts.merge(attachments: attchs)) do |msg, message_opts|
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/exception_notifier/views/exception_notifier/exception_notification.html.erb new/lib/exception_notifier/views/exception_notifier/exception_notification.html.erb
--- old/lib/exception_notifier/views/exception_notifier/exception_notification.html.erb 2016-07-18 01:26:10.000000000 +0200
+++ new/lib/exception_notifier/views/exception_notifier/exception_notification.html.erb 2017-08-12 22:52:29.000000000 +0200
@@ -14,11 +14,9 @@
title = render("title", :title => section).strip
[title, summary]
end
-
rescue Exception => e
title = render("title", :title => section).strip
summary = ["ERROR: Failed to generate exception summary:", [e.class.to_s, e.message].join(": "), e.backtrace && e.backtrace.join("\n")].compact.join("\n\n")
-
[title, summary]
end
end
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/exception_notifier.rb new/lib/exception_notifier.rb
--- old/lib/exception_notifier.rb 2016-07-18 01:26:10.000000000 +0200
+++ new/lib/exception_notifier.rb 2017-08-12 22:52:29.000000000 +0200
@@ -2,8 +2,10 @@
require 'active_support/core_ext/string/inflections'
require 'active_support/core_ext/module/attribute_accessors'
require 'exception_notifier/base_notifier'
+require 'exception_notifier/modules/error_grouping'
module ExceptionNotifier
+ include ErrorGrouping
autoload :BacktraceCleaner, 'exception_notifier/modules/backtrace_cleaner'
@@ -43,6 +45,11 @@
def notify_exception(exception, options={})
return false if ignored_exception?(options[:ignore_exceptions], exception)
return false if ignored?(exception, options)
+ if error_grouping
+ errors_count = group_error!(exception, options)
+ return false unless send_notification?(exception, errors_count)
+ end
+
selected_notifiers = options.delete(:notifiers) || notifiers
[*selected_notifiers].each do |notifier|
fire_notification(notifier, exception, options.dup)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/metadata new/metadata
--- old/metadata 2016-07-18 01:26:10.000000000 +0200
+++ new/metadata 2017-08-12 22:52:29.000000000 +0200
@@ -1,7 +1,7 @@
--- !ruby/object:Gem::Specification
name: exception_notification
version: !ruby/object:Gem::Version
- version: 4.2.1
+ version: 4.2.2
platform: ruby
authors:
- Jamis Buck
@@ -9,7 +9,7 @@
autorequire:
bindir: bin
cert_chain: []
-date: 2016-07-17 00:00:00.000000000 Z
+date: 2017-08-12 00:00:00.000000000 Z
dependencies:
- !ruby/object:Gem::Dependency
name: actionmailer
@@ -269,6 +269,7 @@
- lib/exception_notifier/irc_notifier.rb
- lib/exception_notifier/mattermost_notifier.rb
- lib/exception_notifier/modules/backtrace_cleaner.rb
+- lib/exception_notifier/modules/error_grouping.rb
- lib/exception_notifier/notifier.rb
- lib/exception_notifier/slack_notifier.rb
- lib/exception_notifier/views/exception_notifier/_backtrace.html.erb
@@ -348,6 +349,7 @@
- test/exception_notifier/hipchat_notifier_test.rb
- test/exception_notifier/irc_notifier_test.rb
- test/exception_notifier/mattermost_notifier_test.rb
+- test/exception_notifier/modules/error_grouping_test.rb
- test/exception_notifier/sidekiq_test.rb
- test/exception_notifier/slack_notifier_test.rb
- test/exception_notifier/webhook_notifier_test.rb
@@ -436,6 +438,7 @@
- test/exception_notifier/hipchat_notifier_test.rb
- test/exception_notifier/irc_notifier_test.rb
- test/exception_notifier/mattermost_notifier_test.rb
+- test/exception_notifier/modules/error_grouping_test.rb
- test/exception_notifier/sidekiq_test.rb
- test/exception_notifier/slack_notifier_test.rb
- test/exception_notifier/webhook_notifier_test.rb
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/test/dummy/test/functional/posts_controller_test.rb new/test/dummy/test/functional/posts_controller_test.rb
--- old/test/dummy/test/functional/posts_controller_test.rb 2016-07-18 01:26:10.000000000 +0200
+++ new/test/dummy/test/functional/posts_controller_test.rb 2017-08-12 22:52:29.000000000 +0200
@@ -35,7 +35,7 @@
test "mail subject should have the proper prefix" do
assert_includes @mail.subject, "[Dummy ERROR]"
end
-
+
test "mail subject should include descriptive error message" do
assert_includes @mail.subject, "(NoMethodError) \"undefined method `nw'"
end
@@ -146,6 +146,25 @@
end
end
+class PostsControllerTestWithoutControllerAndActionNames < ActionController::TestCase
+ tests PostsController
+ setup do
+ @email_notifier = ExceptionNotifier::EmailNotifier.new(:include_controller_and_action_names_in_subject => false)
+ begin
+ post :create, method: :post
+ rescue => e
+ @exception = e
+ @mail = @email_notifier.create_email(@exception, {:env => request.env})
+ end
+ end
+
+ test "should include controller and action names in subject" do
+ assert_includes @mail.subject, '[ERROR]'
+ assert_includes @mail.subject, '(NoMethodError)'
+ refute_includes @mail.subject, 'posts#create'
+ end
+end
+
class PostsControllerTestWithSmtpSettings < ActionController::TestCase
tests PostsController
setup do
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/test/exception_notification/rack_test.rb new/test/exception_notification/rack_test.rb
--- old/test/exception_notification/rack_test.rb 2016-07-18 01:26:10.000000000 +0200
+++ new/test/exception_notification/rack_test.rb 2017-08-12 22:52:29.000000000 +0200
@@ -5,6 +5,14 @@
setup do
@pass_app = Object.new
@pass_app.stubs(:call).returns([nil, { 'X-Cascade' => 'pass' }, nil])
+
+ @normal_app = Object.new
+ @normal_app.stubs(:call).returns([nil, { }, nil])
+ end
+
+ teardown do
+ ExceptionNotifier.error_grouping = false
+ ExceptionNotifier.notification_trigger = nil
end
test "should ignore \"X-Cascade\" header by default" do
@@ -17,4 +25,20 @@
ExceptionNotification::Rack.new(@pass_app, :ignore_cascade_pass => false).call({})
end
+ test "should assign error_grouping if error_grouping is specified" do
+ refute ExceptionNotifier.error_grouping
+ ExceptionNotification::Rack.new(@normal_app, error_grouping: true).call({})
+ assert ExceptionNotifier.error_grouping
+ end
+
+ test "should assign notification_trigger if notification_trigger is specified" do
+ assert_nil ExceptionNotifier.notification_trigger
+ ExceptionNotification::Rack.new(@normal_app, notification_trigger: lambda {|i| true}).call({})
+ assert_respond_to ExceptionNotifier.notification_trigger, :call
+ end
+
+ test "should set default cache to Rails cache" do
+ ExceptionNotification::Rack.new(@normal_app, error_grouping: true).call({})
+ assert_equal Rails.cache, ExceptionNotifier.error_grouping_cache
+ end
end
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/test/exception_notifier/campfire_notifier_test.rb new/test/exception_notifier/campfire_notifier_test.rb
--- old/test/exception_notifier/campfire_notifier_test.rb 2016-07-18 01:26:10.000000000 +0200
+++ new/test/exception_notifier/campfire_notifier_test.rb 2017-08-12 22:52:29.000000000 +0200
@@ -52,6 +52,20 @@
assert_nil campfire.call(fake_exception)
end
+ test "should send the new exception message if no :accumulated_errors_count option" do
+ campfire = ExceptionNotifier::CampfireNotifier.new({})
+ campfire.stubs(:active?).returns(true)
+ campfire.expects(:send_notice).with{ |_, _, message| message.start_with?("A new exception occurred") }.once
+ campfire.call(fake_exception)
+ end
+
+ test "shoud send the exception message if :accumulated_errors_count option greater than 1" do
+ campfire = ExceptionNotifier::CampfireNotifier.new({})
+ campfire.stubs(:active?).returns(true)
+ campfire.expects(:send_notice).with{ |_, _, message| message.start_with?("The exception occurred 3 times:") }.once
+ campfire.call(fake_exception, accumulated_errors_count: 3)
+ end
+
test "should call pre/post_callback if specified" do
pre_callback_called, post_callback_called = 0,0
Tinder::Campfire.stubs(:new).returns(Object.new)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/test/exception_notifier/email_notifier_test.rb new/test/exception_notifier/email_notifier_test.rb
--- old/test/exception_notifier/email_notifier_test.rb 2016-07-18 01:26:10.000000000 +0200
+++ new/test/exception_notifier/email_notifier_test.rb 2017-08-12 22:52:29.000000000 +0200
@@ -218,4 +218,18 @@
mail = email_notifier.call(@exception)
assert_equal %w{second@example.com}, mail.to
end
+
+ test "should prepend accumulated_errors_count in email subject if accumulated_errors_count larger than 1" do
+ ActionMailer::Base.deliveries.clear
+
+ email_notifier = ExceptionNotifier::EmailNotifier.new(
+ :email_prefix => '[Dummy ERROR] ',
+ :sender_address => %{"Dummy Notifier" },
+ :exception_recipients => %w{dummyexceptions@example.com},
+ :delivery_method => :test
+ )
+
+ mail = email_notifier.call(@exception, { accumulated_errors_count: 3 })
+ assert mail.subject.start_with?("[Dummy ERROR] (3 times) (ZeroDivisionError)")
+ end
end
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/test/exception_notifier/hipchat_notifier_test.rb new/test/exception_notifier/hipchat_notifier_test.rb
--- old/test/exception_notifier/hipchat_notifier_test.rb 2016-07-18 01:26:10.000000000 +0200
+++ new/test/exception_notifier/hipchat_notifier_test.rb 2017-08-12 22:52:29.000000000 +0200
@@ -101,7 +101,7 @@
:api_token => 'good_token',
:room_name => 'room_name',
:color => 'yellow',
- :message_template => ->(exception) { "This is custom message: '#{exception.message}'" }
+ :message_template => ->(exception, _) { "This is custom message: '#{exception.message}'" }
}
HipChat::Room.any_instance.expects(:send).with('Exception', "This is custom message: '#{fake_exception.message}'", { :color => 'yellow' })
@@ -110,6 +110,30 @@
hipchat.call(fake_exception)
end
+ test "should send hipchat notification exclude accumulated errors count" do
+ options = {
+ :api_token => 'good_token',
+ :room_name => 'room_name',
+ :color => 'yellow'
+ }
+
+ HipChat::Room.any_instance.expects(:send).with{ |_, msg, _| msg.start_with?("A new exception occurred:") }
+ hipchat = ExceptionNotifier::HipchatNotifier.new(options)
+ hipchat.call(fake_exception)
+ end
+
+ test "should send hipchat notification include accumulated errors count" do
+ options = {
+ :api_token => 'good_token',
+ :room_name => 'room_name',
+ :color => 'yellow'
+ }
+
+ HipChat::Room.any_instance.expects(:send).with{ |_, msg, _| msg.start_with?("The exception occurred 3 times:") }
+ hipchat = ExceptionNotifier::HipchatNotifier.new(options)
+ hipchat.call(fake_exception, { accumulated_errors_count: 3 })
+ end
+
test "should send hipchat notification with HTML-escaped meessage if using default message_template" do
options = {
:api_token => 'good_token',
@@ -149,6 +173,20 @@
hipchat = ExceptionNotifier::HipchatNotifier.new(options)
hipchat.call(fake_exception)
+ end
+
+ test "should allow server_url value (for a self-hosted HipChat Server) if set" do
+ options = {
+ :api_token => 'good_token',
+ :room_name => 'room_name',
+ :api_version => 'v2',
+ :server_url => 'https://domain.com',
+ }
+
+ HipChat::Client.stubs(:new).with('good_token', {:api_version => 'v2', :server_url => 'https://domain.com'}).returns({})
+
+ hipchat = ExceptionNotifier::HipchatNotifier.new(options)
+ hipchat.call(fake_exception)
end
private
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/test/exception_notifier/irc_notifier_test.rb new/test/exception_notifier/irc_notifier_test.rb
--- old/test/exception_notifier/irc_notifier_test.rb 2016-07-18 01:26:10.000000000 +0200
+++ new/test/exception_notifier/irc_notifier_test.rb 2017-08-12 22:52:29.000000000 +0200
@@ -16,6 +16,22 @@
irc.call(fake_exception)
end
+ test "should exclude errors count in message if :accumulated_errors_count nil" do
+ irc = ExceptionNotifier::IrcNotifier.new({})
+ irc.stubs(:active?).returns(true)
+
+ irc.expects(:send_message).with{ |message| message.include?("divided by 0") }.once
+ irc.call(fake_exception)
+ end
+
+ test "should include errors count in message if :accumulated_errors_count is 3" do
+ irc = ExceptionNotifier::IrcNotifier.new({})
+ irc.stubs(:active?).returns(true)
+
+ irc.expects(:send_message).with{ |message| message.include?("(3 times)'divided by 0'") }.once
+ irc.call(fake_exception, accumulated_errors_count: 3)
+ end
+
test "should call pre/post_callback if specified" do
pre_callback_called, post_callback_called = 0,0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/test/exception_notifier/mattermost_notifier_test.rb new/test/exception_notifier/mattermost_notifier_test.rb
--- old/test/exception_notifier/mattermost_notifier_test.rb 2016-07-18 01:26:10.000000000 +0200
+++ new/test/exception_notifier/mattermost_notifier_test.rb 2017-08-12 22:52:29.000000000 +0200
@@ -77,6 +77,23 @@
assert 'password', options[:basic_auth][:password]
end
+ test "should use 'An' for exceptions count if :accumulated_errors_count option is nil" do
+ mattermost_notifier = ExceptionNotifier::MattermostNotifier.new
+ exception = ArgumentError.new("foo")
+ mattermost_notifier.instance_variable_set(:@exception, exception)
+ mattermost_notifier.instance_variable_set(:@options, {})
+
+ assert_includes mattermost_notifier.send(:message_header), "An *ArgumentError* occured."
+ end
+
+ test "shoud use direct errors count if :accumulated_errors_count option is 5" do
+ mattermost_notifier = ExceptionNotifier::MattermostNotifier.new
+ exception = ArgumentError.new("foo")
+ mattermost_notifier.instance_variable_set(:@exception, exception)
+ mattermost_notifier.instance_variable_set(:@options, { accumulated_errors_count: 5 })
+
+ assert_includes mattermost_notifier.send(:message_header), "5 *ArgumentError* occured."
+ end
end
class FakeHTTParty
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/test/exception_notifier/modules/error_grouping_test.rb new/test/exception_notifier/modules/error_grouping_test.rb
--- old/test/exception_notifier/modules/error_grouping_test.rb 1970-01-01 01:00:00.000000000 +0100
+++ new/test/exception_notifier/modules/error_grouping_test.rb 2017-08-12 22:52:29.000000000 +0200
@@ -0,0 +1,166 @@
+require 'test_helper'
+
+class ErrorGroupTest < ActiveSupport::TestCase
+
+ setup do
+ module TestModule
+ include ExceptionNotifier::ErrorGrouping
+ @@error_grouping_cache = ActiveSupport::Cache::FileStore.new("test/dummy/tmp/cache")
+ end
+
+ @exception = RuntimeError.new("ERROR")
+ @exception.stubs(:backtrace).returns(["/path/where/error/raised:1"])
+
+ @exception2 = RuntimeError.new("ERROR2")
+ @exception2.stubs(:backtrace).returns(["/path/where/error/found:2"])
+ end
+
+ teardown do
+ TestModule.error_grouping_cache.clear
+ TestModule.fallback_cache_store.clear
+ end
+
+ test "should add additional option: error_grouping" do
+ assert_respond_to TestModule, :error_grouping
+ assert_respond_to TestModule, :error_grouping=
+ end
+
+ test "should set error_grouping to false default" do
+ assert_equal false, TestModule.error_grouping
+ end
+
+ test "should add additional option: error_grouping_cache" do
+ assert_respond_to TestModule, :error_grouping_cache
+ assert_respond_to TestModule, :error_grouping_cache=
+ end
+
+ test "should add additional option: error_grouping_period" do
+ assert_respond_to TestModule, :error_grouping_period
+ assert_respond_to TestModule, :error_grouping_period=
+ end
+
+ test "shoud set error_grouping_period to 5.minutes default" do
+ assert_equal 300, TestModule.error_grouping_period
+ end
+
+ test "should add additional option: notification_trigger" do
+ assert_respond_to TestModule, :notification_trigger
+ assert_respond_to TestModule, :notification_trigger=
+ end
+
+ test "should return errors count nil when not same error for .error_count" do
+ assert_nil TestModule.error_count("something")
+ end
+
+ test "should return errors count when same error for .error_count" do
+ TestModule.error_grouping_cache.write("error_key", 13)
+ assert_equal 13, TestModule.error_count("error_key")
+ end
+
+ test "should fallback to memory store cache if specified cache store failed to read" do
+ TestModule.error_grouping_cache.stubs(:read).raises(RuntimeError.new "Failed to read")
+ original_fallback = TestModule.fallback_cache_store
+ TestModule.expects(:fallback_cache_store).returns(original_fallback).at_least_once
+
+ assert_nil TestModule.error_count("something_to_read")
+ end
+
+ test "should save error with count for .save_error_count" do
+ count = rand(1..10)
+
+ TestModule.save_error_count("error_key", count)
+ assert_equal count, TestModule.error_grouping_cache.read("error_key")
+ end
+
+ test "should fallback to memory store cache if specified cache store failed to write" do
+ TestModule.error_grouping_cache.stubs(:write).raises(RuntimeError.new "Failed to write")
+ original_fallback = TestModule.fallback_cache_store
+ TestModule.expects(:fallback_cache_store).returns(original_fallback).at_least_once
+
+ assert TestModule.save_error_count("something_to_cache", rand(1..10))
+ end
+
+ test "should save accumulated_errors_count into options" do
+ options = {}
+ TestModule.group_error!(@exception, options)
+
+ assert_equal 1, options[:accumulated_errors_count]
+ end
+
+ test "should not group error if different exception in .group_error!" do
+ options1 = {}
+ TestModule.expects(:save_error_count).with{|key, count| key.is_a?(String) && count == 1}.times(4).returns(true)
+ TestModule.group_error!(@exception, options1)
+
+ options2 = {}
+ TestModule.group_error!(NoMethodError.new("method not found"), options2)
+
+ assert_equal 1, options1[:accumulated_errors_count]
+ assert_equal 1, options2[:accumulated_errors_count]
+ end
+
+ test "should not group error is same exception but different message or backtrace" do
+ options1 = {}
+ TestModule.expects(:save_error_count).with{|key, count| key.is_a?(String) && count == 1}.times(4).returns(true)
+ TestModule.group_error!(@exception, options1)
+
+ options2 = {}
+ TestModule.group_error!(@exception2, options2)
+
+ assert_equal 1, options1[:accumulated_errors_count]
+ assert_equal 1, options2[:accumulated_errors_count]
+ end
+
+ test "should group error if same exception and message" do
+ options = {}
+
+ 10.times do |i|
+ @exception2.stubs(:backtrace).returns(["/path:#{i}"])
+ TestModule.group_error!(@exception2, options)
+ end
+
+ assert_equal 10, options[:accumulated_errors_count]
+ end
+
+ test "should group error if same exception and backtrace" do
+ options = {}
+
+ 10.times do |i|
+ @exception2.stubs(:message).returns("ERRORS#{i}")
+ TestModule.group_error!(@exception2, options)
+ end
+
+ assert_equal 10, options[:accumulated_errors_count]
+ end
+
+ test "should group error by that message have high priority" do
+ message_based_key = "exception:#{Zlib.crc32("RuntimeError\nmessage:ERROR")}"
+ backtrace_based_key = "exception:#{Zlib.crc32("RuntimeError\n/path/where/error/raised:1")}"
+
+ TestModule.save_error_count(message_based_key, 1)
+ TestModule.save_error_count(backtrace_based_key, 1)
+
+ TestModule.expects(:save_error_count).with(message_based_key, 2).once
+ TestModule.expects(:save_error_count).with(backtrace_based_key, 2).never
+
+ TestModule.group_error!(@exception, {})
+ end
+
+ test "use default formula if not specify notification_trigger in .send_notification?" do
+ TestModule.stubs(:notification_trigger).returns(nil)
+
+ count = 16
+ Math.expects(:log2).with(count).returns(4)
+
+ assert TestModule.send_notification?(@exception, count)
+ end
+
+ test "use specified trigger in .send_notification?" do
+ trigger = Proc.new { |exception, count| count % 4 == 0 }
+ TestModule.stubs(:notification_trigger).returns(trigger)
+
+ count = 16
+ trigger.expects(:call).with(@exception, count).returns(true)
+ assert TestModule.send_notification?(@exception, count)
+ end
+end
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/test/exception_notifier/slack_notifier_test.rb new/test/exception_notifier/slack_notifier_test.rb
--- old/test/exception_notifier/slack_notifier_test.rb 2016-07-18 01:26:10.000000000 +0200
+++ new/test/exception_notifier/slack_notifier_test.rb 2017-08-12 22:52:29.000000000 +0200
@@ -43,7 +43,8 @@
slack_notifier = ExceptionNotifier::SlackNotifier.new(options)
slack_notifier.call(@exception)
- assert_equal slack_notifier.notifier.channel, options[:channel]
+ channel = slack_notifier.notifier.config.defaults[:channel]
+ assert_equal channel, options[:channel]
end
test "should send the notification to the specified username" do
@@ -57,7 +58,8 @@
slack_notifier = ExceptionNotifier::SlackNotifier.new(options)
slack_notifier.call(@exception)
- assert_equal slack_notifier.notifier.username, options[:username]
+ username = slack_notifier.notifier.config.defaults[:username]
+ assert_equal username, options[:username]
end
test "should send the notification with specific backtrace lines" do
@@ -72,6 +74,22 @@
slack_notifier.call(@exception)
end
+ test "should send the notification with additional fields" do
+ field = {title: "Branch", value: "master", short: true}
+ options = {
+ webhook_url: "http://slack.webhook.url",
+ additional_fields: [field]
+ }
+
+ Slack::Notifier.any_instance.expects(:ping).with('', fake_notification(@exception, {}, nil, 10, [field]))
+
+ slack_notifier = ExceptionNotifier::SlackNotifier.new(options)
+ slack_notifier.call(@exception)
+
+ additional_fields = slack_notifier.notifier.config.defaults[:additional_fields]
+ assert_equal additional_fields, options[:additional_fields]
+ end
+
test "should pass the additional parameters to Slack::Notifier.ping" do
options = {
webhook_url: "http://slack.webhook.url",
@@ -177,7 +195,7 @@
]
end
- def fake_notification(exception = @exception, notification_options = {}, data_string = nil, expected_backtrace_lines = nil)
+ def fake_notification(exception = @exception, notification_options = {}, data_string = nil, expected_backtrace_lines = 10, additional_fields = [])
exception_name = "*#{exception.class.to_s =~ /^[aeiou]/i ? 'An' : 'A'}* `#{exception.class.to_s}`"
if notification_options[:env].nil?
text = "#{exception_name} *occured in background*"
@@ -196,10 +214,11 @@
fields = [ { title: 'Exception', value: exception.message} ]
fields.push({ title: 'Hostname', value: 'example.com' })
if exception.backtrace
- formatted_backtrace = expected_backtrace_lines ? "```#{exception.backtrace.first(expected_backtrace_lines).join("\n")}```" : "```#{exception.backtrace.join("\n")}```"
+ formatted_backtrace = "```#{exception.backtrace.first(expected_backtrace_lines).join("\n")}```"
fields.push({ title: 'Backtrace', value: formatted_backtrace })
end
fields.push({ title: 'Data', value: "```#{data_string}```" }) if data_string
+ additional_fields.each { |f| fields.push(f) }
{ attachments: [ color: 'danger', text: text, fields: fields, mrkdwn_in: %w(text fields) ] }
end
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/test/exception_notifier_test.rb new/test/exception_notifier_test.rb
--- old/test/exception_notifier_test.rb 2016-07-18 01:26:10.000000000 +0200
+++ new/test/exception_notifier_test.rb 2017-08-12 22:52:30.000000000 +0200
@@ -1,6 +1,21 @@
require 'test_helper'
+class ExceptionOne < StandardError;end
+class ExceptionTwo < StandardError;end
+
class ExceptionNotifierTest < ActiveSupport::TestCase
+ setup do
+ @notifier_calls = 0
+ @test_notifier = lambda { |exception, options| @notifier_calls += 1 }
+ end
+
+ teardown do
+ ExceptionNotifier.error_grouping = false
+ ExceptionNotifier.notification_trigger = nil
+ ExceptionNotifier.class_eval("@@notifiers.delete_if { |k, _| k.to_s != \"email\"}") # reset notifiers
+ Rails.cache.clear
+ end
+
test "should have default ignored exceptions" do
assert_equal ExceptionNotifier.ignored_exceptions,
['ActiveRecord::RecordNotFound', 'Mongoid::Errors::DocumentNotFound', 'AbstractController::ActionNotFound',
@@ -69,37 +84,67 @@
env != "production"
end
- notifier_calls = 0
- test_notifier = lambda { |exception, options| notifier_calls += 1 }
- ExceptionNotifier.register_exception_notifier(:test, test_notifier)
+ ExceptionNotifier.register_exception_notifier(:test, @test_notifier)
exception = StandardError.new
ExceptionNotifier.notify_exception(exception, {:notifiers => :test})
- assert_equal notifier_calls, 1
+ assert_equal @notifier_calls, 1
env = "development"
ExceptionNotifier.notify_exception(exception, {:notifiers => :test})
- assert_equal notifier_calls, 1
+ assert_equal @notifier_calls, 1
ExceptionNotifier.clear_ignore_conditions!
- ExceptionNotifier.unregister_exception_notifier(:test)
end
test "should not send notification if one of ignored exceptions" do
- notifier_calls = 0
- test_notifier = lambda { |exception, options| notifier_calls += 1 }
- ExceptionNotifier.register_exception_notifier(:test, test_notifier)
+ ExceptionNotifier.register_exception_notifier(:test, @test_notifier)
exception = StandardError.new
ExceptionNotifier.notify_exception(exception, {:notifiers => :test})
- assert_equal notifier_calls, 1
+ assert_equal @notifier_calls, 1
ExceptionNotifier.notify_exception(exception, {:notifiers => :test, :ignore_exceptions => 'StandardError' })
- assert_equal notifier_calls, 1
+ assert_equal @notifier_calls, 1
+ end
+
+ test "should not call group_error! or send_notification? if error_grouping false" do
+ exception = StandardError.new
+ ExceptionNotifier.expects(:group_error!).never
+ ExceptionNotifier.expects(:send_notification?).never
+
+ ExceptionNotifier.notify_exception(exception)
+ end
+
+ test "should call group_error! and send_notification? if error_grouping true" do
+ ExceptionNotifier.error_grouping = true
- ExceptionNotifier.unregister_exception_notifier(:test)
+ exception = StandardError.new
+ ExceptionNotifier.expects(:group_error!).once
+ ExceptionNotifier.expects(:send_notification?).once
+
+ ExceptionNotifier.notify_exception(exception)
+ end
+
+ test "should skip notification if send_notification? is false" do
+ ExceptionNotifier.error_grouping = true
+
+ exception = StandardError.new
+ ExceptionNotifier.expects(:group_error!).once.returns(1)
+ ExceptionNotifier.expects(:send_notification?).with(exception, 1).once.returns(false)
+
+ refute ExceptionNotifier.notify_exception(exception)
end
+ test "should send notification if send_notification? is true" do
+ ExceptionNotifier.error_grouping = true
+
+ exception = StandardError.new
+ ExceptionNotifier.expects(:group_error!).once.returns(1)
+ ExceptionNotifier.expects(:send_notification?).with(exception, 1).once.returns(true)
+
+ assert ExceptionNotifier.notify_exception(exception)
+ end
end