exception_notification 4.3.0 → 4.4.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 +4 -4
  2. data/Appraisals +2 -2
  3. data/CHANGELOG.rdoc +14 -0
  4. data/CONTRIBUTING.md +18 -0
  5. data/Gemfile +1 -1
  6. data/README.md +64 -935
  7. data/Rakefile +2 -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 +54 -0
  21. data/examples/sinatra/Gemfile +6 -6
  22. data/examples/sinatra/config.ru +1 -1
  23. data/examples/sinatra/sinatra_app.rb +14 -10
  24. data/exception_notification.gemspec +27 -22
  25. data/gemfiles/rails4_0.gemfile +3 -3
  26. data/gemfiles/rails4_1.gemfile +3 -3
  27. data/gemfiles/rails4_2.gemfile +3 -3
  28. data/gemfiles/rails5_0.gemfile +3 -3
  29. data/gemfiles/rails5_1.gemfile +3 -3
  30. data/gemfiles/rails5_2.gemfile +7 -0
  31. data/gemfiles/rails6_0.gemfile +7 -0
  32. data/lib/exception_notification.rb +1 -0
  33. data/lib/exception_notification/rack.rb +8 -21
  34. data/lib/exception_notification/resque.rb +8 -10
  35. data/lib/exception_notification/sidekiq.rb +8 -12
  36. data/lib/exception_notification/version.rb +3 -0
  37. data/lib/exception_notifier.rb +20 -3
  38. data/lib/exception_notifier/base_notifier.rb +2 -3
  39. data/lib/exception_notifier/campfire_notifier.rb +12 -13
  40. data/lib/exception_notifier/datadog_notifier.rb +153 -0
  41. data/lib/exception_notifier/email_notifier.rb +64 -87
  42. data/lib/exception_notifier/google_chat_notifier.rb +25 -119
  43. data/lib/exception_notifier/hipchat_notifier.rb +11 -12
  44. data/lib/exception_notifier/irc_notifier.rb +32 -30
  45. data/lib/exception_notifier/mattermost_notifier.rb +47 -140
  46. data/lib/exception_notifier/modules/backtrace_cleaner.rb +0 -2
  47. data/lib/exception_notifier/modules/error_grouping.rb +5 -5
  48. data/lib/exception_notifier/modules/formatter.rb +118 -0
  49. data/lib/exception_notifier/notifier.rb +5 -6
  50. data/lib/exception_notifier/slack_notifier.rb +63 -40
  51. data/lib/exception_notifier/sns_notifier.rb +17 -11
  52. data/lib/exception_notifier/teams_notifier.rb +58 -44
  53. data/lib/exception_notifier/views/exception_notifier/_backtrace.html.erb +1 -1
  54. data/lib/exception_notifier/views/exception_notifier/_environment.text.erb +1 -1
  55. data/lib/exception_notifier/views/exception_notifier/_request.text.erb +1 -1
  56. data/lib/exception_notifier/views/exception_notifier/exception_notification.html.erb +2 -2
  57. data/lib/exception_notifier/views/exception_notifier/exception_notification.text.erb +2 -2
  58. data/lib/exception_notifier/webhook_notifier.rb +14 -11
  59. data/lib/generators/exception_notification/install_generator.rb +5 -5
  60. data/lib/generators/exception_notification/templates/{exception_notification.rb → exception_notification.rb.erb} +13 -11
  61. data/test/exception_notification/rack_test.rb +27 -11
  62. data/test/exception_notification/resque_test.rb +52 -0
  63. data/test/exception_notifier/campfire_notifier_test.rb +42 -42
  64. data/test/exception_notifier/datadog_notifier_test.rb +151 -0
  65. data/test/exception_notifier/email_notifier_test.rb +269 -153
  66. data/test/exception_notifier/google_chat_notifier_test.rb +154 -101
  67. data/test/exception_notifier/hipchat_notifier_test.rb +78 -81
  68. data/test/exception_notifier/irc_notifier_test.rb +34 -34
  69. data/test/exception_notifier/mattermost_notifier_test.rb +164 -67
  70. data/test/exception_notifier/modules/error_grouping_test.rb +39 -40
  71. data/test/exception_notifier/modules/formatter_test.rb +150 -0
  72. data/test/exception_notifier/sidekiq_test.rb +6 -6
  73. data/test/exception_notifier/slack_notifier_test.rb +61 -60
  74. data/test/exception_notifier/sns_notifier_test.rb +27 -32
  75. data/test/exception_notifier/teams_notifier_test.rb +23 -26
  76. data/test/exception_notifier/webhook_notifier_test.rb +48 -46
  77. data/test/exception_notifier_test.rb +41 -38
  78. data/test/{dummy/app → support}/views/exception_notifier/_new_bkg_section.html.erb +0 -0
  79. data/test/{dummy/app → support}/views/exception_notifier/_new_bkg_section.text.erb +0 -0
  80. data/test/{dummy/app → support}/views/exception_notifier/_new_section.html.erb +0 -0
  81. data/test/{dummy/app → support}/views/exception_notifier/_new_section.text.erb +0 -0
  82. data/test/test_helper.rb +11 -14
  83. metadata +136 -166
  84. data/test/dummy/.gitignore +0 -4
  85. data/test/dummy/Rakefile +0 -7
  86. data/test/dummy/app/controllers/application_controller.rb +0 -3
  87. data/test/dummy/app/controllers/posts_controller.rb +0 -30
  88. data/test/dummy/app/helpers/application_helper.rb +0 -2
  89. data/test/dummy/app/helpers/posts_helper.rb +0 -2
  90. data/test/dummy/app/models/post.rb +0 -2
  91. data/test/dummy/app/views/layouts/application.html.erb +0 -14
  92. data/test/dummy/app/views/posts/_form.html.erb +0 -0
  93. data/test/dummy/app/views/posts/new.html.erb +0 -0
  94. data/test/dummy/app/views/posts/show.html.erb +0 -0
  95. data/test/dummy/config.ru +0 -4
  96. data/test/dummy/config/application.rb +0 -42
  97. data/test/dummy/config/boot.rb +0 -6
  98. data/test/dummy/config/database.yml +0 -22
  99. data/test/dummy/config/environment.rb +0 -17
  100. data/test/dummy/config/environments/development.rb +0 -25
  101. data/test/dummy/config/environments/production.rb +0 -50
  102. data/test/dummy/config/environments/test.rb +0 -35
  103. data/test/dummy/config/initializers/backtrace_silencers.rb +0 -7
  104. data/test/dummy/config/initializers/inflections.rb +0 -10
  105. data/test/dummy/config/initializers/mime_types.rb +0 -5
  106. data/test/dummy/config/initializers/secret_token.rb +0 -8
  107. data/test/dummy/config/initializers/session_store.rb +0 -8
  108. data/test/dummy/config/locales/en.yml +0 -5
  109. data/test/dummy/config/routes.rb +0 -3
  110. data/test/dummy/db/migrate/20110729022608_create_posts.rb +0 -15
  111. data/test/dummy/db/schema.rb +0 -24
  112. data/test/dummy/db/seeds.rb +0 -7
  113. data/test/dummy/lib/tasks/.gitkeep +0 -0
  114. data/test/dummy/public/404.html +0 -26
  115. data/test/dummy/public/422.html +0 -26
  116. data/test/dummy/public/500.html +0 -26
  117. data/test/dummy/public/favicon.ico +0 -0
  118. data/test/dummy/public/images/rails.png +0 -0
  119. data/test/dummy/public/index.html +0 -239
  120. data/test/dummy/public/javascripts/application.js +0 -2
  121. data/test/dummy/public/javascripts/controls.js +0 -965
  122. data/test/dummy/public/javascripts/dragdrop.js +0 -974
  123. data/test/dummy/public/javascripts/effects.js +0 -1123
  124. data/test/dummy/public/javascripts/prototype.js +0 -6001
  125. data/test/dummy/public/javascripts/rails.js +0 -191
  126. data/test/dummy/public/robots.txt +0 -5
  127. data/test/dummy/public/stylesheets/.gitkeep +0 -0
  128. data/test/dummy/public/stylesheets/scaffold.css +0 -56
  129. data/test/dummy/script/rails +0 -6
  130. data/test/dummy/test/functional/posts_controller_test.rb +0 -237
  131. data/test/dummy/test/test_helper.rb +0 -7
