exception_notification 3.0.1 → 4.0.0.rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. data/.gitignore +2 -0
  2. data/.travis.yml +9 -0
  3. data/Appraisals +11 -0
  4. data/CHANGELOG.rdoc +21 -0
  5. data/Gemfile +1 -1
  6. data/Gemfile.lock +49 -7
  7. data/README.md +417 -184
  8. data/Rakefile +4 -2
  9. data/examples/sinatra/Gemfile +8 -0
  10. data/examples/sinatra/Gemfile.lock +95 -0
  11. data/examples/sinatra/Procfile +2 -0
  12. data/examples/sinatra/README.md +11 -0
  13. data/examples/sinatra/config.ru +3 -0
  14. data/examples/sinatra/sinatra_app.rb +28 -0
  15. data/exception_notification.gemspec +10 -4
  16. data/gemfiles/rails3_1.gemfile +7 -0
  17. data/gemfiles/rails3_2.gemfile +7 -0
  18. data/gemfiles/rails4_0.gemfile +7 -0
  19. data/lib/exception_notification.rb +10 -0
  20. data/lib/exception_notification/rack.rb +45 -0
  21. data/lib/exception_notification/rails.rb +8 -0
  22. data/lib/exception_notification/resque.rb +24 -0
  23. data/lib/exception_notification/sidekiq.rb +22 -0
  24. data/lib/exception_notifier.rb +89 -61
  25. data/lib/exception_notifier/campfire_notifier.rb +2 -7
  26. data/lib/exception_notifier/email_notifier.rb +181 -0
  27. data/lib/exception_notifier/notifier.rb +9 -178
  28. data/lib/exception_notifier/views/exception_notifier/_backtrace.html.erb +3 -1
  29. data/lib/exception_notifier/views/exception_notifier/_data.html.erb +6 -1
  30. data/lib/exception_notifier/views/exception_notifier/_environment.html.erb +16 -6
  31. data/lib/exception_notifier/views/exception_notifier/_environment.text.erb +1 -1
  32. data/lib/exception_notifier/views/exception_notifier/_request.html.erb +24 -5
  33. data/lib/exception_notifier/views/exception_notifier/_request.text.erb +2 -0
  34. data/lib/exception_notifier/views/exception_notifier/_session.html.erb +10 -2
  35. data/lib/exception_notifier/views/exception_notifier/_session.text.erb +1 -1
  36. data/lib/exception_notifier/views/exception_notifier/_title.html.erb +3 -3
  37. data/lib/exception_notifier/views/exception_notifier/background_exception_notification.html.erb +38 -11
  38. data/lib/exception_notifier/views/exception_notifier/background_exception_notification.text.erb +0 -1
  39. data/lib/exception_notifier/views/exception_notifier/exception_notification.html.erb +39 -21
  40. data/lib/exception_notifier/views/exception_notifier/exception_notification.text.erb +0 -1
  41. data/lib/exception_notifier/webhook_notifier.rb +21 -0
  42. data/lib/generators/exception_notification/install_generator.rb +15 -0
  43. data/lib/generators/exception_notification/templates/exception_notification.rb +47 -0
  44. data/test/dummy/Gemfile +2 -1
  45. data/test/dummy/Gemfile.lock +79 -78
  46. data/test/dummy/config/environment.rb +9 -7
  47. data/test/dummy/test/functional/posts_controller_test.rb +22 -37
  48. data/test/{campfire_test.rb → exception_notifier/campfire_notifier_test.rb} +4 -4
  49. data/test/exception_notifier/email_notifier_test.rb +144 -0
  50. data/test/exception_notifier/webhook_notifier_test.rb +41 -0
  51. data/test/exception_notifier_test.rb +101 -0
  52. data/test/test_helper.rb +4 -1
  53. metadata +136 -18
  54. data/test/background_exception_notification_test.rb +0 -82
  55. data/test/exception_notification_test.rb +0 -73
@@ -1,6 +1,5 @@
1
- class ExceptionNotifier
1
+ module ExceptionNotifier
2
2
  class CampfireNotifier
