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