@@ -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")
@@ -3,13 +3,12 @@ require 'active_support/core_ext/time'
3
3
 
4
4
  module ExceptionNotifier
5
5
  class WebhookNotifier < BaseNotifier
6
-
7
6
  def initialize(options)
8
7
  super
9
8
  @default_options = options
10
9
  end
11
10
 
12
- def call(exception, options={})
11
+ def call(exception, options = {})
13
12
  env = options[:env]
14
13
 
15
14
  options = options.reverse_merge(@default_options)
@@ -18,23 +17,27 @@ module ExceptionNotifier
18
17
 
19
18
  options[:body] ||= {}
20
19
  options[:body][:server] = Socket.gethostname
21
- options[:body][:process] = $$
20
+ options[:body][:process] = $PROCESS_ID
22
21
  if defined?(Rails) && Rails.respond_to?(:root)
23
22
  options[:body][:rails_root] = Rails.root
24
23
  end
25
- options[:body][:exception] = {:error_class => exception.class.to_s,
26
- :message => exception.message.inspect,
27
- :backtrace => exception.backtrace}
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,14 +1,14 @@
1
1
  module ExceptionNotification
2
2
  module Generators
3
3
  class InstallGenerator < Rails::Generators::Base
4
- desc "Creates a ExceptionNotification initializer."
4
+ desc 'Creates a ExceptionNotification initializer.'
5
5
 
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.'
6
+ source_root File.expand_path('templates', __dir__)
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.'
9
9
 
