ixtlan-error-handler 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.textile ADDED
@@ -0,0 +1,31 @@
1
+ h1. Rails Error Dumper
2
+
3
+ p. the main idea here is notify the developers on error but in a way to protect privacy of the users of the system. to do so ALL data need to remain on the server and they need to be deleted after period of time.
4
+
5
+ * first to dump as much as possible onto the filesystem of the server when an error occurs, i.e. the environment, the request, the response, the session, etc
6
+
7
+ * notify the developers
8
+
9
+ * delete expired error dumps
10
+
11
+ p. the next idea is to collect all possible rails exception and map them to three error pages and pass on a message. for each exception you can decide whether an error dump is needed or not.
12
+
13
+ h2. install
14
+
15
+ p. in Gemfile add *gem 'ixtlan-error-handler'*
16
+
17
+ p. for the configuration add for example in _config/initializers/error_handler.rb_
18
+
19
+ bc. config.error_dumper.dump_dir = Rails.root + "/log/errors" # default: log/errors
20
+ config.error_dumper.email_from = "no-reply@example.com"
21
+ config.error_dumper.email_to = "developer1@example.com,developer2@example.com"
22
+ config.error_dumper.keep_dumps = 30 # days
23
+ config.skip_rescue_module = true # do not include the predefined Rescue
24
+
25
+ h2. relation to ixtlan gem
26
+
27
+ p. the ixtlan gem provides a setup generator which adds configuration examples for this gem in _config/initializer/ixtlan.rb_ (the dynamic configuration is part of the ixtlan gem and it is just easier to keep that inside that gem !!!)
28
+
29
+ h2. relation to ixtlan-audit gem
30
+
31
+ p. if that gem is present and loaded than any error will be log with the help of _Ixtlan::Audit::UserLogger_
@@ -0,0 +1,5 @@
1
+ Feature: Generators for Ixtlan Audit
2
+
3
+ Scenario: The slf4r rails template creates a rails application which uses slf4r-wrapper
4
+ Given I create new rails application with template "simple.template"
5
+ Then the output should contain "setup slf4r logger wrapper with ActiveSupport::BufferedLogger"
@@ -0,0 +1,22 @@
1
+ require 'fileutils'
2
+ Given /^I create new rails application with template "(.*)"$/ do |template|
3
+ name = template.sub(/.template$/, '')
4
+ directory = File.join('target', name)
5
+ rails_version = ENV['RAILS_VERSION'] || '3.0.1'
6
+
7
+ ruby = defined?(JRUBY_VERSION) ? "jruby" : "ruby"
8
+ rails_command = "#{ENV['GEM_HOME']}/bin/rails"
9
+ rails_command = "-S rails" unless File.exists?(rails_command)
10
+ command = "#{rails_command} _#{rails_version}_ new #{directory} -f -m templates/#{template}"
11
+ FileUtils.rm_rf(directory)
12
+
13
+ system "#{ruby} #{command}"
14
+
15
+ @result = File.read("target/#{name}/log/development.log")
16
+ puts @result
17
+ end
18
+
19
+ Then /^the output should contain \"(.*)\"$/ do |expected|
20
+ (@result =~ /.*#{expected}.*/).should_not be_nil
21
+ end
22
+
@@ -0,0 +1,3 @@
1
+ if defined?(Rails)
2
+ require 'ixtlan/errors/railtie'
3
+ end
@@ -0,0 +1,85 @@
1
+ require 'ixtlan/errors/mailer'
2
+ module Ixtlan
3
+ module Errors
4
+ class ErrorDumper
5
+
6
+ attr_accessor :dump_dir, :email_from, :email_to, :keep_dumps
7
+
8
+ def keep_dumps
9
+ @keep_dumps ||= 90
10
+ end
11
+
12
+ def keep_dumps=(ttl)
13
+ @keep_dumps = ttl.to_i
14
+ cleanup
15
+ end
16
+
17
+ def dump_dir
18
+ unless @dump_dir
19
+ @dump_dir = File.join(Rails.root, "log", "errors")
20
+ FileUtils.mkdir_p(@dump_dir)
21
+ end
22
+ @dump_dir
23
+ end
24
+
25
+ def dump_dir=(dr)
26
+ @dump_dir = dir
27
+ FileUtils.mkdir_p(@dump_dir) if dir
28
+ @dump_dir
29
+ end
30
+
31
+ def dump(controller, exception)
32
+ cleanup
33
+ log_file = log_filename
34
+ logger = Logger.new(log_file)
35
+
36
+ dump_environment(logger, exception, controller)
37
+ Mailer.deliver_error_notification(@email_from, @email_to, exception, log_file) unless (@email_to.blank? || @email_from.blank?)
38
+ log_file
39
+ end
40
+
41
+ private
42
+
43
+ def log_filename(time = Time.now)
44
+ error_log_id = "#{time.tv_sec}#{time.tv_usec}"
45
+ File.join(dump_dir, "error-#{error_log_id}.log")
46
+ end
47
+
48
+ def dump_environment_header(logger, header)
49
+ logger.error("\n===================================================================\n#{header}\n===================================================================\n");
50
+ end
51
+
52
+ def dump_environment(logger, exception, controller)
53
+ dump_environment_header(logger, "REQUEST DUMP");
54
+ dump_hashmap(logger, controller.request.env)
55
+
56
+ dump_environment_header(logger, "RESPONSE DUMP");
57
+ dump_hashmap(logger, controller.response.headers)
58
+
59
+ dump_environment_header(logger, "SESSION DUMP");
60
+ dump_hashmap(logger, controller.session)
61
+
62
+ dump_environment_header(logger, "PARAMETER DUMP");
63
+ map = {}
64
+ dump_hashmap(logger, controller.params.each{ |k,v| map[k]=v })
65
+
66
+ dump_environment_header(logger, "EXCEPTION");
67
+ logger.error("#{exception.class}:#{exception.message}")
68
+ logger.error("\t" + exception.backtrace.join("\n\t"))
69
+ end
70
+
71
+ def dump_hashmap(logger, map)
72
+ for key,value in map
73
+ logger.error("\t#{key} => #{value.inspect}")
74
+ end
75
+ end
76
+
77
+ def cleanup
78
+ ref_log_file = log_filename(keep_dumps.ago)
79
+ Dir[File.join(dump_dir, "error-*.log")].each do |f|
80
+ FileUtils.rm(f) if File.basename(f) < ref_log_file
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,80 @@
1
+ module Ixtlan
2
+ module Errors
3
+ module ErrorHandler
4
+
5
+ protected
6
+
7
+ def internal_server_error(exception)
8
+ dump_error(exception)
9
+ status = :internal_server_error
10
+ error_page(:internal_server_error, exception, "internal server error: #{exception.class.name}")
11
+ end
12
+
13
+ def page_not_found(exception)
14
+ log_user_error(exception)
15
+ status = rescue_responses[exception.class.name]
16
+ status = status == :internal_server_error ? :not_found : status
17
+ error_page(status, exception, "page not found")
18
+ end
19
+
20
+ def stale_resource(exception)
21
+ log_user_error(exception)
22
+ respond_to do |format|
23
+ format.html {
24
+ render_stale_error_page
25
+ }
26
+ format.xml { head :conflict }
27
+ end
28
+ end
29
+
30
+ def render_error_page_with_session(status)
31
+ render :template => "errors/error_with_session", :status => status
32
+ end
33
+
34
+ def render_error_page(status)
35
+ render :template => "errors/error", :status => status
36
+ end
37
+
38
+ def render_stale_error_page
39
+ render :template => "errors/stale", :status => :conflict
40
+ end
41
+
42
+ private
43
+
44
+ if defined? ::Ixtlan::Audit
45
+ def user_logger
46
+ @user_logger ||= Ixtlan::Audit::UserLogger.new(Rails.application.config.audit_manager)
47
+ end
48
+
49
+ def log_user_error(exception)
50
+ user_logger.log_action(self, " - #{exception.class} - #{exception.message}")
51
+ logger.error(exception)
52
+ end
53
+ else
54
+ def log_user_error(exception)
55
+ logger.error(exception)
56
+ end
57
+ end
58
+
59
+ def error_page(status, exception, notice)
60
+ respond_to do |format|
61
+ format.html {
62
+ @notice = notice
63
+ if respond_to?(:current_user) && current_user
64
+ render_error_page_with_session(status)
65
+ else
66
+ render_error_page(status)
67
+ end
68
+ }
69
+ format.xml { head status }
70
+ format.json { head status }
71
+ end
72
+ end
73
+
74
+ def dump_error(exception)
75
+ log_user_error(exception)
76
+ Rails.configuration.error_dumper.dump(self, exception)
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,15 @@
1
+ module Ixtlan
2
+ module Errors
3
+ class Mailer < ActionMailer::Base
4
+
5
+ def error_notification(email_from, emails_to, exception, error_file)
6
+ @subject = exception.message
7
+ @body = {:text => "#{error_file}"}
8
+ @recipients = emails_to
9
+ @from = email_from
10
+ @sent_on = Time.now
11
+ @headers = {}
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1 @@
1
+ <%= @text %>
@@ -0,0 +1,30 @@
1
+ require 'rails'
2
+ require 'ixtlan/errors/rescue_module'
3
+ require 'ixtlan/errors/error_handler'
4
+ require 'ixtlan/errors/error_dumper'
5
+
6
+ module Ixtlan
7
+ module Errors
8
+ class Railtie < Rails::Railtie
9
+
10
+ config.before_configuration do |app|
11
+
12
+ path = File.join(File.dirname(__FILE__), "mailer")
13
+ unless ActionMailer::Base.view_paths.member? path
14
+ ActionMailer::Base.view_paths= [ActionMailer::Base.view_paths, path].flatten
15
+ end
16
+
17
+ app.config.class.class_eval do
18
+ attr_accessor :error_dumper, :skip_rescue_module
19
+ app.config.error_dumper = ErrorDumper.new
20
+ app.config.skip_rescue_module = false
21
+ end
22
+ end
23
+
24
+ config.after_initialize do |app|
25
+ ::ActionController::Base.send(:include, RescueModule) unless app.config.skip_rescue_module
26
+ ::ActionController::Base.send(:include, ErrorHandler)
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,42 @@
1
+ module Ixtlan
2
+ module Errors
3
+ module RescueModule
4
+ def self.included(controller)
5
+ if defined? ::Ixtlan::ModifiedBy
6
+ # needs 'optimistic_persistence'
7
+ controller.rescue_from ::Ixtlan::ModifiedBy::StaleResourceError, :with => :stale_resource
8
+ end
9
+
10
+ if defined? ::Ixtlan::Guard
11
+ # needs 'guard'
12
+ controller.rescue_from ::Ixtlan::Guard::GuardException, :with => :page_not_found
13
+ controller.rescue_from ::Ixtlan::Guard::PermissionDenied, :with => :page_not_found
14
+ end
15
+
16
+ if defined? ::DataMapper
17
+ # datamapper
18
+ controller.rescue_from ::DataMapper::ObjectNotFoundError, :with => :page_not_found
19
+ end
20
+
21
+ if defined? ::ActiveRecord
22
+ # activerecord
23
+ controller.rescue_from ::ActiveRecord::RecordNotFound, :with => :page_not_found
24
+ end
25
+
26
+ # standard rails controller
27
+ controller.rescue_from ::ActionController::RoutingError, :with => :page_not_found
28
+ controller.rescue_from ::ActionController::UnknownAction, :with => :page_not_found
29
+ controller.rescue_from ::ActionController::MethodNotAllowed, :with => :page_not_found
30
+ controller.rescue_from ::ActionController::NotImplemented, :with => :page_not_found
31
+ controller.rescue_from ::ActionController::InvalidAuthenticityToken, :with => :stale_resource
32
+
33
+ # have nice stacktraces in development mode
34
+ unless Rails.application.config.consider_all_requests_local
35
+ controller.rescue_from ::ActionView::MissingTemplate, :with => :internal_server_error
36
+ controller.rescue_from ::ActionView::TemplateError, :with => :internal_server_error
37
+ controller.rescue_from ::Exception, :with => :internal_server_error
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
metadata ADDED
@@ -0,0 +1,128 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ixtlan-error-handler
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ - 0
9
+ version: 0.1.0
10
+ platform: ruby
11
+ authors:
12
+ - mkristian
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2011-03-01 00:00:00 +05:30
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: rails
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "="
26
+ - !ruby/object:Gem::Version
27
+ segments:
28
+ - 3
29
+ - 0
30
+ - 1
31
+ version: 3.0.1
32
+ type: :development
33
+ version_requirements: *id001
34
+ - !ruby/object:Gem::Dependency
35
+ name: rspec
36
+ prerelease: false
37
+ requirement: &id002 !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - "="
40
+ - !ruby/object:Gem::Version
41
+ segments:
42
+ - 2
43
+ - 0
44
+ - 1
45
+ version: 2.0.1
46
+ type: :development
47
+ version_requirements: *id002
48
+ - !ruby/object:Gem::Dependency
49
+ name: cucumber
50
+ prerelease: false
51
+ requirement: &id003 !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - "="
54
+ - !ruby/object:Gem::Version
55
+ segments:
56
+ - 0
57
+ - 9
58
+ - 4
59
+ version: 0.9.4
60
+ type: :development
61
+ version_requirements: *id003
62
+ - !ruby/object:Gem::Dependency
63
+ name: rake
64
+ prerelease: false
65
+ requirement: &id004 !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - "="
68
+ - !ruby/object:Gem::Version
69
+ segments:
70
+ - 0
71
+ - 8
72
+ - 7
73
+ version: 0.8.7
74
+ type: :development
75
+ version_requirements: *id004
76
+ description: dump errors on filesystem and notify developers, map different errors to specific pages
77
+ email:
78
+ - m.kristian@web.de
79
+ executables: []
80
+
81
+ extensions: []
82
+
83
+ extra_rdoc_files: []
84
+
85
+ files:
86
+ - README.textile
87
+ - features/step_definitions/simple_steps.rb
88
+ - features/generators.feature
89
+ - lib/ixtlan-error-handler.rb
90
+ - lib/ixtlan/errors/railtie.rb
91
+ - lib/ixtlan/errors/error_handler.rb
92
+ - lib/ixtlan/errors/mailer.rb
93
+ - lib/ixtlan/errors/rescue_module.rb
94
+ - lib/ixtlan/errors/error_dumper.rb
95
+ - lib/ixtlan/errors/mailer/error_notification.erb
96
+ has_rdoc: true
97
+ homepage: http://github.com/mkristian/ixtlan-error-handler
98
+ licenses: []
99
+
100
+ post_install_message:
101
+ rdoc_options:
102
+ - --main
103
+ - README.textile
104
+ require_paths:
105
+ - lib
106
+ required_ruby_version: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ segments:
111
+ - 0
112
+ version: "0"
113
+ required_rubygems_version: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ segments:
118
+ - 0
119
+ version: "0"
120
+ requirements: []
121
+
122
+ rubyforge_project:
123
+ rubygems_version: 1.3.6
124
+ signing_key:
125
+ specification_version: 3
126
+ summary: dump errors on filesystem and notify developers
127
+ test_files: []
128
+