rackamole 0.0.4 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|