Skip to content
Browse files

First!!

  • Loading branch information...
1 parent 2a17905 commit 5cfe389673945dcbaf7ff1d10d7049d0a3edc119 @rsanheim committed Jul 17, 2009
View
20 LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2009 Rob Sanheim
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+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.
View
0 README → README.markdown
File renamed without changes.
View
32 Rakefile
@@ -0,0 +1,32 @@
+require 'rubygems'
+require 'rake'
+
+begin
+ require 'jeweler'
+ Jeweler::Tasks.new do |gem|
+ gem.name = "queued_exceptions"
+ gem.summary = %Q{TODO}
+ gem.email = "rsanheim@gmail.com"
+ gem.homepage = "http://github.com/relevance/queued_exceptions"
+ gem.authors = ["Rob Sanheim"]
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
+ end
+
+rescue LoadError
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
+end
+
+require 'micronaut/rake_task'
+Micronaut::RakeTask.new(:examples) do |examples|
+ examples.pattern = 'examples/**/*_example.rb'
+ examples.ruby_opts << '-Ilib -Iexamples'
+end
+
+Micronaut::RakeTask.new(:rcov) do |examples|
+ examples.pattern = 'examples/**/*_example.rb'
+ examples.rcov_opts = '-Ilib -Iexamples'
+ examples.rcov = true
+end
+
+
+task :default => :examples
View
21 examples/example_helper.rb
@@ -0,0 +1,21 @@
+require 'rubygems'
+require 'micronaut'
+require 'mocha'
+
+$LOAD_PATH.unshift(File.dirname(__FILE__))
+$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
+$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), 'lib'))
+
+require 'queued_exceptions'
+
+def not_in_editor?
+ !(ENV.has_key?('TM_MODE') || ENV.has_key?('EMACS') || ENV.has_key?('VIM'))
+end
+
+Micronaut.configure do |c|
+ c.mock_with :mocha
+ c.color_enabled = not_in_editor?
+ c.filter_run :focused => true
+ c.alias_example_to :fit, :focused => true
+end
+
View
95 examples/lib/queued_exceptions/cleaner_example.rb
@@ -0,0 +1,95 @@
+require File.expand_path(File.join(File.dirname(__FILE__), *%w[.. .. example_helper]))
+require 'ostruct'
+
+describe QueuedExceptions::Cleaner do
+
+ describe "building the message" do
+
+ it "merges hash passed in with default info" do
+ hash = {:message => "hey!"}
+ default_info = mock()
+ default_info.expects(:merge).with(hash)
+ cleaner = QueuedExceptions::Cleaner.new(hash)
+ cleaner.expects(:default_info).returns(default_info)
+ cleaner.clean
+ end
+ end
+
+ describe "exceptions" do
+
+ def raised_exception
+ raise RuntimeError, "Your zing bats got mixed up with the snosh frazzles."
+ rescue => e
+ e
+ end
+
+ it "should extract exception info" do
+ exception = raised_exception
+ data = QueuedExceptions::Cleaner.new(exception).clean
+ data[:error_class].should == "RuntimeError"
+ data[:error_message].should == "RuntimeError: Your zing bats got mixed up with the snosh frazzles."
+ data[:backtrace].should == exception.backtrace
+ end
+
+ it "merges rails info and ruby info into the exception info" do
+ cleaner = QueuedExceptions::Cleaner.new(raised_exception)
+ rails = stub_everything(:version => "2.0", :root => "/rails/root", :env => "production")
+ cleaner.stubs(:rails_configuration).returns(rails)
+ cleaner.clean.should include(:rails_version => "2.0")
+ cleaner.clean.should include(:rails_root => "/rails/root")
+ cleaner.clean.should include(:rails_env => "production")
+ end
+
+ end
+
+ describe "hashes" do
+
+ it "merges rails info and ruby info into the notification" do
+ cleaner = QueuedExceptions::Cleaner.new({})
+ rails = stub_everything(:version => "2.0", :root => "/rails/root", :env => "production")
+ cleaner.stubs(:rails_configuration).returns(rails)
+ cleaner.clean.should include(:rails_version => "2.0")
+ cleaner.clean.should include(:rails_root => "/rails/root")
+ cleaner.clean.should include(:rails_env => "production")
+ end
+
+ end
+
+ describe "default info to be included with every notification" do
+
+ it "should return full ENV" do
+ environment = { "USER" => "jdoe", "PATH" => "/usr/bin", "HOME" => "/usr/home/jdoe" }
+ cleaner = QueuedExceptions::Cleaner.new
+ cleaner.stubs(:env).returns(environment)
+ cleaner.default_info.should include(:environment => environment)
+ end
+
+ it "should return Ruby version and platform" do
+ cleaner = QueuedExceptions::Cleaner.new
+ cleaner.stubs(:ruby_version).returns("1.8.6")
+ cleaner.stubs(:ruby_platform).returns("Mac OS X blah")
+ data = cleaner.default_info
+ data.should include(:ruby_version => "1.8.6")
+ data.should include(:ruby_platform => "Mac OS X blah")
+ end
+
+ describe "when Rails is defined" do
+
+ it "should return Rails info" do
+ cleaner = QueuedExceptions::Cleaner.new
+ rails = stub
+ rails.stubs(:root).returns("/some/path")
+ rails.stubs(:env).returns("production")
+ rails.stubs(:version).returns("2.1.2")
+ cleaner.stubs(:rails_configuration).returns(rails)
+
+ cleaner.default_info.should include(:rails_root => "/some/path")
+ cleaner.default_info.should include(:rails_env => "production")
+ cleaner.default_info.should include(:rails_version => "2.1.2")
+ end
+
+ end
+
+ end
+
+end
View
14 examples/queued_exceptions_example.rb
@@ -0,0 +1,14 @@
+require File.join(File.dirname(__FILE__), *%w[example_helper])
+
+describe QueuedExceptions do
+
+ it "can handle" do
+ exception = RuntimeError.new
+ @handled = []
+ QueuedExceptions.register_handler do |notice|
+ @handled << notice
+ end
+ QueuedExceptions.handle(exception)
+ @handled.size.should == 1
+ end
+end
View
2 lib/consumers.rb
@@ -0,0 +1,2 @@
+require 'action_mailer'
+require File.join(File.dirname(__FILE__), *%w[consumers email_consumer])
View
54 lib/consumers/email_consumer.rb
@@ -0,0 +1,54 @@
+module QueuedExceptions
+ class EmailConsumer
+
+ attr_reader :notice
+
+ def initialize(notice)
+ @notice = notice
+ end
+
+ def process
+ Mailer.deliver_exception_notification(notice)
+ end
+
+ end
+
+ class Mailer < ActionMailer::Base
+ @@sender_address = %("Exception Notifier" <exception.notifier@default.com>)
+ cattr_accessor :sender_address
+
+ @@exception_recipients = []
+ cattr_accessor :exception_recipients
+
+ @@email_prefix = "[ERROR] "
+ cattr_accessor :email_prefix
+
+ self.template_root = File.expand_path(File.join(File.dirname(__FILE__), *%w[.. .. views]))
+
+ def self.reloadable?() false end
+
+ def exception_notification(data={})
+ content_type "text/plain"
+
+ subject "#{email_prefix} Error Caught: (#{data[:error_class]}) #{data[:error_message]}"
+
+ recipients exception_recipients
+ from sender_address
+
+ body data
+ end
+
+ private
+
+ def sanitize_backtrace(trace)
+ re = Regexp.new(/^#{Regexp.escape(rails_root)}/)
+ trace.map { |line| Pathname.new(line.gsub(re, "[RAILS_ROOT]")).cleanpath.to_s }
+ end
+
+ def rails_root
+ @rails_root ||= Pathname.new(RAILS_ROOT).cleanpath.to_s
+ end
+
+ end
+
+end
View
85 lib/queued_exceptions.rb
@@ -0,0 +1,85 @@
+require File.join(File.dirname(__FILE__), *%w[queued_exceptions cleaner])
+require File.join(File.dirname(__FILE__), *%w[consumers])
+
+module QueuedExceptions
+ IGNORE_DEFAULT = ['ActiveRecord::RecordNotFound',
+ 'ActionController::RoutingError',
+ 'ActionController::InvalidAuthenticityToken',
+ 'CGI::Session::CookieStore::TamperedWithCookie',
+ 'ActionController::UnknownAction']
+
+ # Some of these don't exist for Rails 1.2.*, so we have to consider that.
+ IGNORE_DEFAULT.map!{|e| eval(e) rescue nil }.compact!
+ IGNORE_DEFAULT.freeze
+
+ IGNORE_USER_AGENT_DEFAULT = []
+
+ # TODO: logging
+ def self.handle(hash_or_exception)
+ notice = normalize_notice(hash_or_exception)
+ # notice = clean_notice(notice)
+ publish(notice)
+ end
+
+ # Cleaner ? ExceptionCleaner /
+ def self.default_notice_options #:nodoc:
+ {
+ :error_message => 'Notification',
+ :backtrace => caller,
+ :request => {},
+ :session => {},
+ :environment => ENV.to_hash
+ }
+ end
+
+ def self.normalize_notice(notice) #:nodoc:
+ case notice
+ when Hash
+ default_notice_options.merge(notice)
+ when Exception
+ default_notice_options.merge(exception_to_data(notice))
+ end
+ end
+
+ def self.exception_to_data exception #:nodoc:
+ data = {
+ :error_class => exception.class.name,
+ :error_message => "#{exception.class.name}: #{exception.message}",
+ :backtrace => exception.backtrace,
+ :environment => ENV.to_hash
+ }
+
+ if self.respond_to? :request
+ data[:request] = {
+ :params => request.parameters.to_hash,
+ :rails_root => File.expand_path(RAILS_ROOT),
+ :url => "#{request.protocol}#{request.host}#{request.request_uri}"
+ }
+ data[:environment].merge!(request.env.to_hash)
+ end
+
+ if self.respond_to? :session
+ data[:session] = {
+ :key => session.instance_variable_get("@session_id"),
+ :data => session.respond_to?(:to_hash) ?
+ session.to_hash :
+ session.instance_variable_get("@data")
+ }
+ end
+
+ data
+ end
+
+ # Notifiers
+ def self.publish(notice)
+ @handlers.each do |blk|
+ blk.call(notice)
+ end
+ end
+
+ def self.register_handler(&blk)
+ (@handlers = []) << blk
+ end
+
+end
+
View
71 lib/queued_exceptions/cleaner.rb
@@ -0,0 +1,71 @@
+module QueuedExceptions
+
+ class Cleaner
+
+ attr_reader :exception
+
+ def initialize(exception = RuntimeError.new)
+ @exception = exception
+ end
+
+ def clean
+ data = case @exception
+ when Exception
+ {
+ :error_class => exception.class.name,
+ :error_message => "#{exception.class.name}: #{exception.message}",
+ :backtrace => exception.backtrace,
+ }
+ when Hash
+ @exception
+ end
+ default_info.merge(data)
+ end
+
+ def default_info
+ default_info = {
+ :environment => env,
+ :ruby_version => ruby_version,
+ :ruby_platform => ruby_platform
+ }
+ default_info = add_ruby_info(default_info)
+ default_info = add_rails_info(default_info) if rails_configuration
+ default_info
+ end
+
+ # mergers
+ def add_rails_info(data)
+ data.merge({
+ :rails_env => rails_configuration.env,
+ :rails_root => rails_configuration.root,
+ :rails_version => rails_configuration.version
+ })
+ end
+
+ def add_ruby_info(data)
+ data.merge({
+ :ruby_version => ruby_version,
+ :ruby_platform => ruby_platform
+ })
+ end
+
+ # accessors
+ def ruby_version
+ RUBY_VERSION
+ end
+
+ def ruby_platform
+ RUBY_PLATFORM
+ end
+
+ def env
+ ENV.to_hash
+ end
+
+ def rails_configuration
+ defined?(Rails) && Rails
+ end
+
+
+ end
+end
View
1 rails/init.rb
@@ -0,0 +1 @@
+require File.join(File.dirname(__FILE__), *%w[.. lib queued_exceptions])
View
7 views/exception_publisher/mailer/exception_notification.erb
@@ -0,0 +1,7 @@
+A <%= @error_class %> occurred
+
+ <%= @error_message %>
+ <%= @backtrace %>
+
+
+ <%= @environment %>

0 comments on commit 5cfe389

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