log_sense 1.2.2 → 1.3.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: 72d2fbdba26d6fa20cef0fc82c16ecd29eeb135ece6178804a9737d6bc95e838
4
- data.tar.gz: 5ac8a548f0f2ac3db060ac4375d4756539a5262b95b9aebc0fc496e6e0003caf
3
+ metadata.gz: 6498507bb6ae7e6ba5505609421154358790f095bd9cfe5ab912920b2409f7d1
4
+ data.tar.gz: 0ccb5fa005b8f1e47545bf98ce05273f07bd452d3be54c5bd1a7f124c96558d9
5
5
  SHA512:
6
- metadata.gz: 0bdecd45808f1257cb7aeaa97d2cf13cf0e62fbde74365c351a2e5440784e99bbfa29271746db66a5dba2bb5e1ae5891aaac4cb982f37ef8072d9e079e3b032a
7
- data.tar.gz: d88cca775be4a8828ebaecec757cee87e52338f36784c3a59d6ab8b703328982ecff71233640560f832c4b1f9d7fa4c299a47344d3b79139f518404e02f32380
6
+ metadata.gz: e12760ccdfc518c7221afa337f4ac1c8a77219cd3ed65510486e3ce945e6129519aa3fe93b05845a5430b9bc52359d70591e0fcbd346e971527af1760c304e5c
7
+ data.tar.gz: 8d38d8ed84287722b77f51599598b84a2257f9f86d2dd3815faf25dfb7db4ab16e68dbf737c7d005519ac5f9fe1d4b41cd896dfaf862eabb6a7dc4f033d2bb13
data/CHANGELOG.org CHANGED
@@ -2,13 +2,20 @@
2
2
  #+AUTHOR: Adolfo Villafiorita
3
3
  #+STARTUP: showall
4
4
 
5
- * Changes in log_sense 1.1.2
6
- <2021-12-17 Fri>
5
+ * 1.3.2
7
6
 
8
- - Added Rails Log HTML output
7
+ - [Code] HTML reports now generate JSON data which is shared between
8
+ DataTable and Vega Light: this should reduce page size and loading
9
+ time of HTML reports
10
+ - [Doc] Added screenshot and fixed some text
11
+ - [Doc] Fixes requirements on Ruby version
9
12
 
10
- * Changes in log_sense 1.1.1 and earlier
11
- <2021-12-17 Fri>
13
+ * 1.3.1
12
14
 
13
- - In the Git commit messages (not very informative, I am afraid).
15
+ - [Code] Strengthened parsing of Apache Logs (added WebDav and other methods)
14
16
 
17
+ * 1.3.0
18
+
19
+ - [Code] Removed dependency from =apache_log-parser= and implemented our own
20
+ parser for the combined format.
21
+
data/Gemfile.lock CHANGED
@@ -1,8 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- log_sense (1.0.7)
5
- apache_log-parser
4
+ log_sense (1.3.1)
6
5
  browser
7
6
  ipaddr
8
7
  iso_country_codes
@@ -12,7 +11,6 @@ PATH
12
11
  GEM
13
12
  remote: https://rubygems.org/
14
13
  specs:
15
- apache_log-parser (3.1.2)
16
14
  browser (5.3.1)
17
15
  byebug (11.1.3)
18
16
  ipaddr (1.2.3)
data/README.org CHANGED
@@ -4,24 +4,10 @@
4
4
 
5
5
  * Introduction
6
6
 
