exception_notification 3.0.1 → 4.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
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 %>