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
@@ -1,15 +1,16 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'action_dispatch'
2
4
  require 'active_support/core_ext/time'
3
5
 
4
6
  module ExceptionNotifier
5
7
  class WebhookNotifier < BaseNotifier
6
-
7
8
  def initialize(options)
8
9
  super
9
10
  @default_options = options
10
11
  end
11
12
 
12
- def call(exception, options={})
13
+ def call(exception, options = {})
13
14
  env = options[:env]
14
15
 
15
16
  options = options.reverse_merge(@default_options)
@@ -18,23 +19,25 @@ module ExceptionNotifier
18
19
 
19
20
  options[:body] ||= {}
20
21
  options[:body][:server] = Socket.gethostname
21
- options[:body][:process] = $$
22
- if defined?(Rails) && Rails.respond_to?(:root)
23
- options[:body][:rails_root] = Rails.root
24
- end
25
- options[:body][:exception] = {:error_class => exception.class.to_s,
26
- :message => exception.message.inspect,
27
- :backtrace => exception.backtrace}
22
+ options[:body][:process] = $PROCESS_ID
23
+ options[:body][:rails_root] = Rails.root if defined?(Rails) && Rails.respond_to?(:root)
24
+ options[:body][:exception] = {
25
+ error_class: exception.class.to_s,
26
+ message: exception.message.inspect,
27
+ backtrace: exception.backtrace
28
+ }
28
29
  options[:body][:data] = (env && env['exception_notifier.exception_data'] || {}).merge(options[:data] || {})
29
30
 
30
31
  unless env.nil?
31
32
  request = ActionDispatch::Request.new(env)
32
33
 
33
- request_items = {:url => request.original_url,
34
- :http_method => request.method,
35
- :ip_address => request.remote_ip,
36
- :parameters => request.filtered_parameters,
37
- :timestamp => Time.current }
34
+ request_items = {
35
+ url: request.original_url,
36
+ http_method: request.method,
37
+ ip_address: request.remote_ip,
38
+ parameters: request.filtered_parameters,
39
+ timestamp: Time.current
40
+ }
38
41
 
39
42
  options[:body][:request] = request_items
40
43
  options[:body][:session] = request.session
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'logger'
2
4
  require 'active_support/core_ext/string/inflections'
3
5
  require 'active_support/core_ext/module/attribute_accessors'
@@ -8,10 +10,10 @@ module ExceptionNotifier
8
10
  include ErrorGrouping
9
11
 
10
12
  autoload :BacktraceCleaner, 'exception_notifier/modules/backtrace_cleaner'
13
+ autoload :Formatter, 'exception_notifier/modules/formatter'
11
14
 
12
15
  autoload :Notifier, 'exception_notifier/notifier'
13
16
  autoload :EmailNotifier, 'exception_notifier/email_notifier'
14
- autoload :CampfireNotifier, 'exception_notifier/campfire_notifier'
15
17
  autoload :HipchatNotifier, 'exception_notifier/hipchat_notifier'
16
18
  autoload :WebhookNotifier, 'exception_notifier/webhook_notifier'
17
19
  autoload :IrcNotifier, 'exception_notifier/irc_notifier'
@@ -20,6 +22,7 @@ module ExceptionNotifier
20
22
  autoload :TeamsNotifier, 'exception_notifier/teams_notifier'
21
23
  autoload :SnsNotifier, 'exception_notifier/sns_notifier'
22
24
  autoload :GoogleChatNotifier, 'exception_notifier/google_chat_notifier'
25
+ autoload :DatadogNotifier, 'exception_notifier/datadog_notifier'
23
26
 
24
27
  class UndefinedNotifierError < StandardError; end
25
28
 
@@ -29,7 +32,10 @@ module ExceptionNotifier
29
32
 
30
33
  # Define a set of exceptions to be ignored, ie, dont send notifications when any of them are raised.
31
34
  mattr_accessor :ignored_exceptions
32
- @@ignored_exceptions = %w{ActiveRecord::RecordNotFound Mongoid::Errors::DocumentNotFound AbstractController::ActionNotFound ActionController::RoutingError ActionController::UnknownFormat ActionController::UrlGenerationError}
35
+ @@ignored_exceptions = %w[
36
+ ActiveRecord::RecordNotFound Mongoid::Errors::DocumentNotFound AbstractController::ActionNotFound
37
+ ActionController::RoutingError ActionController::UnknownFormat ActionController::UrlGenerationError
38
+ ]
33
39
 
