log_sense 2.1.0 → 2.2.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: e17413654dc552a716548d7a55e127c5f0703cf29643d97d500e0cf51b20eba3
4
- data.tar.gz: 56765b54fdda4f64add3fbb97ea3b4073976d25f4cb67cc932a7d350d85793cb
3
+ metadata.gz: 8b24aa444f64beeb8b13bda22aae730130cff025b4d122557499d3afd2ec2e73
4
+ data.tar.gz: 809c695be701f9a6f17a4b31d4c9c61827a97a9088cb180b6f7f60a794e498f3
5
5
  SHA512:
6
- metadata.gz: dc77b1c63c08a9a190aae0ab0ab44bdffe37e0dc2e999d3dc8a236703dc8e430fb32ff09d884d5d9e90ff7870b7bb212d0cc486e877982ca3c5328a3aeda5943
7
- data.tar.gz: e73ad267ad54dc4e3c09481ec4f055e6d9e1e9346262b04975b754bd8822b19ce27298299ce6fb7cc3511e5248b6303392c984b48d68d40bd398bd14d7be9d80
6
+ metadata.gz: afff457f8f952f7f6b806baaf56ac34010027498be04fdbfaa91653ab70699f3d3d85222b47437c2df151f00fb953e23d21a5ac537562cf14a157459762e4855
7
+ data.tar.gz: 911594383266801861454d6facb9ee4c2db852979d2349250d988934721925ed6592b9fdbb2c63d43814305ca062478d8984bd07166ff90c3928120dc17c3129
data/CHANGELOG.org CHANGED
@@ -2,6 +2,11 @@
2
2
  #+AUTHOR: Adolfo Villafiorita
3
3
  #+STARTUP: showall
4
4
 
5
+ * 2.2.0
6
+
7
+ - [User] Better management of Delayed Job (show also completed)
8
+ - [Refactoring] Regexps are now pre-compiled with /o
9
+
5
10
  * 2.1.0
6
11
 
7
12
  - [User] Delayed Job Errors
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- log_sense (2.0.0)
4
+ log_sense (2.2.0)
5
5
  browser (~> 5.3.0)
6
6
  ipaddr (~> 1.2.0)
7
7
  iso_country_codes (~> 0.7.0)
data/exe/log_sense CHANGED
@@ -168,4 +168,6 @@ else
168
168
 
169
169
  warn "Emitting..." if @options[:verbose]
170
170
  puts LogSense::Emitter.emit @reports, @data, @options
171
+
172
+ exit 0
171
173
  end
@@ -165,6 +165,9 @@ module LogSense
165
165
  # hash
166
166
  pending = {}
167
167
 
168
+ # for delayed jobs
169
+ pending_jobs = {}
170
+
168
171
  # Fatal explanation messages span several lines (2, 4, ?)
169
172
  #
170
173
  # We keep a Hash with the FATAL explanation messages and we persist when
@@ -304,13 +307,75 @@ module LogSense
304
307
  next
305
308
  end
306
309
 
310
+ #
311
+ # Match enqueuing job
312
+ #
313
+ data = match_and_process_enqueuing_job line
314
+ if data
315
+ id = data[:job_id]
316
+ pending_jobs[id] = data.merge(pending_jobs[id] || {})
317
+ next
318
+ end
319
+
320
+ #
321
+ # Match running
322
+ #
323
+ data = match_and_process_running_job line
324
+ if data
325
+ id = data[:job_id]
326
+ # change the key to pid
327
+ pid = data[:object_id]
328
+ pending_jobs[pid] = data.merge(pending_jobs[id] || {})
329
+
330
+ pending_jobs.delete(id)
331
+ next
332
+ end
333
+
334
+ #
335
+ # Match completed
336
+ #
337
+ data = match_and_process_completed_job line
338
+ if data
339
+ id = data[:object_id]
340
+ # it has to be there!
341
+ if pending_jobs[id]
342
+ data = (pending_jobs[id] || {}).merge(data)
343
+ end
344
+
345
+ ins_job.execute(
346
+ data[:started_at],
347
+ data[:ended_at],
348
+ data[:duration_total_ms],
349
+ data[:worker],
350
+ data[:host],
351
+ data[:pid],
352
+ data[:log_id],
353
+ data[:job_id], # no longer necessary
354
+ data[:object_id], # completed jobs are destroyed
355
+ data[:method],
356
+ data[:arguments],
357
+ data[:exit_status],
358
+ data[:attempt],
359
+ data[:error_msg],
360
+ filename,
361
+ line_number
362
+ )
363
+ pending_jobs.delete(id)
364
+ next
365
+ end
366
+
307
367
  #
