exception_notification 4.3.0 → 4.4.0
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 +4 -4
- data/Appraisals +2 -2
- data/CHANGELOG.rdoc +14 -0
- data/CONTRIBUTING.md +18 -0
- data/Gemfile +1 -1
- data/README.md +64 -935
- data/Rakefile +2 -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 +54 -0
- data/examples/sinatra/Gemfile +6 -6
- data/examples/sinatra/config.ru +1 -1
- data/examples/sinatra/sinatra_app.rb +14 -10
- data/exception_notification.gemspec +27 -22
- 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 +7 -0
- data/gemfiles/rails6_0.gemfile +7 -0
- data/lib/exception_notification.rb +1 -0
- data/lib/exception_notification/rack.rb +8 -21
- data/lib/exception_notification/resque.rb +8 -10
- data/lib/exception_notification/sidekiq.rb +8 -12
- data/lib/exception_notification/version.rb +3 -0
- data/lib/exception_notifier.rb +20 -3
- data/lib/exception_notifier/base_notifier.rb +2 -3
- data/lib/exception_notifier/campfire_notifier.rb +12 -13
- data/lib/exception_notifier/datadog_notifier.rb +153 -0
- data/lib/exception_notifier/email_notifier.rb +64 -87
- data/lib/exception_notifier/google_chat_notifier.rb +25 -119
- data/lib/exception_notifier/hipchat_notifier.rb +11 -12
- data/lib/exception_notifier/irc_notifier.rb +32 -30
- data/lib/exception_notifier/mattermost_notifier.rb +47 -140
- data/lib/exception_notifier/modules/backtrace_cleaner.rb +0 -2
- data/lib/exception_notifier/modules/error_grouping.rb +5 -5
- data/lib/exception_notifier/modules/formatter.rb +118 -0
- data/lib/exception_notifier/notifier.rb +5 -6
- data/lib/exception_notifier/slack_notifier.rb +63 -40
- data/lib/exception_notifier/sns_notifier.rb +17 -11
- data/lib/exception_notifier/teams_notifier.rb +58 -44
- 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/exception_notification.html.erb +2 -2
- data/lib/exception_notifier/views/exception_notifier/exception_notification.text.erb +2 -2
- data/lib/exception_notifier/webhook_notifier.rb +14 -11
- data/lib/generators/exception_notification/install_generator.rb +5 -5
- data/lib/generators/exception_notification/templates/{exception_notification.rb → exception_notification.rb.erb} +13 -11
- data/test/exception_notification/rack_test.rb +27 -11
- data/test/exception_notification/resque_test.rb +52 -0
- data/test/exception_notifier/campfire_notifier_test.rb +42 -42
- data/test/exception_notifier/datadog_notifier_test.rb +151 -0
- data/test/exception_notifier/email_notifier_test.rb +269 -153
- data/test/exception_notifier/google_chat_notifier_test.rb +154 -101
- data/test/exception_notifier/hipchat_notifier_test.rb +78 -81
- data/test/exception_notifier/irc_notifier_test.rb +34 -34
- data/test/exception_notifier/mattermost_notifier_test.rb +164 -67
- data/test/exception_notifier/modules/error_grouping_test.rb +39 -40
- data/test/exception_notifier/modules/formatter_test.rb +150 -0
- data/test/exception_notifier/sidekiq_test.rb +6 -6
- data/test/exception_notifier/slack_notifier_test.rb +61 -60
- data/test/exception_notifier/sns_notifier_test.rb +27 -32
- data/test/exception_notifier/teams_notifier_test.rb +23 -26
- data/test/exception_notifier/webhook_notifier_test.rb +48 -46
- data/test/exception_notifier_test.rb +41 -38
- 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 +11 -14
- metadata +136 -166
- 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 -35
- 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 -237
- data/test/dummy/test/test_helper.rb +0 -7
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
module ExceptionNotifier
|
|
2
2
|
module BacktraceCleaner
|
|
3
|
-
|
|
4
3
|
def clean_backtrace(exception)
|
|
5
4
|
if defined?(Rails) && Rails.respond_to?(:backtrace_cleaner)
|
|
6
5
|
Rails.backtrace_cleaner.send(:filter, exception.backtrace)
|
|
@@ -8,6 +7,5 @@ module ExceptionNotifier
|
|
|
8
7
|
exception.backtrace
|
|
9
8
|
end
|
|
10
9
|
end
|
|
11
|
-
|
|
12
10
|
end
|
|
13
11
|
end
|
|
@@ -27,7 +27,7 @@ module ExceptionNotifier
|
|
|
27
27
|
def error_count(error_key)
|
|
28
28
|
count = begin
|
|
29
29
|
error_grouping_cache.read(error_key)
|
|
30
|
-
rescue => e
|
|
30
|
+
rescue StandardError => e
|
|
31
31
|
ExceptionNotifier.logger.warn("#{error_grouping_cache.inspect} failed to read, reason: #{e.message}. Falling back to memory cache store.")
|
|
32
32
|
fallback_cache_store.read(error_key)
|
|
33
33
|
end
|
|
@@ -37,7 +37,7 @@ module ExceptionNotifier
|
|
|
37
37
|
|
|
38
38
|
def save_error_count(error_key, count)
|
|
39
39
|
error_grouping_cache.write(error_key, count, expires_in: error_grouping_period)
|
|
40
|
-
rescue => e
|
|
40
|
+
rescue StandardError => e
|
|
41
41
|
ExceptionNotifier.logger.warn("#{error_grouping_cache.inspect} failed to write, reason: #{e.message}. Falling back to memory cache store.")
|
|
42
42
|
fallback_cache_store.write(error_key, count, expires_in: error_grouping_period)
|
|
43
43
|
end
|
|
@@ -46,13 +46,13 @@ module ExceptionNotifier
|
|
|
46
46
|
message_based_key = "exception:#{Zlib.crc32("#{exception.class.name}\nmessage:#{exception.message}")}"
|
|
47
47
|
accumulated_errors_count = 1
|
|
48
48
|
|
|
49
|
-
if count = error_count(message_based_key)
|
|
49
|
+
if (count = error_count(message_based_key))
|
|
50
50
|
accumulated_errors_count = count + 1
|
|
51
51
|
save_error_count(message_based_key, accumulated_errors_count)
|
|
52
52
|
else
|
|
53
53
|
backtrace_based_key = "exception:#{Zlib.crc32("#{exception.class.name}\npath:#{exception.backtrace.try(:first)}")}"
|
|
54
54
|
|
|
55
|
-
if count = error_grouping_cache.read(backtrace_based_key)
|
|
55
|
+
if (count = error_grouping_cache.read(backtrace_based_key))
|
|
56
56
|
accumulated_errors_count = count + 1
|
|
57
57
|
save_error_count(backtrace_based_key, accumulated_errors_count)
|
|
58
58
|
else
|
|
@@ -74,4 +74,4 @@ module ExceptionNotifier
|
|
|
74
74
|
end
|
|
75
75
|
end
|
|
76
76
|
end
|
|
77
|
-
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
require 'active_support/core_ext/time'
|
|
2
|
+
require 'action_dispatch'
|
|
3
|
+
|
|
4
|
+
module ExceptionNotifier
|
|
5
|
+
class Formatter
|
|
6
|
+
include ExceptionNotifier::BacktraceCleaner
|
|
7
|
+
|
|
8
|
+
attr_reader :app_name
|
|
9
|
+
|
|
10
|
+
def initialize(exception, opts = {})
|
|
11
|
+
@exception = exception
|
|
12
|
+
|
|
13
|
+
@env = opts[:env]
|
|
14
|
+
@errors_count = opts[:accumulated_errors_count].to_i
|
|
15
|
+
@app_name = opts[:app_name] || rails_app_name
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
#
|
|
19
|
+
# :warning: Error occurred in production :warning:
|
|
20
|
+
# :warning: Error occurred :warning:
|
|
21
|
+
#
|
|
22
|
+
def title
|
|
23
|
+
env = Rails.env if defined?(::Rails) && ::Rails.respond_to?(:env)
|
|
24
|
+
|
|
25
|
+
if env
|
|
26
|
+
"⚠️ Error occurred in #{env} ⚠️"
|
|
27
|
+
else
|
|
28
|
+
'⚠️ Error occurred ⚠️'
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
#
|
|
33
|
+
# A *NoMethodError* occurred.
|
|
34
|
+
# 3 *NoMethodError* occurred.
|
|
35
|
+
# A *NoMethodError* occurred in *home#index*.
|
|
36
|
+
#
|
|
37
|
+
def subtitle
|
|
38
|
+
errors_text = if errors_count > 1
|
|
39
|
+
errors_count
|
|
40
|
+
else
|
|
41
|
+
exception.class.to_s =~ /^[aeiou]/i ? 'An' : 'A'
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
in_action = " in *#{controller_and_action}*" if controller
|
|
45
|
+
|
|
46
|
+
"#{errors_text} *#{exception.class}* occurred#{in_action}."
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
#
|
|
50
|
+
#
|
|
51
|
+
# *Request:*
|
|
52
|
+
# ```
|
|
53
|
+
# * url : https://www.example.com/
|
|
54
|
+
# * http_method : GET
|
|
55
|
+
# * ip_address : 127.0.0.1
|
|
56
|
+
# * parameters : {"controller"=>"home", "action"=>"index"}
|
|
57
|
+
# * timestamp : 2019-01-01 00:00:00 UTC
|
|
58
|
+
# ```
|
|
59
|
+
#
|
|
60
|
+
def request_message
|
|
61
|
+
request = ActionDispatch::Request.new(env) if env
|
|
62
|
+
return unless request
|
|
63
|
+
|
|
64
|
+
[
|
|
65
|
+
'```',
|
|
66
|
+
"* url : #{request.original_url}",
|
|
67
|
+
"* http_method : #{request.method}",
|
|
68
|
+
"* ip_address : #{request.remote_ip}",
|
|
69
|
+
"* parameters : #{request.filtered_parameters}",
|
|
70
|
+
"* timestamp : #{Time.current}",
|
|
71
|
+
'```'
|
|
72
|
+
].join("\n")
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
#
|
|
76
|
+
#
|
|
77
|
+
# *Backtrace:*
|
|
78
|
+
# ```
|
|
79
|
+
# * app/controllers/my_controller.rb:99:in `specific_function'
|
|
80
|
+
# * app/controllers/my_controller.rb:70:in `specific_param'
|
|
81
|
+
# * app/controllers/my_controller.rb:53:in `my_controller_params'
|
|
82
|
+
# ```
|
|
83
|
+
#
|
|
84
|
+
def backtrace_message
|
|
85
|
+
backtrace = exception.backtrace ? clean_backtrace(exception) : nil
|
|
86
|
+
|
|
87
|
+
return unless backtrace
|
|
88
|
+
|
|
89
|
+
text = []
|
|
90
|
+
|
|
91
|
+
text << '```'
|
|
92
|
+
backtrace.first(3).each { |line| text << "* #{line}" }
|
|
93
|
+
text << '```'
|
|
94
|
+
|
|
95
|
+
text.join("\n")
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
#
|
|
99
|
+
# home#index
|
|
100
|
+
#
|
|
101
|
+
def controller_and_action
|
|
102
|
+
"#{controller.controller_name}##{controller.action_name}" if controller
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
private
|
|
106
|
+
|
|
107
|
+
attr_reader :exception, :env, :errors_count
|
|
108
|
+
|
|
109
|
+
def rails_app_name
|
|
110
|
+
return unless defined?(::Rails) && ::Rails.respond_to?(:application)
|
|
111
|
+
Rails.application.class.parent_name.underscore
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def controller
|
|
115
|
+
env['action_controller.instance'] if env
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
@@ -2,14 +2,13 @@ require 'active_support/deprecation'
|
|
|
2
2
|
|
|
3
3
|
module ExceptionNotifier
|
|
4
4
|
class Notifier
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
ExceptionNotifier.registered_exception_notifier(:email).create_email(exception, options.merge(:env => env))
|
|
5
|
+
def self.exception_notification(env, exception, options = {})
|
|
6
|
+
ActiveSupport::Deprecation.warn 'Please use ExceptionNotifier.notify_exception(exception, options.merge(env: env)).'
|
|
7
|
+
ExceptionNotifier.registered_exception_notifier(:email).create_email(exception, options.merge(env: env))
|
|
9
8
|
end
|
|
10
9
|
|
|
11
|
-
def self.background_exception_notification(exception, options={})
|
|
12
|
-
ActiveSupport::Deprecation.warn
|
|
10
|
+
def self.background_exception_notification(exception, options = {})
|
|
11
|
+
ActiveSupport::Deprecation.warn 'Please use ExceptionNotifier.notify_exception(exception, options).'
|
|
13
12
|
ExceptionNotifier.registered_exception_notifier(:email).create_email(exception, options)
|
|
14
13
|
end
|
|
15
14
|
end
|
|
@@ -15,21 +15,65 @@ module ExceptionNotifier
|
|
|
15
15
|
@message_opts = options.fetch(:additional_parameters, {})
|
|
16
16
|
@color = @message_opts.delete(:color) { 'danger' }
|
|
17
17
|
@notifier = Slack::Notifier.new webhook_url, options
|
|
18
|
-
rescue
|
|
18
|
+
rescue StandardError
|
|
19
19
|
@notifier = nil
|
|
20
20
|
end
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
-
def call(exception, options={})
|
|
23
|
+
def call(exception, options = {})
|
|
24
|
+
clean_message = exception.message.tr('`', "'")
|
|
25
|
+
attchs = attchs(exception, clean_message, options)
|
|
26
|
+
|
|
27
|
+
return unless valid?
|
|
28
|
+
|
|
29
|
+
args = [exception, options, clean_message, @message_opts.merge(attachments: attchs)]
|
|
30
|
+
send_notice(*args) do |_msg, message_opts|
|
|
31
|
+
message_opts[:channel] = options[:channel] if options.key?(:channel)
|
|
32
|
+
|
|
33
|
+
@notifier.ping '', message_opts
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
protected
|
|
38
|
+
|
|
39
|
+
def valid?
|
|
40
|
+
!@notifier.nil?
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def deep_reject(hash, block)
|
|
44
|
+
hash.each do |k, v|
|
|
45
|
+
deep_reject(v, block) if v.is_a?(Hash)
|
|
46
|
+
|
|
47
|
+
hash.delete(k) if block.call(k, v)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
def attchs(exception, clean_message, options)
|
|
54
|
+
text, data = information_from_options(exception.class, options)
|
|
55
|
+
backtrace = clean_backtrace(exception) if exception.backtrace
|
|
56
|
+
fields = fields(clean_message, backtrace, data)
|
|
57
|
+
|
|
58
|
+
[color: @color, text: text, fields: fields, mrkdwn_in: %w[text fields]]
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def information_from_options(exception_class, options)
|
|
24
62
|
errors_count = options[:accumulated_errors_count].to_i
|
|
25
|
-
measure_word = errors_count > 1 ? errors_count : (exception.class.to_s =~ /^[aeiou]/i ? 'An' : 'A')
|
|
26
|
-
exception_name = "*#{measure_word}* `#{exception.class.to_s}`"
|
|
27
63
|
|
|
28
|
-
if
|
|
64
|
+
measure_word = if errors_count > 1
|
|
65
|
+
errors_count
|
|
66
|
+
else
|
|
67
|
+
exception_class.to_s =~ /^[aeiou]/i ? 'An' : 'A'
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
exception_name = "*#{measure_word}* `#{exception_class}`"
|
|
71
|
+
env = options[:env]
|
|
72
|
+
|
|
73
|
+
if env.nil?
|
|
29
74
|
data = options[:data] || {}
|
|
30
75
|
text = "#{exception_name} *occured in background*\n"
|
|
31
76
|
else
|
|
32
|
-
env = options[:env]
|
|
33
77
|
data = (env['exception_notifier.exception_data'] || {}).merge(options[:data] || {})
|
|
34
78
|
|
|
35
79
|
kontroller = env['action_controller.instance']
|
|
@@ -39,50 +83,29 @@ module ExceptionNotifier
|
|
|
39
83
|
text += "\n"
|
|
40
84
|
end
|
|
41
85
|
|
|
42
|
-
|
|
43
|
-
|
|
86
|
+
[text, data]
|
|
87
|
+
end
|
|
44
88
|
|
|
45
|
-
|
|
89
|
+
def fields(clean_message, backtrace, data)
|
|
90
|
+
fields = [
|
|
91
|
+
{ title: 'Exception', value: clean_message },
|
|
92
|
+
{ title: 'Hostname', value: Socket.gethostname }
|
|
93
|
+
]
|
|
46
94
|
|
|
47
|
-
if
|
|
48
|
-
formatted_backtrace = "```#{
|
|
49
|
-
fields
|
|
95
|
+
if backtrace
|
|
96
|
+
formatted_backtrace = "```#{backtrace.first(@backtrace_lines).join("\n")}```"
|
|
97
|
+
fields << { title: 'Backtrace', value: formatted_backtrace }
|
|
50
98
|
end
|
|
51
99
|
|
|
52
100
|
unless data.empty?
|
|
53
101
|
deep_reject(data, @ignore_data_if) if @ignore_data_if.is_a?(Proc)
|
|
54
|
-
data_string = data.map{|k,v| "#{k}: #{v}"}.join("\n")
|
|
55
|
-
fields
|
|
102
|
+
data_string = data.map { |k, v| "#{k}: #{v}" }.join("\n")
|
|
103
|
+
fields << { title: 'Data', value: "```#{data_string}```" }
|
|
56
104
|
end
|
|
57
105
|
|
|
58
106
|
fields.concat(@additional_fields) if @additional_fields
|
|
59
107
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
if valid?
|
|
63
|
-
send_notice(exception, options, clean_message, @message_opts.merge(attachments: attchs)) do |msg, message_opts|
|
|
64
|
-
@notifier.ping '', message_opts
|
|
65
|
-
end
|
|
66
|
-
end
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
protected
|
|
70
|
-
|
|
71
|
-
def valid?
|
|
72
|
-
!@notifier.nil?
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
def deep_reject(hash, block)
|
|
76
|
-
hash.each do |k, v|
|
|
77
|
-
if v.is_a?(Hash)
|
|
78
|
-
deep_reject(v, block)
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
if block.call(k, v)
|
|
82
|
-
hash.delete(k)
|
|
83
|
-
end
|
|
84
|
-
end
|
|
108
|
+
fields
|
|
85
109
|
end
|
|
86
|
-
|
|
87
110
|
end
|
|
88
111
|
end
|
|
@@ -3,9 +3,9 @@ module ExceptionNotifier
|
|
|
3
3
|
def initialize(options)
|
|
4
4
|
super
|
|
5
5
|
|
|
6
|
-
raise ArgumentError
|
|
7
|
-
raise ArgumentError
|
|
8
|
-
raise ArgumentError
|
|
6
|
+
raise ArgumentError, "You must provide 'region' option" unless options[:region]
|
|
7
|
+
raise ArgumentError, "You must provide 'access_key_id' option" unless options[:access_key_id]
|
|
8
|
+
raise ArgumentError, "You must provide 'secret_access_key' option" unless options[:secret_access_key]
|
|
9
9
|
|
|
10
10
|
@notifier = Aws::SNS::Client.new(
|
|
11
11
|
region: options[:region],
|
|
@@ -35,8 +35,8 @@ module ExceptionNotifier
|
|
|
35
35
|
def build_subject(exception, options)
|
|
36
36
|
subject = "#{options[:sns_prefix]} - "
|
|
37
37
|
subject << accumulated_exception_name(exception, options)
|
|
38
|
-
subject <<
|
|
39
|
-
subject.length > 120 ? subject[0...120] +
|
|
38
|
+
subject << ' occurred'
|
|
39
|
+
subject.length > 120 ? subject[0...120] + '...' : subject
|
|
40
40
|
end
|
|
41
41
|
|
|
42
42
|
def build_message(exception, options)
|
|
@@ -57,16 +57,22 @@ module ExceptionNotifier
|
|
|
57
57
|
text += "Exception: #{exception.message}\n"
|
|
58
58
|
text += "Hostname: #{Socket.gethostname}\n"
|
|
59
59
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
60
|
+
return unless exception.backtrace
|
|
61
|
+
|
|
62
|
+
formatted_backtrace = exception.backtrace.first(options[:backtrace_lines]).join("\n").to_s
|
|
63
|
+
text + "Backtrace:\n#{formatted_backtrace}\n"
|
|
64
64
|
end
|
|
65
65
|
|
|
66
66
|
def accumulated_exception_name(exception, options)
|
|
67
67
|
errors_count = options[:accumulated_errors_count].to_i
|
|
68
|
-
|
|
69
|
-
|
|
68
|
+
|
|
69
|
+
measure_word = if errors_count > 1
|
|
70
|
+
errors_count
|
|
71
|
+
else
|
|
72
|
+
exception.class.to_s =~ /^[aeiou]/i ? 'An' : 'A'
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
"#{measure_word} #{exception.class}"
|
|
70
76
|
end
|
|
71
77
|
|
|
72
78
|
def default_options
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
require 'action_dispatch'
|
|
2
2
|
require 'active_support/core_ext/time'
|
|
3
|
+
require 'json'
|
|
3
4
|
|
|
4
5
|
module ExceptionNotifier
|
|
5
6
|
class TeamsNotifier < BaseNotifier
|
|
6
7
|
include ExceptionNotifier::BacktraceCleaner
|
|
7
8
|
|
|
8
9
|
class MissingController
|
|
9
|
-
def method_missing(*args, &block)
|
|
10
|
-
end
|
|
10
|
+
def method_missing(*args, &block); end
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
attr_accessor :httparty
|
|
@@ -18,21 +18,23 @@ module ExceptionNotifier
|
|
|
18
18
|
@httparty = HTTParty
|
|
19
19
|
end
|
|
20
20
|
|
|
21
|
-
def call(exception, options={})
|
|
21
|
+
def call(exception, options = {})
|
|
22
22
|
@options = options.merge(@default_options)
|
|
23
23
|
@exception = exception
|
|
24
24
|
@backtrace = exception.backtrace ? clean_backtrace(exception) : nil
|
|
25
25
|
|
|
26
26
|
@env = @options.delete(:env)
|
|
27
27
|
|
|
28
|
-
@application_name = @options.delete(:app_name) ||
|
|
28
|
+
@application_name = @options.delete(:app_name) || rails_app_name
|
|
29
29
|
@gitlab_url = @options.delete(:git_url)
|
|
30
30
|
@jira_url = @options.delete(:jira_url)
|
|
31
31
|
|
|
32
32
|
@webhook_url = @options.delete(:webhook_url)
|
|
33
|
-
raise ArgumentError
|
|
33
|
+
raise ArgumentError, "You must provide 'webhook_url' parameter." unless @webhook_url
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
if @env.nil?
|
|
36
|
+
@controller = @request_items = nil
|
|
37
|
+
else
|
|
36
38
|
@controller = @env['action_controller.instance'] || MissingController.new
|
|
37
39
|
|
|
38
40
|
request = ActionDispatch::Request.new(@env)
|
|
@@ -43,19 +45,17 @@ module ExceptionNotifier
|
|
|
43
45
|
parameters: request.filtered_parameters,
|
|
44
46
|
timestamp: Time.current }
|
|
45
47
|
|
|
46
|
-
if request.session[
|
|
47
|
-
current_user = User.find(request.session[
|
|
48
|
-
@request_items
|
|
48
|
+
if request.session['warden.user.user.key']
|
|
49
|
+
current_user = User.find(request.session['warden.user.user.key'][0][0])
|
|
50
|
+
@request_items[:current_user] = { id: current_user.id, email: current_user.email }
|
|
49
51
|
end
|
|
50
|
-
else
|
|
51
|
-
@controller = @request_items = nil
|
|
52
52
|
end
|
|
53
53
|
|
|
54
54
|
payload = message_text
|
|
55
55
|
|
|
56
56
|
@options[:body] = payload.to_json
|
|
57
57
|
@options[:headers] ||= {}
|
|
58
|
-
@options[:headers]
|
|
58
|
+
@options[:headers]['Content-Type'] = 'application/json'
|
|
59
59
|
@options[:debug_output] = $stdout
|
|
60
60
|
|
|
61
61
|
@httparty.post(@webhook_url, @options)
|
|
@@ -67,17 +67,17 @@ module ExceptionNotifier
|
|
|
67
67
|
errors_count = @options[:accumulated_errors_count].to_i
|
|
68
68
|
|
|
69
69
|
text = {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
70
|
+
'@type' => 'MessageCard',
|
|
71
|
+
'@context' => 'http://schema.org/extensions',
|
|
72
|
+
'summary' => "#{@application_name} Exception Alert",
|
|
73
|
+
'title' => "⚠️ Exception Occurred in #{env_name} ⚠️",
|
|
74
|
+
'sections' => [
|
|
75
75
|
{
|
|
76
|
-
|
|
77
|
-
|
|
76
|
+
'activityTitle' => "#{errors_count > 1 ? errors_count : 'A'} *#{@exception.class}* occurred" + (@controller ? " in *#{controller_and_method}*." : '.'),
|
|
77
|
+
'activitySubtitle' => @exception.message.to_s
|
|
78
78
|
}
|
|
79
79
|
],
|
|
80
|
-
|
|
80
|
+
'potentialAction' => []
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
text['sections'].push details
|
|
@@ -90,8 +90,8 @@ module ExceptionNotifier
|
|
|
90
90
|
|
|
91
91
|
def details
|
|
92
92
|
details = {
|
|
93
|
-
|
|
94
|
-
|
|
93
|
+
'title' => 'Details',
|
|
94
|
+
'facts' => []
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
details['facts'].push message_request unless @request_items.nil?
|
|
@@ -102,59 +102,59 @@ module ExceptionNotifier
|
|
|
102
102
|
|
|
103
103
|
def message_request
|
|
104
104
|
{
|
|
105
|
-
|
|
106
|
-
|
|
105
|
+
'name' => 'Request',
|
|
106
|
+
'value' => "#{hash_presentation(@request_items)}\n "
|
|
107
107
|
}
|
|
108
108
|
end
|
|
109
109
|
|
|
110
110
|
def message_backtrace(size = 3)
|
|
111
111
|
text = []
|
|
112
112
|
size = @backtrace.size < size ? @backtrace.size : size
|
|
113
|
-
text <<
|
|
114
|
-
size.times { |i| text <<
|
|
115
|
-
text <<
|
|
113
|
+
text << '```'
|
|
114
|
+
size.times { |i| text << '* ' + @backtrace[i] }
|
|
115
|
+
text << '```'
|
|
116
116
|
|
|
117
117
|
{
|
|
118
|
-
|
|
119
|
-
|
|
118
|
+
'name' => 'Backtrace',
|
|
119
|
+
'value' => text.join(" \n").to_s
|
|
120
120
|
}
|
|
121
121
|
end
|
|
122
122
|
|
|
123
123
|
def gitlab_view_link
|
|
124
124
|
{
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
125
|
+
'@type' => 'ViewAction',
|
|
126
|
+
'name' => "\u{1F98A} View in GitLab",
|
|
127
|
+
'target' => [
|
|
128
128
|
"#{@gitlab_url}/#{@application_name}"
|
|
129
129
|
]
|
|
130
130
|
}
|
|
131
131
|
end
|
|
132
132
|
|
|
133
133
|
def gitlab_issue_link
|
|
134
|
-
link = [@gitlab_url, @application_name,
|
|
134
|
+
link = [@gitlab_url, @application_name, 'issues', 'new'].join('/')
|
|
135
135
|
params = {
|
|
136
|
-
|
|
136
|
+
'issue[title]' => ['[BUG] Error 500 :',
|
|
137
137
|
controller_and_method,
|
|
138
138
|
"(#{@exception.class})",
|
|
139
|
-
@exception.message].compact.join(
|
|
139
|
+
@exception.message].compact.join(' ')
|
|
140
140
|
}.to_query
|
|
141
141
|
|
|
142
142
|
{
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
143
|
+
'@type' => 'ViewAction',
|
|
144
|
+
'name' => "\u{1F98A} Create Issue in GitLab",
|
|
145
|
+
'target' => [
|
|
146
146
|
"#{link}/?#{params}"
|
|
147
|
-
|
|
147
|
+
]
|
|
148
148
|
}
|
|
149
149
|
end
|
|
150
150
|
|
|
151
151
|
def jira_issue_link
|
|
152
152
|
{
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
153
|
+
'@type' => 'ViewAction',
|
|
154
|
+
'name' => '🐞 Create Issue in Jira',
|
|
155
|
+
'target' => [
|
|
156
156
|
"#{@jira_url}/secure/CreateIssue!default.jspa"
|
|
157
|
-
|
|
157
|
+
]
|
|
158
158
|
}
|
|
159
159
|
end
|
|
160
160
|
|
|
@@ -162,7 +162,7 @@ module ExceptionNotifier
|
|
|
162
162
|
if @controller
|
|
163
163
|
"#{@controller.controller_name}##{@controller.action_name}"
|
|
164
164
|
else
|
|
165
|
-
|
|
165
|
+
''
|
|
166
166
|
end
|
|
167
167
|
end
|
|
168
168
|
|
|
@@ -175,5 +175,19 @@ module ExceptionNotifier
|
|
|
175
175
|
|
|
176
176
|
text.join(" \n")
|
|
177
177
|
end
|
|
178
|
+
|
|
179
|
+
def rails_app_name
|
|
180
|
+
return unless defined?(Rails) && Rails.respond_to?(:application)
|
|
181
|
+
|
|
182
|
+
if ::Gem::Version.new(Rails.version) >= ::Gem::Version.new('6.0')
|
|
183
|
+
Rails.application.class.module_parent_name.underscore
|
|
184
|
+
else
|
|
185
|
+
Rails.application.class.parent_name.underscore
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def env_name
|
|
190
|
+
Rails.env if defined?(Rails) && Rails.respond_to?(:env)
|
|
191
|
+
end
|
|
178
192
|
end
|
|
179
193
|
end
|