log_sense 2.1.0 → 2.2.1

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: c2acfa576d18214e05df5520518000955d12957756674c26a5fd63ac6efc9969
4
+ data.tar.gz: 781b38f2a7118efe8f0c1ca0cba6d21bdf0a77bd3fe27a8c1dc27ca5cdc2b3e7
5
5
  SHA512:
6
- metadata.gz: dc77b1c63c08a9a190aae0ab0ab44bdffe37e0dc2e999d3dc8a236703dc8e430fb32ff09d884d5d9e90ff7870b7bb212d0cc486e877982ca3c5328a3aeda5943
7
- data.tar.gz: e73ad267ad54dc4e3c09481ec4f055e6d9e1e9346262b04975b754bd8822b19ce27298299ce6fb7cc3511e5248b6303392c984b48d68d40bd398bd14d7be9d80
6
+ metadata.gz: bc877d0198d6f170addfd9a0972ed7df57df2d5c00fdac850808ab1f8f86c3ed3d2aa37cc1058f1c6ca1da86bbdc6e6e09a44778edca3a48aaf825f4d5aefa26
7
+ data.tar.gz: ed99ed592a9a689fac9d319b1f346e705fcbbde021f45332d219097ed0df351d6b1d8a7bf2e0a72307b9932e2ab63ece7d0d8c2f2ef4fb9be45d14e2a5c6c863
data/CHANGELOG.org CHANGED
@@ -2,6 +2,15 @@
2
2
  #+AUTHOR: Adolfo Villafiorita
3
3
  #+STARTUP: showall
4
4
 
5
+ * 2.2.1
6
+
7
+ - [Bug] Small fixes
8
+
9
+ * 2.2.0
10
+
11
+ - [User] Better management of Delayed Job (show also completed)
12
+ - [Refactoring] Rails rxegexps are now pre-compiled with /o
13
+
5
14
  * 2.1.0
6
15
 
7
16
  - [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,15 +307,79 @@ 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
+ id = data[:object_id]
373
+ # TODO: no need for if (here and everywhere else)
374
+ # it has to be there!
375
+ if pending_jobs[id]
376
+ data = (pending_jobs[id] || {}).merge(data)
377
+ end
378
+
312
379
  ins_job.execute(
313
- data[:ended_at], # this is temporary (while we wait to parse BEGIN) + required by filter
380
+ data[:started_at],
314
381
  data[:ended_at],
315
- 0,
382
+ nil,
316
383
  data[:worker],
317
384
  data[:host],
318
385
  data[:pid],
@@ -327,6 +394,8 @@ module LogSense
327
394
  filename,
328
395
  line_number
329
396
  )
397
+
398
+ pending_jobs.delete(id)
330
399
  end
331
400
  end
332
401
  end
@@ -337,23 +406,37 @@ module LogSense
337
406
  ins_error.execute(value)
338
407
  end
339
408
 
409
+ # DO NOT persist the pending_jobs which have not yet completed (those
410
+ # still available at: pending_jobs).
411
+ #
412
+ # In fact various entries initiated with RUNNING end up with "performed"
413
+ # (rather than COMPLETED). Notice that entries COMPLETED always have a
414
+ # peformed entry as well.
415
+ #
416
+ # Since we do not yet process "performed" entry log and in pending jobs
417
+ # we end up accumulating a bunch of entries which are marked as
418
+ # "performed"
419
+ #
420
+ # Performed entries are tricky since they use JOB_ID, rather than the
421
+ # object_id and probably requires to change how we enter pending_ids
422
+
340
423
  db
341
424
  end
342
425
 
343
426
  # could be private here, I guess we keep them public to make them simpler
344
427
  # to try from irb
345
428
 
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.]+/
429
+ TIMESTAMP = '(?<timestamp>[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\\.[0-9]+)'
430
+ LOG_ID = '(?<log_id>[a-z0-9-]+)'
431
+ VERB = '(?<verb>GET|POST|PATCH|PUT|DELETE)'
432
+ URL = '(?<url>[^"]+)'
433
+ IP = '(?<ip>[0-9.]+)'
434
+ STATUS = '(?<status>[0-9]+)'
435
+ STATUS_IN_WORDS = '(OK|Unauthorized|Found|Internal Server Error|Bad Request|Method Not Allowed|Request Timeout|Not Implemented|Bad Gateway|Service Unavailable)'
436
+ MSECS = '[0-9.]+'
354
437
 
