exception_notification 4.3.0 → 4.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Appraisals +2 -2
- data/CHANGELOG.rdoc +14 -0
- data/CONTRIBUTING.md +18 -0
- data/Gemfile +1 -1
- data/README.md +64 -935
- data/Rakefile +2 -2
- 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 +6 -6
- data/examples/sinatra/config.ru +1 -1
- data/examples/sinatra/sinatra_app.rb +14 -10
- data/exception_notification.gemspec +27 -22
- data/gemfiles/rails4_0.gemfile +3 -3
- data/gemfiles/rails4_1.gemfile +3 -3
- data/gemfiles/rails4_2.gemfile +3 -3
- data/gemfiles/rails5_0.gemfile +3 -3
- data/gemfiles/rails5_1.gemfile +3 -3
- data/gemfiles/rails5_2.gemfile +7 -0
- data/gemfiles/rails6_0.gemfile +7 -0
- data/lib/exception_notification.rb +1 -0
- data/lib/exception_notification/rack.rb +8 -21
- data/lib/exception_notification/resque.rb +8 -10
- data/lib/exception_notification/sidekiq.rb +8 -12
- data/lib/exception_notification/version.rb +3 -0
- data/lib/exception_notifier.rb +20 -3
- data/lib/exception_notifier/base_notifier.rb +2 -3
- data/lib/exception_notifier/campfire_notifier.rb +12 -13
- data/lib/exception_notifier/datadog_notifier.rb +153 -0
- data/lib/exception_notifier/email_notifier.rb +64 -87
- data/lib/exception_notifier/google_chat_notifier.rb +25 -119
- data/lib/exception_notifier/hipchat_notifier.rb +11 -12
- data/lib/exception_notifier/irc_notifier.rb +32 -30
- data/lib/exception_notifier/mattermost_notifier.rb +47 -140
- data/lib/exception_notifier/modules/backtrace_cleaner.rb +0 -2
- data/lib/exception_notifier/modules/error_grouping.rb +5 -5
- data/lib/exception_notifier/modules/formatter.rb +118 -0
- data/lib/exception_notifier/notifier.rb +5 -6
- data/lib/exception_notifier/slack_notifier.rb +63 -40
- data/lib/exception_notifier/sns_notifier.rb +17 -11
- data/lib/exception_notifier/teams_notifier.rb +58 -44
- data/lib/exception_notifier/views/exception_notifier/_backtrace.html.erb +1 -1
- data/lib/exception_notifier/views/exception_notifier/_environment.text.erb +1 -1
- data/lib/exception_notifier/views/exception_notifier/_request.text.erb +1 -1
- data/lib/exception_notifier/views/exception_notifier/exception_notification.html.erb +2 -2
- data/lib/exception_notifier/views/exception_notifier/exception_notification.text.erb +2 -2
- data/lib/exception_notifier/webhook_notifier.rb +14 -11
- data/lib/generators/exception_notification/install_generator.rb +5 -5
- data/lib/generators/exception_notification/templates/{exception_notification.rb → exception_notification.rb.erb} +13 -11
- data/test/exception_notification/rack_test.rb +27 -11
- data/test/exception_notification/resque_test.rb +52 -0
- data/test/exception_notifier/campfire_notifier_test.rb +42 -42
- data/test/exception_notifier/datadog_notifier_test.rb +151 -0
- data/test/exception_notifier/email_notifier_test.rb +269 -153
- data/test/exception_notifier/google_chat_notifier_test.rb +154 -101
- data/test/exception_notifier/hipchat_notifier_test.rb +78 -81
- data/test/exception_notifier/irc_notifier_test.rb +34 -34
- data/test/exception_notifier/mattermost_notifier_test.rb +164 -67
- data/test/exception_notifier/modules/error_grouping_test.rb +39 -40
- data/test/exception_notifier/modules/formatter_test.rb +150 -0
- data/test/exception_notifier/sidekiq_test.rb +6 -6
- data/test/exception_notifier/slack_notifier_test.rb +61 -60
- data/test/exception_notifier/sns_notifier_test.rb +27 -32
- data/test/exception_notifier/teams_notifier_test.rb +23 -26
- data/test/exception_notifier/webhook_notifier_test.rb +48 -46
- data/test/exception_notifier_test.rb +41 -38
- 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 +11 -14
- metadata +136 -166
- data/test/dummy/.gitignore +0 -4
- 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 -17
- data/test/dummy/config/environments/development.rb +0 -25
- data/test/dummy/config/environments/production.rb +0 -50
- 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 -8
- 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/functional/posts_controller_test.rb +0 -237
- data/test/dummy/test/test_helper.rb +0 -7
data/lib/exception_notifier.rb
CHANGED
@@ -8,6 +8,7 @@ module ExceptionNotifier
|
|
8
8
|
include ErrorGrouping
|
9
9
|
|
10
10
|
autoload :BacktraceCleaner, 'exception_notifier/modules/backtrace_cleaner'
|
11
|
+
autoload :Formatter, 'exception_notifier/modules/formatter'
|
11
12
|
|
12
13
|
autoload :Notifier, 'exception_notifier/notifier'
|
13
14
|
autoload :EmailNotifier, 'exception_notifier/email_notifier'
|
@@ -20,6 +21,7 @@ module ExceptionNotifier
|
|
20
21
|
autoload :TeamsNotifier, 'exception_notifier/teams_notifier'
|
21
22
|
autoload :SnsNotifier, 'exception_notifier/sns_notifier'
|
22
23
|
autoload :GoogleChatNotifier, 'exception_notifier/google_chat_notifier'
|
24
|
+
autoload :DatadogNotifier, 'exception_notifier/datadog_notifier'
|
23
25
|
|
24
26
|
class UndefinedNotifierError < StandardError; end
|
25
27
|
|
@@ -29,7 +31,7 @@ module ExceptionNotifier
|
|
29
31
|
|
30
32
|
# Define a set of exceptions to be ignored, ie, dont send notifications when any of them are raised.
|
31
33
|
mattr_accessor :ignored_exceptions
|
32
|
-
@@ignored_exceptions = %w
|
34
|
+
@@ignored_exceptions = %w[ActiveRecord::RecordNotFound Mongoid::Errors::DocumentNotFound AbstractController::ActionNotFound ActionController::RoutingError ActionController::UnknownFormat ActionController::UrlGenerationError]
|
33
35
|
|
34
36
|
mattr_accessor :testing_mode
|
35
37
|
@@testing_mode = false
|
@@ -45,9 +47,10 @@ module ExceptionNotifier
|
|
45
47
|
self.testing_mode = true
|
46
48
|
end
|
47
49
|
|
48
|
-
def notify_exception(exception, options={}, &block)
|
50
|
+
def notify_exception(exception, options = {}, &block)
|
49
51
|
return false if ignored_exception?(options[:ignore_exceptions], exception)
|
50
52
|
return false if ignored?(exception, options)
|
53
|
+
|
51
54
|
if error_grouping
|
52
55
|
errors_count = group_error!(exception, options)
|
53
56
|
return false unless send_notification?(exception, errors_count)
|
@@ -92,13 +95,20 @@ module ExceptionNotifier
|
|
92
95
|
@@ignores << block
|
93
96
|
end
|
94
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
|
+
|
95
104
|
def clear_ignore_conditions!
|
96
105
|
@@ignores.clear
|
97
106
|
end
|
98
107
|
|
99
108
|
private
|
109
|
+
|
100
110
|
def ignored?(exception, options)
|
101
|
-
@@ignores.any?{ |condition| condition.call(exception, options) }
|
111
|
+
@@ignores.any? { |condition| condition.call(exception, options) }
|
102
112
|
rescue Exception => e
|
103
113
|
raise e if @@testing_mode
|
104
114
|
|
@@ -130,5 +140,12 @@ module ExceptionNotifier
|
|
130
140
|
rescue NameError => e
|
131
141
|
raise UndefinedNotifierError, "No notifier named '#{name}' was found. Please, revise your configuration options. Cause: #{e.message}"
|
132
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
|
133
150
|
end
|
134
151
|
end
|
@@ -2,11 +2,11 @@ module ExceptionNotifier
|
|
2
2
|
class BaseNotifier
|
3
3
|
attr_accessor :base_options
|
4
4
|
|
5
|
-
def initialize(options={})
|
5
|
+
def initialize(options = {})
|
6
6
|
@base_options = options
|
7
7
|
end
|
8
8
|
|
9
|
-
def send_notice(exception, options, message, message_opts=nil)
|
9
|
+
def send_notice(exception, options, message, message_opts = nil)
|
10
10
|
_pre_callback(exception, options, message, message_opts)
|
11
11
|
result = yield(message, message_opts)
|
12
12
|
_post_callback(exception, options, message, message_opts)
|
@@ -20,6 +20,5 @@ module ExceptionNotifier
|
|
20
20
|
def _post_callback(exception, options, message, message_opts)
|
21
21
|
@base_options[:post_callback].call(options, self, exception.backtrace, message, message_opts) if @base_options[:post_callback].respond_to?(:call)
|
22
22
|
end
|
23
|
-
|
24
23
|
end
|
25
24
|
end
|
@@ -1,6 +1,5 @@
|
|
1
1
|
module ExceptionNotifier
|
2
2
|
class CampfireNotifier < BaseNotifier
|
3
|
-
|
4
3
|
attr_accessor :subdomain
|
5
4
|
attr_accessor :token
|
6
5
|
attr_accessor :room
|
@@ -12,22 +11,22 @@ module ExceptionNotifier
|
|
12
11
|
room_name = options.delete(:room_name)
|
13
12
|
@campfire = Tinder::Campfire.new subdomain, options
|
14
13
|
@room = @campfire.find_room_by_name room_name
|
15
|
-
rescue
|
14
|
+
rescue StandardError
|
16
15
|
@campfire = @room = nil
|
17
16
|
end
|
18
17
|
end
|
19
18
|
|
20
|
-
def call(exception, options={})
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
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
|
31
30
|
end
|
32
31
|
end
|
33
32
|
|
@@ -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
|
@@ -1,4 +1,3 @@
|
|
1
|
-
require "active_support/core_ext/hash/reverse_merge"
|
2
1
|
require 'active_support/core_ext/time'
|
3
2
|
require 'action_mailer'
|
4
3
|
require 'action_dispatch'
|
@@ -6,47 +5,61 @@ require 'pp'
|
|
6
5
|
|
7
6
|
module ExceptionNotifier
|
8
7
|
class EmailNotifier < BaseNotifier
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
8
|
+
DEFAULT_OPTIONS = {
|
9
|
+
sender_address: %("Exception Notifier" <exception.notifier@example.com>),
|
10
|
+
exception_recipients: [],
|
11
|
+
email_prefix: '[ERROR] ',
|
12
|
+
email_format: :text,
|
13
|
+
sections: %w[request session environment backtrace],
|
14
|
+
background_sections: %w[backtrace data],
|
15
|
+
verbose_subject: true,
|
16
|
+
normalize_subject: false,
|
17
|
+
include_controller_and_action_names_in_subject: true,
|
18
|
+
delivery_method: nil,
|
19
|
+
mailer_settings: nil,
|
20
|
+
email_headers: {},
|
21
|
+
mailer_parent: 'ActionMailer::Base',
|
22
|
+
template_path: 'exception_notifier',
|
23
|
+
deliver_with: nil
|
24
|
+
}.freeze
|
14
25
|
|
15
26
|
module Mailer
|
16
27
|
class MissingController
|
17
|
-
def method_missing(*args, &block)
|
18
|
-
end
|
28
|
+
def method_missing(*args, &block); end
|
19
29
|
end
|
20
30
|
|
21
31
|
def self.extended(base)
|
22
32
|
base.class_eval do
|
23
|
-
|
33
|
+
send(:include, ExceptionNotifier::BacktraceCleaner)
|
24
34
|
|
25
35
|
# Append application view path to the ExceptionNotifier lookup context.
|
26
|
-
|
36
|
+
append_view_path "#{File.dirname(__FILE__)}/views"
|
27
37
|
|
28
|
-
def exception_notification(env, exception, options={}, default_options={})
|
38
|
+
def exception_notification(env, exception, options = {}, default_options = {})
|
29
39
|
load_custom_views
|
30
40
|
|
31
41
|
@env = env
|
32
42
|
@exception = exception
|
33
|
-
|
43
|
+
|
44
|
+
env_options = env['exception_notifier.options'] || {}
|
45
|
+
@options = default_options.merge(env_options).merge(options)
|
46
|
+
|
34
47
|
@kontroller = env['action_controller.instance'] || MissingController.new
|
35
48
|
@request = ActionDispatch::Request.new(env)
|
36
49
|
@backtrace = exception.backtrace ? clean_backtrace(exception) : []
|
37
50
|
@timestamp = Time.current
|
38
51
|
@sections = @options[:sections]
|
39
52
|
@data = (env['exception_notifier.exception_data'] || {}).merge(options[:data] || {})
|
40
|
-
@sections
|
53
|
+
@sections += %w[data] unless @data.empty?
|
41
54
|
|
42
55
|
compose_email
|
43
56
|
end
|
44
57
|
|
45
|
-
def background_exception_notification(exception, options={}, default_options={})
|
58
|
+
def background_exception_notification(exception, options = {}, default_options = {})
|
46
59
|
load_custom_views
|
47
60
|
|
48
61
|
@exception = exception
|
49
|
-
@options =
|
62
|
+
@options = default_options.merge(options).symbolize_keys
|
50
63
|
@backtrace = exception.backtrace || []
|
51
64
|
@timestamp = Time.current
|
52
65
|
@sections = @options[:background_sections]
|
@@ -59,13 +72,13 @@ module ExceptionNotifier
|
|
59
72
|
private
|
60
73
|
|
61
74
|
def compose_subject
|
62
|
-
subject =
|
75
|
+
subject = @options[:email_prefix].to_s.dup
|
63
76
|
subject << "(#{@options[:accumulated_errors_count]} times)" if @options[:accumulated_errors_count].to_i > 1
|
64
77
|
subject << "#{@kontroller.controller_name} #{@kontroller.action_name}" if @kontroller && @options[:include_controller_and_action_names_in_subject]
|
65
78
|
subject << " (#{@exception.class})"
|
66
79
|
subject << " #{@exception.message.inspect}" if @options[:verbose_subject]
|
67
80
|
subject = EmailNotifier.normalize_digits(subject) if @options[:normalize_subject]
|
68
|
-
subject.length > 120 ? subject[0...120] +
|
81
|
+
subject.length > 120 ? subject[0...120] + '...' : subject
|
69
82
|
end
|
70
83
|
|
71
84
|
def set_data_variables
|
@@ -82,17 +95,17 @@ module ExceptionNotifier
|
|
82
95
|
|
83
96
|
def inspect_object(object)
|
84
97
|
case object
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
98
|
+
when Hash, Array
|
99
|
+
truncate(object.inspect, 300)
|
100
|
+
else
|
101
|
+
object.to_s
|
89
102
|
end
|
90
103
|
end
|
91
104
|
|
92
105
|
helper_method :safe_encode
|
93
106
|
|
94
107
|
def safe_encode(value)
|
95
|
-
value.encode(
|
108
|
+
value.encode('utf-8', invalid: :replace, undef: :replace, replace: '_')
|
96
109
|
end
|
97
110
|
|
98
111
|
def html_mail?
|
@@ -106,11 +119,11 @@ module ExceptionNotifier
|
|
106
119
|
exception_recipients = maybe_call(@options[:exception_recipients])
|
107
120
|
|
108
121
|
headers = {
|
109
|
-
:
|
110
|
-
:
|
111
|
-
:
|
112
|
-
:
|
113
|
-
:
|
122
|
+
delivery_method: @options[:delivery_method],
|
123
|
+
to: exception_recipients,
|
124
|
+
from: @options[:sender_address],
|
125
|
+
subject: subject,
|
126
|
+
template_name: name
|
114
127
|
}.merge(@options[:email_headers])
|
115
128
|
|
116
129
|
mail = mail(headers) do |format|
|
@@ -124,9 +137,7 @@ module ExceptionNotifier
|
|
124
137
|
end
|
125
138
|
|
126
139
|
def load_custom_views
|
127
|
-
if defined?(Rails) && Rails.respond_to?(:root)
|
128
|
-
self.prepend_view_path Rails.root.nil? ? "app/views" : "#{Rails.root}/app/views"
|
129
|
-
end
|
140
|
+
prepend_view_path Rails.root.nil? ? 'app/views' : "#{Rails.root}/app/views" if defined?(Rails) && Rails.respond_to?(:root)
|
130
141
|
end
|
131
142
|
|
132
143
|
def maybe_call(maybe_proc)
|
@@ -138,82 +149,48 @@ module ExceptionNotifier
|
|
138
149
|
|
139
150
|
def initialize(options)
|
140
151
|
super
|
152
|
+
|
141
153
|
delivery_method = (options[:delivery_method] || :smtp)
|
142
154
|
mailer_settings_key = "#{delivery_method}_settings".to_sym
|
143
155
|
options[:mailer_settings] = options.delete(mailer_settings_key)
|
144
156
|
|
145
|
-
|
146
|
-
:sender_address, :exception_recipients, :pre_callback,
|
147
|
-
:post_callback, :email_prefix, :email_format,
|
148
|
-
:sections, :background_sections, :verbose_subject, :normalize_subject,
|
149
|
-
:include_controller_and_action_names_in_subject, :delivery_method, :mailer_settings,
|
150
|
-
:email_headers, :mailer_parent, :template_path, :deliver_with].include?(k)}.each{|k,v| send("#{k}=", v)}
|
151
|
-
end
|
152
|
-
|
153
|
-
def options
|
154
|
-
@options ||= {}.tap do |opts|
|
155
|
-
self.instance_variables.each { |var| opts[var[1..-1].to_sym] = self.instance_variable_get(var) }
|
156
|
-
end
|
157
|
-
end
|
158
|
-
|
159
|
-
def mailer
|
160
|
-
@mailer ||= Class.new(mailer_parent.constantize).tap do |mailer|
|
161
|
-
mailer.extend(EmailNotifier::Mailer)
|
162
|
-
mailer.mailer_name = template_path
|
163
|
-
end
|
157
|
+
@base_options = DEFAULT_OPTIONS.merge(options)
|
164
158
|
end
|
165
159
|
|
166
|
-
def call(exception, options={})
|
160
|
+
def call(exception, options = {})
|
167
161
|
message = create_email(exception, options)
|
168
162
|
|
169
|
-
|
170
|
-
if deliver_with == :default
|
171
|
-
if message.respond_to?(:deliver_now)
|
172
|
-
message.deliver_now
|
173
|
-
else
|
174
|
-
message.deliver
|
175
|
-
end
|
176
|
-
else
|
177
|
-
message.send(deliver_with)
|
178
|
-
end
|
163
|
+
message.send(base_options[:deliver_with] || default_deliver_with(message))
|
179
164
|
end
|
180
165
|
|
181
|
-
def create_email(exception, options={})
|
166
|
+
def create_email(exception, options = {})
|
182
167
|
env = options[:env]
|
183
|
-
|
184
|
-
|
185
|
-
|
168
|
+
|
169
|
+
send_notice(exception, options, nil, base_options) do |_, default_opts|
|
170
|
+
if env.nil?
|
186
171
|
mailer.background_exception_notification(exception, options, default_opts)
|
187
|
-
|
188
|
-
else
|
189
|
-
send_notice(exception, options, nil, default_options) do |_, default_opts|
|
172
|
+
else
|
190
173
|
mailer.exception_notification(env, exception, options, default_opts)
|
191
174
|
end
|
192
175
|
end
|
193
176
|
end
|
194
177
|
|
195
|
-
def self.default_options
|
196
|
-
{
|
197
|
-
:sender_address => %("Exception Notifier" <exception.notifier@example.com>),
|
198
|
-
:exception_recipients => [],
|
199
|
-
:email_prefix => "[ERROR] ",
|
200
|
-
:email_format => :text,
|
201
|
-
:sections => %w(request session environment backtrace),
|
202
|
-
:background_sections => %w(backtrace data),
|
203
|
-
:verbose_subject => true,
|
204
|
-
:normalize_subject => false,
|
205
|
-
:include_controller_and_action_names_in_subject => true,
|
206
|
-
:delivery_method => nil,
|
207
|
-
:mailer_settings => nil,
|
208
|
-
:email_headers => {},
|
209
|
-
:mailer_parent => 'ActionMailer::Base',
|
210
|
-
:template_path => 'exception_notifier',
|
211
|
-
:deliver_with => :default
|
212
|
-
}
|
213
|
-
end
|
214
|
-
|
215
178
|
def self.normalize_digits(string)
|
216
179
|
string.gsub(/[0-9]+/, 'N')
|
217
180
|
end
|
181
|
+
|
182
|
+
private
|
183
|
+
|
184
|
+
def mailer
|
185
|
+
@mailer ||= Class.new(base_options[:mailer_parent].constantize).tap do |mailer|
|
186
|
+
mailer.extend(EmailNotifier::Mailer)
|
187
|
+
mailer.mailer_name = base_options[:template_path]
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def default_deliver_with(message)
|
192
|
+
# FIXME: use `if Gem::Version.new(ActionMailer::VERSION::STRING) < Gem::Version.new('4.1')`
|
193
|
+
message.respond_to?(:deliver_now) ? :deliver_now : :deliver
|
194
|
+
end
|
218
195
|
end
|
219
196
|
end
|