rack-analytics 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ pkg/*
2
+ *.gem
3
+ .bundle
4
+ webrat.log
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in rack-analytics.gemspec
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,48 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ rack-analytics (0.0.1)
5
+ activesupport
6
+ bson_ext
7
+ mongo
8
+ rack
9
+
10
+ GEM
11
+ remote: http://rubygems.org/
12
+ specs:
13
+ activesupport (3.0.3)
14
+ bson (1.2.0)
15
+ bson_ext (1.2.0)
16
+ mongo (1.2.0)
17
+ bson (>= 1.2.0)
18
+ nokogiri (1.4.4)
19
+ rack (1.2.1)
20
+ rack-test (0.5.7)
21
+ rack (>= 1.0)
22
+ riot (0.12.1)
23
+ rr
24
+ term-ansicolor
25
+ rr (1.0.2)
26
+ sinatra (1.1.2)
27
+ rack (~> 1.1)
28
+ tilt (~> 1.2)
29
+ term-ansicolor (1.0.5)
30
+ tilt (1.2.1)
31
+ webrat (0.7.3)
32
+ nokogiri (>= 1.2.0)
33
+ rack (>= 1.0)
34
+ rack-test (>= 0.5.3)
35
+
36
+ PLATFORMS
37
+ ruby
38
+
39
+ DEPENDENCIES
40
+ activesupport
41
+ bson_ext
42
+ mongo
43
+ rack
44
+ rack-analytics!
45
+ rack-test
46
+ riot
47
+ sinatra
48
+ webrat
data/README.markdown ADDED
@@ -0,0 +1,74 @@
1
+ # Rack-Analytics #
2
+
3
+ **Rack-Analytics** is a rack middleware that creates a log of all user requests to your application and saves them on a MongoDB database.
4
+
5
+ All requests are created on a separated thread, so it won't add a lot of overhead on each requests.
6
+
7
+ ## Instalation ##
8
+
9
+ If you're using Bundler (like Rails 3, for example), you can add this line on your `Gemfile`:
10
+
11
+ gem 'rack-analytics'
12
+
13
+ Then, run `bundle` to install the gem. If you don't use Bundler, you will need to install the gem manually with the following command:
14
+
15
+ gem install rack-analytics
16
+
17
+ Either way, with the gem installed, you can add this on your `config.ru`:
18
+
19
+ require 'rack/analytics'
20
+ use Rack::Analytics::RequestLogger
21
+
22
+ It will do the trick, connecting to MongoDB and start to log all your requests. If you need to change some of the configuration of the database, you can do:
23
+
24
+ require 'rack/analytics'
25
+
26
+ # To change the database name only
27
+ Rack::Analytics.db_name = 'mydb'
28
+
29
+ # To change the database connection completely
30
+ Rack::Analytics.db = Mongo::Connection.new.db 'mydb'
31
+
32
+ use Rack::Analytics::RequestLogger
33
+
34
+ **Rack-Analytics** runs on a threaded environment, and just like `db` and `db_name`, you can set the Queue and the Thread for the parallelism with the keys `queue` and `thread`, respectively **(i don't advise doing that, though)**.
35
+
36
+ You can also change the parser, to remove some of the fields (on the future, you'll be able to create your own fields based on the request headers):
37
+
38
+ require 'rack/analytics'
39
+
40
+ parser = Rack::Analytics::RequestParser.new
41
+
42
+ # Will log only the path and time
43
+ parser.only = ['time', 'path']
44
+
45
+ # Won't log the time
46
+ parser.except = 'time'
47
+
48
+ Rack::Analytics.parser = parser
49
+
50
+ use Rack::Analytics::RequestLogger
51
+
52
+ Be sure to just use one of those, since they are mutually excludent (and `only` has a preference over `except`).
53
+
54
+ ## Notes on Patches/Pull Requests ##
55
+
56
+ * Fork the project.
57
+ * Make your feature addition or bug fix.
58
+ * Add tests for it. This is important so I don't break it in a future version unintentionally.
59
+ * Commit, do not mess with rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
60
+ * Send me a pull request. Bonus points for topic branches.
61
+
62
+ ## Licence ##
63
+
64
+ Copyright (c) 2011, Cainã Costa <cainan.costa@gmail.com>
65
+
66
+ Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
67
+
68
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
69
+
70
+ ## Thanks ##
71
+
72
+ Those are the people that helped me with this project, both with code and/or with guidance. Put your name here if you create a pull request and think you deserves it!
73
+
74
+ * Rafael França <rafael.ufs@gmail.com>
data/Rakefile ADDED
@@ -0,0 +1,15 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'rake/testtask'
5
+
6
+ desc "Run all our tests"
7
+ task :test do
8
+ Rake::TestTask.new do |t|
9
+ t.libs << "test"
10
+ t.pattern = "test/**/*_test.rb"
11
+ t.verbose = false
12
+ end
13
+ end
14
+
15
+ task :default => :test
@@ -0,0 +1 @@
1
+ require 'rack/analytics'
@@ -0,0 +1,37 @@
1
+ require 'rack/analytics/request_logger'
2
+ require 'rack/analytics/request_parser'
3
+
4
+ require 'active_support/core_ext/module/attribute_accessors'
5
+
6
+ module Rack
7
+ module Analytics
8
+ mattr_accessor :queue
9
+ @@queue = Queue.new
10
+
11
+ mattr_accessor :parser
12
+ @@parser = RequestParser.new
13
+
14
+ mattr_accessor :db_name
15
+ @@db_name = 'rack-analytics'
16
+
17
+ mattr_accessor :db
18
+ @@db = Mongo::Connection.new.db(@@db_name)
19
+
20
+ mattr_accessor :thread
21
+ def self.thread
22
+ @@thread ||= Thread.new do
23
+ while env = queue.pop
24
+ db['views'].insert parser.parse(env).data
25
+ end
26
+ end
27
+ end
28
+
29
+ def self.finish!
30
+ queue << nil
31
+ thread.join
32
+ @@thread = nil
33
+
34
+ thread
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,18 @@
1
+ require "thread"
2
+
3
+ module Rack
4
+ module Analytics
5
+ class RequestLogger
6
+ def initialize app, options = {}
7
+ @app = app
8
+ @options = options
9
+ end
10
+
11
+ def call env
12
+ Rack::Analytics.queue << env if env['REQUEST_METHOD'] == 'GET'
13
+
14
+ @app.call(env)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,33 @@
1
+ module Rack
2
+ module Analytics
3
+ class RequestParser
4
+ attr_reader :data
5
+
6
+ DEFAULT_KEYS = ['time', 'path', 'user_agent', 'referral']
7
+
8
+ def except=(values)
9
+ @except = values.to_a
10
+ end
11
+
12
+ def only=(values)
13
+ @only = values.to_a
14
+ end
15
+
16
+ def parse request
17
+ @data = {}
18
+
19
+ @data['time'] = Time.now if to_parse.include? 'time'
20
+ @data['path'] = request['PATH_INFO'] if to_parse.include? 'path'
21
+ @data['user_agent'] = request['HTTP_USER_AGENT'] if to_parse.include? 'user_agent'
22
+ @data['referral'] = request['HTTP_REFERER'] if to_parse.include? 'referral'
23
+
24
+ return self
25
+ end
26
+
27
+ private
28
+ def to_parse
29
+ @only ? @only.to_a : DEFAULT_KEYS - @except.to_a
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,5 @@
1
+ module Rack
2
+ module Analytics
3
+ VERSION = "0.0.1"
4
+ end
5
+ end
@@ -0,0 +1,30 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "rack/analytics/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "rack-analytics"
7
+ s.version = Rack::Analytics::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Cainã Costa"]
10
+ s.email = ["cainan.costa@gmail.com"]
11
+ s.homepage = "http://rubygems.org/gems/rack-analytics"
12
+ s.summary = %q{A rack middleware that collects access statistics}
13
+ s.description = %q{A rack middleware that collects access statistics and saves them on a MongoDB database.}
14
+
15
+ s.rubyforge_project = "rack-analytics"
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
+ s.require_paths = ["lib"]
21
+
22
+ s.add_runtime_dependency('rack')
23
+ s.add_runtime_dependency('bson_ext')
24
+ s.add_runtime_dependency('mongo')
25
+ s.add_runtime_dependency('activesupport')
26
+
27
+ s.add_development_dependency('riot')
28
+ s.add_development_dependency('sinatra')
29
+ s.add_development_dependency('rack-test')
30
+ end
@@ -0,0 +1,107 @@
1
+ require 'teststrap'
2
+
3
+ context "Rack::Analytics::RequestLogger" do
4
+ helper(:db) { mongo }
5
+ teardown { Rack::Analytics.finish! }
6
+
7
+ context "should render a get request correctly" do
8
+ setup { get '/' }
9
+
10
+ asserts('response is ok') { last_response.ok? }
11
+ asserts('response has correct body') { last_response.body }.equals "homepage"
12
+ end
13
+
14
+ context "should render a get request on a inner path correctly" do
15
+ setup { get '/inner-page' }
16
+
17
+ asserts('response is ok') { last_response.ok? }
18
+ asserts('response has correct body') { last_response.body }.equals "inner page"
19
+ end
20
+
21
+ context "should render a post request correctly" do
22
+ setup { post '/' }
23
+
24
+ asserts('response is ok') { last_response.ok? }
25
+ asserts('response has correct body') { last_response.body }.equals "homepage with post"
26
+ end
27
+
28
+ context "should render a put request correctly" do
29
+ setup { put '/' }
30
+
31
+ asserts('response is ok') { last_response.ok? }
32
+ asserts('response has correct body') { last_response.body }.equals "homepage with put"
33
+ end
34
+
35
+ context "should render a delete request correctly" do
36
+ setup { delete '/' }
37
+
38
+ asserts('response is ok') { last_response.ok? }
39
+ asserts('response has correct body') { last_response.body }.equals "homepage with delete"
40
+ end
41
+
42
+ context "should create a access document when visiting the page" do
43
+ setup do
44
+ db.drop_collection 'views'
45
+
46
+ get '/'
47
+ end
48
+
49
+ asserts('counter has incremented') { db['views'].count }.equals 1
50
+ end
51
+
52
+ context "shouldn't create a access document when with post, put and delete" do
53
+ setup do
54
+ db.drop_collection 'views'
55
+
56
+ post '/'
57
+ put '/'
58
+ delete '/'
59
+ end
60
+
61
+ asserts("counter hasn't incremented") { db['views'].count }.equals 0
62
+ end
63
+
64
+ context "should save the path of the access" do
65
+ setup do
66
+ db.drop_collection 'views'
67
+
68
+ get '/'
69
+ end
70
+
71
+ asserts('it should have a time key') { db['views'].find_one }.includes 'path'
72
+ asserts('it should have a time set') { db['views'].find_one['path'] }.equals '/'
73
+ end
74
+
75
+ context "should save the time of the access" do
76
+ setup do
77
+ db.drop_collection 'views'
78
+
79
+ get '/'
80
+ end
81
+
82
+ asserts('it should have a time key') { db['views'].find_one }.includes 'time'
83
+ asserts('it should have a time set') { db['views'].find_one['time'] }.kind_of Time
84
+ end
85
+
86
+ context "should save the referral information" do
87
+ setup do
88
+ db.drop_collection 'views'
89
+
90
+ get '/', {}, 'HTTP_REFERER' => 'http://www.google.com'
91
+ end
92
+
93
+ asserts('it should have a referral key') { db['views'].find_one }.includes 'referral'
94
+ asserts('it should have a correct referral set') { db['views'].find_one['referral'] }.equals 'http://www.google.com'
95
+ end
96
+
97
+ context "should save the user agent information" do
98
+ setup do
99
+ db.drop_collection 'views'
100
+
101
+ get 'views', {}, 'HTTP_USER_AGENT' => 'Firefox'
102
+ end
103
+
104
+ asserts('it should have a user agent key') { db['views'].find_one }.includes 'user_agent'
105
+ asserts('it should have a correct user agent set') { db['views'].find_one['user_agent'] }.equals 'Firefox'
106
+ end
107
+ end
@@ -0,0 +1,47 @@
1
+ require 'teststrap'
2
+
3
+ context 'Rack::Analytics::RequestParser' do
4
+ helper(:request) { {"HTTP_HOST"=>"example.org", "SERVER_NAME"=>"example.org",
5
+ "HTTP_USER_AGENT"=>"Firefox", "CONTENT_LENGTH"=>"0", "HTTPS"=>"off",
6
+ "REMOTE_ADDR"=>"127.0.0.1", "PATH_INFO"=>"/",
7
+ "SCRIPT_NAME"=>"", "HTTP_COOKIE"=>"", "SERVER_PORT"=>"80",
8
+ "REQUEST_METHOD"=>"GET", "QUERY_STRING"=>"",
9
+ "HTTP_REFERER"=> "http://www.google.com"} }
10
+
11
+ context "should parse the default attributes correctly" do
12
+ setup { Rack::Analytics::RequestParser.new.parse(request) }
13
+
14
+ asserts('it should save the time') { topic.data['time'] }.kind_of Time
15
+ asserts('it should save the path') { topic.data['path'] }.equals '/'
16
+ asserts('it should save the user agent') { topic.data['user_agent'] }.equals 'Firefox'
17
+ asserts('it should save the referral') { topic.data['referral'] }.equals 'http://www.google.com'
18
+ end
19
+
20
+ context "should accept exceptions" do
21
+ setup { Rack::Analytics::RequestParser.new }
22
+
23
+ asserts ('it should accept single arguments') do
24
+ topic.except = 'time'
25
+ topic.parse(request).data['time']
26
+ end.nil
27
+
28
+ asserts ('it should accept multiple values as arguments') do
29
+ topic.except = ['time']
30
+ topic.parse(request).data['time']
31
+ end.nil
32
+ end
33
+
34
+ context "should handle 'only'" do
35
+ setup { Rack::Analytics::RequestParser.new }
36
+
37
+ asserts ('it should accept single arguments') do
38
+ topic.only = 'time'
39
+ topic.parse(request).data['path']
40
+ end.nil
41
+
42
+ asserts ('it should accept multiple values as arguments') do
43
+ topic.except = ['time', 'path']
44
+ topic.parse(request).data['user_agent']
45
+ end.nil
46
+ end
47
+ end
@@ -0,0 +1,23 @@
1
+ require 'sinatra'
2
+
3
+ class DummyApp < Sinatra::Application
4
+ get '/' do
5
+ 'homepage'
6
+ end
7
+
8
+ get '/inner-page' do
9
+ 'inner page'
10
+ end
11
+
12
+ post '/' do
13
+ 'homepage with post'
14
+ end
15
+
16
+ put '/' do
17
+ 'homepage with put'
18
+ end
19
+
20
+ delete '/' do
21
+ 'homepage with delete'
22
+ end
23
+ end
@@ -0,0 +1,15 @@
1
+ module TestHelpers
2
+ def app
3
+ Rack::Builder.new do
4
+ Rack::Analytics.db = mongo
5
+
6
+ use Rack::Analytics::RequestLogger
7
+ run DummyApp
8
+ end
9
+ end
10
+
11
+ def mongo
12
+ connection = Mongo::Connection.new
13
+ connection.db 'rack-analytics-test'
14
+ end
15
+ end
data/test/teststrap.rb ADDED
@@ -0,0 +1,13 @@
1
+ require 'rubygems'
2
+ require 'rack/test'
3
+ require 'mongo'
4
+ require 'riot'
5
+
6
+ require 'test/support/dummy_app'
7
+ require 'test/support/test_helpers'
8
+
9
+ require 'rack/analytics'
10
+
11
+ include Rack::Test::Methods
12
+
13
+ include TestHelpers
metadata ADDED
@@ -0,0 +1,184 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rack-analytics
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - "Cain\xC3\xA3 Costa"
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-01-31 00:00:00 -02:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: rack
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 3
30
+ segments:
31
+ - 0
32
+ version: "0"
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: bson_ext
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ hash: 3
44
+ segments:
45
+ - 0
46
+ version: "0"
47
+ type: :runtime
48
+ version_requirements: *id002
49
+ - !ruby/object:Gem::Dependency
50
+ name: mongo
51
+ prerelease: false
52
+ requirement: &id003 !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ hash: 3
58
+ segments:
59
+ - 0
60
+ version: "0"
61
+ type: :runtime
62
+ version_requirements: *id003
63
+ - !ruby/object:Gem::Dependency
64
+ name: activesupport
65
+ prerelease: false
66
+ requirement: &id004 !ruby/object:Gem::Requirement
67
+ none: false
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ hash: 3
72
+ segments:
73
+ - 0
74
+ version: "0"
75
+ type: :runtime
76
+ version_requirements: *id004
77
+ - !ruby/object:Gem::Dependency
78
+ name: riot
79
+ prerelease: false
80
+ requirement: &id005 !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ hash: 3
86
+ segments:
87
+ - 0
88
+ version: "0"
89
+ type: :development
90
+ version_requirements: *id005
91
+ - !ruby/object:Gem::Dependency
92
+ name: sinatra
93
+ prerelease: false
94
+ requirement: &id006 !ruby/object:Gem::Requirement
95
+ none: false
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ hash: 3
100
+ segments:
101
+ - 0
102
+ version: "0"
103
+ type: :development
104
+ version_requirements: *id006
105
+ - !ruby/object:Gem::Dependency
106
+ name: rack-test
107
+ prerelease: false
108
+ requirement: &id007 !ruby/object:Gem::Requirement
109
+ none: false
110
+ requirements:
111
+ - - ">="
112
+ - !ruby/object:Gem::Version
113
+ hash: 3
114
+ segments:
115
+ - 0
116
+ version: "0"
117
+ type: :development
118
+ version_requirements: *id007
119
+ description: A rack middleware that collects access statistics and saves them on a MongoDB database.
120
+ email:
121
+ - cainan.costa@gmail.com
122
+ executables: []
123
+
124
+ extensions: []
125
+
126
+ extra_rdoc_files: []
127
+
128
+ files:
129
+ - .gitignore
130
+ - Gemfile
131
+ - Gemfile.lock
132
+ - README.markdown
133
+ - Rakefile
134
+ - lib/rack-analytics.rb
135
+ - lib/rack/analytics.rb
136
+ - lib/rack/analytics/request_logger.rb
137
+ - lib/rack/analytics/request_parser.rb
138
+ - lib/rack/analytics/version.rb
139
+ - rack-analytics.gemspec
140
+ - test/request_logger_test.rb
141
+ - test/request_parser_test.rb
142
+ - test/support/dummy_app.rb
143
+ - test/support/test_helpers.rb
144
+ - test/teststrap.rb
145
+ has_rdoc: true
146
+ homepage: http://rubygems.org/gems/rack-analytics
147
+ licenses: []
148
+
149
+ post_install_message:
150
+ rdoc_options: []
151
+
152
+ require_paths:
153
+ - lib
154
+ required_ruby_version: !ruby/object:Gem::Requirement
155
+ none: false
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ hash: 3
160
+ segments:
161
+ - 0
162
+ version: "0"
163
+ required_rubygems_version: !ruby/object:Gem::Requirement
164
+ none: false
165
+ requirements:
166
+ - - ">="
167
+ - !ruby/object:Gem::Version
168
+ hash: 3
169
+ segments:
170
+ - 0
171
+ version: "0"
172
+ requirements: []
173
+
174
+ rubyforge_project: rack-analytics
175
+ rubygems_version: 1.3.7
176
+ signing_key:
177
+ specification_version: 3
178
+ summary: A rack middleware that collects access statistics
179
+ test_files:
180
+ - test/request_logger_test.rb
181
+ - test/request_parser_test.rb
182
+ - test/support/dummy_app.rb
183
+ - test/support/test_helpers.rb
184
+ - test/teststrap.rb