exceptron 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.rdoc +3 -0
  3. data/lib/exceptron.rb +38 -0
  4. data/lib/exceptron/dispatcher.rb +58 -0
  5. data/lib/exceptron/exception.rb +97 -0
  6. data/lib/exceptron/exceptions_controller.rb +19 -0
  7. data/lib/exceptron/helpers.rb +24 -0
  8. data/lib/exceptron/local_exceptions_controller.rb +17 -0
  9. data/lib/exceptron/local_helpers.rb +27 -0
  10. data/lib/exceptron/middleware.rb +27 -0
  11. data/lib/exceptron/railtie.rb +16 -0
  12. data/lib/exceptron/version.rb +3 -0
  13. data/lib/exceptron/views/exceptron/exceptions/internal_server_error.html.erb +26 -0
  14. data/lib/exceptron/views/exceptron/local_exceptions/_request_and_response.erb +27 -0
  15. data/lib/exceptron/views/exceptron/local_exceptions/_trace.erb +26 -0
  16. data/lib/exceptron/views/exceptron/local_exceptions/diagnostics.erb +10 -0
  17. data/lib/exceptron/views/exceptron/local_exceptions/missing_template.erb +2 -0
  18. data/lib/exceptron/views/exceptron/local_exceptions/routing_error.erb +10 -0
  19. data/lib/exceptron/views/exceptron/local_exceptions/template_error.erb +17 -0
  20. data/lib/exceptron/views/exceptron/local_exceptions/unknown_action.erb +2 -0
  21. data/lib/exceptron/views/layouts/exceptron/local_exceptions.erb +31 -0
  22. data/test/dummy/Rakefile +7 -0
  23. data/test/dummy/app/controllers/application_controller.rb +4 -0
  24. data/test/dummy/app/controllers/exceptions_controller.rb +24 -0
  25. data/test/dummy/app/controllers/home_controller.rb +4 -0
  26. data/test/dummy/app/helpers/application_helper.rb +2 -0
  27. data/test/dummy/app/views/app_exceptions/internal_server_error.html.erb +2 -0
  28. data/test/dummy/app/views/app_exceptions/not_found.html.erb +2 -0
  29. data/test/dummy/app/views/home/index.erb +1 -0
  30. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  31. data/test/dummy/app/views/layouts/exceptions.html.erb +12 -0
  32. data/test/dummy/config.ru +4 -0
  33. data/test/dummy/config/application.rb +49 -0
  34. data/test/dummy/config/boot.rb +10 -0
  35. data/test/dummy/config/database.yml +22 -0
  36. data/test/dummy/config/environment.rb +5 -0
  37. data/test/dummy/config/environments/development.rb +25 -0
  38. data/test/dummy/config/environments/production.rb +49 -0
  39. data/test/dummy/config/environments/test.rb +35 -0
  40. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  41. data/test/dummy/config/initializers/inflections.rb +10 -0
  42. data/test/dummy/config/initializers/mime_types.rb +5 -0
  43. data/test/dummy/config/initializers/secret_token.rb +7 -0
  44. data/test/dummy/config/initializers/session_store.rb +8 -0
  45. data/test/dummy/config/locales/en.yml +5 -0
  46. data/test/dummy/config/routes.rb +60 -0
  47. data/test/dummy/db/development.sqlite3 +0 -0
  48. data/test/dummy/db/production.sqlite3 +0 -0
  49. data/test/dummy/db/schema.rb +15 -0
  50. data/test/dummy/db/test.sqlite3 +0 -0
  51. data/test/dummy/log/test.log +40506 -0
  52. data/test/dummy/public/favicon.ico +0 -0
  53. data/test/dummy/public/javascripts/application.js +2 -0
  54. data/test/dummy/public/javascripts/controls.js +965 -0
  55. data/test/dummy/public/javascripts/dragdrop.js +974 -0
  56. data/test/dummy/public/javascripts/effects.js +1123 -0
  57. data/test/dummy/public/javascripts/prototype.js +4874 -0
  58. data/test/dummy/public/javascripts/rails.js +118 -0
  59. data/test/dummy/public/stylesheets/exceptions.css +17 -0
  60. data/test/dummy/script/rails +6 -0
  61. data/test/exceptron_local_custom_test.rb +173 -0
  62. data/test/exceptron_local_test.rb +122 -0
  63. data/test/exceptron_public_custom_test.rb +128 -0
  64. data/test/exceptron_public_test.rb +72 -0
  65. data/test/test_helper.rb +48 -0
  66. data/test/views/exceptions_custom/internal_server_error.da.html.erb +1 -0
  67. data/test/views/exceptions_custom/internal_server_error.json +1 -0
  68. data/test/views/exceptions_custom/not_found.html +26 -0
  69. data/test/views/exceptions_custom/not_found.xml +5 -0
  70. data/test/views/exceptions_custom/not_implemented.html.erb +26 -0
  71. data/test/views/layouts/exception_test.html.erb +12 -0
  72. data/test/views/local_exceptions_custom/diagnostics.da.html.erb +1 -0
  73. data/test/views/local_exceptions_custom/not_implemented.html.erb +26 -0
  74. data/test/views/local_exceptions_custom/unknown_action.erb +2 -0
  75. metadata +197 -0
