exception_notification 4.3.0 → 4.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (131) hide show
  1. checksums.yaml +5 -5
  2. data/Appraisals +4 -2
  3. data/CHANGELOG.rdoc +47 -0
  4. data/CONTRIBUTING.md +18 -0
  5. data/Gemfile +3 -1
  6. data/README.md +97 -945
  7. data/Rakefile +4 -2
  8. data/docs/notifiers/campfire.md +50 -0
  9. data/docs/notifiers/custom.md +42 -0
  10. data/docs/notifiers/datadog.md +51 -0
  11. data/docs/notifiers/email.md +195 -0
  12. data/docs/notifiers/google_chat.md +31 -0
  13. data/docs/notifiers/hipchat.md +66 -0
  14. data/docs/notifiers/irc.md +97 -0
  15. data/docs/notifiers/mattermost.md +115 -0
  16. data/docs/notifiers/slack.md +161 -0
  17. data/docs/notifiers/sns.md +37 -0
  18. data/docs/notifiers/teams.md +54 -0
  19. data/docs/notifiers/webhook.md +60 -0
  20. data/examples/sample_app.rb +56 -0
  21. data/examples/sinatra/Gemfile +8 -6
  22. data/examples/sinatra/config.ru +3 -1
  23. data/examples/sinatra/sinatra_app.rb +19 -11
  24. data/exception_notification.gemspec +30 -24
  25. data/gemfiles/{rails4_0.gemfile → rails5_2.gemfile} +2 -2
  26. data/gemfiles/{rails4_1.gemfile → rails6_0.gemfile} +2 -2
  27. data/gemfiles/{rails4_2.gemfile → rails6_1.gemfile} +2 -2
  28. data/gemfiles/{rails5_0.gemfile → rails7_0.gemfile} +2 -2
  29. data/lib/exception_notification/rack.rb +28 -30
  30. data/lib/exception_notification/rails.rb +2 -0
  31. data/lib/exception_notification/resque.rb +10 -10
  32. data/lib/exception_notification/sidekiq.rb +10 -12
  33. data/lib/exception_notification/version.rb +5 -0
  34. data/lib/exception_notification.rb +3 -0
  35. data/lib/exception_notifier/base_notifier.rb +10 -5
  36. data/lib/exception_notifier/datadog_notifier.rb +156 -0
  37. data/lib/exception_notifier/email_notifier.rb +73 -88
  38. data/lib/exception_notifier/google_chat_notifier.rb +27 -119
  39. data/lib/exception_notifier/hipchat_notifier.rb +13 -12
  40. data/lib/exception_notifier/irc_notifier.rb +36 -33
  41. data/lib/exception_notifier/mattermost_notifier.rb +54 -137
  42. data/lib/exception_notifier/modules/backtrace_cleaner.rb +2 -2
  43. data/lib/exception_notifier/modules/error_grouping.rb +24 -13
  44. data/lib/exception_notifier/modules/formatter.rb +125 -0
  45. data/lib/exception_notifier/notifier.rb +9 -6
  46. data/lib/exception_notifier/slack_notifier.rb +65 -40
  47. data/lib/exception_notifier/sns_notifier.rb +23 -13
  48. data/lib/exception_notifier/teams_notifier.rb +67 -46
  49. data/lib/exception_notifier/views/exception_notifier/_backtrace.html.erb +1 -1
  50. data/lib/exception_notifier/views/exception_notifier/_environment.text.erb +1 -1
  51. data/lib/exception_notifier/views/exception_notifier/_request.text.erb +1 -1
  52. data/lib/exception_notifier/views/exception_notifier/exception_notification.html.erb +2 -2
  53. data/lib/exception_notifier/views/exception_notifier/exception_notification.text.erb +2 -2
  54. data/lib/exception_notifier/webhook_notifier.rb +17 -14
  55. data/lib/exception_notifier.rb +65 -10
  56. data/lib/generators/exception_notification/install_generator.rb +11 -5
  57. data/lib/generators/exception_notification/templates/{exception_notification.rb → exception_notification.rb.erb} +13 -11
  58. data/test/exception_notification/rack_test.rb +75 -13
  59. data/test/exception_notification/resque_test.rb +54 -0
  60. data/test/exception_notifier/datadog_notifier_test.rb +153 -0
  61. data/test/exception_notifier/email_notifier_test.rb +275 -153
  62. data/test/exception_notifier/google_chat_notifier_test.rb +158 -101
  63. data/test/exception_notifier/hipchat_notifier_test.rb +84 -81
  64. data/test/exception_notifier/irc_notifier_test.rb +36 -34
  65. data/test/exception_notifier/mattermost_notifier_test.rb +213 -67
  66. data/test/exception_notifier/modules/error_grouping_test.rb +41 -40
  67. data/test/exception_notifier/modules/formatter_test.rb +152 -0
  68. data/test/exception_notifier/sidekiq_test.rb +9 -17
  69. data/test/exception_notifier/slack_notifier_test.rb +66 -63
  70. data/test/exception_notifier/sns_notifier_test.rb +84 -32
  71. data/test/exception_notifier/teams_notifier_test.rb +25 -26
  72. data/test/exception_notifier/webhook_notifier_test.rb +52 -48
  73. data/test/exception_notifier_test.rb +150 -41
  74. data/test/support/exception_notifier_helper.rb +14 -0
  75. data/test/{dummy/app → support}/views/exception_notifier/_new_bkg_section.html.erb +0 -0
  76. data/test/{dummy/app → support}/views/exception_notifier/_new_bkg_section.text.erb +0 -0
  77. data/test/{dummy/app → support}/views/exception_notifier/_new_section.html.erb +0 -0
  78. data/test/{dummy/app → support}/views/exception_notifier/_new_section.text.erb +0 -0
  79. data/test/test_helper.rb +14 -13
  80. metadata +134 -175
  81. data/gemfiles/rails5_1.gemfile +0 -7
  82. data/lib/exception_notifier/campfire_notifier.rb +0 -40
  83. data/test/dummy/.gitignore +0 -4
  84. data/test/dummy/Rakefile +0 -7
  85. data/test/dummy/app/controllers/application_controller.rb +0 -3
  86. data/test/dummy/app/controllers/posts_controller.rb +0 -30
  87. data/test/dummy/app/helpers/application_helper.rb +0 -2
  88. data/test/dummy/app/helpers/posts_helper.rb +0 -2
  89. data/test/dummy/app/models/post.rb +0 -2
  90. data/test/dummy/app/views/layouts/application.html.erb +0 -14
  91. data/test/dummy/app/views/posts/_form.html.erb +0 -0
  92. data/test/dummy/app/views/posts/new.html.erb +0 -0
  93. data/test/dummy/app/views/posts/show.html.erb +0 -0
  94. data/test/dummy/config/application.rb +0 -42
  95. data/test/dummy/config/boot.rb +0 -6
  96. data/test/dummy/config/database.yml +0 -22
  97. data/test/dummy/config/environment.rb +0 -17
  98. data/test/dummy/config/environments/development.rb +0 -25
  99. data/test/dummy/config/environments/production.rb +0 -50
  100. data/test/dummy/config/environments/test.rb +0 -35
  101. data/test/dummy/config/initializers/backtrace_silencers.rb +0 -7
  102. data/test/dummy/config/initializers/inflections.rb +0 -10
  103. data/test/dummy/config/initializers/mime_types.rb +0 -5
  104. data/test/dummy/config/initializers/secret_token.rb +0 -8
  105. data/test/dummy/config/initializers/session_store.rb +0 -8
  106. data/test/dummy/config/locales/en.yml +0 -5
  107. data/test/dummy/config/routes.rb +0 -3
  108. data/test/dummy/config.ru +0 -4
  109. data/test/dummy/db/migrate/20110729022608_create_posts.rb +0 -15
  110. data/test/dummy/db/schema.rb +0 -24
  111. data/test/dummy/db/seeds.rb +0 -7
  112. data/test/dummy/lib/tasks/.gitkeep +0 -0
  113. data/test/dummy/public/404.html +0 -26
  114. data/test/dummy/public/422.html +0 -26
  115. data/test/dummy/public/500.html +0 -26
  116. data/test/dummy/public/favicon.ico +0 -0
  117. data/test/dummy/public/images/rails.png +0 -0
  118. data/test/dummy/public/index.html +0 -239
  119. data/test/dummy/public/javascripts/application.js +0 -2
  120. data/test/dummy/public/javascripts/controls.js +0 -965
  121. data/test/dummy/public/javascripts/dragdrop.js +0 -974
  122. data/test/dummy/public/javascripts/effects.js +0 -1123
  123. data/test/dummy/public/javascripts/prototype.js +0 -6001
  124. data/test/dummy/public/javascripts/rails.js +0 -191
  125. data/test/dummy/public/robots.txt +0 -5
  126. data/test/dummy/public/stylesheets/.gitkeep +0 -0
  127. data/test/dummy/public/stylesheets/scaffold.css +0 -56
  128. data/test/dummy/script/rails +0 -6
  129. data/test/dummy/test/functional/posts_controller_test.rb +0 -237
  130. data/test/dummy/test/test_helper.rb +0 -7
  131. 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
