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.
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
@@ -0,0 +1,3 @@
1
+ module ExceptionNotification
2
+ VERSION = '4.4.0'.freeze
3
+ end
@@ -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{ActiveRecord::RecordNotFound Mongoid::Errors::DocumentNotFound AbstractController::ActionNotFound ActionController::RoutingError ActionController::UnknownFormat ActionController::UrlGenerationError}
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
- if active?
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
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
- attr_accessor(:sender_address, :exception_recipients,
10
- :pre_callback, :post_callback,
11
- :email_prefix, :email_format, :sections, :background_sections,
12
- :verbose_subject, :normalize_subject, :include_controller_and_action_names_in_subject,
13
- :delivery_method, :mailer_settings, :email_headers, :mailer_parent, :template_path, :deliver_with)
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
- self.send(:include, ExceptionNotifier::BacktraceCleaner)
33
+ send(:include, ExceptionNotifier::BacktraceCleaner)
24
34
 
25
35
  # Append application view path to the ExceptionNotifier lookup context.
26
- self.append_view_path "#{File.dirname(__FILE__)}/views"
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
- @options = options.reverse_merge(env['exception_notifier.options'] || {}).reverse_merge(default_options)
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 = @sections + %w(data) unless @data.empty?
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 = options.reverse_merge(default_options).symbolize_keys
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 = "#{@options[:email_prefix]}"
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] + "..." : subject
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
- when Hash, Array
86
- truncate(object.inspect, 300)
87
- else
88
- object.to_s
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("utf-8", invalid: :replace, undef: :replace, replace: "_")
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
- :delivery_method => @options[:delivery_method],
110
- :to => exception_recipients,
111
- :from => @options[:sender_address],
112
- :subject => subject,
113
- :template_name => name
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
- options.reverse_merge(EmailNotifier.default_options).select{|k,v|[
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
- # FIXME: use `if Gem::Version.new(ActionMailer::VERSION::STRING) < Gem::Version.new('4.1')`
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
- default_options = self.options
184
- if env.nil?
185
- send_notice(exception, options, nil, default_options) do |_, default_opts|
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
- end
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