@@ -0,0 +1,20 @@
1
+ Copyright 2010 JORLHUDA
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,3 @@
1
+ = Exceptron
2
+
3
+ This project rocks and uses MIT-LICENSE.
@@ -0,0 +1,38 @@
1
+ require 'exceptron/railtie'
2
+
3
+ module Exceptron
4
+ autoload :Dispatcher, 'exceptron/dispatcher'
5
+ autoload :Exception, 'exceptron/exception'
6
+ autoload :ExceptionsController, 'exceptron/exceptions_controller'
7
+ autoload :Helpers, 'exceptron/helpers'
8
+ autoload :LocalExceptionsController, 'exceptron/local_exceptions_controller'
9
+ autoload :LocalHelpers, 'exceptron/local_helpers'
10
+ autoload :Middleware, 'exceptron/middleware'
11
+ autoload :VERSION, 'exceptron/version'
12
+
13
+
14
+ mattr_reader :rescue_templates
15
+ @@rescue_templates = Hash.new('diagnostics')
16
+ @@rescue_templates.update(
17
+ 'ActionView::MissingTemplate' => 'missing_template',
18
+ 'ActionController::RoutingError' => 'routing_error',
19
+ 'AbstractController::ActionNotFound' => 'unknown_action',
20
+ 'ActionView::Template::Error' => 'template_error'
21
+ )
22
+
23
+ def self.enable!
24
+ @@enabled = true
25
+ end
26
+
27
+ def self.disable!
28
+ @@enabled = false
29
+ end
30
+
31
+ def self.enabled?
32
+ @@enabled
33
+ end
34
+
35
+ class << self
36
+ attr_accessor :controller, :local_controller
37
+ end
38
+ end
@@ -0,0 +1,58 @@
1
+ module Exceptron
2
+ class Dispatcher
3
+ FAILSAFE_RESPONSE = [500, {'Content-Type' => 'text/html'},
4
+ ["<html><head><title>500 Internal Server Error</title></head>" +
5
+ "<body><h1>500 Internal Server Error</h1>If you are the administrator of " +
6
+ "this website, then please read this web application's log file and/or the " +
7
+ "web server's log file to find out what went wrong.</body></html>"]]
8
+
9
+ def initialize(consider_all_requests_local)
10
+ @consider_all_requests_local = consider_all_requests_local
11
+ @exception_actions_cache = {}
12
+ end
13
+
14
+ def dispatch(env, exception)
15
+ log_error(exception.wrapped_exception)
16
+
17
+ local = @consider_all_requests_local || ActionDispatch::Request.new(env).local?
18
+ controller = exception_controller(local)
19
+ action = exception_action(local, controller, exception)
20
+
21
+ if action
22
+ controller.action(action).call(env)
23
+ else
24
+ FAILSAFE_RESPONSE
25
+ end
26
+ rescue Exception => failsafe_error
27
+ $stderr.puts "Error during failsafe response: #{failsafe_error}"
28
+ $stderr.puts failsafe_error.backtrace.join("\n")
29
+ FAILSAFE_RESPONSE
30
+ end
31
+
32
+ def exception_controller(local)
33
+ local ? Exceptron.local_controller : Exceptron.controller
34
+ end
35
+
36
+ def exception_action(local, controller_klass, exception)
37
+ controller = controller_klass.new
38
+ @exception_actions_cache[controller_klass] ||= {}
39
+ @exception_actions_cache[controller_klass][exception.original_exception.class] ||=
40
+ exception.actions.find { |action| controller.available_action?(action) }
41
+ end
42
+
43
+ def log_error(exception)
44
+ return unless logger
45
+
46
+ ActiveSupport::Deprecation.silence do
47
+ message = "\n#{exception.class} (#{exception.message}):\n"
48
+ message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code)
49
+ message << exception.backtrace.join("\n ")
50
+ logger.fatal("#{message}\n\n")
51
+ end
52
+ end
53
+
54
+ def logger
55
+ defined?(Rails.logger) ? Rails.logger : Logger.new($stderr)
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,97 @@
1
+ class Exceptron::Presenter
2
+ def initialize(exception)
3
+ @wrapped_exception = exception
4
+ end
5
+ attr_reader :wrapped_exception
6
+
7
+ class << self
8
+ attr_accessor :statuses
9
+ end
10
+ self.statuses = {}
11
+
12
+ def to_xml(options={})
13
+ _serialize(:xml, options)
14
+ end
15
+
16
+ def to_json(options={})
17
+ _serialize(:json, options)
18
+ end
19
+
20
+ def status_code
21
+ statuses = self.class.statuses
22
+ registered_exception = original_exception.class.ancestors.find { |klass| statuses.key? klass }
23
+ statuses[registered_exception]
24
+ end
25
+
26
+ def status_message
27
+ status = Rack::Utils::HTTP_STATUS_CODES[status_code]
28
+ status.to_s if status
29
+ end
30
+
31
+ def actions
32
+ original_exception.class.ancestors.map do |klass|
33
+ status_code = self.class.statuses[klass]
34
+ status = Rack::Utils::HTTP_STATUS_CODES[status_code]
35
+ status.to_s.downcase.gsub(/\s|-/, '_') if status
36
+ end.compact
37
+ end
38
+
39
+ def original_exception
40
+ if wrapped_exception.respond_to?(:original_exception)
41
+ wrapped_exception.original_exception
42
+ else
43
+ wrapped_exception
44
+ end
45
+ end
46
+
47
+ protected
48
+
49
+ def _serialize(serializer, options) #:nodoc:
50
+ hash = { :status => status_code, :message => status_message }
51
+ options = { :root => "error" }.merge!(options)
52
+ hash.send(:"to_#{serializer}", options)
53
+ end
54
+ end
55
+
56
+ class Exception
57
+ def self.respond_with(status)
58
+ Exceptron::Presenter.statuses[self] = status
59
+ end
60
+ respond_with 500
61
+ end
62
+
63
+ ActiveSupport.on_load(:action_controller) do
64
+ class ActionController::RoutingError
65
+ respond_with 404
66
+ end
67
+
68
+ class AbstractController::ActionNotFound
69
+ respond_with 404
70
+ end
71
+
72
+ class ActionController::MethodNotAllowed
73
+ respond_with 405
74
+ end
75
+
76
+ class ActionController::NotImplemented
77
+ respond_with 501
78
+ end
79
+ end
80
+
81
+ ActiveSupport.on_load(:active_record) do
82
+ class ActiveRecord::RecordNotFound
83
+ respond_with 404
84
+ end
85
+
86
+ class ActiveRecord::StaleObjectError
87
+ respond_with 409
88
+ end
89
+
90
+ class ActiveRecord::RecordInvalid
91
+ respond_with 422
92
+ end
93
+
94
+ class ActiveRecord::RecordNotSaved
95
+ respond_with 422
96
+ end
97
+ end
@@ -0,0 +1,19 @@
1
+ module Exceptron
2
+ class ExceptionsController < ActionController::Base
3
+ append_view_path File.expand_path("../views", __FILE__)
4
+ respond_to :html, :xml, :json
5
+
6
+ include Exceptron::Helpers
7
+
8
+ def internal_server_error
9
+ respond_with exception_presenter
10
+ end
11
+ alias not_found internal_server_error
12
+ alias unprocessable_entity internal_server_error
13
+
14
+ def self.inherited(subclass)
15
+ super
16
+ Exceptron.controller = subclass
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,24 @@
1
+ module Exceptron
2
+ module Helpers
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ before_filter :set_status_code
7
+ helper_method :exception_presenter, :exception
8
+ end
9
+
10
+ protected
11
+
12
+ def set_status_code
13
+ self.status = exception_presenter.status_code
14
+ end
15
+
16
+ def exception_presenter
17
+ @presenter ||= env["exceptron.presenter"]
18
+ end
19
+
20
+ def exception
21
+ @exception ||= exception_presenter.wrapped_exception
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,17 @@
1
+ module Exceptron
2
+ class LocalExceptionsController < ActionController::Base
3
+ append_view_path File.expand_path("../views", __FILE__)
4
+ include Exceptron::Helpers
5
+
6
+ helper Exceptron::LocalHelpers
7
+
8
+ def internal_server_error
9
+ render :action => Exceptron.rescue_templates[exception_presenter.original_exception.class.name]
10
+ end
11
+
12
+ def self.inherited(subclass)
13
+ super
14
+ Exceptron.local_controller = subclass
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,27 @@
1
+ module Exceptron
2
+ module LocalHelpers
3
+ def application_trace
4
+ clean_backtrace(exception, :silent)
5
+ end
6
+
7
+ def framework_trace
8
+ clean_backtrace(exception, :noise)
9
+ end
10
+
11
+ def full_trace
12
+ clean_backtrace(exception, :all)
13
+ end
14
+
15
+ def debug_hash(hash)
16
+ hash.map { |k, v| "#{k}: #{v.inspect}" }.sort.join("\n")
17
+ end
18
+
19
+ protected
20
+
21
+ def clean_backtrace(exception, *args)
22
+ Rails.respond_to?(:backtrace_cleaner) && Rails.backtrace_cleaner ?
23
+ Rails.backtrace_cleaner.clean(exception.backtrace, *args) :
24
+ exception.backtrace
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,27 @@
1
+ module Exceptron
2
+ class Middleware
3
+ def initialize(app, consider_all_requests_local)
4
+ @app = app
5
+ @dispatcher = Dispatcher.new(consider_all_requests_local)
6
+ end
7
+
8
+ def call(env)
9
+ begin
10
+ status, headers, body = @app.call(env)
11
+ exception = nil
12
+
13
+ # Only this middleware cares about RoutingError. So, let's just raise
14
+ # it here.
15
+ if headers['X-Cascade'] == 'pass'
16
+ raise ActionController::RoutingError, "No route matches [#{env['REQUEST_METHOD']}] #{env['PATH_INFO'].inspect}"
17
+ end
18
+ rescue Exception => exception
19
+ raise exception unless Exceptron.enabled?
20
+ exception = Presenter.new(exception)
21
+ env["exceptron.presenter"] = exception
22
+ end
23
+
24
+ exception ? @dispatcher.dispatch(env, exception) : [status, headers, body]
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,16 @@
1
+ module Exceptron
2
+ class Railtie < Rails::Railtie
3
+ config.exceptron = Exceptron
4
+
5
+ initializer "exceptron.swap_middlewares" do |app|
6
+ app.middleware.swap "ActionDispatch::ShowExceptions",
7
+ "Exceptron::Middleware", app.config.consider_all_requests_local
8
+ end
9
+
10
+ config.after_initialize do
11
+ config.exceptron.enable!
12
+ config.exceptron.controller ||= Exceptron::ExceptionsController
13
+ config.exceptron.local_controller ||= Exceptron::LocalExceptionsController
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,3 @@
1
+ module Exceptron
2
+ VERSION = "0.0.2".freeze
3
+ end
@@ -0,0 +1,26 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>We're sorry, but something went wrong (<%= exception_presenter.status_code %>)</title>
5
+ <style type="text/css">
6
+ body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
7
+ div.dialog {
8
+ width: 25em;
9
+ padding: 0 4em;
10
+ margin: 4em auto 0 auto;
11
+ border: 1px solid #ccc;
12
+ border-right-color: #999;
13
+ border-bottom-color: #999;
14
+ }
15
+ h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
16
+ </style>
17
+ </head>
18
+
19
+ <body>
20
+ <!-- This file lives in public/500.html -->
21
+ <div class="dialog">
22
+ <h1>We're sorry, but something went wrong.</h1>
23
+ <p>We've been notified about this issue and we'll take a look at it shortly.</p>
24
+ </div>
25
+ </body>
26
+ </html>
@@ -0,0 +1,27 @@
1
+ <% unless exception.blamed_files.blank? %>
2
+ <% if (hide = exception.blamed_files.length > 8) %>
3
+ <a href="#" onclick="document.getElementById('blame_trace').style.display='block'; return false;">Show blamed files</a>
4
+ <% end %>
5
+ <pre id="blame_trace" <%='style="display:none"' if hide %>><code><%=h exception.describe_blame %></code></pre>
6
+ <% end %>
7
+
8
+ <%
9
+ clean_params = request.filtered_parameters.clone
10
+ clean_params.delete("action")
11
+ clean_params.delete("controller")
12
+
13
+ request_dump = clean_params.empty? ? 'None' : clean_params.inspect.gsub(',', ",\n")
14
+ %>
15
+
16
+ <h2 style="margin-top: 30px">Request</h2>
17
+ <p><b>Parameters</b>: <pre><%=h request_dump %></pre></p>
18
+
19
+ <p><a href="#" onclick="document.getElementById('session_dump').style.display='block'; return false;">Show session dump</a></p>
20
+ <div id="session_dump" style="display:none"><pre><%= debug_hash request.session %></pre></div>
21
+
22
+ <p><a href="#" onclick="document.getElementById('env_dump').style.display='block'; return false;">Show env dump</a></p>
23
+ <div id="env_dump" style="display:none"><pre><%= debug_hash request.env.slice(*request.class::ENV_METHODS) %></pre></div>
24
+
25
+
26
+ <h2 style="margin-top: 30px">Response</h2>
27
+ <p><b>Headers</b>: <pre><%=h defined?(response) ? response.headers.inspect.gsub(',', ",\n") : 'None' %></pre></p>
@@ -0,0 +1,26 @@
1
+ <%
2
+ traces = [
3
+ ["Application Trace", application_trace],
4
+ ["Framework Trace", framework_trace],
5
+ ["Full Trace", full_trace]
6
+ ]
7
+ names = traces.collect {|name, trace| name}
8
+ %>
9
+
10
+ <p><code>Rails.root: <%= defined?(Rails) && Rails.respond_to?(:root) ? Rails.root : "unset" %></code></p>
11
+
12
+ <div id="traces">
13
+ <% names.each do |name| %>
14
+ <%
15
+ show = "document.getElementById('#{name.gsub(/\s/, '-')}').style.display='block';"
16
+ hide = (names - [name]).collect {|hide_name| "document.getElementById('#{hide_name.gsub(/\s/, '-')}').style.display='none';"}
17
+ %>
18
+ <a href="#" onclick="<%= hide.join %><%= show %>; return false;"><%= name %></a> <%= '|' unless names.last == name %>
19
+ <% end %>
20
+
21
+ <% traces.each do |name, trace| %>
22
+ <div id="<%= name.gsub(/\s/, '-') %>" style="display: <%= (name == "Application Trace") ? 'block' : 'none' %>;">
23
+ <pre><code><%=h trace.join "\n" %></code></pre>
24
+ </div>
25
+ <% end %>
26
+ </div>