- def self.exception_notification(env, exception, options={})
7
- ActiveSupport::Deprecation.warn "Please use ExceptionNotifier.notify_exception(exception, options.merge(:env => env))."
8
- ExceptionNotifier.registered_exception_notifier(:email).create_email(exception, options.merge(:env => env))
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 "Please use ExceptionNotifier.notify_exception(exception, options)."
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 options[:env].nil?
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
- clean_message = exception.message.gsub("`", "'")
43
- fields = [ { title: 'Exception', value: clean_message } ]
88
+ [text, data]
89
+ end
44
90
 
45
- fields.push({ title: 'Hostname', value: Socket.gethostname })
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 exception.backtrace
48
- formatted_backtrace = "```#{exception.backtrace.first(@backtrace_lines).join("\n")}```"
49
- fields.push({ title: 'Backtrace', value: formatted_backtrace })
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.push({ title: 'Data', value: "```#{data_string}```" })
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
- attchs = [color: @color, text: text, fields: fields, mrkdwn_in: %w(text fields)]
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.new "You must provide 'region' option" unless options[:region]
7
- raise ArgumentError.new "You must provide 'access_key_id' option" unless options[:access_key_id]
8
- raise ArgumentError.new "You must provide 'secret_access_key' option" unless options[:secret_access_key]
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 = "#{options[:sns_prefix]} - "
37
- subject << accumulated_exception_name(exception, options)
38
- subject << " occurred"
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
- if exception.backtrace
61
- formatted_backtrace = "#{exception.backtrace.first(options[:backtrace_lines]).join("\n")}"
62
- text += "Backtrace:\n#{formatted_backtrace}\n"
63
- end
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
- measure_word = errors_count > 1 ? errors_count : (exception.class.to_s =~ /^[aeiou]/i ? 'An' : 'A')
69
- "#{measure_word} #{exception.class.to_s}"
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) || Rails.application.class.parent_name.underscore
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.new "You must provide 'webhook_url' parameter." unless @webhook_url
35
+ raise ArgumentError, "You must provide 'webhook_url' parameter." unless @webhook_url
34
36
 