10
10
  def copy_initializer
11
- template 'exception_notification.rb', 'config/initializers/exception_notification.rb'
11
+ template 'exception_notification.rb.erb', 'config/initializers/exception_notification.rb'
12
12
  end
13
13
  end
14
14
  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,13 +1,12 @@
1
1
  require 'test_helper'
2
2
 
3
3
  class RackTest < ActiveSupport::TestCase
4
-
5
4
  setup do
6
5
  @pass_app = Object.new
7
6
  @pass_app.stubs(:call).returns([nil, { 'X-Cascade' => 'pass' }, nil])
8
7
 
9
8
  @normal_app = Object.new
10
- @normal_app.stubs(:call).returns([nil, { }, nil])
9
+ @normal_app.stubs(:call).returns([nil, {}, nil])
11
10
  end
12
11
 
13
12
  teardown do
@@ -15,30 +14,47 @@ class RackTest < ActiveSupport::TestCase
15
14
  ExceptionNotifier.notification_trigger = nil
16
15
  end
17
16
 
18
- test "should ignore \"X-Cascade\" header by default" do
17
+ test 'should ignore "X-Cascade" header by default' do
19
18
  ExceptionNotifier.expects(:notify_exception).never
20
19
  ExceptionNotification::Rack.new(@pass_app).call({})
21
20
  end
22
21
 
23
- test "should notify on \"X-Cascade\" = \"pass\" if ignore_cascade_pass option is false" do
22
+ test 'should notify on "X-Cascade" = "pass" if ignore_cascade_pass option is false' do
24
23
  ExceptionNotifier.expects(:notify_exception).once
25
- ExceptionNotification::Rack.new(@pass_app, :ignore_cascade_pass => false).call({})
24
+ ExceptionNotification::Rack.new(@pass_app, ignore_cascade_pass: false).call({})
26
25
  end
27
26
 
28
- test "should assign error_grouping if error_grouping is specified" do
27
+ test 'should assign error_grouping if error_grouping is specified' do
29
28
  refute ExceptionNotifier.error_grouping
30
29
  ExceptionNotification::Rack.new(@normal_app, error_grouping: true).call({})
31
30
  assert ExceptionNotifier.error_grouping
32
31
  end
33
32
 
