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