exception_notification 3.0.1 → 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.
- checksums.yaml +7 -0
- data/Appraisals +7 -0
- data/CHANGELOG.rdoc +129 -1
- data/CODE_OF_CONDUCT.md +22 -0
- data/CONTRIBUTING.md +29 -1
- data/Gemfile +1 -1
- data/MIT-LICENSE +23 -0
- data/README.md +168 -222
- data/Rakefile +5 -11
- data/docs/notifiers/campfire.md +50 -0
- data/docs/notifiers/custom.md +42 -0
- data/docs/notifiers/datadog.md +51 -0
- data/docs/notifiers/email.md +195 -0
- data/docs/notifiers/google_chat.md +31 -0
- data/docs/notifiers/hipchat.md +66 -0
- data/docs/notifiers/irc.md +97 -0
- data/docs/notifiers/mattermost.md +115 -0
- data/docs/notifiers/slack.md +161 -0
- data/docs/notifiers/sns.md +37 -0
- data/docs/notifiers/teams.md +54 -0
- data/docs/notifiers/webhook.md +60 -0
- data/examples/sample_app.rb +54 -0
- data/examples/sinatra/Gemfile +8 -0
- data/examples/sinatra/Gemfile.lock +95 -0
- data/examples/sinatra/Procfile +2 -0
- data/examples/sinatra/README.md +11 -0
- data/examples/sinatra/config.ru +3 -0
- data/examples/sinatra/sinatra_app.rb +36 -0
- data/exception_notification.gemspec +32 -11
- data/gemfiles/rails4_0.gemfile +7 -0
- data/gemfiles/rails4_1.gemfile +7 -0
- data/gemfiles/rails4_2.gemfile +7 -0
- data/gemfiles/rails5_0.gemfile +7 -0
- data/gemfiles/rails5_1.gemfile +7 -0
- data/gemfiles/rails5_2.gemfile +7 -0
- data/gemfiles/rails6_0.gemfile +7 -0
- data/lib/exception_notification.rb +11 -0
- data/lib/exception_notification/rack.rb +55 -0
- data/lib/exception_notification/rails.rb +9 -0
- data/lib/exception_notification/resque.rb +22 -0
- data/lib/exception_notification/sidekiq.rb +27 -0
- data/lib/exception_notification/version.rb +3 -0
- data/lib/exception_notifier.rb +137 -61
- data/lib/exception_notifier/base_notifier.rb +24 -0
- data/lib/exception_notifier/campfire_notifier.rb +16 -11
- data/lib/exception_notifier/datadog_notifier.rb +153 -0
- data/lib/exception_notifier/email_notifier.rb +196 -0
- data/lib/exception_notifier/google_chat_notifier.rb +42 -0
- data/lib/exception_notifier/hipchat_notifier.rb +49 -0
- data/lib/exception_notifier/irc_notifier.rb +57 -0
- data/lib/exception_notifier/mattermost_notifier.rb +72 -0
- data/lib/exception_notifier/modules/backtrace_cleaner.rb +11 -0
- data/lib/exception_notifier/modules/error_grouping.rb +77 -0
- data/lib/exception_notifier/modules/formatter.rb +118 -0
- data/lib/exception_notifier/notifier.rb +9 -179
- data/lib/exception_notifier/slack_notifier.rb +111 -0
- data/lib/exception_notifier/sns_notifier.rb +85 -0
- data/lib/exception_notifier/teams_notifier.rb +193 -0
- data/lib/exception_notifier/views/exception_notifier/_backtrace.html.erb +3 -1
- data/lib/exception_notifier/views/exception_notifier/_data.html.erb +6 -1
- data/lib/exception_notifier/views/exception_notifier/_environment.html.erb +8 -6
- data/lib/exception_notifier/views/exception_notifier/_environment.text.erb +1 -4
- data/lib/exception_notifier/views/exception_notifier/_request.html.erb +36 -5
- data/lib/exception_notifier/views/exception_notifier/_request.text.erb +10 -5
- data/lib/exception_notifier/views/exception_notifier/_session.html.erb +10 -2
- data/lib/exception_notifier/views/exception_notifier/_session.text.erb +2 -2
- data/lib/exception_notifier/views/exception_notifier/_title.html.erb +3 -3
- data/lib/exception_notifier/views/exception_notifier/background_exception_notification.html.erb +38 -11
- data/lib/exception_notifier/views/exception_notifier/background_exception_notification.text.erb +10 -11
- data/lib/exception_notifier/views/exception_notifier/exception_notification.html.erb +38 -22
- data/lib/exception_notifier/views/exception_notifier/exception_notification.text.erb +2 -3
- data/lib/exception_notifier/webhook_notifier.rb +51 -0
- data/lib/generators/exception_notification/install_generator.rb +15 -0
- data/lib/generators/exception_notification/templates/exception_notification.rb.erb +55 -0
- data/test/exception_notification/rack_test.rb +60 -0
- data/test/exception_notification/resque_test.rb +52 -0
- data/test/exception_notifier/campfire_notifier_test.rb +120 -0
- data/test/exception_notifier/datadog_notifier_test.rb +151 -0
- data/test/exception_notifier/email_notifier_test.rb +351 -0
- data/test/exception_notifier/google_chat_notifier_test.rb +181 -0
- data/test/exception_notifier/hipchat_notifier_test.rb +218 -0
- data/test/exception_notifier/irc_notifier_test.rb +137 -0
- data/test/exception_notifier/mattermost_notifier_test.rb +202 -0
- data/test/exception_notifier/modules/error_grouping_test.rb +165 -0
- data/test/exception_notifier/modules/formatter_test.rb +150 -0
- data/test/exception_notifier/sidekiq_test.rb +38 -0
- data/test/exception_notifier/slack_notifier_test.rb +227 -0
- data/test/exception_notifier/sns_notifier_test.rb +121 -0
- data/test/exception_notifier/teams_notifier_test.rb +90 -0
- data/test/exception_notifier/webhook_notifier_test.rb +96 -0
- data/test/exception_notifier_test.rb +182 -0
- data/test/{dummy/app → support}/views/exception_notifier/_new_bkg_section.html.erb +0 -0
- data/test/{dummy/app → support}/views/exception_notifier/_new_bkg_section.text.erb +0 -0
- data/test/{dummy/app → support}/views/exception_notifier/_new_section.html.erb +0 -0
- data/test/{dummy/app → support}/views/exception_notifier/_new_section.text.erb +0 -0
- data/test/test_helper.rb +12 -8
- metadata +333 -164
- data/.gemtest +0 -0
- data/Gemfile.lock +0 -122
- data/test/background_exception_notification_test.rb +0 -82
- data/test/campfire_test.rb +0 -53
- data/test/dummy/.gitignore +0 -4
- data/test/dummy/Gemfile +0 -33
- data/test/dummy/Gemfile.lock +0 -118
- data/test/dummy/Rakefile +0 -7
- data/test/dummy/app/controllers/application_controller.rb +0 -3
- data/test/dummy/app/controllers/posts_controller.rb +0 -30
- data/test/dummy/app/helpers/application_helper.rb +0 -2
- data/test/dummy/app/helpers/posts_helper.rb +0 -2
- data/test/dummy/app/models/post.rb +0 -2
- data/test/dummy/app/views/layouts/application.html.erb +0 -14
- data/test/dummy/app/views/posts/_form.html.erb +0 -0
- data/test/dummy/app/views/posts/new.html.erb +0 -0
- data/test/dummy/app/views/posts/show.html.erb +0 -0
- data/test/dummy/config.ru +0 -4
- data/test/dummy/config/application.rb +0 -42
- data/test/dummy/config/boot.rb +0 -6
- data/test/dummy/config/database.yml +0 -22
- data/test/dummy/config/environment.rb +0 -13
- data/test/dummy/config/environments/development.rb +0 -24
- data/test/dummy/config/environments/production.rb +0 -49
- data/test/dummy/config/environments/test.rb +0 -35
- data/test/dummy/config/initializers/backtrace_silencers.rb +0 -7
- data/test/dummy/config/initializers/inflections.rb +0 -10
- data/test/dummy/config/initializers/mime_types.rb +0 -5
- data/test/dummy/config/initializers/secret_token.rb +0 -7
- data/test/dummy/config/initializers/session_store.rb +0 -8
- data/test/dummy/config/locales/en.yml +0 -5
- data/test/dummy/config/routes.rb +0 -3
- data/test/dummy/db/migrate/20110729022608_create_posts.rb +0 -15
- data/test/dummy/db/schema.rb +0 -24
- data/test/dummy/db/seeds.rb +0 -7
- data/test/dummy/lib/tasks/.gitkeep +0 -0
- data/test/dummy/public/404.html +0 -26
- data/test/dummy/public/422.html +0 -26
- data/test/dummy/public/500.html +0 -26
- data/test/dummy/public/favicon.ico +0 -0
- data/test/dummy/public/images/rails.png +0 -0
- data/test/dummy/public/index.html +0 -239
- data/test/dummy/public/javascripts/application.js +0 -2
- data/test/dummy/public/javascripts/controls.js +0 -965
- data/test/dummy/public/javascripts/dragdrop.js +0 -974
- data/test/dummy/public/javascripts/effects.js +0 -1123
- data/test/dummy/public/javascripts/prototype.js +0 -6001
- data/test/dummy/public/javascripts/rails.js +0 -191
- data/test/dummy/public/robots.txt +0 -5
- data/test/dummy/public/stylesheets/.gitkeep +0 -0
- data/test/dummy/public/stylesheets/scaffold.css +0 -56
- data/test/dummy/script/rails +0 -6
- data/test/dummy/test/fixtures/posts.yml +0 -11
- data/test/dummy/test/functional/posts_controller_test.rb +0 -239
- data/test/dummy/test/test_helper.rb +0 -13
- data/test/exception_notification_test.rb +0 -73
|
@@ -8,18 +8,17 @@
|
|
|
8
8
|
begin
|
|
9
9
|
summary = render(section).strip
|
|
10
10
|
unless summary.blank?
|
|
11
|
-
title = render("title", :
|
|
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", :
|
|
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")
|
|
20
20
|
end
|
|
21
21
|
end.join
|
|
22
|
-
sections = sections.force_encoding('UTF-8').encode('UTF-16LE', :invalid => :replace).encode('UTF-8') if sections.respond_to?(:force_encoding)
|
|
23
22
|
%>
|
|
24
23
|
|
|
25
24
|
<%= raw sections %>
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
require 'action_dispatch'
|
|
2
|
+
require 'active_support/core_ext/time'
|
|
3
|
+
|
|
4
|
+
module ExceptionNotifier
|
|
5
|
+
class WebhookNotifier < BaseNotifier
|
|
6
|
+
def initialize(options)
|
|
7
|
+
super
|
|
8
|
+
@default_options = options
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def call(exception, options = {})
|
|
12
|
+
env = options[:env]
|
|
13
|
+
|
|
14
|
+
options = options.reverse_merge(@default_options)
|
|
15
|
+
url = options.delete(:url)
|
|
16
|
+
http_method = options.delete(:http_method) || :post
|
|
17
|
+
|
|
18
|
+
options[:body] ||= {}
|
|
19
|
+
options[:body][:server] = Socket.gethostname
|
|
20
|
+
options[:body][:process] = $PROCESS_ID
|
|
21
|
+
if defined?(Rails) && Rails.respond_to?(:root)
|
|
22
|
+
options[:body][:rails_root] = Rails.root
|
|
23
|
+
end
|
|
24
|
+
options[:body][:exception] = {
|
|
25
|
+
error_class: exception.class.to_s,
|
|
26
|
+
message: exception.message.inspect,
|
|
27
|
+
backtrace: exception.backtrace
|
|
28
|
+
}
|
|
29
|
+
options[:body][:data] = (env && env['exception_notifier.exception_data'] || {}).merge(options[:data] || {})
|
|
30
|
+
|
|
31
|
+
unless env.nil?
|
|
32
|
+
request = ActionDispatch::Request.new(env)
|
|
33
|
+
|
|
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
|
+
}
|
|
41
|
+
|
|
42
|
+
options[:body][:request] = request_items
|
|
43
|
+
options[:body][:session] = request.session
|
|
44
|
+
options[:body][:environment] = request.filtered_env
|
|
45
|
+
end
|
|
46
|
+
send_notice(exception, options, nil, @default_options) do |_, _|
|
|
47
|
+
HTTParty.send(http_method, url, options)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module ExceptionNotification
|
|
2
|
+
module Generators
|
|
3
|
+
class InstallGenerator < Rails::Generators::Base
|
|
4
|
+
desc 'Creates a ExceptionNotification initializer.'
|
|
5
|
+
|
|
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
|
+
|
|
10
|
+
def copy_initializer
|
|
11
|
+
template 'exception_notification.rb.erb', 'config/initializers/exception_notification.rb'
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
require 'exception_notification/rails'
|
|
2
|
+
<% if options.sidekiq? %>
|
|
3
|
+
require 'exception_notification/sidekiq'
|
|
4
|
+
<% end %>
|
|
5
|
+
<% if options.resque? %>
|
|
6
|
+
require 'resque/failure/multiple'
|
|
7
|
+
require 'resque/failure/redis'
|
|
8
|
+
require 'exception_notification/resque'
|
|
9
|
+
|
|
10
|
+
Resque::Failure::Multiple.classes = [Resque::Failure::Redis, ExceptionNotification::Resque]
|
|
11
|
+
Resque::Failure.backend = Resque::Failure::Multiple
|
|
12
|
+
<% end %>
|
|
13
|
+
|
|
14
|
+
ExceptionNotification.configure do |config|
|
|
15
|
+
# Ignore additional exception types.
|
|
16
|
+
# ActiveRecord::RecordNotFound, Mongoid::Errors::DocumentNotFound, AbstractController::ActionNotFound and ActionController::RoutingError are already added.
|
|
17
|
+
# config.ignored_exceptions += %w{ActionView::TemplateError CustomError}
|
|
18
|
+
|
|
19
|
+
# Adds a condition to decide when an exception must be ignored or not.
|
|
20
|
+
# The ignore_if method can be invoked multiple times to add extra conditions.
|
|
21
|
+
# config.ignore_if do |exception, options|
|
|
22
|
+
# not Rails.env.production?
|
|
23
|
+
# end
|
|
24
|
+
|
|
25
|
+
# Ignore exceptions generated by crawlers
|
|
26
|
+
# config.ignore_crawlers %w{Googlebot bingbot}
|
|
27
|
+
|
|
28
|
+
# Notifiers =================================================================
|
|
29
|
+
|
|
30
|
+
# Email notifier sends notifications by email.
|
|
31
|
+
config.add_notifier :email, {
|
|
32
|
+
email_prefix: '[ERROR] ',
|
|
33
|
+
sender_address: %{"Notifier" <notifier@example.com>},
|
|
34
|
+
exception_recipients: %w{exceptions@example.com}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
# Campfire notifier sends notifications to your Campfire room. Requires 'tinder' gem.
|
|
38
|
+
# config.add_notifier :campfire, {
|
|
39
|
+
# subdomain: 'my_subdomain',
|
|
40
|
+
# token: 'my_token',
|
|
41
|
+
# room_name: 'my_room'
|
|
42
|
+
# }
|
|
43
|
+
|
|
44
|
+
# HipChat notifier sends notifications to your HipChat room. Requires 'hipchat' gem.
|
|
45
|
+
# config.add_notifier :hipchat, {
|
|
46
|
+
# api_token: 'my_token',
|
|
47
|
+
# room_name: 'my_room'
|
|
48
|
+
# }
|
|
49
|
+
|
|
50
|
+
# Webhook notifier sends notifications over HTTP protocol. Requires 'httparty' gem.
|
|
51
|
+
# config.add_notifier :webhook, {
|
|
52
|
+
# url: 'http://example.com:5555/hubot/path',
|
|
53
|
+
# http_method: :post
|
|
54
|
+
# }
|
|
55
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
require 'test_helper'
|
|
2
|
+
|
|
3
|
+
class RackTest < ActiveSupport::TestCase
|
|
4
|
+
setup do
|
|
5
|
+
@pass_app = Object.new
|
|
6
|
+
@pass_app.stubs(:call).returns([nil, { 'X-Cascade' => 'pass' }, nil])
|
|
7
|
+
|
|
8
|
+
@normal_app = Object.new
|
|
9
|
+
@normal_app.stubs(:call).returns([nil, {}, nil])
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
teardown do
|
|
13
|
+
ExceptionNotifier.error_grouping = false
|
|
14
|
+
ExceptionNotifier.notification_trigger = nil
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
test 'should ignore "X-Cascade" header by default' do
|
|
18
|
+
ExceptionNotifier.expects(:notify_exception).never
|
|
19
|
+
ExceptionNotification::Rack.new(@pass_app).call({})
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
test 'should notify on "X-Cascade" = "pass" if ignore_cascade_pass option is false' do
|
|
23
|
+
ExceptionNotifier.expects(:notify_exception).once
|
|
24
|
+
ExceptionNotification::Rack.new(@pass_app, ignore_cascade_pass: false).call({})
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
test 'should assign error_grouping if error_grouping is specified' do
|
|
28
|
+
refute ExceptionNotifier.error_grouping
|
|
29
|
+
ExceptionNotification::Rack.new(@normal_app, error_grouping: true).call({})
|
|
30
|
+
assert ExceptionNotifier.error_grouping
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
test 'should assign notification_trigger if notification_trigger is specified' do
|
|
34
|
+
assert_nil ExceptionNotifier.notification_trigger
|
|
35
|
+
ExceptionNotification::Rack.new(@normal_app, notification_trigger: ->(_i) { true }).call({})
|
|
36
|
+
assert_respond_to ExceptionNotifier.notification_trigger, :call
|
|
37
|
+
end
|
|
38
|
+
|
|
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
|
|
59
|
+
end
|
|
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
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
require 'test_helper'
|
|
2
|
+
|
|
3
|
+
# silence_warnings trick around require can be removed once
|
|
4
|
+
# https://github.com/collectiveidea/tinder/pull/77
|
|
5
|
+
# gets merged and released
|
|
6
|
+
silence_warnings do
|
|
7
|
+
require 'tinder'
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
class CampfireNotifierTest < ActiveSupport::TestCase
|
|
11
|
+
test 'should send campfire notification if properly configured' do
|
|
12
|
+
ExceptionNotifier::CampfireNotifier.stubs(:new).returns(Object.new)
|
|
13
|
+
campfire = ExceptionNotifier::CampfireNotifier.new(subdomain: 'test', token: 'test_token', room_name: 'test_room')
|
|
14
|
+
campfire.stubs(:call).returns(fake_notification)
|
|
15
|
+
notif = campfire.call(fake_exception)
|
|
16
|
+
|
|
17
|
+
assert !notif[:message].empty?
|
|
18
|
+
assert_equal notif[:message][:type], 'PasteMessage'
|
|
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'
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
test 'should send campfire notification without backtrace info if properly configured' do
|
|
25
|
+
ExceptionNotifier::CampfireNotifier.stubs(:new).returns(Object.new)
|
|
26
|
+
campfire = ExceptionNotifier::CampfireNotifier.new(subdomain: 'test', token: 'test_token', room_name: 'test_room')
|
|
27
|
+
campfire.stubs(:call).returns(fake_notification_without_backtrace)
|
|
28
|
+
notif = campfire.call(fake_exception_without_backtrace)
|
|
29
|
+
|
|
30
|
+
assert !notif[:message].empty?
|
|
31
|
+
assert_equal notif[:message][:type], 'PasteMessage'
|
|
32
|
+
assert_includes notif[:message][:body], 'A new exception occurred:'
|
|
33
|
+
assert_includes notif[:message][:body], 'my custom error'
|
|
34
|
+
end
|
|
35
|
+
|
|
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)
|
|
39
|
+
campfire = ExceptionNotifier::CampfireNotifier.new(wrong_params)
|
|
40
|
+
|
|
41
|
+
assert_nil campfire.room
|
|
42
|
+
assert_nil campfire.call(fake_exception)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
test 'should not send campfire notification if config attr missing' do
|
|
46
|
+
wrong_params = { subdomain: 'test', room_name: 'test_room' }
|
|
47
|
+
Tinder::Campfire.stubs(:new).with('test', {}).returns(nil)
|
|
48
|
+
campfire = ExceptionNotifier::CampfireNotifier.new(wrong_params)
|
|
49
|
+
|
|
50
|
+
assert_nil campfire.room
|
|
51
|
+
assert_nil campfire.call(fake_exception)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
test 'should send the new exception message if no :accumulated_errors_count option' do
|
|
55
|
+
campfire = ExceptionNotifier::CampfireNotifier.new({})
|
|
56
|
+
campfire.stubs(:active?).returns(true)
|
|
57
|
+
campfire.expects(:send_notice).with { |_, _, message| message.start_with?('A new exception occurred') }.once
|
|
58
|
+
campfire.call(fake_exception)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
test 'shoud send the exception message if :accumulated_errors_count option greater than 1' do
|
|
62
|
+
campfire = ExceptionNotifier::CampfireNotifier.new({})
|
|
63
|
+
campfire.stubs(:active?).returns(true)
|
|
64
|
+
campfire.expects(:send_notice).with { |_, _, message| message.start_with?('The exception occurred 3 times:') }.once
|
|
65
|
+
campfire.call(fake_exception, accumulated_errors_count: 3)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
test 'should call pre/post_callback if specified' do
|
|
69
|
+
pre_callback_called = 0
|
|
70
|
+
post_callback_called = 0
|
|
71
|
+
Tinder::Campfire.stubs(:new).returns(Object.new)
|
|
72
|
+
|
|
73
|
+
campfire = ExceptionNotifier::CampfireNotifier.new(
|
|
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
|
|
82
|
+
}
|
|
83
|
+
)
|
|
84
|
+
campfire.room = Object.new
|
|
85
|
+
campfire.room.stubs(:paste).returns(fake_notification)
|
|
86
|
+
campfire.call(fake_exception)
|
|
87
|
+
assert_equal(1, pre_callback_called)
|
|
88
|
+
assert_equal(1, post_callback_called)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
private
|
|
92
|
+
|
|
93
|
+
def fake_notification
|
|
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
|
+
}
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def fake_exception
|
|
103
|
+
5 / 0
|
|
104
|
+
rescue StandardError => e
|
|
105
|
+
e
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def fake_notification_without_backtrace
|
|
109
|
+
{
|
|
110
|
+
message: {
|
|
111
|
+
type: 'PasteMessage',
|
|
112
|
+
body: "A new exception occurred: 'my custom error'"
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def fake_exception_without_backtrace
|
|
118
|
+
StandardError.new('my custom error')
|
|
119
|
+
end
|
|
120
|
+
end
|
|
@@ -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
|