3
- cattr_accessor :tinder_available, true
4
3
 
5
4
  attr_accessor :subdomain
6
5
  attr_accessor :token
@@ -8,8 +7,6 @@ class ExceptionNotifier
8
7
 
9
8
  def initialize(options)
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
@@ -19,7 +16,7 @@ class ExceptionNotifier
19
16
  end
20
17
  end
21
18
 
22
- def exception_notification(exception)
19
+ def call(exception, options={})
23
20
  @room.paste "A new exception occurred: '#{exception.message}' on '#{exception.backtrace.first}'" if active?
24
21
  end
25
22
 
@@ -30,5 +27,3 @@ class ExceptionNotifier
30
27
  end
31
28
  end
32
29
  end
33
-
34
- ExceptionNotifier::CampfireNotifier.tinder_available = Gem.loaded_specs.keys.include? 'tinder'
@@ -0,0 +1,181 @@
1
+ require 'action_mailer'
2
+ require 'action_dispatch'
3
+ require 'pp'
4
+
5
+ module ExceptionNotifier
6
+ class EmailNotifier < Struct.new(:sender_address, :exception_recipients,
7
+ :email_prefix, :email_format, :sections, :background_sections,
8
+ :verbose_subject, :normalize_subject, :delivery_method, :mailer_settings,
9
+ :email_headers, :mailer_parent, :template_path)
10
+
11
+ module Mailer
12
+ class MissingController
13
+ def method_missing(*args, &block)
14
+ end
15
+ end
16
+
17
+ def self.extended(base)
18
+ base.class_eval do
19
+ # Append application view path to the ExceptionNotifier lookup context.
20
+ self.append_view_path "#{File.dirname(__FILE__)}/views"
21
+
22
+ def exception_notification(env, exception, options={}, default_options={})
23
+ load_custom_views
24
+
25
+ @env = env
26
+ @exception = exception
27
+ @options = options.reverse_merge(env['exception_notifier.options'] || {}).reverse_merge(default_options)
28
+ @kontroller = env['action_controller.instance'] || MissingController.new
29
+ @request = ActionDispatch::Request.new(env)
30
+ @backtrace = exception.backtrace ? clean_backtrace(exception) : []
31
+ @sections = @options[:sections]
32
+ @data = (env['exception_notifier.exception_data'] || {}).merge(options[:data] || {})
33
+ @sections = @sections + %w(data) unless @data.empty?
34
+
35
+ compose_email
36
+ end
37
+
38
+ def background_exception_notification(exception, options={}, default_options={})
39
+ load_custom_views
40
+
41
+ @exception = exception
42
+ @options = options.reverse_merge(default_options)
43
+ @backtrace = exception.backtrace || []
44
+ @sections = @options[:background_sections]
45
+ @data = options[:data] || {}
46
+
47
+ compose_email
48
+ end
49
+
50
+ private
51
+
52
+ def compose_subject
53
+ subject = "#{@options[:email_prefix]}"
54
+ subject << "#{@kontroller.controller_name}##{@kontroller.action_name}" if @kontroller
55
+ subject << " (#{@exception.class})"
56
+ subject << " #{@exception.message.inspect}" if @options[:verbose_subject]
57
+ subject = EmailNotifier.normalize_digits(subject) if @options[:normalize_subject]
58
+ subject.length > 120 ? subject[0...120] + "..." : subject
59
+ end
60
+
61
+ def set_data_variables
62
+ @data.each do |name, value|
63
+ instance_variable_set("@#{name}", value)
64
+ end
65
+ end
66
+
67
+ def clean_backtrace(exception)
68
+ if defined?(Rails) && Rails.respond_to?(:backtrace_cleaner)
69
+ Rails.backtrace_cleaner.send(:filter, exception.backtrace)
70
+ else
71
+ exception.backtrace
72
+ end
73
+ end
74
+
75
+ helper_method :inspect_object
76
+
77
+ def inspect_object(object)
78
+ case object
79
+ when Hash, Array
80
+ object.inspect
81
+ else
82
+ object.to_s
83
+ end
84
+ end
85
+
86
+ def html_mail?
87
+ @options[:email_format] == :html
88
+ end
89
+
90
+ def compose_email
91
+ set_data_variables
92
+ subject = compose_subject
93
+ name = @env.nil? ? 'background_exception_notification' : 'exception_notification'
94
+
95
+ headers = {
96
+ :delivery_method => @options[:delivery_method],
97
+ :to => @options[:exception_recipients],
98
+ :from => @options[:sender_address],
99
+ :subject => subject,
100
+ :template_name => name
101
+ }.merge(@options[:email_headers])
102
+
103
+ mail = mail(headers) do |format|
104
+ format.text
105
+ format.html if html_mail?
106
+ end
107
+
108
+ mail.delivery_method.settings.merge!(@options[:mailer_settings]) if @options[:mailer_settings]
109
+
110
+ mail
111
+ end
112
+
113
+ def load_custom_views
114
+ self.prepend_view_path Rails.root.nil? ? "app/views" : "#{Rails.root}/app/views" if defined?(Rails)
115
+ end
116
+ end
117
+ end
118
+ end
119
+
120
+ def initialize(options)
121
+ delivery_method = (options[:delivery_method] || :smtp)
122
+ mailer_settings_key = "#{delivery_method}_settings".to_sym
123
+ options[:mailer_settings] = options.delete(mailer_settings_key)
124
+
125
+ super(*options.reverse_merge(EmailNotifier.default_options).values_at(
126
+ :sender_address, :exception_recipients,
127
+ :email_prefix, :email_format, :sections, :background_sections,
128
+ :verbose_subject, :normalize_subject, :delivery_method, :mailer_settings,
129
+ :email_headers, :mailer_parent, :template_path))
130
+ end
131
+
132
+ def options
133
+ @options ||= {}.tap do |opts|
134
+ each_pair { |k,v| opts[k] = v }
135
+ end
136
+ end
137
+
138
+ def mailer
139
+ @mailer ||= Class.new(mailer_parent.constantize).tap do |mailer|
140
+ mailer.extend(EmailNotifier::Mailer)
141
+ mailer.mailer_name = template_path
142
+ end
143
+ end
144
+
145
+ def call(exception, options={})
146
+ create_email(exception, options).deliver
147
+ end
148
+
149
+ def create_email(exception, options={})
150
+ env = options[:env]
151
+ default_options = self.options
152
+ if env.nil?
153
+ mailer.background_exception_notification(exception, options, default_options)
154
+ else
155
+ mailer.exception_notification(env, exception, options, default_options)
156
+ end
157
+ end
158
+
159
+ def self.default_options
160
+ {
161
+ :sender_address => %("Exception Notifier" <exception.notifier@example.com>),
162
+ :exception_recipients => [],
163
+ :email_prefix => "[ERROR] ",
164
+ :email_format => :text,
165
+ :sections => %w(request session environment backtrace),
166
+ :background_sections => %w(backtrace data),
167
+ :verbose_subject => true,
168
+ :normalize_subject => false,
169
+ :delivery_method => nil,
170
+ :mailer_settings => nil,
171
+ :email_headers => {},
172
+ :mailer_parent => 'ActionMailer::Base',
173
+ :template_path => 'exception_notifier'
174
+ }
175
+ end
176
+
177
+ def self.normalize_digits(string)
178
+ string.gsub(/[0-9]+/, 'N')
179
+ end
180
+ end
181
+ end
@@ -1,185 +1,16 @@
1
- require 'action_mailer'
2
- require 'pp'
1
+ require 'active_support/deprecation'
3
2
 
