loga 2.6.1 → 2.7.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 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!