35
- unless @env.nil?
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["warden.user.user.key"]
47
- current_user = User.find(request.session["warden.user.user.key"][0][0])
48
- @request_items.merge!({ current_user: { id: current_user.id, email: current_user.email } })
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].merge!({ 'Content-Type' => 'application/json' })
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
- "@type" => "MessageCard",
71
- "@context" => "http://schema.org/extensions",
72
- "summary" => "#{@application_name} Exception Alert",
73
- "title" => "⚠️ Exception Occurred in #{Rails.env} ⚠️",
74
- "sections" => [
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
- "activityTitle" => "#{errors_count > 1 ? errors_count : 'A'} *#{@exception.class}* occurred" + if @controller then " in *#{controller_and_method}*." else "." end,
77
- "activitySubtitle" => "#{@exception.message}"
76
+ 'activityTitle' => activity_title,
77
+ 'activitySubtitle' => @exception.message.to_s
78
78
  }
79
79
  ],
80
- "potentialAction" => []
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
- "title" => "Details",
94
- "facts" => []
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
- "name" => "Request",
106
- "value" => "#{hash_presentation(@request_items)}\n "
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 << "* " + @backtrace[i] }
115
- text << "```"
120
+ text << '```'
121
+ size.times { |i| text << '* ' + @backtrace[i] }
122
+ text << '```'
116
123
 
117
124
  {
118
- "name" => "Backtrace",
119
- "value" => "#{text.join(" \n")}"
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
- "@type" => "ViewAction",
126
- "name" => "🦊 View in GitLab",
127
- "target" => [
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, "issues", "new"].join("/")
141
+ link = [@gitlab_url, @application_name, 'issues', 'new'].join('/')
135
142
  params = {
136
- "issue[title]" => ["[BUG] Error 500 :",
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
- "@type" => "ViewAction",
144
- "name" => "🦊 Create Issue in GitLab",
145
- "target" => [
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
- "@type" => "ViewAction",
154
- "name" => "🐞 Create Issue in Jira",
155
- "target" => [
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,3 +1,3 @@
1
1
  <pre style="font-size: 12px; padding: 10px; border: 1px solid #e1e1e8; background-color:#f5f5f5">
2
- <%= @backtrace.join("\n") %>
2
+ <%= @backtrace.join("\n") %>
3
3
  </pre>
@@ -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 -%>
@@ -5,6 +5,6 @@
5
5
  * Timestamp : <%= raw @timestamp %>
6
6
  * Server : <%= raw Socket.gethostname %>
7
7
  <% if defined?(Rails) && Rails.respond_to?(:root) %>
8
- * Rails root : <%= raw Rails.root %>
8
+ * Rails root : <%= raw Rails.root %>
9
9
  <% end %>
10
10
  * Process: <%= raw $$ %>
@@ -11,11 +11,11 @@
11
11
  begin
12
12
  summary = render(section).strip
13
13
  unless summary.blank?
14
- title = render("title", :title => section).strip
14
+ title = render("title", title: section).strip
15
15
  [title, summary]
16
16
  end
17
17
  rescue Exception => e
18
- title = render("title", :title => section).strip
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", :title => section).strip
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", :title => section).strip
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")