4
- class ExceptionNotifier
5
- class Notifier < ActionMailer::Base
6
- self.mailer_name = 'exception_notifier'
3
+ module ExceptionNotifier
4
+ class Notifier
7
5
 
8
- #Append application view path to the ExceptionNotifier lookup context.
9
- self.append_view_path "#{File.dirname(__FILE__)}/views"
10
-
11
- class << self
12
- attr_writer :default_sender_address
13
- attr_writer :default_exception_recipients
14
- attr_writer :default_email_prefix
15
- attr_writer :default_email_format
16
- attr_writer :default_sections
17
- attr_writer :default_background_sections
18
- attr_writer :default_verbose_subject
19
- attr_writer :default_normalize_subject
20
- attr_writer :default_smtp_settings
21
- attr_writer :default_email_headers
22
-
23
- def default_sender_address
24
- @default_sender_address || %("Exception Notifier" <exception.notifier@default.com>)
25
- end
26
-
27
- def default_exception_recipients
28
- @default_exception_recipients || []
29
- end
30
-
31
- def default_email_prefix
32
- @default_email_prefix || "[ERROR] "
33
- end
34
-
35
- def default_email_format
36
- @default_email_format || :text
37
- end
38
-
39
- def default_sections
40
- @default_sections || %w(request session environment backtrace)
41
- end
42
-
43
- def default_background_sections
44
- @default_background_sections || %w(backtrace data)
45
- end
46
-
47
- def default_verbose_subject
48
- @default_verbose_subject.nil? || @default_verbose_subject
49
- end
50
-
51
- def default_normalize_subject
52
- @default_normalize_prefix || false
53
- end
54
-
55
- def default_smtp_settings
56
- @default_smtp_settings || nil
57
- end
58
-
59
- def default_email_headers
60
- @default_email_headers || {}
61
- end
62
-
63
- def default_options
64
- { :sender_address => default_sender_address,
65
- :exception_recipients => default_exception_recipients,
66
- :email_prefix => default_email_prefix,
67
- :email_format => default_email_format,
68
- :sections => default_sections,
69
- :background_sections => default_background_sections,
70
- :verbose_subject => default_verbose_subject,
71
- :normalize_subject => default_normalize_subject,
72
- :template_path => mailer_name,
73
- :smtp_settings => default_smtp_settings,
74
- :email_headers => default_email_headers }
75
- end
76
-
77
- def normalize_digits(string)
78
- string.gsub(/[0-9]+/, 'N')
79
- end
80
- end
81
-
82
- class MissingController
83
- def method_missing(*args, &block)
84
- end
85
- end
86
-
87
- def exception_notification(env, exception, options={})
88
- load_custom_views
89
-
90
- @env = env
91
- @exception = exception
92
- @options = options.reverse_merge(env['exception_notifier.options'] || {}).reverse_merge(self.class.default_options)
93
- @kontroller = env['action_controller.instance'] || MissingController.new
94
- @request = ActionDispatch::Request.new(env)
95
- @backtrace = exception.backtrace ? clean_backtrace(exception) : []
96
- @sections = @options[:sections]
97
- @data = (env['exception_notifier.exception_data'] || {}).merge(options[:data] || {})
98
- @sections = @sections + %w(data) unless @data.empty?
99
-
100
- compose_email
101
- end
102
-
103
- def background_exception_notification(exception, options={})
104
- load_custom_views
105
-
106
- if @notifier = Rails.application.config.middleware.detect{ |x| x.klass == ExceptionNotifier }
107
- @options = options.reverse_merge(@notifier.args.first || {}).reverse_merge(self.class.default_options)
108
- @exception = exception
109
- @backtrace = exception.backtrace || []
110
- @sections = @options[:background_sections]
111
- @data = options[:data] || {}
112
-
113
- compose_email
114
- end
115
- end
116
-
117
- private
118
-
119
- def compose_subject
120
- subject = "#{@options[:email_prefix]}"
121
- subject << "#{@kontroller.controller_name}##{@kontroller.action_name}" if @kontroller
122
- subject << " (#{@exception.class})"
123
- subject << " #{@exception.message.inspect}" if @options[:verbose_subject]
124
- subject = normalize_digits(subject) if @options[:normalize_subject]
125
- subject.length > 120 ? subject[0...120] + "..." : subject
126
- end
127
-
128
- def set_data_variables
129
- @data.each do |name, value|
130
- instance_variable_set("@#{name}", value)
131
- end
132
- end
133
-
134
- def clean_backtrace(exception)
135
- if Rails.respond_to?(:backtrace_cleaner)
136
- Rails.backtrace_cleaner.send(:filter, exception.backtrace)
137
- else
138
- exception.backtrace
139
- end
140
- end
141
-
142
- helper_method :inspect_object
143
-
144
- def inspect_object(object)
145
- case object
146
- when Hash, Array
147
- object.inspect
148
- when ActionController::Base
149
- "#{object.controller_name}##{object.action_name}"
150
- else
151
- object.to_s
152
- end
153
- end
154
-
155
- def html_mail?
156
- @options[:email_format] == :html
157
- end
158
-
159
- def compose_email
160
- set_data_variables
161
- subject = compose_subject
162
- name = @env.nil? ? 'background_exception_notification' : 'exception_notification'
163
-
164
- headers = {
165
- :to => @options[:exception_recipients],
166
- :from => @options[:sender_address],
167
- :subject => subject,
168
- :template_name => name
169
- }.merge(@options[:email_headers])
170
-
171
- mail = mail(headers) do |format|
172
- format.text
173
- format.html if html_mail?
174
- end
175
-
176
- mail.delivery_method.settings.merge!(@options[:smtp_settings]) if @options[:smtp_settings]
177
-
178
- mail
6
+ def self.exception_notification(env, exception, options={})
7
+ ActiveSupport::Deprecation.warn "Please use ExceptionNotifier.notify_exception(exception, options.merge(:env => env))."
8
+ ExceptionNotifier.registered_exception_notifier(:email).create_email(exception, options.merge(:env => env))
179
9
  end