34
- test "should assign notification_trigger if notification_trigger is specified" do
33
+ test 'should assign notification_trigger if notification_trigger is specified' do
35
34
  assert_nil ExceptionNotifier.notification_trigger
36
- ExceptionNotification::Rack.new(@normal_app, notification_trigger: lambda {|i| true}).call({})
35
+ ExceptionNotification::Rack.new(@normal_app, notification_trigger: ->(_i) { true }).call({})
37
36
  assert_respond_to ExceptionNotifier.notification_trigger, :call
38
37
  end
39
38
 
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
39
+ if defined?(Rails) && Rails.respond_to?(:cache)
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
43
+ end
44
+ end
45
+
46
+ test 'should ignore exceptions with Usar Agent in ignore_crawlers' do
47
+ exception_app = Object.new
48
+ exception_app.stubs(:call).raises(RuntimeError)
49
+
50
+ env = { 'HTTP_USER_AGENT' => 'Mozilla/5.0 (compatible; Crawlerbot/2.1;)' }
51
+
52
+ begin
53
+ ExceptionNotification::Rack.new(exception_app, ignore_crawlers: %w[Crawlerbot]).call(env)
54
+
55
+ flunk
56
+ rescue StandardError
57
+ refute env['exception_notifier.delivered']
58
+ end
43
59
  end
44
60
  end
@@ -0,0 +1,52 @@
1
+ require 'test_helper'
2
+
3
+ require 'exception_notification/resque'
4
+ require 'resque'
5
+ require 'mock_redis'
6
+ require 'resque/failure/multiple'
7
+ require 'resque/failure/redis'
8
+
9
+ class ResqueTest < ActiveSupport::TestCase
10
+ setup do
11
+ # Resque.redis=() only supports a String or Redis instance in Resque 1.8
12
+ Resque.instance_variable_set(:@redis, MockRedis.new)
13
+
14
+ Resque::Failure::Multiple.classes = [Resque::Failure::Redis, ExceptionNotification::Resque]
15
+ Resque::Failure.backend = Resque::Failure::Multiple
16
+
17
+ @worker = Resque::Worker.new(:jobs)
18
+ # Forking causes issues with Mocha's `.expects`
19
+ @worker.cant_fork = true
20
+ end
21
+
22
+ test 'count returns the number of failures' do
23
+ Resque::Job.create(:jobs, BadJob)
24
+ @worker.work(0)
25
+ assert_equal 1, ExceptionNotification::Resque.count
26
+ end
27
+
28
+ test 'notifies exception when job fails' do
29
+ ExceptionNotifier.expects(:notify_exception).with do |ex, opts|
30
+ ex.is_a?(RuntimeError) &&
31
+ ex.message == 'Bad job!' &&
32
+ opts[:data][:resque][:error_class] == 'RuntimeError' &&
33
+ opts[:data][:resque][:error_message] == 'Bad job!' &&
34
+ opts[:data][:resque][:failed_at].present? &&
35
+ opts[:data][:resque][:payload] == {
36
+ 'class' => 'ResqueTest::BadJob',
37
+ 'args' => []
38
+ } &&
39
+ opts[:data][:resque][:queue] == :jobs &&
40
+ opts[:data][:resque][:worker].present?
41
+ end
42
+
43
+ Resque::Job.create(:jobs, BadJob)
44
+ @worker.work(0)
45
+ end
46
+
47
+ class BadJob
48
+ def self.perform
49
+ raise 'Bad job!'
50
+ end
51
+ end
52
+ end
@@ -8,43 +8,42 @@ silence_warnings do
8
8
  end
9
9
 
10
10
  class CampfireNotifierTest < ActiveSupport::TestCase
11
-
12
- test "should send campfire notification if properly configured" do
11
+ test 'should send campfire notification if properly configured' do
13
12
  ExceptionNotifier::CampfireNotifier.stubs(:new).returns(Object.new)
14
- campfire = ExceptionNotifier::CampfireNotifier.new({:subdomain => 'test', :token => 'test_token', :room_name => 'test_room'})
13
+ campfire = ExceptionNotifier::CampfireNotifier.new(subdomain: 'test', token: 'test_token', room_name: 'test_room')
15
14
  campfire.stubs(:call).returns(fake_notification)