355
438
  # 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/
439
+ STARTED_REGEXP = /I, \[#{TIMESTAMP} #[0-9]+\] INFO -- : \[#{LOG_ID}\] Started #{VERB} "#{URL}" for #{IP} at/o
357
440
 
358
441
  def match_and_process_start(line)
359
442
  matchdata = STARTED_REGEXP.match line
@@ -372,14 +455,14 @@ module LogSense
372
455
  # 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
456
  # 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
457
  # 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]+))?\)/
458
+ 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
459
 
377
460
  def match_and_process_completed(line)
378
461
  matchdata = (COMPLETED_REGEXP.match line)
379
462
  # exit_status = matchdata[:status].to_i == 500 ? "E" : "I"
380
463
  if matchdata
381
464
  {
382
- exit_status: "I",
465
+ exit_status: "S:COMPLETED",
383
466
  ended_at: matchdata[:timestamp],
384
467
  log_id: matchdata[:log_id],
385
468
  status: matchdata[:status],
@@ -393,7 +476,7 @@ module LogSense
393
476
  end
394
477
 
395
478
  # 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/
479
+ PROCESSING_REGEXP = /I, \[#{TIMESTAMP} #[0-9]+\] INFO -- : \[#{LOG_ID}\] Processing by (?<controller>[^ ]+) as/o
397
480
 
398
481
  def match_and_process_processing_by line
399
482
  matchdata = PROCESSING_REGEXP.match line
@@ -410,13 +493,13 @@ module LogSense
410
493
  # F, [2021-12-04T00:34:05.839209 #2735058] FATAL -- : [3a16162e-a6a5-435e-a9d8-c4df5dc0f728]
411
494
  # 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
495
 
413
- FATAL_REGEXP = /F, \[#{TIMESTAMP} #[0-9]+\] FATAL -- : \[#{LOG_ID}\]/
496
+ FATAL_REGEXP = /F, \[#{TIMESTAMP} #[0-9]+\] FATAL -- : \[#{LOG_ID}\]/o
414
497
 
415
498
  def match_and_process_fatal(line)
416
499
  matchdata = FATAL_REGEXP.match line
417
500
  if matchdata
418
501
  {
419
- exit_status: "F",
502
+ exit_status: "S:FAILED",
420
503
  log_id: matchdata[:log_id],
421
504
  }
422
505
  end
@@ -440,8 +523,8 @@ module LogSense
440
523
  # [f57e3648-568a-48f9-ae3a-a522b1ff3298] app/models/donations/donation.rb:440:in `build_items_for_delivery'
441
524
  # [f57e3648-568a-48f9-ae3a-a522b1ff3298] app/controllers/donations_controller.rb:1395:in `create_delivery'
442
525
 
443
- EXCEPTION = /[A-Za-z_0-9:]+(Error|NotFound|Invalid|Unknown|Missing|ENOSPC)/
444
- FATAL_EXPLANATION_REGEXP = /^\[#{LOG_ID}\] (?<context>#{EXCEPTION})?(?<description>.*)/
526
+ EXCEPTION = "[A-Za-z_0-9:]+(Error|NotFound|Invalid|Unknown|Missing|ENOSPC)"
527
+ FATAL_EXPLANATION_REGEXP = /^\[#{LOG_ID}\] (?<context>#{EXCEPTION})?(?<description>.*)/o
445
528
  def match_and_process_fatal_explanation(line)
446
529
  matchdata = FATAL_EXPLANATION_REGEXP.match line
447
530
  if matchdata
@@ -454,7 +537,7 @@ module LogSense
454
537
  end
455
538
 
456
539
  # 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>.+)"/
540
+ BROWSER_INFO_REGEXP = /BrowserInfo: "(?<browser>.+)","(?<platform>.+)","(?<device_name>.+)","(?<controller>.+)","(?<method>.+)","(?<request_format>.+)","(?<anon_ip>.+)","(?<timestamp>.+)"/o
458
541
 
459
542
  def match_and_process_browser_info(line)
460
543
  matchdata = BROWSER_INFO_REGEXP.match line
@@ -474,12 +557,14 @@ module LogSense
474
557
 
475
558
  # Sequence:
476
559
  #
477
- # - enqueued
478
- # - running
479
- # - performing
560
+ # - enqueued (LOG_ID user event, JOB_ID assigned by system)
561
+ # - running (JOB_ID links to enqueued; PID assigned by system; OBJECT_ID assigned by the system)
562
+ # - performing (OBJECT_ID links to running; OBJECT_ID assigned by the system; JOB_ID is new)
480
563
  # - (rendering)
481
- # - performed
482
- # - completed
564
+ # - performed (OBJECT_ID links to running; JOB_ID links to previous)
565
+ # - completed (OBJECT_ID links to running; JOB_ID links to previous)
566
+ #
567
+ # SOMETIMES PERFORMED APPEARS WITH NO COMPLETED.
483
568
  #
484
569
  # 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
570
  #
@@ -489,41 +574,107 @@ module LogSense
489
574
  #
490
575
  # 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
576
  #
577
+ #
578
+ # 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
579
+ #
580
+ #
492
581
  # 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
582
 
494
583
  # Sequence with errors:
495
584
  # (two log entries per error)
496
585
  #
497
586
  # 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.
587
+
498
588
  # 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
589
 
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>.+)/
590
+ 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]+)'
591
+ ID = '(?<id>[0-9]+)'
592
+ JOB_ID = '(?<job_id>[a-zA-Z0-9-]+)'
593
+ WORKER = 'Worker\\((?<worker>.+) host:(?<host>.+) pid:(?<pid>[0-9]+)\\)'
594
+ METHOD = '(?<method>[A-Za-z0-9:#_]+)'
595
+ TIMES = '(?<attempt>[0-9]+)'
596
+ ERROR_MSG = '(?<error_msg>.+)'
597
+ ARGUMENTS = '(?<arguments>.+)'
507
598
 
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}/
599
+ #
600
+ # these are together, since they return temporary data
601
+ #
602
+ ENQUEUEING = /I, \[#{TIMESTAMP} #[0-9]+\] INFO -- : \[#{LOG_ID}\] \[ActiveJob\] Enqueued #{METHOD} \(Job ID: #{JOB_ID}\) to .* with arguments: #{ARGUMENTS}/o
509
603
 
510
- ERROR_MESSAGE_SHORT = /E, \[#{TIMESTAMP} #[0-9]+\] ERROR -- : #{TIMESTAMP_WITH_TZONE}: \[#{WORKER}\] Job #{METHOD} \(id=#{ID}\) FAILED \(#{TIMES} prior attempts\) with #{ERROR_MSG}/
604
+ def match_and_process_enqueuing_job(line)
605
+ matchdata = ENQUEUEING.match line
606
+ if matchdata
607
+ {
608
+ log_id: matchdata[:log_id],
609
+ job_id: matchdata[:job_id]
610
+ }
611
+ end
612
+ end
613
+
614
+ RUNNING_MESSAGE = /I, \[#{TIMESTAMP} #[0-9]+\] INFO -- : #{TIMESTAMP_WITH_TZONE}: \[#{WORKER}\] Job #{METHOD} \[#{JOB_ID}\] from .+ with arguments: \[#{ARGUMENTS}\] \(id=#{ID}\) \(queue=.*\) RUNNING/o
615
+
616
+ def match_and_process_running_job(line)
617
+ matchdata = RUNNING_MESSAGE.match line
618
+ if matchdata
619
+ {
620
+ started_at: matchdata[:timestamp],
621
+ job_id: matchdata[:job_id],
622
+ object_id: matchdata[:id],
623
+ pid: matchdata[:pid]
624
+ }
625
+ end
626
+ end
627
+
628
+ 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
629
+
630
+ def match_and_process_completed_job(line)
631
+ matchdata = COMPLETED_MESSAGE.match line
632
+ if matchdata
633
+ {
634
+ ended_at: matchdata[:timestamp],
635
+ duration_total_ms: matchdata[:duration_total_ms],
636
+ id: matchdata[:id],
637
+ job_id: matchdata[:job_id],
638
+ worker: matchdata[:worker],
639
+ host: matchdata[:host],
640
+ pid: matchdata[:pid],
641
+ log_id: matchdata.named_captures["log_id"],
642
+ object_id: matchdata[:id],
643
+ method: matchdata[:method],
644
+ exit_status: 'S:COMPLETED',
645
+ arguments: matchdata[:arguments]
646
+ }
647
+ end
648
+ end
649
+
650
+ # similar to completed with I->E, INFO->ERROR, COMPLETED->FAILED and final message structure a bit different
651
+ 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
652
+
653
+ 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
654
+
655
+ 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
656
 
512
657
  def match_and_process_job_error(line)
513
- [ERROR_MESSAGE, ERROR_MESSAGE_SHORT].map do |regexp|
658
+ [ERROR_MESSAGE_PERMANENT, ERROR_MESSAGE, ERROR_MESSAGE_SHORT].map do |regexp|
514
659
  matchdata = regexp.match line
515
660
  if matchdata
661
+ exit_status = if matchdata[:error_msg].include?("permanently")
662
+ "S:FAILED"
663
+ else
664
+ "S:ERROR"
665
+ end
666
+
516
667
  return {
517
668
  ended_at: matchdata[:timestamp],
518
- duration_total_ms: 0,
669
+ duration_total_ms: nil, # we could compute the time to failure
519
670
  worker: matchdata[:worker],
520
671
  host: matchdata[:host],
521
672
  pid: matchdata[:pid],
522
- log_id: matchdata.named_captures["log_id"],
673
+ job_id: matchdata.named_captures["job_id"],
523
674
  object_id: matchdata[:id],
524
675
  method: matchdata[:method],
525
676
  arguments: matchdata.named_captures["arguments"],
526
- exit_status: 'E',
677
+ exit_status:,
527
678
  attempt: matchdata[:attempt],
528
679
  error_msg: matchdata[:error_msg],
529
680
  }
@@ -108,7 +108,7 @@ module LogSense
108
108
  sum(iif(context NOT LIKE '%ActionController::RoutingError%', 1, 0)) as OtherErrors
109
109
  FROM Event JOIN Error
110
110
  ON event.log_id == error.log_id
111
- WHERE #{filter} and exit_status == 'F'
111
+ WHERE #{filter} and exit_status == 'S:FAILED'
112
112
  GROUP BY strftime("%Y-%m-%d", started_at)
113
113
  ).gsub("\n", "") || [[]]
114
114
 
@@ -121,7 +121,7 @@ module LogSense
121
121
  event.log_id
122
122
  FROM Event JOIN Error
123
123
  ON event.log_id == error.log_id
124
- WHERE #{filter} and exit_status == 'F'
124
+ WHERE #{filter} and exit_status == 'S:FAILED'
125
125
  ).gsub("\n", "") || [[]]
126
126
 
127
127
  @fatal_grouped = @db.execute %(
@@ -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 == 'S:COMPLETED', 1, 0)) as Completed,
140
+ sum(iif(exit_status == 'S:ERROR' or exit_status == 'S:FAILED', 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,
@@ -161,12 +165,13 @@ module LogSense
161
165
  host,
162
166
  pid,
163
167
  object_id,
168
+ exit_status,
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 == 'S:ERROR' or exit_status == 'S:FAILED')
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 right left 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],
259
- column_alignment: %i[left left left left left left left right],
260
- column_width: ["5%", "5%", "5%", "5%", "20%", "30%", "20%", "10%"],
281
+ header: %w[Worker Host PID ID Exit_Status Error Method Arguments Attempts],
282
+ column_alignment: %i[left left left left left left left left right],
283
+ column_width: ["5%", "5%", "5%", "5%", "5%", "20%", "25%", "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.1"
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.1
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