lib/rackamole/mole.rb in rackamole-0.0.9 vs lib/rackamole/mole.rb in rackamole-0.1.0
- old
+ new
@@ -15,14 +15,22 @@
# specifying a different store option.
#
# === Options
#
# :app_name :: The name of the application (Default: Moled App)
+ # :log_level :: Rackamole logger level. (Default: info )
# :environment :: The environment for the application ie :environment => RAILS_ENV
# :perf_threshold :: Any request taking longer than this value will get moled. Default: 10secs
# :moleable :: Enable/Disable the MOle (Default:true)
# :store :: The storage instance ie log file or mongodb [Default:stdout]
+ # :expiration :: Number of seconds to expiration. The mole will not keep sending alert if a particular
+ # mole type has been reported in the past. This threshold specifies the limit at which
+ # the previously sent alerts will expire and thus will be sent again.
+ # For instance, it might be the case that the app is consistently slow for a particular action.
+ # On the first encounter an alert will be sent ( if configured ). Any subsequent requests for this action
+ # will not fire an alert until the expiration threshold is hit. The default is 1 hour.
+ # Setting this threshold to Rackamole::Stash::Collector::NEVER will result in alerts being fired continually.
# :user_key :: If sessions are enable, this represents the session key for the user name or
# user_id.
# ==
# If the username resides in the session hash with key :user_name the you can use:
# :user_key => :user_name
@@ -44,22 +52,26 @@
# and :alert_on options to indicate which mole type your wish to be alerted on.
# ==
# :email => { :from => 'fred@acme.com', :to => ['blee@acme.com', 'doh@acme.com'], :alert_on => [Rackamole.perf, Rackamole.fault] }
# ==
#
- def initialize( app, opts={} )
- @app = app
+ def initialize( app, opts={} )
+ @app = app
init_options( opts )
validate_options
+ @logger = Rackamole::Logger.new( :logger_name => 'RACKAMOLE', :log_level => options[:log_level] )
end
# Entering the MOle zone...
# Watches incoming requests and report usage information. The mole will also track request that
# are taking longer than expected and also report any requests that are raising exceptions.
- def call( env )
+ def call( env )
# Bail if application is not moleable
return @app.call( env ) unless moleable?
+
+ @stash = env['mole.stash'] if env['mole.stash']
+ @stash = Rackamole::Stash::Collector.new( options[:app_name], options[:environment], options[:expiration] ) unless stash
status, headers, body = nil
elapsed = Hitimes::Interval.measure do
begin
status, headers, body = @app.call( env )
@@ -74,21 +86,23 @@
end
# ===========================================================================
private
- attr_reader :options #:nodoc:
+ attr_reader :options, :logger, :stash #:nodoc:
# Load up configuration options
def init_options( opts )
@options = default_options.merge( opts )
end
# Mole default options
def default_options
{
- :moleable => true,
+ :moleable => true,
+ :log_level => :info,
+ :expiration => 60*60, # 1 hour
:app_name => "Moled App",
:environment => 'test',
:excluded_paths => [/.?\.ico/, /.?\.png/],
:perf_threshold => 10.0,
:store => Rackamole::Store::Log.new
@@ -107,28 +121,64 @@
configured?( :twitter, [:username, :password, :alert_on], true )
configured?( :email , [:from, :to, :alert_on], true )
end
# Send moled info to store and potentially send out alerts...
- def mole_feature( env, elapsed, status, headers, body )
- attrs = mole_info( env, elapsed, status, headers, body )
+ def mole_feature( env, elapsed, status, headers, body )
+ env['mole.stash'] = stash
+ attrs = mole_info( env, elapsed, status, headers, body )
+
+ # If nothing to mole bail out!
+ return if attrs.empty?
+
# send info to configured store
options[:store].mole( attrs )
- # send email alert ?
- if alertable?( :email, attrs[:type] )
- Rackamole::Alert::Emole.deliver_alert( options[:email][:from], options[:email][:to], attrs )
+ # Check for dups. If we've logged this req before don't log it again...
+ unless duplicated?( env, attrs )
+ # send email alert ?
+ if alertable?( :email, attrs[:type] )
+ logger.debug ">>> Sending out email on mole type #{attrs[:type]} to #{options[:email][:to].join( ", ")}"
+ Rackamole::Alert::Emole.deliver_alert( options[:email][:from], options[:email][:to], attrs )
+ end
+
+ # send twitter alert ?
+ if alertable?( :twitter, attrs[:type] )
+ logger.debug ">>> Sending out twitt on mole type #{attrs[:type]} on @#{options[:twitter][:username]}"
+ Rackamole::Alert::Twitt.deliver_alert( options[:twitter][:username], options[:twitter][:password], attrs )
+ end
end
+ rescue => boom
+ logger.error "!! MOLE RECORDING CRAPPED OUT !! -- #{boom}"
+ boom.backtrace.each { |l| logger.error l }
+ end
+
+ # Check if we've already seen such an error
+ def duplicated?( env, attrs )
+ # Skip features for now...
+ return true if attrs[:type] == Rackamole.feature
- # send twitter alert ?
- if alertable?( :twitter, attrs[:type] )
- twitt.send_alert( attrs )
+ # Don't bother if expiration is set to never. ie fire alerts all the time
+ return false if options[:expiration] == Rackamole::Stash::Collector::NEVER
+
+ now = Time.now
+ app_id = [attrs[:app_name], attrs[:environment]].join( '_' )
+ path = attrs[:route_info] ? "#{attrs[:route_info][:controller]}#{attrs[:route_info][:action]}" : attrs[:path]
+
+ # Check expired entries
+ stash.expire!
+
+ # check if we've seen this error before. If so stash it.
+ if attrs[:type] == Rackamole.fault
+ return stash.stash_fault( path, attrs[:stack].first, now.utc )
+ end
+
+ # Check if we've seen this perf issue before. If so stash it
+ if attrs[:type] == Rackamole.perf
+ return stash.stash_perf( path, attrs[:request_time], now.utc )
end
- rescue => boom
- $stderr.puts "!! MOLE RECORDING CRAPPED OUT !! -- #{boom}"
- boom.backtrace.each { |l| $stderr.puts l }
end
# Check if an options is set and configured
def configured?( key, configs, optional=true )
return false if optional and !options.has_key?(key)
@@ -146,16 +196,11 @@
def alertable?( filter, type )
return false unless configured?( filter, [:alert_on] )
return false unless options[filter][:alert_on]
options[filter][:alert_on].include?( type )
end
-
- # Create or retrieve twitter client
- def twitt
- @twitt ||= Rackamole::Alert::Twitt.new( options[:twitter][:username], options[:twitter][:password] )
- end
-
+
# Check if this request should be moled according to the exclude filters
def mole_request?( request )
options[:excluded_paths].each do |exclude_path|
return false if request.path.match( exclude_path )
end
@@ -164,13 +209,11 @@
# Extract interesting information from the request
def mole_info( env, elapsed, status, headers, body )
request = Rack::Request.new( env )
info = OrderedHash.new
-
- # dump( env )
-
+
return info unless mole_request?( request )
session = env['rack.session']
route = get_route( request )
@@ -204,10 +247,11 @@
info[:request_time] = elapsed if elapsed
info[:url] = request.url
info[:method] = env['REQUEST_METHOD']
info[:path] = request.path
info[:route_info] = route if route
+ info[:created_at] = Time.now.utc
# Dump request params
unless request.params.empty?
info[:params] = OrderedHash.new
request.params.keys.sort.each { |k| info[:params][k.to_sym] = request.params[k].to_json }
@@ -259,30 +303,29 @@
end
# Fetch route info if any...
def get_route( request )
return nil unless defined?( RAILS_ENV )
-
# Check for invalid route exception...
begin
return ::ActionController::Routing::Routes.recognize_path( request.path, {:method => request.request_method.downcase.to_sym } )
- rescue
+ rescue => boom
return nil
end
end
- # Dump env to stdout
- # def dump( env, level=0 )
- # env.keys.sort{ |a,b| a.to_s <=> b.to_s }.each do |k|
- # value = env[k]
- # if value.respond_to?(:each_pair)
- # puts "%s %-#{40-level}s" % [' '*level,k]
- # dump( env[k], level+1 )
- # elsif value.instance_of?(::ActionController::Request) or value.instance_of?(::ActionController::Response)
- # puts "%s %-#{40-level}s %s" % [ ' '*level, k, value.class ]
- # else
- # puts "%s %-#{40-level}s %s" % [ ' '*level, k, value.inspect ]
- # end
- # end
- # end
+ # Debug - Dump env to stdout
+ def dump( env, level=0 )
+ env.keys.sort{ |a,b| a.to_s <=> b.to_s }.each do |k|
+ value = env[k]
+ if value.respond_to?(:each_pair)
+ puts "%s %-#{40-level}s" % [' '*level,k]
+ dump( env[k], level+1 )
+ elsif value.instance_of?(::ActionController::Request) or value.instance_of?(::ActionController::Response)
+ puts "%s %-#{40-level}s %s" % [ ' '*level, k, value.class ]
+ else
+ puts "%s %-#{40-level}s %s" % [ ' '*level, k, value.inspect ]
+ end
+ end
+ end
end
end
\ No newline at end of file