180
10
 
181
- def load_custom_views
182
- self.prepend_view_path Rails.root.nil? ? "app/views" : "#{Rails.root}/app/views" if defined?(Rails)
11
+ def self.background_exception_notification(exception, options={})
12
+ ActiveSupport::Deprecation.warn "Please use ExceptionNotifier.notify_exception(exception, options)."
13
+ ExceptionNotifier.registered_exception_notifier(:email).create_email(exception, options)
183
14
  end
184
15
  end
185
16
  end
@@ -1 +1,3 @@
1
- <%= raw @backtrace.join("\n") %>
1
+ <pre style="font-size: 12px; padding: 10px; border: 1px solid #e1e1e8; background-color:#f5f5f5">
2
+ <%= raw @backtrace.join("\n") %>
3
+ </pre>
@@ -1 +1,6 @@
1
- * data: <%= raw PP.pp(@data, "") %>
1
+ <ul style="list-style: none">
2
+ <li>
3
+ <strong>data:</strong>
4
+ <span><%= raw PP.pp(@data, "") %></span>
5
+ </li>
6
+ </ul>
@@ -1,8 +1,18 @@
1
1
  <% filtered_env = @request.filtered_env -%>
2
- <% max = filtered_env.keys.map(&:to_s).max { |a, b| a.length <=> b.length } -%>
3
- <% filtered_env.keys.map(&:to_s).sort.each do |key| -%>
4
- * <%= raw("%-*s: %s" % [max.length, key, inspect_object(filtered_env[key])]) %>
5
- <% end -%>
6
2
 
7
- * Process: <%= raw $$ %>
8
- * Server : <%= raw `hostname`.chomp %>
3
+ <ul style="list-style: none">
4
+ <% filtered_env.keys.map(&:to_s).sort.each do |key| -%>
5
+ <li>
6
+ <strong><%= key %>:</strong>
7
+ <span><%= inspect_object(filtered_env[key]) %></span>
8
+ </li>
9
+ <% end -%>
10
+ <li>
11
+ <strong>Process:</strong>
12
+ <span><%= raw $$ %></span>
13
+ </li>
14
+ <li>
15
+ <strong>Server:</strong>
16
+ <span><%= raw Socket.gethostname %></span>
17
+ </li>
18
+ </ul>
@@ -5,4 +5,4 @@
5
5
  <% end -%>
6
6
 
7
7
  * Process: <%= raw $$ %>
8
- * Server : <%= raw `hostname`.chomp %>
8
+ * Server : <%= raw Socket.gethostname %>