16
15
  notif = campfire.call(fake_exception)
17
16
 
18
17
  assert !notif[:message].empty?
19
18
  assert_equal notif[:message][:type], 'PasteMessage'
20
- assert_includes notif[:message][:body], "A new exception occurred:"
21
- assert_includes notif[:message][:body], "divided by 0"
22
- assert_includes notif[:message][:body], "/exception_notification/test/campfire_test.rb:45"
19
+ assert_includes notif[:message][:body], 'A new exception occurred:'
20
+ assert_includes notif[:message][:body], 'divided by 0'
21
+ assert_includes notif[:message][:body], '/exception_notification/test/campfire_test.rb:45'
23
22
  end
24
23
 
25
- test "should send campfire notification without backtrace info if properly configured" do
24
+ test 'should send campfire notification without backtrace info if properly configured' do
26
25
  ExceptionNotifier::CampfireNotifier.stubs(:new).returns(Object.new)
27
- campfire = ExceptionNotifier::CampfireNotifier.new({:subdomain => 'test', :token => 'test_token', :room_name => 'test_room'})
26
+ campfire = ExceptionNotifier::CampfireNotifier.new(subdomain: 'test', token: 'test_token', room_name: 'test_room')
28
27
  campfire.stubs(:call).returns(fake_notification_without_backtrace)
29
28
  notif = campfire.call(fake_exception_without_backtrace)
30
29
 
31
30
  assert !notif[:message].empty?
32
31
  assert_equal notif[:message][:type], 'PasteMessage'
33
- assert_includes notif[:message][:body], "A new exception occurred:"
34
- assert_includes notif[:message][:body], "my custom error"
32
+ assert_includes notif[:message][:body], 'A new exception occurred:'
33
+ assert_includes notif[:message][:body], 'my custom error'
35
34
  end
36
35
 
37
- test "should not send campfire notification if badly configured" do
38
- wrong_params = {:subdomain => 'test', :token => 'bad_token', :room_name => 'test_room'}
39
- Tinder::Campfire.stubs(:new).with('test', {:token => 'bad_token'}).returns(nil)
36
+ test 'should not send campfire notification if badly configured' do
37
+ wrong_params = { subdomain: 'test', token: 'bad_token', room_name: 'test_room' }
38
+ Tinder::Campfire.stubs(:new).with('test', token: 'bad_token').returns(nil)
40
39
  campfire = ExceptionNotifier::CampfireNotifier.new(wrong_params)
41
40
 
42
41
  assert_nil campfire.room
43
42
  assert_nil campfire.call(fake_exception)
44
43
  end
45
44
 
46
- test "should not send campfire notification if config attr missing" do
47
- wrong_params = {:subdomain => 'test', :room_name => 'test_room'}
45
+ test 'should not send campfire notification if config attr missing' do
46
+ wrong_params = { subdomain: 'test', room_name: 'test_room' }
48
47
  Tinder::Campfire.stubs(:new).with('test', {}).returns(nil)
49
48
  campfire = ExceptionNotifier::CampfireNotifier.new(wrong_params)
50
49
 
@@ -52,35 +51,34 @@ class CampfireNotifierTest < ActiveSupport::TestCase
52
51
  assert_nil campfire.call(fake_exception)
53
52
  end
54
53
 
55
- test "should send the new exception message if no :accumulated_errors_count option" do
54
+ test 'should send the new exception message if no :accumulated_errors_count option' do
56
55
  campfire = ExceptionNotifier::CampfireNotifier.new({})
57
56
  campfire.stubs(:active?).returns(true)
58
- campfire.expects(:send_notice).with{ |_, _, message| message.start_with?("A new exception occurred") }.once
57
+ campfire.expects(:send_notice).with { |_, _, message| message.start_with?('A new exception occurred') }.once
59
58
  campfire.call(fake_exception)
60
59
  end
61
60
 
62
- test "shoud send the exception message if :accumulated_errors_count option greater than 1" do
61
+ test 'shoud send the exception message if :accumulated_errors_count option greater than 1' do
63
62
  campfire = ExceptionNotifier::CampfireNotifier.new({})
