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.
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")