log_sense 2.0.0 → 2.1.0
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 +20 -14
- data/README.org +4 -3
- data/ip_locations/dbip-country-lite.sqlite3 +0 -0
- data/lib/log_sense/apache_aggregator.rb +1 -1
- data/lib/log_sense/emitter.rb +9 -0
- data/lib/log_sense/rails/log_parser.rb +224 -150
- data/lib/log_sense/rails_aggregator.rb +63 -39
- data/lib/log_sense/rails_report_shaper.rb +68 -28
- data/lib/log_sense/report_shaper.rb +3 -2
- data/lib/log_sense/templates/_output_table.html.erb +13 -16
- data/lib/log_sense/templates/_stylesheet.css +20 -1
- data/lib/log_sense/templates/report_html.erb +4 -0
- data/lib/log_sense/templates/world.svg +1 -1
- data/lib/log_sense/version.rb +1 -1
- data/todo.org +7 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e17413654dc552a716548d7a55e127c5f0703cf29643d97d500e0cf51b20eba3
|
4
|
+
data.tar.gz: 56765b54fdda4f64add3fbb97ea3b4073976d25f4cb67cc932a7d350d85793cb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dc77b1c63c08a9a190aae0ab0ab44bdffe37e0dc2e999d3dc8a236703dc8e430fb32ff09d884d5d9e90ff7870b7bb212d0cc486e877982ca3c5328a3aeda5943
|
7
|
+
data.tar.gz: e73ad267ad54dc4e3c09481ec4f055e6d9e1e9346262b04975b754bd8822b19ce27298299ce6fb7cc3511e5248b6303392c984b48d68d40bd398bd14d7be9d80
|
data/CHANGELOG.org
CHANGED
@@ -2,20 +2,26 @@
|
|
2
2
|
#+AUTHOR: Adolfo Villafiorita
|
3
3
|
#+STARTUP: showall
|
4
4
|
|
5
|
-
* 2.0
|
6
|
-
|
7
|
-
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
-
|
13
|
-
|
14
|
-
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
-
|
5
|
+
* 2.1.0
|
6
|
+
|
7
|
+
- [User] Delayed Job Errors
|
8
|
+
- [User] Improve scatter plot about performances (Use labels, swap x and y axes,
|
9
|
+
better tooltip)
|
10
|
+
- [User] Updated DB-IP country file to Aug 2024 version (19/08).
|
11
|
+
- [User] Fixes CHANGELOG
|
12
|
+
- [User] Revises "Fatal Errors" removing a bit of redundancy and collecting all
|
13
|
+
message
|
14
|
+
- [Bug/User] Added filtering on date on reports which still did not use it
|
15
|
+
|
16
|
+
* 2.0.0
|
17
|
+
|
18
|
+
- [User] World Map
|
19
|
+
- [User] Dark mode
|
20
|
+
- [User] Bars in the statuses bar plot are now colored according to status
|
21
|
+
- [User] Add "statuses by day" in Rails report
|
22
|
+
- [User] Enlarge "errors" and "potential attacks" reports
|
23
|
+
- [Bug] Fix link colors in sidebar
|
24
|
+
- [Code] Various smaller fixes
|
19
25
|
|
20
26
|
* 1.9.0
|
21
27
|
|
data/README.org
CHANGED
@@ -47,9 +47,10 @@ reports in TXT and HTML:
|
|
47
47
|
- Rails Performance
|
48
48
|
- Controller and Methods by Device
|
49
49
|
- Fatal Events
|
50
|
-
-
|
51
|
-
-
|
52
|
-
-
|
50
|
+
- Fatal Events
|
51
|
+
- Fatal Events (grouped by type)
|
52
|
+
- Job Error
|
53
|
+
- Job Errors (grouped)
|
53
54
|
- Browsers
|
54
55
|
- Platforms
|
55
56
|
- IPs
|
Binary file
|
@@ -112,7 +112,7 @@ module LogSense
|
|
112
112
|
count(path),
|
113
113
|
count(distinct(unique_visitor)),
|
114
114
|
#{human_readable_size}, status from LogLine
|
115
|
-
where #{
|
115
|
+
where #{filter} and #{result} and #{type}
|
116
116
|
group by path
|
117
117
|
order by count(path) desc
|
118
118
|
limit #{@options[:limit]}"
|
data/lib/log_sense/emitter.rb
CHANGED
@@ -79,6 +79,15 @@ module LogSense
|
|
79
79
|
"$" => "\\$"
|
80
80
|
}
|
81
81
|
|
82
|
+
# output text-left, center, right, according to column_alignment
|
83
|
+
def self.alignment_class(report, index)
|
84
|
+
if report[:column_alignment]
|
85
|
+
"text-#{report[:column_alignment][index]}"
|
86
|
+
else
|
87
|
+
""
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
82
91
|
# taken from Ruby on Rails
|
83
92
|
def self.escape_javascript(javascript)
|
84
93
|
javascript = javascript.to_s
|
@@ -84,28 +84,6 @@ module LogSense
|
|
84
84
|
values (?, ?, ?, ?, ?)
|
85
85
|
EOS
|
86
86
|
|
87
|
-
db.execute <<-EOS
|
88
|
-
CREATE TABLE IF NOT EXISTS Render(
|
89
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
90
|
-
partial TEXT,
|
91
|
-
duration_ms FLOAT,
|
92
|
-
allocations INTEGER,
|
93
|
-
filename TEXT,
|
94
|
-
line_number INTEGER
|
95
|
-
)
|
96
|
-
EOS
|
97
|
-
|
98
|
-
ins_rendered = db.prepare <<-EOS
|
99
|
-
insert into Render(
|
100
|
-
partial,
|
101
|
-
duration_ms,
|
102
|
-
allocations,
|
103
|
-
filename,
|
104
|
-
line_number
|
105
|
-
)
|
106
|
-
values (?, ?, ?, ?, ?)
|
107
|
-
EOS
|
108
|
-
|
109
87
|
db.execute <<-EOS
|
110
88
|
CREATE TABLE IF NOT EXISTS BrowserInfo(
|
111
89
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
@@ -134,6 +112,51 @@ module LogSense
|
|
134
112
|
values (?, ?, ?, ?, ?, ?, ?, ?)
|
135
113
|
EOS
|
136
114
|
|
115
|
+
# jobs
|
116
|
+
db.execute <<-EOS
|
117
|
+
CREATE TABLE IF NOT EXISTS Job(
|
118
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
119
|
+
started_at TEXT,
|
120
|
+
ended_at TEXT,
|
121
|
+
duration_total_ms FLOAT,
|
122
|
+
worker TEXT,
|
123
|
+
host TEXT,
|
124
|
+
pid TEXT,
|
125
|
+
log_id TEXT,
|
126
|
+
job_id TEXT,
|
127
|
+
object_id TEXT,
|
128
|
+
method TEXT,
|
129
|
+
arguments TEXT,
|
130
|
+
exit_status TEXT,
|
131
|
+
attempt INTEGER,
|
132
|
+
error_msg TEXT,
|
133
|
+
source_file TEXT,
|
134
|
+
line_number INTEGER
|
135
|
+
)
|
136
|
+
EOS
|
137
|
+
|
138
|
+
ins_job = db.prepare <<-EOS
|
139
|
+
insert into Job(
|
140
|
+
started_at,
|
141
|
+
ended_at,
|
142
|
+
duration_total_ms,
|
143
|
+
worker,
|
144
|
+
host,
|
145
|
+
pid,
|
146
|
+
log_id,
|
147
|
+
job_id,
|
148
|
+
object_id,
|
149
|
+
method,
|
150
|
+
arguments,
|
151
|
+
exit_status,
|
152
|
+
attempt,
|
153
|
+
error_msg,
|
154
|
+
source_file,
|
155
|
+
line_number
|
156
|
+
)
|
157
|
+
values (#{Array.new(16, "?").join(", ")})
|
158
|
+
EOS
|
159
|
+
|
137
160
|
# requests in the log might be interleaved.
|
138
161
|
#
|
139
162
|
# We use the 'pending' variable to progressively store data
|
@@ -142,6 +165,12 @@ module LogSense
|
|
142
165
|
# hash
|
143
166
|
pending = {}
|
144
167
|
|
168
|
+
# Fatal explanation messages span several lines (2, 4, ?)
|
169
|
+
#
|
170
|
+
# We keep a Hash with the FATAL explanation messages and we persist when
|
171
|
+
# the parsing ends
|
172
|
+
fatal_explanation_messages = {}
|
173
|
+
|
145
174
|
# Log lines are either one of:
|
146
175
|
#
|
147
176
|
# LOG_LEVEL, [ZULU_TIMESTAMP #NUMBER] INFO --: [ID] Started VERB "URL" for IP at TIMESTAMP
|
@@ -158,24 +187,9 @@ module LogSense
|
|
158
187
|
stream.readlines.each_with_index do |line, line_number|
|
159
188
|
filename = stream == $stdin ? "stdin" : stream.path
|
160
189
|
|
161
|
-
#
|
162
|
-
#
|
163
|
-
|
164
|
-
|
165
|
-
data = match_and_process_rendered line
|
166
|
-
if data
|
167
|
-
ins_rendered.execute(
|
168
|
-
data[:partial], data[:duration], data[:allocations],
|
169
|
-
filename, line_number
|
170
|
-
)
|
171
|
-
end
|
172
|
-
|
173
|
-
#
|
174
|
-
#
|
175
|
-
#
|
176
|
-
|
177
|
-
# I and F for completed requests, [ is for error messages
|
178
|
-
next if line[0] != 'I' and line[0] != 'F' and line[0] != '['
|
190
|
+
# I, F are for completed and failed requests, [ is for the FATAL
|
191
|
+
# error message explanation
|
192
|
+
next unless ['I', 'F', 'E', '['].include? line[0]
|
179
193
|
|
180
194
|
data = match_and_process_browser_info line
|
181
195
|
if data
|
@@ -190,16 +204,6 @@ module LogSense
|
|
190
204
|
next
|
191
205
|
end
|
192
206
|
|
193
|
-
data = match_and_process_error line
|
194
|
-
if data
|
195
|
-
ins_error.execute(data[:log_id],
|
196
|
-
data[:context],
|
197
|
-
data[:description],
|
198
|
-
filename,
|
199
|
-
line_number)
|
200
|
-
next
|
201
|
-
end
|
202
|
-
|
203
207
|
data = match_and_process_start line
|
204
208
|
if data
|
205
209
|
id = data[:log_id]
|
@@ -214,15 +218,14 @@ module LogSense
|
|
214
218
|
next
|
215
219
|
end
|
216
220
|
|
221
|
+
# fatal message is alternative to completed and is used to insert an
|
222
|
+
# Event
|
217
223
|
data = match_and_process_fatal line
|
218
224
|
if data
|
219
225
|
id = data[:log_id]
|
220
|
-
# it might as well be that the first event started before
|
221
|
-
# the log. With this, we make sure we add only events whose
|
222
|
-
# start was logged and parsed
|
223
226
|
if pending[id]
|
224
|
-
|
225
|
-
|
227
|
+
# data last, so that we respect, for instance, the 'F' state
|
228
|
+
event = pending[id].merge(data)
|
226
229
|
ins.execute(
|
227
230
|
event[:exit_status],
|
228
231
|
event[:started_at],
|
@@ -247,7 +250,7 @@ module LogSense
|
|
247
250
|
end
|
248
251
|
end
|
249
252
|
|
250
|
-
data =
|
253
|
+
data = match_and_process_completed line
|
251
254
|
if data
|
252
255
|
id = data[:log_id]
|
253
256
|
|
@@ -255,7 +258,9 @@ module LogSense
|
|
255
258
|
# the log. With this, we make sure we add only events whose
|
256
259
|
# start was logged and parsed
|
257
260
|
if pending[id]
|
258
|
-
|
261
|
+
# data last, so that we respect the most recent data (the last
|
262
|
+
# log line)
|
263
|
+
event = pending[id].merge(data)
|
259
264
|
|
260
265
|
ins.execute(
|
261
266
|
event[:exit_status],
|
@@ -280,15 +285,66 @@ module LogSense
|
|
280
285
|
pending.delete(id)
|
281
286
|
end
|
282
287
|
end
|
288
|
+
|
289
|
+
# fatal_explanations are multiple lines with a description of the
|
290
|
+
# fatal error and they all use the ID of the FATAL event (so that
|
291
|
+
# we can later join)
|
292
|
+
data = match_and_process_fatal_explanation line
|
293
|
+
if data
|
294
|
+
previous = fatal_explanation_messages[data[:log_id]]
|
295
|
+
|
296
|
+
# keep adding to the explanation
|
297
|
+
fatal_explanation_messages[data[:log_id]] = [
|
298
|
+
data[:log_id],
|
299
|
+
[previous ? previous[1] : "", data[:context]].compact.join(" "),
|
300
|
+
[previous ? previous[2] : "", data[:description]].compact.join(" "),
|
301
|
+
filename,
|
302
|
+
line_number
|
303
|
+
]
|
304
|
+
next
|
305
|
+
end
|
306
|
+
|
307
|
+
#
|
308
|
+
# Match job errors
|
309
|
+
#
|
310
|
+
data = match_and_process_job_error line
|
311
|
+
if data
|
312
|
+
ins_job.execute(
|
313
|
+
data[:ended_at], # this is temporary (while we wait to parse BEGIN) + required by filter
|
314
|
+
data[:ended_at],
|
315
|
+
0,
|
316
|
+
data[:worker],
|
317
|
+
data[:host],
|
318
|
+
data[:pid],
|
319
|
+
data[:log_id],
|
320
|
+
"",
|
321
|
+
data[:object_id],
|
322
|
+
data[:method],
|
323
|
+
data[:arguments],
|
324
|
+
data[:exit_status],
|
325
|
+
data[:attempt],
|
326
|
+
data[:error_msg],
|
327
|
+
filename,
|
328
|
+
line_number
|
329
|
+
)
|
330
|
+
end
|
283
331
|
end
|
284
332
|
end
|
285
333
|
|
334
|
+
# persist the fatal error messages
|
335
|
+
# TODO massive update
|
336
|
+
fatal_explanation_messages.values.map do |value|
|
337
|
+
ins_error.execute(value)
|
338
|
+
end
|
339
|
+
|
286
340
|
db
|
287
341
|
end
|
288
342
|
|
343
|
+
# could be private here, I guess we keep them public to make them simpler
|
344
|
+
# to try from irb
|
289
345
|
|
290
|
-
TIMESTAMP = /(?<timestamp>[
|
291
|
-
|
346
|
+
TIMESTAMP = /(?<timestamp>[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]+)/
|
347
|
+
LOG_ID = /(?<log_id>[a-z0-9-]+)/
|
292
348
|
VERB = /(?<verb>GET|POST|PATCH|PUT|DELETE)/
|
293
349
|
URL = /(?<url>[^"]+)/
|
294
350
|
IP = /(?<ip>[0-9.]+)/
|
@@ -296,51 +352,15 @@ module LogSense
|
|
296
352
|
STATUS_IN_WORDS = /(OK|Unauthorized|Found|Internal Server Error|Bad Request|Method Not Allowed|Request Timeout|Not Implemented|Bad Gateway|Service Unavailable)/
|
297
353
|
MSECS = /[0-9.]+/
|
298
354
|
|
299
|
-
# I, [2024-07-01T02:21:34.339058 #1392909] INFO -- : [815b3e28-8d6e-4741-8605-87654a9ff58c] BrowserInfo: "Unknown Browser","unknown_platform","Unknown","Devise::SessionsController","new","html","4db749654a0fcacbf3868f87723926e7405262f8d596e8514f4997dc80a3cd7e","2024-07-01T02:21:34+02:00"
|
300
|
-
BROWSER_INFO_REGEXP = /BrowserInfo: "(?<browser>.+)","(?<platform>.+)","(?<device_name>.+)","(?<controller>.+)","(?<method>.+)","(?<request_format>.+)","(?<anon_ip>.+)","(?<timestamp>.+)"/
|
301
|
-
def match_and_process_browser_info(line)
|
302
|
-
matchdata = BROWSER_INFO_REGEXP.match line
|
303
|
-
if matchdata
|
304
|
-
{
|
305
|
-
browser: matchdata[:browser],
|
306
|
-
platform: matchdata[:platform],
|
307
|
-
device_name: matchdata[:device_name],
|
308
|
-
controller: matchdata[:controller],
|
309
|
-
method: matchdata[:method],
|
310
|
-
request_format: matchdata[:request_format],
|
311
|
-
anon_ip: matchdata[:anon_ip],
|
312
|
-
timestamp: matchdata[:timestamp],
|
313
|
-
}
|
314
|
-
end
|
315
|
-
end
|
316
|
-
|
317
|
-
# Error Messages
|
318
|
-
# [584cffcc-f1fd-4b5c-bb8b-b89621bd4921] ActionController::RoutingError (No route matches [GET] "/assets/foundation-icons.svg"):
|
319
|
-
# [fd8df8b5-83c9-48b5-a056-e5026e31bd5e] ActionView::Template::Error (undefined method `all_my_ancestor' for nil:NilClass):
|
320
|
-
# [d17ed55c-f5f1-442a-a9d6-3035ab91adf0] ActionView::Template::Error (undefined method `volunteer_for' for #<DonationsController:0x007f4864c564b8>
|
321
|
-
EXCEPTION = /[A-Za-z_0-9:]+(Error)?/
|
322
|
-
ERROR_REGEXP = /^\[#{ID}\] (?<context>#{EXCEPTION}) \((?<description>(#{EXCEPTION})?.*)\):/
|
323
|
-
|
324
|
-
def match_and_process_error(line)
|
325
|
-
matchdata = ERROR_REGEXP.match line
|
326
|
-
if matchdata
|
327
|
-
{
|
328
|
-
log_id: matchdata[:id],
|
329
|
-
context: matchdata[:context],
|
330
|
-
description: matchdata[:description]
|
331
|
-
}
|
332
|
-
end
|
333
|
-
end
|
334
|
-
|
335
355
|
# I, [2021-10-19T08:16:34.343858 #10477] INFO -- : [67103c0d-455d-4fe8-951e-87e97628cb66] Started GET "/grow/people/471" for 217.77.80.35 at 2021-10-19 08:16:34 +0000
|
336
|
-
STARTED_REGEXP = /I, \[#{TIMESTAMP} #[0-9]+\] INFO -- : \[#{
|
356
|
+
STARTED_REGEXP = /I, \[#{TIMESTAMP} #[0-9]+\] INFO -- : \[#{LOG_ID}\] Started #{VERB} "#{URL}" for #{IP} at/
|
337
357
|
|
338
358
|
def match_and_process_start(line)
|
339
359
|
matchdata = STARTED_REGEXP.match line
|
340
360
|
if matchdata
|
341
361
|
{
|
342
362
|
started_at: matchdata[:timestamp],
|
343
|
-
log_id: matchdata[:
|
363
|
+
log_id: matchdata[:log_id],
|
344
364
|
html_verb: matchdata[:verb],
|
345
365
|
url: matchdata[:url],
|
346
366
|
ip: matchdata[:ip]
|
@@ -352,7 +372,7 @@ module LogSense
|
|
352
372
|
# I, [2021-10-19T08:16:34.712331 #10477] INFO -- : [67103c0d-455d-4fe8-951e-87e97628cb66] Completed 200 OK in 367ms (Views: 216.7ms | ActiveRecord: 141.3ms | Allocations: 168792)
|
353
373
|
# I, [2021-12-09T16:53:52.657727 #2735058] INFO -- : [0064e403-9eb2-439d-8fe1-a334c86f5532] Completed 200 OK in 13ms (Views: 11.1ms | ActiveRecord: 1.2ms)
|
354
374
|
# I, [2021-12-06T14:28:19.736545 #2804090] INFO -- : [34091cb5-3e7b-4042-aaf8-6c6510d3f14c] Completed 500 Internal Server Error in 66ms (ActiveRecord: 8.0ms | Allocations: 24885)
|
355
|
-
COMPLETED_REGEXP = /I, \[#{TIMESTAMP} #[0-9]+\] INFO -- : \[#{
|
375
|
+
COMPLETED_REGEXP = /I, \[#{TIMESTAMP} #[0-9]+\] INFO -- : \[#{LOG_ID}\] Completed #{STATUS} #{STATUS_IN_WORDS} in (?<total>#{MSECS})ms \((Views: (?<views>#{MSECS})ms \| )?ActiveRecord: (?<arec>#{MSECS})ms( \| Allocations: (?<alloc>[0-9]+))?\)/
|
356
376
|
|
357
377
|
def match_and_process_completed(line)
|
358
378
|
matchdata = (COMPLETED_REGEXP.match line)
|
@@ -361,7 +381,7 @@ module LogSense
|
|
361
381
|
{
|
362
382
|
exit_status: "I",
|
363
383
|
ended_at: matchdata[:timestamp],
|
364
|
-
log_id: matchdata[:
|
384
|
+
log_id: matchdata[:log_id],
|
365
385
|
status: matchdata[:status],
|
366
386
|
duration_total_ms: matchdata[:total],
|
367
387
|
duration_views_ms: matchdata[:views],
|
@@ -373,13 +393,13 @@ module LogSense
|
|
373
393
|
end
|
374
394
|
|
375
395
|
# I, [2021-10-19T08:16:34.345162 #10477] INFO -- : [67103c0d-455d-4fe8-951e-87e97628cb66] Processing by PeopleController#show as HTML
|
376
|
-
PROCESSING_REGEXP = /I, \[#{TIMESTAMP} #[0-9]+\] INFO -- : \[#{
|
396
|
+
PROCESSING_REGEXP = /I, \[#{TIMESTAMP} #[0-9]+\] INFO -- : \[#{LOG_ID}\] Processing by (?<controller>[^ ]+) as/
|
377
397
|
|
378
398
|
def match_and_process_processing_by line
|
379
399
|
matchdata = PROCESSING_REGEXP.match line
|
380
400
|
if matchdata
|
381
401
|
{
|
382
|
-
log_id: matchdata[:
|
402
|
+
log_id: matchdata[:log_id],
|
383
403
|
controller: matchdata[:controller]
|
384
404
|
}
|
385
405
|
end
|
@@ -389,76 +409,130 @@ module LogSense
|
|
389
409
|
# F, [2021-12-04T00:34:05.839157 #2735058] FATAL -- : [3a16162e-a6a5-435e-a9d8-c4df5dc0f728] ActionController::RoutingError (No route matches [GET] "/wp/wp-includes/wlwmanifest.xml"):
|
390
410
|
# F, [2021-12-04T00:34:05.839209 #2735058] FATAL -- : [3a16162e-a6a5-435e-a9d8-c4df5dc0f728]
|
391
411
|
# F, [2021-12-04T00:34:05.839269 #2735058] FATAL -- : [3a16162e-a6a5-435e-a9d8-c4df5dc0f728] actionpack (5.2.4.4) lib/action_dispatch/middleware/debug_exceptions.rb:65:in `call'
|
392
|
-
|
412
|
+
|
413
|
+
FATAL_REGEXP = /F, \[#{TIMESTAMP} #[0-9]+\] FATAL -- : \[#{LOG_ID}\]/
|
393
414
|
|
394
415
|
def match_and_process_fatal(line)
|
395
416
|
matchdata = FATAL_REGEXP.match line
|
396
417
|
if matchdata
|
397
418
|
{
|
398
419
|
exit_status: "F",
|
399
|
-
log_id: matchdata[:
|
400
|
-
comment: matchdata[:comment]
|
420
|
+
log_id: matchdata[:log_id],
|
401
421
|
}
|
402
422
|
end
|
403
423
|
end
|
404
424
|
|
405
|
-
#
|
406
|
-
#
|
407
|
-
# Parameters: {"locale"=>"it"}
|
408
|
-
# [...]
|
409
|
-
# Completed 200 OK in 135ms (Views: 128.0ms | ActiveRecord: 2.5ms | Allocations: 453450)
|
410
|
-
#
|
411
|
-
# Started GET "/serviceworker.js" for 127.0.0.1 at 2024-06-06 23:23:29 +0200
|
412
|
-
# ActionController::RoutingError (No route matches [GET] "/serviceworker.js"):
|
425
|
+
# Explanation of what caused a FATAL event. List of lines starting with the
|
426
|
+
# ID of the fatal event and providing some sort of explanation.
|
413
427
|
#
|
428
|
+
# Notice that we have more than one line per FATAL event
|
414
429
|
#
|
415
|
-
#
|
416
|
-
# Processing by ProjectsController#create as TURBO_STREAM
|
417
|
-
# Parameters: {"authenticity_token"=>"[FILTERED]", "project"=>{"name"=>"AA", "funding_agency"=>"", "total_cost"=>"0,00", "personnel_cost"=>"0,00", "percentage_funded"=>"0", "from_date"=>"2024-01-01", "to_date"=>"2025-12-31", "notes"=>""}, "commit"=>"Crea Progetto", "locale"=>"it"}
|
430
|
+
# [584cffcc-f1fd-4b5c-bb8b-b89621bd4921] ActionController::RoutingError (No route matches [GET] "/assets/foundation-icons.svg"):
|
418
431
|
#
|
419
|
-
#
|
420
|
-
# Completed 422 Unprocessable Entity in 16ms (Views: 5.1ms | ActiveRecord: 2.0ms | Allocations: 10093)
|
432
|
+
# [fd8df8b5-83c9-48b5-a056-e5026e31bd5e] ActionView::Template::Error (undefined method `all_my_ancestor' for nil:NilClass):
|
421
433
|
#
|
422
|
-
#
|
423
|
-
# ActionView::Template::Error (Error: Undefined variable: "$white".
|
424
|
-
# on line 6:28 of app/assets/stylesheets/_animations.scss
|
425
|
-
# from line 16:9 of app/assets/stylesheets/application.scss
|
426
|
-
# >> from { background-color: $white; }
|
427
|
-
|
428
|
-
# ---------------------------^
|
429
|
-
# ):
|
430
|
-
# 9: = csrf_meta_tags
|
431
|
-
# 10: = csp_meta_tag
|
432
|
-
# 11:
|
433
|
-
# 12: = stylesheet_link_tag "application", "data-turbo-track": "reload"
|
434
|
-
# 13: = javascript_importmap_tags
|
435
|
-
# 14:
|
436
|
-
# 15: %body
|
434
|
+
# [d17ed55c-f5f1-442a-a9d6-3035ab91adf0] ActionView::Template::Error (undefined method `volunteer_for' for #<DonationsController:0x007f4864c564b8>
|
437
435
|
#
|
438
|
-
#
|
439
|
-
#
|
440
|
-
|
441
|
-
#
|
442
|
-
#
|
443
|
-
#
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
RENDERED_REGEXP = /^ *Rendered (?<partial>[^ ]+) .*\(Duration: (?<duration>[0-9.]+)ms \| Allocations: (?<allocations>[0-9]+)\)$/
|
450
|
-
|
451
|
-
def match_and_process_rendered(line)
|
452
|
-
matchdata = RENDERED_REGEXP.match line
|
436
|
+
#, [2024-08-20T09:41:35.140725 #4151931] FATAL -- : [f57e3648-568a-48f9-ae3a-a522b1ff3298]
|
437
|
+
# [f57e3648-568a-48f9-ae3a-a522b1ff3298] NoMethodError (undefined method `available_quantity' for nil:NilClass
|
438
|
+
# [f57e3648-568a-48f9-ae3a-a522b1ff3298]
|
439
|
+
# [f57e3648-568a-48f9-ae3a-a522b1ff3298] app/models/donations/donation.rb:462:in `block in build_items_for_delivery'
|
440
|
+
# [f57e3648-568a-48f9-ae3a-a522b1ff3298] app/models/donations/donation.rb:440:in `build_items_for_delivery'
|
441
|
+
# [f57e3648-568a-48f9-ae3a-a522b1ff3298] app/controllers/donations_controller.rb:1395:in `create_delivery'
|
442
|
+
|
443
|
+
EXCEPTION = /[A-Za-z_0-9:]+(Error|NotFound|Invalid|Unknown|Missing|ENOSPC)/
|
444
|
+
FATAL_EXPLANATION_REGEXP = /^\[#{LOG_ID}\] (?<context>#{EXCEPTION})?(?<description>.*)/
|
445
|
+
def match_and_process_fatal_explanation(line)
|
446
|
+
matchdata = FATAL_EXPLANATION_REGEXP.match line
|
453
447
|
if matchdata
|
454
448
|
{
|
455
|
-
|
456
|
-
|
457
|
-
|
449
|
+
log_id: matchdata[:log_id],
|
450
|
+
context: matchdata[:context],
|
451
|
+
description: matchdata[:description].gsub(/^ *\(/, "").gsub(/\):$/, "")
|
452
|
+
}
|
453
|
+
end
|
454
|
+
end
|
455
|
+
|
456
|
+
# I, [2024-07-01T02:21:34.339058 #1392909] INFO -- : [815b3e28-8d6e-4741-8605-87654a9ff58c] BrowserInfo: "Unknown Browser","unknown_platform","Unknown","Devise::SessionsController","new","html","4db749654a0fcacbf3868f87723926e7405262f8d596e8514f4997dc80a3cd7e","2024-07-01T02:21:34+02:00"
|
457
|
+
BROWSER_INFO_REGEXP = /BrowserInfo: "(?<browser>.+)","(?<platform>.+)","(?<device_name>.+)","(?<controller>.+)","(?<method>.+)","(?<request_format>.+)","(?<anon_ip>.+)","(?<timestamp>.+)"/
|
458
|
+
|
459
|
+
def match_and_process_browser_info(line)
|
460
|
+
matchdata = BROWSER_INFO_REGEXP.match line
|
461
|
+
if matchdata
|
462
|
+
{
|
463
|
+
browser: matchdata[:browser],
|
464
|
+
platform: matchdata[:platform],
|
465
|
+
device_name: matchdata[:device_name],
|
466
|
+
controller: matchdata[:controller],
|
467
|
+
method: matchdata[:method],
|
468
|
+
request_format: matchdata[:request_format],
|
469
|
+
anon_ip: matchdata[:anon_ip],
|
470
|
+
timestamp: matchdata[:timestamp],
|
458
471
|
}
|
459
472
|
end
|
460
473
|
end
|
461
474
|
|
475
|
+
# Sequence:
|
476
|
+
#
|
477
|
+
# - enqueued
|
478
|
+
# - running
|
479
|
+
# - performing
|
480
|
+
# - (rendering)
|
481
|
+
# - performed
|
482
|
+
# - completed
|
483
|
+
#
|
484
|
+
# I, [2024-08-01T06:21:16.302152 #3569287] INFO -- : [96d14192-c7cc-48a9-9df7-3786de20b085] [ActiveJob] Enqueued ActionMailer::Parameterized::DeliveryJob (Job ID: 01e82c5c-fb42-4e5f-b0a7-6fa9512a9fb5) to DelayedJob(mailers) with arguments: "MessageMailer", "build_message", "deliver_now", {:project_id=>1, :email_to=>"activpentrutine@gmail.com", :hash=>{:event_name=>"download", :subject=>"Aviz BRAC-MEGA240176", :download=>#<GlobalID:0x00007f02d8e1ad98 @uri=#<URI::GID gid://btf3/Download/10652>>, :group=>#<GlobalID:0x00007f02d8e1a820 @uri=#<URI::GID gid://btf3/Organization/10061>>}, :locale=>:ro}
|
485
|
+
#
|
486
|
+
# I, [2024-08-01T06:21:21.235006 #3563911] INFO -- : 2024-08-01T06:21:21+0200: [Worker(delayed_job host:shair1 pid:3563911)] Job ActionMailer::Parameterized::DeliveryJob [01e82c5c-fb42-4e5f-b0a7-6fa9512a9fb5] from DelayedJob(mailers) with arguments: ["MessageMailer", "build_message", "deliver_now", {"project_id"=>1, "email_to"=>"activpentrutine@gmail.com", "hash"=>{"event_name"=>"download", "subject"=>"Aviz BRAC-MEGA240176", "download"=>{"_aj_globalid"=>"gid://btf3/Download/10652"}, "group"=>{"_aj_globalid"=>"gid://btf3/Organization/10061"}, "_aj_symbol_keys"=>["event_name", "subject", "download", "group"]}, "locale"=>{"_aj_serialized"=>"ActiveJob::Serializers::SymbolSerializer", "value"=>"ro"}, "_aj_symbol_keys"=>["project_id", "email_to", "hash", "locale"]}] (id=212885) (queue=mailers) RUNNING
|
487
|
+
#
|
488
|
+
# I, [2024-08-01T06:21:21.251282 #3563911] INFO -- : [ActiveJob] [ActionMailer::Parameterized::DeliveryJob] [01e82c5c-fb42-4e5f-b0a7-6fa9512a9fb5] Performing ActionMailer::Parameterized::DeliveryJob (Job ID: 01e82c5c-fb42-4e5f-b0a7-6fa9512a9fb5) from DelayedJob(mailers) enqueued at 2024-08-01T04:21:16Z with arguments: "MessageMailer", "build_message", "deliver_now", {:project_id=>1, :email_to=>"activpentrutine@gmail.com", :hash=>{:event_name=>"download", :subject=>"Aviz BRAC-MEGA240176", :download=>#<GlobalID:0x00007fbb86760950 @uri=#<URI::GID gid://btf3/Download/10652>>, :group=>#<GlobalID:0x00007fbb86760220 @uri=#<URI::GID gid://btf3/Organization/10061>>}, :locale=>:ro}
|
489
|
+
#
|
490
|
+
# I, [2024-08-01T06:21:22.137863 #3563911] INFO -- : [ActiveJob] [ActionMailer::Parameterized::DeliveryJob] [01e82c5c-fb42-4e5f-b0a7-6fa9512a9fb5] Performed ActionMailer::Parameterized::DeliveryJob (Job ID: 01e82c5c-fb42-4e5f-b0a7-6fa9512a9fb5) from DelayedJob(mailers) in 886.42ms
|
491
|
+
#
|
492
|
+
# I, [2024-08-01T06:21:22.141853 #3563911] INFO -- : 2024-08-01T06:21:22+0200: [Worker(delayed_job host:shair1 pid:3563911)] Job ActionMailer::Parameterized::DeliveryJob [01e82c5c-fb42-4e5f-b0a7-6fa9512a9fb5] from DelayedJob(mailers) with arguments: ["MessageMailer", "build_message", "deliver_now", {"project_id"=>1, "email_to"=>"activpentrutine@gmail.com", "hash"=>{"event_name"=>"download", "subject"=>"Aviz BRAC-MEGA240176", "download"=>{"_aj_globalid"=>"gid://btf3/Download/10652"}, "group"=>{"_aj_globalid"=>"gid://btf3/Organization/10061"}, "_aj_symbol_keys"=>["event_name", "subject", "download", "group"]}, "locale"=>{"_aj_serialized"=>"ActiveJob::Serializers::SymbolSerializer", "value"=>"ro"}, "_aj_symbol_keys"=>["project_id", "email_to", "hash", "locale"]}] (id=212885) (queue=mailers) COMPLETED after 0.9067
|
493
|
+
|
494
|
+
# Sequence with errors:
|
495
|
+
# (two log entries per error)
|
496
|
+
#
|
497
|
+
# E, [2024-08-15T05:10:30.613623 #4150573] ERROR -- : [ActiveJob] [ActionMailer::Parameterized::DeliveryJob] [79ea42c0-d280-4cf9-b77e-65917d4bc9fc] Error performing ActionMailer::Parameterized::DeliveryJob (Job ID: 79ea42c0-d280-4cf9-b77e-65917d4bc9fc) from DelayedJob(mailers) in 462.62ms: Net::SMTPFatalError (553 Recipient domain not specified.
|
498
|
+
# E, [2024-08-15T05:10:30.614189 #4150573] ERROR -- : 2024-08-15T05:10:30+0200: [Worker(delayed_job host:shair1 pid:4150573)] Job ActionMailer::Parameterized::DeliveryJob [79ea42c0-d280-4cf9-b77e-65917d4bc9fc] from DelayedJob(mailers) with arguments: ["MessageMailer", "build_message", "deliver_now", {"project_id"=>1, "email_to"=>"-", "hash"=>{"event_name"=>"download", "subject"=>"Aviz BvREWE240258.2", "download"=>{"_aj_globalid"=>"gid://btf3/Download/10877"}, "group"=>{"_aj_globalid"=>"gid://btf3/Organization/10060"}, "_aj_symbol_keys"=>["event_name", "subject", "download", "group"]}, "locale"=>{"_aj_serialized"=>"ActiveJob::Serializers::SymbolSerializer", "value"=>"ro"}, "_aj_symbol_keys"=>["project_id", "email_to", "hash", "locale"]}] (id=213242) (queue=mailers) FAILED (22 prior attempts) with Net::SMTPFatalError: 553 Recipient domain not specified.
|
499
|
+
|
500
|
+
TIMESTAMP_WITH_TZONE = /(?<timestamp_tzone>[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\+[0-9]+)/
|
501
|
+
ID = /(?<id>[0-9]+)/
|
502
|
+
WORKER = /Worker\((?<worker>.*) host:(?<host>.+) pid:(?<pid>[0-9]+)\)/
|
503
|
+
METHOD = /(?<method>[A-Za-z0-9:#]+)/
|
504
|
+
TIMES = /(?<attempt>[0-9]+)/
|
505
|
+
ERROR_MSG = /(?<error_msg>.+)/
|
506
|
+
ARGUMENTS = /(?<arguments>.+)/
|
507
|
+
|
508
|
+
ERROR_MESSAGE = /E, \[#{TIMESTAMP} #[0-9]+\] ERROR -- : #{TIMESTAMP_WITH_TZONE}: \[#{WORKER}\] Job #{METHOD} \[#{LOG_ID}\] from .+ with arguments: \[#{ARGUMENTS}\] \(id=#{ID}\) \(queue=.*\) FAILED \(#{TIMES} prior attempts\) with #{ERROR_MSG}/
|
509
|
+
|
510
|
+
ERROR_MESSAGE_SHORT = /E, \[#{TIMESTAMP} #[0-9]+\] ERROR -- : #{TIMESTAMP_WITH_TZONE}: \[#{WORKER}\] Job #{METHOD} \(id=#{ID}\) FAILED \(#{TIMES} prior attempts\) with #{ERROR_MSG}/
|
511
|
+
|
512
|
+
def match_and_process_job_error(line)
|
513
|
+
[ERROR_MESSAGE, ERROR_MESSAGE_SHORT].map do |regexp|
|
514
|
+
matchdata = regexp.match line
|
515
|
+
if matchdata
|
516
|
+
return {
|
517
|
+
ended_at: matchdata[:timestamp],
|
518
|
+
duration_total_ms: 0,
|
519
|
+
worker: matchdata[:worker],
|
520
|
+
host: matchdata[:host],
|
521
|
+
pid: matchdata[:pid],
|
522
|
+
log_id: matchdata.named_captures["log_id"],
|
523
|
+
object_id: matchdata[:id],
|
524
|
+
method: matchdata[:method],
|
525
|
+
arguments: matchdata.named_captures["arguments"],
|
526
|
+
exit_status: 'E',
|
527
|
+
attempt: matchdata[:attempt],
|
528
|
+
error_msg: matchdata[:error_msg],
|
529
|
+
}
|
530
|
+
end
|
531
|
+
end
|
532
|
+
|
533
|
+
nil
|
534
|
+
end
|
535
|
+
|
462
536
|
# generate a unique visitor id from an event
|
463
537
|
def unique_visitor_id(event)
|
464
538
|
date = event[:started_at] || event[:ended_at] || "1970-01-01"
|
@@ -1,5 +1,7 @@
|
|
1
1
|
module LogSense
|
2
2
|
class RailsAggregator < Aggregator
|
3
|
+
WORDS_SEPARATOR = ' · '
|
4
|
+
|
3
5
|
def initialize(db, options = { limit: 900 })
|
4
6
|
@table = "Event"
|
5
7
|
@date_field = "started_at"
|
@@ -76,6 +78,7 @@ module LogSense
|
|
76
78
|
sum(iif(platform != 'ios' and platform != 'android' and platform != 'mac' and platform != 'windows' and platform != 'linux', 1, 0)) as Other,
|
77
79
|
count(distinct(id)) as Total
|
78
80
|
from BrowserInfo
|
81
|
+
where #{filter}
|
79
82
|
group by controller, method, request_format
|
80
83
|
)
|
81
84
|
|
@@ -86,6 +89,7 @@ module LogSense
|
|
86
89
|
SELECT browser as Browser,
|
87
90
|
count(distinct(id)) as Visits
|
88
91
|
from BrowserInfo
|
92
|
+
where #{filter}
|
89
93
|
group by browser
|
90
94
|
)
|
91
95
|
|
@@ -93,58 +97,78 @@ module LogSense
|
|
93
97
|
SELECT platform as Platform,
|
94
98
|
count(distinct(id)) as Visits
|
95
99
|
from BrowserInfo
|
100
|
+
where #{filter}
|
96
101
|
group by platform
|
97
102
|
)
|
98
103
|
|
99
|
-
@
|
104
|
+
@fatal_plot = @db.execute %(
|
105
|
+
SELECT strftime("%Y-%m-%d", started_at) as Day,
|
106
|
+
sum(distinct(event.id)) as Errors,
|
107
|
+
sum(iif(context LIKE '%ActionController::RoutingError%', 1, 0)) as RoutingErrors,
|
108
|
+
sum(iif(context NOT LIKE '%ActionController::RoutingError%', 1, 0)) as OtherErrors
|
109
|
+
FROM Event JOIN Error
|
110
|
+
ON event.log_id == error.log_id
|
111
|
+
WHERE #{filter} and exit_status == 'F'
|
112
|
+
GROUP BY strftime("%Y-%m-%d", started_at)
|
113
|
+
).gsub("\n", "") || [[]]
|
114
|
+
|
115
|
+
@fatal = @db.execute %(
|
100
116
|
SELECT strftime("%Y-%m-%d %H:%M", started_at),
|
101
117
|
ip,
|
102
118
|
url,
|
103
|
-
|
119
|
+
context,
|
120
|
+
description,
|
104
121
|
event.log_id
|
105
122
|
FROM Event JOIN Error
|
106
123
|
ON event.log_id == error.log_id
|
107
|
-
WHERE #{filter} and exit_status == 'F').gsub("\n", "") || [[]]
|
108
|
-
|
109
|
-
@fatal_plot = @db.execute %Q(
|
110
|
-
SELECT strftime("%Y-%m-%d", started_at) as Day,
|
111
|
-
count(distinct(event.id)) as Errors
|
112
|
-
FROM Event JOIN Error
|
113
|
-
ON event.log_id == error.log_id
|
114
124
|
WHERE #{filter} and exit_status == 'F'
|
115
|
-
|
116
|
-
|
117
|
-
@
|
118
|
-
SELECT strftime("%Y-%m-%d %H:%M", started_at), status, ip, url,
|
119
|
-
error.description,
|
120
|
-
event.log_id
|
121
|
-
FROM Event JOIN Error
|
122
|
-
ON event.log_id == error.log_id
|
123
|
-
WHERE #{filter} and substr(status, 1, 1) == '5').gsub("\n", "") || [[]]
|
124
|
-
|
125
|
-
@internal_server_error_plot = @db.execute %Q(
|
126
|
-
SELECT strftime('%Y-%m-%d', started_at) as Day,
|
127
|
-
count(distinct(event.id)) as Errors
|
128
|
-
FROM Event JOIN Error
|
129
|
-
ON event.log_id == error.log_id
|
130
|
-
WHERE #{filter} and substr(status, 1, 1) == '5'
|
131
|
-
GROUP BY strftime('%Y-%m-%d', started_at)).gsub("\n", "") || [[]]
|
132
|
-
|
133
|
-
@error = @db.execute %Q(
|
125
|
+
).gsub("\n", "") || [[]]
|
126
|
+
|
127
|
+
@fatal_grouped = @db.execute %(
|
134
128
|
SELECT filename,
|
135
|
-
log_id,
|
129
|
+
group_concat(log_id, '#{WORDS_SEPARATOR}'),
|
130
|
+
context,
|
131
|
+
description,
|
132
|
+
count(distinct(error.id))
|
136
133
|
FROM Error
|
137
|
-
|
138
|
-
|
139
|
-
GROUP BY description).gsub("\n", "") || [[]]
|
134
|
+
GROUP BY description
|
135
|
+
).gsub("\n", "") || [[]]
|
140
136
|
|
141
|
-
@
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
137
|
+
@job_error_plot = @db.execute %(
|
138
|
+
SELECT strftime("%Y-%m-%d", ended_at) as Day,
|
139
|
+
count(distinct(id)) as Errors
|
140
|
+
FROM Job
|
141
|
+
WHERE #{filter}
|
142
|
+
GROUP BY strftime("%Y-%m-%d", ended_at)
|
143
|
+
).gsub("\n", "") || [[]]
|
144
|
+
|
145
|
+
@job_error = @db.execute %(
|
146
|
+
SELECT strftime("%Y-%m-%d %H:%M", ended_at),
|
147
|
+
worker,
|
148
|
+
host,
|
149
|
+
pid,
|
150
|
+
object_id,
|
151
|
+
method,
|
152
|
+
arguments,
|
153
|
+
error_msg,
|
154
|
+
attempt
|
155
|
+
FROM Job
|
156
|
+
WHERE #{filter}
|
157
|
+
).gsub("\n", "") || [[]]
|
158
|
+
|
159
|
+
@job_error_grouped = @db.execute %(
|
160
|
+
SELECT worker,
|
161
|
+
host,
|
162
|
+
pid,
|
163
|
+
object_id,
|
164
|
+
method,
|
165
|
+
arguments,
|
166
|
+
error_msg,
|
167
|
+
max(attempt)
|
168
|
+
FROM Job
|
169
|
+
WHERE #{filter}
|
170
|
+
GROUP BY object_id
|
171
|
+
).gsub("\n", "") || [[]]
|
148
172
|
|
149
173
|
instance_vars_to_hash
|
150
174
|
end
|
@@ -48,27 +48,51 @@ module LogSense
|
|
48
48
|
column_alignment: %i[left right right right right],
|
49
49
|
rows: data[:performance],
|
50
50
|
col: "small-12 cell",
|
51
|
-
echarts_height: "
|
51
|
+
echarts_height: "800px",
|
52
52
|
echarts_spec: "{
|
53
53
|
xAxis: {
|
54
|
+
name: 'Hits',
|
55
|
+
type: 'value',
|
56
|
+
minInterval: 1,
|
57
|
+
axisLabel: {
|
58
|
+
formatter: function(val) {
|
59
|
+
return val.toFixed(0);
|
60
|
+
}
|
61
|
+
}
|
54
62
|
},
|
55
63
|
yAxis: {
|
64
|
+
name: 'Average Time',
|
65
|
+
type: 'value',
|
66
|
+
axisLabel: {
|
67
|
+
formatter: function(val) {
|
68
|
+
return val.toFixed(0) + ' ms';
|
69
|
+
}
|
70
|
+
}
|
56
71
|
},
|
57
72
|
tooltip: {
|
58
|
-
trigger: '
|
73
|
+
trigger: 'item',
|
74
|
+
formatter: function(params) {
|
75
|
+
var index = params.dataIndex
|
76
|
+
var row = SERIES_DATA[index]
|
77
|
+
var controller = row['Controller']
|
78
|
+
var hits = Number(params.value[0]).toFixed(0).toLocaleString('en')
|
79
|
+
var average = Number(params.value[1]).toFixed(0).toLocaleString('en') + ' ms'
|
80
|
+
return `<b>${controller}</b><br/>Hits: ${hits}<br>Average Time: ${average}`;
|
81
|
+
}
|
59
82
|
},
|
60
83
|
series: [
|
61
84
|
{
|
62
|
-
data: SERIES_DATA.map(row => [row['
|
85
|
+
data: SERIES_DATA.map(row => [row['Hits'], row['Avg']]),
|
63
86
|
type: 'scatter',
|
64
87
|
color: '#D30001',
|
65
88
|
label: {
|
66
89
|
show: true,
|
67
90
|
position: 'right',
|
68
|
-
formatter: function
|
91
|
+
formatter: function(params) {
|
69
92
|
var row = SERIES_DATA[params.dataIndex]
|
70
|
-
return row['Controller']
|
71
|
-
|
93
|
+
return row['Controller'];
|
94
|
+
// +
|
95
|
+
// ':\\n(' + row['Hits'] + ', ' + row['Avg'] + ')';
|
72
96
|
}
|
73
97
|
},
|
74
98
|
}
|
@@ -125,8 +149,9 @@ module LogSense
|
|
125
149
|
},
|
126
150
|
{
|
127
151
|
title: "Fatal Events",
|
128
|
-
header: %w[Date IP URL Description
|
129
|
-
column_alignment: %i[left left left left left],
|
152
|
+
header: %w[Date IP URL Context Description ID],
|
153
|
+
column_alignment: %i[left left left left left left],
|
154
|
+
column_width: ["10%", "10%", "20%", "10%", "30%", "20%"],
|
130
155
|
rows: data[:fatal],
|
131
156
|
col: "small-12 cell",
|
132
157
|
echarts_extra: "var fatal_plot=#{data[:fatal_plot].to_json}",
|
@@ -154,7 +179,18 @@ module LogSense
|
|
154
179
|
},
|
155
180
|
series: [
|
156
181
|
{
|
157
|
-
|
182
|
+
name: 'Routing Errors',
|
183
|
+
data: fatal_plot.filter(row => row[0] != '').map(row => row[2]),
|
184
|
+
type: 'bar',
|
185
|
+
color: '#DEDEDE',
|
186
|
+
label: {
|
187
|
+
show: true,
|
188
|
+
position: 'top'
|
189
|
+
},
|
190
|
+
},
|
191
|
+
{
|
192
|
+
name: 'Other Errors',
|
193
|
+
data: fatal_plot.filter(row => row[0] != '').map(row => row[3]),
|
158
194
|
type: 'bar',
|
159
195
|
color: '#D30001',
|
160
196
|
label: {
|
@@ -166,12 +202,21 @@ module LogSense
|
|
166
202
|
};"
|
167
203
|
},
|
168
204
|
{
|
169
|
-
title: "
|
170
|
-
header: %w[
|
171
|
-
column_alignment: %i[left left left left
|
172
|
-
|
205
|
+
title: "Fatal Events (grouped by type)",
|
206
|
+
header: %w[Log ID Context Description Count],
|
207
|
+
column_alignment: %i[left left left left right],
|
208
|
+
column_width: ["10%", "20%", "10%", "60%", "5%"],
|
209
|
+
rows: data[:fatal_grouped],
|
210
|
+
col: "small-12 cell"
|
211
|
+
},
|
212
|
+
{
|
213
|
+
title: "Job Error",
|
214
|
+
header: %w[Date Worker Host PID ID Error Method Arguments Attempt],
|
215
|
+
column_alignment: %i[left left left left left left left left right],
|
216
|
+
column_width: ["10%", "5%", "5%", "5%", "5%", "20%", "20%", "20%", "10%"],
|
217
|
+
rows: data[:job_error],
|
173
218
|
col: "small-12 cell",
|
174
|
-
echarts_extra: "var
|
219
|
+
echarts_extra: "var fatal_plot=#{data[:job_error_plot].to_json}",
|
175
220
|
echarts_spec: "{
|
176
221
|
toolbox: {
|
177
222
|
feature: {
|
@@ -183,7 +228,7 @@ module LogSense
|
|
183
228
|
},
|
184
229
|
xAxis: {
|
185
230
|
type: 'category',
|
186
|
-
data:
|
231
|
+
data: fatal_plot.filter(row => row[0] != '').map(row => row[0]),
|
187
232
|
showGrid: true,
|
188
233
|
axisLabel: {
|
189
234
|
rotate: 45 // Rotate the labels
|
@@ -196,29 +241,24 @@ module LogSense
|
|
196
241
|
},
|
197
242
|
series: [
|
198
243
|
{
|
199
|
-
|
244
|
+
name: 'Errors',
|
245
|
+
data: fatal_plot.filter(row => row[0] != '').map(row => row[1]),
|
200
246
|
type: 'bar',
|
201
247
|
color: '#D30001',
|
202
248
|
label: {
|
203
249
|
show: true,
|
204
250
|
position: 'top'
|
205
251
|
},
|
206
|
-
}
|
252
|
+
}
|
207
253
|
]
|
208
254
|
};"
|
209
255
|
},
|
210
256
|
{
|
211
|
-
title: "Errors",
|
212
|
-
header: %w[
|
213
|
-
column_alignment: %i[left left left right],
|
214
|
-
|
215
|
-
|
216
|
-
},
|
217
|
-
{
|
218
|
-
title: "Potential Attacks",
|
219
|
-
header: %w[Log ID Description Count],
|
220
|
-
column_alignment: %i[left left left right],
|
221
|
-
rows: data[:possible_attacks],
|
257
|
+
title: "Job Errors (grouped)",
|
258
|
+
header: %w[Worker Host PID ID Error Method Arguments Attempt],
|
259
|
+
column_alignment: %i[left left left left left left left right],
|
260
|
+
column_width: ["5%", "5%", "5%", "5%", "20%", "30%", "20%", "10%"],
|
261
|
+
rows: data[:job_error_grouped],
|
222
262
|
col: "small-12 cell"
|
223
263
|
},
|
224
264
|
browsers(data),
|
@@ -277,7 +277,7 @@ module LogSense
|
|
277
277
|
title: "Sessions",
|
278
278
|
report: :html,
|
279
279
|
header: ["IP", "Days", "Date", "Visits", "Distinct URL", "URL List"],
|
280
|
-
column_alignment: %i[left left right right right
|
280
|
+
column_alignment: %i[left left right right right left],
|
281
281
|
rows: data,
|
282
282
|
col: "small-12 cell"
|
283
283
|
}
|
@@ -288,6 +288,7 @@ module LogSense
|
|
288
288
|
title: "IP per hour",
|
289
289
|
header: ["IP"] + (0..23).map { |hour| hour.to_s },
|
290
290
|
column_alignment: %i[left] + (%i[right] * 24),
|
291
|
+
column_width: ["10%"] + (["3.75%"] * 24),
|
291
292
|
rows: data,
|
292
293
|
col: "small-12 cell"
|
293
294
|
}
|
@@ -419,7 +420,7 @@ module LogSense
|
|
419
420
|
max = country_and_hits.map { |x| x[1] }.max
|
420
421
|
|
421
422
|
country_and_hits.map do |element|
|
422
|
-
underscored = (element[0] || "").gsub(" ", "_")
|
423
|
+
underscored = (element[0] || "").gsub(" ", "_").gsub(/[()]/, "")
|
423
424
|
bin = bin(element[1], max:)
|
424
425
|
<<-EOS
|
425
426
|
/* bin: #{bin} */
|
@@ -16,15 +16,20 @@
|
|
16
16
|
pageLength: 25,
|
17
17
|
<%= report[:datatable_options] + "," if report[:datatable_options] %>
|
18
18
|
columns: [
|
19
|
-
<% report[:header].
|
20
|
-
<% if header == "Size" -%>
|
19
|
+
<% report[:header].each_with_index do |header, index| %>
|
21
20
|
{
|
22
|
-
data: '
|
23
|
-
className: '<%= Emitter::slugify(
|
21
|
+
data: '<%= header %>',
|
22
|
+
className: '<%= Emitter::slugify(header) %> <%= Emitter.alignment_class(report, index) %>',
|
23
|
+
<% if report[:column_width] %>
|
24
|
+
width: '<%= report[:column_width][index] %>',
|
25
|
+
<% end %>
|
26
|
+
|
27
|
+
<%# USE A SPECIFIC RENDERER FOR SOME TYPES OF COLUMNS %>
|
28
|
+
<% if header == "Size" -%>
|
24
29
|
render: function(data, type, row) {
|
25
30
|
// If display or filter data is requested, format the date
|
26
31
|
if ( type === 'display' || type === 'filter' ) {
|
27
|
-
|
32
|
+
return data;
|
28
33
|
}
|
29
34
|
// Otherwise the data type requested (`type`) is type detection or
|
30
35
|
// sorting data, for which we want to use an integer value
|
@@ -48,11 +53,7 @@
|
|
48
53
|
}
|
49
54
|
return size * multiplier;
|
50
55
|
}
|
51
|
-
|
52
|
-
<% elsif header == "IP" -%>
|
53
|
-
{
|
54
|
-
data: 'IP',
|
55
|
-
className: '<%= Emitter::slugify("IP") %>',
|
56
|
+
<% elsif header == "IP" -%>
|
56
57
|
render: function(data, type, row) {
|
57
58
|
// If display or filter data is requested, format the data
|
58
59
|
if ( type === 'display' || type === 'filter' ) {
|
@@ -61,15 +62,11 @@
|
|
61
62
|
// For any other purpose return data
|
62
63
|
return data;
|
63
64
|
}
|
64
|
-
|
65
|
-
<% else -%>
|
66
|
-
{
|
67
|
-
data: '<%= header %>',
|
68
|
-
className: '<%= Emitter::slugify(header) %>',
|
65
|
+
<% else -%>
|
69
66
|
render: DataTable.render.text()
|
67
|
+
<% end -%>
|
70
68
|
},
|
71
69
|
<% end -%>
|
72
|
-
<% end -%>
|
73
70
|
]
|
74
71
|
});
|
75
72
|
});
|
@@ -85,6 +85,12 @@ h2 {
|
|
85
85
|
background: var(--background-color);
|
86
86
|
}
|
87
87
|
|
88
|
+
table {
|
89
|
+
width: 100% !important;
|
90
|
+
/* table-layout: fixed !important;*/
|
91
|
+
word-wrap: break-word;
|
92
|
+
}
|
93
|
+
|
88
94
|
table thead, table tbody, table tfoot {
|
89
95
|
border-color: var(--row-border) !important;
|
90
96
|
}
|
@@ -174,7 +180,16 @@ ul.pagination, li.paginate_button {
|
|
174
180
|
font-weight: normal;
|
175
181
|
}
|
176
182
|
|
177
|
-
|
183
|
+
/* datatable resists aligment directives. We make them important */
|
184
|
+
.text-left {
|
185
|
+
text-align: left !important;
|
186
|
+
}
|
187
|
+
|
188
|
+
.text-center {
|
189
|
+
text-align: center !important;
|
190
|
+
}
|
191
|
+
|
192
|
+
.text-right {
|
178
193
|
text-align: right !important;
|
179
194
|
}
|
180
195
|
|
@@ -182,3 +197,7 @@ td {
|
|
182
197
|
vertical-align: top;
|
183
198
|
}
|
184
199
|
|
200
|
+
.url {
|
201
|
+
overflow: hidden;
|
202
|
+
text-overflow: ellipsis;
|
203
|
+
}
|
@@ -61,6 +61,7 @@
|
|
61
61
|
<% if report[:rows] %>
|
62
62
|
<%= render "report_data.html.erb", report: report, index: index %>
|
63
63
|
<% end %>
|
64
|
+
|
64
65
|
<% if report[:vega_spec] %>
|
65
66
|
<div id="<%= "plot-#{index}" %>" class="plot-canvas">
|
66
67
|
</div>
|
@@ -78,6 +79,7 @@
|
|
78
79
|
vegaEmbed('#<%= "plot-#{index}"%>', plot_spec_<%= index %>);
|
79
80
|
</script>
|
80
81
|
<% end %>
|
82
|
+
|
81
83
|
<% if report[:echarts_spec] %>
|
82
84
|
<% height = report[:echarts_height] || "400px"%>
|
83
85
|
<div id="<%= "echart-#{index}" %>" style="width: 100%;height: <%= height %>;"></div>
|
@@ -91,12 +93,14 @@
|
|
91
93
|
|
92
94
|
</script>
|
93
95
|
<% end %>
|
96
|
+
|
94
97
|
<% if report[:raw_html] %>
|
95
98
|
<% height = report[:raw_html_height] || "400px"%>
|
96
99
|
<div id="raw-html-#{index}" style="width: 100%;height: <%= height %>">
|
97
100
|
<%= report[:raw_html] %>
|
98
101
|
</div>
|
99
102
|
<% end %>
|
103
|
+
|
100
104
|
<% if report[:rows] %>
|
101
105
|
<%= render "output_table.html.erb", report: report, index: index %>
|
102
106
|
<% end %>
|
@@ -312,7 +312,7 @@ THE SOFTWARE.
|
|
312
312
|
</path>
|
313
313
|
<path d="M974.8 276l1.9 4.1 0.3 3.9 1.9 6.8 1.4 1.4-1 2.5-7.1 1.1-2.5 2.3-3.1 0.6-0.3 4.8-6.4 2.5-2.1 3.2-4.5 1.7-5.4 1-8.9 4.8-0.1 7.6-0.9 0 0.1 3.4-3.4 0.2-1.8 1.5-2.5 0-2-0.9-4.6 0.7-1.9 5-1.8 0.5-2.7 8.1-7.9 6.9-2 8.9-2.4 2.9-0.7 2.3-12.5 0.5-0.1 0 0.3-3 2.2-1.7 1.9-3.4-0.3-2.2 2-4.5 3.2-4.1 1.9-1 1.6-3.7 0.2-3.5 2.1-3.9 3.8-2.4 3.6-6.5 0.1-0.1 2.9-2.5 5.1-0.7 4.4-4.4 2.8-1.7 4.7-5.4-1.2-7.9 2.2-5.6 0.9-3.4 3.6-4.3 5.4-2.9 4.1-2.7 3.7-6.6 1.8-4 3.9 0.1 3.1 2.7 5.1-0.4 5.5 1.4 2.4 0z" id="MA" name="Morocco">
|
314
314
|
</path>
|
315
|
-
<path d="M1129.4 210.3l-1.3-2.9 0.2-2.7-0.6-2.7-3.4-3.8-2-2.6-1.8-1.8-1.6-0.7 1.1-0.9 3.2-0.6 4 1.9 2 0.3 2.6 1.7-0.1 2.1 2 1 1.1 2.6 2 1.6-0.2 1 1 0.6-1.3 0.5-3-0.2-0.6-0.9-1 0.5 0.6 1.1-1.1 2.1-0.6 2.1-1.2 0.7z" id="MD" name="
|
315
|
+
<path d="M1129.4 210.3l-1.3-2.9 0.2-2.7-0.6-2.7-3.4-3.8-2-2.6-1.8-1.8-1.6-0.7 1.1-0.9 3.2-0.6 4 1.9 2 0.3 2.6 1.7-0.1 2.1 2 1 1.1 2.6 2 1.6-0.2 1 1 0.6-1.3 0.5-3-0.2-0.6-0.9-1 0.5 0.6 1.1-1.1 2.1-0.6 2.1-1.2 0.7z" id="MD" name="Moldova_Republic_of">
|
316
316
|
</path>
|
317
317
|
<path d="M1267.9 588.9l0.4 7.7 1.3 3-0.7 3.1-1.2 1.8-1.6-3.7-1.2 1.9 0.8 4.7-0.7 2.8-1.7 1.4-0.7 5.5-2.7 7.5-3.4 8.8-4.3 12.2-2.9 8.9-3.1 7.5-4.6 1.5-5.1 2.7-3-1.6-4.2-2.3-1.2-3.4 0-5.7-1.5-5.1-0.2-4.7 1.3-4.6 2.6-1.1 0.2-2.1 2.9-4.9 0.8-4.1-1.1-3-0.8-4.1-0.1-5.9 2.2-3.6 1-4.1 2.8-0.2 3.2-1.3 2.2-1.2 2.4-0.1 3.4-3.6 4.9-4 1.8-3.2-0.6-2.8 2.4 0.8 3.3-4.4 0.3-3.9 2-2.9 1.8 2.8 1.4 2.7 1.2 4.3z" id="MG" name="Madagascar">
|
318
318
|
</path>
|
data/lib/log_sense/version.rb
CHANGED
data/todo.org
CHANGED
@@ -2,6 +2,13 @@
|
|
2
2
|
|
3
3
|
* Todo (2024-08-01)
|
4
4
|
|
5
|
+
** T Distinguish between exceptions, errors with no timestamps and errors with timestamps :feature:
|
6
|
+
- In rails reports some errors don't have a timestamp, whereas other have.
|
7
|
+
|
8
|
+
1. Check whether we can always get an ID of an event
|
9
|
+
2.
|
10
|
+
|
11
|
+
|
5
12
|
** T Move geolocation to aggregator, so that we can perform queries more efficiently :refactoring:
|
6
13
|
** T Filter on dates :feature:
|
7
14
|
** T Visits/Hits :feature:
|
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: 2.
|
4
|
+
version: 2.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Adolfo Villafiorita
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-08-
|
11
|
+
date: 2024-08-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: browser
|