7
- LogSense generates reports and statistics from Apache web logs in the
8
- =combined= format and from Rails logs. Written in Ruby, it runs from
9
- the command line, it is fast, and it can be installed on any system
10
- which supports Ruby.
11
-
12
- LogSense moves along the lines of tools such as [[https://goaccess.io/][GoAccess]]
13
- and [[https://umami.is/][Umami]], focusing on privacy and data-ownership: the data
14
- generated by LogSense is stored on your computer and owned by
15
- you (like it should be).
16
-
17
- LogSense is also inspired by *static websites generators*: statistics
18
- are generated from the command line and accessed as static HTML files.
19
- By generating static resources, LogSense significantly reduces the
20
- attack surface of your webserver and installation headaches.
21
-
22
- We have, for instance, a cron job running on our servers, generating
23
- statistics at night. The generated files are then made available on a
24
- private area on the web.
7
+ LogSense generates reports and statistics from Apache and Ruby on
8
+ Rails logs. Written in Ruby, it runs from the command line, it is
9
+ fast, and it can be installed on any system with a relatively recent
10
+ version of Ruby. We tested on Ruby 2.6.9, Ruby 3.0.x and later.
25
11
 
26
12
  LogSense reports the following data:
27
13
 
@@ -40,7 +26,27 @@ LogSense reports the following data:
40
26
  Filters from the command line allow to analyze specific periods and
41
27
  distinguish traffic generated by self polls and crawlers.
42
28
 
43
- LogSense generates HTML, txt (Org Mode), and SQLite outputs.
29
+ LogSense generates HTML, txt, and SQLite outputs.
30
+
31
+ And, of course, the compulsory screenshot:
32
+
33
+ #+ATTR_HTML: :width 80%
34
+ [[file:./apache-screenshot.png]]
35
+
36
+ * Motivation
37
+
38
+ LogSense moves along the lines of tools such as [[https://goaccess.io/][GoAccess]] (which
39
+ strongly inspired the development of Log Sense) and [[https://umami.is/][Umami]], focusing on
40
+ *privacy* and *data-ownership*: the data generated by LogSense is
41
+ stored on your computer and owned by you (like it should be)[fn:1].
42
+
43
+ LogSense is also inspired by *static websites generators*: statistics
44
+ are generated from the command line and accessed as static HTML files.
45
+ LogSense thus significantly reduces the attack surface of your
46
+ webserver and installation headaches. We have, for instance, a Cron
47
+ job running on our servers, generating statistics at night. The
48
+ generated files are then made available on a private area on the web.
49
+
44
50
 
45
51
  * Installation
46
52
 
@@ -81,37 +87,48 @@ LogSense generates HTML, txt (Org Mode), and SQLite outputs.
81
87
  - txt
82
88
  #+end_example
83
89
 
90
+ Examples:
91
+
92
+ #+begin_example sh
93
+ log_sense -f apache -i access.log -t txt > access-data.txt
94
+ log_sense -f rails -i production.log -t html > performance.txt
95
+ #+end_example
96
+
84
97
  * Change Log
85
98
 
86
99
  See the [[file:CHANGELOG.org][CHANGELOG]] file.
87
100
 
88
101
  * Compatibility
89
102
 
90
- LogSense should run on any system on which Ruby runs.
103
+ LogSense should run on any system on which a recent version of Ruby
104
+ runs. We tested it with Ruby 2.6.9 and Ruby 3.x.x.
91
105
 
92
106
  Concerning the outputs:
93
107
 
94
- - The HTML report uses [[https://picturepan2.github.io/spectre/][Spectre.css]] and (will use) [[https://vega.github.io/vega-lite/][Vega Light]], which
95
- are downloaded from a CDN
96
- - The textual format is compatible with Org Mode and can be further
97
- processed to any format Org Mode can be exported to (including HTML
98
- and PDF),
108
+ - HTML reports use [[https://get.foundation/][Zurb Foundation]], [[https://www.datatables.net/][Data Tables]], and [[https://vega.github.io/vega-lite/][Vega Light]], which
109
+ are all downloaded from a CDN
110
+ - The textual format is compatible with [[https://orgmode.org/][Org Mode]] and can be further
111
+ processed to any format [[https://orgmode.org/][Org Mode]] can be exported to (including HTML
112
+ and PDF)
99
113
 
100
114
  * Author and Contributors
101
115
 
102
- [[http://ict4g.net/adolfo][Adolfo Villafiorita]].
116
+ [[https://shair.tech][Shair.Tech]]
103
117
 
104
118
  * Known Bugs
105
119
 
106
- Some known bugs and an unknown number of unknown bugs.
107
-
120
+ No known bugs; an unknown number of unknown bugs.
108
121
  (See the open issues for the known bugs.)
109
122
 
110
123
  * License
111
124
 
112
125
  Distributed under the terms of the [[http://opensource.org/licenses/MIT][MIT License]].
113
126
 
114
- Geolocation is made possible by the DB-IP.com IP to City database, released under
115
- a CC license.
116
-
127
+ Geolocation is made possible by the DB-IP.com IP to City database,
128
+ released under a CC license.
117
129
 
130
+ [fn:1] There is a small catch: CSS and JavaScript for layout and plots
131
+ are downloaded from a CDN. Technically, thus, if you generate HTML
132
+ reports and open them, a request is performed and the CDN might keep a
133
+ track (see [[https://en.wikipedia.org/wiki/Content_delivery_network#Security_and_privacy][CDN Security and Privacy on Wikipedia]] for more details).
134
+ Textual reports don't have this issue.
Binary file
@@ -0,0 +1,56 @@
1
+ module LogSense
2
+ class ApacheLogLineParser
3
+ # parses a query and makes it into an expression which can be evaluated
4
+ # LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\"" combined
5
+ #
6
+ # %h: IP
7
+ # %l: ident or -
8
+ # %u: userid or -
9
+ # %t: [10/Oct/2000:13:55:36 -0700]
10
+ # day = 2*digit
11
+ # month = 3*letter
12
+ # year = 4*digit
13
+ # hour = 2*digit
14
+ # minute = 2*digit
15
+ # second = 2*digit
16
+ # zone = (`+' | `-') 4*digit
17
+ # %r: GET /apache_pb.gif HTTP/1.0
18
+ # %{User-agent}: "
19
+ #
20
+ # 116.179.32.16 - - [19/Dec/2021:22:35:11 +0100] "GET / HTTP/1.1" 200 135 "-" "Mozilla/5.0 (compatible; Baiduspider/2.0; +http://www.baidu.com/search/spider.html)"
21
+
22
+ DAY = /[0-9]{2}/
23
+ MONTH = /[A-Za-z]{3}/
24
+ YEAR = /[0-9]{4}/
25
+ TIMEC = /[0-9]{2}/
26
+ TIMEZONE = /(\+|-)[0-9]{4}/
27
+
28
+ IP = /(?<ip>[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}|::1)/
29
+ IDENT = /(?<ident>[^ ]+|-)/
30
+ USERID = /(?<userid>[^ ]+|-)/
31
+
32
+ TIMESTAMP = /(?<date>#{DAY}\/#{MONTH}\/#{YEAR}):(?<time>#{TIMEC}:#{TIMEC}:#{TIMEC} #{TIMEZONE})/
33
+
34
+ HTTP_METHODS=/GET|HEAD|POST|PUT|DELETE|CONNECT|OPTIONS|TRACE|PATCH/
35
+ WEBDAV_METHODS=/COPY|LOCK|MKCOL|MOVE|PROPFIND|PROPPATCH|UNLOCK/
36
+ OTHER_METHODS=/SEARCH|REPORT/
37
+ METHOD=/(?<method>#{HTTP_METHODS}|#{WEBDAV_METHODS}|#{OTHER_METHODS})/
38
+ PROTOCOL=/(?<protocol>HTTP\/[0-9]\.[0-9])/
39
+ URL=/(?<url>[^ ]+)/
40
+ REFERER=/(?<referer>[^ ]+)/
41
+ RETURN_CODE=/(?<status>[1-5][0-9][0-9])/
42
+ SIZE=/(?<size>[0-9]+|-)/
43
+
44
+ USER_AGENT = /(?<user_agent>[^"]+)/
45
+
46
+ attr_reader :format
47
+
48
+ def initialize
49
+ @format = /#{IP} #{IDENT} #{USERID} \[#{TIMESTAMP}\] "#{METHOD} #{URL} #{PROTOCOL}" #{RETURN_CODE} #{SIZE} "#{REFERER}" "#{USER_AGENT}"/
50
+ end
51
+
52
+ def parse line
53
+ hash = @format.match(line) || raise("Apache LogLine Parser Error: Could not parse #{line}")
54
+ end
55
+ end
56
+ end
@@ -1,4 +1,3 @@
1
- require 'apache_log/parser'
2
1
  require 'sqlite3'
3
2
  require 'browser'
4
3
 
@@ -50,21 +49,20 @@ module LogSense
50
49
  platform_version)
51
50
  values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)')
52
51
 
53
- parser = ApacheLog::Parser.new(options[:format] || 'combined')
52
+ parser = ApacheLogLineParser.new
54
53
 
55
54
  content.each do |line|
56
55
  begin
57
56
  hash = parser.parse line
58
-
59
57
  ua = Browser.new(hash[:user_agent], accept_language: "en-us")
60
58
  ins.execute(
61
- hash[:datetime].iso8601,
62
- hash[:remote_host],
63
- hash[:user],
64
- hash[:datetime].strftime("%Y-%m-%d") + " " + hash[:remote_host] + " " + hash[:user_agent],
65
- hash[:request][:method],
66
- hash[:request][:path],
67
- (hash[:request][:path] ? File.extname(hash[:request][:path]) : ""),
59
+ DateTime.parse("#{hash[:date]}T#{hash[:time]}").iso8601,
60
+ hash[:ip],
61
+ hash[:userid],
62
+ unique_visitor_id(hash),
63
+ hash[:method],
64
+ hash[:url],
65
+ (hash[:url] ? File.extname(hash[:url]) : ""),
68
66
  hash[:status],
69
67
  hash[:size].to_i,
70
68
  hash[:referer],
@@ -75,13 +73,17 @@ module LogSense
75
73
  (ua.platform.name || ""),
76
74
  (ua.platform.version || "")
77
75
  )
78
- rescue
79
- STDERR.puts "Apache Log parser error: could not parse #{line}"
76
+ rescue StandardError => e
77
+ STDERR.puts e.message
80
78
  end
81
79
  end
82
80
 
83
81
  db
84
82
  end
85
83
 
84
+ def self.unique_visitor_id hash
85
+ "#{hash[:date]} #{hash[:ip]} #{hash[:user_agent]}"
86
+ end
87
+
86
88
  end
87
89
  end
@@ -14,11 +14,11 @@
14
14
  <%= data[:log_size] %> <span class="stats-list-label">Total Entries</span>
15
15
  </li>
16
16
  <li class="stats-list-negative">
17
- <%= data[:selfpolls_size] %> <span class="stats-list-label">Self Polls Entries</span>
17
+ <%= data[:selfpolls_size] %> <span class="stats-list-label">Self Polls</span>
18
18
  </li>
19
19
  <li class="stats-list-negative">
20
20
  <td><%= data[:crawlers_size] %></td>
21
- <span class="stats-list-label">Crawlers Entries</span>
21
+ <span class="stats-list-label">Crawlers</span>
22
22
  </li>
23
23
  </ul>
24
24
 
@@ -4,22 +4,26 @@ def slugify string
4
4
  end
5
5
  %>
6
6
 
7
- <table id="<%= slugify(title || "") %>-table" class="table unstriped data-table <%= slugify(title || "") %>">
7
+ <table id="table-<%= index %>" class="table unstriped">
8
8
  <thead>
9
9
  <tr>
10
- <% header.each do |heading| %>
11
- <th class="<%= slugify(heading) %>"><%= heading %></th>
10
+ <% report[:header].each do |header| %>
11
+ <th><%= header %></th>
12
12
  <% end %>
13
13
  </tr>
14
14
  </thead>
15
15
  <tbody>
16
- <% rows.each do |row| %>
17
- <tr>
18
- <% row.each_with_index do |cell, i| %>
19
- <td class="<%= slugify (header[i] || "") %>"><%= cell %></td>
20
- <% end %>
21
- </tr>
22
- <% end %>
23
16
  </tbody>
24
17
  </table>
25
-
18
+ <script>
19
+ $(document).ready(function(){
20
+ $('#table-<%= index %>').dataTable({
21
+ data: data_<%= index %>,
22
+ columns: [
23
+ <% report[:header].each do |header| %>
24
+ { data: '<%= header %>', className: '<%= slugify(header) %>' },
25
+ <% end %>
26
+ ]
27
+ });
28
+ });
29
+ </script>
@@ -0,0 +1,12 @@
1
+ <script>
2
+ /* this is used both by Vega and DataTable */
3
+ data_<%= index %> = [
4
+ <% report[:rows].each do |row| %>
5
+ {
6
+ <% report[:header].each_with_index do |h, i| %>
7
+ "<%= h %>": <%= (row[i].class == Integer or row[i].class == Float) ? row[i] : "\"#{row[i]}\"" %>,
8
+ <% end %>
9
+ },
10
+ <% end %>
11
+ ]
12
+ </script>
@@ -17,6 +17,8 @@
17
17
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/foundation-sites@6.7.4/dist/css/foundation.min.css">
18
18
  <link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/v/zf/dt-1.11.3/datatables.min.css"/>
19
19
 
20
+ <script type="text/javascript" src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
21
+ <script type="text/javascript" src="https://cdn.datatables.net/v/zf/dt-1.11.3/datatables.min.js"></script>
20
22
 
21
23
  <script src="https://cdn.jsdelivr.net/npm/vega@5.21.0"></script>
22
24
  <script src="https://cdn.jsdelivr.net/npm/vega-lite@5.2.0"></script>
@@ -29,14 +31,14 @@
29
31
  }
30
32
 
31
33
  #offCanvas {
32
- color: white;
33
- background: #0D0630;
34
+ color: #DEDEDE;
35
+ background: #1C1C1C;
34
36
  border-right: none;
35
37
  box-shadow: none;
36
38
  padding: 0.5rem;
37
39
  }
38
40
  #offCanvas a {
39
- color: #E6F9AF;
41
+ color: #FFFFFF;
40
42
  }
41
43
 
42
44
  .contents-button {
@@ -69,7 +71,7 @@
69
71
 
70
72
  .card-divider {
71
73
  padding: 0.2rem 0.4rem 0.2rem 0.4rem;
72
- background: #0d0630;
74
+ background: #1C1C1C;
73
75
  color: white;
74
76
  }
75
77
 
@@ -224,24 +226,93 @@
224
226
  header: ["Day", "DOW", "Hits", "Visits", "Size"],
225
227
  rows: data[:daily_distribution],
226
228
  vega_spec: {
227
- "mark": {
228
- "type": "line",
229
- "point": {
230
- "filled": false,
231
- "fill": "white"
232
- }
233
- },
229
+ "layer": [
230
+ {
231
+ "mark": {
232
+ "type": "line",
233
+ "point": {
234
+ "filled": false,
235
+ "fill": "white"
236
+ }
237
+ },
238
+ "encoding": {
239
+ "y": {"field": "Hits", "type": "quantitative"}
240
+ }
241
+ },
242
+ {
243
+ "mark": {
244
+ "type": "text",
245
+ "color": "#3E5772",
246
+ "align": "middle",
247
+ "baseline": "top",
248
+ "dx": -10,
249
+ "yOffset": -15
250
+ },
251
+ "encoding": {
252
+ "text": {"field": "Hits", "type": "quantitative"},
253
+ "y": {"field": "Hits", "type": "quantitative"}
254
+ }
255
+ },
256
+
257
+ {
258
+ "mark": {
259
+ "type": "line",
260
+ "color": "#A52A2A",
261
+ "point": {
262
+ "color": "#A52A2A",
263
+ "filled": false,
264
+ "fill": "white",
265
+ }
266
+ },
267
+ "encoding": {
268
+ "y": {"field": "Visits", "type": "quantitative"}
269
+ }
270
+ },
271
+
272
+ {
273
+ "mark": {
274
+ "type": "text",
275
+ "color": "#A52A2A",
276
+ "align": "middle",
277
+ "baseline": "top",
278
+ "dx": -10,
279
+ "yOffset": -15
280
+ },
281
+ "encoding": {
282
+ "text": {"field": "Visits", "type": "quantitative"},
283
+ "y": {"field": "Visits", "type": "quantitative"}
284
+ }
285
+ },
286
+
287
+ ],
234
288
  "encoding": {
235
289
  "x": {"field": "Day", "type": "temporal"},
236
- "y": {"field": "Hits", "type": "quantitative"}
237
290
  }
238
291
  }
292
+
239
293
  },
240
294
  { title: "Time Distribution",
241
295
  header: ["Hour", "Hits", "Visits", "Size"],
242
296
  rows: data[:time_distribution],
243
297
  vega_spec: {
244
- "mark": "bar",
298
+ "layer": [
299
+ {
300
+ "mark": "bar"
301
+ },
302
+ {
303
+ "mark": {
304
+ "type": "text",
305
+ "align": "middle",
306
+ "baseline": "top",
307
+ "dx": -10,
308
+ "yOffset": -15
309
+ },
310
+ "encoding": {
311
+ "text": {"field": "Hits", "type": "quantitative"},
312
+ "y": {"field": "Hits", "type": "quantitative"}
313
+ }
314
+ },
315
+ ],
245
316
  "encoding": {
246
317
  "x": {"field": "Hour", "type": "nominal"},
247
318
  "y": {"field": "Hits", "type": "quantitative"}
@@ -299,7 +370,21 @@
299
370
  header: ["Browser", "Hits", "Visits", "Size"],
300
371
  rows: data[:browsers],
301
372
  vega_spec: {
302
- "mark": "bar",
373
+ "layer": [
374
+ { "mark": "bar" },
375
+ {
376
+ "mark": {
377
+ "type": "text",
378
+ "align": "middle",
379
+ "baseline": "top",
380
+ "dx": -10,
381
+ "yOffset": -15
382
+ },
383
+ "encoding": {
384
+ "text": {"field": "Hits", "type": "quantitative"},
385
+ }
386
+ },
387
+ ],
303
388
  "encoding": {
304
389
  "x": {"field": "Browser", "type": "nominal"},
305
390
  "y": {"field": "Hits", "type": "quantitative"}
@@ -310,7 +395,21 @@
310
395
  header: ["Platform", "Hits", "Visits", "Size"],
311
396
  rows: data[:platforms],
312
397
  vega_spec: {
313
- "mark": "bar",
398
+ "layer": [
399
+ { "mark": "bar" },
400
+ {
401
+ "mark": {
402
+ "type": "text",
403
+ "align": "middle",
404
+ "baseline": "top",
405
+ "dx": -10,
406
+ "yOffset": -15
407
+ },
408
+ "encoding": {
409
+ "text": {"field": "Hits", "type": "quantitative"},
410
+ }
411
+ },
412
+ ],
314
413
  "encoding": {
315
414
  "x": {"field": "Platform", "type": "nominal"},
316
415
  "y": {"field": "Hits", "type": "quantitative"}
@@ -325,11 +424,11 @@
325
424
  <% @reports.each_with_index do |report, index| %>
326
425
  <article class="card cell <%= report[:col] || "small-12 large-6" %>" >
327
426
  <div class="card-divider">
328
- <h2>
427
+ <h2 id="<%= report[:title].downcase.gsub(' ', '-') %>">
329
428
  <%= report[:title] %>
330
429
  </h2>
331
430
  </div>
332
-
431
+ <%= render "report_data.html.erb", report: report, index: index %>
333
432
  <% if report[:vega_spec] %>
334
433
  <div id="<%= "plot-#{index}" %>"></div>
335
434
  <script>
@@ -339,22 +438,15 @@
339
438
  width: "container",
340
439
  description: "<%= report[:title] %>",
341
440
  data: {
342
- values: [
343
- <% report[:rows].each do |row| %>
344
- {
345
- <% report[:header].each_with_index do |h, i| %>
346
- "<%= h %>": <%= (row[i].class == Integer or row[i].class == Float) ? row[i] : "\"#{row[i]}\"" %>,
347
- <% end %>
348
- },
349
- <% end %>
350
- ]
441
+ values: data_<%= index %>
351
442
  },
352
443
  });
353
444
  vegaEmbed('#<%= "plot-#{index}"%>', plot_spec_<%= index %>);
354
445
  </script>
355
446
  <% end %>
356
- <div class="card-section">
357
- <%= render "output_table.html.erb", report %>
447
+
448
+ <div class="card-section">
449
+ <%= render "output_table.html.erb", report: report, index: index %>
358
450
  </div>
359
451
  </article>
360
452
  <% end %>
@@ -472,9 +564,7 @@
472
564
  </section>
473
565
  </div>
474
566
 
475
- <script type="text/javascript" src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
476
567
  <script type="text/javascript" src="js/vendor/what-input.js"></script>
477
- <script type="text/javascript" src="https://cdn.datatables.net/v/zf/dt-1.11.3/datatables.min.js"></script>
478
568
  <script src="https://cdn.jsdelivr.net/npm/vega@5"></script>
479
569
  <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/foundation-sites@6.7.4/dist/js/foundation.min.js" crossorigin="anonymous"></script>
480
570
  <script>
@@ -17,6 +17,8 @@
17
17
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/foundation-sites@6.7.4/dist/css/foundation.min.css">
18
18
  <link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/v/zf/dt-1.11.3/datatables.min.css"/>
19
19
 
20
+ <script type="text/javascript" src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
21
+ <script type="text/javascript" src="https://cdn.datatables.net/v/zf/dt-1.11.3/datatables.min.js"></script>
20
22
 
21
23
  <script src="https://cdn.jsdelivr.net/npm/vega@5.21.0"></script>
22
24
  <script src="https://cdn.jsdelivr.net/npm/vega-lite@5.2.0"></script>
@@ -29,14 +31,14 @@
29
31
  }
30
32
 
31
33
  #offCanvas {
32
- color: white;
33
- background: #0D0630;
34
+ color: #CECECE;
35
+ background: #BD000D;
34
36
  border-right: none;
35
37
  box-shadow: none;
36
38
  padding: 0.5rem;
37
39
  }
38
40
  #offCanvas a {
39
- color: #E6F9AF;
41
+ color: #FFFFFF;
40
42
  }
41
43
 
42
44
  .contents-button {
@@ -218,24 +220,55 @@
218
220
  header: ["Day", "DOW", "Hits"],
219
221
  rows: data[:daily_distribution],
220
222
  vega_spec: {
221
- "mark": {
222
- "type": "line",
223
- "point": {
224
- "filled": false,
225
- "fill": "white"
226
- }
227
- },
228
223
  "encoding": {
229
224
  "x": {"field": "Day", "type": "temporal"},
230
225
  "y": {"field": "Hits", "type": "quantitative"}
231
- }
226
+ },
227
+ "layer": [
228
+ {
229
+ "mark": {
230
+ "type": "line",
231
+ "point": {
232
+ "filled": false,
233
+ "fill": "white"
234
+ }
235
+ }
236
+ },
237
+ {
238
+ "mark": {
239
+ "type": "text",
240
+ "align": "left",
241
+ "baseline": "middle",
242
+ "dx": 5
243
+ },
244
+ "encoding": {
245
+ "text": {"field": "Hits", "type": "quantitative"}
246
+ }
247
+ }
248
+ ]
232
249
  }
233
250
  },
234
251
  { title: "Time Distribution",
235
252
  header: ["Hour", "Hits"],
236
253
  rows: data[:time_distribution],
237
254
  vega_spec: {
238
- "mark": "bar",
255
+ "layer": [
256
+ {
257
+ "mark": "bar",
258
+ },
259
+ {
260
+ "mark": {
261
+ "type": "text",
262
+ "align": "middle",
263
+ "baseline": "top",
264
+ "dx": -10,
265
+ "yOffset": -15
266
+ },
267
+ "encoding": {
268
+ "text": {"field": "Hits", "type": "quantitative"}
269
+ }
270
+ }
271
+ ],
239
272
  "encoding": {
240
273
  "x": {"field": "Hour", "type": "nominal"},
241
274
  "y": {"field": "Hits", "type": "quantitative"}
@@ -246,7 +279,23 @@
246
279
  header: ["Status", "Count"],
247
280
  rows: data[:statuses],
248
281
  vega_spec: {
249
- "mark": "bar",
282
+ "layer": [
283
+ {
284
+ "mark": "bar"
285
+ },
286
+ {
287
+ "mark": {
288
+ "type": "text",
289
+ "align": "left",
290
+ "baseline": "top",
291
+ "dx": -10,
292
+ "yOffset": -20
293
+ },
294
+ "encoding": {
295
+ "text": {"field": "Count", "type": "quantitative"}
296
+ }
297
+ }
298
+ ],
250
299
  "encoding": {
251
300
  "x": {"field": "Status", "type": "nominal"},
252
301
  "y": {"field": "Count", "type": "quantitative"}
@@ -257,7 +306,11 @@
257
306
  header: ['Controller', 'Hits', 'Min', 'Avg', 'Max'],
258
307
  rows: @data[:performance],
259
308
  vega_spec: {
260
- "mark": "point",
309
+ "layer": [
310
+ {
311
+ "mark": "point"
312
+ },
313
+ ],
261
314
  "encoding": {
262
315
  "x": {"field": "Avg", "type": "quantitative"},
263
316
  "y": {"field": "Hits", "type": "quantitative"}
@@ -286,11 +339,11 @@
286
339
  <% @reports.each_with_index do |report, index| %>
287
340
  <article class="card cell <%= report[:col] || "small-12 large-6" %>" >
288
341
  <div class="card-divider">
289
- <h2>
342
+ <h2 id="<%= report[:title].downcase.gsub(' ', '-') %>">
290
343
  <%= report[:title] %>
291
344
  </h2>
292
345
  </div>
293
-
346
+ <%= render "report_data.html.erb", report: report, index: index %>
294
347
  <% if report[:vega_spec] %>
295
348
  <div id="<%= "plot-#{index}" %>"></div>
296
349
  <script>
@@ -300,22 +353,14 @@
300
353
  width: "container",
301
354
  description: "<%= report[:title] %>",
302
355
  data: {
303
- values: [
304
- <% report[:rows].each do |row| %>
305
- {
306
- <% report[:header].each_with_index do |h, i| %>
307
- "<%= h %>": <%= (row[i].class == Integer or row[i].class == Float) ? row[i] : "\"#{row[i]}\"" %>,
308
- <% end %>
309
- },
310
- <% end %>
311
- ]
356
+ values: data_<%= index %>
312
357
  },
313
358
  });
314
359
  vegaEmbed('#<%= "plot-#{index}"%>', plot_spec_<%= index %>);
315
360
  </script>
316
361
  <% end %>
317
362
  <div class="card-section">
318
- <%= render "output_table.html.erb", report %>
363
+ <%= render "output_table.html.erb", report: report, index: index %>
319
364
  </div>
320
365
  </article>
321
366
  <% end %>
@@ -341,9 +386,7 @@
341
386
  </section>
342
387
  </div>
343
388
 
344
- <script type="text/javascript" src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
345
389
  <script type="text/javascript" src="js/vendor/what-input.js"></script>
346
- <script type="text/javascript" src="https://cdn.datatables.net/v/zf/dt-1.11.3/datatables.min.js"></script>
347
390
  <script src="https://cdn.jsdelivr.net/npm/vega@5"></script>
348
391
  <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/foundation-sites@6.7.4/dist/js/foundation.min.js" crossorigin="anonymous"></script>
349
392
  <script>
@@ -1,3 +1,3 @@
1
1
  module LogSense
2
- VERSION = "1.2.2"
2
+ VERSION = "1.3.2"
3
3
  end
data/lib/log_sense.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  require 'log_sense/version'
2
2
  require 'log_sense/options_parser'
3
+ require 'log_sense/apache_log_line_parser'
3
4
  require 'log_sense/apache_log_parser'
4
5
  require 'log_sense/apache_data_cruncher'
5
6
  require 'log_sense/rails_log_parser'
data/log_sense.gemspec CHANGED
@@ -10,7 +10,7 @@ Gem::Specification.new do |spec|
10
10
  spec.description = %q{Generate analystics in HTML, txt, and SQLite format from an Apache and Rails log files.}
11
11
  spec.homepage = "https://www.ict4g.net/gitea/adolfo/log_sense"
12
12
  spec.license = "MIT"
13
- spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
13
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.6.9")
14
14
 
15
15
  spec.metadata["allowed_push_host"] = "https://rubygems.org/"
16
16
 
@@ -27,7 +27,6 @@ Gem::Specification.new do |spec|
27
27
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
28
28
  spec.require_paths = ["lib"]
29
29
 
30
- spec.add_dependency "apache_log-parser"
31
30
  spec.add_dependency "browser"
32
31
  spec.add_dependency "ipaddr"
33
32
  spec.add_dependency "iso_country_codes"
metadata CHANGED
@@ -1,29 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: log_sense
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.2
4
+ version: 1.3.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Adolfo Villafiorita
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-12-21 00:00:00.000000000 Z
11
+ date: 2022-01-12 00:00:00.000000000 Z
12
12
  dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: apache_log-parser
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - ">="
18
- - !ruby/object:Gem::Version
19
- version: '0'
20
- type: :runtime
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - ">="
25
- - !ruby/object:Gem::Version
26
- version: '0'
27
13
  - !ruby/object:Gem::Dependency
28
14
  name: browser
29
15
  requirement: !ruby/object:Gem::Requirement
@@ -138,12 +124,14 @@ files:
138
124
  - LICENSE.txt
139
125
  - README.org
140
126
  - Rakefile
127
+ - apache-screenshot.png
141
128
  - bin/console
142
129
  - bin/setup
143
130
  - exe/log_sense
144
131
  - ip_locations/dbip-country-lite.sqlite3
145
132
  - lib/log_sense.rb
146
133
  - lib/log_sense/apache_data_cruncher.rb
134
+ - lib/log_sense/apache_log_line_parser.rb
147
135
  - lib/log_sense/apache_log_parser.rb
148
136
  - lib/log_sense/emitter.rb
149
137
  - lib/log_sense/ip_locator.rb
@@ -156,6 +144,7 @@ files:
156
144
  - lib/log_sense/templates/_output_table.html.erb
157
145
  - lib/log_sense/templates/_performance.html.erb
158
146
  - lib/log_sense/templates/_performance.txt.erb
147
+ - lib/log_sense/templates/_report_data.html.erb
159
148
  - lib/log_sense/templates/_summary.html.erb
160
149
  - lib/log_sense/templates/_summary.txt.erb
161
150
  - lib/log_sense/templates/apache.html.erb
@@ -182,14 +171,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
182
171
  requirements:
183
172
  - - ">="
184
173
  - !ruby/object:Gem::Version
185
- version: 2.3.0
174
+ version: 2.6.9
186
175
  required_rubygems_version: !ruby/object:Gem::Requirement
187
176
  requirements:
188
177
  - - ">="
189
178
  - !ruby/object:Gem::Version
190
179
  version: '0'
191
180
  requirements: []
192
- rubygems_version: 3.2.32
181
+ rubygems_version: 3.0.3.1
193
182
  signing_key:
194
183
  specification_version: 4
195
184
  summary: Generate analytics from an Apache and Rails log file.