Object
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.
| :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. |
: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} }
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. |
:perf_excludes => [ {:context => '/blee/fred}, {:context => /^\/duh.*/, :threshold => 10 } ]
| :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.
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. |
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
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.
# 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
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
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
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
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
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
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
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 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
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
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
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
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
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
Checks if this application is moleable
# File lib/rackamole/mole.rb, line 413
413: def moleable?
414: options[:moleable]
415: end
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
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
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 trace
# File lib/rackamole/mole.rb, line 403
403: def trim_stack( boom )
404: boom.backtrace[0...4]
405: end
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.
Generated with the Darkfish Rdoc Generator 1.1.6.