mongoid_traffic 0.2.5 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -0
- data/README.md +88 -91
- data/Rakefile +2 -19
- data/lib/mongoid_traffic.rb +4 -1
- data/lib/mongoid_traffic/log.rb +29 -49
- data/lib/mongoid_traffic/logger.rb +33 -88
- data/lib/mongoid_traffic/version.rb +1 -1
- data/mongoid_traffic.gemspec +10 -10
- metadata +15 -84
- data/lib/mongoid_traffic/controller_additions.rb +0 -31
- data/lib/mongoid_traffic/logger/bots.rb +0 -31
- data/lib/mongoid_traffic/logger/browser.rb +0 -29
- data/lib/mongoid_traffic/logger/geo_ip.rb +0 -20
- data/lib/mongoid_traffic/logger/referer.rb +0 -25
- data/test/mongoid_traffic/controller_additions_test.rb +0 -25
- data/test/mongoid_traffic/log_test.rb +0 -143
- data/test/mongoid_traffic/logger/bots_test.rb +0 -21
- data/test/mongoid_traffic/logger/browser_test.rb +0 -24
- data/test/mongoid_traffic/logger/geoip_test.rb +0 -15
- data/test/mongoid_traffic/logger/referer_test.rb +0 -28
- data/test/mongoid_traffic/logger_test.rb +0 -62
- data/test/support/database_cleaner.rb +0 -10
- data/test/support/mongoid.rb +0 -8
- data/test/test_helper.rb +0 -22
- data/vendor/mongoid_traffic/GeoIP.dat +0 -0
- data/vendor/mongoid_traffic/allagents.xml +0 -22134
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e2e697436b535a20d799099b53576a88bf7e0145ca1110010b7b27b05cab2f4d
|
4
|
+
data.tar.gz: d3d50e3806776e41747c2a46abe7d38da795cc8bc961794f3a273acaea0b5510
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dddd2282c221bd0a955ed6ffd23e0c886a0a1c79119fe8b3106bc55b9714e7330f62399c07af92fea2cd88aa85dfbcbab6ceaa16224ba8cbb8ee9ab004e85a17
|
7
|
+
data.tar.gz: 1a86c94669b57cfa3a08b20658ec456fa3623c1d1a45c172929253a333d98a4b812a861fa66ecf8e2ee7c717194bbb6ff6ef054c78f41936b38fb7e678a53b16
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,15 @@
|
|
1
1
|
# CHANGELOG
|
2
2
|
|
3
|
+
## 1.0.0
|
4
|
+
|
5
|
+
* BREAKING: refactored and abstracted for more general purpose
|
6
|
+
* removed bot detection
|
7
|
+
* removed user_agent
|
8
|
+
* removed geo_ip
|
9
|
+
* removed referer
|
10
|
+
* removed rails controller additions
|
11
|
+
* removed scope
|
12
|
+
|
3
13
|
## 0.2.5
|
4
14
|
|
5
15
|
* Mongoid 7 compatibility
|
data/README.md
CHANGED
@@ -30,154 +30,140 @@ Setup your class for storing the log:
|
|
30
30
|
|
31
31
|
```ruby
|
32
32
|
class MyLog
|
33
|
-
|
34
|
-
|
33
|
+
include Mongoid::Document
|
34
|
+
include MongoidTraffic::Log
|
35
35
|
end
|
36
36
|
```
|
37
37
|
|
38
38
|
Log your traffic like this:
|
39
39
|
|
40
40
|
```ruby
|
41
|
-
MongoidTraffic::Logger.log(MyLog
|
41
|
+
MongoidTraffic::Logger.log(MyLog)
|
42
42
|
```
|
43
43
|
|
44
44
|
Or, if you prefer, directly on the `MyLog` class:
|
45
45
|
|
46
46
|
```ruby
|
47
|
-
MyLog.log
|
47
|
+
MyLog.log
|
48
48
|
```
|
49
49
|
|
50
|
-
This will (by default) create
|
50
|
+
This will (by default) create four `MyLog` documents: each with `:df(date_from)` and `:dt(date_to)` fields specifying yearly, monthly, weekly and daily log. Each log has an `:access_count` attribute incremented with subsequent `.log` calls.
|
51
51
|
|
52
52
|
### Optional arguments
|
53
53
|
|
54
54
|
#### Time scope:
|
55
55
|
|
56
|
-
By default, the `.log` method creates/updates a document with aggregations for month and
|
56
|
+
By default, the `.log` method creates/updates a document with aggregations for year, month, week and day. Subset of those can be specified as:
|
57
57
|
|
58
58
|
```ruby
|
59
|
-
MyLog.log(time_scope: %i
|
59
|
+
MyLog.log(time_scope: %i[day])
|
60
60
|
```
|
61
61
|
|
62
|
-
The available options are: `%
|
62
|
+
The available options are: `%i[year month week day]`
|
63
63
|
|
64
64
|
#### Scope:
|
65
65
|
|
66
|
-
|
67
|
-
MyLog.log(scope: '/pages/123')
|
68
|
-
```
|
69
|
-
|
70
|
-
Allows to create several logs for different scopes of your application (typically URLs).
|
71
|
-
|
72
|
-
#### User-Agent:
|
66
|
+
It is possible to scope the log by an arbitrary number of parameters.
|
73
67
|
|
74
68
|
```ruby
|
75
|
-
MyLog
|
76
|
-
|
69
|
+
class MyLog
|
70
|
+
include Mongoid::Document
|
71
|
+
include MongoidTraffic::Log
|
77
72
|
|
78
|
-
|
73
|
+
belongs_to :page
|
79
74
|
|
80
|
-
|
81
|
-
|
75
|
+
scope :for_page, -> (page) { where(page: page) }
|
76
|
+
end
|
82
77
|
```
|
83
78
|
|
84
|
-
|
85
|
-
|
86
|
-
#### Referer:
|
79
|
+
For example:
|
87
80
|
|
88
81
|
```ruby
|
89
|
-
|
82
|
+
MongoidTraffic::Logger.log(MyLog.for_page(page))
|
90
83
|
```
|
91
84
|
|
92
|
-
|
85
|
+
or
|
93
86
|
|
94
87
|
```ruby
|
95
|
-
|
88
|
+
MyLog.for_page(page).log(page: my_page)
|
96
89
|
```
|
97
90
|
|
98
|
-
|
91
|
+
PS If you query by a scope often do not forget to add corresponding indexes to your log model.
|
99
92
|
|
100
|
-
|
93
|
+
#### Arbitrary counter:
|
101
94
|
|
102
|
-
|
103
|
-
|
104
|
-
```ruby
|
105
|
-
MyLog.log(ip_address: '123.123.123.123')
|
106
|
-
```
|
95
|
+
It is possible to count any number of arbitrary additional values. For example, to count unique country etc.
|
107
96
|
|
108
|
-
|
97
|
+
First, specify the additional counter in the log model (prefer short aliased field names):
|
109
98
|
|
110
99
|
```ruby
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
Uses the [GeoIP](https://github.com/cjheath/geoip) library to infer country_code from IP address.
|
115
|
-
|
116
|
-
You can use the [countries gem](https://github.com/hexorx/countries) to convert the country code to country name etc.
|
117
|
-
|
118
|
-
#### Unique id:
|
100
|
+
class MyLog
|
101
|
+
include Mongoid::Document
|
102
|
+
include MongoidTraffic::Log
|
119
103
|
|
120
|
-
|
121
|
-
|
104
|
+
additional_counter :c, as: :country
|
105
|
+
end
|
122
106
|
```
|
123
107
|
|
124
|
-
|
108
|
+
Track access by country as:
|
125
109
|
|
126
110
|
```ruby
|
127
|
-
|
111
|
+
MyLog.log(country: 'CZ')
|
112
|
+
MyLog.log(country: 'NL')
|
128
113
|
```
|
129
114
|
|
130
|
-
|
131
|
-
|
132
|
-
## Rails
|
133
|
-
|
134
|
-
In case of Rails, you can use the `.after_action` macro with the `#log_traffic` helper method in your controllers:
|
115
|
+
Which will create Hash with counts per country:
|
135
116
|
|
136
117
|
```ruby
|
137
|
-
|
138
|
-
after_action -> { log_traffic(MyLog) }, only: [:show]
|
139
|
-
end
|
118
|
+
{ _id: …, df(date_from): …, dt(date_to): …, ac(access_count): 2, c(country): { 'CZ' => 1, 'NL' => 1 } }
|
140
119
|
```
|
141
120
|
|
142
|
-
|
143
|
-
|
144
|
-
Additionally the `:log_scoped_traffic` method adds a scope by the current request path (`/pages/123`):
|
121
|
+
You can take advantage of the fact, that the underlying queries support dot-notation, and track on deeply nested hashes. For example, should you want to track access per browser:
|
145
122
|
|
146
123
|
```ruby
|
147
|
-
class
|
148
|
-
|
124
|
+
class MyLog
|
125
|
+
include Mongoid::Document
|
126
|
+
include MongoidTraffic::Log
|
127
|
+
|
128
|
+
additional_counter :b, as: :browser
|
149
129
|
end
|
150
130
|
```
|
151
131
|
|
152
|
-
|
132
|
+
Then track access by browser as:
|
153
133
|
|
154
134
|
```ruby
|
155
|
-
|
156
|
-
|
157
|
-
end
|
135
|
+
MyLog.log(browser: "Mac.Safari.8") # log access by Mac Safari 8
|
136
|
+
MyLog.log(browser: "Mac.Safari.7%2E1") # log access by Mac Safari 7.1
|
158
137
|
```
|
159
138
|
|
160
|
-
|
139
|
+
Which will create following log document:
|
161
140
|
|
162
141
|
```ruby
|
163
|
-
|
164
|
-
after_action -> { log_traffic(MyLog) }, only: [:show]
|
165
|
-
after_action -> { log_scoped_traffic(MyLog) }, only: [:show]
|
166
|
-
end
|
142
|
+
{ _id: …, df(date_from): …, dt(date_to): …, ac(access_count): 2, b(browser): { 'Mac' => { 'Safari' => { '8' => 1, '7%2E1' => 1 } } } }
|
167
143
|
```
|
168
144
|
|
145
|
+
Please note all `.` not intended to denote nesting need to be escaped (here as `%2E`).
|
146
|
+
|
169
147
|
## Accessing the log
|
170
148
|
|
171
|
-
The log
|
149
|
+
The log can be accessed using a combination of Mongoid Criteria and aggregation methods.
|
172
150
|
|
173
151
|
### Criteria
|
174
152
|
|
175
153
|
The following time based criteria are predefined as Mongoid scopes:
|
176
154
|
|
177
|
-
* `.
|
178
|
-
* `.
|
179
|
-
* `.
|
180
|
-
* `.
|
155
|
+
* `.day(date)`
|
156
|
+
* `.week(week, year)`
|
157
|
+
* `.month(month, year)`
|
158
|
+
* `.year(year)`
|
159
|
+
* `.for_dates(date_from, date_to)`
|
160
|
+
|
161
|
+
To select by log type:
|
162
|
+
|
163
|
+
* `.daily`
|
164
|
+
* `.weekly`
|
165
|
+
* `.monthly`
|
166
|
+
* `.yearly`
|
181
167
|
|
182
168
|
To narrow down by scope:
|
183
169
|
|
@@ -186,48 +172,59 @@ To narrow down by scope:
|
|
186
172
|
### Aggregation method
|
187
173
|
|
188
174
|
* `.aggregate_on(:access_count)`
|
189
|
-
* `.aggregate_on(
|
190
|
-
* `.aggregate_on(:referers)`
|
191
|
-
* `.aggregate_on(:countries)`
|
192
|
-
* `.aggregate_on(:unique_ids)`
|
175
|
+
* `.aggregate_on(ARBITRARY_COUNTER)`
|
193
176
|
|
194
177
|
Behind the scenes, this method will take all documents returned by your criteria and combines the values of the specified field (in case of `:access_count` it is simple sum of the values, in other cases it is sum of nested hashes).
|
195
178
|
|
196
|
-
###
|
197
|
-
|
198
|
-
Lastly, to retrieve the number of unique visits:
|
179
|
+
### Examples
|
199
180
|
|
200
|
-
|
181
|
+
#### Time
|
201
182
|
|
202
|
-
|
183
|
+
```ruby
|
184
|
+
MyLog.day(Date.today)
|
185
|
+
```
|
203
186
|
|
204
|
-
|
187
|
+
Eventually by date range (when using the `.for_dates` scope make sure to specify which log type you wish to access):
|
205
188
|
|
206
189
|
```ruby
|
207
|
-
MyLog.daily(Date.today)
|
190
|
+
MyLog.daily.for_dates(Date.yesterday, Date.today)
|
208
191
|
```
|
209
192
|
|
210
|
-
|
193
|
+
#### Scope
|
211
194
|
|
212
195
|
```ruby
|
213
|
-
MyLog.
|
196
|
+
MyLog.for_page(page).day(Date.today)
|
214
197
|
```
|
215
198
|
|
216
|
-
|
199
|
+
Make sure that the order of drilling down corresponds to the indexes on your model.
|
200
|
+
|
201
|
+
#### Aggregations
|
202
|
+
|
203
|
+
On access count:
|
217
204
|
|
218
205
|
```ruby
|
219
|
-
MyLog.
|
206
|
+
MyLog.day(Date.today).scoped_to('/pages/123').aggregate_on(:access_count) # => 1
|
220
207
|
```
|
221
208
|
|
222
209
|
The scope query accepts regular expressions, which allows for aggregations on specific parts of your site. For example should you want to query for all pages that have path beginning with '/blog':
|
223
210
|
|
224
211
|
```ruby
|
225
|
-
MyLog.
|
212
|
+
MyLog.month(8, 2014).scoped_to(/\A\/blog/).aggregate_on(:access_count) # => 1
|
226
213
|
```
|
227
214
|
|
228
|
-
|
215
|
+
On additional counter:
|
229
216
|
|
230
|
-
|
217
|
+
```ruby
|
218
|
+
MyLog.day(Date.today).scoped_to('/pages/123').aggregate_on(:country) # => { 'CZ' => 1, 'NL' => 1 }
|
219
|
+
```
|
220
|
+
|
221
|
+
#### Pluck
|
222
|
+
|
223
|
+
For plotting charts you might make use of standard `:pluck`:
|
224
|
+
|
225
|
+
```ruby
|
226
|
+
MyLog.daily.for_dates(Date.today - 1.week, Date.today).pluck(:date_from, :access_count) # => returns array of dates and counts per day
|
227
|
+
```
|
231
228
|
|
232
229
|
## Further reading
|
233
230
|
|
data/Rakefile
CHANGED
@@ -1,26 +1,9 @@
|
|
1
1
|
require 'bundler/gem_tasks'
|
2
2
|
require 'rake/testtask'
|
3
|
-
|
3
|
+
|
4
4
|
Rake::TestTask.new do |t|
|
5
5
|
t.libs << 'test'
|
6
6
|
t.pattern = 'test/**/*_test.rb'
|
7
7
|
end
|
8
8
|
|
9
|
-
|
10
|
-
require File.dirname(__FILE__) + "/lib/mongoid_traffic/logger/bots"
|
11
|
-
require File.dirname(__FILE__) + "/lib/mongoid_traffic/logger/geo_ip"
|
12
|
-
|
13
|
-
desc "output the list of bots"
|
14
|
-
task :update_bots_data do
|
15
|
-
`cd vendor/mongoid_traffic && curl -O #{MongoidTraffic::Logger::Bots::DATA_URL}`
|
16
|
-
end
|
17
|
-
|
18
|
-
desc "output the geoip.dat"
|
19
|
-
task :update_geoip_data do
|
20
|
-
`cd vendor/mongoid_traffic && curl -O #{MongoidTraffic::Logger::GeoIp::DATA_URL}`
|
21
|
-
`cd vendor/mongoid_traffic && gunzip -f GeoIP.dat.gz`
|
22
|
-
end
|
23
|
-
|
24
|
-
end
|
25
|
-
|
26
|
-
task :default => :test
|
9
|
+
task :default => :test
|
data/lib/mongoid_traffic.rb
CHANGED
data/lib/mongoid_traffic/log.rb
CHANGED
@@ -1,83 +1,63 @@
|
|
1
|
-
require 'logger'
|
2
|
-
|
3
1
|
module MongoidTraffic
|
4
2
|
module Log
|
5
3
|
def self.included(base)
|
6
4
|
base.extend ClassMethods
|
7
5
|
base.class_eval do
|
8
|
-
field :s, as: :scope, type: String
|
9
|
-
|
10
|
-
field :ac, as: :access_count, type: Integer
|
11
|
-
|
12
|
-
field :b, as: :browsers, type: Hash, default: {}
|
13
|
-
field :c, as: :countries, type: Hash, default: {}
|
14
|
-
field :r, as: :referers, type: Hash, default: {}
|
15
|
-
field :u, as: :unique_ids, type: Hash, default: {}
|
16
|
-
|
17
|
-
field :uat, as: :updated_at, type: Time
|
18
|
-
|
19
|
-
# ---------------------------------------------------------------------
|
20
|
-
|
21
6
|
field :df, as: :date_from, type: Date
|
22
7
|
field :dt, as: :date_to, type: Date
|
8
|
+
field :ts, as: :time_scope, type: Integer
|
23
9
|
|
24
|
-
|
10
|
+
field :ac, as: :access_count, type: Integer
|
11
|
+
field :uat, as: :updated_at, type: Time
|
25
12
|
|
26
13
|
validates :date_from, presence: true
|
27
14
|
validates :date_to, presence: true
|
28
15
|
|
29
|
-
|
16
|
+
scope :yearly, -> { where(ts: TIME_SCOPE_OPTIONS[:year]) }
|
17
|
+
scope :monthly, -> { where(ts: TIME_SCOPE_OPTIONS[:month]) }
|
18
|
+
scope :weekly, -> { where(ts: TIME_SCOPE_OPTIONS[:year]) }
|
19
|
+
scope :daily, -> { where(ts: TIME_SCOPE_OPTIONS[:day]) }
|
30
20
|
|
31
|
-
|
21
|
+
scope :for_dates, ->(date_from, date_to) { where(:date_from.lte => date_to, :date_to.gte => date_from) }
|
22
|
+
scope :year, ->(year) { yearly.for_dates(Date.parse("01/01/#{year}"), Date.parse("01/01/#{year}").at_end_of_year) }
|
23
|
+
scope :month, ->(month, year) { monthly.for_dates(Date.parse("01/#{month}/#{year}"), Date.parse("01/#{month}/#{year}").at_end_of_month) }
|
24
|
+
scope :week, ->(week, year) { weekly.for_dates(Date.commercial(year, week), Date.commercial(year, week).at_end_of_week) }
|
25
|
+
scope :day, ->(date) { daily.for_dates(date, date) }
|
32
26
|
|
33
|
-
|
34
|
-
|
35
|
-
scope :yearly, -> (year) { for_dates(Date.parse("01/01/#{year}"), Date.parse("01/01/#{year}").at_end_of_year) }
|
36
|
-
scope :monthly, -> (month, year) { for_dates(Date.parse("01/#{month}/#{year}"), Date.parse("01/#{month}/#{year}").at_end_of_month) }
|
37
|
-
scope :weekly, -> (week, year) { for_dates(Date.commercial(year, week), Date.commercial(year, week).at_end_of_week) }
|
38
|
-
scope :daily, -> (date) { for_dates(date, date) }
|
39
|
-
|
40
|
-
scope :scoped_to, -> (scope) { where(scope: scope) }
|
41
|
-
|
42
|
-
# ---------------------------------------------------------------------
|
43
|
-
|
44
|
-
index(scope: 1, date_from: 1, date_to: 1)
|
27
|
+
index time_scope: 1, date_from: 1, date_to: 1
|
45
28
|
end
|
46
29
|
end
|
47
30
|
|
48
|
-
# =====================================================================
|
49
|
-
|
50
31
|
module ClassMethods
|
51
32
|
def log(*args)
|
52
33
|
MongoidTraffic::Logger.log(self, *args)
|
53
34
|
end
|
54
35
|
|
55
|
-
def
|
56
|
-
|
57
|
-
when 'Integer' then sum(att)
|
58
|
-
when 'Hash' then sum_hash(att)
|
59
|
-
end
|
36
|
+
def additional_counter(name, as: nil)
|
37
|
+
field name, as: as, type: Hash, default: {}
|
60
38
|
end
|
61
39
|
|
62
|
-
def
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
40
|
+
def aggregate_on(att)
|
41
|
+
field = find_field(att)
|
42
|
+
case field.type.to_s
|
43
|
+
when 'Integer' then sum(field.name)
|
44
|
+
when 'Hash' then sum_hash(field.name)
|
67
45
|
end
|
68
46
|
end
|
69
47
|
|
70
|
-
|
71
|
-
|
72
|
-
|
48
|
+
private
|
49
|
+
|
50
|
+
def find_field(name)
|
51
|
+
fields.detect do |field_name, field|
|
52
|
+
field_name == name.to_s || field.options[:as].to_s == name.to_s
|
53
|
+
end.try(:last)
|
73
54
|
end
|
74
55
|
|
75
56
|
def sum_hash(field_name)
|
76
57
|
pluck(field_name).inject({}) do |res, h|
|
77
|
-
merger = proc do |
|
78
|
-
|
79
|
-
|
80
|
-
when Hash === v2 then v2
|
58
|
+
merger = proc do |_, v1, v2|
|
59
|
+
if Hash === v1 && Hash === v2 then v1.merge(v2, &merger)
|
60
|
+
elsif Hash === v2 then v2
|
81
61
|
else v1.to_i + v2.to_i
|
82
62
|
end
|
83
63
|
end
|