derailed-mole 1.0.10
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +17 -0
- data/Manifest.txt +137 -0
- data/README.txt +216 -0
- data/Rakefile +46 -0
- data/bin/molify +64 -0
- data/config/database.yml +21 -0
- data/config/test_database.yml +69 -0
- data/lib/mole.rb +260 -0
- data/lib/mole/db/migrate.rb +90 -0
- data/lib/mole/e_mole.rb +75 -0
- data/lib/mole/e_mole_helper.rb +2 -0
- data/lib/mole/logger.rb +134 -0
- data/lib/mole/models/mole_feature.rb +58 -0
- data/lib/mole/models/mole_log.rb +31 -0
- data/lib/mole/module.rb +292 -0
- data/lib/mole/moler.rb +71 -0
- data/lib/mole/utils/frameworks.rb +53 -0
- data/lib/mole/version.rb +15 -0
- data/mole.gemspec +37 -0
- data/samples/merbapp/README +14 -0
- data/samples/merbapp/Rakefile +124 -0
- data/samples/merbapp/app/controllers/application.rb +3 -0
- data/samples/merbapp/app/controllers/exceptions.rb +13 -0
- data/samples/merbapp/app/controllers/moled.rb +25 -0
- data/samples/merbapp/app/helpers/global_helper.rb +5 -0
- data/samples/merbapp/app/mailers/views/layout/application.html.erb +1 -0
- data/samples/merbapp/app/mailers/views/layout/application.text.erb +1 -0
- data/samples/merbapp/app/parts/views/layout/application.html.erb +1 -0
- data/samples/merbapp/app/views/exceptions/internal_server_error.html.erb +216 -0
- data/samples/merbapp/app/views/exceptions/not_acceptable.html.erb +38 -0
- data/samples/merbapp/app/views/exceptions/not_found.html.erb +40 -0
- data/samples/merbapp/app/views/layout/application.html.erb +11 -0
- data/samples/merbapp/app/views/moled/index.html.erb +5 -0
- data/samples/merbapp/app/views/moled/result.html.erb +5 -0
- data/samples/merbapp/config/boot.rb +11 -0
- data/samples/merbapp/config/dependencies.rb +41 -0
- data/samples/merbapp/config/environments/development.rb +1 -0
- data/samples/merbapp/config/environments/production.rb +1 -0
- data/samples/merbapp/config/environments/test.rb +1 -0
- data/samples/merbapp/config/merb.yml +82 -0
- data/samples/merbapp/config/merb_init.rb +26 -0
- data/samples/merbapp/config/mole_config.rb +33 -0
- data/samples/merbapp/config/router.rb +38 -0
- data/samples/merbapp/config/upload.conf +0 -0
- data/samples/merbapp/public/images/merb.jpg +0 -0
- data/samples/merbapp/public/merb.fcgi +6 -0
- data/samples/merbapp/public/stylesheets/master.css +119 -0
- data/samples/merbapp/script/destroy +32 -0
- data/samples/merbapp/script/generate +32 -0
- data/samples/merbapp/script/stop_merb +13 -0
- data/samples/merbapp/spec/spec_helper.rb +15 -0
- data/samples/merbapp/test/test_helper.rb +14 -0
- data/samples/railsapp/README +14 -0
- data/samples/railsapp/Rakefile +10 -0
- data/samples/railsapp/app/controllers/application.rb +13 -0
- data/samples/railsapp/app/controllers/moled_controller.rb +24 -0
- data/samples/railsapp/app/helpers/application_helper.rb +3 -0
- data/samples/railsapp/app/views/moled/index.html.erb +5 -0
- data/samples/railsapp/app/views/moled/result.html.erb +5 -0
- data/samples/railsapp/config/boot.rb +109 -0
- data/samples/railsapp/config/database.yml +13 -0
- data/samples/railsapp/config/environment.rb +59 -0
- data/samples/railsapp/config/environments/development.rb +18 -0
- data/samples/railsapp/config/environments/production.rb +20 -0
- data/samples/railsapp/config/environments/test.rb +22 -0
- data/samples/railsapp/config/initializers/inflections.rb +10 -0
- data/samples/railsapp/config/initializers/mime_types.rb +5 -0
- data/samples/railsapp/config/initializers/mole.rb +10 -0
- data/samples/railsapp/config/moles/mole_config.rb +44 -0
- data/samples/railsapp/config/routes.rb +35 -0
- data/samples/railsapp/doc/README_FOR_APP +2 -0
- data/samples/railsapp/public/.htaccess +40 -0
- data/samples/railsapp/public/404.html +30 -0
- data/samples/railsapp/public/422.html +30 -0
- data/samples/railsapp/public/500.html +30 -0
- data/samples/railsapp/public/dispatch.cgi +10 -0
- data/samples/railsapp/public/dispatch.fcgi +24 -0
- data/samples/railsapp/public/dispatch.rb +10 -0
- data/samples/railsapp/public/favicon.ico +0 -0
- data/samples/railsapp/public/images/rails.png +0 -0
- data/samples/railsapp/public/javascripts/application.js +2 -0
- data/samples/railsapp/public/javascripts/controls.js +963 -0
- data/samples/railsapp/public/javascripts/dragdrop.js +972 -0
- data/samples/railsapp/public/javascripts/effects.js +1120 -0
- data/samples/railsapp/public/javascripts/prototype.js +4225 -0
- data/samples/railsapp/public/robots.txt +5 -0
- data/samples/railsapp/script/about +3 -0
- data/samples/railsapp/script/console +3 -0
- data/samples/railsapp/script/destroy +3 -0
- data/samples/railsapp/script/generate +3 -0
- data/samples/railsapp/script/performance/benchmarker +3 -0
- data/samples/railsapp/script/performance/profiler +3 -0
- data/samples/railsapp/script/performance/request +3 -0
- data/samples/railsapp/script/plugin +3 -0
- data/samples/railsapp/script/process/inspector +3 -0
- data/samples/railsapp/script/process/reaper +3 -0
- data/samples/railsapp/script/process/spawner +3 -0
- data/samples/railsapp/script/runner +3 -0
- data/samples/railsapp/script/server +3 -0
- data/samples/railsapp/test/test_helper.rb +38 -0
- data/samples/rubyapp/README +22 -0
- data/samples/rubyapp/bin/ruby_app +35 -0
- data/samples/rubyapp/config/mole_conf.rb +31 -0
- data/samples/rubyapp/lib/fred.rb +22 -0
- data/spec/config/auto_mole_config.rb +26 -0
- data/spec/config/mole_config.rb +0 -0
- data/spec/config/moles/fred_config.rb +0 -0
- data/spec/data/blee.rb +64 -0
- data/spec/db/migrate_spec.rb +19 -0
- data/spec/emole_spec.rb +43 -0
- data/spec/logger_spec.rb +56 -0
- data/spec/models/mole_feature_spec.rb +48 -0
- data/spec/models/mole_log_spec.rb +62 -0
- data/spec/module_spec.rb +229 -0
- data/spec/mole_spec.rb +135 -0
- data/spec/moler_spec.rb +77 -0
- data/spec/spec_helper.rb +76 -0
- data/spec/utils/framework_spec.rb +99 -0
- data/tasks/ann.rake +76 -0
- data/tasks/annotations.rake +22 -0
- data/tasks/doc.rake +48 -0
- data/tasks/gem.rake +110 -0
- data/tasks/manifest.rake +49 -0
- data/tasks/mole.rake +115 -0
- data/tasks/post_load.rake +26 -0
- data/tasks/rubyforge.rake +57 -0
- data/tasks/setup.rb +227 -0
- data/tasks/spec.rake +54 -0
- data/tasks/svn.rake +44 -0
- data/tasks/test.rake +38 -0
- data/templates/mole/e_mole/exception_alerts.rhtml +14 -0
- data/templates/mole/e_mole/feature_alerts.rhtml +11 -0
- data/templates/mole/e_mole/perf_alerts.rhtml +12 -0
- metadata +206 -0
data/lib/mole/logger.rb
ADDED
@@ -0,0 +1,134 @@
|
|
1
|
+
require 'logging'
|
2
|
+
require 'forwardable'
|
3
|
+
|
4
|
+
module Mole
|
5
|
+
class Logger
|
6
|
+
class ConfigurationError < StandardError ; end #:nodoc:
|
7
|
+
|
8
|
+
attr_reader :log # here for testing, don't really use it.
|
9
|
+
|
10
|
+
extend Forwardable
|
11
|
+
def_delegators :@log, :debug, :warn, :info, :error, :fatal
|
12
|
+
def_delegators :@log, :level=, :level
|
13
|
+
def_delegators :@log, :debug?, :warn?, :info?, :error?, :fatal?
|
14
|
+
def_delegators :@log, :add, :clear_appenders
|
15
|
+
|
16
|
+
# there are more options here than are typically utilized for the
|
17
|
+
# logger, they are made available if someone wants to utilize
|
18
|
+
# them directly.
|
19
|
+
#
|
20
|
+
def self.default_options #:nodoc:
|
21
|
+
@default_options ||= {
|
22
|
+
|
23
|
+
# log event layout pattern
|
24
|
+
# YYYY-MM-DDTHH:MM:SS 12345 LEVEL LoggerName : The Log message
|
25
|
+
#
|
26
|
+
:layout_pattern => '%d %5p %5l %c : %m\n'.freeze ,
|
27
|
+
:logger_name => Mole ,
|
28
|
+
:additive => true ,
|
29
|
+
|
30
|
+
# log file configuration options
|
31
|
+
# age -> how often to rotate the logs if a file is used
|
32
|
+
# keep_count -> how many of the log files to keep
|
33
|
+
:log_level => :info ,
|
34
|
+
:log_file => $stdout ,
|
35
|
+
:log_file_age => 'daily'.freeze ,
|
36
|
+
:log_file_keep_count => 7 ,
|
37
|
+
|
38
|
+
# email logging options
|
39
|
+
# buffsize -> number of log events used as a threshold to send an
|
40
|
+
# email. If that is not reached before the program
|
41
|
+
# exists then the at_exit handler for logging will flush
|
42
|
+
# the log to smtp.
|
43
|
+
:email_alerts_to => nil ,
|
44
|
+
:email_alert_level => :error ,
|
45
|
+
:email_alert_server => nil ,
|
46
|
+
:email_alert_buffsize => 200 ,
|
47
|
+
}
|
48
|
+
end
|
49
|
+
|
50
|
+
# create a new logger
|
51
|
+
#
|
52
|
+
def initialize( opts = {} )
|
53
|
+
@options = ::Mole::Logger.default_options.merge(opts)
|
54
|
+
@log = ::Logging::Logger[@options[:logger_name]]
|
55
|
+
@layout = ::Logging::Layouts::Pattern.new( { :pattern => @options[:layout_pattern] } )
|
56
|
+
|
57
|
+
# add appenders explicitly, since this logger may already be defined and
|
58
|
+
# already have loggers
|
59
|
+
@appenders = []
|
60
|
+
@appenders << log_file_appender if @options[:log_file]
|
61
|
+
@appenders << email_appender if @options[:email_alerts_to]
|
62
|
+
|
63
|
+
@log.appenders = @appenders
|
64
|
+
@log.level = @options[:log_level]
|
65
|
+
@log.additive = @options[:additive]
|
66
|
+
end
|
67
|
+
|
68
|
+
# File appender, this is either an IO appender or a RollingFile appender
|
69
|
+
# depending on if an IO object or a String is passed in.
|
70
|
+
#
|
71
|
+
# a configuration error is raised in any other circumstance
|
72
|
+
def log_file_appender #:nodoc:
|
73
|
+
appender_name = "#{@log.name}-log_file_appender"
|
74
|
+
if String === @options[:log_file] then
|
75
|
+
::Logging::Appenders::RollingFile.new( appender_name,
|
76
|
+
{ :layout => @layout,
|
77
|
+
:filename => @options[:log_file],
|
78
|
+
:age => @options[:log_file_age],
|
79
|
+
:keep => @options[:log_file_keep_count],
|
80
|
+
:safe => true # make sure log files are rolled using lockfile
|
81
|
+
})
|
82
|
+
elsif @options[:log_file].respond_to?(:print) then
|
83
|
+
::Logging::Appenders::IO.new( appender_name, @options[:log_file], :layout => @layout )
|
84
|
+
else
|
85
|
+
raise ConfigurationError, "Invalid :log_file option [#{@options[:log_file].inspect}]"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# an email appender that uses :email_alerts_to option to send emails to.
|
90
|
+
# :email_alerts_to can either be a singe email address as a string or an
|
91
|
+
# Array of email addresses. Any other option for :email_alerts_to is
|
92
|
+
# invalid and raises an error.
|
93
|
+
#
|
94
|
+
def email_appender #:nodoc:
|
95
|
+
email_alerts_to = [ @options[:email_alerts_to] ].flatten.reject { |x| x == nil }
|
96
|
+
raise ConfigurationError, "Invalid :email_alerts_to option [#{@options[:email_alerts_to].inspect}]" unless email_alerts_to.size > 0
|
97
|
+
::Logging::Appenders::Email.new("#{@log.name}-email_appender",
|
98
|
+
{
|
99
|
+
:level => @options[:email_alert_level],
|
100
|
+
:layout => @layout,
|
101
|
+
:from => "#{@log.name}",
|
102
|
+
:to => "#{email_alerts_to.join(', ')}",
|
103
|
+
:subject => "Logging Alert from #{@log.name} on #{ENV['HOSTNAME']}",
|
104
|
+
:server => @options[:email_alert_server],
|
105
|
+
:buffsize => @options[:email_alert_buffsize], # lines
|
106
|
+
})
|
107
|
+
end
|
108
|
+
|
109
|
+
# create a new logger thi logger has no options when it is created although
|
110
|
+
# more can be added with the logger instance that is returned. The
|
111
|
+
# appenders of the current instance of Logger will be set on the new
|
112
|
+
# logger and the options of the current logger will be applied
|
113
|
+
def for( arg ) #:nodoc:
|
114
|
+
new_logger = ::Logging::Logger[arg]
|
115
|
+
new_logger.level = @options[:level]
|
116
|
+
new_logger.additive = @options[:additive]
|
117
|
+
new_logger.appenders = @appenders
|
118
|
+
return new_logger
|
119
|
+
end
|
120
|
+
|
121
|
+
# mole the bastard - create an entry into MOle log file
|
122
|
+
def log_it( context, feature, user_id, args )
|
123
|
+
args ||= "no args"
|
124
|
+
user_id ||= "N/A"
|
125
|
+
ip_addr, browser_type = MoleLog.log_details( context )
|
126
|
+
info = []
|
127
|
+
args.keys.sort { |a,b| a.to_s <=> b.to_s }.each { |k| info << "#{k}=>#{args[k]}" }
|
128
|
+
buff = ""
|
129
|
+
buff << "[#{ip_addr}/#{browser_type}]--" if ip_addr and browser_type
|
130
|
+
buff << "#{context.class.name}(#{feature}) --- #{user_id} -> #{info.join( ', ' ) }"
|
131
|
+
self.info( buff )
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# Feature model - Tracks the various application features in the db.
|
2
|
+
class MoleFeature < ActiveRecord::Base
|
3
|
+
has_many :mole_logs
|
4
|
+
|
5
|
+
class << self
|
6
|
+
# famous constants...
|
7
|
+
def all() "ALL" ; end
|
8
|
+
def exception() "Exception" ; end
|
9
|
+
def performance() "Performance"; end
|
10
|
+
|
11
|
+
# find performance feature
|
12
|
+
def find_performance_feature( app_name )
|
13
|
+
find_or_create_feature( performance, app_name )
|
14
|
+
end
|
15
|
+
|
16
|
+
# find exception feature
|
17
|
+
def find_exception_feature( app_name )
|
18
|
+
find_or_create_feature( exception, app_name )
|
19
|
+
end
|
20
|
+
|
21
|
+
# Find the all feature ( wildcard feature for given app )
|
22
|
+
def find_all_feature( app_name )
|
23
|
+
find_or_create_feature( all, app_name )
|
24
|
+
end
|
25
|
+
|
26
|
+
# Find all the MOled applications
|
27
|
+
def find_moled_application_names
|
28
|
+
res = find( :all,
|
29
|
+
:select => "distinct( app_name )",
|
30
|
+
:order => "name asc" )
|
31
|
+
res.map(&:app_name)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Finds all the features available for a given application
|
35
|
+
def find_features( app_name )
|
36
|
+
# Creates the all feature if necessary
|
37
|
+
find_all_feature( app_name )
|
38
|
+
find( :all,
|
39
|
+
:conditions => ["app_name = ?", app_name],
|
40
|
+
:select => "id, name, context",
|
41
|
+
:order => "name asc" )
|
42
|
+
end
|
43
|
+
|
44
|
+
# locates an existing feature or create a new one if it does not exist.
|
45
|
+
def find_or_create_feature( name, app_name, ctx_name=nil )
|
46
|
+
if name.nil? or name.empty?
|
47
|
+
::Mole.logger.error( "--- MOLE ERROR - Invalid feature. Empty or nil" )
|
48
|
+
return nil
|
49
|
+
end
|
50
|
+
if ctx_name
|
51
|
+
res = find_by_name_and_context_and_app_name( name, ctx_name, app_name )
|
52
|
+
else
|
53
|
+
res = find_by_name_and_app_name( name, app_name )
|
54
|
+
end
|
55
|
+
res || create(:name => name,:context => ctx_name, :app_name => app_name )
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# Log Model - Tracks and record user interactions with various features
|
2
|
+
# This will make it easy to write an app on top on the MOle to track a
|
3
|
+
# particular application utilization.
|
4
|
+
class MoleLog < ActiveRecord::Base
|
5
|
+
belongs_to :mole_feature
|
6
|
+
|
7
|
+
class << self
|
8
|
+
# mole the bastard - create db entry into mole logs
|
9
|
+
def log_it( context, feature, user_id, args )
|
10
|
+
args ||= "no args"
|
11
|
+
user_id ||= "N/A"
|
12
|
+
ip_addr, browser_type = log_details( context )
|
13
|
+
MoleLog.create( :mole_feature => feature,
|
14
|
+
:user_id => user_id,
|
15
|
+
:host_name => `hostname`,
|
16
|
+
:params => args.to_yaml,
|
17
|
+
:ip_address => ip_addr,
|
18
|
+
:browser_type => browser_type )
|
19
|
+
end
|
20
|
+
|
21
|
+
# extract orginating ip address and browser type
|
22
|
+
def log_details( context ) #:nodoc:
|
23
|
+
ip_addr, browser_type = nil
|
24
|
+
if context.respond_to? :request
|
25
|
+
ip_addr, browser_type = context.request.env['REMOTE_ADDR'], context.request.env['HTTP_USER_AGENT']
|
26
|
+
end
|
27
|
+
return ip_addr, browser_type
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
data/lib/mole/module.rb
ADDED
@@ -0,0 +1,292 @@
|
|
1
|
+
# =============================================================================
|
2
|
+
# Extends module to inject interceptors
|
3
|
+
# =============================================================================
|
4
|
+
require 'benchmark'
|
5
|
+
require 'active_support'
|
6
|
+
|
7
|
+
class Module
|
8
|
+
# intercepts a collection of features and wrap performance check based on the
|
9
|
+
# specified :perf_threshold. The trigger defaults to 5 secs if not explicitly set.
|
10
|
+
#
|
11
|
+
# Example:
|
12
|
+
#
|
13
|
+
# MyClass.mole_perf do |context, action, elapsed_time, ret, block, *args|
|
14
|
+
# Mole::DbMole.perf_it( context.session[:user_id],
|
15
|
+
# :controller => context.class.name,
|
16
|
+
# :action => action,
|
17
|
+
# :elapsed_time => "%3.3f" % elapsed_time )
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# This will trap all public methods on the MyClass that takes more than
|
21
|
+
# :perf_threshold to complete. You can override this default by using the option
|
22
|
+
# :features => [m1,m2,...]. This is handy for controller moling rails
|
23
|
+
# and merb context.
|
24
|
+
#
|
25
|
+
# If you elect not to use the block form of the call, you can pass in the
|
26
|
+
# following arguments to the option hash:
|
27
|
+
# <tt>:interceptor</tt>:: The class name of your interceptor class
|
28
|
+
# <tt>:method</tt>:: The name of the method to callback the interceptor on
|
29
|
+
# once a perf condition has been trapped.
|
30
|
+
def mole_perf( opts={}, &interceptor )
|
31
|
+
opts[:interceptor] ||= interceptor
|
32
|
+
opts[:method] ||= :call
|
33
|
+
opts[:features] ||= instance_methods( false )
|
34
|
+
opts[:features].each do |feature|
|
35
|
+
wrap feature
|
36
|
+
perf_mole_filters[feature.to_s] << [opts[:interceptor], opts[:method]]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# monitors a collections of features and wrap rescue logic to trap unchecked
|
41
|
+
# exceptions. You can handle to trap differently by either logging the event
|
42
|
+
# in the db or sending out email/IM notification.
|
43
|
+
#
|
44
|
+
# Example:
|
45
|
+
#
|
46
|
+
# MyClass.mole_unchecked do |context, action, boom, block, *args|
|
47
|
+
# Mole::Moler.check_it( context.session[:user_id],
|
48
|
+
# :controller => context.class.name,
|
49
|
+
# :action => action,
|
50
|
+
# :boom => boom )
|
51
|
+
# end
|
52
|
+
#
|
53
|
+
# This will wrap all public instance methods on MyClass. If any of these methods
|
54
|
+
# raises an unchecked exception, the MOle will surface the condition.
|
55
|
+
# This call also takes in a :features option to specify a list of methods if the
|
56
|
+
# default instance methods is not suitable, you can pass in a collection of methods
|
57
|
+
# that you wish to mole. This is handy in the case of Rails/Merb where conveniences
|
58
|
+
# are provided to gather controller actions
|
59
|
+
#
|
60
|
+
# If you elect not to use the block form of the call, you can pass in the
|
61
|
+
# following arguments to the option hash:
|
62
|
+
# <tt>:interceptor</tt>:: The class name of your interceptor class
|
63
|
+
# <tt>:method</tt>:: The name of the method to callback the interceptor on
|
64
|
+
# once an exception condition has been trapped.
|
65
|
+
def mole_unchecked( opts={}, &interceptor )
|
66
|
+
opts[:interceptor] ||= interceptor
|
67
|
+
opts[:method] ||= :call
|
68
|
+
opts[:features] ||= instance_methods( false )
|
69
|
+
opts[:features].each do |feature|
|
70
|
+
wrap feature
|
71
|
+
unchecked_mole_filters[feature.to_s] << [opts[:interceptor], opts[:method]]
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# intercepts a feature before the feature is called. During the callback
|
76
|
+
# you will have access to the object ( context ) on which the call is intercepted,
|
77
|
+
# as well as the arguments/block, the feature was issued with.
|
78
|
+
#
|
79
|
+
# Example:
|
80
|
+
#
|
81
|
+
# MyClass.mole_before( :feature => :blee ) do |context, feature, block, *args|
|
82
|
+
# Mole::Moler.mole_it( context, feature, context.session[:user_id],
|
83
|
+
# :args => args )
|
84
|
+
# end
|
85
|
+
#
|
86
|
+
# This will wrap the method blee with a before interceptor. Before the blee call
|
87
|
+
# is issued on MyClass, control will be handed to the before interceptor.
|
88
|
+
#
|
89
|
+
# Options:
|
90
|
+
# <tt>:feature</tt>:: The name of the feature to be intercepted
|
91
|
+
# If you elect not to use the block form of the call, you can pass in the
|
92
|
+
# following arguments to the option hash:
|
93
|
+
# <tt>:interceptor</tt>:: The class name of your interceptor class. If no interceptor block is specified
|
94
|
+
# <tt>:method</tt>:: The name of the method to callback the interceptor on
|
95
|
+
# once an exception condition has been trapped.
|
96
|
+
def mole_before(opts={}, &interceptor)
|
97
|
+
raise "Missing :feature option" if opts[:feature].nil? or opts[:feature].to_s.empty?
|
98
|
+
opts[:interceptor] ||= interceptor
|
99
|
+
opts[:method] ||= :call
|
100
|
+
feature = opts[:feature].to_s
|
101
|
+
if before_mole_filters[feature].empty?
|
102
|
+
wrap feature
|
103
|
+
before_mole_filters[feature] << [opts[:interceptor], opts[:method]]
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# intercepts a feature after the feature is called. During the callback
|
108
|
+
# you will have access to the object ( context ) on which the call is intercepted,
|
109
|
+
# as well as the arguments/block and return values the feature was issued with.
|
110
|
+
#
|
111
|
+
# Example:
|
112
|
+
#
|
113
|
+
# MyClass.mole_after( :feature => :blee ) do |context, feature, ret_val, block, *args|
|
114
|
+
# Mole::Moler.mole_it( context, feature, context.session[:user_id],
|
115
|
+
# :args => args )
|
116
|
+
# end
|
117
|
+
#
|
118
|
+
# This will wrap the method blee with an after interceptor. After the blee call
|
119
|
+
# is issued on MyClass, control will be handed to the after interceptor.
|
120
|
+
#
|
121
|
+
# Options:
|
122
|
+
# <tt>:feature</tt>:: The name of the feature to be intercepted
|
123
|
+
# <tt>:interceptor</tt>:: The class name of your interceptor class. If no interceptor block is specified
|
124
|
+
# <tt>:method</tt>:: The name of the method to callback the interceptor on
|
125
|
+
# once an exception condition has been trapped.
|
126
|
+
def mole_after(opts = {}, &interceptor)
|
127
|
+
raise "Missing :feature option" if opts[:feature].nil? or opts[:feature].to_s.empty?
|
128
|
+
opts[:interceptor] ||= interceptor
|
129
|
+
opts[:method] ||= :call
|
130
|
+
feature = opts[:feature].to_s
|
131
|
+
if after_mole_filters[feature].empty?
|
132
|
+
wrap feature
|
133
|
+
after_mole_filters[feature] << [opts[:interceptor], opts[:method]]
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# ---------------------------------------------------------------------------
|
138
|
+
# Dumps moled feature info
|
139
|
+
def mole_dump( msg=nil )
|
140
|
+
puts "\n------------------------------------------------------------------"
|
141
|
+
puts "From #{msg}" if msg
|
142
|
+
puts "MOle Info for class <- #{self} ->"
|
143
|
+
puts "\nBefore filters"
|
144
|
+
before_mole_filters.keys.sort.each { |k| puts "\t#{k} --> #{before_mole_filters[k]}" }
|
145
|
+
puts "\nAfter filters"
|
146
|
+
after_mole_filters.keys.sort.each { |k| puts "\t#{k} --> #{after_mole_filters[k]}" }
|
147
|
+
puts "\nUnchecked filters"
|
148
|
+
unchecked_mole_filters.keys.sort.each { |k| puts "\t#{k} --> #{unchecked_mole_filters[k]}" }
|
149
|
+
puts "\nPerf filters"
|
150
|
+
perf_mole_filters.keys.sort.each { |k| puts "\t#{k} --> #{perf_mole_filters[k]}" }
|
151
|
+
puts "---------------------------------------------------------------------\n"
|
152
|
+
end
|
153
|
+
|
154
|
+
# ===========================================================================
|
155
|
+
private
|
156
|
+
|
157
|
+
# Strip out all invalid punctuation
|
158
|
+
def clean_method_name( method )
|
159
|
+
return method.to_s.sub(/([?!=])$/, ''), $1
|
160
|
+
end
|
161
|
+
|
162
|
+
# Clear MOle state for this class # Used for testing only
|
163
|
+
def mole_clear!
|
164
|
+
@before_mole_filters = nil
|
165
|
+
@after_mole_filters = nil
|
166
|
+
@perf_mole_filters = nil
|
167
|
+
@unchecked_mole_filters = nil
|
168
|
+
end
|
169
|
+
|
170
|
+
# ===========================================================================
|
171
|
+
public
|
172
|
+
|
173
|
+
# -------------------------------------------------------------------------
|
174
|
+
# Holds before filters
|
175
|
+
def before_mole_filters #:nodoc:
|
176
|
+
@before_mole_filters ||= Hash.new{ |h,k| h[k] = [] }
|
177
|
+
end
|
178
|
+
|
179
|
+
# -------------------------------------------------------------------------
|
180
|
+
# Holds after filters
|
181
|
+
def after_mole_filters #:nodoc:
|
182
|
+
@after_mole_filters ||= Hash.new{ |h,k| h[k] = [] }
|
183
|
+
end
|
184
|
+
|
185
|
+
# -------------------------------------------------------------------------
|
186
|
+
# Holds perf around filters
|
187
|
+
def perf_mole_filters #:nodoc:
|
188
|
+
@perf_mole_filters ||= Hash.new{ |h,k| h[k] = []}
|
189
|
+
end
|
190
|
+
|
191
|
+
# -------------------------------------------------------------------------
|
192
|
+
# Holds unchecked exception filters
|
193
|
+
def unchecked_mole_filters #:nodoc:
|
194
|
+
@unchecked_mole_filters ||= Hash.new{ |h,k| h[k] = []}
|
195
|
+
end
|
196
|
+
|
197
|
+
# -------------------------------------------------------------------------
|
198
|
+
# Attempt to find singleton class method with given name
|
199
|
+
# TODO Figure out how to get method for static signature...
|
200
|
+
# def find_public_class_method(method)
|
201
|
+
# singleton_methods.each { |name| puts "Looking for #{method}--#{method.class} -- #{name}#{name.class}";return name if name == method }
|
202
|
+
# nil
|
203
|
+
# end
|
204
|
+
|
205
|
+
# -------------------------------------------------------------------------
|
206
|
+
# Wrap method call
|
207
|
+
# TODO Add support for wrapping class methods ??
|
208
|
+
def wrap( method ) #:nodoc:
|
209
|
+
return if wrapped?( method )
|
210
|
+
begin
|
211
|
+
between = instance_method( method )
|
212
|
+
rescue
|
213
|
+
# between = find_public_class_method( method )
|
214
|
+
raise "Unable to find moled feature `#{method}" unless(between)
|
215
|
+
end
|
216
|
+
method_name, punctuation = clean_method_name( method )
|
217
|
+
code = <<-code
|
218
|
+
def #{method_name}_with_mole#{punctuation} (*a, &b)
|
219
|
+
key = '#{method}'
|
220
|
+
klass = self.class
|
221
|
+
between = klass.wrapped[key]
|
222
|
+
ret_val = nil
|
223
|
+
klass.apply_before_filters( klass.before_mole_filters[key], self, key, *a, &b ) if klass.before_mole_filters[key]
|
224
|
+
begin
|
225
|
+
elapsed = Benchmark::realtime do
|
226
|
+
ret_val = between.bind(self).call( *a, &b )
|
227
|
+
end
|
228
|
+
klass.apply_perf_filters( elapsed, klass.perf_mole_filters[key], self, key, ret_val, *a, &b ) if klass.perf_mole_filters[key]
|
229
|
+
rescue => boom
|
230
|
+
klass.apply_unchecked_filters( boom, klass.unchecked_mole_filters[key], self, key, *a, &b ) if klass.unchecked_mole_filters[key]
|
231
|
+
raise boom
|
232
|
+
end
|
233
|
+
klass.apply_after_filters( klass.after_mole_filters[key], self, key, ret_val, *a, &b ) if klass.after_mole_filters[key]
|
234
|
+
ret_val
|
235
|
+
end
|
236
|
+
code
|
237
|
+
|
238
|
+
module_eval code
|
239
|
+
alias_method_chain method, "mole"
|
240
|
+
wrapped[method.to_s] = between
|
241
|
+
end
|
242
|
+
|
243
|
+
def apply_before_filters( filters, clazz, key, *a, &b ) #:nodoc:
|
244
|
+
begin
|
245
|
+
filters.each { |r,m| r.send( m, clazz, key, b, *a ) }
|
246
|
+
rescue => ca_boom
|
247
|
+
::Mole.logger.error ">>> MOle Failure: Before-Filter -- " + ca_boom
|
248
|
+
ca_boom.backtrace.each { |l| ::Mole.logger.error l }
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
def apply_after_filters( filters, clazz, key, ret_val, *a, &b ) #:nodoc:
|
253
|
+
begin
|
254
|
+
filters.each { |r,m| r.send( m, clazz, key, ret_val, b, *a ) }
|
255
|
+
rescue => ca_boom
|
256
|
+
::Mole.logger.error ">>> MOle Failure: After-Filter -- " + ca_boom
|
257
|
+
ca_boom.backtrace.each { |l| ::Mole.logger.error l }
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
def apply_perf_filters( elapsed, filters, clazz, key, ret_val, *a, &b ) #:nodoc:
|
262
|
+
begin
|
263
|
+
if ( elapsed >= Mole.perf_threshold )
|
264
|
+
filters.each { |r,m| r.send( m, clazz, key, elapsed, ret_val, b, *a ) }
|
265
|
+
end
|
266
|
+
rescue => ca_boom
|
267
|
+
::Mole.logger.error ">>> MOle Failure: Perf-Filter -- " + ca_boom
|
268
|
+
ca_boom.backtrace.each { |l| ::Mole.logger.error l }
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
def apply_unchecked_filters( boom, filters, clazz, key, *a, &b ) #:nodoc:
|
273
|
+
begin
|
274
|
+
filters.each { |r,m| r.send( m, clazz, key, boom, b, *a ) }
|
275
|
+
rescue => ca_boom
|
276
|
+
::Mole.logger.error ">>> MOle Failure: Unchecked-Filter -- " + ca_boom
|
277
|
+
ca_boom.backtrace.each { |l| ::Mole.logger.error l }
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
# ---------------------------------------------------------------------------
|
282
|
+
# Log wrapped class
|
283
|
+
def wrapped #:nodoc:
|
284
|
+
@wrapped ||= {}
|
285
|
+
end
|
286
|
+
|
287
|
+
# ---------------------------------------------------------------------------
|
288
|
+
# Check if method has been wrapped
|
289
|
+
def wrapped?(which) #:nodoc:
|
290
|
+
wrapped.has_key?(which.to_s)
|
291
|
+
end
|
292
|
+
end
|