rackamole 0.0.4 → 0.0.6
Sign up to get free protection for your applications and to get access to all the features.
- data/{README.txt → README.rdoc} +3 -3
- data/Rakefile +5 -2
- data/lib/rackamole/mole.rb +43 -25
- data/lib/rackamole/store/mongo_db.rb +139 -0
- data/lib/rackamole/store.rb +1 -0
- data/lib/rackamole.rb +1 -1
- data/spec/rackamole/mole_spec.rb +52 -15
- data/spec/rackamole/store/log_spec.rb +0 -1
- data/spec/rackamole/store/mongo_db_spec.rb +128 -0
- data/tasks/rdoc.rake +54 -0
- data/tasks/setup.rb +1 -1
- data/tasks/spec.rake +1 -1
- data/tasks/test.rake +1 -1
- metadata +23 -15
- data/History.txt +0 -3
- data/bin/rackamole +0 -8
- data/lib/rackamole/store/mongo.rb +0 -121
- data/spec/rackamole/store/mongo_spec.rb +0 -120
data/{README.txt → README.rdoc}
RENAMED
@@ -19,9 +19,9 @@ interactions and leverage these findings for the next iteration of your applicat
|
|
19
19
|
== PROJECT INFORMATION
|
20
20
|
|
21
21
|
* Developer: Fernand Galiana
|
22
|
-
* Blog: liquidrail.com
|
23
|
-
* Site: rackamole.com
|
24
|
-
* Twitter:
|
22
|
+
* Blog: http://www.liquidrail.com
|
23
|
+
* Site: http://rackamole.com
|
24
|
+
* Twitter: https://twitter.com/rackamole
|
25
25
|
* Forum: http://groups.google.com/group/rackamole
|
26
26
|
* Git: git://github.com/derailed/rackamole.git
|
27
27
|
|
data/Rakefile
CHANGED
@@ -19,10 +19,13 @@ PROJ.authors = 'Fernand Galiana'
|
|
19
19
|
PROJ.email = 'fernand.galiana@gmail.com'
|
20
20
|
PROJ.url = 'http://rackamole.liquidrail.com'
|
21
21
|
PROJ.version = Rackamole::VERSION
|
22
|
-
PROJ.spec.opts
|
22
|
+
PROJ.spec.opts << '--color'
|
23
23
|
PROJ.ruby_opts = %w[-W0]
|
24
|
+
PROJ.readme = 'README.rdoc'
|
25
|
+
PROJ.rcov.opts = ["--sort", "coverage", "-T", '-x mongo']
|
24
26
|
|
25
27
|
# Dependencies
|
26
28
|
depend_on "logging" , ">= 1.2.2"
|
27
29
|
depend_on "hitimes" , ">= 1.0.3"
|
28
|
-
depend_on "
|
30
|
+
depend_on "mongo" , ">= 0.17.1"
|
31
|
+
depend_on "darkfish-rdoc", ">= 1.1.5"
|
data/lib/rackamole/mole.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
require 'hitimes'
|
2
|
-
require 'mongo/util/ordered_hash'
|
3
2
|
require 'json'
|
3
|
+
require 'mongo'
|
4
4
|
|
5
5
|
module Rack
|
6
6
|
class Mole
|
@@ -81,33 +81,33 @@ module Rack
|
|
81
81
|
session = env['rack.session']
|
82
82
|
route = get_route( request )
|
83
83
|
|
84
|
-
ip,
|
85
|
-
user_id
|
86
|
-
user_name
|
84
|
+
ip, user_agent = identify( env )
|
85
|
+
user_id = nil
|
86
|
+
user_name = nil
|
87
87
|
|
88
88
|
# BOZO !! This could be slow if have to query db to get user name...
|
89
89
|
# Preferred store username in session and give at key
|
90
90
|
if session and @user_key
|
91
|
-
if @user_key.instance_of?
|
92
|
-
user_name = session[@user_key]
|
93
|
-
elsif @user_key.instance_of? Hash
|
91
|
+
if @user_key.instance_of? Hash
|
94
92
|
user_id = session[ @user_key[:session_key] ]
|
95
93
|
if @user_key[:extractor]
|
96
94
|
user_name = @user_key[:extractor].call( user_id )
|
97
95
|
end
|
96
|
+
else
|
97
|
+
user_name = session[@user_key]
|
98
98
|
end
|
99
99
|
end
|
100
100
|
|
101
101
|
info[:app_name] = @app_name
|
102
|
-
info[:environment] = @environment
|
102
|
+
info[:environment] = @environment || "Unknown"
|
103
103
|
info[:user_id] = user_id if user_id
|
104
104
|
info[:user_name] = user_name || "Unknown"
|
105
105
|
info[:ip] = ip
|
106
|
-
info[:browser] =
|
106
|
+
info[:browser] = id_browser( user_agent )
|
107
107
|
info[:host] = env['SERVER_NAME']
|
108
108
|
info[:software] = env['SERVER_SOFTWARE']
|
109
109
|
info[:request_time] = elapsed if elapsed
|
110
|
-
info[:
|
110
|
+
info[:performance] = (elapsed and elapsed > @perf_threshold)
|
111
111
|
info[:url] = request.url
|
112
112
|
info[:method] = env['REQUEST_METHOD']
|
113
113
|
info[:path] = request.path
|
@@ -138,6 +138,18 @@ module Rack
|
|
138
138
|
boom.backtrace.each { |l| $stderr.puts l }
|
139
139
|
end
|
140
140
|
|
141
|
+
# Attempts to detect browser type from agent info.
|
142
|
+
# BOZO !! Probably more efficient way to do this...
|
143
|
+
def browser_types() @browsers ||= [ 'Firefox', 'Safari', 'MSIE 8.0', 'MSIE 7.0', 'MSIE 6.0', 'Opera', 'Chrome' ] end
|
144
|
+
|
145
|
+
def id_browser( user_agent )
|
146
|
+
return "N/A" if !user_agent or user_agent.empty?
|
147
|
+
browser_types.each do |b|
|
148
|
+
return b if user_agent.match( /.*?#{b.gsub(/\./,'\.')}.*?/ )
|
149
|
+
end
|
150
|
+
"N/A"
|
151
|
+
end
|
152
|
+
|
141
153
|
# Trim stack trace
|
142
154
|
def trim_stack( boom )
|
143
155
|
boom.backtrace[0...4]
|
@@ -155,23 +167,29 @@ module Rack
|
|
155
167
|
|
156
168
|
# Fetch route info if any...
|
157
169
|
def get_route( request )
|
158
|
-
return nil unless defined?( RAILS_ENV )
|
159
|
-
|
170
|
+
return nil unless defined?( RAILS_ENV )
|
171
|
+
|
172
|
+
# Check for invalid route exception...
|
173
|
+
begin
|
174
|
+
return ::ActionController::Routing::Routes.recognize_path( request.path, {:method => request.request_method.downcase.to_sym } )
|
175
|
+
rescue
|
176
|
+
return nil
|
177
|
+
end
|
160
178
|
end
|
161
179
|
|
162
180
|
# Dump env to stdout
|
163
|
-
def dump( env, level=0 )
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
end
|
181
|
+
# def dump( env, level=0 )
|
182
|
+
# env.keys.sort{ |a,b| a.to_s <=> b.to_s }.each do |k|
|
183
|
+
# value = env[k]
|
184
|
+
# if value.respond_to?(:each_pair)
|
185
|
+
# puts "%s %-#{40-level}s" % [' '*level,k]
|
186
|
+
# dump( env[k], level+1 )
|
187
|
+
# elsif value.instance_of?(::ActionController::Request) or value.instance_of?(::ActionController::Response)
|
188
|
+
# puts "%s %-#{40-level}s %s" % [ ' '*level, k, value.class ]
|
189
|
+
# else
|
190
|
+
# puts "%s %-#{40-level}s %s" % [ ' '*level, k, value.inspect ]
|
191
|
+
# end
|
192
|
+
# end
|
193
|
+
# end
|
176
194
|
end
|
177
195
|
end
|
@@ -0,0 +1,139 @@
|
|
1
|
+
require 'mongo'
|
2
|
+
|
3
|
+
# TODO !! Need to deal with auth
|
4
|
+
# BOZO !! Deal with indexes here ?
|
5
|
+
module Rackamole
|
6
|
+
module Store
|
7
|
+
# Mongo adapter. Stores mole info in a mongo database.
|
8
|
+
class MongoDb
|
9
|
+
|
10
|
+
attr_reader :database, :logs, :features
|
11
|
+
|
12
|
+
# Defines the various feature types
|
13
|
+
FEATURE = 0
|
14
|
+
PERFORMANCE = 1
|
15
|
+
EXCEPTION = 2
|
16
|
+
|
17
|
+
def initialize( options={} )
|
18
|
+
opts = default_options.merge( options )
|
19
|
+
init_mongo( opts )
|
20
|
+
end
|
21
|
+
|
22
|
+
# clear out db content ( used in testing... )
|
23
|
+
def reset!
|
24
|
+
logs.remove
|
25
|
+
features.remove
|
26
|
+
end
|
27
|
+
|
28
|
+
# Dump mole info to logger
|
29
|
+
def mole( arguments )
|
30
|
+
return if arguments.empty?
|
31
|
+
|
32
|
+
# get a dup of the args since will mock with the original
|
33
|
+
args = arguments.clone
|
34
|
+
|
35
|
+
# dump request info to mongo
|
36
|
+
save_log( save_feature( args ), args )
|
37
|
+
rescue => mole_boom
|
38
|
+
$stderr.puts "MOLE STORE CRAPPED OUT -- #{mole_boom}"
|
39
|
+
$stderr.puts mole_boom.backtrace.join( "\n " )
|
40
|
+
end
|
41
|
+
|
42
|
+
# =======================================================================
|
43
|
+
private
|
44
|
+
|
45
|
+
def init_mongo( opts )
|
46
|
+
@connection = Mongo::Connection.new( opts[:host], opts[:port], :logger => opts[:logger] )
|
47
|
+
@database = @connection.db( opts[:database] )
|
48
|
+
@features = database.collection( 'features' )
|
49
|
+
@logs = database.collection( 'logs' )
|
50
|
+
end
|
51
|
+
|
52
|
+
# Set up mongo default options ie localhost host, default mongo port and
|
53
|
+
# the database being mole_mdb
|
54
|
+
def default_options
|
55
|
+
{
|
56
|
+
:host => 'localhost',
|
57
|
+
:port => 27017,
|
58
|
+
:database => 'mole_mdb'
|
59
|
+
}
|
60
|
+
end
|
61
|
+
|
62
|
+
# Find or create a mole feature
|
63
|
+
def save_feature( args )
|
64
|
+
app_name = args.delete( :app_name )
|
65
|
+
route_info = args.delete( :route_info )
|
66
|
+
environment = args.delete( :environment )
|
67
|
+
|
68
|
+
row = { min_field(:app_name) => app_name, min_field(:env) => environment }
|
69
|
+
if route_info
|
70
|
+
row[min_field(:controller)] = route_info[:controller]
|
71
|
+
row[min_field(:action)] = route_info[:action]
|
72
|
+
else
|
73
|
+
row[min_field(:context)] = args.delete( :path )
|
74
|
+
end
|
75
|
+
|
76
|
+
feature = features.find_one( row )
|
77
|
+
return feature if feature
|
78
|
+
|
79
|
+
id = features.save( row )
|
80
|
+
features.find_one( id )
|
81
|
+
end
|
82
|
+
|
83
|
+
# Insert a new feature in the db
|
84
|
+
def save_log( feature, args )
|
85
|
+
type = FEATURE
|
86
|
+
type = EXCEPTION if args[:stack]
|
87
|
+
type = PERFORMANCE if args.delete(:performance)
|
88
|
+
|
89
|
+
# BOZO !! to reduce collection space...
|
90
|
+
# Using cryptic key to reduce storage needs.
|
91
|
+
# Also narrowing date/time to ints
|
92
|
+
now = Time.now
|
93
|
+
row = {
|
94
|
+
min_field( :type ) => type,
|
95
|
+
min_field( :feature_id ) => feature['_id'].to_s,
|
96
|
+
min_field( :date_id ) => ("%4d%02d%02d" %[now.year, now.month, now.day]).to_i,
|
97
|
+
min_field( :time_id ) => ("%02d%02d%02d" %[now.hour, now.min, now.sec] ).to_i
|
98
|
+
}
|
99
|
+
|
100
|
+
args.each do |k,v|
|
101
|
+
row[min_field(k)] = v if v
|
102
|
+
end
|
103
|
+
logs.save( row )
|
104
|
+
end
|
105
|
+
|
106
|
+
# For storage reason minify the json to save space...
|
107
|
+
def min_field( field )
|
108
|
+
field_map[field] || field
|
109
|
+
end
|
110
|
+
|
111
|
+
# Normalize all accessors to 3 chars.
|
112
|
+
def field_map
|
113
|
+
@field_map ||= {
|
114
|
+
:app_name => :app,
|
115
|
+
:context => :ctx,
|
116
|
+
:controller => :ctl,
|
117
|
+
:action => :act,
|
118
|
+
:type => :typ,
|
119
|
+
:feature_id => :fid,
|
120
|
+
:date_id => :did,
|
121
|
+
:time_id => :tid,
|
122
|
+
:user_id => :uid,
|
123
|
+
:user_name => :una,
|
124
|
+
:browser => :bro,
|
125
|
+
:host => :hos,
|
126
|
+
:software => :sof,
|
127
|
+
:request_time => :rti,
|
128
|
+
:performance => :per,
|
129
|
+
:method => :met,
|
130
|
+
:path => :pat,
|
131
|
+
:session => :ses,
|
132
|
+
:params => :par,
|
133
|
+
:ruby_version => :ver,
|
134
|
+
:stack => :sta
|
135
|
+
}
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
Rackamole.require_all_libs_relative_to(__FILE__)
|
data/lib/rackamole.rb
CHANGED
data/spec/rackamole/mole_spec.rb
CHANGED
@@ -1,16 +1,16 @@
|
|
1
1
|
require File.join(File.dirname(__FILE__), %w[.. spec_helper])
|
2
|
-
require 'actionpack'
|
2
|
+
# require 'actionpack'
|
3
3
|
|
4
4
|
describe Rack::Mole do
|
5
5
|
include Rack::Test::Methods
|
6
6
|
|
7
7
|
before :each do
|
8
8
|
@response = [ 200, {"Content-Type" => "text/plain"}, ["success"] ]
|
9
|
+
@test_env = { 'rack.session' => { :user_id => 100 }, 'HTTP_X_FORWARDED_FOR' => '1.1.1.1', 'HTTP_USER_AGENT' => "Firefox" }
|
9
10
|
end
|
10
11
|
|
11
12
|
class TestStore
|
12
|
-
attr_accessor :mole_result
|
13
|
-
|
13
|
+
attr_accessor :mole_result
|
14
14
|
def mole( args )
|
15
15
|
@mole_result = args
|
16
16
|
end
|
@@ -24,11 +24,34 @@ describe Rack::Mole do
|
|
24
24
|
run lambda { |env| response }
|
25
25
|
end
|
26
26
|
end
|
27
|
-
|
27
|
+
|
28
|
+
def error_app( opts={} )
|
29
|
+
@app ||= Rack::Builder.new do
|
30
|
+
use Rack::Lint
|
31
|
+
use Rack::Mole, opts
|
32
|
+
run lambda { |env| raise "Oh Snap!" }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should mole a framwework exception correctly" do
|
37
|
+
@test_store = TestStore.new
|
38
|
+
error_app(
|
39
|
+
:app_name => "Test App",
|
40
|
+
:environment => :test,
|
41
|
+
:perf_threshold => 0.1,
|
42
|
+
:user_key => { :session_key => :user_id, :extractor => lambda{ |k| "Test user #{k}"} },
|
43
|
+
:store => @test_store )
|
44
|
+
|
45
|
+
begin
|
46
|
+
get "/", nil, @test_env
|
47
|
+
rescue
|
48
|
+
@test_store.mole_result[:stack].should have(4).items
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
28
52
|
describe 'moling a request' do
|
29
53
|
before :each do
|
30
54
|
@test_store = TestStore.new
|
31
|
-
@test_env = { 'rack.session' => { :user_id => 100 }, 'HTTP_X_FORWARDED_FOR' => '1.1.1.1', 'HTTP_USER_AGENT' => "IBrowse" }
|
32
55
|
app(
|
33
56
|
:app_name => "Test App",
|
34
57
|
:environment => :test,
|
@@ -44,11 +67,11 @@ describe Rack::Mole do
|
|
44
67
|
@test_store.mole_result[:user_id].should == 100
|
45
68
|
@test_store.mole_result[:user_name].should == 'Test user 100'
|
46
69
|
@test_store.mole_result[:ip].should == '1.1.1.1'
|
47
|
-
@test_store.mole_result[:browser].should == '
|
70
|
+
@test_store.mole_result[:browser].should == 'Firefox'
|
48
71
|
@test_store.mole_result[:method].should == 'GET'
|
49
72
|
@test_store.mole_result[:url].should == 'http://example.org/'
|
50
73
|
@test_store.mole_result[:path].should == '/'
|
51
|
-
@test_store.mole_result[:
|
74
|
+
@test_store.mole_result[:performance].should == false
|
52
75
|
@test_store.mole_result[:params].should be_nil
|
53
76
|
@test_store.mole_result[:session].should_not be_nil
|
54
77
|
@test_store.mole_result[:session].should == { :user_id => '100' }
|
@@ -58,11 +81,11 @@ describe Rack::Mole do
|
|
58
81
|
begin
|
59
82
|
raise 'Oh snap!'
|
60
83
|
rescue => boom
|
61
|
-
get "/", nil, { 'mole.exception' => boom
|
84
|
+
get "/", nil, @test_env.merge( { 'mole.exception' => boom } )
|
62
85
|
@test_store.mole_result[:stack].should have(4).items
|
63
86
|
end
|
64
87
|
end
|
65
|
-
|
88
|
+
|
66
89
|
it "should capture request parameters correctly" do
|
67
90
|
get "/", { :blee => 'duh' }, @test_env
|
68
91
|
@test_store.mole_result[:params].should == { :blee => "duh".to_json }
|
@@ -72,7 +95,6 @@ describe Rack::Mole do
|
|
72
95
|
describe 'username in session' do
|
73
96
|
before :each do
|
74
97
|
@test_store = TestStore.new
|
75
|
-
@test_env = { 'rack.session' => { :user_name => "Fernand" } }
|
76
98
|
app(
|
77
99
|
:app_name => "Test App",
|
78
100
|
:environment => :test,
|
@@ -81,10 +103,25 @@ describe Rack::Mole do
|
|
81
103
|
:store => @test_store )
|
82
104
|
end
|
83
105
|
|
84
|
-
it "should
|
85
|
-
get "/", nil, @test_env
|
86
|
-
@test_store.mole_result[:user_id].should
|
87
|
-
@test_store.mole_result[:user_name].should
|
88
|
-
end
|
106
|
+
it "should pickup the user name from the session correctly" do
|
107
|
+
get "/", nil, @test_env.merge( { 'rack.session' => { :user_name => "Fernand" } } )
|
108
|
+
@test_store.mole_result[:user_id].should be_nil
|
109
|
+
@test_store.mole_result[:user_name].should == 'Fernand'
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
describe '#id_browser' do
|
114
|
+
before :all do
|
115
|
+
@rack = Rack::Mole.new( nil )
|
116
|
+
end
|
117
|
+
|
118
|
+
it "should detect a browser type correctly" do
|
119
|
+
browser = @rack.send( :id_browser, "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30; .NET CLR 3.0.04506.648; InfoPath.2; MS-RTC LM 8; SPC 3.1 P1 Ta)")
|
120
|
+
browser.should == 'MSIE 7.0'
|
121
|
+
end
|
122
|
+
|
123
|
+
it "should return unknow if can't detect it" do
|
124
|
+
@rack.send( :id_browser, 'IBrowse' ).should == 'N/A'
|
125
|
+
end
|
89
126
|
end
|
90
127
|
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), %w[.. .. spec_helper])
|
2
|
+
|
3
|
+
describe Rackamole::Store::MongoDb do
|
4
|
+
|
5
|
+
describe "#mole" do
|
6
|
+
before( :all ) do
|
7
|
+
@store = Rackamole::Store::MongoDb.new(
|
8
|
+
:host => 'localhost',
|
9
|
+
:port => 27017,
|
10
|
+
:database => 'test_mole_mdb',
|
11
|
+
:logger => Rackamole::Logger.new( :file_name => $stdout, :log_level => 'info' ) )
|
12
|
+
@db = @store.database
|
13
|
+
end
|
14
|
+
|
15
|
+
before( :each ) do
|
16
|
+
@store.reset!
|
17
|
+
|
18
|
+
@args = OrderedHash.new
|
19
|
+
@args[:app_name] = "Test app"
|
20
|
+
@args[:environment] = :test
|
21
|
+
@args[:perf_issue] = false
|
22
|
+
@args[:ip] = "1.1.1.1"
|
23
|
+
@args[:browser] = "Ibrowse"
|
24
|
+
@args[:user_id] = 100
|
25
|
+
@args[:user_name] = "Fernand"
|
26
|
+
@args[:request_time] = 1.0
|
27
|
+
@args[:url] = "http://test_me/"
|
28
|
+
@args[:path] = "/fred"
|
29
|
+
@args[:method] = 'GET'
|
30
|
+
@args[:params] = { :blee => "duh".to_json }
|
31
|
+
@args[:session] = { :fred => 10.to_json }
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should mole a context based feature correctly" do
|
35
|
+
@store.mole( @args )
|
36
|
+
@store.features.count.should == 1
|
37
|
+
@store.logs.count.should == 1
|
38
|
+
|
39
|
+
feature = @store.features.find_one()
|
40
|
+
feature.should_not be_nil
|
41
|
+
feature['app'].should == 'Test app'
|
42
|
+
feature['env'].should == :test
|
43
|
+
feature['ctx'].should == '/fred'
|
44
|
+
|
45
|
+
log = @store.logs.find_one()
|
46
|
+
log.should_not be_nil
|
47
|
+
log['typ'].should == Rackamole::Store::MongoDb::FEATURE
|
48
|
+
log['fid'].should_not be_nil
|
49
|
+
log['par'].should == { 'blee' => 'duh'.to_json }
|
50
|
+
log['ip'].should == '1.1.1.1'
|
51
|
+
log['bro'].should == 'Ibrowse'
|
52
|
+
log['url'].should == 'http://test_me/'
|
53
|
+
log['met'].should == 'GET'
|
54
|
+
log['ses'].should == { 'fred' => '10' }
|
55
|
+
log['una'].should == 'Fernand'
|
56
|
+
log['uid'].should == 100
|
57
|
+
log['rti'].should == 1.0
|
58
|
+
log['did'].should_not be_nil
|
59
|
+
log['tid'].should_not be_nil
|
60
|
+
|
61
|
+
@store.features.find_one( Mongo::ObjectID.from_string( log['fid'] ) )['app'].should == 'Test app'
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should mole a rails feature correctly" do
|
65
|
+
@args[:path] = '/fred/blee/duh'
|
66
|
+
@args[:route_info] = { :controller => 'fred', :action => 'blee', :id => 'duh' }
|
67
|
+
@store.mole( @args )
|
68
|
+
|
69
|
+
@store.features.count.should == 1
|
70
|
+
@store.logs.count.should == 1
|
71
|
+
|
72
|
+
feature = @store.features.find_one()
|
73
|
+
feature.should_not be_nil
|
74
|
+
feature['ctl'].should == 'fred'
|
75
|
+
feature['act'].should == 'blee'
|
76
|
+
feature['ctx'].should be_nil
|
77
|
+
|
78
|
+
log = @store.logs.find_one()
|
79
|
+
log.should_not be_nil
|
80
|
+
log['typ'].should == Rackamole::Store::MongoDb::FEATURE
|
81
|
+
log['pat'].should_not be_nil
|
82
|
+
end
|
83
|
+
|
84
|
+
it "should reuse an existing feature" do
|
85
|
+
@store.mole( @args )
|
86
|
+
@store.mole( @args )
|
87
|
+
|
88
|
+
@store.features.count.should == 1
|
89
|
+
@store.logs.count.should == 2
|
90
|
+
end
|
91
|
+
|
92
|
+
it "should mole perf correctly" do
|
93
|
+
@args[:performance] = true
|
94
|
+
@store.mole( @args )
|
95
|
+
|
96
|
+
@store.features.count.should == 1
|
97
|
+
@store.logs.count.should == 1
|
98
|
+
|
99
|
+
feature = @store.features.find_one()
|
100
|
+
feature.should_not be_nil
|
101
|
+
|
102
|
+
log = @store.logs.find_one()
|
103
|
+
log.should_not be_nil
|
104
|
+
log['typ'].should == Rackamole::Store::MongoDb::PERFORMANCE
|
105
|
+
end
|
106
|
+
|
107
|
+
it 'should mole an exception correctly' do
|
108
|
+
@args[:stack] = ['fred']
|
109
|
+
@store.mole( @args )
|
110
|
+
|
111
|
+
@store.features.count.should == 1
|
112
|
+
@store.logs.count.should == 1
|
113
|
+
|
114
|
+
feature = @store.features.find_one()
|
115
|
+
feature.should_not be_nil
|
116
|
+
|
117
|
+
log = @store.logs.find_one()
|
118
|
+
log.should_not be_nil
|
119
|
+
log['typ'].should == Rackamole::Store::MongoDb::EXCEPTION
|
120
|
+
log['sta'].should == ['fred']
|
121
|
+
end
|
122
|
+
|
123
|
+
it 'should keep count an similar exceptions or perf issues' do
|
124
|
+
pending "NYI"
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
data/tasks/rdoc.rake
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'rake/rdoctask'
|
2
|
+
require 'darkfish-rdoc'
|
3
|
+
|
4
|
+
namespace :doc do
|
5
|
+
|
6
|
+
desc 'Generate RDoc documentation'
|
7
|
+
Rake::RDocTask.new do |rd|
|
8
|
+
rdoc = PROJ.rdoc
|
9
|
+
rd.main = rdoc.main
|
10
|
+
rd.rdoc_dir = rdoc.dir
|
11
|
+
|
12
|
+
incl = Regexp.new(rdoc.include.join('|'))
|
13
|
+
excl = Regexp.new(rdoc.exclude.join('|'))
|
14
|
+
files = PROJ.gem.files.find_all do |fn|
|
15
|
+
case fn
|
16
|
+
when excl; false
|
17
|
+
when incl; true
|
18
|
+
else false end
|
19
|
+
end
|
20
|
+
rd.rdoc_files.push(*files)
|
21
|
+
|
22
|
+
name = PROJ.name
|
23
|
+
rf_name = PROJ.rubyforge.name
|
24
|
+
|
25
|
+
title = "#{name}-#{PROJ.version} Documentation"
|
26
|
+
title = "#{rf_name}'s " + title if rf_name.valid? and rf_name != name
|
27
|
+
|
28
|
+
rd.options << "-t #{title}"
|
29
|
+
rd.options << "-SHN"
|
30
|
+
rd.options << "-f"
|
31
|
+
rd.options << "darkfish"
|
32
|
+
rd.options.concat(rdoc.opts)
|
33
|
+
end
|
34
|
+
|
35
|
+
desc 'Generate ri locally for testing'
|
36
|
+
task :ri => :clobber_ri do
|
37
|
+
sh "#{RDOC} --ri -o ri ."
|
38
|
+
end
|
39
|
+
|
40
|
+
task :clobber_ri do
|
41
|
+
rm_r 'ri' rescue nil
|
42
|
+
end
|
43
|
+
|
44
|
+
end # namespace :doc
|
45
|
+
|
46
|
+
desc 'Alias to doc:rdoc'
|
47
|
+
task :doc => 'doc:rdoc'
|
48
|
+
|
49
|
+
desc 'Remove all build products'
|
50
|
+
task :clobber => %w(doc:clobber_rdoc doc:clobber_ri)
|
51
|
+
|
52
|
+
remove_desc_for_task %w(doc:clobber_rdoc)
|
53
|
+
|
54
|
+
# EOF
|
data/tasks/setup.rb
CHANGED
data/tasks/spec.rake
CHANGED
data/tasks/test.rake
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rackamole
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Fernand Galiana
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-11-
|
12
|
+
date: 2009-11-21 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -33,14 +33,24 @@ dependencies:
|
|
33
33
|
version: 1.0.3
|
34
34
|
version:
|
35
35
|
- !ruby/object:Gem::Dependency
|
36
|
-
name:
|
36
|
+
name: mongo
|
37
37
|
type: :runtime
|
38
38
|
version_requirement:
|
39
39
|
version_requirements: !ruby/object:Gem::Requirement
|
40
40
|
requirements:
|
41
41
|
- - ">="
|
42
42
|
- !ruby/object:Gem::Version
|
43
|
-
version: 0.
|
43
|
+
version: 0.17.1
|
44
|
+
version:
|
45
|
+
- !ruby/object:Gem::Dependency
|
46
|
+
name: darkfish-rdoc
|
47
|
+
type: :runtime
|
48
|
+
version_requirement:
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 1.1.5
|
44
54
|
version:
|
45
55
|
- !ruby/object:Gem::Dependency
|
46
56
|
name: bones
|
@@ -64,22 +74,18 @@ description: "The MOle is a rack application that monitors user interactions wit
|
|
64
74
|
your coolest features are thought as such by your users. You will be able to elegantly record user\n\
|
65
75
|
interactions and leverage these findings for the next iteration of your application. "
|
66
76
|
email: fernand.galiana@gmail.com
|
67
|
-
executables:
|
68
|
-
|
77
|
+
executables: []
|
78
|
+
|
69
79
|
extensions: []
|
70
80
|
|
71
81
|
extra_rdoc_files:
|
72
|
-
-
|
73
|
-
- README.txt
|
82
|
+
- README.rdoc
|
74
83
|
- aaa.txt
|
75
|
-
- bin/rackamole
|
76
84
|
- samples/rails/moled/public/robots.txt
|
77
85
|
files:
|
78
|
-
-
|
79
|
-
- README.txt
|
86
|
+
- README.rdoc
|
80
87
|
- Rakefile
|
81
88
|
- aaa.txt
|
82
|
-
- bin/rackamole
|
83
89
|
- images/mole_logo.png
|
84
90
|
- images/mole_logo.psd
|
85
91
|
- images/mole_logo_small.png
|
@@ -88,8 +94,9 @@ files:
|
|
88
94
|
- lib/rackamole/interceptor.rb
|
89
95
|
- lib/rackamole/logger.rb
|
90
96
|
- lib/rackamole/mole.rb
|
97
|
+
- lib/rackamole/store.rb
|
91
98
|
- lib/rackamole/store/log.rb
|
92
|
-
- lib/rackamole/store/
|
99
|
+
- lib/rackamole/store/mongo_db.rb
|
93
100
|
- samples/rails/moled/README
|
94
101
|
- samples/rails/moled/Rakefile
|
95
102
|
- samples/rails/moled/app/controllers/application_controller.rb
|
@@ -152,7 +159,7 @@ files:
|
|
152
159
|
- spec/rackamole/logger_spec.rb
|
153
160
|
- spec/rackamole/mole_spec.rb
|
154
161
|
- spec/rackamole/store/log_spec.rb
|
155
|
-
- spec/rackamole/store/
|
162
|
+
- spec/rackamole/store/mongo_db_spec.rb
|
156
163
|
- spec/rackamole_spec.rb
|
157
164
|
- spec/spec_helper.rb
|
158
165
|
- tasks/bones.rake
|
@@ -160,6 +167,7 @@ files:
|
|
160
167
|
- tasks/git.rake
|
161
168
|
- tasks/notes.rake
|
162
169
|
- tasks/post_load.rake
|
170
|
+
- tasks/rdoc.rake
|
163
171
|
- tasks/rubyforge.rake
|
164
172
|
- tasks/setup.rb
|
165
173
|
- tasks/spec.rake
|
@@ -173,7 +181,7 @@ licenses: []
|
|
173
181
|
post_install_message:
|
174
182
|
rdoc_options:
|
175
183
|
- --main
|
176
|
-
- README.
|
184
|
+
- README.rdoc
|
177
185
|
require_paths:
|
178
186
|
- lib
|
179
187
|
required_ruby_version: !ruby/object:Gem::Requirement
|
data/History.txt
DELETED
data/bin/rackamole
DELETED
@@ -1,121 +0,0 @@
|
|
1
|
-
require 'mongo'
|
2
|
-
|
3
|
-
include Mongo
|
4
|
-
|
5
|
-
# TODO !! Need to deal with auth
|
6
|
-
module Rackamole
|
7
|
-
module Store
|
8
|
-
# Mongo adapter. Stores mole info in a mongo database.
|
9
|
-
# Two collections are available namely features and logs. Logs references
|
10
|
-
# the features collection.
|
11
|
-
class Mongo
|
12
|
-
|
13
|
-
attr_reader :connection
|
14
|
-
|
15
|
-
def initialize( options={} )
|
16
|
-
opts = default_options.merge( options )
|
17
|
-
@connection = Connection.new( opts[:host], opts[:port] ).db( opts[:database] )
|
18
|
-
end
|
19
|
-
|
20
|
-
# clear out db content
|
21
|
-
def reset!
|
22
|
-
features.clear
|
23
|
-
logs.clear
|
24
|
-
end
|
25
|
-
|
26
|
-
# Dump mole info to logger
|
27
|
-
def mole( args )
|
28
|
-
return if args.empty?
|
29
|
-
|
30
|
-
feature = find_or_create_feature( args )
|
31
|
-
log_feature( feature, args )
|
32
|
-
rescue => mole_boom
|
33
|
-
$stderr.puts "MOLE STORE CRAPPED OUT -- #{mole_boom}"
|
34
|
-
$stderr.puts mole_boom.backtrace.join( "\n " )
|
35
|
-
end
|
36
|
-
|
37
|
-
# Convenience to access mole features cltn
|
38
|
-
def features
|
39
|
-
@features ||= @connection['features']
|
40
|
-
end
|
41
|
-
|
42
|
-
# Convenience to access mole log cltn
|
43
|
-
def logs
|
44
|
-
@logs ||= @connection['logs']
|
45
|
-
end
|
46
|
-
|
47
|
-
# =======================================================================
|
48
|
-
private
|
49
|
-
|
50
|
-
# Set up mongo default options ie localhost host, default mongo port and
|
51
|
-
# the database being mole_mdb
|
52
|
-
def default_options
|
53
|
-
{
|
54
|
-
:host => 'localhost',
|
55
|
-
:port => 27017,
|
56
|
-
:database => 'mole_mdb'
|
57
|
-
}
|
58
|
-
end
|
59
|
-
|
60
|
-
# retrieves a feature if exists or create a new one otherwise
|
61
|
-
def find_or_create_feature( args )
|
62
|
-
if args[:route_info]
|
63
|
-
controller = args[:route_info][:controller]
|
64
|
-
action = args[:route_info][:action]
|
65
|
-
end
|
66
|
-
|
67
|
-
feature = find_feature( args[:app_name], args[:path], controller, action )
|
68
|
-
|
69
|
-
# Got one
|
70
|
-
return feature if feature
|
71
|
-
|
72
|
-
# If not create a new feature
|
73
|
-
row = { :app_name => args[:app_name] }
|
74
|
-
if controller and action
|
75
|
-
row['controller'] = controller
|
76
|
-
row['action'] = action
|
77
|
-
else
|
78
|
-
row['context'] = args[:path]
|
79
|
-
end
|
80
|
-
row['created_at'] = Time.now
|
81
|
-
row['updated_at'] = Time.now
|
82
|
-
features.insert( row )
|
83
|
-
end
|
84
|
-
|
85
|
-
# Attempt to find a mole feature
|
86
|
-
def find_feature( app_name, path, controller, action )
|
87
|
-
conds = { 'app_name' => app_name }
|
88
|
-
|
89
|
-
# For rails use controller/action
|
90
|
-
if controller and action
|
91
|
-
conds['controller'] = controller
|
92
|
-
conds['action'] = action
|
93
|
-
# otherwise use path...
|
94
|
-
else
|
95
|
-
conds['context'] = path
|
96
|
-
end
|
97
|
-
features.find_one( conds )
|
98
|
-
end
|
99
|
-
|
100
|
-
# Insert a new feature in the db
|
101
|
-
def log_feature( feature, args )
|
102
|
-
type = 'Feature'
|
103
|
-
type = 'Exception' if args[:stack]
|
104
|
-
type = 'Performance' if args[:performance]
|
105
|
-
|
106
|
-
row = {
|
107
|
-
:type => type,
|
108
|
-
:feature => ::DBRef.new( 'features', feature.instance_of?( ObjectID ) ? feature : feature['_id'] ),
|
109
|
-
:created_at => Time.now,
|
110
|
-
:updated_at => Time.now
|
111
|
-
}
|
112
|
-
|
113
|
-
skip_cols = [:app_name]
|
114
|
-
args.each do |k,v|
|
115
|
-
row[k] = v unless skip_cols.include?( k )
|
116
|
-
end
|
117
|
-
logs.insert( row )
|
118
|
-
end
|
119
|
-
end
|
120
|
-
end
|
121
|
-
end
|
@@ -1,120 +0,0 @@
|
|
1
|
-
require File.join(File.dirname(__FILE__), %w[.. .. spec_helper])
|
2
|
-
require 'mongo/util/ordered_hash'
|
3
|
-
|
4
|
-
describe Rackamole::Store::Mongo do
|
5
|
-
|
6
|
-
describe "#mole" do
|
7
|
-
before( :each ) do
|
8
|
-
@store = Rackamole::Store::Mongo.new( :host => 'localhost', :port => 27017, :database => 'test_mole_mdb' )
|
9
|
-
@store.reset!
|
10
|
-
|
11
|
-
@args = OrderedHash.new
|
12
|
-
@args[:app_name] = "Test app"
|
13
|
-
@args[:environment] = :test
|
14
|
-
@args[:perf_issue] = false
|
15
|
-
@args[:ip] = "1.1.1.1"
|
16
|
-
@args[:browser] = "Ibrowse"
|
17
|
-
@args[:user_id] = 100
|
18
|
-
@args[:user_name] = "Fernand"
|
19
|
-
@args[:request_time] = 1.0
|
20
|
-
@args[:url] = "http://test_me/"
|
21
|
-
@args[:path] = "/fred"
|
22
|
-
@args[:method] = 'GET'
|
23
|
-
@args[:params] = { :blee => "duh".to_json }
|
24
|
-
@args[:session] = { :fred => 10.to_json }
|
25
|
-
end
|
26
|
-
|
27
|
-
it "should mole a feature correctly" do
|
28
|
-
@store.mole( @args )
|
29
|
-
@store.features.count.should == 1
|
30
|
-
@store.logs.count.should == 1
|
31
|
-
|
32
|
-
feature = @store.features.find_one( {} )
|
33
|
-
feature.should_not be_nil
|
34
|
-
feature['app_name'].should == 'Test app'
|
35
|
-
feature['context'].should == '/fred'
|
36
|
-
feature['created_at'].should_not be_nil
|
37
|
-
feature['updated_at'].should_not be_nil
|
38
|
-
|
39
|
-
log = @store.logs.find_one( {} )
|
40
|
-
log.should_not be_nil
|
41
|
-
log['params'].should == { 'blee' => 'duh'.to_json }
|
42
|
-
log['ip'].should == '1.1.1.1'
|
43
|
-
log['browser'].should == 'Ibrowse'
|
44
|
-
log['environment'].should == :test
|
45
|
-
log['path'].should == '/fred'
|
46
|
-
log['url'].should == 'http://test_me/'
|
47
|
-
log['method'].should == 'GET'
|
48
|
-
log['session'].should == { 'fred' => '10' }
|
49
|
-
log['user_name'].should == 'Fernand'
|
50
|
-
log['user_id'].should == 100
|
51
|
-
log['request_time'].should == 1.0
|
52
|
-
log['perf_issue'].should == false
|
53
|
-
log['created_at'].should_not be_nil
|
54
|
-
log['updated_at'].should_not be_nil
|
55
|
-
@store.connection.dereference( log['feature'] )['app_name'].should == 'Test app'
|
56
|
-
end
|
57
|
-
|
58
|
-
it "should mole a rails feature correctly" do
|
59
|
-
@args[:path] = '/fred/blee/duh'
|
60
|
-
@args[:route_info] = { :controller => 'fred', :action => 'blee', :id => 'duh' }
|
61
|
-
@store.mole( @args )
|
62
|
-
|
63
|
-
@store.features.count.should == 1
|
64
|
-
@store.logs.count.should == 1
|
65
|
-
|
66
|
-
feature = @store.features.find_one( {} )
|
67
|
-
feature.should_not be_nil
|
68
|
-
feature['controller'].should == 'fred'
|
69
|
-
feature['action'].should == 'blee'
|
70
|
-
feature['context'].should be_nil
|
71
|
-
|
72
|
-
log = @store.logs.find_one( {} )
|
73
|
-
log.should_not be_nil
|
74
|
-
log['route_info'].should_not be_nil
|
75
|
-
end
|
76
|
-
|
77
|
-
it "should reuse an existing feature" do
|
78
|
-
@store.mole( @args )
|
79
|
-
@store.mole( @args )
|
80
|
-
|
81
|
-
@store.features.count.should == 1
|
82
|
-
@store.logs.count.should == 2
|
83
|
-
end
|
84
|
-
|
85
|
-
it "should mole perf correctly" do
|
86
|
-
@args[:perf_issue] = true
|
87
|
-
@store.mole( @args )
|
88
|
-
|
89
|
-
@store.features.count.should == 1
|
90
|
-
@store.logs.count.should == 1
|
91
|
-
|
92
|
-
feature = @store.features.find_one( {} )
|
93
|
-
feature.should_not be_nil
|
94
|
-
|
95
|
-
log = @store.logs.find_one( {} )
|
96
|
-
log.should_not be_nil
|
97
|
-
log['perf_issue'].should == true
|
98
|
-
end
|
99
|
-
|
100
|
-
it 'should mole an exception correctly' do
|
101
|
-
@args[:exception] = ['fred']
|
102
|
-
@store.mole( @args )
|
103
|
-
|
104
|
-
@store.features.count.should == 1
|
105
|
-
@store.logs.count.should == 1
|
106
|
-
|
107
|
-
feature = @store.features.find_one( {} )
|
108
|
-
feature.should_not be_nil
|
109
|
-
|
110
|
-
log = @store.logs.find_one( {} )
|
111
|
-
log.should_not be_nil
|
112
|
-
log['exception'].should == ['fred']
|
113
|
-
end
|
114
|
-
|
115
|
-
it 'should keep count an similar exceptions or perf issues' do
|
116
|
-
pending "NYI"
|
117
|
-
end
|
118
|
-
end
|
119
|
-
|
120
|
-
end
|