308
368
  # Match job errors
309
369
  #
310
370
  data = match_and_process_job_error line
311
371
  if data
372
+ # it has to be there!
373
+ if pending_jobs[id]
374
+ data = (pending_jobs[id] || {}).merge(data)
375
+ end
376
+
312
377
  ins_job.execute(
313
- data[:ended_at], # this is temporary (while we wait to parse BEGIN) + required by filter
378
+ data[:started_at],
314
379
  data[:ended_at],
315
380
  0,
316
381
  data[:worker],
@@ -327,6 +392,8 @@ module LogSense
327
392
  filename,
328
393
  line_number
329
394
  )
395
+
396
+ pending_jobs.delete(id)
330
397
  end
331
398
  end
332
399
  end
@@ -337,23 +404,37 @@ module LogSense
337
404
  ins_error.execute(value)
338
405
  end
339
406
 
407
+ # DO NOT persist the pending_jobs which have not yet completed (those
408
+ # still available at: pending_jobs).
409
+ #
410
+ # In fact various entries initiated with RUNNING end up with "performed"
411
+ # (rather than COMPLETED). Notice that entries COMPLETED always have a
412
+ # peformed entry as well.
413
+ #
414
+ # Since we do not yet process "performed" entry log and in pending jobs
415
+ # we end up accumulating a bunch of entries which are marked as
416
+ # "performed"
417
+ #
418
+ # Performed entries are tricky since they use JOB_ID, rather than the
419
+ # object_id and probably requires to change how we enter pending_ids
420
+
340
421
  db
341
422
  end
342
423
 
343
424
  # could be private here, I guess we keep them public to make them simpler
344
425
  # to try from irb
345
426
 
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-]+)/
348
- VERB = /(?<verb>GET|POST|PATCH|PUT|DELETE)/
349
- URL = /(?<url>[^"]+)/
350
- IP = /(?<ip>[0-9.]+)/
351
- STATUS = /(?<status>[0-9]+)/
352
- STATUS_IN_WORDS = /(OK|Unauthorized|Found|Internal Server Error|Bad Request|Method Not Allowed|Request Timeout|Not Implemented|Bad Gateway|Service Unavailable)/
353
- MSECS = /[0-9.]+/
427
+ TIMESTAMP = '(?<timestamp>[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\\.[0-9]+)'
428
+ LOG_ID = '(?<log_id>[a-z0-9-]+)'
429
+ VERB = '(?<verb>GET|POST|PATCH|PUT|DELETE)'
430
+ URL = '(?<url>[^"]+)'
431
+ IP = '(?<ip>[0-9.]+)'
432
+ STATUS = '(?<status>[0-9]+)'
433
+ STATUS_IN_WORDS = '(OK|Unauthorized|Found|Internal Server Error|Bad Request|Method Not Allowed|Request Timeout|Not Implemented|Bad Gateway|Service Unavailable)'
434
+ MSECS = '[0-9.]+'
354
435
 
355
436
  # 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
356
- STARTED_REGEXP = /I, \[#{TIMESTAMP} #[0-9]+\] INFO -- : \[#{LOG_ID}\] Started #{VERB} "#{URL}" for #{IP} at/
437
+ STARTED_REGEXP = /I, \[#{TIMESTAMP} #[0-9]+\] INFO -- : \[#{LOG_ID}\] Started #{VERB} "#{URL}" for #{IP} at/o
357
438
 
358
439
  def match_and_process_start(line)
359
440
  matchdata = STARTED_REGEXP.match line
@@ -372,7 +453,7 @@ module LogSense
372
453
  # 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)
373
454
  # 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)
374
455
  # 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)
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]+))?\)/
456
+ 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]+))?\)/o
376
457
 
