Parent

Rack::Mole

Public Class Methods

new( app, opts={} ) click to toggle source

Initialize The Mole rack component. It is recommended that you specify at a minimum a user_key to track interactions on a per user basis. If you wish to use the mole for the same application in different environments you should set the environment attribute RAILS_ENV for example. The perf_threshold setting is also recommended to get performance notifications should your web app start sucking. By default your app will be moleable upon installation and you should see mole features spewing out in your logs. This is the default setting. Alternatively you can store mole information in a mongo database by specifying a different store option.

Options

:config_file

This option will load rackamole options from a file versus individual options. You can leverage yaml and erb with the current rackamole context to specify each of the following options but within a yaml file. This gives more flexibility to customize the rack component for specific environment. You can specify the :environment option or the default will be development.

: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 alert 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
 Or you can eval it on the fly - though this will be much slower and not recommended
   :user_key => { :session_key => :user_id, :extractor => lambda{ |id| User.find( id ).name} }

:mole_excludes :: Exclude some of the parameters that the mole would normally include. The list of

                  customizable parameters is: software, ip, browser type, url, path, status, headers and body.
                  This options takes an array of symbols. Defaults to [:body].
:perf_excludes

Specifies a set of paths that will be excluded when a performance issue is detected. This is useful when certain requests are known to be slow. When a match is found an alert won’t be issued but the context will still be moled.

