exception_notification 4.2.0 → 4.4.1

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 (134) hide show
  1. checksums.yaml +5 -5
  2. data/Appraisals +4 -3
  3. data/CHANGELOG.rdoc +57 -1
  4. data/CONTRIBUTING.md +21 -2
  5. data/Gemfile +3 -1
  6. data/README.md +106 -789
  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 -23
  25. data/gemfiles/rails4_0.gemfile +1 -2
  26. data/gemfiles/rails4_1.gemfile +1 -2
  27. data/gemfiles/rails4_2.gemfile +1 -2
  28. data/gemfiles/rails5_0.gemfile +1 -2
  29. data/gemfiles/rails5_1.gemfile +7 -0
  30. data/gemfiles/rails5_2.gemfile +7 -0
  31. data/gemfiles/rails6_0.gemfile +7 -0
  32. data/lib/exception_notification.rb +3 -0
  33. data/lib/exception_notification/rack.rb +34 -27
  34. data/lib/exception_notification/rails.rb +3 -0
  35. data/lib/exception_notification/resque.rb +10 -10
  36. data/lib/exception_notification/sidekiq.rb +10 -12
  37. data/lib/exception_notification/version.rb +5 -0
  38. data/lib/exception_notifier.rb +79 -11
  39. data/lib/exception_notifier/base_notifier.rb +10 -5
  40. data/lib/exception_notifier/campfire_notifier.rb +14 -9
  41. data/lib/exception_notifier/datadog_notifier.rb +156 -0
  42. data/lib/exception_notifier/email_notifier.rb +78 -87
  43. data/lib/exception_notifier/google_chat_notifier.rb +44 -0
  44. data/lib/exception_notifier/hipchat_notifier.rb +16 -10
  45. data/lib/exception_notifier/irc_notifier.rb +38 -31
  46. data/lib/exception_notifier/mattermost_notifier.rb +54 -131
  47. data/lib/exception_notifier/modules/backtrace_cleaner.rb +2 -2
  48. data/lib/exception_notifier/modules/error_grouping.rb +87 -0
  49. data/lib/exception_notifier/modules/formatter.rb +121 -0
  50. data/lib/exception_notifier/notifier.rb +9 -6
  51. data/lib/exception_notifier/slack_notifier.rb +75 -32
  52. data/lib/exception_notifier/sns_notifier.rb +86 -0
  53. data/lib/exception_notifier/teams_notifier.rb +200 -0
  54. data/lib/exception_notifier/views/exception_notifier/_backtrace.html.erb +1 -1
  55. data/lib/exception_notifier/views/exception_notifier/_environment.text.erb +1 -1
  56. data/lib/exception_notifier/views/exception_notifier/_request.text.erb +1 -1
  57. data/lib/exception_notifier/views/exception_notifier/background_exception_notification.text.erb +9 -9
  58. data/lib/exception_notifier/views/exception_notifier/exception_notification.html.erb +2 -4
  59. data/lib/exception_notifier/views/exception_notifier/exception_notification.text.erb +2 -2
  60. data/lib/exception_notifier/webhook_notifier.rb +19 -16
  61. data/lib/generators/exception_notification/install_generator.rb +11 -5
  62. data/lib/generators/exception_notification/templates/{exception_notification.rb → exception_notification.rb.erb} +14 -12
  63. data/test/exception_notification/rack_test.rb +90 -4
  64. data/test/exception_notification/resque_test.rb +54 -0
  65. data/test/exception_notifier/campfire_notifier_test.rb +66 -39
  66. data/test/exception_notifier/datadog_notifier_test.rb +153 -0
  67. data/test/exception_notifier/email_notifier_test.rb +301 -145
  68. data/test/exception_notifier/google_chat_notifier_test.rb +185 -0
  69. data/test/exception_notifier/hipchat_notifier_test.rb +112 -65
  70. data/test/exception_notifier/irc_notifier_test.rb +48 -30
  71. data/test/exception_notifier/mattermost_notifier_test.rb +218 -55
  72. data/test/exception_notifier/modules/error_grouping_test.rb +167 -0
  73. data/test/exception_notifier/modules/formatter_test.rb +152 -0
  74. data/test/exception_notifier/sidekiq_test.rb +9 -6
  75. data/test/exception_notifier/slack_notifier_test.rb +109 -59
  76. data/test/exception_notifier/sns_notifier_test.rb +123 -0
  77. data/test/exception_notifier/teams_notifier_test.rb +92 -0
  78. data/test/exception_notifier/webhook_notifier_test.rb +68 -38
  79. data/test/exception_notifier_test.rb +220 -37
  80. data/test/support/exception_notifier_helper.rb +14 -0
  81. data/test/{dummy/app → support}/views/exception_notifier/_new_bkg_section.html.erb +0 -0
  82. data/test/{dummy/app → support}/views/exception_notifier/_new_bkg_section.text.erb +0 -0
  83. data/test/{dummy/app → support}/views/exception_notifier/_new_section.html.erb +0 -0
  84. data/test/{dummy/app → support}/views/exception_notifier/_new_section.text.erb +0 -0
  85. data/test/test_helper.rb +14 -13
  86. metadata +154 -162
  87. data/test/dummy/.gitignore +0 -4
  88. data/test/dummy/Rakefile +0 -7
  89. data/test/dummy/app/controllers/application_controller.rb +0 -3
  90. data/test/dummy/app/controllers/posts_controller.rb +0 -30
  91. data/test/dummy/app/helpers/application_helper.rb +0 -2
  92. data/test/dummy/app/helpers/posts_helper.rb +0 -2
  93. data/test/dummy/app/models/post.rb +0 -2
  94. data/test/dummy/app/views/layouts/application.html.erb +0 -14
  95. data/test/dummy/app/views/posts/_form.html.erb +0 -0
  96. data/test/dummy/app/views/posts/new.html.erb +0 -0
  97. data/test/dummy/app/views/posts/show.html.erb +0 -0
  98. data/test/dummy/config.ru +0 -4
  99. data/test/dummy/config/application.rb +0 -42
  100. data/test/dummy/config/boot.rb +0 -6
  101. data/test/dummy/config/database.yml +0 -22
  102. data/test/dummy/config/environment.rb +0 -17
  103. data/test/dummy/config/environments/development.rb +0 -25
  104. data/test/dummy/config/environments/production.rb +0 -50
  105. data/test/dummy/config/environments/test.rb +0 -38
  106. data/test/dummy/config/initializers/backtrace_silencers.rb +0 -7
  107. data/test/dummy/config/initializers/inflections.rb +0 -10
  108. data/test/dummy/config/initializers/mime_types.rb +0 -5
  109. data/test/dummy/config/initializers/secret_token.rb +0 -8
  110. data/test/dummy/config/initializers/session_store.rb +0 -8
  111. data/test/dummy/config/locales/en.yml +0 -5
  112. data/test/dummy/config/routes.rb +0 -3
  113. data/test/dummy/db/migrate/20110729022608_create_posts.rb +0 -15
  114. data/test/dummy/db/schema.rb +0 -24
  115. data/test/dummy/db/seeds.rb +0 -7
  116. data/test/dummy/lib/tasks/.gitkeep +0 -0
  117. data/test/dummy/public/404.html +0 -26
  118. data/test/dummy/public/422.html +0 -26
  119. data/test/dummy/public/500.html +0 -26
  120. data/test/dummy/public/favicon.ico +0 -0
  121. data/test/dummy/public/images/rails.png +0 -0
  122. data/test/dummy/public/index.html +0 -239
  123. data/test/dummy/public/javascripts/application.js +0 -2
  124. data/test/dummy/public/javascripts/controls.js +0 -965
  125. data/test/dummy/public/javascripts/dragdrop.js +0 -974
  126. data/test/dummy/public/javascripts/effects.js +0 -1123
  127. data/test/dummy/public/javascripts/prototype.js +0 -6001
  128. data/test/dummy/public/javascripts/rails.js +0 -191
  129. data/test/dummy/public/robots.txt +0 -5
  130. data/test/dummy/public/stylesheets/.gitkeep +0 -0
  131. data/test/dummy/public/stylesheets/scaffold.css +0 -56
  132. data/test/dummy/script/rails +0 -6
  133. data/test/dummy/test/functional/posts_controller_test.rb +0 -218
  134. data/test/dummy/test/test_helper.rb +0 -7