34
40
  mattr_accessor :testing_mode
35
41
  @@testing_mode = false
@@ -38,6 +44,9 @@ module ExceptionNotifier
38
44
  # Store conditions that decide when exceptions must be ignored or not.
39
45
  @@ignores = []
40
46
 
47
+ # Store by-notifier conditions that decide when exceptions must be ignored or not.
48
+ @@by_notifier_ignores = {}
49
+
41
50
  # Store notifiers that send notifications when exceptions are raised.
42
51
  @@notifiers = {}
43
52
 
@@ -45,19 +54,25 @@ module ExceptionNotifier
45
54
  self.testing_mode = true
46
55
  end
47
56
 
48
- def notify_exception(exception, options={}, &block)
57
+ def notify_exception(exception, options = {}, &block)
49
58
  return false if ignored_exception?(options[:ignore_exceptions], exception)
50
59
  return false if ignored?(exception, options)
60
+
51
61
  if error_grouping
52
62
  errors_count = group_error!(exception, options)
53
63
  return false unless send_notification?(exception, errors_count)
54
64
  end
55
65
 
66
+ notification_fired = false
56
67
  selected_notifiers = options.delete(:notifiers) || notifiers
57
68
  [*selected_notifiers].each do |notifier|
58
- fire_notification(notifier, exception, options.dup, &block)
69
+ unless notifier_ignored?(exception, options, notifier: notifier)
70
+ fire_notification(notifier, exception, options.dup, &block)
71
+ notification_fired = true
72
+ end
59
73
  end
60
- true
74
+
75
+ notification_fired
61
76
  end
62
77
 
63
78
  def register_exception_notifier(name, notifier_or_options)
@@ -92,23 +107,52 @@ module ExceptionNotifier
92
107
  @@ignores << block
93
108
  end
94
109
 
110
+ def ignore_notifier_if(notifier, &block)
111
+ @@by_notifier_ignores[notifier] = block
112
+ end
113
+
114
+ def ignore_crawlers(crawlers)
115
+ ignore_if do |_exception, opts|
116
+ opts.key?(:env) && from_crawler(opts[:env], crawlers)
117
+ end
118
+ end
119
+
95
120
  def clear_ignore_conditions!
96
121
  @@ignores.clear
122
+ @@by_notifier_ignores.clear
97
123
  end
98
124
 
99
125
  private
126
+
100
127
  def ignored?(exception, options)
101
- @@ignores.any?{ |condition| condition.call(exception, options) }
128
+ @@ignores.any? { |condition| condition.call(exception, options) }
102
129
  rescue Exception => e
103
130
  raise e if @@testing_mode
104
131
 
105
- logger.warn "An error occurred when evaluating an ignore condition. #{e.class}: #{e.message}\n#{e.backtrace.join("\n")}"
132
+ logger.warn(
133
+ "An error occurred when evaluating an ignore condition. #{e.class}: #{e.message}\n#{e.backtrace.join("\n")}"
134
+ )
135
+ false
136
+ end
137
+
138
+ def notifier_ignored?(exception, options, notifier:)
139
+ return false unless @@by_notifier_ignores.key?(notifier)
140
+
141
+ condition = @@by_notifier_ignores[notifier]
142
+ condition.call(exception, options)
143
+ rescue Exception => e
144
+ raise e if @@testing_mode
145
+
146
+ logger.warn(<<~"MESSAGE")
147
+ An error occurred when evaluating a by-notifier ignore condition. #{e.class}: #{e.message}
148
+ #{e.backtrace.join("\n")}
149
+ MESSAGE
106
150
  false
107
151
  end
108
152
 
109
153
  def ignored_exception?(ignore_array, exception)
110
154
  all_ignored_exceptions = (Array(ignored_exceptions) + Array(ignore_array)).map(&:to_s)
111
- exception_ancestors = exception.class.ancestors.map(&:to_s)
155
+ exception_ancestors = exception.singleton_class.ancestors.map(&:to_s)
112
156
  !(all_ignored_exceptions & exception_ancestors).empty?
113
157
  end
114
158
 
