exception_notification 4.4.0 → 4.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/Appraisals +3 -1
- data/CHANGELOG.rdoc +10 -0
- data/Gemfile +2 -0
- data/README.md +34 -8
- data/Rakefile +2 -0
- data/examples/sample_app.rb +2 -0
- data/examples/sinatra/Gemfile +2 -0
- data/examples/sinatra/config.ru +2 -0
- data/examples/sinatra/sinatra_app.rb +6 -2
- data/exception_notification.gemspec +9 -8
- data/gemfiles/rails4_0.gemfile +3 -3
- data/gemfiles/rails4_1.gemfile +3 -3
- data/gemfiles/rails4_2.gemfile +3 -3
- data/gemfiles/rails5_0.gemfile +3 -3
- data/gemfiles/rails5_1.gemfile +3 -3
- data/gemfiles/rails5_2.gemfile +3 -3
- data/lib/exception_notification.rb +2 -0
- data/lib/exception_notification/rack.rb +24 -13
- data/lib/exception_notification/rails.rb +2 -0
- data/lib/exception_notification/resque.rb +2 -0
- data/lib/exception_notification/sidekiq.rb +5 -3
- data/lib/exception_notification/version.rb +3 -1
- data/lib/exception_notifier.rb +46 -7
- data/lib/exception_notifier/base_notifier.rb +8 -2
- data/lib/exception_notifier/campfire_notifier.rb +2 -0
- data/lib/exception_notifier/datadog_notifier.rb +12 -9
- data/lib/exception_notifier/email_notifier.rb +11 -3
- data/lib/exception_notifier/google_chat_notifier.rb +2 -0
- data/lib/exception_notifier/hipchat_notifier.rb +2 -0
- data/lib/exception_notifier/irc_notifier.rb +4 -3
- data/lib/exception_notifier/mattermost_notifier.rb +10 -0
- data/lib/exception_notifier/modules/backtrace_cleaner.rb +2 -0
- data/lib/exception_notifier/modules/error_grouping.rb +19 -9
- data/lib/exception_notifier/modules/formatter.rb +3 -0
- data/lib/exception_notifier/notifier.rb +5 -1
- data/lib/exception_notifier/slack_notifier.rb +2 -0
- data/lib/exception_notifier/sns_notifier.rb +4 -3
- data/lib/exception_notifier/teams_notifier.rb +10 -3
- data/lib/exception_notifier/webhook_notifier.rb +3 -3
- data/lib/generators/exception_notification/install_generator.rb +8 -2
- data/test/exception_notification/rack_test.rb +48 -2
- data/test/exception_notification/resque_test.rb +2 -0
- data/test/exception_notifier/campfire_notifier_test.rb +8 -1
- data/test/exception_notifier/datadog_notifier_test.rb +2 -0
- data/test/exception_notifier/email_notifier_test.rb +29 -3
- data/test/exception_notifier/google_chat_notifier_test.rb +15 -11
- data/test/exception_notifier/hipchat_notifier_test.rb +8 -2
- data/test/exception_notifier/irc_notifier_test.rb +2 -0
- data/test/exception_notifier/mattermost_notifier_test.rb +73 -24
- data/test/exception_notifier/modules/error_grouping_test.rb +2 -0
- data/test/exception_notifier/modules/formatter_test.rb +2 -0
- data/test/exception_notifier/sidekiq_test.rb +3 -11
- data/test/exception_notifier/slack_notifier_test.rb +12 -10
- data/test/exception_notifier/sns_notifier_test.rb +9 -7
- data/test/exception_notifier/teams_notifier_test.rb +2 -0
- data/test/exception_notifier/webhook_notifier_test.rb +6 -4
- data/test/exception_notifier_test.rb +112 -6
- data/test/support/exception_notifier_helper.rb +14 -0
- data/test/test_helper.rb +5 -1
- metadata +22 -27
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ExceptionNotifier
|
2
4
|
class BaseNotifier
|
3
5
|
attr_accessor :base_options
|
@@ -14,11 +16,15 @@ module ExceptionNotifier
|
|
14
16
|
end
|
15
17
|
|
16
18
|
def _pre_callback(exception, options, message, message_opts)
|
17
|
-
|
19
|
+
return unless @base_options[:pre_callback].respond_to?(:call)
|
20
|
+
|
21
|
+
@base_options[:pre_callback].call(options, self, exception.backtrace, message, message_opts)
|
18
22
|
end
|
19
23
|
|
20
24
|
def _post_callback(exception, options, message, message_opts)
|
21
|
-
|
25
|
+
return unless @base_options[:post_callback].respond_to?(:call)
|
26
|
+
|
27
|
+
@base_options[:post_callback].call(options, self, exception.backtrace, message, message_opts)
|
22
28
|
end
|
23
29
|
end
|
24
30
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'action_dispatch'
|
2
4
|
|
3
5
|
module ExceptionNotifier
|
@@ -30,7 +32,7 @@ module ExceptionNotifier
|
|
30
32
|
MAX_TITLE_LENGTH = 120
|
31
33
|
MAX_VALUE_LENGTH = 300
|
32
34
|
MAX_BACKTRACE_SIZE = 3
|
33
|
-
ALERT_TYPE = 'error'
|
35
|
+
ALERT_TYPE = 'error'
|
34
36
|
|
35
37
|
attr_reader :exception,
|
36
38
|
:options
|
@@ -74,11 +76,8 @@ module ExceptionNotifier
|
|
74
76
|
end
|
75
77
|
|
76
78
|
def formatted_title
|
77
|
-
title =
|
78
|
-
|
79
|
-
title << "#{controller.controller_name} #{controller.action_name}" if controller
|
80
|
-
title << " (#{exception.class})"
|
81
|
-
title << " #{exception.message.inspect}"
|
79
|
+
title =
|
80
|
+
"#{title_prefix}#{controller_subtitle} (#{exception.class}) #{exception.message.inspect}"
|
82
81
|
|
83
82
|
truncate(title, MAX_TITLE_LENGTH)
|
84
83
|
end
|
@@ -108,9 +107,7 @@ module ExceptionNotifier
|
|
108
107
|
text << formatted_key_value('Parameters', request.filtered_parameters.inspect)
|
109
108
|
text << formatted_key_value('Timestamp', Time.current)
|
110
109
|
text << formatted_key_value('Server', Socket.gethostname)
|
111
|
-
if defined?(Rails) && Rails.respond_to?(:root)
|
112
|
-
text << formatted_key_value('Rails root', Rails.root)
|
113
|
-
end
|
110
|
+
text << formatted_key_value('Rails root', Rails.root) if defined?(Rails) && Rails.respond_to?(:root)
|
114
111
|
text << formatted_key_value('Process', $PROCESS_ID)
|
115
112
|
text << '___'
|
116
113
|
text.join("\n")
|
@@ -148,6 +145,12 @@ module ExceptionNotifier
|
|
148
145
|
object.to_s
|
149
146
|
end
|
150
147
|
end
|
148
|
+
|
149
|
+
private
|
150
|
+
|
151
|
+
def controller_subtitle
|
152
|
+
"#{controller.controller_name} #{controller.action_name}" if controller
|
153
|
+
end
|
151
154
|
end
|
152
155
|
end
|
153
156
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'active_support/core_ext/time'
|
2
4
|
require 'action_mailer'
|
3
5
|
require 'action_dispatch'
|
@@ -74,13 +76,17 @@ module ExceptionNotifier
|
|
74
76
|
def compose_subject
|
75
77
|
subject = @options[:email_prefix].to_s.dup
|
76
78
|
subject << "(#{@options[:accumulated_errors_count]} times)" if @options[:accumulated_errors_count].to_i > 1
|
77
|
-
subject << "#{@kontroller.controller_name} #{@kontroller.action_name}" if
|
79
|
+
subject << "#{@kontroller.controller_name} #{@kontroller.action_name}" if include_controller?
|
78
80
|
subject << " (#{@exception.class})"
|
79
81
|
subject << " #{@exception.message.inspect}" if @options[:verbose_subject]
|
80
82
|
subject = EmailNotifier.normalize_digits(subject) if @options[:normalize_subject]
|
81
83
|
subject.length > 120 ? subject[0...120] + '...' : subject
|
82
84
|
end
|
83
85
|
|
86
|
+
def include_controller?
|
87
|
+
@kontroller && @options[:include_controller_and_action_names_in_subject]
|
88
|
+
end
|
89
|
+
|
84
90
|
def set_data_variables
|
85
91
|
@data.each do |name, value|
|
86
92
|
instance_variable_set("@#{name}", value)
|
@@ -121,7 +127,7 @@ module ExceptionNotifier
|
|
121
127
|
headers = {
|
122
128
|
delivery_method: @options[:delivery_method],
|
123
129
|
to: exception_recipients,
|
124
|
-
from: @options[:sender_address],
|
130
|
+
from: self.class.default[:from] || @options[:sender_address],
|
125
131
|
subject: subject,
|
126
132
|
template_name: name
|
127
133
|
}.merge(@options[:email_headers])
|
@@ -137,7 +143,9 @@ module ExceptionNotifier
|
|
137
143
|
end
|
138
144
|
|
139
145
|
def load_custom_views
|
140
|
-
|
146
|
+
return unless defined?(Rails) && Rails.respond_to?(:root)
|
147
|
+
|
148
|
+
prepend_view_path Rails.root.nil? ? 'app/views' : "#{Rails.root}/app/views"
|
141
149
|
end
|
142
150
|
|
143
151
|
def maybe_call(maybe_proc)
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ExceptionNotifier
|
2
4
|
class IrcNotifier < BaseNotifier
|
3
5
|
def initialize(options)
|
@@ -9,9 +11,8 @@ module ExceptionNotifier
|
|
9
11
|
def call(exception, options = {})
|
10
12
|
errors_count = options[:accumulated_errors_count].to_i
|
11
13
|
|
12
|
-
|
13
|
-
message
|
14
|
-
|
14
|
+
occurrences = "(#{errors_count} times)" if errors_count > 1
|
15
|
+
message = "#{occurrences}'#{exception.message}'"
|
15
16
|
message += " on '#{exception.backtrace.first}'" if exception.backtrace
|
16
17
|
|
17
18
|
return unless active?
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'httparty'
|
2
4
|
|
3
5
|
module ExceptionNotifier
|
@@ -10,6 +12,8 @@ module ExceptionNotifier
|
|
10
12
|
|
11
13
|
@gitlab_url = options[:git_url]
|
12
14
|
|
15
|
+
@env = options[:env] || {}
|
16
|
+
|
13
17
|
payload = {
|
14
18
|
text: message_text.compact.join("\n"),
|
15
19
|
username: options[:username] || 'Exception Notifier'
|
@@ -52,6 +56,12 @@ module ExceptionNotifier
|
|
52
56
|
text << backtrace
|
53
57
|
end
|
54
58
|
|
59
|
+
if (exception_data = @env['exception_notifier.exception_data'])
|
60
|
+
text << '### Data'
|
61
|
+
data_string = exception_data.map { |k, v| "* #{k} : #{v}" }.join("\n")
|
62
|
+
text << "```\n#{data_string}\n```"
|
63
|
+
end
|
64
|
+
|
55
65
|
text << message_issue_link if @gitlab_url
|
56
66
|
|
57
67
|
text
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'active_support/core_ext/numeric/time'
|
2
4
|
require 'active_support/concern'
|
3
5
|
|
@@ -25,20 +27,21 @@ module ExceptionNotifier
|
|
25
27
|
end
|
26
28
|
|
27
29
|
def error_count(error_key)
|
28
|
-
count =
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
30
|
+
count =
|
31
|
+
begin
|
32
|
+
error_grouping_cache.read(error_key)
|
33
|
+
rescue StandardError => e
|
34
|
+
log_cache_error(error_grouping_cache, e, :read)
|
35
|
+
fallback_cache_store.read(error_key)
|
36
|
+
end
|
34
37
|
|
35
|
-
count
|
38
|
+
count&.to_i
|
36
39
|
end
|
37
40
|
|
38
41
|
def save_error_count(error_key, count)
|
39
42
|
error_grouping_cache.write(error_key, count, expires_in: error_grouping_period)
|
40
43
|
rescue StandardError => e
|
41
|
-
|
44
|
+
log_cache_error(error_grouping_cache, e, :write)
|
42
45
|
fallback_cache_store.write(error_key, count, expires_in: error_grouping_period)
|
43
46
|
end
|
44
47
|
|
@@ -50,7 +53,8 @@ module ExceptionNotifier
|
|
50
53
|
accumulated_errors_count = count + 1
|
51
54
|
save_error_count(message_based_key, accumulated_errors_count)
|
52
55
|
else
|
53
|
-
backtrace_based_key =
|
56
|
+
backtrace_based_key =
|
57
|
+
"exception:#{Zlib.crc32("#{exception.class.name}\npath:#{exception.backtrace.try(:first)}")}"
|
54
58
|
|
55
59
|
if (count = error_grouping_cache.read(backtrace_based_key))
|
56
60
|
accumulated_errors_count = count + 1
|
@@ -72,6 +76,12 @@ module ExceptionNotifier
|
|
72
76
|
factor.to_i == factor
|
73
77
|
end
|
74
78
|
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
def log_cache_error(cache, exception, action)
|
83
|
+
"#{cache.inspect} failed to #{action}, reason: #{exception.message}. Falling back to memory cache store."
|
84
|
+
end
|
75
85
|
end
|
76
86
|
end
|
77
87
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'active_support/core_ext/time'
|
2
4
|
require 'action_dispatch'
|
3
5
|
|
@@ -108,6 +110,7 @@ module ExceptionNotifier
|
|
108
110
|
|
109
111
|
def rails_app_name
|
110
112
|
return unless defined?(::Rails) && ::Rails.respond_to?(:application)
|
113
|
+
|
111
114
|
Rails.application.class.parent_name.underscore
|
112
115
|
end
|
113
116
|
|
@@ -1,9 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'active_support/deprecation'
|
2
4
|
|
3
5
|
module ExceptionNotifier
|
4
6
|
class Notifier
|
5
7
|
def self.exception_notification(env, exception, options = {})
|
6
|
-
ActiveSupport::Deprecation.warn
|
8
|
+
ActiveSupport::Deprecation.warn(
|
9
|
+
'Please use ExceptionNotifier.notify_exception(exception, options.merge(env: env)).'
|
10
|
+
)
|
7
11
|
ExceptionNotifier.registered_exception_notifier(:email).create_email(exception, options.merge(env: env))
|
8
12
|
end
|
9
13
|
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ExceptionNotifier
|
2
4
|
class SnsNotifier < BaseNotifier
|
3
5
|
def initialize(options)
|
@@ -33,9 +35,8 @@ module ExceptionNotifier
|
|
33
35
|
attr_reader :notifier, :options
|
34
36
|
|
35
37
|
def build_subject(exception, options)
|
36
|
-
subject =
|
37
|
-
|
38
|
-
subject << ' occurred'
|
38
|
+
subject =
|
39
|
+
"#{options[:sns_prefix]} - #{accumulated_exception_name(exception, options)} occurred"
|
39
40
|
subject.length > 120 ? subject[0...120] + '...' : subject
|
40
41
|
end
|
41
42
|
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'action_dispatch'
|
2
4
|
require 'active_support/core_ext/time'
|
3
5
|
require 'json'
|
@@ -64,8 +66,6 @@ module ExceptionNotifier
|
|
64
66
|
private
|
65
67
|
|
66
68
|
def message_text
|
67
|
-
errors_count = @options[:accumulated_errors_count].to_i
|
68
|
-
|
69
69
|
text = {
|
70
70
|
'@type' => 'MessageCard',
|
71
71
|
'@context' => 'http://schema.org/extensions',
|
@@ -73,7 +73,7 @@ module ExceptionNotifier
|
|
73
73
|
'title' => "⚠️ Exception Occurred in #{env_name} ⚠️",
|
74
74
|
'sections' => [
|
75
75
|
{
|
76
|
-
'activityTitle' =>
|
76
|
+
'activityTitle' => activity_title,
|
77
77
|
'activitySubtitle' => @exception.message.to_s
|
78
78
|
}
|
79
79
|
],
|
@@ -100,6 +100,13 @@ module ExceptionNotifier
|
|
100
100
|
details
|
101
101
|
end
|
102
102
|
|
103
|
+
def activity_title
|
104
|
+
errors_count = @options[:accumulated_errors_count].to_i
|
105
|
+
|
106
|
+
"#{errors_count > 1 ? errors_count : 'A'} *#{@exception.class}* occurred" +
|
107
|
+
(@controller ? " in *#{controller_and_method}*." : '.')
|
108
|
+
end
|
109
|
+
|
103
110
|
def message_request
|
104
111
|
{
|
105
112
|
'name' => 'Request',
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'action_dispatch'
|
2
4
|
require 'active_support/core_ext/time'
|
3
5
|
|
@@ -18,9 +20,7 @@ module ExceptionNotifier
|
|
18
20
|
options[:body] ||= {}
|
19
21
|
options[:body][:server] = Socket.gethostname
|
20
22
|
options[:body][:process] = $PROCESS_ID
|
21
|
-
if defined?(Rails) && Rails.respond_to?(:root)
|
22
|
-
options[:body][:rails_root] = Rails.root
|
23
|
-
end
|
23
|
+
options[:body][:rails_root] = Rails.root if defined?(Rails) && Rails.respond_to?(:root)
|
24
24
|
options[:body][:exception] = {
|
25
25
|
error_class: exception.class.to_s,
|
26
26
|
message: exception.message.inspect,
|
@@ -1,11 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ExceptionNotification
|
2
4
|
module Generators
|
3
5
|
class InstallGenerator < Rails::Generators::Base
|
4
6
|
desc 'Creates a ExceptionNotification initializer.'
|
5
7
|
|
6
8
|
source_root File.expand_path('templates', __dir__)
|
7
|
-
class_option :resque,
|
8
|
-
|
9
|
+
class_option :resque,
|
10
|
+
type: :boolean,
|
11
|
+
desc: 'Add support for sending notifications when errors occur in Resque jobs.'
|
12
|
+
class_option :sidekiq,
|
13
|
+
type: :boolean,
|
14
|
+
desc: 'Add support for sending notifications when errors occur in Sidekiq jobs.'
|
9
15
|
|
10
16
|
def copy_initializer
|
11
17
|
template 'exception_notification.rb.erb', 'config/initializers/exception_notification.rb'
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'test_helper'
|
2
4
|
|
3
5
|
class RackTest < ActiveSupport::TestCase
|
@@ -10,8 +12,7 @@ class RackTest < ActiveSupport::TestCase
|
|
10
12
|
end
|
11
13
|
|
12
14
|
teardown do
|
13
|
-
ExceptionNotifier.
|
14
|
-
ExceptionNotifier.notification_trigger = nil
|
15
|
+
ExceptionNotifier.reset_notifiers!
|
15
16
|
end
|
16
17
|
|
17
18
|
test 'should ignore "X-Cascade" header by default' do
|
@@ -57,4 +58,49 @@ class RackTest < ActiveSupport::TestCase
|
|
57
58
|
refute env['exception_notifier.delivered']
|
58
59
|
end
|
59
60
|
end
|
61
|
+
|
62
|
+
test 'should ignore exceptions if ignore_if condition is met' do
|
63
|
+
exception_app = Object.new
|
64
|
+
exception_app.stubs(:call).raises(RuntimeError)
|
65
|
+
|
66
|
+
env = {}
|
67
|
+
|
68
|
+
begin
|
69
|
+
ExceptionNotification::Rack.new(
|
70
|
+
exception_app,
|
71
|
+
ignore_if: ->(_env, exception) { exception.is_a? RuntimeError }
|
72
|
+
).call(env)
|
73
|
+
|
74
|
+
flunk
|
75
|
+
rescue StandardError
|
76
|
+
refute env['exception_notifier.delivered']
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
test 'should ignore exceptions with notifiers that satisfies ignore_notifier_if condition' do
|
81
|
+
exception_app = Object.new
|
82
|
+
exception_app.stubs(:call).raises(RuntimeError)
|
83
|
+
|
84
|
+
notifier1_called = notifier2_called = false
|
85
|
+
notifier1 = ->(_exception, _options) { notifier1_called = true }
|
86
|
+
notifier2 = ->(_exception, _options) { notifier2_called = true }
|
87
|
+
|
88
|
+
env = {}
|
89
|
+
|
90
|
+
begin
|
91
|
+
ExceptionNotification::Rack.new(
|
92
|
+
exception_app,
|
93
|
+
ignore_notifier_if: {
|
94
|
+
notifier1: ->(_env, exception) { exception.is_a? RuntimeError }
|
95
|
+
},
|
96
|
+
notifier1: notifier1,
|
97
|
+
notifier2: notifier2
|
98
|
+
).call(env)
|
99
|
+
|
100
|
+
flunk
|
101
|
+
rescue StandardError
|
102
|
+
refute notifier1_called
|
103
|
+
assert notifier2_called
|
104
|
+
end
|
105
|
+
end
|
60
106
|
end
|