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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1f20a0d2041df1f2414bd0015fff27764c28a89dfd2d45fdb275141638bc5784
4
- data.tar.gz: 14426b7383fe9b0077c2852f177545384a369f35489aa5d95372c20cbe19732d
3
+ metadata.gz: 5f37b2247014af9bccfb8bdd205b54eb51cbef35495904444765648a96e0b9ac
4
+ data.tar.gz: a9cd03afb6770bf854791b8ec93e87d09532828d894b28f5dde670f1af1b1b1d
5
5
  SHA512:
6
- metadata.gz: 9defcff35a5b3802d7d1a7db596a30aa01e28d3b9d784619de5f61713a587e427ccce76fcfcd478e946a0822af8d0822b23bad6392c2a71690ccd07250d5cfb7
7
- data.tar.gz: 8b9143feb4f7508de9b7f60eddacafc4713c064f8bda21ed9ae92de364aa5e94fed36ff6d12f2b2aa108337d92c25868bce21d172397f48ff3c41ed93dc9a2b5
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 DBIP
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.2)
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
- byebug (11.1.3)
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
- sqlite3 (1.4.2)
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.1.0)
30
+ unicode-display_width (2.2.0)
24
31
 
25
32
  PLATFORMS
26
33
  ruby
27
34
 
28
35
  DEPENDENCIES
29
- byebug
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 DPIP lite country DB
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 900)
85
- -w, --width=WIDTH Maximum width of URL and description columns in text reports
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.1
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.txt
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
- Distributed under the terms of the [[http://opensource.org/licenses/MIT][MIT License]].
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]}"
@@ -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).result(OpenStruct.new(vars).instance_eval { binding })
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, URL, Path, and Description
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
- # indexes of columns which have to be shortened
74
- keywords = %w[URL Referers Description Path]
75
- to_shorten = keywords.map { |x| heading.index x }.compact
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 width.nil? || to_shorten.empty? || data[0].nil?
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
- should_shorten = x[col] && x[col].size > width - 3 && to_shorten.include?(col)
84
- should_shorten ? "#{x[col][0..(width - 3)]}..." : x[col]
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
- 'type': 'text',
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
- 'text': {'field': 'Hits', 'type': 'quantitative'},
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
- 'type': 'line',
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
- 'type': 'text',
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
- 'encoding': {
237
- 'x': {'field': 'Status', 'type': 'nominal'},
238
- 'y': {'field': 'Count', 'type': 'quantitative'}
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
- 'mark': 'bar',
250
- 'encoding': {
251
- 'x': {
252
- 'field': 'Date',
253
- 'type': 'ordinal',
254
- 'timeUnit': 'day',
255
- },
256
- 'y': {
257
- 'aggregate': 'sum',
258
- 'field': 'value',
259
- 'type': 'quantitative'
260
- },
261
- 'color': {
262
- 'field': 'key',
263
- 'type': 'nominal',
264
- 'scale': {
265
- 'domain': ['S_2xx', 'S_3xx', 'S_4xx'],
266
- 'range': ['#228b22', '#ff8c00', '#a52a2a']
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[IPs Hits Visits Size Country],
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: %w[Country Hits Visits IPs],
335
- column_alignment: %i[left right right left],
336
- rows: data[:countries]&.map do |k, v|
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] }.join(' ')
403
+ v.map { |x| x[0] }.size,
404
+ v.map { |x| x[0] }.join(WORDS_SEPARATOR)
342
405
  ]
343
- end
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 do |k, v|
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
- end
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 = 900
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 URL and description columns in text reports') do |n|
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] %> <span class="stats-list-label">Days in Log</span>
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] %> <span class="stats-list-label">Total Entries</span>
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] %> <span class="stats-list-label">Self Polls</span>
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(report[:rows], report[:header], data[:width])
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] %> <span class="stats-list-label">Days</span>
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] %> <span class="stats-list-label">Hits</span>
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] %> <span class="stats-list-label">Unique Visits</span>
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] %>
@@ -1,3 +1,3 @@
1
1
  module LogSense
2
- VERSION = "1.5.1"
2
+ VERSION = "1.5.2"
3
3
  end
data/log_sense.gemspec CHANGED
@@ -1,38 +1,38 @@
1
- require_relative 'lib/log_sense/version'
1
+ require_relative "lib/log_sense/version"
2
2
 
3
3
  Gem::Specification.new do |spec|
4
- spec.name = 'log_sense'
4
+ spec.name = "log_sense"
5
5
  spec.version = LogSense::VERSION
6
- spec.authors = ['Adolfo Fibrillation']
7
- spec.email = ['adolfo@shair.tech']
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 = 'https://github.com/shair-tech/log_sense/log_sense'
12
- spec.license = 'MIT'
13
- spec.required_ruby_version = Gem::Requirement.new('>= 2.6.9')
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['allowed_push_host'] = 'https://rubygems.org/'
15
+ spec.metadata["allowed_push_host"] = "https://rubygems.org/"
16
16
 
17
- spec.metadata['homepage_uri'] = spec.homepage
18
- spec.metadata['source_code_uri'] = 'https://github.com/shair-tech/log_sense/log_sense'
19
- spec.metadata['changelog_uri'] = 'https://github.com/shair-tech/log_sense/blob/main/CHANGELOG.org'
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('..', __FILE__)) do
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 = 'exe'
26
+ spec.bindir = "exe"
27
27
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
28
- spec.require_paths = ['lib']
28
+ spec.require_paths = ["lib"]
29
29
 
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'
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 'byebug'
37
- spec.add_development_dependency 'minitest'
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.1
4
+ version: 1.5.2
5
5
  platform: ruby
6
6
  authors:
7
- - Adolfo Fibrillation
7
+ - Adolfo Villafiorita
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-03-09 00:00:00.000000000 Z
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: byebug
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/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.0.3.1
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.
File without changes