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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 288570381159c730801985845d064e62fa8ad08ee6b44f48ebf2928e60e80e47
4
- data.tar.gz: 64d80612d568f4fd1991257d2755ec38ef94543b1fd451097efd6e9006ab551f
3
+ metadata.gz: e17413654dc552a716548d7a55e127c5f0703cf29643d97d500e0cf51b20eba3
4
+ data.tar.gz: 56765b54fdda4f64add3fbb97ea3b4073976d25f4cb67cc932a7d350d85793cb
5
5
  SHA512:
6
- metadata.gz: 6352f42ccbd453e9adf4af83372ad8c7113f58d419e502e444dad2b3f3ad5e54b1f31f077631040ea473a9b2976e38ff2b8960cb91091f65830ed03b987cb3e8
7
- data.tar.gz: 4f01dc9e7ea6a53d983d51508e69710999741f6e5c74b49c2ab42e45a312f3364a07f7ff58fe6dd8e852f08d67d1df110a86c7e0330c6301e522bf8c595839f3
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.1
6
-
7
- - Add GitHub action for publishing to RubyGems after repeated failures
8
- with authentication from the command line
9
-
10
- * 2.0.0 (Not released)
11
-
12
- - World Map
13
- - Dark mode
14
- - Fix link colors in sidebar
15
- - Bars in the statuses bar plot are now colored according to status
16
- - Add "statuses by day" in Rails report
17
- - Enlarge "errors" and "potential attacks" reports
18
- - Various smaller fixes
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
- - Internal Server Errors
51
- - Errors
52
- - Potential Attacks
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 #{result} and #{type} and #{filter}
115
+ where #{filter} and #{result} and #{type}
116
116
  group by path
117
117
  order by count(path) desc
118
118
  limit #{@options[:limit]}"
@@ -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
- # These are for development logs
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
- event = data.merge(pending[id] || {})
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 = self.match_and_process_completed line
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
- event = data.merge (pending[id] || {})
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
- ID = /(?<id>[a-z0-9-]+)/
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 -- : \[#{ID}\] Started #{VERB} "#{URL}" for #{IP} at/
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[:id],
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 -- : \[#{ID}\] Completed #{STATUS} #{STATUS_IN_WORDS} in (?<total>#{MSECS})ms \((Views: (?<views>#{MSECS})ms \| )?ActiveRecord: (?<arec>#{MSECS})ms( \| Allocations: (?<alloc>[0-9]+))?\)/
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[:id],
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 -- : \[#{ID}\] Processing by (?<controller>[^ ]+) as/
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[:id],
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
- FATAL_REGEXP = /F, \[#{TIMESTAMP} #[0-9]+\] FATAL -- : \[#{ID}\] (?<comment>.*)$/
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[:id],
400
- comment: matchdata[:comment]
420
+ log_id: matchdata[:log_id],
401
421
  }
402
422
  end
403
423
  end
404
424
 
405
- # Started GET "/projects?locale=it" for 127.0.0.1 at 2024-06-06 23:23:31 +0200
406
- # Processing by EmployeesController#index as HTML
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
- # Started POST "/projects?locale=it" for 127.0.0.1 at 2024-06-06 23:34:33 +0200
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
- # Completed in 48801ms (ActiveRecord: 17.8ms | Allocations: 2274498)
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
- # Completed 500 Internal Server Error in 24ms (ActiveRecord: 1.4ms | Allocations: 4660)
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
- # app/views/layouts/application.html.haml:12
439
- # app/controllers/application_controller.rb:26:in `switch_locale'
440
-
441
- # Rendered devise/sessions/_project_partial.html.erb (Duration: 78.4ms | Allocations: 88373)
442
- # Rendered devise/sessions/new.html.haml within layouts/application (Duration: 100.0ms | Allocations: 104118)
443
- # Rendered application/_favicon.html.erb (Duration: 2.6ms | Allocations: 4454)
444
- # Rendered layouts/_manage_notice.html.erb (Duration: 0.3ms | Allocations: 193)
445
- # Rendered layout layouts/application.html.erb (Duration: 263.4ms | Allocations: 367467)
446
- # Rendered donations/_switcher.html.haml (Duration: 41.1ms | Allocations: 9550)
447
- # Rendered donations/_status_header.html.haml (Duration: 1.4ms | Allocations: 3192)
448
- # Rendered donations/_status_header.html.haml (Duration: 0.0ms | Allocations: 7)
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
- partial: matchdata[:partial],
456
- duration: matchdata[:duration],
457
- allocations: matchdata[:allocations]
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
- @fatal = @db.execute %Q(
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
- error.description,
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
- GROUP BY strftime("%Y-%m-%d", started_at)).gsub("\n", "") || [[]]
116
-
117
- @internal_server_error = @db.execute %Q(
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, description, count(log_id)
129
+ group_concat(log_id, '#{WORDS_SEPARATOR}'),
130
+ context,
131
+ description,
132
+ count(distinct(error.id))
136
133
  FROM Error
137
- WHERE (description NOT LIKE '%No route matches%' and
138
- description NOT LIKE '%Couldn''t find%')
139
- GROUP BY description).gsub("\n", "") || [[]]
134
+ GROUP BY description
135
+ ).gsub("\n", "") || [[]]
140
136
 
141
- @possible_attacks = @db.execute %Q(
142
- SELECT filename,
143
- log_id, description, count(log_id)
144
- FROM Error
145
- WHERE (description LIKE '%No route matches%' or
146
- description LIKE '%Couldn''t find%')
147
- GROUP BY description).gsub("\n", "") || [[]]
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: "600px",
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: 'axis'
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['Avg'], row['Hits']]),
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 (params) {
91
+ formatter: function(params) {
69
92
  var row = SERIES_DATA[params.dataIndex]
70
- return row['Controller'] +
71
- ': (' + row['Avg'] + ', ' + row['Hits'] + ')';
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 Log ID],
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
- data: fatal_plot.filter(row => row[0] != '').map(row => row[1]),
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: "Internal Server Errors",
170
- header: %w[Date Status IP URL Description Log ID],
171
- column_alignment: %i[left left left left left left],
172
- rows: data[:internal_server_error],
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 internal_server_error_plot=#{data[:internal_server_error_plot].to_json}",
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: internal_server_error_plot.filter(row => row[0] != '').map(row => row[0]),
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
- data: internal_server_error_plot.filter(row => row[0] != '').map(row => row[1]),
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[Log ID Description Count],
213
- column_alignment: %i[left left left right],
214
- rows: data[:error],
215
- col: "small-12 cell"
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 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].each do |header| %>
20
- <% if header == "Size" -%>
19
+ <% report[:header].each_with_index do |header, index| %>
21
20
  {
22
- data: 'Size',
23
- className: '<%= Emitter::slugify("Size") %>',
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
- return data;
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
- .hits, .visits, .size, .count, .s2xx, .s3xx, .so4xx, .total-hits, .total-visits {
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="Moldova">
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>
@@ -1,3 +1,3 @@
1
1
  module LogSense
2
- VERSION = "2.0.0"
2
+ VERSION = "2.1.0"
3
3
  end
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.0.0
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-19 00:00:00.000000000 Z
11
+ date: 2024-08-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: browser