log_sense 2.0.0 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|