Permalink
Browse files

first commit

  • Loading branch information...
0 parents commit 4b25b733f534883aaeb7aa809a029ef52888442e @mkristian committed Feb 28, 2011
@@ -0,0 +1,3 @@
+target
+*.pom
+*~
@@ -0,0 +1,31 @@
+h1. Rails Error Dumper
+
+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.
+
+* 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
+
+* notify the developers
+
+* delete expired error dumps
+
+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.
+
+h2. install
+
+p. in Gemfile add *gem 'ixtlan-error-handler'*
+
+p. for the configuration add for example in _config/initializers/error_handler.rb_
+
+bc. config.error_dumper.dump_dir = Rails.root + "/log/errors" # default: log/errors
+config.error_dumper.email_from = "no-reply@example.com"
+config.error_dumper.email_to = "developer1@example.com,developer2@example.com"
+config.error_dumper.keep_dumps = 30 # days
+config.skip_rescue_module = true # do not include the predefined Rescue
+
+h2. relation to ixtlan gem
+
+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 !!!)
+
+h2. relation to ixtlan-audit gem
+
+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 @@
+Feature: Generators for Ixtlan Audit
+
+ Scenario: The slf4r rails template creates a rails application which uses slf4r-wrapper
+ Given I create new rails application with template "simple.template"
+ Then the output should contain "setup slf4r logger wrapper with ActiveSupport::BufferedLogger"
@@ -0,0 +1,22 @@
+require 'fileutils'
+Given /^I create new rails application with template "(.*)"$/ do |template|
+ name = template.sub(/.template$/, '')
+ directory = File.join('target', name)
+ rails_version = ENV['RAILS_VERSION'] || '3.0.1'
+
+ ruby = defined?(JRUBY_VERSION) ? "jruby" : "ruby"
+ rails_command = "#{ENV['GEM_HOME']}/bin/rails"
+ rails_command = "-S rails" unless File.exists?(rails_command)
+ command = "#{rails_command} _#{rails_version}_ new #{directory} -f -m templates/#{template}"
+ FileUtils.rm_rf(directory)
+
+ system "#{ruby} #{command}"
+
+ @result = File.read("target/#{name}/log/development.log")
+ puts @result
+end
+
+Then /^the output should contain \"(.*)\"$/ do |expected|
+ (@result =~ /.*#{expected}.*/).should_not be_nil
+end
+
@@ -0,0 +1,32 @@
+# -*- mode: ruby -*-
+Gem::Specification.new do |s|
+ s.name = 'ixtlan-error-handler'
+ s.version = '0.1.0'
+
+ s.summary = 'dump errors on filesystem and notify developers'
+ s.description = 'dump errors on filesystem and notify developers, map different errors to specific pages'
+ s.homepage = 'http://github.com/mkristian/ixtlan-error-handler'
+
+ s.authors = ['mkristian']
+ s.email = ['m.kristian@web.de']
+
+ s.files = Dir['MIT-LICENSE']
+ s.licenses << 'MIT-LICENSE'
+# s.files += Dir['History.txt']
+ s.files += Dir['README.textile']
+# s.extra_rdoc_files = ['History.txt','README.textile']
+ s.rdoc_options = ['--main','README.textile']
+ s.files += Dir['lib/**/*']
+ s.files += Dir['spec/**/*']
+ s.files += Dir['features/**/*rb']
+ s.files += Dir['features/**/*feature']
+ s.test_files += Dir['spec/**/*_spec.rb']
+ s.test_files += Dir['features/*.feature']
+ s.test_files += Dir['features/step_definitions/*.rb']
+ s.add_development_dependency 'rails', '3.0.1'
+ s.add_development_dependency 'rspec', '2.0.1'
+ s.add_development_dependency 'cucumber', '0.9.4'
+ s.add_development_dependency 'rake', '0.8.7'
+end
+
+# vim: syntax=Ruby
@@ -0,0 +1,3 @@
+if defined?(Rails)
+ require 'ixtlan/errors/railtie'
+end
@@ -0,0 +1,85 @@
+require 'ixtlan/errors/mailer'
+module Ixtlan
+ module Errors
+ class ErrorDumper
+
+ attr_accessor :dump_dir, :email_from, :email_to, :keep_dumps
+
+ def keep_dumps
+ @keep_dumps ||= 90
+ end
+
+ def keep_dumps=(ttl)
+ @keep_dumps = ttl.to_i
+ cleanup
+ end
+
+ def dump_dir
+ unless @dump_dir
+ @dump_dir = File.join(Rails.root, "log", "errors")
+ FileUtils.mkdir_p(@dump_dir)
+ end
+ @dump_dir
+ end
+
+ def dump_dir=(dr)
+ @dump_dir = dir
+ FileUtils.mkdir_p(@dump_dir) if dir
+ @dump_dir
+ end
+
+ def dump(controller, exception)
+ cleanup
+ log_file = log_filename
+ logger = Logger.new(log_file)
+
+ dump_environment(logger, exception, controller)
+ Mailer.deliver_error_notification(@email_from, @email_to, exception, log_file) unless (@email_to.blank? || @email_from.blank?)
+ log_file
+ end
+
+ private
+
+ def log_filename(time = Time.now)
+ error_log_id = "#{time.tv_sec}#{time.tv_usec}"
+ File.join(dump_dir, "error-#{error_log_id}.log")
+ end
+
+ def dump_environment_header(logger, header)
+ logger.error("\n===================================================================\n#{header}\n===================================================================\n");
+ end
+
+ def dump_environment(logger, exception, controller)
+ dump_environment_header(logger, "REQUEST DUMP");
+ dump_hashmap(logger, controller.request.env)
+
+ dump_environment_header(logger, "RESPONSE DUMP");
+ dump_hashmap(logger, controller.response.headers)
+
+ dump_environment_header(logger, "SESSION DUMP");
+ dump_hashmap(logger, controller.session)
+
+ dump_environment_header(logger, "PARAMETER DUMP");
+ map = {}
+ dump_hashmap(logger, controller.params.each{ |k,v| map[k]=v })
+
+ dump_environment_header(logger, "EXCEPTION");
+ logger.error("#{exception.class}:#{exception.message}")
+ logger.error("\t" + exception.backtrace.join("\n\t"))
+ end
+
+ def dump_hashmap(logger, map)
+ for key,value in map
+ logger.error("\t#{key} => #{value.inspect}")
+ end
+ end
+
+ def cleanup
+ ref_log_file = log_filename(keep_dumps.ago)
+ Dir[File.join(dump_dir, "error-*.log")].each do |f|
+ FileUtils.rm(f) if File.basename(f) < ref_log_file
+ end
+ end
+ end
+ end
+end
@@ -0,0 +1,80 @@
+module Ixtlan
+ module Errors
+ module ErrorHandler
+
+ protected
+
+ def internal_server_error(exception)
+ dump_error(exception)
+ status = :internal_server_error
+ error_page(:internal_server_error, exception, "internal server error: #{exception.class.name}")
+ end
+
+ def page_not_found(exception)
+ log_user_error(exception)
+ status = rescue_responses[exception.class.name]
+ status = status == :internal_server_error ? :not_found : status
+ error_page(status, exception, "page not found")
+ end
+
+ def stale_resource(exception)
+ log_user_error(exception)
+ respond_to do |format|
+ format.html {
+ render_stale_error_page
+ }
+ format.xml { head :conflict }
+ end
+ end
+
+ def render_error_page_with_session(status)
+ render :template => "errors/error_with_session", :status => status
+ end
+
+ def render_error_page(status)
+ render :template => "errors/error", :status => status
+ end
+
+ def render_stale_error_page
+ render :template => "errors/stale", :status => :conflict
+ end
+
+ private
+
+ if defined? ::Ixtlan::Audit
+ def user_logger
+ @user_logger ||= Ixtlan::Audit::UserLogger.new(Rails.application.config.audit_manager)
+ end
+
+ def log_user_error(exception)
+ user_logger.log_action(self, " - #{exception.class} - #{exception.message}")
+ logger.error(exception)
+ end
+ else
+ def log_user_error(exception)
+ logger.error(exception)
+ end
+ end
+
+ def error_page(status, exception, notice)
+ respond_to do |format|
+ format.html {
+ @notice = notice
+ if respond_to?(:current_user) && current_user
+ render_error_page_with_session(status)
+ else
+ render_error_page(status)
+ end
+ }
+ format.xml { head status }
+ format.json { head status }
+ end
+ end
+
+ def dump_error(exception)
+ log_user_error(exception)
+ Rails.configuration.error_dumper.dump(self, exception)
+ end
+ end
+ end
+end
@@ -0,0 +1,15 @@
+module Ixtlan
+ module Errors
+ class Mailer < ActionMailer::Base
+
+ def error_notification(email_from, emails_to, exception, error_file)
+ @subject = exception.message
+ @body = {:text => "#{error_file}"}
+ @recipients = emails_to
+ @from = email_from
+ @sent_on = Time.now
+ @headers = {}
+ end
+ end
+ end
+end
@@ -0,0 +1 @@
+<%= @text %>
@@ -0,0 +1,30 @@
+require 'rails'
+require 'ixtlan/errors/rescue_module'
+require 'ixtlan/errors/error_handler'
+require 'ixtlan/errors/error_dumper'
+
+module Ixtlan
+ module Errors
+ class Railtie < Rails::Railtie
+
+ config.before_configuration do |app|
+
+ path = File.join(File.dirname(__FILE__), "mailer")
+ unless ActionMailer::Base.view_paths.member? path
+ ActionMailer::Base.view_paths= [ActionMailer::Base.view_paths, path].flatten
+ end
+
+ app.config.class.class_eval do
+ attr_accessor :error_dumper, :skip_rescue_module
+ app.config.error_dumper = ErrorDumper.new
+ app.config.skip_rescue_module = false
+ end
+ end
+
+ config.after_initialize do |app|
+ ::ActionController::Base.send(:include, RescueModule) unless app.config.skip_rescue_module
+ ::ActionController::Base.send(:include, ErrorHandler)
+ end
+ end
+ end
+end
@@ -0,0 +1,42 @@
+module Ixtlan
+ module Errors
+ module RescueModule
+ def self.included(controller)
+ if defined? ::Ixtlan::ModifiedBy
+ # needs 'optimistic_persistence'
+ controller.rescue_from ::Ixtlan::ModifiedBy::StaleResourceError, :with => :stale_resource
+ end
+
+ if defined? ::Ixtlan::Guard
+ # needs 'guard'
+ controller.rescue_from ::Ixtlan::Guard::GuardException, :with => :page_not_found
+ controller.rescue_from ::Ixtlan::Guard::PermissionDenied, :with => :page_not_found
+ end
+
+ if defined? ::DataMapper
+ # datamapper
+ controller.rescue_from ::DataMapper::ObjectNotFoundError, :with => :page_not_found
+ end
+
+ if defined? ::ActiveRecord
+ # activerecord
+ controller.rescue_from ::ActiveRecord::RecordNotFound, :with => :page_not_found
+ end
+
+ # standard rails controller
+ controller.rescue_from ::ActionController::RoutingError, :with => :page_not_found
+ controller.rescue_from ::ActionController::UnknownAction, :with => :page_not_found
+ controller.rescue_from ::ActionController::MethodNotAllowed, :with => :page_not_found
+ controller.rescue_from ::ActionController::NotImplemented, :with => :page_not_found
+ controller.rescue_from ::ActionController::InvalidAuthenticityToken, :with => :stale_resource
+
+ # have nice stacktraces in development mode
+ unless Rails.application.config.consider_all_requests_local
+ controller.rescue_from ::ActionView::MissingTemplate, :with => :internal_server_error
+ controller.rescue_from ::ActionView::TemplateError, :with => :internal_server_error
+ controller.rescue_from ::Exception, :with => :internal_server_error
+ end
+ end
+ end
+ end
+end

0 comments on commit 4b25b73

Please sign in to comment.