64
63
  campfire.stubs(:active?).returns(true)
65
- campfire.expects(:send_notice).with{ |_, _, message| message.start_with?("The exception occurred 3 times:") }.once
64
+ campfire.expects(:send_notice).with { |_, _, message| message.start_with?('The exception occurred 3 times:') }.once
66
65
  campfire.call(fake_exception, accumulated_errors_count: 3)
67
66
  end
68
67
 
69
- test "should call pre/post_callback if specified" do
70
- pre_callback_called, post_callback_called = 0,0
68
+ test 'should call pre/post_callback if specified' do
69
+ pre_callback_called = 0
70
+ post_callback_called = 0
71
71
  Tinder::Campfire.stubs(:new).returns(Object.new)
72
72
 
73
73
  campfire = ExceptionNotifier::CampfireNotifier.new(
74
- {
75
- :subdomain => 'test',
76
- :token => 'test_token',
77
- :room_name => 'test_room',
78
- :pre_callback => proc { |opts, notifier, backtrace, message, message_opts|
79
- pre_callback_called += 1
80
- },
81
- :post_callback => proc { |opts, notifier, backtrace, message, message_opts|
82
- post_callback_called += 1
83
- }
74
+ subdomain: 'test',
75
+ token: 'test_token',
76
+ room_name: 'test_room',
77
+ pre_callback: proc { |_opts, _notifier, _backtrace, _message, _message_opts|
78
+ pre_callback_called += 1
79
+ },
80
+ post_callback: proc { |_opts, _notifier, _backtrace, _message, _message_opts|
81
+ post_callback_called += 1
84
82
  }
85
83
  )
86
84
  campfire.room = Object.new
@@ -93,24 +91,26 @@ class CampfireNotifierTest < ActiveSupport::TestCase
93
91
  private
94
92
 
95
93
  def fake_notification
96
- {:message => {:type => 'PasteMessage',
97
- :body => "A new exception occurred: 'divided by 0' on '/Users/sebastian/exception_notification/test/campfire_test.rb:45:in `/'"
98
- }
94
+ {
95
+ message: {
96
+ type: 'PasteMessage',
97
+ body: "A new exception occurred: 'divided by 0' on '/Users/sebastian/exception_notification/test/campfire_test.rb:45:in `/'"
98
+ }
99
99
  }
100
100
  end
101
101
 
102
102
  def fake_exception
103
- begin
104
- 5/0
105
- rescue Exception => e
106
- e
107
- end
103
+ 5 / 0
104
+ rescue StandardError => e
105
+ e
108
106
  end
109
107
 
110
108
  def fake_notification_without_backtrace
111
- {:message => {:type => 'PasteMessage',
112
- :body => "A new exception occurred: 'my custom error'"
113
- }
109
+ {
110
+ message: {
111
+ type: 'PasteMessage',
112
+ body: "A new exception occurred: 'my custom error'"
113
+ }
114
114
  }
115
115
  end
116
116
 
@@ -0,0 +1,151 @@
1
+ require 'test_helper'
2
+ require 'dogapi/common'
3
+ require 'dogapi/event'
4
+
5
+ class DatadogNotifierTest < ActiveSupport::TestCase
6
+ def setup
7
+ @client = FakeDatadogClient.new
8
+ @options = {
9
+ client: @client
10
+ }
11
+ @notifier = ExceptionNotifier::DatadogNotifier.new(@options)
12
+ @exception = FakeException.new
13
+ @controller = FakeController.new
14
+ @request = FakeRequest.new
15
+ end
16
+
17
+ test 'should send an event to datadog' do
18
+ fake_event = Dogapi::Event.any_instance
19
+ @client.expects(:emit_event).with(fake_event)
20
+
21
+ @notifier.stubs(:datadog_event).returns(fake_event)
22
+ @notifier.call(@exception)
23
+ end
24
+
25
+ test 'should include exception class in event title' do
26
+ event = @notifier.datadog_event(@exception)
27
+ assert_includes event.msg_title, 'FakeException'
28
+ end
29
+
30
+ test 'should include prefix in event title and not append previous events' do
31
+ options = {
32
+ client: @client,
33
+ title_prefix: 'prefix'
34
+ }
35
+
36
+ notifier = ExceptionNotifier::DatadogNotifier.new(options)
37
+ event = notifier.datadog_event(@exception)
38
+ assert_equal event.msg_title, 'prefix (DatadogNotifierTest::FakeException) "Fake exception message"'
39
+
40
+ event2 = notifier.datadog_event(@exception)
41
+ assert_equal event2.msg_title, 'prefix (DatadogNotifierTest::FakeException) "Fake exception message"'
42
+ end
43
+
44
+ test 'should include exception message in event title' do
45
+ event = @notifier.datadog_event(@exception)
46
+ assert_includes event.msg_title, 'Fake exception message'
47
+ end
48
+
49
+ test 'should include controller info in event title if controller information is available' do
50
+ event = @notifier.datadog_event(@exception,
51
+ env: {
52
+ 'action_controller.instance' => @controller,
53
+ 'REQUEST_METHOD' => 'GET',
54
+ 'rack.input' => ''
55
+ })
56
+ assert_includes event.msg_title, 'Fake controller'
57
+ assert_includes event.msg_title, 'Fake action'
58
+ end
59
+
60
+ test 'should include backtrace info in event body' do
61
+ event = @notifier.datadog_event(@exception)
62
+ assert_includes event.msg_text, "backtrace line 1\nbacktrace line 2\nbacktrace line 3"
63
+ end
64
+
65
+ test 'should include request info in event body' do
66
+ ActionDispatch::Request.stubs(:new).returns(@request)
67
+
68
+ event = @notifier.datadog_event(@exception,
69
+ env: {
70
+ 'action_controller.instance' => @controller,
71
+ 'REQUEST_METHOD' => 'GET',
72
+ 'rack.input' => ''
73
+ })
74
+ assert_includes event.msg_text, 'http://localhost:8080'
75
+ assert_includes event.msg_text, 'GET'
76
+ assert_includes event.msg_text, '127.0.0.1'
77
+ assert_includes event.msg_text, '{"param 1"=>"value 1", "param 2"=>"value 2"}'
78
+ end
79
+
80
+ test 'should include tags in event' do
81
+ options = {
82
+ client: @client,
83
+ tags: %w[error production]
84
+ }
85
+ notifier = ExceptionNotifier::DatadogNotifier.new(options)
86
+ event = notifier.datadog_event(@exception)
87
+ assert_equal event.tags, %w[error production]
88
+ end
89
+
90
+ test 'should include event title in event aggregation key' do
91
+ event = @notifier.datadog_event(@exception)
92
+ assert_equal event.aggregation_key, [event.msg_title]
93
+ end
94
+
95
+ class FakeDatadogClient
96
+ def emit_event(event); end
97
+ end
98
+
99
+ class FakeController
100
+ def controller_name
101
+ 'Fake controller'
102
+ end
103
+
104
+ def action_name
105
+ 'Fake action'
106
+ end
107
+ end
108
+
109
+ class FakeException
110
+ def backtrace
111
+ [
112
+ 'backtrace line 1',
113
+ 'backtrace line 2',
114
+ 'backtrace line 3',
115
+ 'backtrace line 4',
116
+ 'backtrace line 5'
117
+ ]
118
+ end
119
+
120
+ def message
121
+ 'Fake exception message'
122
+ end
123
+ end
124
+
125
+ class FakeRequest
126
+ def url
127
+ 'http://localhost:8080'
128
+ end
129
+
130
+ def request_method
131
+ 'GET'
132
+ end
133
+
134
+ def remote_ip
135
+ '127.0.0.1'
136
+ end
137
+
138
+ def filtered_parameters
139
+ {
140
+ 'param 1' => 'value 1',
141
+ 'param 2' => 'value 2'
142
+ }
143
+ end
144
+
145
+ def session
146
+ {
147
+ 'session_id' => '1234'
148
+ }
149
+ end
150
+ end
151
+ end