377
458
  def match_and_process_completed(line)
378
459
  matchdata = (COMPLETED_REGEXP.match line)
@@ -393,7 +474,7 @@ module LogSense
393
474
  end
394
475
 
395
476
  # I, [2021-10-19T08:16:34.345162 #10477] INFO -- : [67103c0d-455d-4fe8-951e-87e97628cb66] Processing by PeopleController#show as HTML
396
- PROCESSING_REGEXP = /I, \[#{TIMESTAMP} #[0-9]+\] INFO -- : \[#{LOG_ID}\] Processing by (?<controller>[^ ]+) as/
477
+ PROCESSING_REGEXP = /I, \[#{TIMESTAMP} #[0-9]+\] INFO -- : \[#{LOG_ID}\] Processing by (?<controller>[^ ]+) as/o
397
478
 
398
479
  def match_and_process_processing_by line
399
480
  matchdata = PROCESSING_REGEXP.match line
@@ -410,7 +491,7 @@ module LogSense
410
491
  # F, [2021-12-04T00:34:05.839209 #2735058] FATAL -- : [3a16162e-a6a5-435e-a9d8-c4df5dc0f728]
411
492
  # 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'
412
493
 
413
- FATAL_REGEXP = /F, \[#{TIMESTAMP} #[0-9]+\] FATAL -- : \[#{LOG_ID}\]/
494
+ FATAL_REGEXP = /F, \[#{TIMESTAMP} #[0-9]+\] FATAL -- : \[#{LOG_ID}\]/o
414
495
 
415
496
  def match_and_process_fatal(line)
416
497
  matchdata = FATAL_REGEXP.match line
@@ -440,8 +521,8 @@ module LogSense
440
521
  # [f57e3648-568a-48f9-ae3a-a522b1ff3298] app/models/donations/donation.rb:440:in `build_items_for_delivery'
441
522
  # [f57e3648-568a-48f9-ae3a-a522b1ff3298] app/controllers/donations_controller.rb:1395:in `create_delivery'
442
523
 
443
- EXCEPTION = /[A-Za-z_0-9:]+(Error|NotFound|Invalid|Unknown|Missing|ENOSPC)/
444
- FATAL_EXPLANATION_REGEXP = /^\[#{LOG_ID}\] (?<context>#{EXCEPTION})?(?<description>.*)/
524
+ EXCEPTION = "[A-Za-z_0-9:]+(Error|NotFound|Invalid|Unknown|Missing|ENOSPC)"
525
+ FATAL_EXPLANATION_REGEXP = /^\[#{LOG_ID}\] (?<context>#{EXCEPTION})?(?<description>.*)/o
445
526
  def match_and_process_fatal_explanation(line)
446
527
  matchdata = FATAL_EXPLANATION_REGEXP.match line
447
528
  if matchdata
@@ -454,7 +535,7 @@ module LogSense
454
535
  end
455
536
 
456
537
  # 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>.+)"/
538
+ BROWSER_INFO_REGEXP = /BrowserInfo: "(?<browser>.+)","(?<platform>.+)","(?<device_name>.+)","(?<controller>.+)","(?<method>.+)","(?<request_format>.+)","(?<anon_ip>.+)","(?<timestamp>.+)"/o
458
539
 
459
540
  def match_and_process_browser_info(line)
460
541
  matchdata = BROWSER_INFO_REGEXP.match line
@@ -474,12 +555,14 @@ module LogSense
474
555
 
475
556
  # Sequence:
476
557
  #
477
- # - enqueued
478
- # - running
479
- # - performing
558
+ # - enqueued (LOG_ID user event, JOB_ID assigned by system)
559
+ # - running (JOB_ID links to enqueued; PID assigned by system; OBJECT_ID assigned by the system)
560
+ # - performing (OBJECT_ID links to running; OBJECT_ID assigned by the system; JOB_ID is new)
480
561
  # - (rendering)
481
- # - performed
482
- # - completed
562
+ # - performed (OBJECT_ID links to running; JOB_ID links to previous)
563
+ # - completed (OBJECT_ID links to running; JOB_ID links to previous)
564
+ #
565
+ # SOMETIMES PERFORMED APPEARS WITH NO COMPLETED.
483
566
  #
484
567
  # 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
568
  #
@@ -489,37 +572,99 @@ module LogSense
489
572
  #
490
573
  # 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
574
  #
575
+ #
576
+ # I, [2024-08-01T06:38:41.005687 #3563911] INFO -- : 2024-08-01T06:38:41+0200: [Worker(delayed_job host:shair1 pid:3563911)] 1 jobs processed at 1.4476 j/s, 0 failed
577
+ #
578
+ #
492
579
  # 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
580
 
494
581
  # Sequence with errors:
495
582
  # (two log entries per error)
496
583
  #
497
584
  # 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.
585
+
498
586
  # 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
587
 
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>.+)/
588
+ 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]+)'
589
+ ID = '(?<id>[0-9]+)'
590
+ JOB_ID = '(?<job_id>[a-zA-Z0-9-]+)'
591
+ WORKER = 'Worker\\((?<worker>.+) host:(?<host>.+) pid:(?<pid>[0-9]+)\\)'
592
+ METHOD = '(?<method>[A-Za-z0-9:#_]+)'
593
+ TIMES = '(?<attempt>[0-9]+)'
594
+ ERROR_MSG = '(?<error_msg>.+)'
595
+ ARGUMENTS = '(?<arguments>.+)'
507
596
 
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}/
597
+ #
598
+ # these are together, since they return temporary data
599
+ #
600
+ ENQUEUEING = /I, \[#{TIMESTAMP} #[0-9]+\] INFO -- : \[#{LOG_ID}\] \[ActiveJob\] Enqueued #{METHOD} \(Job ID: #{JOB_ID}\) to .* with arguments: #{ARGUMENTS}/o
509
601
 
510
- ERROR_MESSAGE_SHORT = /E, \[#{TIMESTAMP} #[0-9]+\] ERROR -- : #{TIMESTAMP_WITH_TZONE}: \[#{WORKER}\] Job #{METHOD} \(id=#{ID}\) FAILED \(#{TIMES} prior attempts\) with #{ERROR_MSG}/
602
+ def match_and_process_enqueuing_job(line)
603
+ matchdata = ENQUEUEING.match line
604
+ if matchdata
605
+ {
606
+ log_id: matchdata[:log_id],
607
+ job_id: matchdata[:job_id]
608
+ }
609
+ end
610
+ end
611
+
612
+ RUNNING_MESSAGE = /I, \[#{TIMESTAMP} #[0-9]+\] INFO -- : #{TIMESTAMP_WITH_TZONE}: \[#{WORKER}\] Job #{METHOD} \[#{JOB_ID}\] from .+ with arguments: \[#{ARGUMENTS}\] \(id=#{ID}\) \(queue=.*\) RUNNING/o
613
+
614
+ def match_and_process_running_job(line)
615
+ matchdata = RUNNING_MESSAGE.match line
616
+ if matchdata
617
+ {
618
+ started_at: matchdata[:timestamp],
619
+ job_id: matchdata[:job_id],
620
+ object_id: matchdata[:id],
621
+ pid: matchdata[:pid]
622
+ }
623
+ end
624
+ end
625
+
626
+ COMPLETED_MESSAGE = /I, \[#{TIMESTAMP} #[0-9]+\] INFO -- : #{TIMESTAMP_WITH_TZONE}: \[#{WORKER}\] Job #{METHOD} \[#{JOB_ID}\] from .+ with arguments: \[#{ARGUMENTS}\] \(id=#{ID}\) \(queue=.*\) COMPLETED after (?<duration_total_ms>#{MSECS})/o
627
+
628
+ def match_and_process_completed_job(line)
629
+ matchdata = COMPLETED_MESSAGE.match line
630
+ if matchdata
631
+ {
632
+ ended_at: matchdata[:timestamp],
633
+ duration_total_ms: matchdata[:duration_total_ms],
634
+ id: matchdata[:id],
635
+ job_id: matchdata[:job_id],
636
+ worker: matchdata[:worker],
637
+ host: matchdata[:host],
638
+ pid: matchdata[:pid],
639
+ log_id: matchdata.named_captures["log_id"],
640
+ object_id: matchdata[:id],
641
+ method: matchdata[:method],
642
+ exit_status: 'C',
643
+ arguments: matchdata[:arguments]
644
+ }
645
+ end
646
+ end
647
+
648
+ # similar to completed with I->E, INFO->ERROR, COMPLETED->FAILED and final message structure a bit different
649
+ ERROR_MESSAGE_PERMANENT = /E, \[#{TIMESTAMP} #[0-9]+\] ERROR -- : #{TIMESTAMP_WITH_TZONE}: \[#{WORKER}\] Job #{METHOD} \[#{JOB_ID}\] from .+ with arguments: \[#{ARGUMENTS}\] \(id=#{ID}\) \(queue=.*\) (?<error_msg>FAILED permanently because of #{TIMES} consecutive failures)/o
650
+
651
+ ERROR_MESSAGE = /E, \[#{TIMESTAMP} #[0-9]+\] ERROR -- : #{TIMESTAMP_WITH_TZONE}: \[#{WORKER}\] Job #{METHOD} \[#{JOB_ID}\] from .+ with arguments: \[#{ARGUMENTS}\] \(id=#{ID}\) \(queue=.*\) FAILED \(#{TIMES} prior attempts\) with #{ERROR_MSG}/o
652
+
653
+ ERROR_MESSAGE_SHORT = /E, \[#{TIMESTAMP} #[0-9]+\] ERROR -- : #{TIMESTAMP_WITH_TZONE}: \[#{WORKER}\] Job #{METHOD} \(id=#{ID}\) FAILED \(#{TIMES} prior attempts\) with #{ERROR_MSG}/o
511
654
 
512
655
  def match_and_process_job_error(line)
513
- [ERROR_MESSAGE, ERROR_MESSAGE_SHORT].map do |regexp|
656
+ [ERROR_MESSAGE_PERMANENT, ERROR_MESSAGE, ERROR_MESSAGE_SHORT].map do |regexp|
514
657
  matchdata = regexp.match line
515
658
  if matchdata
659
+ exit_status = matchdata[:error_msg].include? "permanently" ? "F" : "E"
660
+
516
661
  return {
517
662
  ended_at: matchdata[:timestamp],
518
- duration_total_ms: 0,
663
+ duration_total_ms: nil, # we could compute the time to failure
519
664
  worker: matchdata[:worker],
520
665
  host: matchdata[:host],
521
666
  pid: matchdata[:pid],
522
- log_id: matchdata.named_captures["log_id"],
667
+ job_id: matchdata.named_captures["job_id"],
523
668
  object_id: matchdata[:id],
524
669
  method: matchdata[:method],
525
670
  arguments: matchdata.named_captures["arguments"],
@@ -134,20 +134,24 @@ module LogSense
134
134
  GROUP BY description
135
135
  ).gsub("\n", "") || [[]]
136
136
 
137
- @job_error_plot = @db.execute %(
137
+ @job_plot = @db.execute %(
138
138
  SELECT strftime("%Y-%m-%d", ended_at) as Day,
139
- count(distinct(id)) as Errors
139
+ sum(iif(exit_status == 'C', 1, 0)) as Completed,
140
+ sum(iif(exit_status == 'E', 1, 0)) as Errors
140
141
  FROM Job
141
142
  WHERE #{filter}
142
143
  GROUP BY strftime("%Y-%m-%d", ended_at)
143
144
  ).gsub("\n", "") || [[]]
144
145
 
145
- @job_error = @db.execute %(
146
- SELECT strftime("%Y-%m-%d %H:%M", ended_at),
147
- worker,
148
- host,
146
+ # worker,
147
+ # host,
148
+ # pid,
149
+ @jobs = @db.execute %(
150
+ SELECT strftime("%Y-%m-%d %H:%M", started_at),
151
+ duration_total_ms,
149
152
  pid,
150
153
  object_id,
154
+ exit_status,
151
155
  method,
152
156
  arguments,
153
157
  error_msg,
@@ -160,13 +164,14 @@ module LogSense
160
164
  SELECT worker,
161
165
  host,
162
166
  pid,
167
+ exit_status,
163
168
  object_id,
169
+ GROUP_CONCAT(DISTINCT(error_msg)),
164
170
  method,
165
171
  arguments,
166
- error_msg,
167
172
  max(attempt)
168
173
  FROM Job
169
- WHERE #{filter}
174
+ WHERE #{filter} and exit_status == 'E'
170
175
  GROUP BY object_id
171
176
  ).gsub("\n", "") || [[]]
172
177
 
@@ -182,7 +182,7 @@ module LogSense
182
182
  name: 'Routing Errors',
183
183
  data: fatal_plot.filter(row => row[0] != '').map(row => row[2]),
184
184
  type: 'bar',
185
- color: '#DEDEDE',
185
+ color: '#D0D0D0',
186
186
  label: {
187
187
  show: true,
188
188
  position: 'top'
@@ -209,14 +209,27 @@ module LogSense
209
209
  rows: data[:fatal_grouped],
210
210
  col: "small-12 cell"
211
211
  },
212
+ browsers(data),
213
+ platforms(data),
214
+ ips(data),
215
+ countries(data),
216
+ ip_per_hour_report_spec(ips_per_hour(data[:ips_per_hour])),
217
+ session_report_spec(ips_detailed(data[:ips_per_day_detailed])),
212
218
  {
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],
219
+ title: "Jobs (Completed and Failed)",
220
+ explanation: %(
221
+ This report includes completed and failed jobs, parsing lines
222
+ marked as COMPLETED or ERROR/FAILED.
223
+
224
+ This excludes from the table entries marked as RUNNING and then
225
+ completed with "performed".
226
+ ),
227
+ header: %w[Date Duration PID ID Exit_Status Method Arguments Error_Msg Attempts],
228
+ column_alignment: %i[left left right left left left left left right],
229
+ column_width: ["10%", "5%", "5%", "5%", "5%", "15%", "25%", "25%", "5%"],
230
+ rows: data[:jobs],
218
231
  col: "small-12 cell",
219
- echarts_extra: "var fatal_plot=#{data[:job_error_plot].to_json}",
232
+ echarts_extra: "var fatal_plot=#{data[:job_plot].to_json}",
220
233
  echarts_spec: "{
221
234
  toolbox: {
222
235
  feature: {
@@ -241,9 +254,19 @@ module LogSense
241
254
  },
242
255
  series: [
243
256
  {
244
- name: 'Errors',
257
+ name: 'Completed',
245
258
  data: fatal_plot.filter(row => row[0] != '').map(row => row[1]),
246
259
  type: 'bar',
260
+ color: '#D0D0D0',
261
+ label: {
262
+ show: true,
263
+ position: 'top'
264
+ },
265
+ },
266
+ {
267
+ name: 'Errors',
268
+ data: fatal_plot.filter(row => row[0] != '').map(row => row[2]),
269
+ type: 'bar',
247
270
  color: '#D30001',
248
271
  label: {
249
272
  show: true,
@@ -255,18 +278,12 @@ module LogSense
255
278
  },
256
279
  {
257
280
  title: "Job Errors (grouped)",
258
- header: %w[Worker Host PID ID Error Method Arguments Attempt],
281
+ header: %w[Worker Host PID ID Error Method Arguments Attempts],
259
282
  column_alignment: %i[left left left left left left left right],
260
283
  column_width: ["5%", "5%", "5%", "5%", "20%", "30%", "20%", "10%"],
261
284
  rows: data[:job_error_grouped],
262
285
  col: "small-12 cell"
263
- },
264
- browsers(data),
265
- platforms(data),
266
- ips(data),
267
- countries(data),
268
- ip_per_hour_report_spec(ips_per_hour(data[:ips_per_hour])),
269
- session_report_spec(ips_detailed(data[:ips_per_day_detailed]))
286
+ }
270
287
  ]
271
288
  end
272
289
  end
@@ -1,5 +1,6 @@
1
1
  module LogSense
2
2
  class ReportShaper
3
+ SESSION_URL_LIMIT = 300
3
4
  WORDS_SEPARATOR = ' · '
4
5
 
5
6
  # return { [ip,day] => { [ hits, list of urls ] } }
@@ -14,7 +15,11 @@ module LogSense
14
15
  date,
15
16
  hash[ip][date].size,
16
17
  hash[ip][date].uniq.size,
17
- hash[ip][date].uniq.size < 100 ? hash[ip][date].uniq.join(WORDS_SEPARATOR) : "[too many]"
18
+ if hash[ip][date].uniq.size < SESSION_URL_LIMIT
19
+ hash[ip][date].uniq.join(WORDS_SEPARATOR)
20
+ else
21
+ "[too many]"
22
+ end
18
23
  ]
19
24
  end
20
25
  end
@@ -1,3 +1,3 @@
1
1
  module LogSense
2
- VERSION = "2.1.0"
2
+ VERSION = "2.2.0"
3
3
  end
data/todo.org CHANGED
@@ -2,12 +2,12 @@
2
2
 
3
3
  * Todo (2024-08-01)
4
4
 
5
+ ** T Break rails log_parser in subclasses (one per log type)
6
+
5
7
  ** T Distinguish between exceptions, errors with no timestamps and errors with timestamps :feature:
6
8
  - In rails reports some errors don't have a timestamp, whereas other have.
7
9
 
8
10
  1. Check whether we can always get an ID of an event
9
- 2.
10
-
11
11
 
12
12
  ** T Move geolocation to aggregator, so that we can perform queries more efficiently :refactoring:
13
13
  ** T Filter on dates :feature:
@@ -18,9 +18,7 @@
18
18
  ** T error on data
19
19
  - The data reported by echarts on the number of hits of a controller
20
20
  differs from the data shown in the table?
21
- ** T Sidebar foreground color in new apache report
22
- ** T Dark style
23
- ** T Remove dependency from Zurb Foundation (native css grid instead)
21
+ ** T Remove dependency from Zurb Foundation (native css grid instead) :refactoring:
24
22
  ** T refactor report specifications in their own class :refactoring:
25
23
  ** T Add lines not parsed to the report :feature:
26
24
  ** T Using an empty log as input raises an error :feature:
@@ -314,3 +312,22 @@
314
312
  :ARCHIVE_CATEGORY: todo
315
313
  :ARCHIVE_TODO: REJECTED
316
314
  :END:
315
+
316
+ ** D Dark style
317
+ :PROPERTIES:
318
+ :ARCHIVE_TIME: 2024-08-23 Fri 16:25
319
+ :ARCHIVE_FILE: ~/Sources/ruby/log_sense/todo.org
320
+ :ARCHIVE_OLPATH: Todo (2024-08-01)
321
+ :ARCHIVE_CATEGORY: todo
322
+ :ARCHIVE_TODO: D
323
+ :END:
324
+
325
+ ** D Sidebar foreground color in new apache report
326
+ :PROPERTIES:
327
+ :ARCHIVE_TIME: 2024-08-23 Fri 16:25
328
+ :ARCHIVE_FILE: ~/Sources/ruby/log_sense/todo.org
329
+ :ARCHIVE_OLPATH: Todo (2024-08-01)
330
+ :ARCHIVE_CATEGORY: todo
331
+ :ARCHIVE_TODO: D
332
+ :END:
333
+
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.1.0
4
+ version: 2.2.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-20 00:00:00.000000000 Z
11
+ date: 2024-08-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: browser