Skip to content

Commit

Permalink
0.1.6 - The safety nets
Browse files Browse the repository at this point in the history
  • Loading branch information
kennethkalmer committed May 13, 2009
1 parent a8faf8d commit 81693c8
Show file tree
Hide file tree
Showing 10 changed files with 254 additions and 3 deletions.
9 changes: 9 additions & 0 deletions History.txt
Original file line number Diff line number Diff line change
@@ -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,
Expand Down
4 changes: 4 additions & 0 deletions Manifest.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion TODO.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
4 changes: 4 additions & 0 deletions app_generators/daemon_kit/templates/config/environment.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
3 changes: 2 additions & 1 deletion lib/daemon_kit.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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
32 changes: 32 additions & 0 deletions lib/daemon_kit/error_handlers/base.rb
Original file line number Diff line number Diff line change
@@ -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
61 changes: 61 additions & 0 deletions lib/daemon_kit/error_handlers/hoptoad.rb
Original file line number Diff line number Diff line change
@@ -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
48 changes: 48 additions & 0 deletions lib/daemon_kit/error_handlers/mail.rb
Original file line number Diff line number Diff line change
@@ -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
8 changes: 7 additions & 1 deletion lib/daemon_kit/initializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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!
Expand All @@ -206,6 +210,8 @@ def initialize
self.multiple = false
self.force_kill_wait = false

self.safety_net = DaemonKit::Safety.instance

@signal_traps = {}
end

Expand Down
85 changes: 85 additions & 0 deletions lib/daemon_kit/safety.rb
Original file line number Diff line number Diff line change
@@ -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.