@@ -118,7 +162,10 @@ module ExceptionNotifier
118
162
  rescue Exception => e
119
163
  raise e if @@testing_mode
120
164
 
121
- logger.warn "An error occurred when sending a notification using '#{notifier_name}' notifier. #{e.class}: #{e.message}\n#{e.backtrace.join("\n")}"
165
+ logger.warn(
166
+ "An error occurred when sending a notification using '#{notifier_name}' notifier." \
167
+ "#{e.class}: #{e.message}\n#{e.backtrace.join("\n")}"
168
+ )
122
169
  false
123
170
  end
124
171
 
@@ -128,7 +175,15 @@ module ExceptionNotifier
128
175
  notifier = notifier_class.new(options)
129
176
  register_exception_notifier(name, notifier)
130
177
  rescue NameError => e
131
- raise UndefinedNotifierError, "No notifier named '#{name}' was found. Please, revise your configuration options. Cause: #{e.message}"
178
+ raise UndefinedNotifierError,
179
+ "No notifier named '#{name}' was found. Please, revise your configuration options. Cause: #{e.message}"
180
+ end
181
+
182
+ def from_crawler(env, ignored_crawlers)
183
+ agent = env['HTTP_USER_AGENT']
184
+ Array(ignored_crawlers).any? do |crawler|
185
+ agent =~ Regexp.new(crawler)
186
+ end
132
187
  end
133
188
  end
134
189
  end
@@ -1,14 +1,20 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ExceptionNotification
2
4
  module Generators
3
5
  class InstallGenerator < Rails::Generators::Base
4
- desc "Creates a ExceptionNotification initializer."
6
+ desc 'Creates a ExceptionNotification initializer.'
5
7
 
6
- source_root File.expand_path('../templates', __FILE__)
7
- class_option :resque, :type => :boolean, :desc => 'Add support for sending notifications when errors occur in Resque jobs.'
8
- class_option :sidekiq, :type => :boolean, :desc => 'Add support for sending notifications when errors occur in Sidekiq jobs.'
8
+ source_root File.expand_path('templates', __dir__)
9
+ class_option :resque,
10
+ type: :boolean,
11
+ desc: 'Add support for sending notifications when errors occur in Resque jobs.'
12
+ class_option :sidekiq,
13
+ type: :boolean,
14
+ desc: 'Add support for sending notifications when errors occur in Sidekiq jobs.'
9
15
 
10
16
  def copy_initializer
11
- template 'exception_notification.rb', 'config/initializers/exception_notification.rb'
17
+ template 'exception_notification.rb.erb', 'config/initializers/exception_notification.rb'
12
18
  end
13
19
  end
14
20
  end
@@ -22,32 +22,34 @@ ExceptionNotification.configure do |config|
22
22
  # not Rails.env.production?
23
23
  # end
24
24
 
25
+ # Ignore exceptions generated by crawlers
26
+ # config.ignore_crawlers %w{Googlebot bingbot}
27
+
25
28
  # Notifiers =================================================================
26
29
 
27
30
  # Email notifier sends notifications by email.
28
31
  config.add_notifier :email, {
29
- :email_prefix => "[ERROR] ",
30
- :sender_address => %{"Notifier" <notifier@example.com>},
31
- :exception_recipients => %w{exceptions@example.com}
32
+ email_prefix: '[ERROR] ',
33
+ sender_address: %{"Notifier" <notifier@example.com>},
34
+ exception_recipients: %w{exceptions@example.com}
32
35
  }
33
36
 
34
37
  # Campfire notifier sends notifications to your Campfire room. Requires 'tinder' gem.
35
38
  # config.add_notifier :campfire, {
36
- # :subdomain => 'my_subdomain',
37
- # :token => 'my_token',
38
- # :room_name => 'my_room'
39
+ # subdomain: 'my_subdomain',
40
+ # token: 'my_token',
41
+ # room_name: 'my_room'
39
42
  # }
40
43
 
41
44
  # HipChat notifier sends notifications to your HipChat room. Requires 'hipchat' gem.
42
45
  # config.add_notifier :hipchat, {
43
- # :api_token => 'my_token',
44
- # :room_name => 'my_room'
46
+ # api_token: 'my_token',
47
+ # room_name: 'my_room'
45
48
  # }
46
49
 
