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 +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.
|