Permalink
Browse files

0.1.6 - The safety nets

  • Loading branch information...
1 parent a8faf8d commit 81693c84d255666b09fa5aecfa7a51d249c81146 @kennethkalmer committed May 13, 2009
View
9 History.txt
@@ -1,3 +1,12 @@
+== 0.1.6 2009-05-13
+
+* DaemonKit::Safety class to handle the trapping and logging of
+ exceptions, as well as email notifications or Hoptoad notifications.
+* New config/pre-daemonize and config/post-daemonize structure
+* New tasks to simplify upgrading daemon-kit projects
+* Fixed some other annoyances and bugs
+* Bigger TODO list
+
== 0.1.5 2009-05-07
* DaemonKit::Config class to easy the use of YAML configs internally,
View
4 Manifest.txt
@@ -44,11 +44,15 @@ lib/daemon_kit/amqp.rb
lib/daemon_kit/application.rb
lib/daemon_kit/config.rb
lib/daemon_kit/cron.rb
+lib/daemon_kit/error_handlers/base.rb
+lib/daemon_kit/error_handlers/hoptoad.rb
+lib/daemon_kit/error_handlers/mail.rb
lib/daemon_kit/initializer.rb
lib/daemon_kit/jabber.rb
lib/daemon_kit/nanite.rb
lib/daemon_kit/nanite/agent.rb
lib/daemon_kit/patches/force_kill_wait.rb
+lib/daemon_kit/safety.rb
lib/daemon_kit/tasks.rb
lib/daemon_kit/tasks/environment.rake
lib/daemon_kit/tasks/framework.rake
View
3 TODO.txt
@@ -4,7 +4,7 @@ DaemonKit TODO List
This is purely a drop in the bucket of what has to come...
* [DONE] Easy way to trap signals
-* Error handling to the degree Rails does
+* [IN PROGRESS] Error handling to the degree Rails does
* Easy configuration of an ORM of choice, including patching it if needed (ActiveRecord *cough*)
* Improved generators for creating skeleton daemons:
* [DONE] Jabber bot
@@ -14,6 +14,7 @@ This is purely a drop in the bucket of what has to come...
* Queue (SQS, AMQP, etc) pollers
* Rake tasks for generating:
* god configs
+ * Sys-V style init scripts
* Pre-built capistrano configs for easy deployment
* Support for dropping privileges
* Support for chroot'ing
View
4 app_generators/daemon_kit/templates/config/environment.rb
@@ -16,4 +16,8 @@
# Force the daemon to be killed after X seconds from asking it to
# config.force_kill_wait = 30
+
+ # Configure the safety net (see DaemonKit::Safety)
+ # config.safety_net.handler = :mail # (or :hoptoad )
+ # config.safety_net.mail.host = 'localhost'
end
View
3 lib/daemon_kit.rb
@@ -4,7 +4,7 @@
require 'rubygems'
module DaemonKit
- VERSION = '0.1.5.1'
+ VERSION = '0.1.6'
autoload :Initializer, 'daemon_kit/initializer'
autoload :Application, 'daemon_kit/application'
@@ -13,4 +13,5 @@ module DaemonKit
autoload :Jabber, 'daemon_kit/jabber'
autoload :AMQP, 'daemon_kit/amqp'
autoload :Nanite, 'daemon_kit/nanite'
+ autoload :Safety, 'daemon_kit/safety'
end
View
32 lib/daemon_kit/error_handlers/base.rb
@@ -0,0 +1,32 @@
+module DaemonKit
+ module ErrorHandlers
+ # Error handlers in DaemonKit are used by the #Safety class. Any
+ # error handler has to support the interface provided by this
+ # class. It's also required that safety handlers implement a
+ # singleton approach (handled by default by #Base).
+ class Base
+
+ class << self
+
+ @instance = nil
+
+ def instance
+ @instance ||= new
+ end
+ private :new
+
+ # When we're inherited, immediately register the handler with
+ # the safety net
+ def inherited( child ) #:nodoc:
+ Safety.register_error_handler( child )
+ end
+ end
+
+ # Error handlers should overwrite this method and implement
+ # their own reporting method.
+ def handle_exception( exception )
+ raise NoMethodError, "Error handler doesn't support #handle_exception"
+ end
+ end
+ end
+end
View
61 lib/daemon_kit/error_handlers/hoptoad.rb
@@ -0,0 +1,61 @@
+require 'net/http'
+
+module DaemonKit
+ module ErrorHandlers
+ # Error reporting via Hoptoad.
+ class Hoptoad < Base
+
+ # Your hoptoad API key
+ @api_key = nil
+ attr_accessor :api_key
+
+ def handle_exception( exception )
+ headers = {
+ 'Content-type' => 'application/x-yaml',
+ 'Accept' => 'text/xml, application/xml'
+ }
+
+ http = Net::HTTP.new( url.host, url.port )
+ data = clean_exception( exception )
+
+ response = begin
+ http.post( url.path, data.to_yaml, headers )
+ rescue TimoutError => e
+ DaemonKit.logger.error("Timeout while contacting the Hoptoad server.")
+ nil
+ end
+ case response
+ when Net::HTTPSuccess then
+ DaemonKit.logger.info "Hoptoad Success: #{response.class}"
+ else
+ DaemonKit.logger.error "Hoptoad Failure: #{response.class}\n#{response.body if response.respond_to? :body}"
+ end
+ end
+
+ def url
+ URI.parse("http://hoptoadapp.com/notices/")
+ end
+
+ def clean_exception( exception )
+ data = {
+ :api_key => self.api_key,
+ :error_class => exception.class.name,
+ :error_message => "#{exception.class.name}: #{exception.message}",
+ :backtrace => exception.backtrace,
+ :environment => ENV.to_hash,
+ :request => [],
+ :session => []
+ }
+
+ stringify_keys( data )
+ end
+
+ def stringify_keys(hash) #:nodoc:
+ hash.inject({}) do |h, pair|
+ h[pair.first.to_s] = pair.last.is_a?(Hash) ? stringify_keys(pair.last) : pair.last
+ h
+ end
+ end
+ end
+ end
+end
View
48 lib/daemon_kit/error_handlers/mail.rb
@@ -0,0 +1,48 @@
+require 'net/smtp'
+
+module DaemonKit
+ module ErrorHandlers
+ # Send an email notification of the exception via SMTP
+ class Mail < Base
+
+ # SMTP hostname
+ @host = 'localhost'
+ attr_accessor :host
+
+ # Recipients of the notification
+ @recipients = []
+ attr_accessor :recipients
+
+ # Subject prefix
+ @prefix = '[DAEMON-KIT]'
+ attr_accessor :prefix
+
+ # Sender address
+ @sender = 'daemon-kit'
+ attr_accessor :sender
+
+ def handle_exception( exception )
+ email = <<EOF
+To: #{self.recipients.map { |r| '<' + r + '>' }.join(', ')}
+From: <#{self.sender}>
+Subject: #{self.prefix} #{exception.message}
+Date: #{Time.now}
+
+DaemonKit caught an exception inside #{DaemonKit.configuration.daemon_name}.
+
+Message: #{exception.message}
+Backtrace:
+#{exception.backtrace.join("\n ")}
+
+Environment: #{ENV.inspect}
+EOF
+ begin
+ Net::SMTP.start( self.host ) do |smtp|
+ smtp.send_message( email, self.sender, self.recipients )
+ end
+ rescue
+ end
+ end
+ end
+ end
+end
View
8 lib/daemon_kit/initializer.rb
@@ -80,8 +80,9 @@ def before_daemonize
def after_daemonize
initialize_logger
initialize_signal_traps
- load_postdaemonize_configs
+
include_core_lib
+ load_postdaemonize_configs
end
def set_load_path
@@ -195,6 +196,9 @@ class Configuration
# Collection of signal traps
attr_reader :signal_traps
+ # Our safety net (#Safety) instance
+ attr_accessor :safety_net
+
def initialize
set_root_path!
set_daemon_defaults!
@@ -206,6 +210,8 @@ def initialize
self.multiple = false
self.force_kill_wait = false
+ self.safety_net = DaemonKit::Safety.instance
+
@signal_traps = {}
end
View
85 lib/daemon_kit/safety.rb
@@ -0,0 +1,85 @@
+module DaemonKit
+ # Provides a wrapper for running code inside a 'safety net' Any
+ # exceptions raised inside a safety net is handled and reported via
+ # loggers, email or Hoptoad.
+ #
+ # The safety net can be configured via DaemonKit.config.safety,
+ # which holds the only instance of the safety net.
+ class Safety
+
+ # Who get's notified.
+ @handler = nil
+ attr_accessor :handler
+
+ # Registered error handlers
+ @error_handlers = {}
+ attr_reader :error_handlers
+
+ class << self
+
+ # Singleton
+ @instance = nil
+
+ def instance
+ @instance ||= new
+ end
+ private :new
+
+ # Run the provided block inside a safety net.
+ def run(&block)
+ self.instance.run(&block)
+ end
+
+ def register_error_handler( klass )
+ name = klass.to_s.split('::').last.downcase
+
+ DaemonKit.logger.debug( "Registering error handler '#{name}' (#{klass})" ) if DaemonKit.logger
+
+ instance.instance_eval( <<-EOF, __FILE__, __LINE__ )
+ def #{name}
+ @#{name} ||= #{klass}.instance
+ end
+ EOF
+ end
+ end
+
+ # Run the provided block inside a safety net.
+ def run(&block)
+ begin
+ block.call
+ rescue => e
+ # Log
+ DaemonKit.logger.fatal "Safety net caught exception: #{e.message}"
+ DaemonKit.logger.fatal "Backtrace: #{e.backtrace.join("\n ")}"
+
+ get_handler.handle_exception( e ) if get_handler
+ end
+ end
+
+ def get_handler
+ if @handler && self.respond_to?( @handler )
+ h = send( @handler )
+ return h if h.class.ancestors.include?( DaemonKit::ErrorHandlers::Base )
+ end
+
+ return nil
+ end
+ end
+end
+
+class Object
+ class << self
+ def safely(&block)
+ DaemonKit::Safety.run(&block)
+ end
+ end
+
+ def safely(&block)
+ DaemonKit::Safety.run(&block)
+ end
+end
+
+# Load our error handlers
+require 'daemon_kit/error_handlers/base'
+require 'daemon_kit/error_handlers/mail'
+require 'daemon_kit/error_handlers/hoptoad'

0 comments on commit 81693c8

Please sign in to comment.