47
50
  # Webhook notifier sends notifications over HTTP protocol. Requires 'httparty' gem.
48
51
  # config.add_notifier :webhook, {
49
- # :url => 'http://example.com:5555/hubot/path',
50
- # :http_method => :post
52
+ # url: 'http://example.com:5555/hubot/path',
53
+ # http_method: :post
51
54
  # }
52
-
53
55
  end
@@ -1,44 +1,106 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'test_helper'
2
4
 
3
5
  class RackTest < ActiveSupport::TestCase
4
-
5
6
  setup do
6
7
  @pass_app = Object.new
7
8
  @pass_app.stubs(:call).returns([nil, { 'X-Cascade' => 'pass' }, nil])
8
9
 
9
10
  @normal_app = Object.new
10
- @normal_app.stubs(:call).returns([nil, { }, nil])
11
+ @normal_app.stubs(:call).returns([nil, {}, nil])
11
12
  end
12
13
 
13
14
  teardown do
14
- ExceptionNotifier.error_grouping = false
15
- ExceptionNotifier.notification_trigger = nil
15
+ ExceptionNotifier.reset_notifiers!
16
16
  end
17
17
 
18
- test "should ignore \"X-Cascade\" header by default" do
18
+ test 'should ignore "X-Cascade" header by default' do
19
19
  ExceptionNotifier.expects(:notify_exception).never
20
20
  ExceptionNotification::Rack.new(@pass_app).call({})
21
21
  end
22
22
 
23
- test "should notify on \"X-Cascade\" = \"pass\" if ignore_cascade_pass option is false" do
23
+ test 'should notify on "X-Cascade" = "pass" if ignore_cascade_pass option is false' do
24
24
  ExceptionNotifier.expects(:notify_exception).once
25
- ExceptionNotification::Rack.new(@pass_app, :ignore_cascade_pass => false).call({})
25
+ ExceptionNotification::Rack.new(@pass_app, ignore_cascade_pass: false).call({})
26
26
  end
27
27
 
28
- test "should assign error_grouping if error_grouping is specified" do
28
+ test 'should assign error_grouping if error_grouping is specified' do
29
29
  refute ExceptionNotifier.error_grouping
30
30
  ExceptionNotification::Rack.new(@normal_app, error_grouping: true).call({})
31
31
  assert ExceptionNotifier.error_grouping
32
32
  end
33
33
 
34
- test "should assign notification_trigger if notification_trigger is specified" do
34
+ test 'should assign notification_trigger if notification_trigger is specified' do
35
35
  assert_nil ExceptionNotifier.notification_trigger
36
- ExceptionNotification::Rack.new(@normal_app, notification_trigger: lambda {|i| true}).call({})
36
+ ExceptionNotification::Rack.new(@normal_app, notification_trigger: ->(_i) { true }).call({})
37
37
  assert_respond_to ExceptionNotifier.notification_trigger, :call
38
38
  end
39
39
 