@@ -0,0 +1,121 @@
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
+ Rails.application.class.parent_name.underscore
115
+ end
116
+
117
+ def controller
118
+ env['action_controller.instance'] if env
119
+ end
120
+ end
121
+ 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
@@ -8,43 +10,29 @@ module ExceptionNotifier
8
10
  super
9
11
  begin
10
12
  @ignore_data_if = options[:ignore_data_if]
13
+ @backtrace_lines = options.fetch(:backtrace_lines, 10)
14
+ @additional_fields = options[:additional_fields]
11
15
 
12
16
  webhook_url = options.fetch(:webhook_url)
13
17
  @message_opts = options.fetch(:additional_parameters, {})
18
+ @color = @message_opts.delete(:color) { 'danger' }
14
19
  @notifier = Slack::Notifier.new webhook_url, options
15
- rescue
20
+ rescue StandardError
16
21
  @notifier = nil
17
22
  end
18
23
  end
19
24
 
20
- def call(exception, options={})
21
- env = options[:env] || {}
22
- title = "#{env['REQUEST_METHOD']} <#{env['REQUEST_URI']}>"
23
- data = (env['exception_notifier.exception_data'] || {}).merge(options[:data] || {})
24
- text = "*An exception occurred while doing*: `#{title}`\n"
25
-
26
- clean_message = exception.message.gsub("`", "'")
27
- fields = [ { title: 'Exception', value: clean_message} ]
28
-
29
- fields.push({ title: 'Hostname', value: Socket.gethostname })
30
-
31
- if exception.backtrace
32
- formatted_backtrace = "```#{exception.backtrace.join("\n")}```"
33
- fields.push({ title: 'Backtrace', value: formatted_backtrace })
34
- end
25
+ def call(exception, options = {})
26
+ clean_message = exception.message.tr('`', "'")
27
+ attchs = attchs(exception, clean_message, options)
35
28
 
