loga 2.6.1 → 2.7.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: 1c431f4c2be4360f29975cc8434c86647c77cf8a375bcd8fa6e6a0b47bb8a2e8
4
- data.tar.gz: 2ec1e76ed48281e99acca8bdc3dd55b3fb6068dee599dd5c4bf92008e1af6eb0
3
+ metadata.gz: 8a97eae050b9e31ef0ddb1e6f327563c4e0f54eb1a42ea7bb53b03e22c8fb9c9
4
+ data.tar.gz: 771373253651884fead19e93038d5c7e31827d3c562d2cd748e0acd66f31124e
5
5
  SHA512:
6
- metadata.gz: 5c09402cb2b8f7e90bb53ccba07df43c759a9d4cea9cd5a19f8fbfcf332b0ce7914dca1d3c085cd43d9d081e40cc2c4d18452bd5c6b44d10e1df2771c7a88146
7
- data.tar.gz: f9f937d8e0a600c05ca50e60c05b084d6756b76158f2b4dcfc7a070ee30ad535ef2df6f0c10d6f61a99152ec2fa2cdb1815b450ce600e4be72e51e797d1165b6
6
+ metadata.gz: 28d17a44bf2c8c0a919916438c8935a4033e14176185ca87ff69987ee03507dee5ae854c2887ff89d174c78fd0fa846b8d8237392b6705eb552365202f2f7680
7
+ data.tar.gz: 45bf838fa85c9a3a01ba1134034b2ab2e577cd9f9c28ecceaf175935ff69627f156856dc8e383b5212829636934657ee4fb0c40f5e5e992a54eb1027ee47d74f
data/.circleci/config.yml CHANGED
@@ -30,10 +30,6 @@ test_build: &test_build
30
30
 
31
31
  version: 2
32
32
  jobs:
33
- ruby-2.4:
34
- docker:
35
- - image: circleci/ruby:2.4
36
- <<: *test_build
37
33
  ruby-2.5:
38
34
  docker:
39
35
  - image: circleci/ruby:2.5
@@ -45,10 +41,12 @@ jobs:
45
41
  ruby-2.7:
46
42
  docker:
47
43
  - image: cimg/ruby:2.7
44
+ - image: redis:7
48
45
  <<: *test_build
49
46
  ruby-3.0:
50
47
  docker:
51
48
  - image: cimg/ruby:3.0
49
+ - image: redis:7
52
50
  <<: *test_build
53
51
  rubocop:
54
52
  <<: *basic_build
@@ -83,10 +81,6 @@ workflows:
83
81
  filters:
84
82
  tags:
85
83
  only: /.*/
86
- - ruby-2.4:
87
- filters:
88
- tags:
89
- only: /.*/
90
84
  - ruby-2.5:
91
85
  filters:
92
86
  tags:
@@ -112,7 +106,6 @@ workflows:
112
106
  ignore: /.*/
113
107
  requires:
114
108
  - rubocop
115
- - ruby-2.4
116
109
  - ruby-2.5
117
110
  - ruby-2.6
118
111
  - ruby-2.7
data/Appraisals CHANGED
@@ -1,13 +1,3 @@
1
- if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.4.0')
2
- appraise 'rails32' do
3
- gem 'rails', '~> 3.2.0'
4
- end
5
-
6
- appraise 'rails40' do
7
- gem 'rails', '~> 4.0.0'
8
- end
9
- end
10
-
11
1
  if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.7.0')
12
2
  appraise 'rails42' do
13
3
  gem 'rails', '~> 4.2.0'
@@ -31,13 +21,29 @@ if Gem::Version.new(RUBY_VERSION) > Gem::Version.new('2.5.0')
31
21
  gem 'rails', '~> 6.0.0'
32
22
  end
33
23
 
34
- appraise 'sidekiq6' do
35
- gem 'sidekiq', '~> 6.0'
24
+ appraise 'sidekiq60' do
25
+ gem 'sidekiq', '~> 6.0.0'
36
26
  end
37
27
 
38
28
  appraise 'sidekiq61' do
39
29
  gem 'sidekiq', '~> 6.1.0'
40
30
  end
31
+
32
+ appraise 'sidekiq62' do
33
+ gem 'sidekiq', '~> 6.2.0'
34
+ end
35
+
36
+ appraise 'sidekiq63' do
37
+ gem 'sidekiq', '~> 6.3.0'
38
+ end
39
+
40
+ appraise 'sidekiq64' do
41
+ gem 'sidekiq', '~> 6.4.0'
42
+ end
43
+
44
+ appraise 'sidekiq65' do
45
+ gem 'sidekiq', '~> 6.5.0'
46
+ end
41
47
  end
42
48
 
43
49
  if Gem::Version.new(RUBY_VERSION) > Gem::Version.new('2.7.0')
@@ -48,6 +54,18 @@ if Gem::Version.new(RUBY_VERSION) > Gem::Version.new('2.7.0')
48
54
  appraise 'rails70' do
49
55
  gem 'rails', '~> 7.0.0'
50
56
  end
57
+
58
+ appraise 'sidekiq7' do
59
+ gem 'sidekiq', '~> 7.0'
60
+ end
61
+
62
+ appraise 'sidekiq70' do
63
+ gem 'sidekiq', '~> 7.0.0'
64
+ end
65
+
66
+ appraise 'sidekiq71' do
67
+ gem 'sidekiq', '~> 7.1.0'
68
+ end
51
69
  end
52
70
 
53
71
  appraise 'sidekiq51' do
