exception_notification 4.2.0 → 4.4.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/Appraisals +4 -3
- data/CHANGELOG.rdoc +57 -1
- data/CONTRIBUTING.md +21 -2
- data/Gemfile +3 -1
- data/README.md +106 -789
- data/Rakefile +4 -2
- data/docs/notifiers/campfire.md +50 -0
- data/docs/notifiers/custom.md +42 -0
- data/docs/notifiers/datadog.md +51 -0
- data/docs/notifiers/email.md +195 -0
- data/docs/notifiers/google_chat.md +31 -0
- data/docs/notifiers/hipchat.md +66 -0
- data/docs/notifiers/irc.md +97 -0
- data/docs/notifiers/mattermost.md +115 -0
- data/docs/notifiers/slack.md +161 -0
- data/docs/notifiers/sns.md +37 -0
- data/docs/notifiers/teams.md +54 -0
- data/docs/notifiers/webhook.md +60 -0
- data/examples/sample_app.rb +56 -0
- data/examples/sinatra/Gemfile +8 -6
- data/examples/sinatra/config.ru +3 -1
- data/examples/sinatra/sinatra_app.rb +19 -11
- data/exception_notification.gemspec +30 -23
- data/gemfiles/rails4_0.gemfile +1 -2
- data/gemfiles/rails4_1.gemfile +1 -2
- data/gemfiles/rails4_2.gemfile +1 -2
- data/gemfiles/rails5_0.gemfile +1 -2
- data/gemfiles/rails5_1.gemfile +7 -0
- data/gemfiles/rails5_2.gemfile +7 -0
- data/gemfiles/rails6_0.gemfile +7 -0
- data/lib/exception_notification.rb +3 -0
- data/lib/exception_notification/rack.rb +34 -27
- data/lib/exception_notification/rails.rb +3 -0
- data/lib/exception_notification/resque.rb +10 -10
- data/lib/exception_notification/sidekiq.rb +10 -12
- data/lib/exception_notification/version.rb +5 -0
- data/lib/exception_notifier.rb +79 -11
- data/lib/exception_notifier/base_notifier.rb +10 -5
- data/lib/exception_notifier/campfire_notifier.rb +14 -9
- data/lib/exception_notifier/datadog_notifier.rb +156 -0
- data/lib/exception_notifier/email_notifier.rb +78 -87
- data/lib/exception_notifier/google_chat_notifier.rb +44 -0
- data/lib/exception_notifier/hipchat_notifier.rb +16 -10
- data/lib/exception_notifier/irc_notifier.rb +38 -31
- data/lib/exception_notifier/mattermost_notifier.rb +54 -131
- data/lib/exception_notifier/modules/backtrace_cleaner.rb +2 -2
- data/lib/exception_notifier/modules/error_grouping.rb +87 -0
- data/lib/exception_notifier/modules/formatter.rb +121 -0
- data/lib/exception_notifier/notifier.rb +9 -6
- data/lib/exception_notifier/slack_notifier.rb +75 -32
- data/lib/exception_notifier/sns_notifier.rb +86 -0
- data/lib/exception_notifier/teams_notifier.rb +200 -0
- data/lib/exception_notifier/views/exception_notifier/_backtrace.html.erb +1 -1
- data/lib/exception_notifier/views/exception_notifier/_environment.text.erb +1 -1
- data/lib/exception_notifier/views/exception_notifier/_request.text.erb +1 -1
- data/lib/exception_notifier/views/exception_notifier/background_exception_notification.text.erb +9 -9
- data/lib/exception_notifier/views/exception_notifier/exception_notification.html.erb +2 -4
- data/lib/exception_notifier/views/exception_notifier/exception_notification.text.erb +2 -2
- data/lib/exception_notifier/webhook_notifier.rb +19 -16
- data/lib/generators/exception_notification/install_generator.rb +11 -5
- data/lib/generators/exception_notification/templates/{exception_notification.rb → exception_notification.rb.erb} +14 -12
- data/test/exception_notification/rack_test.rb +90 -4
- data/test/exception_notification/resque_test.rb +54 -0
- data/test/exception_notifier/campfire_notifier_test.rb +66 -39
- data/test/exception_notifier/datadog_notifier_test.rb +153 -0
- data/test/exception_notifier/email_notifier_test.rb +301 -145
- data/test/exception_notifier/google_chat_notifier_test.rb +185 -0
- data/test/exception_notifier/hipchat_notifier_test.rb +112 -65
- data/test/exception_notifier/irc_notifier_test.rb +48 -30
- data/test/exception_notifier/mattermost_notifier_test.rb +218 -55
- data/test/exception_notifier/modules/error_grouping_test.rb +167 -0
- data/test/exception_notifier/modules/formatter_test.rb +152 -0
- data/test/exception_notifier/sidekiq_test.rb +9 -6
- data/test/exception_notifier/slack_notifier_test.rb +109 -59
- data/test/exception_notifier/sns_notifier_test.rb +123 -0
- data/test/exception_notifier/teams_notifier_test.rb +92 -0
- data/test/exception_notifier/webhook_notifier_test.rb +68 -38
- data/test/exception_notifier_test.rb +220 -37
- data/test/support/exception_notifier_helper.rb +14 -0
- data/test/{dummy/app → support}/views/exception_notifier/_new_bkg_section.html.erb +0 -0
- data/test/{dummy/app → support}/views/exception_notifier/_new_bkg_section.text.erb +0 -0
- data/test/{dummy/app → support}/views/exception_notifier/_new_section.html.erb +0 -0
- data/test/{dummy/app → support}/views/exception_notifier/_new_section.text.erb +0 -0
- data/test/test_helper.rb +14 -13
- metadata +154 -162
- data/test/dummy/.gitignore +0 -4
- data/test/dummy/Rakefile +0 -7
- data/test/dummy/app/controllers/application_controller.rb +0 -3
- data/test/dummy/app/controllers/posts_controller.rb +0 -30
- data/test/dummy/app/helpers/application_helper.rb +0 -2
- data/test/dummy/app/helpers/posts_helper.rb +0 -2
- data/test/dummy/app/models/post.rb +0 -2
- data/test/dummy/app/views/layouts/application.html.erb +0 -14
- data/test/dummy/app/views/posts/_form.html.erb +0 -0
- data/test/dummy/app/views/posts/new.html.erb +0 -0
- data/test/dummy/app/views/posts/show.html.erb +0 -0
- data/test/dummy/config.ru +0 -4
- data/test/dummy/config/application.rb +0 -42
- data/test/dummy/config/boot.rb +0 -6
- data/test/dummy/config/database.yml +0 -22
- data/test/dummy/config/environment.rb +0 -17
- data/test/dummy/config/environments/development.rb +0 -25
- data/test/dummy/config/environments/production.rb +0 -50
- data/test/dummy/config/environments/test.rb +0 -38
- data/test/dummy/config/initializers/backtrace_silencers.rb +0 -7
- data/test/dummy/config/initializers/inflections.rb +0 -10
- data/test/dummy/config/initializers/mime_types.rb +0 -5
- data/test/dummy/config/initializers/secret_token.rb +0 -8
- data/test/dummy/config/initializers/session_store.rb +0 -8
- data/test/dummy/config/locales/en.yml +0 -5
- data/test/dummy/config/routes.rb +0 -3
- data/test/dummy/db/migrate/20110729022608_create_posts.rb +0 -15
- data/test/dummy/db/schema.rb +0 -24
- data/test/dummy/db/seeds.rb +0 -7
- data/test/dummy/lib/tasks/.gitkeep +0 -0
- data/test/dummy/public/404.html +0 -26
- data/test/dummy/public/422.html +0 -26
- data/test/dummy/public/500.html +0 -26
- data/test/dummy/public/favicon.ico +0 -0
- data/test/dummy/public/images/rails.png +0 -0
- data/test/dummy/public/index.html +0 -239
- data/test/dummy/public/javascripts/application.js +0 -2
- data/test/dummy/public/javascripts/controls.js +0 -965
- data/test/dummy/public/javascripts/dragdrop.js +0 -974
- data/test/dummy/public/javascripts/effects.js +0 -1123
- data/test/dummy/public/javascripts/prototype.js +0 -6001
- data/test/dummy/public/javascripts/rails.js +0 -191
- data/test/dummy/public/robots.txt +0 -5
- data/test/dummy/public/stylesheets/.gitkeep +0 -0
- data/test/dummy/public/stylesheets/scaffold.css +0 -56
- data/test/dummy/script/rails +0 -6
- data/test/dummy/test/functional/posts_controller_test.rb +0 -218
- data/test/dummy/test/test_helper.rb +0 -7
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'httparty'
|
4
|
+
|
5
|
+
module ExceptionNotifier
|
6
|
+
class GoogleChatNotifier < BaseNotifier
|
7
|
+
def call(exception, opts = {})
|
8
|
+
options = base_options.merge(opts)
|
9
|
+
formatter = Formatter.new(exception, options)
|
10
|
+
|
11
|
+
HTTParty.post(
|
12
|
+
options[:webhook_url],
|
13
|
+
body: { text: body(exception, formatter) }.to_json,
|
14
|
+
headers: { 'Content-Type' => 'application/json' }
|
15
|
+
)
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def body(exception, formatter)
|
21
|
+
text = [
|
22
|
+
"\nApplication: *#{formatter.app_name}*",
|
23
|
+
formatter.subtitle,
|
24
|
+
'',
|
25
|
+
formatter.title,
|
26
|
+
"*#{exception.message.tr('`', "'")}*"
|
27
|
+
]
|
28
|
+
|
29
|
+
if (request = formatter.request_message.presence)
|
30
|
+
text << ''
|
31
|
+
text << '*Request:*'
|
32
|
+
text << request
|
33
|
+
end
|
34
|
+
|
35
|
+
if (backtrace = formatter.backtrace_message.presence)
|
36
|
+
text << ''
|
37
|
+
text << '*Backtrace:*'
|
38
|
+
text << backtrace
|
39
|
+
end
|
40
|
+
|
41
|
+
text.compact.join("\n")
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -1,6 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ExceptionNotifier
|
2
4
|
class HipchatNotifier < BaseNotifier
|
3
|
-
|
4
5
|
attr_accessor :from
|
5
6
|
attr_accessor :room
|
6
7
|
attr_accessor :message_options
|
@@ -11,26 +12,31 @@ module ExceptionNotifier
|
|
11
12
|
api_token = options.delete(:api_token)
|
12
13
|
room_name = options.delete(:room_name)
|
13
14
|
opts = {
|
14
|
-
|
15
|
-
|
15
|
+
api_version: options.delete(:api_version) || 'v1'
|
16
|
+
}
|
17
|
+
opts[:server_url] = options.delete(:server_url) if options[:server_url]
|
16
18
|
@from = options.delete(:from) || 'Exception'
|
17
19
|
@room = HipChat::Client.new(api_token, opts)[room_name]
|
18
|
-
@message_template = options.delete(:message_template) ||
|
19
|
-
msg =
|
20
|
+
@message_template = options.delete(:message_template) || lambda { |exception, errors_count|
|
21
|
+
msg = if errors_count > 1
|
22
|
+
"The exception occurred #{errors_count} times: '#{Rack::Utils.escape_html(exception.message)}'"
|
23
|
+
else
|
24
|
+
"A new exception occurred: '#{Rack::Utils.escape_html(exception.message)}'"
|
25
|
+
end
|
20
26
|
msg += " on '#{exception.backtrace.first}'" if exception.backtrace
|
21
27
|
msg
|
22
28
|
}
|
23
|
-
@message_options
|
29
|
+
@message_options = options
|
24
30
|
@message_options[:color] ||= 'red'
|
25
|
-
rescue
|
31
|
+
rescue StandardError
|
26
32
|
@room = nil
|
27
33
|
end
|
28
34
|
end
|
29
35
|
|
30
|
-
def call(exception, options={})
|
31
|
-
return
|
36
|
+
def call(exception, options = {})
|
37
|
+
return unless active?
|
32
38
|
|
33
|
-
message = @message_template.call(exception)
|
39
|
+
message = @message_template.call(exception, options[:accumulated_errors_count].to_i)
|
34
40
|
send_notice(exception, options, message, @message_options) do |msg, message_opts|
|
35
41
|
@room.send(@from, msg, message_opts)
|
36
42
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ExceptionNotifier
|
2
4
|
class IrcNotifier < BaseNotifier
|
3
5
|
def initialize(options)
|
@@ -6,46 +8,51 @@ module ExceptionNotifier
|
|
6
8
|
parse_options(options)
|
7
9
|
end
|
8
10
|
|
9
|
-
def call(exception, options={})
|
10
|
-
|
11
|
+
def call(exception, options = {})
|
12
|
+
errors_count = options[:accumulated_errors_count].to_i
|
13
|
+
|
14
|
+
occurrences = "(#{errors_count} times)" if errors_count > 1
|
15
|
+
message = "#{occurrences}'#{exception.message}'"
|
11
16
|
message += " on '#{exception.backtrace.first}'" if exception.backtrace
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
17
|
+
|
18
|
+
return unless active?
|
19
|
+
|
20
|
+
send_notice(exception, options, message) do |msg, _|
|
21
|
+
send_message([*@config.prefix, *msg].join(' '))
|
16
22
|
end
|
17
23
|
end
|
18
24
|
|
19
25
|
def send_message(message)
|
20
|
-
CarrierPigeon.send @config.irc.merge(
|
26
|
+
CarrierPigeon.send @config.irc.merge(message: message)
|
21
27
|
end
|
22
28
|
|
23
29
|
private
|
24
|
-
def parse_options(options)
|
25
|
-
nick = options.fetch(:nick, 'ExceptionNotifierBot')
|
26
|
-
password = options[:password] ? ":#{options[:password]}" : nil
|
27
|
-
domain = options.fetch(:domain, nil)
|
28
|
-
port = options[:port] ? ":#{options[:port]}" : nil
|
29
|
-
channel = options.fetch(:channel, '#log')
|
30
|
-
notice = options.fetch(:notice, false)
|
31
|
-
ssl = options.fetch(:ssl, false)
|
32
|
-
join = options.fetch(:join, false)
|
33
|
-
uri = "irc://#{nick}#{password}@#{domain}#{port}/#{channel}"
|
34
|
-
prefix = options.fetch(:prefix, nil)
|
35
|
-
recipients = options[:recipients] ? options[:recipients].join(', ') + ':' : nil
|
36
|
-
|
37
|
-
@config.prefix = [*prefix, *recipients].join(' ')
|
38
|
-
@config.irc = { uri: uri, ssl: ssl, notice: notice, join: join }
|
39
|
-
end
|
40
30
|
|
41
|
-
|
42
|
-
|
43
|
-
|
31
|
+
def parse_options(options)
|
32
|
+
nick = options.fetch(:nick, 'ExceptionNotifierBot')
|
33
|
+
password = options[:password] ? ":#{options[:password]}" : nil
|
34
|
+
domain = options.fetch(:domain, nil)
|
35
|
+
port = options[:port] ? ":#{options[:port]}" : nil
|
36
|
+
channel = options.fetch(:channel, '#log')
|
37
|
+
notice = options.fetch(:notice, false)
|
38
|
+
ssl = options.fetch(:ssl, false)
|
39
|
+
join = options.fetch(:join, false)
|
40
|
+
uri = "irc://#{nick}#{password}@#{domain}#{port}/#{channel}"
|
41
|
+
prefix = options.fetch(:prefix, nil)
|
42
|
+
recipients = options[:recipients] ? options[:recipients].join(', ') + ':' : nil
|
44
43
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
44
|
+
@config.prefix = [*prefix, *recipients].join(' ')
|
45
|
+
@config.irc = { uri: uri, ssl: ssl, notice: notice, join: join }
|
46
|
+
end
|
47
|
+
|
48
|
+
def active?
|
49
|
+
valid_uri? @config.irc[:uri]
|
50
|
+
end
|
51
|
+
|
52
|
+
def valid_uri?(uri)
|
53
|
+
URI.parse(uri)
|
54
|
+
rescue URI::InvalidURIError
|
55
|
+
false
|
56
|
+
end
|
50
57
|
end
|
51
58
|
end
|
@@ -1,159 +1,82 @@
|
|
1
|
-
|
2
|
-
require 'active_support/core_ext/time'
|
1
|
+
# frozen_string_literal: true
|
3
2
|
|
4
|
-
|
5
|
-
class MattermostNotifier
|
6
|
-
include ExceptionNotifier::BacktraceCleaner
|
7
|
-
|
8
|
-
attr_accessor :httparty
|
9
|
-
|
10
|
-
def initialize(options = {})
|
11
|
-
super()
|
12
|
-
@default_options = options
|
13
|
-
@httparty = HTTParty
|
14
|
-
end
|
3
|
+
require 'httparty'
|
15
4
|
|
16
|
-
|
17
|
-
|
5
|
+
module ExceptionNotifier
|
6
|
+
class MattermostNotifier < BaseNotifier
|
7
|
+
def call(exception, opts = {})
|
8
|
+
options = opts.merge(base_options)
|
18
9
|
@exception = exception
|
19
|
-
@backtrace = exception.backtrace ? clean_backtrace(exception) : nil
|
20
|
-
|
21
|
-
@env = @options.delete(:env)
|
22
10
|
|
23
|
-
@
|
24
|
-
@gitlab_url = @options.delete(:git_url)
|
25
|
-
@username = @options.delete(:username) || "Exception Notifier"
|
26
|
-
@avatar = @options.delete(:avatar)
|
11
|
+
@formatter = Formatter.new(exception, options)
|
27
12
|
|
28
|
-
@
|
29
|
-
@webhook_url = @options.delete(:webhook_url)
|
30
|
-
raise ArgumentError.new "You must provide 'webhook_url' parameter." unless @webhook_url
|
13
|
+
@gitlab_url = options[:git_url]
|
31
14
|
|
32
|
-
|
33
|
-
@controller = @env['action_controller.instance'] || MissingController.new
|
15
|
+
@env = options[:env] || {}
|
34
16
|
|
35
|
-
|
17
|
+
payload = {
|
18
|
+
text: message_text.compact.join("\n"),
|
19
|
+
username: options[:username] || 'Exception Notifier'
|
20
|
+
}
|
36
21
|
|
37
|
-
|
38
|
-
|
39
|
-
ip_address: request.remote_ip,
|
40
|
-
parameters: request.filtered_parameters,
|
41
|
-
timestamp: Time.current }
|
42
|
-
|
43
|
-
if request.session["warden.user.user.key"]
|
44
|
-
current_user = User.find(request.session["warden.user.user.key"][0][0])
|
45
|
-
@request_items.merge!({ current_user: { id: current_user.id, email: current_user.email } })
|
46
|
-
end
|
47
|
-
else
|
48
|
-
@controller = @request_items = nil
|
49
|
-
end
|
22
|
+
payload[:icon_url] = options[:avatar] if options[:avatar]
|
23
|
+
payload[:channel] = options[:channel] if options[:channel]
|
50
24
|
|
51
|
-
|
25
|
+
httparty_options = options.except(
|
26
|
+
:avatar, :channel, :username, :git_url, :webhook_url,
|
27
|
+
:env, :accumulated_errors_count, :app_name
|
28
|
+
)
|
52
29
|
|
53
|
-
|
54
|
-
|
55
|
-
|
30
|
+
httparty_options[:body] = payload.to_json
|
31
|
+
httparty_options[:headers] ||= {}
|
32
|
+
httparty_options[:headers]['Content-Type'] = 'application/json'
|
56
33
|
|
57
|
-
|
34
|
+
HTTParty.post(options[:webhook_url], httparty_options)
|
58
35
|
end
|
59
36
|
|
60
37
|
private
|
61
38
|
|
62
|
-
|
63
|
-
if @channel
|
64
|
-
{ channel: @channel }
|
65
|
-
else
|
66
|
-
{}
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
|
-
def user_info
|
71
|
-
infos = {}
|
72
|
-
|
73
|
-
infos.merge!({ username: @username }) if @username
|
74
|
-
infos.merge!({ icon_url: @avatar }) if @avatar
|
75
|
-
|
76
|
-
infos
|
77
|
-
end
|
78
|
-
|
79
|
-
def message_text
|
80
|
-
text = []
|
81
|
-
|
82
|
-
text += ["@channel"]
|
83
|
-
text += message_header
|
84
|
-
text += message_request if @request_items
|
85
|
-
text += message_backtrace if @backtrace
|
86
|
-
text += message_issue_link if @gitlab_url
|
87
|
-
|
88
|
-
{ text: text.join("\n") }
|
89
|
-
end
|
90
|
-
|
91
|
-
def message_header
|
92
|
-
text = []
|
93
|
-
|
94
|
-
text << "### :warning: Error 500 in #{Rails.env} :warning:"
|
95
|
-
text << "An *#{@exception.class}* occured" + if @controller then " in *#{controller_and_method}*." else "." end
|
96
|
-
text << "*#{@exception.message}*"
|
97
|
-
|
98
|
-
text
|
99
|
-
end
|
100
|
-
|
101
|
-
def message_request
|
102
|
-
text = []
|
103
|
-
|
104
|
-
text << "### Request"
|
105
|
-
text << "```"
|
106
|
-
text << hash_presentation(@request_items)
|
107
|
-
text << "```"
|
108
|
-
|
109
|
-
text
|
110
|
-
end
|
111
|
-
|
112
|
-
def message_backtrace(size = 3)
|
113
|
-
text = []
|
39
|
+
attr_reader :formatter
|
114
40
|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
41
|
+
def message_text
|
42
|
+
text = [
|
43
|
+
'@channel',
|
44
|
+
"### #{formatter.title}",
|
45
|
+
formatter.subtitle,
|
46
|
+
"*#{@exception.message}*"
|
47
|
+
]
|
120
48
|
|
121
|
-
|
49
|
+
if (request = formatter.request_message.presence)
|
50
|
+
text << '### Request'
|
51
|
+
text << request
|
122
52
|
end
|
123
53
|
|
124
|
-
|
125
|
-
text
|
126
|
-
|
127
|
-
link = [@gitlab_url, @application_name, "issues", "new"].join("/")
|
128
|
-
params = {
|
129
|
-
"issue[title]" => ["[BUG] Error 500 :",
|
130
|
-
controller_and_method,
|
131
|
-
"(#{@exception.class})",
|
132
|
-
@exception.message].compact.join(" ")
|
133
|
-
}.to_query
|
134
|
-
|
135
|
-
text << "[Create an issue](#{link}/?#{params})"
|
136
|
-
|
137
|
-
text
|
54
|
+
if (backtrace = formatter.backtrace_message.presence)
|
55
|
+
text << '### Backtrace'
|
56
|
+
text << backtrace
|
138
57
|
end
|
139
58
|
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
""
|
145
|
-
end
|
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```"
|
146
63
|
end
|
147
64
|
|
148
|
-
|
149
|
-
text = []
|
65
|
+
text << message_issue_link if @gitlab_url
|
150
66
|
|
151
|
-
|
152
|
-
|
153
|
-
end
|
67
|
+
text
|
68
|
+
end
|
154
69
|
|
155
|
-
|
156
|
-
|
70
|
+
def message_issue_link
|
71
|
+
link = [@gitlab_url, formatter.app_name, 'issues', 'new'].join('/')
|
72
|
+
params = {
|
73
|
+
'issue[title]' => ['[BUG] Error 500 :',
|
74
|
+
formatter.controller_and_action || '',
|
75
|
+
"(#{@exception.class})",
|
76
|
+
@exception.message].compact.join(' ')
|
77
|
+
}.to_query
|
157
78
|
|
79
|
+
"[Create an issue](#{link}/?#{params})"
|
80
|
+
end
|
158
81
|
end
|
159
82
|
end
|
@@ -1,6 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ExceptionNotifier
|
2
4
|
module BacktraceCleaner
|
3
|
-
|
4
5
|
def clean_backtrace(exception)
|
5
6
|
if defined?(Rails) && Rails.respond_to?(:backtrace_cleaner)
|
6
7
|
Rails.backtrace_cleaner.send(:filter, exception.backtrace)
|
@@ -8,6 +9,5 @@ module ExceptionNotifier
|
|
8
9
|
exception.backtrace
|
9
10
|
end
|
10
11
|
end
|
11
|
-
|
12
12
|
end
|
13
13
|
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support/core_ext/numeric/time'
|
4
|
+
require 'active_support/concern'
|
5
|
+
|
6
|
+
module ExceptionNotifier
|
7
|
+
module ErrorGrouping
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
|
10
|
+
included do
|
11
|
+
mattr_accessor :error_grouping
|
12
|
+
self.error_grouping = false
|
13
|
+
|
14
|
+
mattr_accessor :error_grouping_period
|
15
|
+
self.error_grouping_period = 5.minutes
|
16
|
+
|
17
|
+
mattr_accessor :notification_trigger
|
18
|
+
|
19
|
+
mattr_accessor :error_grouping_cache
|
20
|
+
end
|
21
|
+
|
22
|
+
module ClassMethods
|
23
|
+
# Fallback to the memory store while the specified cache store doesn't work
|
24
|
+
#
|
25
|
+
def fallback_cache_store
|
26
|
+
@fallback_cache_store ||= ActiveSupport::Cache::MemoryStore.new
|
27
|
+
end
|
28
|
+
|
29
|
+
def error_count(error_key)
|
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
|
37
|
+
|
38
|
+
count&.to_i
|
39
|
+
end
|
40
|
+
|
41
|
+
def save_error_count(error_key, count)
|
42
|
+
error_grouping_cache.write(error_key, count, expires_in: error_grouping_period)
|
43
|
+
rescue StandardError => e
|
44
|
+
log_cache_error(error_grouping_cache, e, :write)
|
45
|
+
fallback_cache_store.write(error_key, count, expires_in: error_grouping_period)
|
46
|
+
end
|
47
|
+
|
48
|
+
def group_error!(exception, options)
|
49
|
+
message_based_key = "exception:#{Zlib.crc32("#{exception.class.name}\nmessage:#{exception.message}")}"
|
50
|
+
accumulated_errors_count = 1
|
51
|
+
|
52
|
+
if (count = error_count(message_based_key))
|
53
|
+
accumulated_errors_count = count + 1
|
54
|
+
save_error_count(message_based_key, accumulated_errors_count)
|
55
|
+
else
|
56
|
+
backtrace_based_key =
|
57
|
+
"exception:#{Zlib.crc32("#{exception.class.name}\npath:#{exception.backtrace.try(:first)}")}"
|
58
|
+
|
59
|
+
if (count = error_grouping_cache.read(backtrace_based_key))
|
60
|
+
accumulated_errors_count = count + 1
|
61
|
+
save_error_count(backtrace_based_key, accumulated_errors_count)
|
62
|
+
else
|
63
|
+
save_error_count(backtrace_based_key, accumulated_errors_count)
|
64
|
+
save_error_count(message_based_key, accumulated_errors_count)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
options[:accumulated_errors_count] = accumulated_errors_count
|
69
|
+
end
|
70
|
+
|
71
|
+
def send_notification?(exception, count)
|
72
|
+
if notification_trigger.respond_to?(:call)
|
73
|
+
notification_trigger.call(exception, count)
|
74
|
+
else
|
75
|
+
factor = Math.log2(count)
|
76
|
+
factor.to_i == factor
|
77
|
+
end
|
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
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|