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
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
module ExceptionNotification
|
|
2
|
+
class Engine < ::Rails::Engine
|
|
3
|
+
config.exception_notification = ExceptionNotifier
|
|
4
|
+
config.exception_notification.logger = Rails.logger
|
|
5
|
+
config.exception_notification.error_grouping_cache = Rails.cache
|
|
6
|
+
|
|
7
|
+
config.app_middleware.use ExceptionNotification::Rack
|
|
8
|
+
end
|
|
9
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
require 'resque/failure/base'
|
|
2
|
+
|
|
3
|
+
module ExceptionNotification
|
|
4
|
+
class Resque < Resque::Failure::Base
|
|
5
|
+
def self.count
|
|
6
|
+
::Resque::Stat[:failed]
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def save
|
|
10
|
+
data = {
|
|
11
|
+
error_class: exception.class.name,
|
|
12
|
+
error_message: exception.message,
|
|
13
|
+
failed_at: Time.now.to_s,
|
|
14
|
+
payload: payload,
|
|
15
|
+
queue: queue,
|
|
16
|
+
worker: worker.to_s
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
ExceptionNotifier.notify_exception(exception, data: { resque: data })
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
require 'sidekiq'
|
|
2
|
+
|
|
3
|
+
# Note: this class is only needed for Sidekiq version < 3.
|
|
4
|
+
module ExceptionNotification
|
|
5
|
+
class Sidekiq
|
|
6
|
+
def call(_worker, msg, _queue)
|
|
7
|
+
yield
|
|
8
|
+
rescue Exception => exception
|
|
9
|
+
ExceptionNotifier.notify_exception(exception, data: { sidekiq: msg })
|
|
10
|
+
raise exception
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
if ::Sidekiq::VERSION < '3'
|
|
16
|
+
::Sidekiq.configure_server do |config|
|
|
17
|
+
config.server_middleware do |chain|
|
|
18
|
+
chain.add ::ExceptionNotification::Sidekiq
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
else
|
|
22
|
+
::Sidekiq.configure_server do |config|
|
|
23
|
+
config.error_handlers << proc do |ex, context|
|
|
24
|
+
ExceptionNotifier.notify_exception(ex, data: { sidekiq: context })
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
data/lib/exception_notifier.rb
CHANGED
|
@@ -1,75 +1,151 @@
|
|
|
1
|
-
require '
|
|
2
|
-
require '
|
|
3
|
-
require '
|
|
1
|
+
require 'logger'
|
|
2
|
+
require 'active_support/core_ext/string/inflections'
|
|
3
|
+
require 'active_support/core_ext/module/attribute_accessors'
|
|
4
|
+
require 'exception_notifier/base_notifier'
|
|
5
|
+
require 'exception_notifier/modules/error_grouping'
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
module ExceptionNotifier
|
|
8
|
+
include ErrorGrouping
|
|
6
9
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
autoload :BacktraceCleaner, 'exception_notifier/modules/backtrace_cleaner'
|
|
11
|
+
autoload :Formatter, 'exception_notifier/modules/formatter'
|
|
12
|
+
|
|
13
|
+
autoload :Notifier, 'exception_notifier/notifier'
|
|
14
|
+
autoload :EmailNotifier, 'exception_notifier/email_notifier'
|
|
15
|
+
autoload :CampfireNotifier, 'exception_notifier/campfire_notifier'
|
|
16
|
+
autoload :HipchatNotifier, 'exception_notifier/hipchat_notifier'
|
|
17
|
+
autoload :WebhookNotifier, 'exception_notifier/webhook_notifier'
|
|
18
|
+
autoload :IrcNotifier, 'exception_notifier/irc_notifier'
|
|
19
|
+
autoload :SlackNotifier, 'exception_notifier/slack_notifier'
|
|
20
|
+
autoload :MattermostNotifier, 'exception_notifier/mattermost_notifier'
|
|
21
|
+
autoload :TeamsNotifier, 'exception_notifier/teams_notifier'
|
|
22
|
+
autoload :SnsNotifier, 'exception_notifier/sns_notifier'
|
|
23
|
+
autoload :GoogleChatNotifier, 'exception_notifier/google_chat_notifier'
|
|
24
|
+
autoload :DatadogNotifier, 'exception_notifier/datadog_notifier'
|
|
25
|
+
|
|
26
|
+
class UndefinedNotifierError < StandardError; end
|
|
27
|
+
|
|
28
|
+
# Define logger
|
|
29
|
+
mattr_accessor :logger
|
|
30
|
+
@@logger = Logger.new(STDOUT)
|
|
31
|
+
|
|
32
|
+
# Define a set of exceptions to be ignored, ie, dont send notifications when any of them are raised.
|
|
33
|
+
mattr_accessor :ignored_exceptions
|
|
34
|
+
@@ignored_exceptions = %w[ActiveRecord::RecordNotFound Mongoid::Errors::DocumentNotFound AbstractController::ActionNotFound ActionController::RoutingError ActionController::UnknownFormat ActionController::UrlGenerationError]
|
|
35
|
+
|
|
36
|
+
mattr_accessor :testing_mode
|
|
37
|
+
@@testing_mode = false
|
|
38
|
+
|
|
39
|
+
class << self
|
|
40
|
+
# Store conditions that decide when exceptions must be ignored or not.
|
|
41
|
+
@@ignores = []
|
|
42
|
+
|
|
43
|
+
# Store notifiers that send notifications when exceptions are raised.
|
|
44
|
+
@@notifiers = {}
|
|
45
|
+
|
|
46
|
+
def testing_mode!
|
|
47
|
+
self.testing_mode = true
|
|
12
48
|
end
|
|
13
|
-
end
|
|
14
49
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
50
|
+
def notify_exception(exception, options = {}, &block)
|
|
51
|
+
return false if ignored_exception?(options[:ignore_exceptions], exception)
|
|
52
|
+
return false if ignored?(exception, options)
|
|
18
53
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
Notifier.default_exception_recipients = @options[:exception_recipients]
|
|
24
|
-
Notifier.default_email_prefix = @options[:email_prefix]
|
|
25
|
-
Notifier.default_email_format = @options[:email_format]
|
|
26
|
-
Notifier.default_sections = @options[:sections]
|
|
27
|
-
Notifier.default_background_sections = @options[:background_sections]
|
|
28
|
-
Notifier.default_verbose_subject = @options[:verbose_subject]
|
|
29
|
-
Notifier.default_normalize_subject = @options[:normalize_subject]
|
|
30
|
-
Notifier.default_smtp_settings = @options[:smtp_settings]
|
|
31
|
-
Notifier.default_email_headers = @options[:email_headers]
|
|
32
|
-
|
|
33
|
-
@campfire = CampfireNotifier.new @options[:campfire]
|
|
34
|
-
|
|
35
|
-
@options[:ignore_exceptions] ||= self.class.default_ignore_exceptions
|
|
36
|
-
@options[:ignore_crawlers] ||= self.class.default_ignore_crawlers
|
|
37
|
-
@options[:ignore_if] ||= lambda { |env, e| false }
|
|
38
|
-
end
|
|
54
|
+
if error_grouping
|
|
55
|
+
errors_count = group_error!(exception, options)
|
|
56
|
+
return false unless send_notification?(exception, errors_count)
|
|
57
|
+
end
|
|
39
58
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
unless ignored_exception(options[:ignore_exceptions], exception) ||
|
|
47
|
-
from_crawler(options[:ignore_crawlers], env['HTTP_USER_AGENT']) ||
|
|
48
|
-
conditionally_ignored(options[:ignore_if], env, exception)
|
|
49
|
-
Notifier.exception_notification(env, exception).deliver
|
|
50
|
-
@campfire.exception_notification(exception)
|
|
51
|
-
env['exception_notifier.delivered'] = true
|
|
59
|
+
selected_notifiers = options.delete(:notifiers) || notifiers
|
|
60
|
+
[*selected_notifiers].each do |notifier|
|
|
61
|
+
fire_notification(notifier, exception, options.dup, &block)
|
|
62
|
+
end
|
|
63
|
+
true
|
|
52
64
|
end
|
|
53
65
|
|
|
54
|
-
|
|
55
|
-
|
|
66
|
+
def register_exception_notifier(name, notifier_or_options)
|
|
67
|
+
if notifier_or_options.respond_to?(:call)
|
|
68
|
+
@@notifiers[name] = notifier_or_options
|
|
69
|
+
elsif notifier_or_options.is_a?(Hash)
|
|
70
|
+
create_and_register_notifier(name, notifier_or_options)
|
|
71
|
+
else
|
|
72
|
+
raise ArgumentError, "Invalid notifier '#{name}' defined as #{notifier_or_options.inspect}"
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
alias add_notifier register_exception_notifier
|
|
56
76
|
|
|
57
|
-
|
|
77
|
+
def unregister_exception_notifier(name)
|
|
78
|
+
@@notifiers.delete(name)
|
|
79
|
+
end
|
|
58
80
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
81
|
+
def registered_exception_notifier(name)
|
|
82
|
+
@@notifiers[name]
|
|
83
|
+
end
|
|
62
84
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
end unless ignore_array.blank?
|
|
67
|
-
false
|
|
68
|
-
end
|
|
85
|
+
def notifiers
|
|
86
|
+
@@notifiers.keys
|
|
87
|
+
end
|
|
69
88
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
89
|
+
# Adds a condition to decide when an exception must be ignored or not.
|
|
90
|
+
#
|
|
91
|
+
# ExceptionNotifier.ignore_if do |exception, options|
|
|
92
|
+
# not Rails.env.production?
|
|
93
|
+
# end
|
|
94
|
+
def ignore_if(&block)
|
|
95
|
+
@@ignores << block
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def ignore_crawlers(crawlers)
|
|
99
|
+
ignore_if do |_exception, opts|
|
|
100
|
+
opts.key?(:env) && from_crawler(opts[:env], crawlers)
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def clear_ignore_conditions!
|
|
105
|
+
@@ignores.clear
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
private
|
|
109
|
+
|
|
110
|
+
def ignored?(exception, options)
|
|
111
|
+
@@ignores.any? { |condition| condition.call(exception, options) }
|
|
112
|
+
rescue Exception => e
|
|
113
|
+
raise e if @@testing_mode
|
|
114
|
+
|
|
115
|
+
logger.warn "An error occurred when evaluating an ignore condition. #{e.class}: #{e.message}\n#{e.backtrace.join("\n")}"
|
|
116
|
+
false
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def ignored_exception?(ignore_array, exception)
|
|
120
|
+
all_ignored_exceptions = (Array(ignored_exceptions) + Array(ignore_array)).map(&:to_s)
|
|
121
|
+
exception_ancestors = exception.class.ancestors.map(&:to_s)
|
|
122
|
+
!(all_ignored_exceptions & exception_ancestors).empty?
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def fire_notification(notifier_name, exception, options, &block)
|
|
126
|
+
notifier = registered_exception_notifier(notifier_name)
|
|
127
|
+
notifier.call(exception, options, &block)
|
|
128
|
+
rescue Exception => e
|
|
129
|
+
raise e if @@testing_mode
|
|
130
|
+
|
|
131
|
+
logger.warn "An error occurred when sending a notification using '#{notifier_name}' notifier. #{e.class}: #{e.message}\n#{e.backtrace.join("\n")}"
|
|
132
|
+
false
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def create_and_register_notifier(name, options)
|
|
136
|
+
notifier_classname = "#{name}_notifier".camelize
|
|
137
|
+
notifier_class = ExceptionNotifier.const_get(notifier_classname)
|
|
138
|
+
notifier = notifier_class.new(options)
|
|
139
|
+
register_exception_notifier(name, notifier)
|
|
140
|
+
rescue NameError => e
|
|
141
|
+
raise UndefinedNotifierError, "No notifier named '#{name}' was found. Please, revise your configuration options. Cause: #{e.message}"
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def from_crawler(env, ignored_crawlers)
|
|
145
|
+
agent = env['HTTP_USER_AGENT']
|
|
146
|
+
Array(ignored_crawlers).any? do |crawler|
|
|
147
|
+
agent =~ Regexp.new(crawler)
|
|
148
|
+
end
|
|
149
|
+
end
|
|
74
150
|
end
|
|
75
151
|
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module ExceptionNotifier
|
|
2
|
+
class BaseNotifier
|
|
3
|
+
attr_accessor :base_options
|
|
4
|
+
|
|
5
|
+
def initialize(options = {})
|
|
6
|
+
@base_options = options
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def send_notice(exception, options, message, message_opts = nil)
|
|
10
|
+
_pre_callback(exception, options, message, message_opts)
|
|
11
|
+
result = yield(message, message_opts)
|
|
12
|
+
_post_callback(exception, options, message, message_opts)
|
|
13
|
+
result
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def _pre_callback(exception, options, message, message_opts)
|
|
17
|
+
@base_options[:pre_callback].call(options, self, exception.backtrace, message, message_opts) if @base_options[:pre_callback].respond_to?(:call)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def _post_callback(exception, options, message, message_opts)
|
|
21
|
+
@base_options[:post_callback].call(options, self, exception.backtrace, message, message_opts) if @base_options[:post_callback].respond_to?(:call)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -1,26 +1,33 @@
|
|
|
1
|
-
|
|
2
|
-
class CampfireNotifier
|
|
3
|
-
cattr_accessor :tinder_available, true
|
|
4
|
-
|
|
1
|
+
module ExceptionNotifier
|
|
2
|
+
class CampfireNotifier < BaseNotifier
|
|
5
3
|
attr_accessor :subdomain
|
|
6
4
|
attr_accessor :token
|
|
7
5
|
attr_accessor :room
|
|
8
6
|
|
|
9
7
|
def initialize(options)
|
|
8
|
+
super
|
|
10
9
|
begin
|
|
11
|
-
return unless tinder_available
|
|
12
|
-
|
|
13
10
|
subdomain = options.delete(:subdomain)
|
|
14
11
|
room_name = options.delete(:room_name)
|
|
15
12
|
@campfire = Tinder::Campfire.new subdomain, options
|
|
16
13
|
@room = @campfire.find_room_by_name room_name
|
|
17
|
-
rescue
|
|
14
|
+
rescue StandardError
|
|
18
15
|
@campfire = @room = nil
|
|
19
16
|
end
|
|
20
17
|
end
|
|
21
18
|
|
|
22
|
-
def
|
|
23
|
-
|
|
19
|
+
def call(exception, options = {})
|
|
20
|
+
return unless active?
|
|
21
|
+
|
|
22
|
+
message = if options[:accumulated_errors_count].to_i > 1
|
|
23
|
+
"The exception occurred #{options[:accumulated_errors_count]} times: '#{exception.message}'"
|
|
24
|
+
else
|
|
25
|
+
"A new exception occurred: '#{exception.message}'"
|
|
26
|
+
end
|
|
27
|
+
message += " on '#{exception.backtrace.first}'" if exception.backtrace
|
|
28
|
+
send_notice(exception, options, message) do |msg, _|
|
|
29
|
+
@room.paste msg
|
|
30
|
+
end
|
|
24
31
|
end
|
|
25
32
|
|
|
26
33
|
private
|
|
@@ -30,5 +37,3 @@ class ExceptionNotifier
|
|
|
30
37
|
end
|
|
31
38
|
end
|
|
32
39
|
end
|
|
33
|
-
|
|
34
|
-
ExceptionNotifier::CampfireNotifier.tinder_available = Gem.loaded_specs.keys.include? 'tinder'
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
require 'action_dispatch'
|
|
2
|
+
|
|
3
|
+
module ExceptionNotifier
|
|
4
|
+
class DatadogNotifier < BaseNotifier
|
|
5
|
+
attr_reader :client,
|
|
6
|
+
:default_options
|
|
7
|
+
|
|
8
|
+
def initialize(options)
|
|
9
|
+
super
|
|
10
|
+
@client = options.fetch(:client)
|
|
11
|
+
@default_options = options
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def call(exception, options = {})
|
|
15
|
+
client.emit_event(
|
|
16
|
+
datadog_event(exception, options)
|
|
17
|
+
)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def datadog_event(exception, options = {})
|
|
21
|
+
DatadogExceptionEvent.new(
|
|
22
|
+
exception,
|
|
23
|
+
options.reverse_merge(default_options)
|
|
24
|
+
).event
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
class DatadogExceptionEvent
|
|
28
|
+
include ExceptionNotifier::BacktraceCleaner
|
|
29
|
+
|
|
30
|
+
MAX_TITLE_LENGTH = 120
|
|
31
|
+
MAX_VALUE_LENGTH = 300
|
|
32
|
+
MAX_BACKTRACE_SIZE = 3
|
|
33
|
+
ALERT_TYPE = 'error'.freeze
|
|
34
|
+
|
|
35
|
+
attr_reader :exception,
|
|
36
|
+
:options
|
|
37
|
+
|
|
38
|
+
def initialize(exception, options)
|
|
39
|
+
@exception = exception
|
|
40
|
+
@options = options
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def request
|
|
44
|
+
@request ||= ActionDispatch::Request.new(options[:env]) if options[:env]
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def controller
|
|
48
|
+
@controller ||= options[:env] && options[:env]['action_controller.instance']
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def backtrace
|
|
52
|
+
@backtrace ||= exception.backtrace ? clean_backtrace(exception) : []
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def tags
|
|
56
|
+
options[:tags] || []
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def title_prefix
|
|
60
|
+
options[:title_prefix] || ''
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def event
|
|
64
|
+
title = formatted_title
|
|
65
|
+
body = formatted_body
|
|
66
|
+
|
|
67
|
+
Dogapi::Event.new(
|
|
68
|
+
body,
|
|
69
|
+
msg_title: title,
|
|
70
|
+
alert_type: ALERT_TYPE,
|
|
71
|
+
tags: tags,
|
|
72
|
+
aggregation_key: [title]
|
|
73
|
+
)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def formatted_title
|
|
77
|
+
title = ''
|
|
78
|
+
title << title_prefix
|
|
79
|
+
title << "#{controller.controller_name} #{controller.action_name}" if controller
|
|
80
|
+
title << " (#{exception.class})"
|
|
81
|
+
title << " #{exception.message.inspect}"
|
|
82
|
+
|
|
83
|
+
truncate(title, MAX_TITLE_LENGTH)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def formatted_body
|
|
87
|
+
text = []
|
|
88
|
+
|
|
89
|
+
text << '%%%'
|
|
90
|
+
text << formatted_request if request
|
|
91
|
+
text << formatted_session if request
|
|
92
|
+
text << formatted_backtrace
|
|
93
|
+
text << '%%%'
|
|
94
|
+
|
|
95
|
+
text.join("\n")
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def formatted_key_value(key, value)
|
|
99
|
+
"**#{key}:** #{value}"
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def formatted_request
|
|
103
|
+
text = []
|
|
104
|
+
text << '### **Request**'
|
|
105
|
+
text << formatted_key_value('URL', request.url)
|
|
106
|
+
text << formatted_key_value('HTTP Method', request.request_method)
|
|
107
|
+
text << formatted_key_value('IP Address', request.remote_ip)
|
|
108
|
+
text << formatted_key_value('Parameters', request.filtered_parameters.inspect)
|
|
109
|
+
text << formatted_key_value('Timestamp', Time.current)
|
|
110
|
+
text << formatted_key_value('Server', Socket.gethostname)
|
|
111
|
+
if defined?(Rails) && Rails.respond_to?(:root)
|
|
112
|
+
text << formatted_key_value('Rails root', Rails.root)
|
|
113
|
+
end
|
|
114
|
+
text << formatted_key_value('Process', $PROCESS_ID)
|
|
115
|
+
text << '___'
|
|
116
|
+
text.join("\n")
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def formatted_session
|
|
120
|
+
text = []
|
|
121
|
+
text << '### **Session**'
|
|
122
|
+
text << formatted_key_value('Data', request.session.to_hash)
|
|
123
|
+
text << '___'
|
|
124
|
+
text.join("\n")
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def formatted_backtrace
|
|
128
|
+
size = [backtrace.size, MAX_BACKTRACE_SIZE].min
|
|
129
|
+
|
|
130
|
+
text = []
|
|
131
|
+
text << '### **Backtrace**'
|
|
132
|
+
text << '````'
|
|
133
|
+
size.times { |i| text << backtrace[i] }
|
|
134
|
+
text << '````'
|
|
135
|
+
text << '___'
|
|
136
|
+
text.join("\n")
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def truncate(string, max)
|
|
140
|
+
string.length > max ? "#{string[0...max]}..." : string
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def inspect_object(object)
|
|
144
|
+
case object
|
|
145
|
+
when Hash, Array
|
|
146
|
+
truncate(object.inspect, MAX_VALUE_LENGTH)
|
|
147
|
+
else
|
|
148
|
+
object.to_s
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|