36
- unless data.empty?
37
- deep_reject(data, @ignore_data_if) if @ignore_data_if.is_a?(Proc)
38
- data_string = data.map{|k,v| "#{k}: #{v}"}.join("\n")
39
- fields.push({ title: 'Data', value: "```#{data_string}```" })
40
- end
29
+ return unless valid?
41
30
 
42
- attchs = [color: 'danger', text: text, fields: fields, mrkdwn_in: %w(text fields)]
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)
43
34
 
44
- if valid?
45
- send_notice(exception, options, clean_message, @message_opts.merge(attachments: attchs)) do |msg, message_opts|
46
- @notifier.ping '', message_opts
47
- end
35
+ @notifier.ping '', message_opts
48
36
  end
49
37
  end
50
38
 
@@ -56,15 +44,70 @@ module ExceptionNotifier
56
44
 
57
45
  def deep_reject(hash, block)
58
46
  hash.each do |k, v|
59
- if v.is_a?(Hash)
60
- deep_reject(v, block)
61
- end
47
+ deep_reject(v, block) if v.is_a?(Hash)
62
48
 
63
- if block.call(k, v)
64
- hash.delete(k)
65
- end
49
+ hash.delete(k) if block.call(k, v)
66
50
  end
67
51
  end
68
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)
64
+ errors_count = options[:accumulated_errors_count].to_i
65
+
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?
76
+ data = options[:data] || {}
77
+ text = "#{exception_name} *occured in background*\n"
78
+ else
79
+ data = (env['exception_notifier.exception_data'] || {}).merge(options[:data] || {})
80
+
81
+ kontroller = env['action_controller.instance']
82
+ request = "#{env['REQUEST_METHOD']} <#{env['REQUEST_URI']}>"
83
+ text = "#{exception_name} *occurred while* `#{request}`"
84
+ text += " *was processed by* `#{kontroller.controller_name}##{kontroller.action_name}`" if kontroller
85
+ text += "\n"
86
+ end
87
+
88
+ [text, data]
89
+ end
90
+
91
+ def fields(clean_message, backtrace, data)
92
+ fields = [
93
+ { title: 'Exception', value: clean_message },
94
+ { title: 'Hostname', value: Socket.gethostname }
95
+ ]
96
+
97
+ if backtrace
98
+ formatted_backtrace = "```#{backtrace.first(@backtrace_lines).join("\n")}```"
99
+ fields << { title: 'Backtrace', value: formatted_backtrace }
100
+ end
101
+
102
+ unless data.empty?
103
+ deep_reject(data, @ignore_data_if) if @ignore_data_if.is_a?(Proc)
104
+ data_string = data.map { |k, v| "#{k}: #{v}" }.join("\n")
105
+ fields << { title: 'Data', value: "```#{data_string}```" }
106
+ end
107
+
108
+ fields.concat(@additional_fields) if @additional_fields
109
+
110
+ fields
111
+ end
69
112
  end
