Skip to content
Browse files

merged and resolved conflicts from akitaonrails/master

  • Loading branch information...
2 parents 7c67b0a + e5717cf commit 51318b68deca504b8b9a055b4f7e4bb5acc0a65f @pboling committed
View
1 .gitignore
@@ -1,3 +1,4 @@
*.gem
doc
.project
+.idea
View
4 MIT-LICENSE
@@ -1,5 +1,5 @@
# Copyright (c) 2008 Peter Boling
-# Portions Copyright (c) 2005 Jamis Buck
+# Copyright (c) 2005 Jamis Buck
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@@ -18,4 +18,4 @@
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
View
25 README.rdoc
@@ -277,9 +277,28 @@ in ExceptionNotifiable for an example of how to go about that.
== HTTP Error Codes Used by Exception Notifier by default
- For reference these are the error codes that Exception Notifier can inherently handle.
- Official w3.org HTTP 1.1 Error Codes:
- http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
+ For reference these are the error codes that Exception Notifier can inherently handle.
+ Official w3.org HTTP 1.1 Error Codes:
+ http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
+
+ 400 Bad Request
+ * The request could not be understood by the server due to malformed syntax.
+ * The client SHOULD NOT repeat the request without modifications.
+ 403 Forbidden
+ * The server understood the request, but is refusing to fulfill it
+ 404 Not Found
+ * The server has not found anything matching the Request-URI
+ 405 Method Not Allowed
+ * The method specified in the Request-Line is not allowed for the resource identified by the Request-URI
+ * This is not implemented entirely as the response is supposed to contain a list of accepted methods.
+ 410 Gone
+ * The requested resource is no longer available at the server and no forwarding address is known. This condition is expected to be considered permanent
+ 500 Internal Server Error
+ * The server encountered an unexpected condition which prevented it from fulfilling the request.
+ 501 Not Implemented
+ * The server does not support the functionality required to fulfill the request.
+ 503 Service Unavailable
+ * The server is currently unable to handle the request due to a temporary overloading or maintenance of the server.
Copyright (c) 2008 Peter H. Boling, released under the MIT license
Portions Copyright (c) 2005 Jamis Buck, released under the MIT license
View
37 Rakefile
@@ -1,15 +1,22 @@
-require 'rubygems'
-require 'rake/testtask'
-require 'rake/rdoctask'
-
-Rake::TestTask.new do |t|
- t.libs << "test"
- t.test_files = FileList['test/*_test.rb']
- t.verbose = true
-end
-
-Rake::RDocTask.new do |rd|
- rd.main = "README.rdoc"
- rd.rdoc_files.include("README.rdoc", "lib/*.rb")
- rd.rdoc_dir = 'doc'
-end
+require 'rake'
+require 'rake/testtask'
+require 'rake/rdoctask'
+
+desc 'Default: run unit tests.'
+task :default => :test
+
+desc 'Test exception_notification plugin.'
+Rake::TestTask.new(:test) do |t|
+ t.libs << 'lib'
+ t.pattern = 'test/**/*_test.rb'
+ t.verbose = true
+end
+
+desc 'Generate documentation for exception_notification plugin.'
+Rake::RDocTask.new(:rdoc) do |rdoc|
+ rdoc.rdoc_dir = 'rdoc'
+ rdoc.title = 'exception_notification'
+ rdoc.options << '--line-numbers' << '--inline-source'
+ rdoc.rdoc_files.include('README')
+ rdoc.rdoc_files.include('lib/**/*.rb')
+end
View
2 VERSION.yml
@@ -1,4 +1,4 @@
---
:major: 1
-:minor: 4
+:minor: 5
:patch: 0
View
2 exception_notification.gemspec
@@ -1,6 +1,6 @@
Gem::Specification.new do |s|
s.name = 'exception_notification'
- s.version = '1.4'
+ s.version = '1.5'
s.date = '2008-07-28'
s.summary = "Allows unhandled exceptions to be captured and sent via email"
View
107 lib/exception_notifiable.rb
@@ -1,16 +1,15 @@
require 'ipaddr'
module ExceptionNotifiable
- include ExceptionHandler
+ include ExceptionHandler
- # exceptions of these types will not generate notification emails
SILENT_EXCEPTIONS = [
ActiveRecord::RecordNotFound,
ActionController::UnknownController,
ActionController::UnknownAction,
ActionController::RoutingError,
ActionController::MethodNotAllowed
- ]
+ ] unless defined?(SILENT_EXCEPTIONS)
HTTP_ERROR_CODES = {
"400" => "Bad Request",
@@ -21,8 +20,8 @@ module ExceptionNotifiable
"500" => "Internal Server Error",
"501" => "Not Implemented",
"503" => "Service Unavailable"
- }
-
+ } unless defined?(HTTP_ERROR_CODES)
+
def self.codes_for_rails_error_classes
classes = {
NameError => "503",
@@ -46,8 +45,6 @@ def self.included(base)
# can be defined at controller level to the name of the layout,
# or set to true to render the controller's own default layout,
# or set to false to render errors with no layout
- base.cattr_accessor :silent_exceptions
- base.silent_exceptions = SILENT_EXCEPTIONS
base.cattr_accessor :http_error_codes
base.http_error_codes = HTTP_ERROR_CODES
base.cattr_accessor :error_layout
@@ -56,6 +53,8 @@ def self.included(base)
base.rails_error_classes = self.codes_for_rails_error_classes
base.cattr_accessor :exception_notifier_verbose
base.exception_notifier_verbose = false
+ base.cattr_accessor :silent_exceptions
+ base.silent_exceptions = SILENT_EXCEPTIONS
end
module ClassMethods
@@ -92,61 +91,83 @@ def local_request?
!self.class.local_addresses.detect { |addr| addr.include?(remote) }.nil?
end
- def render_error_template(status_cd, request, exception, file_path = nil)
+ def notify_and_render_error_template(status_cd, request, exception, file_path = nil)
status = self.class.http_error_codes[status_cd] ? status_cd + " " + self.class.http_error_codes[status_cd] : status_cd
-
file = file_path ? ExceptionNotifier.get_view_path(file_path) : ExceptionNotifier.get_view_path(status_cd)
- send_email = ExceptionNotifier.should_send_email?(status_cd, exception)
- if self.class.exception_notifier_verbose
- puts "[EXCEPTION] #{exception}"
- puts "[EXCEPTION CLASS] #{exception.class}"
- puts "[EXCEPTION STATUS_CD] #{status_cd}"
- puts "[ERROR LAYOUT] #{self.class.error_layout}"
- puts "[ERROR VIEW PATH] #{ExceptionNotifier.view_path}" if !ExceptionNotifier.nil?
- puts "[ERROR RENDER] #{file}"
- puts "[ERROR EMAIL] #{send_email ? "YES" : "NO"}"
- logger.error("render_error(#{status_cd}, #{self.class.http_error_codes[status_cd]}) invoked for request_uri=#{request.request_uri} and env=#{request.env.inspect}")
- end
-
- #send the email before rendering to avert possible errors on render preventing the email from being sent.
+ send_email = should_notify_on_exception?(status_cd, exception)
+
+ # Debugging output
+ verbose_output(exception, status_cd, file, send_email, request) if self.class.exception_notifier_verbose
+ # Send the email before rendering to avert possible errors on render preventing the email from being sent.
send_exception_email(exception) if send_email
-
+ # Render the error page to the end user
+ render_error_template(file, status)
+ end
+
+ def render_error_template(file, status)
respond_to do |type|
type.html { render :file => file,
- :layout => self.class.error_layout,
+ :layout => self.class.error_layout,
:status => status }
- type.all { render :nothing => true,
+ type.all { render :nothing => true,
:status => status}
end
end
- def send_exception_email(exception)
- unless self.class.silent_exceptions.any? {|klass| klass === exception}
- deliverer = self.class.exception_data
- data = case deliverer
- when nil then {}
- when Symbol then send(deliverer)
- when Proc then deliverer.call(self)
- end
- the_blamed = lay_blame(exception)
+ def verbose_output(exception, status_cd, file, send_email, request = nil)
+ puts "[EXCEPTION] #{exception}"
+ puts "[EXCEPTION CLASS] #{exception.class}"
+ puts "[EXCEPTION STATUS_CD] #{status_cd}"
+ puts "[ERROR LAYOUT] #{self.class.error_layout}"
+ puts "[ERROR VIEW PATH] #{ExceptionNotifier.view_path}" if !ExceptionNotifier.nil?
+ puts "[ERROR RENDER] #{file}"
+ puts "[ERROR EMAIL] #{send_email ? "YES" : "NO"}"
+ req = request ? " for request_uri=#{request.request_uri} and env=#{request.env.inspect}" : ""
+ logger.error("render_error(#{status_cd}, #{self.class.http_error_codes[status_cd]}) invoked#{req}")
+ end
- ExceptionNotifier.deliver_exception_notification(exception, self,
- request, data, the_blamed)
+ def send_exception_email(exception)
+ deliverer = self.class.exception_data
+ data = case deliverer
+ when nil then {}
+ when Symbol then send(deliverer)
+ when Proc then deliverer.call(self)
end
+ the_blamed = lay_blame(exception)
+
+ ExceptionNotifier.deliver_exception_notification(exception, self,
+ request, data, the_blamed)
+ end
+
+ def should_notify_on_exception?(status_cd, exception)
+ #don't mail exceptions raised locally
+ return false if (consider_all_requests_local || local_request?)
+ #don't mail exceptions raised that match ExceptionNotifiable.silent_exceptions
+ return false if self.class.silent_exceptions.any? {|klass| klass === exception}
+ return false unless ExceptionNotifier.should_send_email?(status_cd, exception)
+ return true
end
def rescue_action_in_public(exception)
- status_code = self.class.rails_error_classes[exception.class].nil? ? '500' : self.class.rails_error_classes[exception.class].blank? ? '200' : self.class.rails_error_classes[exception.class]
# If the error class is NOT listed in the rails_errror_class hash then we get a generic 500 error:
# OTW if the error class is listed, but has a blank code or the code is == '200' then we get a custom error layout rendered
# OTW the error class is listed!
+ status_code = status_code_for_exception(exception)
if status_code == '200'
- render_error_template(status_code, request, exception, exception.to_s.delete(':').gsub( /([A-Za-z])([A-Z])/, '\1' << '_' << '\2' ).downcase)
+ notify_and_render_error_template(status_code, request, exception, exception_to_filename(exception))
else
- render_error_template(status_code, request, exception)
+ notify_and_render_error_template(status_code, request, exception)
end
end
-
+
+ def status_code_for_exception(exception)
+ self.class.rails_error_classes[exception.class].nil? ? '500' : self.class.rails_error_classes[exception.class].blank? ? '200' : self.class.rails_error_classes[exception.class]
+ end
+
+ def exception_to_filename(exception)
+ exception.to_s.delete(':').gsub( /([A-Za-z])([A-Z])/, '\1' << '_' << '\2' ).downcase
+ end
+
def lay_blame(exception)
error = {}
unless(ExceptionNotifier.git_repo_path.nil?)
@@ -172,16 +193,16 @@ def lay_blame(exception)
end
error
end
-
+
def blame_output(line_number, path)
app_directory = Dir.pwd
Dir.chdir ExceptionNotifier.git_repo_path
blame = `git blame -p -L #{line_number},#{line_number} #{path}`
Dir.chdir app_directory
-
+
blame
end
-
+
def exception_in_project?(path) # should be a path like /path/to/broken/thingy.rb
dir = File.split(path).first rescue ''
if(File.directory?(dir) and !(path =~ /vendor\/plugins/) and path.include?(RAILS_ROOT))
View
3 lib/exception_notifier.rb
@@ -56,6 +56,9 @@ def exception_notification(exception, controller = nil, request = nil, data={},
#content_type "text/plain"
recipients exception_recipients
from sender_address
+
+ request.session.inspect # Ensure session data is loaded (Rails 2.3 lazy-loading)
+
subject "#{email_prefix}#{data[:location]} (#{exception.class}) #{exception.message.inspect}"
body data
end
View
10 lib/exception_notifier_helper.rb
@@ -1,9 +1,9 @@
require 'pp'
module ExceptionNotifierHelper
- VIEW_PATH = "views/exception_notifier"
- APP_PATH = "#{RAILS_ROOT}/app/#{VIEW_PATH}"
- PARAM_FILTER_REPLACEMENT = "[FILTERED]"
+ VIEW_PATH = "views/exception_notifier" unless defined?(VIEW_PATH)
+ APP_PATH = "#{RAILS_ROOT}/app/#{VIEW_PATH}" unless defined?(APP_PATH)
+ PARAM_FILTER_REPLACEMENT = "[FILTERED]" unless defined?(PARAM_FILTER_REPLACEMENT)
COMPAT_MODE = RAILS_GEM_VERSION ? RAILS_GEM_VERSION < '2' : false
def render_section(section)
@@ -44,11 +44,11 @@ def object_to_yaml(object)
def exclude_raw_post_parameters?
@controller && @controller.respond_to?(:filter_parameters)
end
-
+
def filter_sensitive_post_data_parameters(parameters)
exclude_raw_post_parameters? ? COMPAT_MODE ? @controller.filter_parameters(parameters) : @controller.__send__(:filter_parameters, parameters) : parameters
end
-
+
def filter_sensitive_post_data_from_env(env_key, env_value)
return env_value unless exclude_raw_post_parameters?
return PARAM_FILTER_REPLACEMENT if (env_key =~ /RAW_POST_DATA/i)
View
2 rails/init.rb
@@ -1,6 +1,8 @@
require "action_mailer"
+$:.unshift "#{File.dirname(__FILE__)}/lib"
require "exception_notifier"
require "exception_notifiable"
require "exception_notifier_helper"
+require "notifiable"
Object.class_eval do include Notifiable end
View
25 test/exception_notifier_helper_test.rb
@@ -1,4 +1,4 @@
-require File.dirname(__FILE__) + '/test_helper'
+require File.expand_path(File.dirname(__FILE__) + '/test_helper')
require 'exception_notifier_helper'
class ExceptionNotifierHelperTest < Test::Unit::TestCase
@@ -38,31 +38,34 @@ def test_should_return_params_if_controller_can_not_filter_parameters
assert_equal :params, @helper.filter_sensitive_post_data_parameters(:params)
end
- # Controller with filtering
+ # Controller with filter paramaters method, no params to filter
class ControllerWithFilterParameters
- def filter_parameters(params); {:some_key => :filtered} end
+ def filter_parameters(params)
+ params.keys.each do |k|
+ params[k] = :filtered
+ end
+ params
+ end
end
def test_should_filter_env_values_for_raw_post_data_keys_if_controller_can_filter_parameters
stub_controller(ControllerWithFilterParameters.new)
- assert_equal "[FILTERED]", @helper.filter_sensitive_post_data_from_env("RAW_POST_DATA", "secret")
- end
- def test_should_filter_env_values_for_all_keys_if_controller_can_filter_parameters
- stub_controller(ControllerWithFilterParameters.new)
- assert_equal :filtered, @helper.filter_sensitive_post_data_from_env("SOME_OTHER_KEY", "secret")
+
+ assert_equal("[FILTERED]", @helper.filter_sensitive_post_data_from_env("RAW_POST_DATA", "secret"))
+ assert_equal(:filtered, @helper.filter_sensitive_post_data_from_env("SOME_OTHER_KEY", "secret"))
end
def test_should_exclude_raw_post_parameters_if_controller_can_filter_parameters
- stub_controller(ControllerWithFilterParameters.new)
+ stub_controller(ControllerWithFilterParametersThatDoesntFilter.new)
assert @helper.exclude_raw_post_parameters?
end
def test_should_delegate_param_filtering_to_controller_if_controller_can_filter_parameters
stub_controller(ControllerWithFilterParameters.new)
- assert_equal :filtered, @helper.filter_sensitive_post_data_parameters(:params).values[0]
+ assert_equal({:param => :filtered}, @helper.filter_sensitive_post_data_parameters({:param => :value}))
end
private
def stub_controller(controller)
@helper.instance_variable_set(:@controller, controller)
end
-end
+end
View
82 test/exception_notify_functional_test.rb
@@ -0,0 +1,82 @@
+require File.expand_path(File.dirname(__FILE__) + '/test_helper')
+require 'test/unit'
+
+RAILS_DEFAULT_LOGGER = Logger.new(nil)
+
+require File.join(File.dirname(__FILE__), 'mocks/controllers')
+
+ActionController::Routing::Routes.clear!
+ActionController::Routing::Routes.draw {|m| m.connect ':controller/:action/:id' }
+
+class ExceptionNotifyFunctionalTest < ActionController::TestCase
+
+ def setup
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+ ActionController::Base.consider_all_requests_local = false
+ @@delivered_mail = []
+ ActionMailer::Base.class_eval do
+ def deliver!(mail = @mail)
+ @@delivered_mail << mail
+ end
+ end
+ end
+
+ def test_old_style_where_requests_are_local
+ ActionController::Base.consider_all_requests_local = true
+ @controller = OldStyle.new
+ get "runtime_error"
+
+ assert_nothing_mailed
+ end
+
+ def test_new_style_where_requests_are_local
+ ActionController::Base.consider_all_requests_local = true
+ @controller = NewStyle.new
+ get "runtime_error"
+
+ # puts @response.body
+ assert_nothing_mailed
+ end
+
+ def test_old_style_runtime_error_sends_mail
+ @controller = OldStyle.new
+ get "runtime_error"
+ assert_error_mail_contains("This is a runtime error that we should be emailed about")
+ end
+
+ def test_old_style_record_not_found_does_not_send_mail
+ @controller = OldStyle.new
+ get "record_not_found"
+ assert_nothing_mailed
+ end
+
+ def test_new_style_runtime_error_sends_mail
+ @controller = NewStyle.new
+ get "runtime_error"
+ assert_error_mail_contains("This is a runtime error that we should be emailed about")
+ end
+
+ def test_new_style_record_not_found_does_not_send_mail
+ @controller = NewStyle.new
+ get "record_not_found"
+ assert_nothing_mailed
+ end
+
+ private
+
+ def assert_error_mail_contains(text)
+ assert(mailed_error.index(text),
+ "Expected mailed error body to contain '#{text}', but not found. \n actual contents: \n#{mailed_error}")
+ end
+
+ def assert_nothing_mailed
+ assert @@delivered_mail.empty?, "Expected to have NOT mailed out a notification about an error occuring, but mailed: \n#{@@delivered_mail}"
+ end
+
+ def mailed_error
+ assert @@delivered_mail.last, "Expected to have mailed out a notification about an error occuring, but none mailed"
+ @@delivered_mail.last.encoded
+ end
+
+end
View
1 test/mocks/404.html
@@ -0,0 +1 @@
+simulate 404 error file in public
View
1 test/mocks/500.html
@@ -0,0 +1 @@
+simulate 500 error file in public
View
41 test/mocks/controllers.rb
@@ -0,0 +1,41 @@
+module Rails
+ def self.public_path
+ File.dirname(__FILE__)
+ end
+end
+
+class Application < ActionController::Base
+
+ def runtime_error
+ raise "This is a runtime error that we should be emailed about"
+ end
+
+ def record_not_found
+ raise ActiveRecord::RecordNotFound
+ end
+
+ def local_request?
+ false
+ end
+
+end
+
+class OldStyle < Application
+ include ExceptionNotifiable
+
+end
+
+class SpecialErrorThing < RuntimeError
+end
+
+class NewStyle < Application
+ include ExceptionNotifiable
+
+ rescue_from ActiveRecord::RecordNotFound do |exception|
+ render :text => "404", :status => 404
+ end
+
+ rescue_from RuntimeError do |exception|
+ render :text => "500", :status => 500
+ end
+end
View
14 test/test_helper.rb
@@ -2,6 +2,18 @@
require 'rubygems'
require 'active_support'
-$:.unshift File.join(File.dirname(__FILE__), '../lib')
+require 'active_record'
+
+#just requiring active record wasn't loading classes soon enough for SILENT_EXCEPTIONS
+ActiveRecord::Base
+
+require 'action_controller'
+require 'action_controller/test_case'
+require 'action_controller/test_process'
+
+#just requiring action controller wasn't loading classes soon enough for SILENT_EXCEPTIONS
+ActionController::Base
RAILS_ROOT = '.' unless defined?(RAILS_ROOT)
+
+require File.join(File.dirname(__FILE__), "..", "init")
View
4 views/exception_notifier/_session.rhtml
@@ -1,2 +1,2 @@
-* session id: <%= @request.session.instance_variable_get(:@session_id).inspect %>
-* data: <%= PP.pp(@request.session.instance_variable_get(:@data),"").gsub(/\n/, "\n ").strip %>
+* session id: <%= @request.session_options[:id] %>
+* data: <%= PP.pp(@request.session.to_hash, "").gsub(/\n/, "\n ").strip %>

0 comments on commit 51318b6

Please sign in to comment.
Something went wrong with that request. Please try again.