Don’t send an alert when perf is found on /blee/fred. Don’t alert on /duh unless the request took longer than 10 secs

  :perf_excludes => [ {:context => '/blee/fred}, {:context => /^\/duh.*/, :threshold => 10 } ]

:excluded_paths:: Exclude paths that you do not wish to mole by specifying an array of regular expresssions.

:param_excludes

Exempt certain sensitive request parameters from being logged to the mole. Specify an array of keys as symbols ie [:password, :card_number].

:session_excludes

Exempt session params from being logged by the mole. Specify an array of keys as symbols

                  ie [:fred, :blee] to exclude session[:fred] and session[:blee] from being stored.
:twitter

Set this option to have the mole twitt certain alerts. You must configure your twitter auth via the :username and :password keys and :alert_on with an array of mole types you wish to be notified on.

:twitter => { :username => ‘fred’, :password => ‘blee’, :alert_on => [Rackamole.perf, Rackamole.fault] }

==== BOZO! currently there is not support for throttling or monitoring these alerts.

:email :: The mole can be configured to send out emails bases on interesting mole features.

                 This feature uses Pony to send out the email. You must specify a hash with the following keys :from, :to 
                 and :alert_on options to indicate which mole type your wish to be alerted on. 
                 See Pony docs for custom options for your emails

:email => { :from => ‘fred@acme.com’, :to => [‘blee@acme.com’, ‘doh@acme.com’], :alert_on => [Rackamole.perf, Rackamole.fault] }

:growl :: You can set up rackamole to send growl notifications when certain features

                 are encounters in the same way email and twitt alerts works. The :to options
                 describes a collection of hash values with the ip and optional password to the recipients.

:growl => { :to => [{ :ip => ‘1.1.1.1’ }], :alert_on => [Rackamole.perf, Rackamole.fault] }

    # File lib/rackamole/mole.rb, line 85
85:     def initialize( app, opts={} )    
86:       @app       = app
87:       init_options( opts )
88:       validate_options
89:       @logger = Rackamole::Logger.new( :logger_name => 'RACKAMOLE', :log_level => options[:log_level] )
90:     end

Public Instance Methods

call( env ) click to toggle source

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.

     # File lib/rackamole/mole.rb, line 95
 95:     def call( env )      
 96:       # Bail if application is not moleable
 97:       return @app.call( env ) unless moleable?
 98:                 
 99:       @stash = env['mole.stash'] if env['mole.stash']      
100:       @stash = Rackamole::Stash::Collector.new( options[:app_name], options[:environment], options[:expiration] ) unless stash
101:             
102:       status, headers, body = nil
103:       elapsed = Hitimes::Interval.measure do
104:         begin
105:           status, headers, body = @app.call( env )
106:         rescue => boom    
107:           env['mole.exception'] = boom
108:           mole_feature( env, elapsed, status, headers, body )
109:           raise boom
110:         end
111:       end      
112:       mole_feature( env, elapsed, status, headers, body )
113:       return status, headers, body
114:     end

Private Instance Methods

alertable?( filter, type ) click to toggle source

Check if feature should be send to alert clients ie email or twitter

     # File lib/rackamole/mole.rb, line 264
264:     def alertable?( filter, type )
265:       return false unless configured?( filter, [:alert_on] )
266:       return false unless options[filter][:alert_on]
267:       options[filter][:alert_on].include?( type )
268:     end
configured?( key, configs, optional=true ) click to toggle source

Check if an options is set and configured

     # File lib/rackamole/mole.rb, line 251
251:     def configured?( key, configs, optional=true )
252:       return false if optional and !options.has_key?(key)
253:       raise "Missing option key :#{key}" unless options.has_key?(key)
254:       configs.each do |c|
255:         raise "Invalid value for option :#{key}. Expecting a hash with symbols [#{configs.join(',')}]" unless options[key].respond_to? :key?
256:         unless options[key].key?(c)
257:           raise "Option :#{key} is not properly configured. Missing #{c.inspect} in [#{options[key].keys.sort{|a,b| a.to_s <=> b.to_s}.join(',')}]"
258:         end
259:       end
260:       true
261:     end
default_options() click to toggle source

Mole default options

     # File lib/rackamole/mole.rb, line 136
136:     def default_options
137:       { 
138:         :moleable        =>  true,    
139:         :log_level       =>  :info,
140:         :expiration      =>  60*60, # 1 hour
141:         :environment     =>  'development',
142:         :excluded_paths  =>  [/.*?\/stylesheets\/.*/, /.*?\/javascripts\/.*/,/\.*?\/images\/.*/ ],
143:         :mole_excludes   =>  [:body],
144:         :perf_threshold  =>  10.0,
145:         :store           =>  Rackamole::Store::Log.new
146:       }
147:     end
duplicated?( env, attrs ) click to toggle source

Check if we’ve already seen such an error

     # File lib/rackamole/mole.rb, line 201
201:     def duplicated?( env, attrs )
202:       # Skip features for now...
203:       return true if attrs[:type] == Rackamole.feature
204:       
205:       # Don't bother if expiration is set to never. ie fire alerts all the time
206:       return false if options[:expiration] == Rackamole::Stash::Collector::NEVER
207:       
208:       now    = Time.now
209:       app_id = [attrs[:app_name], attrs[:environment]].join( '_' )
210:       path   = attrs[:route_info] ? "#{attrs[:route_info][:controller]}#{attrs[:route_info][:action]}" : attrs[:path]      
211:                         
212:       # Check expired entries
213:       stash.expire!
214:       
215:       # check if we've seen this error before. If so stash it.
216:       if attrs[:type] == Rackamole.fault
217:         return stash.stash_fault( path, attrs[:stack].first, now.utc )
218:       end
219:       
220:       # Check if we've seen this perf issue before. If so stash it
221:       if attrs[:type] == Rackamole.perf
222:         # Don't send alert if exempted
223:         return true if perf_exempt?( attrs[:path], attrs[:request_time])
224:         return stash.stash_perf( path, attrs[:request_time], now.utc )
225:       end      
226:     end
filter_params( source, excludes, nest=false ) click to toggle source

filters out params hash and convert values to json

     # File lib/rackamole/mole.rb, line 387
387:     def filter_params( source, excludes, nest=false )
388:        results = nest ? {} : BSON::OrderedHash.new
389:        source.keys.sort{ |a,b| a.to_s <=> b.to_s }.each do |k|
390:          unless excludes.include? k.to_sym
391:            results[k.to_sym] = if source[k].is_a?(Hash)
392:                          filter_params( source[k], excludes, true)
393:                        else
394:                          source[k]
395:                        end
396:            results[k.to_sym] = results[k.to_sym].to_json unless nest
397:          end
398:        end
399:        results
400:     end
get_route( request ) click to toggle source

Fetch route info if any...

     # File lib/rackamole/mole.rb, line 418
418:     def get_route( request )
419:       return nil unless defined?( RAILS_ENV )
420:       # Check for invalid route exception...
421:       begin
422:         return ::ActionController::Routing::Routes.recognize_path( request.path, {:method => request.request_method.downcase.to_sym } )
423:       rescue => boom      
424:         return nil
425:       end
426:     end
identify( request_env ) click to toggle source

Identify request ie ip and browser configuration

     # File lib/rackamole/mole.rb, line 408
408:     def identify( request_env )
409:       return request_env['HTTP_X_FORWARDED_FOR'] || request_env['REMOTE_ADDR'], request_env['HTTP_USER_AGENT']
410:     end
init_options( opts ) click to toggle source

Load up configuration options

     # File lib/rackamole/mole.rb, line 122
122:     def init_options( opts )
123:       if opts[:config_file] && (env = opts[:environment] || "development")
124:         raise "Unable to find rackamole config file #{opts[:config_file]}" unless ::File.exists?( opts[:config_file] )
125:         begin
126:           opts = YAML.load( ERB.new( IO.read( opts[:config_file] ) ).result( binding ) )[env]
127:           opts[:environment] = env 
128:         rescue => boom
129:           raise "Unable to parse Rackamole config file #{boom}"
130:         end        
131:       end      
132:       @options = default_options.merge( opts )      
133:     end
mole?( key, stash, value ) click to toggle source

check exclusion to see if the information should be moled

     # File lib/rackamole/mole.rb, line 381
381:     def mole?( key, stash, value )
382:       return stash[key] = value if !options[:mole_excludes] or options[:mole_excludes].empty?
383:       stash[key] = (options[:mole_excludes].include?( key ) ? nil : value)
384:     end
mole_feature( env, elapsed, status, headers, body ) click to toggle source

Send moled info to store and potentially send out alerts...

     # File lib/rackamole/mole.rb, line 164
164:     def mole_feature( env, elapsed, status, headers, body )   
165:       env['mole.stash'] = stash
166:       
167:       attrs = mole_info( env, elapsed, status, headers, body )
168: 
169:       # If nothing to mole bail out!
170:       return if attrs.empty?
171: 
172:       # send info to configured store
173:       options[:store].mole( attrs )
174:       
175:       # Check for dups. If we've reported this req before don't report it again...
176:       unless duplicated?( env, attrs )
177:         # send email alert ?
178:         if alertable?( :email, attrs[:type] )
179:           logger.debug ">>> Sending out email on mole type #{attrs[:type]} from #{options[:email][:from]} to #{options[:email][:to].join( ", ")}"
180:           Rackamole::Alert::Emole.deliver_alert( logger, options, attrs ) 
181:         end
182: 
183:         # send growl alert ?
184:         if alertable?( :growl, attrs[:type] )
185:           logger.debug ">>> Sending out growl on mole type #{attrs[:type]} to @#{options[:growl][:to].inspect}"
186:           Rackamole::Alert::Growl.deliver_alert( logger, options, attrs )
187:         end
188:       
189:         # send twitter alert ?
190:         if alertable?( :twitter, attrs[:type] )
191:           logger.debug ">>> Sending out twitt on mole type #{attrs[:type]} on @#{options[:twitter][:username]}"
192:           Rackamole::Alert::Twitt.deliver_alert( logger, options, attrs )
193:         end
194:       end
195:     rescue => boom
196:       logger.error "!! MOLE RECORDING CRAPPED OUT !! -- #{boom}"
197:       boom.backtrace.each { |l| logger.error l }
198:     end
mole_info( env, elapsed, status, headers, body ) click to toggle source

Extract interesting information from the request

     # File lib/rackamole/mole.rb, line 285
285:     def mole_info( env, elapsed, status, headers, body )
286:       request  = Rack::Request.new( env )
287:       response = nil
288:       begin
289:         response = Rack::Response.new( body, status, headers )
290:       rescue
291:         ;
292:       end
293:         
294:       info     = BSON::OrderedHash.new
295:                            
296:       return info unless mole_request?( request )
297:                                                 
298:       session     = env['rack.session']
299:       route       = get_route( request )
300: 
301:       ip, user_agent = identify( env )
302:       user_id        = nil
303:       user_name      = nil
304:            
305:       # BOZO !! This could be slow if have to query db to get user name...
306:       # Preferred store username in session and give at key
307:       user_key = options[:user_key]
308:       if session and user_key
309:         if user_key.instance_of? Hash
310:           user_id  = session[ user_key[:session_key] ]
311:           if user_key[:extractor]
312:             user_name = user_key[:extractor].call( user_id )
313:           end
314:         else
315:           user_name = session[user_key]
316:         end
317:       end
318:           
319:       info[:type]         = (elapsed and elapsed > options[:perf_threshold] ? Rackamole.perf : Rackamole.feature)
320:       info[:app_name]     = options[:app_name]
321:       info[:environment]  = options[:environment] || "Unknown"
322:       info[:user_id]      = user_id if user_id
323:       info[:user_name]    = user_name || "Unknown"
324:       info[:host]         = env['SERVER_NAME']
325:       info[:request_time] = elapsed if elapsed
326:       info[:method]       = env['REQUEST_METHOD']
327:       info[:route_info]   = route if route      
328:       info[:created_at]   = Time.now.utc
329:             
330:       # Optional elements...
331:       mole?( :software  , info, env['SERVER_SOFTWARE'] )
332:       mole?( :ip        , info, ip )
333:       mole?( :url       , info, request.url )
334:       mole?( :path      , info, request.path )
335:       mole?( :status    , info, status )
336:       mole?( :headers   , info, headers )
337:       mole?( :body      , info, response.body ) if response
338:       
339:       # Gather up browser and client machine info
340:       agent_info = Rackamole::Utils::AgentDetect.parse( user_agent )
341:       %(browser machine).each { |type| mole?(type.to_sym, info, agent_info[type.to_sym] ) }
342: 
343:       # Dump request params
344:       unless request.params.empty?
345:         info[:params] = filter_params( request.params, options[:param_excludes] || [] )
346:       end
347:       if route
348:         info[:params] ||= {}
349:         info[:params].merge!( filter_params( params_from_route( route ), options[:param_excludes] || [] ) )
350:       end
351:             
352:       # Dump session var
353:       if session and !session.empty?
354:         info[:session] = filter_params( session, options[:session_excludes] || [] )
355:       end
356:       
357:       # Check if an exception was raised. If so consume it and clear state
358:       exception = env['mole.exception']
359:       if exception
360:         info[:ruby_version]   = %[ruby -v]
361:         info[:fault]          = exception.to_s
362:         info[:stack]          = trim_stack( exception )
363:         info[:type]           = Rackamole.fault
364:         env['mole.exception'] = nil
365:       end      
366:       info
367:     end
mole_request?( request ) click to toggle source

Check if this request should be moled according to the exclude filters

     # File lib/rackamole/mole.rb, line 271
271:     def mole_request?( request )
272:       if options.has_key?( :excluded_paths ) and options[:excluded_paths]
273:         options[:excluded_paths].each do |exclude_path|
274:           exclude_path = Regexp.new( exclude_path ) if exclude_path.is_a?( String )
275:           if request.path =~ exclude_path
276:             logger.debug ">>> Excluded request -- #{request.path} using exclude flag #{exclude_path}"
277:             return false 
278:           end
279:         end
280:       end
281:       true
282:     end
moleable?() click to toggle source

Checks if this application is moleable

     # File lib/rackamole/mole.rb, line 413
413:     def moleable?
414:       options[:moleable]
415:     end
option_defined?( key ) click to toggle source

Check if an option is defined

     # File lib/rackamole/mole.rb, line 246
246:     def option_defined?( key )
247:       options.has_key?(key) and options[key]
248:     end
params_from_route( route ) click to toggle source

Parse out rails request params if in rails env

     # File lib/rackamole/mole.rb, line 370
370:     def params_from_route( route )
371:       params = {}
372:       except = [:controller, :action]
373:       route.each_pair do |k,v|
374:         next if except.include?( k )
375:         params[k] = v
376:       end
377:       params
378:     end
perf_exempt?( path, req_time ) click to toggle source

Check if request should be exempted from perf alert

     # File lib/rackamole/mole.rb, line 229
229:     def perf_exempt?( path, req_time )
230:       return false unless option_defined?( :perf_excludes )
231:       options[:perf_excludes].each do |hash|
232:         context    = hash[:context]
233:         threshold  = hash[:threshold]
234:         time_trip  = (threshold ? req_time <= threshold : true)
235:         
236:         if context.is_a? String
237:           return true if path == context and time_trip
238:         elsif context.is_a? Regexp
239:           return true if path.match(context) and time_trip
240:         end
241:       end
242:       false
243:     end
trim_stack( boom ) click to toggle source

Trim stack trace

     # File lib/rackamole/mole.rb, line 403
403:     def trim_stack( boom )
404:       boom.backtrace[0...4]
405:     end
validate_options() click to toggle source

Validates all configured options... Throws error if invalid configuration

     # File lib/rackamole/mole.rb, line 150
150:     def validate_options
151:       return unless options[:moleable]
152:       
153:       ]app_name environment perf_threshold store].each do |k|
154:         raise "[M()le] -- Unable to locate required option key `#{k}" unless options[k.to_sym]
155:       end
156:       
157:       # Barf early if something is wrong in the configuration
158:       configured?( :twitter, [:username, :password, :alert_on], true )
159:       configured?( :email  , [:from, :to, :alert_on], true )
160:       configured?( :growl  , [:to, :alert_on], true )      
161:     end

Disabled; run with --debug to generate this.

[Validate]

Generated with the Darkfish Rdoc Generator 1.1.6.