log_sense 1.2.2 → 1.3.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 +13 -6
- data/Gemfile.lock +1 -3
- data/README.org +48 -31
- data/apache-screenshot.png +0 -0
- data/lib/log_sense/apache_log_line_parser.rb +56 -0
- data/lib/log_sense/apache_log_parser.rb +14 -12
- data/lib/log_sense/templates/_log_structure.html.erb +2 -2
- data/lib/log_sense/templates/_output_table.html.erb +15 -11
- data/lib/log_sense/templates/_report_data.html.erb +12 -0
- data/lib/log_sense/templates/apache.html.erb +120 -30
- data/lib/log_sense/templates/rails.html.erb +71 -28
- data/lib/log_sense/version.rb +1 -1
- data/lib/log_sense.rb +1 -0
- data/log_sense.gemspec +1 -2
- metadata +7 -18
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6498507bb6ae7e6ba5505609421154358790f095bd9cfe5ab912920b2409f7d1
|
4
|
+
data.tar.gz: 0ccb5fa005b8f1e47545bf98ce05273f07bd452d3be54c5bd1a7f124c96558d9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
*
|
6
|
-
<2021-12-17 Fri>
|
5
|
+
* 1.3.2
|
7
6
|
|
8
|
-
-
|
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
|
-
*
|
11
|
-
<2021-12-17 Fri>
|
13
|
+
* 1.3.1
|
12
14
|
|
13
|
-
-
|
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.
|
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
|
8
|
-
|
9
|
-
|
10
|
-
|
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
|
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
|
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
|
-
-
|
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
|
-
[[
|
116
|
+
[[https://shair.tech][Shair.Tech]]
|
103
117
|
|
104
118
|
* Known Bugs
|
105
119
|
|
106
|
-
|
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,
|
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 =
|
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[:
|
62
|
-
hash[:
|
63
|
-
hash[:
|
64
|
-
hash
|
65
|
-
hash[:
|
66
|
-
hash[:
|
67
|
-
(hash[:
|
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
|
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
|
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
|
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="
|
7
|
+
<table id="table-<%= index %>" class="table unstriped">
|
8
8
|
<thead>
|
9
9
|
<tr>
|
10
|
-
<% header.each do |
|
11
|
-
<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:
|
33
|
-
background: #
|
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: #
|
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: #
|
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
|
-
"
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
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
|
-
"
|
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
|
-
"
|
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
|
-
"
|
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
|
-
|
357
|
-
|
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:
|
33
|
-
background: #
|
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: #
|
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
|
-
"
|
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
|
-
"
|
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
|
-
"
|
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>
|
data/lib/log_sense/version.rb
CHANGED
data/lib/log_sense.rb
CHANGED
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.
|
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.
|
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:
|
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.
|
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.
|
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.
|