log_sense 1.5.1 → 1.5.2
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.
- 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
|