40
- test "should set default cache to Rails cache" do
41
- ExceptionNotification::Rack.new(@normal_app, error_grouping: true).call({})
42
- assert_equal Rails.cache, ExceptionNotifier.error_grouping_cache
40
+ if defined?(Rails) && Rails.respond_to?(:cache)
41
+ test 'should set default cache to Rails cache' do
42
+ ExceptionNotification::Rack.new(@normal_app, error_grouping: true).call({})
43
+ assert_equal Rails.cache, ExceptionNotifier.error_grouping_cache
44
+ end
45
+ end
46
+
47
+ test 'should ignore exceptions with Usar Agent in ignore_crawlers' do
48
+ exception_app = Object.new
49
+ exception_app.stubs(:call).raises(RuntimeError)
50
+
51
+ env = { 'HTTP_USER_AGENT' => 'Mozilla/5.0 (compatible; Crawlerbot/2.1;)' }
52
+
53
+ begin
54
+ ExceptionNotification::Rack.new(exception_app, ignore_crawlers: %w[Crawlerbot]).call(env)
55
+
56
+ flunk
57
+ rescue StandardError
58
+ refute env['exception_notifier.delivered']
59
+ end
60
+ end
61
+
62
+ test 'should ignore exceptions if ignore_if condition is met' do
63
+ exception_app = Object.new
64
+ exception_app.stubs(:call).raises(RuntimeError)
65
+
66
+ env = {}
67
+
68
+ begin
69
+ ExceptionNotification::Rack.new(
70
+ exception_app,
71
+ ignore_if: ->(_env, exception) { exception.is_a? RuntimeError }
72
+ ).call(env)
73
+
74
+ flunk
75
+ rescue StandardError
76
+ refute env['exception_notifier.delivered']
77
+ end
78
+ end
79
+
80
+ test 'should ignore exceptions with notifiers that satisfies ignore_notifier_if condition' do
81
+ exception_app = Object.new
82
+ exception_app.stubs(:call).raises(RuntimeError)
83
+
84
+ notifier1_called = notifier2_called = false
85
+ notifier1 = ->(_exception, _options) { notifier1_called = true }
86
+ notifier2 = ->(_exception, _options) { notifier2_called = true }
87
+
88
+ env = {}
89
+
90
+ begin
91
+ ExceptionNotification::Rack.new(
92
+ exception_app,
93
+ ignore_notifier_if: {
94
+ notifier1: ->(_env, exception) { exception.is_a? RuntimeError }
95
+ },
96
+ notifier1: notifier1,
97
+ notifier2: notifier2
98
+ ).call(env)
99
+
100
+ flunk
101
+ rescue StandardError
102
+ refute notifier1_called
103
+ assert notifier2_called
104
+ end
43
105
  end
44
106
  end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_helper'