70
113
  end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ExceptionNotifier
4
+ class SnsNotifier < BaseNotifier
5
+ def initialize(options)
6
+ super
7
+
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]
11
+
12
+ @notifier = Aws::SNS::Client.new(
13
+ region: options[:region],
14
+ access_key_id: options[:access_key_id],
15
+ secret_access_key: options[:secret_access_key]
16
+ )
17
+ @options = default_options.merge(options)
18
+ end
19
+
20
+ def call(exception, custom_opts = {})
21
+ custom_options = options.merge(custom_opts)
22
+
23
+ subject = build_subject(exception, custom_options)
24
+ message = build_message(exception, custom_options)
25
+
26
+ notifier.publish(
27
+ topic_arn: custom_options[:topic_arn],
28
+ message: message,
29
+ subject: subject
30
+ )
31
+ end
32
+
33
+ private
34
+
35
+ attr_reader :notifier, :options
36
+
37
+ def build_subject(exception, options)
38
+ subject =
39
+ "#{options[:sns_prefix]} - #{accumulated_exception_name(exception, options)} occurred"
40
+ subject.length > 120 ? subject[0...120] + '...' : subject
41
+ end
42
+
43
+ def build_message(exception, options)
44
+ exception_name = accumulated_exception_name(exception, options)
45
+
46
+ if options[:env].nil?
47
+ text = "#{exception_name} occured in background\n"
48
+ else
49
+ env = options[:env]
50
+
51
+ kontroller = env['action_controller.instance']
52
+ request = "#{env['REQUEST_METHOD']} <#{env['REQUEST_URI']}>"
53
+
54
+ text = "#{exception_name} occurred while #{request}"
55
+ text += " was processed by #{kontroller.controller_name}##{kontroller.action_name}\n" if kontroller
56
+ end
57
+
58
+ text += "Exception: #{exception.message}\n"
59
+ text += "Hostname: #{Socket.gethostname}\n"
60
+
61
+ return unless exception.backtrace
62
+
63
+ formatted_backtrace = exception.backtrace.first(options[:backtrace_lines]).join("\n").to_s
64
+ text + "Backtrace:\n#{formatted_backtrace}\n"
65
+ end
66
+
67
+ def accumulated_exception_name(exception, options)
68
+ errors_count = options[:accumulated_errors_count].to_i
69
+
70
+ measure_word = if errors_count > 1
71
+ errors_count
72
+ else
73
+ exception.class.to_s =~ /^[aeiou]/i ? 'An' : 'A'
74
+ end
75
+
76
+ "#{measure_word} #{exception.class}"
77
+ end
78
+
79
+ def default_options
80
+ {
81
+ sns_prefix: '[ERROR]',
82
+ backtrace_lines: 10
83
+ }
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,200 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'action_dispatch'
4
+ require 'active_support/core_ext/time'
5
+ require 'json'
6
+
7
+ module ExceptionNotifier
8
+ class TeamsNotifier < BaseNotifier
9
+ include ExceptionNotifier::BacktraceCleaner
10
+
11
+ class MissingController
12
+ def method_missing(*args, &block); end
13
+ end
14
+
15
+ attr_accessor :httparty
16
+
17
+ def initialize(options = {})
18
+ super
19
+ @default_options = options
20
+ @httparty = HTTParty
21
+ end
22
+
23
+ def call(exception, options = {})
24
+ @options = options.merge(@default_options)
25
+ @exception = exception
26
+ @backtrace = exception.backtrace ? clean_backtrace(exception) : nil
27
+
28
+ @env = @options.delete(:env)
29
+
30
+ @application_name = @options.delete(:app_name) || rails_app_name
31
+ @gitlab_url = @options.delete(:git_url)
32
+ @jira_url = @options.delete(:jira_url)
33
+
34
+ @webhook_url = @options.delete(:webhook_url)
35
+ raise ArgumentError, "You must provide 'webhook_url' parameter." unless @webhook_url
36
+
37
+ if @env.nil?
38
+ @controller = @request_items = nil
39
+ else
40
+ @controller = @env['action_controller.instance'] || MissingController.new
41
+
42
+ request = ActionDispatch::Request.new(@env)
43
+
44
+ @request_items = { url: request.original_url,
45
+ http_method: request.method,
46
+ ip_address: request.remote_ip,
47
+ parameters: request.filtered_parameters,
48
+ timestamp: Time.current }
49
+
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 }
53
+ end
54
+ end
55
+
56
+ payload = message_text
57
+
58
+ @options[:body] = payload.to_json
59
+ @options[:headers] ||= {}
60
+ @options[:headers]['Content-Type'] = 'application/json'
61
+ @options[:debug_output] = $stdout
62
+
63
+ @httparty.post(@webhook_url, @options)
64
+ end
65
+
66
+ private
67
+
68
+ def message_text
69
+ text = {
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
+ {
76
+ 'activityTitle' => activity_title,
77
+ 'activitySubtitle' => @exception.message.to_s
78
+ }
79
+ ],
80
+ 'potentialAction' => []
81
+ }
82
+
83
+ text['sections'].push details
84
+ text['potentialAction'].push gitlab_view_link unless @gitlab_url.nil?
85
+ text['potentialAction'].push gitlab_issue_link unless @gitlab_url.nil?
86
+ text['potentialAction'].push jira_issue_link unless @jira_url.nil?
87
+
88
+ text
89
+ end
90
+
91
+ def details
92
+ details = {
93
+ 'title' => 'Details',
94
+ 'facts' => []
95
+ }
96
+
97
+ details['facts'].push message_request unless @request_items.nil?
98
+ details['facts'].push message_backtrace unless @backtrace.nil?
99
+
100
+ details
101
+ end
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
+
110
+ def message_request
111
+ {
112
+ 'name' => 'Request',
113
+ 'value' => "#{hash_presentation(@request_items)}\n "
114
+ }
115
+ end
116
+
117
+ def message_backtrace(size = 3)
118
+ text = []
119
+ size = @backtrace.size < size ? @backtrace.size : size
120
+ text << '```'
121
+ size.times { |i| text << '* ' + @backtrace[i] }
122
+ text << '```'
123
+
124
+ {
125
+ 'name' => 'Backtrace',
126
+ 'value' => text.join(" \n").to_s
127
+ }
128
+ end
129
+
130
+ def gitlab_view_link
131
+ {
132
+ '@type' => 'ViewAction',
133
+ 'name' => "\u{1F98A} View in GitLab",
134
+ 'target' => [
135
+ "#{@gitlab_url}/#{@application_name}"
136
+ ]
137
+ }
138
+ end
139
+
140
+ def gitlab_issue_link
141
+ link = [@gitlab_url, @application_name, 'issues', 'new'].join('/')
142
+ params = {
143
+ 'issue[title]' => ['[BUG] Error 500 :',
144
+ controller_and_method,
145
+ "(#{@exception.class})",
146
+ @exception.message].compact.join(' ')
147
+ }.to_query
148
+
149
+ {
150
+ '@type' => 'ViewAction',
151
+ 'name' => "\u{1F98A} Create Issue in GitLab",
152
+ 'target' => [
153
+ "#{link}/?#{params}"
154
+ ]
155
+ }
156
+ end
157
+
158
+ def jira_issue_link
159
+ {
160
+ '@type' => 'ViewAction',
161
+ 'name' => '🐞 Create Issue in Jira',
162
+ 'target' => [
163
+ "#{@jira_url}/secure/CreateIssue!default.jspa"
164
+ ]
165
+ }
166
+ end
167
+
168
+ def controller_and_method
169
+ if @controller
170
+ "#{@controller.controller_name}##{@controller.action_name}"
171
+ else
172
+ ''
173
+ end
174
+ end
175
+
176
+ def hash_presentation(hash)
177
+ text = []
178
+
179
+ hash.each do |key, value|
180
+ text << "* **#{key}** : `#{value}`"
181
+ end
182
+
183
+ text.join(" \n")
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
199
+ end
200
+ end