data/CHANGELOG.md CHANGED
@@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
4
4
  The format is based on [Keep a Changelog](http://keepachangelog.com/)
5
5
  and this project adheres to [Semantic Versioning](http://semver.org/).
6
6
 
7
+ ## [2.7.0] - 2023-03-28
8
+ ### Added
9
+ - Support for sidekiq 7
10
+
7
11
  ## [2.6.1] - 2022-02-22
8
12
  ### Fixed
9
13
  - Fix compatibility with sidekiq 6.4.1
data/README.md CHANGED
@@ -150,7 +150,7 @@ LOGA_FORMAT=simple rackup
150
150
 
151
151
  ### Sidekiq
152
152
 
153
- Loga `2.3` provides an out-of-the-box support for `Sidekiq ~> 5.0`.
153
+ Loga `2.7` provides an out-of-the-box support for `Sidekiq ~> 7.0`.
154
154
 
155
155
  ## Output Example
156
156
 
@@ -2,10 +2,10 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
 
5
- gem "rails", "~> 3.2.0"
5
+ gem "sidekiq", "~> 6.0.0"
6
6
 
7
7
  group :test do
8
- gem "simplecov"
8
+ gem "simplecov", "~> 0.17.0"
9
9
  end
10
10
 
11
11
  gemspec path: "../"
@@ -2,10 +2,10 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
 
5
- gem "rails", "~> 4.0.0"
5
+ gem "sidekiq", "~> 6.2.0"
6
6
 
7
7
  group :test do
8
- gem "simplecov"
8
+ gem "simplecov", "~> 0.17.0"
9
9
  end
10
10
 
11
11
  gemspec path: "../"
@@ -0,0 +1,11 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "sidekiq", "~> 6.3.0"
6
+
7
+ group :test do
8
+ gem "simplecov", "~> 0.17.0"
9
+ end
10
+
11
+ gemspec path: "../"
@@ -0,0 +1,11 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "sidekiq", "~> 6.4.0"
6
+
7
+ group :test do
8
+ gem "simplecov", "~> 0.17.0"
9
+ end
10
+
11
+ gemspec path: "../"
@@ -0,0 +1,11 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "sidekiq", "~> 6.5.0"
6
+
7
+ group :test do
8
+ gem "simplecov", "~> 0.17.0"
9
+ end
10
+
11
+ gemspec path: "../"
@@ -2,7 +2,7 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
 
5
- gem "sidekiq", "~> 6.0"
5
+ gem "sidekiq", "~> 7.0"
6
6
 
7
7
  group :test do
8
8
  gem "simplecov", "~> 0.17.0"
@@ -0,0 +1,11 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "sidekiq", "~> 7.0.0"
6
+
7
+ group :test do
8
+ gem "simplecov", "~> 0.17.0"
9
+ end
10
+
11
+ gemspec path: "../"
@@ -0,0 +1,11 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "sidekiq", "~> 7.1.0"
6
+
7
+ group :test do
8
+ gem "simplecov", "~> 0.17.0"
9
+ end
10
+
11
+ gemspec path: "../"
data/lib/loga/sidekiq.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Loga
2
4
  module Sidekiq
3
5
  def self.configure_logging
@@ -5,20 +7,41 @@ module Loga
5
7
  return if Gem::Version.new(::Sidekiq::VERSION) < Gem::Version.new('5.0')
6
8
 
7
9
  if Gem::Version.new(::Sidekiq::VERSION) < Gem::Version.new('6.0')
8
- require 'loga/sidekiq5/job_logger'
9
-
10
- ::Sidekiq.configure_server do |config|
11
- config.options[:job_logger] = Loga::Sidekiq5::JobLogger
12
- end
10
+ configure_for_sidekiq5
13
11
  elsif Gem::Version.new(::Sidekiq::VERSION) < Gem::Version.new('7.0')
14
- require 'loga/sidekiq6/job_logger'
12
+ configure_for_sidekiq6
13
+ elsif Gem::Version.new(::Sidekiq::VERSION) < Gem::Version.new('8.0')
14
+ configure_for_sidekiq7
15
+ end
16
+ end
15
17
 
16
- ::Sidekiq.configure_server do |config|
17
- config.options[:job_logger] = Loga::Sidekiq6::JobLogger
18
- end
18
+ def self.configure_for_sidekiq5
19
+ require 'loga/sidekiq5/job_logger'
20
+
21
+ ::Sidekiq.configure_server do |config|
22
+ config.options[:job_logger] = Loga::Sidekiq5::JobLogger
19
23
  end
20
24
 
21
25
  ::Sidekiq.logger = Loga.configuration.logger
22
26
  end
27
+
28
+ def self.configure_for_sidekiq6
29
+ require 'loga/sidekiq6/job_logger'
30
+
31
+ ::Sidekiq.configure_server do |config|
32
+ config.options[:job_logger] = Loga::Sidekiq6::JobLogger
33
+ end
34
+
35
+ ::Sidekiq.logger = Loga.configuration.logger
36
+ end
37
+
38
+ def self.configure_for_sidekiq7
39
+ require 'loga/sidekiq7/job_logger'
40
+
41
+ ::Sidekiq.configure_server do |config|
42
+ config[:job_logger] = Loga::Sidekiq7::JobLogger
43
+ config.logger = Loga.configuration.logger
44
+ end
45
+ end
23
46
  end
24
47
  end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'sidekiq/job_logger'
4
+
5
+ module Loga
6
+ module Sidekiq7
7
+ class JobLogger < ::Sidekiq::JobLogger
8
+ EVENT_TYPE = 'sidekiq'.freeze
9
+
10
+ def call(item, _queue)
11
+ start = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
12
+
13
+ yield
14
+
15
+ ::Sidekiq::Context.add(:elapsed, elapsed(start))
16
+
17
+ loga_log(message: "#{item['class']} with jid: '#{item['jid']}' done", item: item)
18
+ rescue Exception => e # rubocop:disable Lint/RescueException
19
+ ::Sidekiq::Context.add(:elapsed, elapsed(start))
20
+
21
+ loga_log(
22
+ message: "#{item['class']} with jid: '#{item['jid']}' fail", item: item,
23
+ exception: e
24
+ )
25
+
26
+ raise
27
+ end
28
+
29
+ private
30
+
31
+ def loga_log(message:, item:, exception: nil)
32
+ data = item.select do |k, _v|
33
+ %w[created_at enqueued_at jid queue retry
34
+ class].include? k
35
+ end
36
+
37
+ data['params'] = item['args']
38
+
39
+ data['exception'] = exception if exception
40
+
41
+ event = Event.new(type: EVENT_TYPE, message: message, data: data)
42
+
43
+ if exception
44
+ @logger.warn(event)
45
+ else
46
+ @logger.info(event)
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
data/lib/loga/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Loga
2
- VERSION = '2.6.1'.freeze
2
+ VERSION = '2.7.0'.freeze
3
3
  end
@@ -0,0 +1,180 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+ require 'timecop'
5
+ require 'fakeredis'
6
+
7
+ dummy_redis_config = ConnectionPool.new(size: 5) { Redis.new }
8
+
9
+ Sidekiq.configure_client do |config|
10
+ config.redis = dummy_redis_config
11
+ end
12
+
13
+ Sidekiq.configure_server do |config|
14
+ config.redis = dummy_redis_config
15
+ end
16
+
17
+ class MySidekiqWorker
18
+ include Sidekiq::Worker
19
+
20
+ def perform(_name)
21
+ logger.info('Hello from MySidekiqWorker')
22
+ end
23
+ end
24
+
25
+ describe 'Sidekiq client logger' do
26
+ let(:mgr) do
27
+ # https://github.com/mperham/sidekiq/blob/v6.0.0/test/test_actors.rb#L57:79
28
+ Class.new do
29
+ attr_reader :latest_error
30
+ attr_reader :mutex
31
+ attr_reader :cond
32
+
33
+ def initialize
34
+ @mutex = ::Mutex.new
35
+ @cond = ::ConditionVariable.new
36
+ end
37
+
38
+ def processor_died(_inst, err)
39
+ @latest_error = err
40
+ @mutex.synchronize do
41
+ @cond.signal
42
+ end
43
+ end
44
+
45
+ def processor_stopped(_inst)
46
+ @mutex.synchronize do
47
+ @cond.signal
48
+ end
49
+ end
50
+
51
+ def options
52
+ {
53
+ concurrency: 3,
54
+ job_logger: Loga::Sidekiq6::JobLogger,
55
+ queues: ['default'],
56
+ }
57
+ end
58
+ end
59
+ end
60
+
61
+ let(:target) { StringIO.new }
62
+
63
+ def read_json_log(line:)
64
+ target.rewind
65
+ JSON.parse(target.each_line.drop(line - 1).first)
66
+ end
67
+
68
+ def dump_log
69
+ offset = target.pos
70
+
71
+ target.rewind
72
+ target.each_line { puts _1 }
73
+
74
+ target.pos = offset
75
+ end
76
+
77
+ before do
78
+ Redis.current.flushall
79
+
80
+ Loga.reset
81
+
82
+ Loga.configure(
83
+ service_name: 'hello_world_app',
84
+ service_version: '1.0',
85
+ device: target,
86
+ format: :gelf,
87
+ )
88
+ end
89
+
90
+ it 'has the proper job logger' do
91
+ expect(Sidekiq.options[:job_logger]).to eq Loga::Sidekiq6::JobLogger
92
+ end
93
+
94
+ it 'has the proper logger for Sidekiq.logger' do
95
+ expect(Sidekiq.logger).to eq Loga.logger
96
+ end
97
+
98
+ it 'pushes a new element in the default queue' do
99
+ MySidekiqWorker.perform_async('Bob')
100
+
101
+ last_element = JSON.parse(Redis.current.lpop('queue:default'))
102
+
103
+ aggregate_failures do
104
+ expect(last_element['class']).to eq 'MySidekiqWorker'
105
+ expect(last_element['args']).to eq ['Bob']
106
+ expect(last_element['retry']).to eq true
107
+ expect(last_element['queue']).to eq 'default'
108
+ end
109
+ end
110
+
111
+ def test_log_from_worker(json_line)
112
+ aggregate_failures do
113
+ expect(json_line).to include(
114
+ '_class' => 'MySidekiqWorker',
115
+ '_service.name' => 'hello_world_app',
116
+ '_service.version' => '1.0',
117
+ '_tags' => '',
118
+ 'level' => 6,
119
+ 'version' => '1.1',
120
+ 'short_message' => 'Hello from MySidekiqWorker',
121
+ )
122
+
123
+ %w[_jid timestamp host].each do |key|
124
+ expect(json_line).to have_key(key)
125
+ end
126
+
127
+ expect(json_line).not_to include('_duration')
128
+ end
129
+ end
130
+
131
+ def test_job_end_log(json_line) # rubocop:disable Metrics/MethodLength
132
+ aggregate_failures do
133
+ expect(json_line).to include(
134
+ '_queue' => 'default',
135
+ '_retry' => true,
136
+ '_params' => ['Bob'],
137
+ '_class' => 'MySidekiqWorker',
138
+ '_type' => 'sidekiq',
139
+ '_service.name' => 'hello_world_app',
140
+ '_service.version' => '1.0',
141
+ '_tags' => '',
142
+ 'level' => 6,
143
+ 'version' => '1.1',
144
+ )
145
+
146
+ %w[_created_at _enqueued_at _jid _duration timestamp host].each do |key|
147
+ expect(json_line).to have_key(key)
148
+ end
149
+
150
+ expect(json_line['_duration']).to be < 500
151
+ expect(json_line['short_message']).to match(/MySidekiqWorker with jid:*/)
152
+ end
153
+ end
154
+
155
+ it 'logs the job processing event' do
156
+ MySidekiqWorker.perform_async('Bob')
157
+
158
+ require 'sidekiq/processor'
159
+
160
+ Sidekiq::Processor.new(mgr.new).start
161
+
162
+ sleep 0.5
163
+
164
+ test_log_from_worker(read_json_log(line: 1))
165
+ test_job_end_log(read_json_log(line: 2))
166
+
167
+ # This was a bug - the duration was constantly incresing based on when
168
+ # the logger was created. https://github.com/FundingCircle/loga/pull/117
169
+ #
170
+ # Test that after sleeping for few seconds the duration is still under 500ms
171
+ sleep 1
172
+
173
+ MySidekiqWorker.perform_async('Bob')
174
+
175
+ sleep 1
176
+
177
+ test_log_from_worker(read_json_log(line: 3))
178
+ test_job_end_log(read_json_log(line: 4))
179
+ end
180
+ end
@@ -0,0 +1,166 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+ require 'timecop'
5
+ require 'fakeredis'
6
+
7
+ dummy_redis_config = ConnectionPool.new(size: 5) { Redis.new }
8
+
9
+ Sidekiq.configure_client do |config|
10
+ config.redis = dummy_redis_config
11
+ end
12
+
13
+ Sidekiq.configure_server do |config|
14
+ config.redis = dummy_redis_config
15
+ end
16
+
17
+ class MySidekiqWorker
18
+ include Sidekiq::Job
19
+
20
+ def perform(_name)
21
+ logger.info('Hello from MySidekiqWorker')
22
+ end
23
+ end
24
+
25
+ module Sidekiq
26
+ def self.reset!
27
+ @config = DEFAULTS.dup
28
+ end
29
+ end
30
+
31
+ describe 'Sidekiq client logger' do
32
+ let(:target) { StringIO.new }
33
+ let(:config) do
34
+ c = Sidekiq
35
+
36
+ c[:queues] = %w[default]
37
+ c[:fetch] = Sidekiq::BasicFetch.new(c)
38
+ c[:error_handlers] << Sidekiq.method(:default_error_handler)
39
+
40
+ c
41
+ end
42
+
43
+ def read_json_log(line:)
44
+ target.rewind
45
+ JSON.parse(target.each_line.drop(line - 1).first)
46
+ end
47
+
48
+ before do
49
+ Sidekiq.reset!
50
+ Sidekiq.redis(&:flushdb)
51
+
52
+ Loga.reset
53
+
54
+ Loga.configure(
55
+ service_name: 'hello_world_app',
56
+ service_version: '1.0',
57
+ device: target,
58
+ format: :gelf,
59
+ )
60
+ end
61
+
62
+ it 'has the proper job logger' do
63
+ expect(Sidekiq.options[:job_logger]).to eq Loga::Sidekiq6::JobLogger
64
+ end
65
+
66
+ it 'has the proper logger for Sidekiq.logger' do
67
+ expect(Sidekiq.logger).to eq Loga.logger
68
+ end
69
+
70
+ it 'pushes a new element in the default queue' do
71
+ MySidekiqWorker.perform_async('Bob')
72
+
73
+ last_element = JSON.parse(Redis.current.lpop('queue:default'))
74
+
75
+ aggregate_failures do
76
+ expect(last_element['class']).to eq 'MySidekiqWorker'
77
+ expect(last_element['args']).to eq ['Bob']
78
+ expect(last_element['retry']).to eq true
79
+ expect(last_element['queue']).to eq 'default'
80
+ end
81
+ end
82
+
83
+ def test_log_from_worker(json_line)
84
+ aggregate_failures do
85
+ expect(json_line).to include(
86
+ '_class' => 'MySidekiqWorker',
87
+ '_service.name' => 'hello_world_app',
88
+ '_service.version' => '1.0',
89
+ '_tags' => '',
90
+ 'level' => 6,
91
+ 'version' => '1.1',
92
+ 'short_message' => 'Hello from MySidekiqWorker',
93
+ )
94
+
95
+ %w[_jid timestamp host].each do |key|
96
+ expect(json_line).to have_key(key)
97
+ end
98
+
99
+ expect(json_line).not_to include('_duration')
100
+ end
101
+ end
102
+
103
+ def test_job_end_log(json_line) # rubocop:disable Metrics/MethodLength
104
+ aggregate_failures do
105
+ expect(json_line).to include(
106
+ '_queue' => 'default',
107
+ '_retry' => true,
108
+ '_params' => ['Bob'],
109
+ '_class' => 'MySidekiqWorker',
110
+ '_type' => 'sidekiq',
111
+ '_service.name' => 'hello_world_app',
112
+ '_service.version' => '1.0',
113
+ '_tags' => '',
114
+ 'level' => 6,
115
+ 'version' => '1.1',
116
+ )
117
+
118
+ %w[_created_at _enqueued_at _jid _duration timestamp host].each do |key|
119
+ expect(json_line).to have_key(key)
120
+ end
121
+
122
+ expect(json_line['_duration']).to be < 500
123
+ expect(json_line['short_message']).to match(/MySidekiqWorker with jid:*/)
124
+ end
125
+ end
126
+
127
+ context 'with processor' do
128
+ let(:mutex) { Mutex.new }
129
+ let(:cond) { ConditionVariable.new }
130
+
131
+ def result(_processor, _exception)
132
+ mutex.synchronize { cond.signal }
133
+ end
134
+
135
+ def await(timeout: 0.5)
136
+ mutex.synchronize do
137
+ yield
138
+ cond.wait(mutex, timeout)
139
+ end
140
+ end
141
+
142
+ it 'logs the job processing event' do
143
+ MySidekiqWorker.perform_async('Bob')
144
+
145
+ require 'sidekiq/processor'
146
+
147
+ p = Sidekiq::Processor.new(config) { |pr, ex| result(pr, ex) }
148
+
149
+ await { p.start }
150
+
151
+ test_log_from_worker(read_json_log(line: 1))
152
+ test_job_end_log(read_json_log(line: 2))
153
+
154
+ # This was a bug - the duration was constantly incresing based on when
155
+ # the logger was created. https://github.com/FundingCircle/loga/pull/117
156
+ #
157
+ # Test that after sleeping for few seconds the duration is still under 500ms
158
+ sleep 1
159
+
160
+ await { MySidekiqWorker.perform_async('Bob') }
161
+
162
+ test_log_from_worker(read_json_log(line: 3))
163
+ test_job_end_log(read_json_log(line: 4))
164
+ end
165
+ end
166
+ end
@@ -0,0 +1,193 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe 'Sidekiq client logger' do
6
+ let(:target) { StringIO.new }
7
+ let(:config) { Sidekiq.instance_variable_get :@config }
8
+
9
+ before do
10
+ Sidekiq.configure_server do |config|
11
+ config.redis = { pool_name: :default }
12
+ end
13
+
14
+ Loga.reset
15
+
16
+ Loga.configure(
17
+ service_name: 'hello_world_app',
18
+ service_version: '1.0',
19
+ device: target,
20
+ format: :gelf,
21
+ )
22
+ end
23
+
24
+ it 'has the proper job logger' do
25
+ expect(config[:job_logger]).to eq Loga::Sidekiq7::JobLogger
26
+ end
27
+
28
+ it 'has the proper logger for Sidekiq.logger' do
29
+ expect(Sidekiq.logger).to eq Loga.logger
30
+ end
31
+
32
+ context 'with processor' do
33
+ require 'sidekiq/processor'
34
+
35
+ let(:mutex) { Mutex.new }
36
+ let(:cond) { ConditionVariable.new }
37
+ let(:processor) do
38
+ Sidekiq::Processor.new(config.default_capsule) { |pr, ex| result(pr, ex) }
39
+ end
40
+
41
+ before do
42
+ @exception = nil
43
+ end
44
+
45
+ context 'with a successful job' do
46
+ before do
47
+ MySidekiqWorker.perform_async('Bob')
48
+
49
+ await { processor.start }
50
+
51
+ processor.terminate(true)
52
+ end
53
+
54
+ it 'logs the job processing event' do
55
+ test_log_from_worker(read_json_log(line: -2))
56
+ end
57
+
58
+ it 'logs the "done" event' do
59
+ test_job_end_log(read_json_log(line: -1))
60
+ end
61
+ end
62
+
63
+ context 'with an error' do
64
+ before do
65
+ MySidekiqWorker.perform_async('Boom')
66
+
67
+ await { processor.start }
68
+
69
+ processor.terminate(true)
70
+ end
71
+
72
+ it 'logs the "error" event' do
73
+ test_job_fail_log(read_json_log(line: 0))
74
+ end
75
+
76
+ it 're-throws the error' do
77
+ # rubocop:disable RSpec/InstanceVariable
78
+ expect(@exception.message).to eq('Boom')
79
+ # rubocop:enable RSpec/InstanceVariable
80
+ end
81
+ end
82
+
83
+ def result(_processor, exception)
84
+ @exception = exception
85
+ mutex.synchronize { cond.signal }
86
+ end
87
+
88
+ def await(timeout: 0.1)
89
+ mutex.synchronize do
90
+ yield
91
+ cond.wait(mutex, timeout)
92
+ end
93
+ end
94
+
95
+ def common_log_fields
96
+ {
97
+ '_class' => 'MySidekiqWorker',
98
+ '_service.name' => 'hello_world_app',
99
+ '_service.version' => '1.0',
100
+ '_tags' => '',
101
+ 'version' => '1.1',
102
+ }.freeze
103
+ end
104
+
105
+ def job_logger_common_fields
106
+ common_log_fields.merge(
107
+ '_queue' => 'default',
108
+ '_retry' => true,
109
+ '_type' => 'sidekiq',
110
+ )
111
+ end
112
+
113
+ def test_log_from_worker(json_line)
114
+ aggregate_failures do
115
+ expect(json_line).to include(
116
+ common_log_fields.merge(
117
+ 'level' => 6,
118
+ 'short_message' => 'Hello from MySidekiqWorker',
119
+ ),
120
+ )
121
+
122
+ %w[_jid timestamp host].each do |key|
123
+ expect(json_line).to have_key(key)
124
+ end
125
+
126
+ expect(json_line).not_to include('_duration')
127
+ end
128
+ end
129
+
130
+ def test_job_end_log(json_line)
131
+ aggregate_failures do
132
+ expect(json_line).to include(
133
+ job_logger_common_fields.merge(
134
+ '_params' => ['Bob'],
135
+ 'level' => 6,
136
+ ),
137
+ )
138
+
139
+ %w[_created_at _enqueued_at _jid _duration timestamp host].each do |key|
140
+ expect(json_line).to have_key(key)
141
+ end
142
+
143
+ expect(json_line['_duration']).to be < 500
144
+ expect(json_line['short_message'])
145
+ .to match(/MySidekiqWorker with jid: '\w+' done/)
146
+ end
147
+ end
148
+
149
+ def test_job_fail_log(json_line)
150
+ aggregate_failures do
151
+ expect(json_line).to include(
152
+ job_logger_common_fields.merge(
153
+ '_params' => ['Boom'],
154
+ 'level' => 4,
155
+ ),
156
+ )
157
+
158
+ %w[_created_at _enqueued_at _jid _duration timestamp host].each do |key|
159
+ expect(json_line).to have_key(key)
160
+ end
161
+
162
+ expect(json_line['_duration']).to be < 500
163
+ expect(json_line['short_message'])
164
+ .to match(/MySidekiqWorker with jid: '\w+' fail/)
165
+ end
166
+ end
167
+ end
168
+
169
+ def dump_log
170
+ offset = target.pos
171
+
172
+ target.rewind
173
+ target.each_line { puts _1 }
174
+
175
+ target.pos = offset
176
+ end
177
+
178
+ def read_json_log(line:)
179
+ target.rewind
180
+
181
+ JSON.parse(target.readlines[line])
182
+ end
183
+ end
184
+
185
+ class MySidekiqWorker
186
+ include Sidekiq::Job
187
+
188
+ def perform(name)
189
+ raise name if name == 'Boom'
190
+
191
+ logger.info('Hello from MySidekiqWorker')
192
+ end
193
+ end
@@ -0,0 +1,125 @@
1
+ require 'spec_helper'
2
+ require 'loga/sidekiq7/job_logger'
3
+
4
+ RSpec.describe Loga::Sidekiq7::JobLogger do
5
+ subject(:job_logger) { described_class.new(logger) }
6
+
7
+ let(:target) { StringIO.new }
8
+
9
+ let(:json_line) do
10
+ target.rewind
11
+ JSON.parse(target.read.split("\n").last)
12
+ end
13
+
14
+ let(:logger) { Loga.logger }
15
+
16
+ before do
17
+ Loga.reset
18
+
19
+ Loga.configure(
20
+ service_name: 'hello_world_app',
21
+ service_version: '1.0',
22
+ device: target,
23
+ format: :gelf,
24
+ )
25
+ end
26
+
27
+ # https://github.com/mperham/sidekiq/blob/v6.1.2/lib/sidekiq/job_logger.rb
28
+ it 'inherits from ::Sidekiq::JobLogger' do
29
+ expect(subject).to be_a(::Sidekiq::JobLogger)
30
+ end
31
+
32
+ describe '#call' do
33
+ context 'when the job passess successfully' do
34
+ let(:item_data) do
35
+ {
36
+ 'class' => 'HardWorker',
37
+ 'args' => ['asd'],
38
+ 'retry' => true,
39
+ 'queue' => 'default',
40
+ 'jid' => '591f6f66ee0d218fb451dfb6',
41
+ 'created_at' => 1_528_799_582.904939,
42
+ 'enqueued_at' => 1_528_799_582.9049861,
43
+ }
44
+ end
45
+
46
+ it 'has the the required attributes on call' do
47
+ job_logger.call(item_data, 'queue') do
48
+ # something
49
+ end
50
+
51
+ expected_body = {
52
+ 'version' => '1.1',
53
+ 'level' => 6,
54
+ '_type' => 'sidekiq',
55
+ '_created_at' => 1_528_799_582.904939,
56
+ '_enqueued_at' => 1_528_799_582.9049861,
57
+ '_jid' => '591f6f66ee0d218fb451dfb6',
58
+ '_retry' => true,
59
+ '_queue' => 'default',
60
+ '_service.name' => 'hello_world_app',
61
+ '_class' => 'HardWorker',
62
+ '_service.version' => '1.0',
63
+ '_tags' => '',
64
+ '_params' => ['asd'],
65
+ }
66
+
67
+ aggregate_failures do
68
+ expect(json_line).to include(expected_body)
69
+ expect(json_line['_duration']).to be_a(Float)
70
+ expect(json_line['timestamp']).to be_a(Float)
71
+ expect(json_line['host']).to be_a(String)
72
+ expect(json_line['short_message']).to match(/HardWorker with jid:*/)
73
+ end
74
+ end
75
+ end
76
+
77
+ context 'when the job fails' do
78
+ let(:item_data) do
79
+ {
80
+ 'class' => 'HardWorker',
81
+ 'args' => ['asd'],
82
+ 'retry' => true,
83
+ 'queue' => 'default',
84
+ 'jid' => '591f6f66ee0d218fb451dfb6',
85
+ 'created_at' => 1_528_799_582.904939,
86
+ 'enqueued_at' => 1_528_799_582.9049861,
87
+ }
88
+ end
89
+
90
+ it 'has the the required attributes on call' do
91
+ failed_job = lambda do
92
+ job_logger.call(item_data, 'queue') do
93
+ raise StandardError
94
+ end
95
+ end
96
+
97
+ expected_body = {
98
+ 'version' => '1.1',
99
+ 'level' => 4,
100
+ '_type' => 'sidekiq',
101
+ '_created_at' => 1_528_799_582.904939,
102
+ '_enqueued_at' => 1_528_799_582.9049861,
103
+ '_jid' => '591f6f66ee0d218fb451dfb6',
104
+ '_retry' => true,
105
+ '_queue' => 'default',
106
+ '_service.name' => 'hello_world_app',
107
+ '_class' => 'HardWorker',
108
+ '_service.version' => '1.0',
109
+ '_tags' => '',
110
+ '_params' => ['asd'],
111
+ '_exception' => 'StandardError',
112
+ }
113
+
114
+ aggregate_failures do
115
+ expect(&failed_job).to raise_error(StandardError)
116
+ expect(json_line['_duration']).to be_a(Float)
117
+ expect(json_line).to include(expected_body)
118
+ expect(json_line['timestamp']).to be_a(Float)
119
+ expect(json_line['host']).to be_a(String)
120
+ expect(json_line['short_message']).to match(/HardWorker with jid:*/)
121
+ end
122
+ end
123
+ end
124
+ end
125
+ end
data/spec/spec_helper.rb CHANGED
@@ -30,21 +30,40 @@ when /unit/
30
30
  rspec_pattern = 'unit/**/*_spec.rb'
31
31
  require 'loga'
32
32
  when /sidekiq(?<version>\d+)/
33
- case $LAST_MATCH_INFO['version']
33
+ sidekiq_version = $LAST_MATCH_INFO['version']
34
+ case sidekiq_version
34
35
  when '51'
35
36
  rspec_pattern = [
36
37
  'spec/integration/sidekiq5_spec.rb',
37
38
  'spec/loga/sidekiq5/**/*_spec.rb',
38
39
  'spec/loga/sidekiq_spec.rb',
39
40
  ].join(',')
40
- when '6', '61'
41
+ when '60'
41
42
  rspec_pattern = [
42
- 'spec/integration/sidekiq6_spec.rb',
43
+ 'spec/integration/sidekiq60_spec.rb',
44
+ 'spec/loga/sidekiq5/**/*_spec.rb',
45
+ 'spec/loga/sidekiq_spec.rb',
46
+ ].join(',')
47
+ when '61', '62', '63', '64'
48
+ rspec_pattern = [
49
+ 'spec/integration/sidekiq61_spec.rb',
43
50
  'spec/loga/sidekiq6/**/*_spec.rb',
44
51
  'spec/loga/sidekiq_spec.rb',
45
52
  ].join(',')
53
+ when '65'
54
+ rspec_pattern = [
55
+ 'spec/integration/sidekiq65_spec.rb',
56
+ 'spec/loga/sidekiq6/**/*_spec.rb',
57
+ 'spec/loga/sidekiq_spec.rb',
58
+ ].join(',')
59
+ when '7', '70', '71'
60
+ rspec_pattern = [
61
+ 'spec/integration/sidekiq7_spec.rb',
62
+ 'spec/loga/sidekiq7/**/*_spec.rb',
63
+ 'spec/loga/sidekiq_spec.rb',
64
+ ].join(',')
46
65
  else
47
- raise 'FIXME: Unknown sidekiq - update this file.'
66
+ raise "FIXME: Unknown sidekiq #{sidekiq_version} - update this file."
48
67
  end
49
68
 
50
69
  require 'sidekiq'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: loga
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.6.1
4
+ version: 2.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Funding Circle
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-02-22 00:00:00.000000000 Z
11
+ date: 2023-11-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -254,8 +254,6 @@ files:
254
254
  - LICENSE.txt
255
255
  - README.md
256
256
  - Rakefile
257
- - gemfiles/rails32.gemfile
258
- - gemfiles/rails40.gemfile
259
257
  - gemfiles/rails42.gemfile
260
258
  - gemfiles/rails50.gemfile
261
259
  - gemfiles/rails52.gemfile
@@ -263,8 +261,15 @@ files:
263
261
  - gemfiles/rails61.gemfile
264
262
  - gemfiles/rails70.gemfile
265
263
  - gemfiles/sidekiq51.gemfile
266
- - gemfiles/sidekiq6.gemfile
264
+ - gemfiles/sidekiq60.gemfile
267
265
  - gemfiles/sidekiq61.gemfile
266
+ - gemfiles/sidekiq62.gemfile
267
+ - gemfiles/sidekiq63.gemfile
268
+ - gemfiles/sidekiq64.gemfile
269
+ - gemfiles/sidekiq65.gemfile
270
+ - gemfiles/sidekiq7.gemfile
271
+ - gemfiles/sidekiq70.gemfile
272
+ - gemfiles/sidekiq71.gemfile
268
273
  - gemfiles/sinatra14.gemfile
269
274
  - gemfiles/unit.gemfile
270
275
  - lib/loga.rb
@@ -286,13 +291,12 @@ files:
286
291
  - lib/loga/sidekiq.rb
287
292
  - lib/loga/sidekiq5/job_logger.rb
288
293
  - lib/loga/sidekiq6/job_logger.rb
294
+ - lib/loga/sidekiq7/job_logger.rb
289
295
  - lib/loga/tagged_logging.rb
290
296
  - lib/loga/utilities.rb
291
297
  - lib/loga/version.rb
292
298
  - loga.gemspec
293
299
  - spec/fixtures/README.md
294
- - spec/fixtures/rails32.rb
295
- - spec/fixtures/rails40.rb
296
300
  - spec/fixtures/rails42.rb
297
301
  - spec/fixtures/rails50.rb
298
302
  - spec/fixtures/rails52.rb
@@ -304,10 +308,14 @@ files:
304
308
  - spec/integration/rails/railtie_spec.rb
305
309
  - spec/integration/rails/request_spec.rb
306
310
  - spec/integration/sidekiq5_spec.rb
307
- - spec/integration/sidekiq6_spec.rb
311
+ - spec/integration/sidekiq60_spec.rb
312
+ - spec/integration/sidekiq61_spec.rb
313
+ - spec/integration/sidekiq65_spec.rb
314
+ - spec/integration/sidekiq7_spec.rb
308
315
  - spec/integration/sinatra_spec.rb
309
316
  - spec/loga/sidekiq5/job_logger_spec.rb
310
317
  - spec/loga/sidekiq6/job_logger_spec.rb
318
+ - spec/loga/sidekiq7/job_logger_spec.rb
311
319
  - spec/loga/sidekiq_spec.rb
312
320
  - spec/spec_helper.rb
313
321
  - spec/support/gethostname_shared.rb
@@ -350,8 +358,6 @@ specification_version: 4
350
358
  summary: Facilitate log aggregation via unified logging
351
359
  test_files:
352
360
  - spec/fixtures/README.md
353
- - spec/fixtures/rails32.rb
354
- - spec/fixtures/rails40.rb
355
361
  - spec/fixtures/rails42.rb
356
362
  - spec/fixtures/rails50.rb
357
363
  - spec/fixtures/rails52.rb
@@ -363,10 +369,14 @@ test_files:
363
369
  - spec/integration/rails/railtie_spec.rb
364
370
  - spec/integration/rails/request_spec.rb
365
371
  - spec/integration/sidekiq5_spec.rb
366
- - spec/integration/sidekiq6_spec.rb
372
+ - spec/integration/sidekiq60_spec.rb
373
+ - spec/integration/sidekiq61_spec.rb
374
+ - spec/integration/sidekiq65_spec.rb
375
+ - spec/integration/sidekiq7_spec.rb
367
376
  - spec/integration/sinatra_spec.rb
368
377
  - spec/loga/sidekiq5/job_logger_spec.rb
369
378
  - spec/loga/sidekiq6/job_logger_spec.rb
379
+ - spec/loga/sidekiq7/job_logger_spec.rb
370
380
  - spec/loga/sidekiq_spec.rb
371
381
  - spec/spec_helper.rb
372
382
  - spec/support/gethostname_shared.rb
@@ -1,80 +0,0 @@
1
- require 'action_controller/railtie'
2
- require 'action_mailer/railtie'
3
-
4
- Bundler.require(*Rails.groups(assets: %w[development test]))
5
-
6
- STREAM = StringIO.new unless defined?(STREAM)
7
-
8
- class Dummy < Rails::Application
9
- config.filter_parameters += [:password]
10
- config.secret_token = '32431967aed1c4357d311f27708a1837a938f07e0abfdefa6b8b398d7024c08c6b883ce9254cdd8573ce8e78f9dd192efff39395127811040fc695ab23677452'
11
- config.session_store :cookie_store, key: '_rails32_session'
12
-
13
- config.log_tags = [:uuid, 'TEST_TAG']
14
- config.loga = {
15
- device: STREAM,
16
- host: 'bird.example.com',
17
- service_name: 'hello_world_app',
18
- service_version: '1.0',
19
- }
20
- config.action_mailer.delivery_method = :test
21
- config.active_support.deprecation = :notify
22
- end
23
-
24
- class ApplicationController < ActionController::Base
25
- include Rails.application.routes.url_helpers
26
- protect_from_forgery
27
-
28
- def ok
29
- render text: 'Hello Rails'
30
- end
31
-
32
- def error
33
- nil.name
34
- end
35
-
36
- def show
37
- render json: params
38
- end
39
-
40
- def create
41
- render json: params
42
- end
43
-
44
- def new
45
- redirect_to :ok
46
- end
47
-
48
- def update
49
- @id = params[:id]
50
- render '/user'
51
- end
52
- end
53
-
54
- class FakeMailer < ActionMailer::Base
55
- default from: 'notifications@example.com'
56
-
57
- def self.send_email
58
- basic_mail.deliver
59
- end
60
-
61
- def basic_mail
62
- mail(
63
- to: 'user@example.com',
64
- subject: 'Welcome to My Awesome Site',
65
- body: 'Banana muffin',
66
- content_type: 'text/html',
67
- )
68
- end
69
- end
70
-
71
- Dummy.routes.append do
72
- get 'ok' => 'application#ok'
73
- get 'error' => 'application#error'
74
- get 'show' => 'application#show'
75
- post 'users' => 'application#create'
76
- get 'new' => 'application#new'
77
- put 'users/:id' => 'application#update'
78
- end
79
-
80
- Dummy.initialize!
@@ -1,80 +0,0 @@
1
- require 'action_controller/railtie'
2
- require 'action_mailer/railtie'
3
-
4
- Bundler.require(*Rails.groups)
5
-
6
- STREAM = StringIO.new unless defined?(STREAM)
7
-
8
- class Dummy < Rails::Application
9
- config.eager_load = true
10
- config.filter_parameters += [:password]
11
- config.secret_key_base = '2624599ca9ab3cf3823626240138a128118a87683bf03ab8f155844c33b3cd8cbbfa3ef5e29db6f5bd182f8bd4776209d9577cfb46ac51bfd232b00ab0136b24'
12
- config.session_store :cookie_store, key: '_rails40_session'
13
-
14
- config.log_tags = [:uuid, 'TEST_TAG']
15
- config.loga = {
16
- device: STREAM,
17
- host: 'bird.example.com',
18
- service_name: 'hello_world_app',
19
- service_version: '1.0',
20
- }
21
- config.action_mailer.delivery_method = :test
22
- end
23
-
24
- class ApplicationController < ActionController::Base
25
- include Rails.application.routes.url_helpers
26
- protect_from_forgery
27
-
28
- def ok
29
- render text: 'Hello Rails'
30
- end
31
-
32
- def error
33
- nil.name
34
- end
35
-
36
- def show
37
- render json: params
38
- end
39
-
40
- def create
41
- render json: params
42
- end
43
-
44
- def new
45
- redirect_to :ok
46
- end
47
-
48
- def update
49
- @id = params[:id]
50
- render '/user'
51
- end
52
- end
53
-
54
- class FakeMailer < ActionMailer::Base
55
- default from: 'notifications@example.com'
56
-
57
- def self.send_email
58
- basic_mail.deliver
59
- end
60
-
61
- def basic_mail
62
- mail(
63
- to: 'user@example.com',
64
- subject: 'Welcome to My Awesome Site',
65
- body: 'Banana muffin',
66
- content_type: 'text/html',
67
- )
68
- end
69
- end
70
-
71
- Dummy.routes.append do
72
- get 'ok' => 'application#ok'
73
- get 'error' => 'application#error'
74
- get 'show' => 'application#show'
75
- post 'users' => 'application#create'
76
- get 'new' => 'application#new'
77
- put 'users/:id' => 'application#update'
78
- end
79
-
80
- Dummy.initialize!