4
+
5
+ require 'exception_notification/resque'
6
+ require 'resque'
7
+ require 'mock_redis'
8
+ require 'resque/failure/multiple'
9
+ require 'resque/failure/redis'
10
+
11
+ class ResqueTest < ActiveSupport::TestCase
12
+ setup do
13
+ # Resque.redis=() only supports a String or Redis instance in Resque 1.8
14
+ Resque.instance_variable_set(:@redis, MockRedis.new)
15
+
16
+ Resque::Failure::Multiple.classes = [Resque::Failure::Redis, ExceptionNotification::Resque]
17
+ Resque::Failure.backend = Resque::Failure::Multiple
18
+
19
+ @worker = Resque::Worker.new(:jobs)
20
+ # Forking causes issues with Mocha's `.expects`
21
+ @worker.cant_fork = true
22
+ end
23
+
24
+ test 'count returns the number of failures' do
25
+ Resque::Job.create(:jobs, BadJob)
26
+ @worker.work(0)
27
+ assert_equal 1, ExceptionNotification::Resque.count
28
+ end
29
+
30
+ test 'notifies exception when job fails' do
31
+ ExceptionNotifier.expects(:notify_exception).with do |ex, opts|
32
+ ex.is_a?(RuntimeError) &&
33
+ ex.message == 'Bad job!' &&
34
+ opts[:data][:resque][:error_class] == 'RuntimeError' &&
35
+ opts[:data][:resque][:error_message] == 'Bad job!' &&
36
+ opts[:data][:resque][:failed_at].present? &&
37
+ opts[:data][:resque][:payload] == {
38
+ 'class' => 'ResqueTest::BadJob',
39
+ 'args' => []
40
+ } &&
41
+ opts[:data][:resque][:queue] == :jobs &&
42
+ opts[:data][:resque][:worker].present?
43
+ end
44
+
45
+ Resque::Job.create(:jobs, BadJob)
46
+ @worker.work(0)
47
+ end
48
+
49
+ class BadJob
50
+ def self.perform
51
+ raise 'Bad job!'
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,153 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_helper'
4
+ require 'dogapi/common'
5
+ require 'dogapi/event'
6
+
7
+ class DatadogNotifierTest < ActiveSupport::TestCase
8
+ def setup
9
+ @client = FakeDatadogClient.new
10
+ @options = {
11
+ client: @client
12
+ }
13
+ @notifier = ExceptionNotifier::DatadogNotifier.new(@options)
14
+ @exception = FakeException.new
15
+ @controller = FakeController.new
16
+ @request = FakeRequest.new
17
+ end
18
+
19
+ test 'should send an event to datadog' do
20
+ fake_event = Dogapi::Event.any_instance
21
+ @client.expects(:emit_event).with(fake_event)
22
+
23
+ @notifier.stubs(:datadog_event).returns(fake_event)
24
+ @notifier.call(@exception)
25
+ end
26
+
27
+ test 'should include exception class in event title' do
28
+ event = @notifier.datadog_event(@exception)
29
+ assert_includes event.msg_title, 'FakeException'
30
+ end
31
+
32
+ test 'should include prefix in event title and not append previous events' do
33
+ options = {
34
+ client: @client,
35
+ title_prefix: 'prefix'
36
+ }
37
+
38
+ notifier = ExceptionNotifier::DatadogNotifier.new(options)
39
+ event = notifier.datadog_event(@exception)
40
+ assert_equal event.msg_title, 'prefix (DatadogNotifierTest::FakeException) "Fake exception message"'
41
+
42
+ event2 = notifier.datadog_event(@exception)
43
+ assert_equal event2.msg_title, 'prefix (DatadogNotifierTest::FakeException) "Fake exception message"'
44
+ end
45
+
46
+ test 'should include exception message in event title' do
47
+ event = @notifier.datadog_event(@exception)
48
+ assert_includes event.msg_title, 'Fake exception message'
49
+ end
50
+
51
+ test 'should include controller info in event title if controller information is available' do
52
+ event = @notifier.datadog_event(@exception,
53
+ env: {
54
+ 'action_controller.instance' => @controller,
55
+ 'REQUEST_METHOD' => 'GET',
56
+ 'rack.input' => ''
57
+ })
58
+ assert_includes event.msg_title, 'Fake controller'
59
+ assert_includes event.msg_title, 'Fake action'
60
+ end
61
+
62
+ test 'should include backtrace info in event body' do
63
+ event = @notifier.datadog_event(@exception)
64
+ assert_includes event.msg_text, "backtrace line 1\nbacktrace line 2\nbacktrace line 3"
65
+ end
66
+
67
+ test 'should include request info in event body' do
68
+ ActionDispatch::Request.stubs(:new).returns(@request)
69
+
70
+ event = @notifier.datadog_event(@exception,
71
+ env: {
72
+ 'action_controller.instance' => @controller,
73
+ 'REQUEST_METHOD' => 'GET',
74
+ 'rack.input' => ''
75
+ })
76
+ assert_includes event.msg_text, 'http://localhost:8080'
77
+ assert_includes event.msg_text, 'GET'
78
+ assert_includes event.msg_text, '127.0.0.1'
79
+ assert_includes event.msg_text, '{"param 1"=>"value 1", "param 2"=>"value 2"}'
80
+ end
81
+
82
+ test 'should include tags in event' do
83
+ options = {
84
+ client: @client,
85
+ tags: %w[error production]
86
+ }
87
+ notifier = ExceptionNotifier::DatadogNotifier.new(options)
88
+ event = notifier.datadog_event(@exception)
89
+ assert_equal event.tags, %w[error production]
90
+ end
91
+
92
+ test 'should include event title in event aggregation key' do
93
+ event = @notifier.datadog_event(@exception)
94
+ assert_equal event.aggregation_key, [event.msg_title]
95
+ end
96
+
97
+ class FakeDatadogClient
98
+ def emit_event(event); end
99
+ end
100
+
101
+ class FakeController
102
+ def controller_name
103
+ 'Fake controller'
104
+ end
105
+
106
+ def action_name
107
+ 'Fake action'
108
+ end
109
+ end
110
+
111
+ class FakeException
112
+ def backtrace
113
+ [
114
+ 'backtrace line 1',
115
+ 'backtrace line 2',
116
+ 'backtrace line 3',
117
+ 'backtrace line 4',
118
+ 'backtrace line 5'
119
+ ]
120
+ end
121
+
122
+ def message
123
+ 'Fake exception message'
124
+ end
125
+ end
126
+
127
+ class FakeRequest
128
+ def url
129
+ 'http://localhost:8080'
130
+ end
131
+
132
+ def request_method
133
+ 'GET'
134
+ end
135
+
136
+ def remote_ip
137
+ '127.0.0.1'
138
+ end
139
+
140
+ def filtered_parameters
141
+ {
142
+ 'param 1' => 'value 1',
143
+ 'param 2' => 'value 2'
144
+ }
145
+ end
146
+
147
+ def session
148
+ {
149
+ 'session_id' => '1234'
150
+ }
151
+ end
152
+ end
153
+ end