log_sense 1.5.1 → 1.5.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.org +24 -1
- data/Gemfile.lock +12 -5
- data/LICENSE.txt +9 -2
- data/README.org +13 -10
- data/ip_locations/dbip-country-lite.sqlite3 +0 -0
- data/lib/log_sense/apache_data_cruncher.rb +6 -0
- data/lib/log_sense/emitter.rb +127 -55
- data/lib/log_sense/options_parser.rb +6 -2
- data/lib/log_sense/templates/_log_structure.html.erb +6 -3
- data/lib/log_sense/templates/_output_table.html.erb +49 -1
- data/lib/log_sense/templates/_output_table.txt.erb +3 -1
- data/lib/log_sense/templates/_report_data.html.erb +4 -4
- data/lib/log_sense/templates/_summary.html.erb +10 -3
- data/lib/log_sense/version.rb +1 -1
- data/log_sense.gemspec +21 -21
- metadata +6 -7
- data/sample_logs/empty_log.log +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5f37b2247014af9bccfb8bdd205b54eb51cbef35495904444765648a96e0b9ac
|
4
|
+
data.tar.gz: a9cd03afb6770bf854791b8ec93e87d09532828d894b28f5dde670f1af1b1b1d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 62981216e38b92e1c3ea227726dccd592c441de32519a4df6f39bac127f55da32e82b4a1a14897b09b7032c7b3f9506a14e80e49dc43bfe8c9d86bfaa340da82
|
7
|
+
data.tar.gz: e5e6180008a12561d688668cffb2ef3d885d7733cf877aafaed0397b9fa0e91dd12a2998b19006dad4fe6b202bd203a00757f86d5d37efee777dbc7be43e5ab1
|
data/CHANGELOG.org
CHANGED
@@ -2,6 +2,29 @@
|
|
2
2
|
#+AUTHOR: Adolfo Villafiorita
|
3
3
|
#+STARTUP: showall
|
4
4
|
|
5
|
+
* 1.5.2
|
6
|
+
|
7
|
+
- [User] Updated DB-IP country file.
|
8
|
+
- [User] Added reports "Missed Pages by IP" and "Missed Resources by
|
9
|
+
IP". It can help pinpoint attack sources.
|
10
|
+
- [User] Added report "Combined Platform", which puts together
|
11
|
+
Browser, OS, and IP.
|
12
|
+
- [User] Summary now shows total size transferred.
|
13
|
+
- [User] Added link to DB-IP page for IPs in some tables.
|
14
|
+
- [User] Added count of IPs by Country.
|
15
|
+
- [User] Improved textual report: values in cells holding multiple
|
16
|
+
values (e.g. IPs) are now shown in distinct lines in the cell. A new
|
17
|
+
option -r limits the number of lines shown per cell.
|
18
|
+
- [Default] The number of rows initially shown in HTML reports is now 25.
|
19
|
+
- [Default] Default for number of entries in textual report is now
|
20
|
+
100 (used to be 900).
|
21
|
+
- [Fixed] The size column in HTML reports is now sorted numerically.
|
22
|
+
- [Code] Improved performances of DataTable rendering, using the
|
23
|
+
dataRender flag.
|
24
|
+
- [Code] Use trim_mode in ERB to avoid empty lines in HTML output.
|
25
|
+
- [Code] Moved to the debug gem.
|
26
|
+
- [Gem] Updated email and author's name.
|
27
|
+
|
5
28
|
* 1.5.1
|
6
29
|
|
7
30
|
- [User] Option --input-files allows to specify input files
|
@@ -16,7 +39,7 @@
|
|
16
39
|
- [User] Present Unique Visits / day as integer
|
17
40
|
- [User] Added Country and Streaks report for rails
|
18
41
|
- [User] Changed Streak report in Apache
|
19
|
-
- [Gem] Updated
|
42
|
+
- [Gem] Updated DB-IP
|
20
43
|
- [Gem] Updated Bundle
|
21
44
|
- [Code] Refactored all reports, so that they are specified
|
22
45
|
in the same way
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
log_sense (1.
|
4
|
+
log_sense (1.5.2)
|
5
5
|
browser
|
6
6
|
ipaddr
|
7
7
|
iso_country_codes
|
@@ -12,21 +12,28 @@ GEM
|
|
12
12
|
remote: https://rubygems.org/
|
13
13
|
specs:
|
14
14
|
browser (5.3.1)
|
15
|
-
|
15
|
+
debug (1.6.2)
|
16
|
+
irb (>= 1.3.6)
|
17
|
+
reline (>= 0.3.1)
|
18
|
+
io-console (0.5.11)
|
16
19
|
ipaddr (1.2.4)
|
20
|
+
irb (1.4.1)
|
21
|
+
reline (>= 0.3.0)
|
17
22
|
iso_country_codes (0.7.8)
|
18
23
|
minitest (5.15.0)
|
19
24
|
rake (12.3.3)
|
20
|
-
|
25
|
+
reline (0.3.1)
|
26
|
+
io-console (~> 0.5)
|
27
|
+
sqlite3 (1.4.4)
|
21
28
|
terminal-table (3.0.2)
|
22
29
|
unicode-display_width (>= 1.1.1, < 3)
|
23
|
-
unicode-display_width (2.
|
30
|
+
unicode-display_width (2.2.0)
|
24
31
|
|
25
32
|
PLATFORMS
|
26
33
|
ruby
|
27
34
|
|
28
35
|
DEPENDENCIES
|
29
|
-
|
36
|
+
debug
|
30
37
|
log_sense!
|
31
38
|
minitest
|
32
39
|
rake (~> 12.0)
|
data/LICENSE.txt
CHANGED
@@ -1,5 +1,4 @@
|
|
1
|
-
The MIT License (MIT)
|
2
|
-
|
1
|
+
The source code is distributed under the terms of the MIT License (MIT)
|
3
2
|
Copyright (c) 2021 Shair.Tech
|
4
3
|
|
5
4
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
@@ -19,3 +18,11 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
18
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
19
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
20
|
THE SOFTWARE.
|
21
|
+
|
22
|
+
==============================================================================
|
23
|
+
LogSense uses data from The free DB-IP Lite database for geolocation purposes.
|
24
|
+
|
25
|
+
The DB-IP Lite Database is licensed under a Creative Commons Attribution 4.0
|
26
|
+
International License.
|
27
|
+
|
28
|
+
For more information: https://db-ip.com
|
data/README.org
CHANGED
@@ -14,10 +14,12 @@ LogSense reports the following data:
|
|
14
14
|
- Visitors, hits, unique visitors, bandwidth used
|
15
15
|
- Most accessed HTML pages
|
16
16
|
- Most accessed resources
|
17
|
+
- Missed resources (also by IP) which helps highlight
|
18
|
+
potential attacks
|
17
19
|
- Response statuses
|
18
20
|
- Referers
|
19
21
|
- OS, browsers, and devices
|
20
|
-
- IP Country location, thanks to the
|
22
|
+
- IP Country location, thanks to the DP-IP lite country DB
|
21
23
|
- Streaks: resources accessed by a given IP over time
|
22
24
|
- Performance of Rails requests
|
23
25
|
|
@@ -81,32 +83,33 @@ generated files are then made available on a private area on the web.
|
|
81
83
|
-o, --output-file=OUTPUT_FILE Output file
|
82
84
|
-b, --begin=DATE Consider entries after or on DATE
|
83
85
|
-e, --end=DATE Consider entries before or on DATE
|
84
|
-
-l, --limit=N Limit to the N most requested resources (defaults to
|
85
|
-
-w, --width=WIDTH Maximum width of
|
86
|
+
-l, --limit=N Limit to the N most requested resources (defaults to 100)
|
87
|
+
-w, --width=WIDTH Maximum width of long columns in textual reports
|
88
|
+
-r, --rows=ROWS Maximum number of rows for columns with multiple entries in textual reports
|
86
89
|
-c, --crawlers=POLICY Decide what to do with crawlers (applies to Apache Logs)
|
87
90
|
-n, --no-selfpolls Ignore self poll entries (requests from ::1; applies to Apache Logs)
|
88
91
|
--verbose Inform about progress (prints to STDERR)
|
89
92
|
-v, --version Prints version information
|
90
93
|
-h, --help Prints this help
|
91
94
|
|
92
|
-
This is version 1.5.
|
95
|
+
This is version 1.5.2
|
93
96
|
|
94
97
|
Output formats
|
95
|
-
rails parsing can produce the following outputs:
|
96
|
-
- sqlite
|
97
|
-
- txt
|
98
|
-
- html
|
99
98
|
apache parsing can produce the following outputs:
|
100
99
|
- sqlite
|
100
|
+
- html
|
101
101
|
- txt
|
102
|
+
rails parsing can produce the following outputs:
|
103
|
+
- sqlite
|
102
104
|
- html
|
105
|
+
- txt
|
103
106
|
#+end_example
|
104
107
|
|
105
108
|
Examples:
|
106
109
|
|
107
110
|
#+begin_example sh
|
108
111
|
log_sense -f apache -i access.log -t txt > access-data.txt
|
109
|
-
log_sense -f rails -i production.log -t html -o performance.
|
112
|
+
log_sense -f rails -i production.log -t html -o performance.html
|
110
113
|
#+end_example
|
111
114
|
|
112
115
|
|
@@ -138,7 +141,7 @@ the known bugs.)
|
|
138
141
|
|
139
142
|
* License
|
140
143
|
|
141
|
-
|
144
|
+
Source code distributed under the terms of the [[http://opensource.org/licenses/MIT][MIT License]].
|
142
145
|
|
143
146
|
Geolocation is made possible by the DB-IP.com IP to City database,
|
144
147
|
released under a CC license.
|
Binary file
|
@@ -101,6 +101,9 @@ module LogSense
|
|
101
101
|
@missed_pages = db.execute "SELECT path, count(path), count(distinct(unique_visitor)), status from LogLine where #{bad_statuses} and #{html_page} and #{filter} group by path order by count(path) desc limit #{options[:limit]}"
|
102
102
|
@missed_resources = db.execute "SELECT path, count(path), count(distinct(unique_visitor)), status from LogLine where #{bad_statuses} and #{filter} group by path order by count(path) desc limit #{options[:limit]}"
|
103
103
|
|
104
|
+
@missed_pages_by_ip = db.execute "SELECT ip, path, status from LogLine where #{bad_statuses} and #{html_page} and #{filter} limit #{options[:limit]}"
|
105
|
+
@missed_resources_by_ip = db.execute "SELECT ip, path, status from LogLine where #{bad_statuses} and #{filter} limit #{options[:limit]}"
|
106
|
+
|
104
107
|
@statuses = db.execute "SELECT status, count(status) from LogLine where #{filter} group by status order by status"
|
105
108
|
|
106
109
|
@by_day_4xx = db.execute "SELECT date(datetime), count(datetime) from LogLine where substr(status, 1,1) == '4' and #{filter} group by date(datetime)"
|
@@ -113,6 +116,9 @@ module LogSense
|
|
113
116
|
|
114
117
|
@browsers = db.execute "SELECT browser, count(browser), count(distinct(unique_visitor)), #{human_readable_size} from LogLine where #{filter} group by browser order by count(browser) desc"
|
115
118
|
@platforms = db.execute "SELECT platform, count(platform), count(distinct(unique_visitor)), #{human_readable_size} from LogLine where #{filter} group by platform order by count(platform) desc"
|
119
|
+
|
120
|
+
@combined_platforms = db.execute "SELECT browser, platform, ip, count(datetime), #{human_readable_size} from LogLine where #{filter} group by browser, platform, ip order by count(datetime) desc limit #{options[:limit]}"
|
121
|
+
|
116
122
|
@referers = db.execute "SELECT referer, count(referer), count(distinct(unique_visitor)), #{human_readable_size} from LogLine where #{filter} group by referer order by count(referer) desc limit #{options[:limit]}"
|
117
123
|
|
118
124
|
@ips = db.execute "SELECT ip, count(ip), count(distinct(unique_visitor)), #{human_readable_size} from LogLine where #{filter} group by ip order by count(ip) desc limit #{options[:limit]}"
|
data/lib/log_sense/emitter.rb
CHANGED
@@ -4,10 +4,24 @@ require 'json'
|
|
4
4
|
require 'erb'
|
5
5
|
require 'ostruct'
|
6
6
|
module LogSense
|
7
|
+
WORDS_SEPARATOR = ' · '
|
8
|
+
|
7
9
|
#
|
8
10
|
# Emit Data
|
9
11
|
#
|
10
12
|
module Emitter
|
13
|
+
def self.human_readable_size(size)
|
14
|
+
if size < 1024
|
15
|
+
"%d B" % size
|
16
|
+
elsif size < 1024 * 1024
|
17
|
+
"%.2f KB" % (size.to_f / 1024)
|
18
|
+
elsif size < 1024 * 1024 * 1024
|
19
|
+
"%.2f MB" % (size.to_f / (1024 * 1024))
|
20
|
+
else
|
21
|
+
"%.2f GB" % (size.to_f / (1024 * 1024 * 1024))
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
11
25
|
def self.emit(data = {}, options = {})
|
12
26
|
@input_format = options[:input_format] || 'apache'
|
13
27
|
@output_format = options[:output_format] || 'html'
|
@@ -20,7 +34,7 @@ module LogSense
|
|
20
34
|
# determine the main template to read
|
21
35
|
@template = File.join(File.dirname(__FILE__), 'templates', "#{@input_format}.#{@output_format}.erb")
|
22
36
|
erb_template = File.read @template
|
23
|
-
output = ERB.new(erb_template).result(binding)
|
37
|
+
output = ERB.new(erb_template, trim_mode: "-").result(binding)
|
24
38
|
|
25
39
|
if options[:output_file]
|
26
40
|
file = File.open options[:output_file], 'w'
|
@@ -31,10 +45,14 @@ module LogSense
|
|
31
45
|
end
|
32
46
|
end
|
33
47
|
|
48
|
+
#
|
49
|
+
# This is used in templates
|
50
|
+
#
|
34
51
|
def self.render(template, vars = {})
|
35
52
|
@template = File.join(File.dirname(__FILE__), 'templates', "_#{template}")
|
36
53
|
erb_template = File.read @template
|
37
|
-
ERB.new(erb_template
|
54
|
+
ERB.new(erb_template, trim_mode: "-")
|
55
|
+
.result(OpenStruct.new(vars).instance_eval { binding })
|
38
56
|
end
|
39
57
|
|
40
58
|
def self.escape_javascript(string)
|
@@ -65,23 +83,37 @@ module LogSense
|
|
65
83
|
[Integer, Float].include?(klass) ? value : escape_javascript(value || '')
|
66
84
|
end
|
67
85
|
|
68
|
-
# limit width of special columns, that is,
|
86
|
+
# limit width of special columns, that is, those in keywords
|
69
87
|
# - data: array of arrays
|
70
88
|
# - heading: array with column names
|
71
89
|
# - width width to set
|
72
|
-
def self.shorten(data, heading, width)
|
73
|
-
#
|
74
|
-
keywords =
|
75
|
-
|
90
|
+
def self.shorten(data, heading, width, inner_rows)
|
91
|
+
# columns which need to be shortened
|
92
|
+
keywords = ["URL", "Referers", "Description", "Path", "Paths", "IP List"]
|
93
|
+
# indexes of columns which have to be shortened (= index in array)
|
94
|
+
to_shorten = keywords.map { |idx| heading.index idx }.compact
|
76
95
|
|
77
|
-
if
|
96
|
+
if data[0].nil? || width.nil? || to_shorten.empty?
|
78
97
|
data
|
79
98
|
else
|
99
|
+
# how many columns do we have?
|
80
100
|
table_columns = data[0].size
|
81
101
|
data.map do |x|
|
102
|
+
# we iterate over all columns, because we want to return a table
|
103
|
+
# with the same structure of the input table
|
82
104
|
(0..table_columns - 1).each.map do |col|
|
83
|
-
|
84
|
-
|
105
|
+
# split cell into (internal) rows, if necessary
|
106
|
+
content_in_rows = x[col].to_s.split WORDS_SEPARATOR
|
107
|
+
# remove excess rows, shorten each string and return what's left
|
108
|
+
# single cells are returned as they are
|
109
|
+
rows_limit = inner_rows || content_in_rows.size
|
110
|
+
content = content_in_rows[0..(rows_limit - 1)].map { |x|
|
111
|
+
if x.size > width && to_shorten.include?(col)
|
112
|
+
"#{x[0..(width - 3)]}..."
|
113
|
+
else
|
114
|
+
x
|
115
|
+
end
|
116
|
+
}.join("\n")
|
85
117
|
end
|
86
118
|
end
|
87
119
|
end
|
@@ -119,7 +151,7 @@ module LogSense
|
|
119
151
|
},
|
120
152
|
{
|
121
153
|
'mark': {
|
122
|
-
|
154
|
+
'type': 'text',
|
123
155
|
'color': '#3E5772',
|
124
156
|
'align': 'middle',
|
125
157
|
'baseline': 'top',
|
@@ -127,14 +159,14 @@ module LogSense
|
|
127
159
|
'yOffset': -15
|
128
160
|
},
|
129
161
|
'encoding': {
|
130
|
-
|
162
|
+
'text': {'field': 'Hits', 'type': 'quantitative'},
|
131
163
|
'y': {'field': 'Hits', 'type': 'quantitative'}
|
132
164
|
}
|
133
165
|
},
|
134
166
|
|
135
167
|
{
|
136
168
|
'mark': {
|
137
|
-
|
169
|
+
'type': 'line',
|
138
170
|
'color': '#A52A2A',
|
139
171
|
'point': {
|
140
172
|
'color': '#A52A2A',
|
@@ -149,7 +181,7 @@ module LogSense
|
|
149
181
|
|
150
182
|
{
|
151
183
|
'mark': {
|
152
|
-
|
184
|
+
'type': 'text',
|
153
185
|
'color': '#A52A2A',
|
154
186
|
'align': 'middle',
|
155
187
|
'baseline': 'top',
|
@@ -203,28 +235,58 @@ module LogSense
|
|
203
235
|
header: %w[Path Hits Visits Size Status],
|
204
236
|
column_alignment: %i[left right right right right],
|
205
237
|
rows: data[:most_requested_pages],
|
206
|
-
datatable_options: 'columnDefs: [{ width: \'40%\', targets: 0 } ]'
|
238
|
+
datatable_options: 'columnDefs: [{ width: \'40%\', targets: 0 }, { width: \'15%\', targets: [1, 2, 3, 4] }], dataRender: true'
|
207
239
|
},
|
208
240
|
{
|
209
241
|
title: '20_ and 30_ on other resources',
|
210
242
|
header: %w[Path Hits Visits Size Status],
|
211
243
|
column_alignment: %i[left right right right right],
|
212
244
|
rows: data[:most_requested_resources],
|
213
|
-
datatable_options: 'columnDefs: [{ width: \'40%\', targets: 0 } ]'
|
245
|
+
datatable_options: 'columnDefs: [{ width: \'40%\', targets: 0 }, { width: \'15%\', targets: [1, 2, 3, 4] }], dataRender: true'
|
214
246
|
},
|
215
247
|
{
|
216
248
|
title: '40_ and 50_x on HTML pages',
|
217
249
|
header: %w[Path Hits Visits Status],
|
218
250
|
column_alignment: %i[left right right right],
|
219
251
|
rows: data[:missed_pages],
|
220
|
-
datatable_options: 'columnDefs: [{ width: \'40%\', targets: 0 } ]'
|
252
|
+
datatable_options: 'columnDefs: [{ width: \'40%\', targets: 0 }, { width: \'20%\', targets: [1, 2, 3] }], dataRender: true'
|
221
253
|
},
|
222
254
|
{
|
223
255
|
title: '40_ and 50_ on other resources',
|
224
256
|
header: %w[Path Hits Visits Status],
|
225
257
|
column_alignment: %i[left right right right],
|
226
258
|
rows: data[:missed_resources],
|
227
|
-
datatable_options: 'columnDefs: [{ width: \'40%\', targets: 0 } ]'
|
259
|
+
datatable_options: 'columnDefs: [{ width: \'40%\', targets: 0 }, { width: \'20%\', targets: [1, 2, 3] }], dataRender: true'
|
260
|
+
},
|
261
|
+
{
|
262
|
+
title: '40_ and 50_x on HTML pages by IP',
|
263
|
+
header: %w[IP Hits Paths],
|
264
|
+
column_alignment: %i[left right left],
|
265
|
+
# Value is something along the line of:
|
266
|
+
# [["66.249.79.93", "/adolfo/notes/calendar/2014/11/16.html", "404"],
|
267
|
+
# ["66.249.79.93", "/adolfo/website-specification/generate-xml-sitemap.org.html", "404"]]
|
268
|
+
rows: data[:missed_pages_by_ip]&.group_by { |x| x[0] }&.map { |k, v|
|
269
|
+
[
|
270
|
+
k,
|
271
|
+
v.size,
|
272
|
+
v.map { |x| x[1] }.join(WORDS_SEPARATOR)
|
273
|
+
]
|
274
|
+
}&.sort { |x, y| y[1] <=> x[1] }
|
275
|
+
},
|
276
|
+
{
|
277
|
+
title: '40_ and 50_ on other resources by IP',
|
278
|
+
header: %w[IP Hits Paths],
|
279
|
+
column_alignment: %i[left right left],
|
280
|
+
# Value is something along the line of:
|
281
|
+
# [["66.249.79.93", "/adolfo/notes/calendar/2014/11/16.html", "404"],
|
282
|
+
# ["66.249.79.93", "/adolfo/website-specification/generate-xml-sitemap.org.html", "404"]]
|
283
|
+
rows: data[:missed_resources_by_ip]&.group_by { |x| x[0] }&.map { |k, v|
|
284
|
+
[
|
285
|
+
k,
|
286
|
+
v.size,
|
287
|
+
v.map { |x| x[1] }.join(WORDS_SEPARATOR)
|
288
|
+
]
|
289
|
+
}&.sort { |x, y| y[1] <=> x[1] }
|
228
290
|
},
|
229
291
|
{
|
230
292
|
title: 'Statuses',
|
@@ -233,10 +295,10 @@ module LogSense
|
|
233
295
|
rows: data[:statuses],
|
234
296
|
vega_spec: {
|
235
297
|
'mark': 'bar',
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
298
|
+
'encoding': {
|
299
|
+
'x': {'field': 'Status', 'type': 'nominal'},
|
300
|
+
'y': {'field': 'Count', 'type': 'quantitative'}
|
301
|
+
}
|
240
302
|
}
|
241
303
|
},
|
242
304
|
{
|
@@ -246,27 +308,27 @@ module LogSense
|
|
246
308
|
rows: data[:statuses_by_day],
|
247
309
|
vega_spec: {
|
248
310
|
'transform': [ {'fold': ['S_2xx', 'S_3xx', 'S_4xx' ] }],
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
311
|
+
'mark': 'bar',
|
312
|
+
'encoding': {
|
313
|
+
'x': {
|
314
|
+
'field': 'Date',
|
315
|
+
'type': 'ordinal',
|
316
|
+
'timeUnit': 'day',
|
317
|
+
},
|
318
|
+
'y': {
|
319
|
+
'aggregate': 'sum',
|
320
|
+
'field': 'value',
|
321
|
+
'type': 'quantitative'
|
322
|
+
},
|
323
|
+
'color': {
|
324
|
+
'field': 'key',
|
325
|
+
'type': 'nominal',
|
326
|
+
'scale': {
|
327
|
+
'domain': ['S_2xx', 'S_3xx', 'S_4xx'],
|
328
|
+
'range': ['#228b22', '#ff8c00', '#a52a2a']
|
329
|
+
},
|
330
|
+
}
|
331
|
+
}
|
270
332
|
}
|
271
333
|
},
|
272
334
|
{
|
@@ -325,27 +387,36 @@ module LogSense
|
|
325
387
|
},
|
326
388
|
{
|
327
389
|
title: 'IPs',
|
328
|
-
header: %w[
|
390
|
+
header: %w[IP Hits Visits Size Country],
|
329
391
|
column_alignment: %i[left right right right left],
|
330
392
|
rows: data[:ips]
|
331
393
|
},
|
332
394
|
{
|
333
395
|
title: 'Countries',
|
334
|
-
header:
|
335
|
-
column_alignment: %i[left right right left],
|
336
|
-
rows: data[:countries]&.map
|
396
|
+
header: ["Country", "Hits", "Visits", "IPs", "IP List"],
|
397
|
+
column_alignment: %i[left right right right left],
|
398
|
+
rows: data[:countries]&.map { |k, v|
|
337
399
|
[
|
338
400
|
k,
|
339
401
|
v.map { |x| x[1] }.inject(&:+),
|
340
402
|
v.map { |x| x[2] }.inject(&:+),
|
341
|
-
v.map { |x| x[0] }.
|
403
|
+
v.map { |x| x[0] }.size,
|
404
|
+
v.map { |x| x[0] }.join(WORDS_SEPARATOR)
|
342
405
|
]
|
343
|
-
|
406
|
+
}&.sort { |x, y| y[3] <=> x[3] }
|
407
|
+
},
|
408
|
+
{
|
409
|
+
title: 'Combined Platform Data',
|
410
|
+
header: %w[ Browser OS IP Hits Size],
|
411
|
+
column_alignment: %i[left left left right right],
|
412
|
+
col: 'small-12 cell',
|
413
|
+
rows: data[:combined_platforms],
|
344
414
|
},
|
345
415
|
{
|
346
416
|
title: 'Referers',
|
347
417
|
header: %w[Referers Hits Visits Size],
|
348
418
|
column_alignment: %i[left right right right],
|
419
|
+
datatable_options: 'columnDefs: [{ width: \'50%\', targets: 0 } ], dataRender: true',
|
349
420
|
rows: data[:referers],
|
350
421
|
col: 'small-12 cell'
|
351
422
|
},
|
@@ -354,14 +425,15 @@ module LogSense
|
|
354
425
|
report: :html,
|
355
426
|
header: ['IP', 'Date', 'Total HTML', 'Total Other', 'HTML', 'Other'],
|
356
427
|
column_alignment: %i[left left right right left left],
|
428
|
+
datatable_options: 'columnDefs: [{ width: \'30%\', targets: [4, 5] }, { width: \'10%\', targets: [0, 1, 2, 3]} ], dataRender: true',
|
357
429
|
rows: data[:streaks]&.group_by { |x| [x[0], x[1]] }&.map do |k, v|
|
358
430
|
[
|
359
431
|
k[0],
|
360
432
|
k[1],
|
361
433
|
v.map { |x| x[2] }.compact.select { |x| x.match(/\.html?$/) }.size,
|
362
434
|
v.map { |x| x[2] }.compact.reject { |x| x.match(/\.html?$/) }.size,
|
363
|
-
v.map { |x| x[2] }.compact.select { |x| x.match(/\.html?$/) }.join(
|
364
|
-
v.map { |x| x[2] }.compact.reject { |x| x.match(/\.html?$/) }.join(
|
435
|
+
v.map { |x| x[2] }.compact.select { |x| x.match(/\.html?$/) }.join(WORDS_SEPARATOR),
|
436
|
+
v.map { |x| x[2] }.compact.reject { |x| x.match(/\.html?$/) }.join(WORDS_SEPARATOR)
|
365
437
|
]
|
366
438
|
end,
|
367
439
|
col: 'small-12 cell'
|
@@ -528,13 +600,13 @@ module LogSense
|
|
528
600
|
title: 'Countries',
|
529
601
|
header: %w[Country Hits IPs],
|
530
602
|
column_alignment: %i[left right left],
|
531
|
-
rows: data[:countries]&.map
|
603
|
+
rows: data[:countries]&.map { |k, v|
|
532
604
|
[
|
533
605
|
k,
|
534
606
|
v.map { |x| x[1] }.inject(&:+),
|
535
|
-
v.map { |x| x[0] }.join(
|
607
|
+
v.map { |x| x[0] }.join(WORDS_SEPARATOR)
|
536
608
|
]
|
537
|
-
|
609
|
+
}&.sort { |x, y| x[0] <=> y[0] }
|
538
610
|
},
|
539
611
|
{
|
540
612
|
title: 'Streaks',
|
@@ -546,7 +618,7 @@ module LogSense
|
|
546
618
|
k[0],
|
547
619
|
k[1],
|
548
620
|
v.size,
|
549
|
-
v.map { |x| x[2] }.join(
|
621
|
+
v.map { |x| x[2] }.join(WORDS_SEPARATOR)
|
550
622
|
]
|
551
623
|
end,
|
552
624
|
col: 'small-12 cell'
|
@@ -8,7 +8,7 @@ module LogSense
|
|
8
8
|
# parse command line options
|
9
9
|
#
|
10
10
|
def self.parse(options)
|
11
|
-
limit =
|
11
|
+
limit = 100
|
12
12
|
args = {}
|
13
13
|
|
14
14
|
opt_parser = OptionParser.new do |opts|
|
@@ -46,10 +46,14 @@ module LogSense
|
|
46
46
|
args[:limit] = n
|
47
47
|
end
|
48
48
|
|
49
|
-
opts.on('-wWIDTH', '--width=WIDTH', Integer, 'Maximum width of
|
49
|
+
opts.on('-wWIDTH', '--width=WIDTH', Integer, 'Maximum width of long columns in textual reports') do |n|
|
50
50
|
args[:width] = n
|
51
51
|
end
|
52
52
|
|
53
|
+
opts.on('-rROWS', '--rows=ROWS', Integer, 'Maximum number of rows for columns with multiple entries in textual reports') do |n|
|
54
|
+
args[:inner_rows] = n
|
55
|
+
end
|
56
|
+
|
53
57
|
opts.on('-cPOLICY', '--crawlers=POLICY', String, 'Decide what to do with crawlers (applies to Apache Logs)') do |n|
|
54
58
|
case n
|
55
59
|
when 'only'
|
@@ -8,13 +8,16 @@
|
|
8
8
|
<span class="stats-list-label">To</span>
|
9
9
|
</li>
|
10
10
|
<li class="stats-list-positive">
|
11
|
-
<%= data[:total_days] %>
|
11
|
+
<%= data[:total_days] %>
|
12
|
+
<span class="stats-list-label">Days in Log</span>
|
12
13
|
</li>
|
13
14
|
<li class="stats-list-negative">
|
14
|
-
<%= data[:log_size] %>
|
15
|
+
<%= data[:log_size] %>
|
16
|
+
<span class="stats-list-label">Total Entries</span>
|
15
17
|
</li>
|
16
18
|
<li class="stats-list-negative">
|
17
|
-
<%= data[:selfpolls_size] %>
|
19
|
+
<%= data[:selfpolls_size] %>
|
20
|
+
<span class="stats-list-label">Self Polls</span>
|
18
21
|
</li>
|
19
22
|
<li class="stats-list-negative">
|
20
23
|
<td><%= data[:crawlers_size] %></td>
|
@@ -13,11 +13,59 @@
|
|
13
13
|
$(document).ready(function(){
|
14
14
|
$('#table-<%= index %>').dataTable({
|
15
15
|
data: data_<%= index %>,
|
16
|
+
pageLength: 25,
|
16
17
|
<%= report[:datatable_options] + "," if report[:datatable_options] %>
|
17
18
|
columns: [
|
18
19
|
<% report[:header].each do |header| %>
|
20
|
+
<% if header == "Size" -%>
|
21
|
+
{
|
22
|
+
data: 'Size',
|
23
|
+
className: '<%= Emitter::slugify("Size") %>',
|
24
|
+
render: function(data, type, row) {
|
25
|
+
// If display or filter data is requested, format the date
|
26
|
+
if ( type === 'display' || type === 'filter' ) {
|
27
|
+
return data;
|
28
|
+
}
|
29
|
+
// Otherwise the data type requested (`type`) is type detection or
|
30
|
+
// sorting data, for which we want to use an integer value
|
31
|
+
value = data.split(/(\s+)/);
|
32
|
+
size = parseInt(value[0])
|
33
|
+
switch(value[2]) {
|
34
|
+
case 'B':
|
35
|
+
multiplier = 1
|
36
|
+
break;
|
37
|
+
case 'KB':
|
38
|
+
multiplier = 1024
|
39
|
+
break;
|
40
|
+
case 'MB':
|
41
|
+
multiplier = 1024 * 1024
|
42
|
+
break;
|
43
|
+
case 'GB':
|
44
|
+
multiplier = 1024 * 1024 * 1024
|
45
|
+
break;
|
46
|
+
default:
|
47
|
+
multiplier = 1
|
48
|
+
}
|
49
|
+
return size * multiplier;
|
50
|
+
}
|
51
|
+
},
|
52
|
+
<% elsif header == "IP" -%>
|
53
|
+
{
|
54
|
+
data: 'IP',
|
55
|
+
className: '<%= Emitter::slugify("IP") %>',
|
56
|
+
render: function(data, type, row) {
|
57
|
+
// If display or filter data is requested, format the date
|
58
|
+
if ( type === 'display' || type === 'filter' ) {
|
59
|
+
return "<a target=\"_blank\" href=\"https://db-ip.com/" + data + "\">" + data + "</a>";
|
60
|
+
}
|
61
|
+
// For any other purpose return data
|
62
|
+
return data;
|
63
|
+
}
|
64
|
+
},
|
65
|
+
<% else -%>
|
19
66
|
{ data: '<%= header %>', className: '<%= Emitter::slugify(header) %>' },
|
20
|
-
<% end
|
67
|
+
<% end -%>
|
68
|
+
<% end -%>
|
21
69
|
]
|
22
70
|
});
|
23
71
|
});
|
@@ -1,6 +1,8 @@
|
|
1
1
|
<%=
|
2
2
|
# shortens long URLs and long descriptions
|
3
|
-
shortened = Emitter::shorten(
|
3
|
+
shortened = Emitter::shorten(
|
4
|
+
report[:rows], report[:header], data[:width], data[:inner_rows]
|
5
|
+
)
|
4
6
|
|
5
7
|
# build and style the table
|
6
8
|
table = Terminal::Table.new headings: report[:header], rows: shortened
|
@@ -1,12 +1,12 @@
|
|
1
1
|
<script>
|
2
2
|
/* this is used both by Vega and DataTable for <%= report[:title] %>*/
|
3
3
|
data_<%= index %> = [
|
4
|
-
<% report[:rows].each do |row|
|
4
|
+
<% report[:rows].each do |row| -%>
|
5
5
|
{
|
6
|
-
<% report[:header].each_with_index do |h, i|
|
6
|
+
<% report[:header].each_with_index do |h, i| -%>
|
7
7
|
"<%= h %>": "<%= Emitter::process row[i] %>",
|
8
|
-
<% end
|
8
|
+
<% end -%>
|
9
9
|
},
|
10
|
-
<% end
|
10
|
+
<% end -%>
|
11
11
|
]
|
12
12
|
</script>
|
@@ -8,13 +8,20 @@
|
|
8
8
|
<span class="stats-list-label">To</span>
|
9
9
|
</li>
|
10
10
|
<li class="stats-list-positive">
|
11
|
-
<%= data[:total_days_in_analysis] %>
|
11
|
+
<%= data[:total_days_in_analysis] %>
|
12
|
+
<span class="stats-list-label">Days</span>
|
13
|
+
</li>
|
14
|
+
<li class="stats-list-positive">
|
15
|
+
<%= data[:total_size] %>
|
16
|
+
<span class="stats-list-label">Data Transferred</span>
|
12
17
|
</li>
|
13
18
|
<li class="stats-list-negative">
|
14
|
-
<%= data[:total_hits] %>
|
19
|
+
<%= data[:total_hits] %>
|
20
|
+
<span class="stats-list-label">Hits</span>
|
15
21
|
</li>
|
16
22
|
<li class="stats-list-negative">
|
17
|
-
<%= data[:total_unique_visits] %>
|
23
|
+
<%= data[:total_unique_visits] %>
|
24
|
+
<span class="stats-list-label">Unique Visits</span>
|
18
25
|
</li>
|
19
26
|
<li class="stats-list-negative">
|
20
27
|
<% days = data[:last_day_in_analysis] - data[:first_day_in_analysis] %>
|
data/lib/log_sense/version.rb
CHANGED
data/log_sense.gemspec
CHANGED
@@ -1,38 +1,38 @@
|
|
1
|
-
require_relative
|
1
|
+
require_relative "lib/log_sense/version"
|
2
2
|
|
3
3
|
Gem::Specification.new do |spec|
|
4
|
-
spec.name =
|
4
|
+
spec.name = "log_sense"
|
5
5
|
spec.version = LogSense::VERSION
|
6
|
-
spec.authors = [
|
7
|
-
spec.email = [
|
6
|
+
spec.authors = ["Adolfo Villafiorita"]
|
7
|
+
spec.email = ["adolfo@shair.tech"]
|
8
8
|
|
9
9
|
spec.summary = %q{Generate analytics for Apache and Rails log file.}
|
10
10
|
spec.description = %q{Generate analytics in HTML, txt, and SQLite format for Apache and Rails log files.}
|
11
|
-
spec.homepage =
|
12
|
-
spec.license =
|
13
|
-
spec.required_ruby_version = Gem::Requirement.new(
|
11
|
+
spec.homepage = "https://github.com/shair-tech/log_sense/log_sense"
|
12
|
+
spec.license = "MIT"
|
13
|
+
spec.required_ruby_version = Gem::Requirement.new(">= 2.6.9")
|
14
14
|
|
15
|
-
spec.metadata[
|
15
|
+
spec.metadata["allowed_push_host"] = "https://rubygems.org/"
|
16
16
|
|
17
|
-
spec.metadata[
|
18
|
-
spec.metadata[
|
19
|
-
spec.metadata[
|
17
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
18
|
+
spec.metadata["source_code_uri"] = "https://github.com/shair-tech/log_sense/"
|
19
|
+
spec.metadata["changelog_uri"] = "https://github.com/shair-tech/log_sense/blob/main/CHANGELOG.org"
|
20
20
|
|
21
21
|
# Specify which files should be added to the gem when it is released.
|
22
22
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
23
|
-
spec.files = Dir.chdir(File.expand_path(
|
23
|
+
spec.files = Dir.chdir(File.expand_path("..", __FILE__)) do
|
24
24
|
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
25
25
|
end
|
26
|
-
spec.bindir =
|
26
|
+
spec.bindir = "exe"
|
27
27
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
28
|
-
spec.require_paths = [
|
28
|
+
spec.require_paths = ["lib"]
|
29
29
|
|
30
|
-
spec.add_dependency
|
31
|
-
spec.add_dependency
|
32
|
-
spec.add_dependency
|
33
|
-
spec.add_dependency
|
34
|
-
spec.add_dependency
|
30
|
+
spec.add_dependency "browser"
|
31
|
+
spec.add_dependency "ipaddr"
|
32
|
+
spec.add_dependency "iso_country_codes"
|
33
|
+
spec.add_dependency "sqlite3"
|
34
|
+
spec.add_dependency "terminal-table"
|
35
35
|
|
36
|
-
spec.add_development_dependency
|
37
|
-
spec.add_development_dependency
|
36
|
+
spec.add_development_dependency "debug"
|
37
|
+
spec.add_development_dependency "minitest"
|
38
38
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: log_sense
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.5.
|
4
|
+
version: 1.5.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
|
-
- Adolfo
|
7
|
+
- Adolfo Villafiorita
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-08-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: browser
|
@@ -81,7 +81,7 @@ dependencies:
|
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '0'
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
|
-
name:
|
84
|
+
name: debug
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
86
86
|
requirements:
|
87
87
|
- - ">="
|
@@ -157,7 +157,6 @@ files:
|
|
157
157
|
- lib/log_sense/templates/rails.txt.erb
|
158
158
|
- lib/log_sense/version.rb
|
159
159
|
- log_sense.gemspec
|
160
|
-
- sample_logs/empty_log.log
|
161
160
|
- sample_logs/safety-critical_org.log
|
162
161
|
- sample_logs/spmbook_com.log
|
163
162
|
homepage: https://github.com/shair-tech/log_sense/log_sense
|
@@ -166,7 +165,7 @@ licenses:
|
|
166
165
|
metadata:
|
167
166
|
allowed_push_host: https://rubygems.org/
|
168
167
|
homepage_uri: https://github.com/shair-tech/log_sense/log_sense
|
169
|
-
source_code_uri: https://github.com/shair-tech/log_sense/
|
168
|
+
source_code_uri: https://github.com/shair-tech/log_sense/
|
170
169
|
changelog_uri: https://github.com/shair-tech/log_sense/blob/main/CHANGELOG.org
|
171
170
|
post_install_message:
|
172
171
|
rdoc_options: []
|
@@ -183,7 +182,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
183
182
|
- !ruby/object:Gem::Version
|
184
183
|
version: '0'
|
185
184
|
requirements: []
|
186
|
-
rubygems_version: 3.
|
185
|
+
rubygems_version: 3.3.7
|
187
186
|
signing_key:
|
188
187
|
specification_version: 4
|
189
188
|
summary: Generate analytics for Apache and Rails log file.
|
data/sample_logs/empty_log.log
DELETED
File without changes
|