loga 2.5.2 → 2.6.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,80 @@
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: '_rails61_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 with: :null_session
27
+
28
+ def ok
29
+ render plain: '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_now
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!
@@ -0,0 +1,80 @@
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: '_rails70_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 with: :null_session
27
+
28
+ def ok
29
+ render plain: '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_now
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!
@@ -0,0 +1,143 @@
1
+ require 'spec_helper'
2
+ require 'timecop'
3
+ require 'fakeredis'
4
+
5
+ dummy_redis_config = ConnectionPool.new(size: 5) { Redis.new }
6
+
7
+ Sidekiq.configure_client do |config|
8
+ config.redis = dummy_redis_config
9
+ end
10
+
11
+ Sidekiq.configure_server do |config|
12
+ config.redis = dummy_redis_config
13
+ end
14
+
15
+ class MySidekiqWorker
16
+ include Sidekiq::Worker
17
+
18
+ def perform(_name); end
19
+ end
20
+
21
+ describe 'Sidekiq client logger' do
22
+ let(:mgr) do
23
+ Class.new do
24
+ attr_reader :latest_error, :mutex, :cond
25
+
26
+ def initialize
27
+ @mutex = ::Mutex.new
28
+ @cond = ::ConditionVariable.new
29
+ end
30
+
31
+ def processor_died(_inst, err)
32
+ @latest_error = err
33
+
34
+ @mutex.synchronize { @cond.signal }
35
+ end
36
+
37
+ def processor_stopped(_inst)
38
+ @mutex.synchronize { @cond.signal }
39
+ end
40
+
41
+ def options
42
+ {
43
+ concurrency: 3,
44
+ queues: ['default'],
45
+ job_logger: Loga::Sidekiq5::JobLogger,
46
+ }
47
+ end
48
+ end
49
+ end
50
+
51
+ let(:target) { StringIO.new }
52
+
53
+ def read_json_log(line:)
54
+ target.rewind
55
+ JSON.parse(target.each_line.drop(line - 1).first)
56
+ end
57
+
58
+ before do
59
+ Redis.current.flushall
60
+
61
+ Loga.reset
62
+
63
+ Loga.configure(
64
+ service_name: 'hello_world_app',
65
+ service_version: '1.0',
66
+ device: target,
67
+ format: :gelf,
68
+ )
69
+ end
70
+
71
+ it 'has the proper job logger' do
72
+ expect(Sidekiq.options[:job_logger]).to eq Loga::Sidekiq5::JobLogger
73
+ end
74
+
75
+ it 'has the proper logger for Sidekiq.logger' do
76
+ expect(Sidekiq.logger).to eq Loga.logger
77
+ end
78
+
79
+ it 'pushes a new element in the default queue' do
80
+ MySidekiqWorker.perform_async('Bob')
81
+
82
+ last_element = JSON.parse(Redis.current.lpop('queue:default'))
83
+
84
+ aggregate_failures do
85
+ expect(last_element['class']).to eq 'MySidekiqWorker'
86
+ expect(last_element['args']).to eq ['Bob']
87
+ expect(last_element['retry']).to eq true
88
+ expect(last_element['queue']).to eq 'default'
89
+ end
90
+ end
91
+
92
+ it 'has the proper logger Sidekiq::Logging.logger' do
93
+ expect(Sidekiq::Logging.logger).to eq Loga.logger
94
+ end
95
+
96
+ # https://github.com/mperham/sidekiq/blob/97363210b47a4f8a1d8c1233aaa059d6643f5040/test/test_actors.rb#L57-L79
97
+
98
+ it 'logs the job processing event' do
99
+ MySidekiqWorker.perform_async('Bob')
100
+
101
+ require 'sidekiq/processor'
102
+
103
+ Sidekiq::Processor.new(mgr.new).start
104
+ sleep 0.5
105
+
106
+ json_line = read_json_log(line: 1)
107
+
108
+ aggregate_failures do
109
+ expect(json_line).to include(
110
+ '_queue'=> 'default',
111
+ '_retry'=> true,
112
+ '_params'=> ['Bob'],
113
+ '_class'=> 'MySidekiqWorker',
114
+ '_type'=> 'sidekiq',
115
+ '_service.name'=> 'hello_world_app',
116
+ '_service.version'=> '1.0',
117
+ '_tags'=> '',
118
+ 'level'=> 6,
119
+ 'version'=> '1.1',
120
+ )
121
+
122
+ %w[_created_at _enqueued_at _jid _duration timestamp host].each do |key|
123
+ expect(json_line).to have_key(key)
124
+ end
125
+
126
+ expect(json_line['short_message']).to match(/MySidekiqWorker with jid:*/)
127
+ end
128
+
129
+ # This was a bug - the duration was constantly incresing based on when
130
+ # the logger was created. https://github.com/FundingCircle/loga/pull/117
131
+ #
132
+ # Test that after sleeping for few seconds the duration is still under 500ms
133
+ sleep 1
134
+
135
+ MySidekiqWorker.perform_async('Bob')
136
+
137
+ sleep 1
138
+
139
+ json_line = read_json_log(line: 2)
140
+
141
+ expect(json_line['_duration']).to be < 500
142
+ end
143
+ end
@@ -0,0 +1,164 @@
1
+ require 'spec_helper'
2
+ require 'timecop'
3
+ require 'fakeredis'
4
+
5
+ dummy_redis_config = ConnectionPool.new(size: 5) { Redis.new }
6
+
7
+ Sidekiq.configure_client do |config|
8
+ config.redis = dummy_redis_config
9
+ end
10
+
11
+ Sidekiq.configure_server do |config|
12
+ config.redis = dummy_redis_config
13
+ end
14
+
15
+ class MySidekiqWorker
16
+ include Sidekiq::Worker
17
+
18
+ def perform(_name)
19
+ logger.info('Hello from MySidekiqWorker')
20
+ end
21
+ end
22
+
23
+ describe 'Sidekiq client logger' do
24
+ let(:mgr) do
25
+ # https://github.com/mperham/sidekiq/blob/v6.1.2/test/test_actors.rb#L58-L82
26
+ Class.new do
27
+ attr_reader :latest_error, :mutex, :cond
28
+
29
+ def initialize
30
+ @mutex = ::Mutex.new
31
+ @cond = ::ConditionVariable.new
32
+ end
33
+
34
+ def processor_died(_inst, err)
35
+ @latest_error = err
36
+
37
+ @mutex.synchronize { @cond.signal }
38
+ end
39
+
40
+ def processor_stopped(_inst)
41
+ @mutex.synchronize { @cond.signal }
42
+ end
43
+
44
+ def options
45
+ {
46
+ concurrency: 3,
47
+ queues: ['default'],
48
+ job_logger: Loga::Sidekiq6::JobLogger,
49
+ }.tap { |opts| opts[:fetch] = ::Sidekiq::BasicFetch.new(opts) }
50
+ end
51
+ end
52
+ end
53
+
54
+ let(:target) { StringIO.new }
55
+
56
+ def read_json_log(line:)
57
+ target.rewind
58
+ JSON.parse(target.each_line.drop(line - 1).first)
59
+ end
60
+
61
+ before do
62
+ Redis.current.flushall
63
+
64
+ Loga.reset
65
+
66
+ Loga.configure(
67
+ service_name: 'hello_world_app',
68
+ service_version: '1.0',
69
+ device: target,
70
+ format: :gelf,
71
+ )
72
+ end
73
+
74
+ it 'has the proper job logger' do
75
+ expect(Sidekiq.options[:job_logger]).to eq Loga::Sidekiq6::JobLogger
76
+ end
77
+
78
+ it 'has the proper logger for Sidekiq.logger' do
79
+ expect(Sidekiq.logger).to eq Loga.logger
80
+ end
81
+
82
+ it 'pushes a new element in the default queue' do
83
+ MySidekiqWorker.perform_async('Bob')
84
+
85
+ last_element = JSON.parse(Redis.current.lpop('queue:default'))
86
+
87
+ aggregate_failures do
88
+ expect(last_element['class']).to eq 'MySidekiqWorker'
89
+ expect(last_element['args']).to eq ['Bob']
90
+ expect(last_element['retry']).to eq true
91
+ expect(last_element['queue']).to eq 'default'
92
+ end
93
+ end
94
+
95
+ def test_log_from_worker(json_line)
96
+ aggregate_failures do
97
+ expect(json_line).to include(
98
+ '_class' => 'MySidekiqWorker',
99
+ '_service.name' => 'hello_world_app',
100
+ '_service.version' => '1.0',
101
+ '_tags' => '',
102
+ 'level' => 6,
103
+ 'version' => '1.1',
104
+ 'short_message' => 'Hello from MySidekiqWorker',
105
+ )
106
+
107
+ %w[_jid timestamp host].each do |key|
108
+ expect(json_line).to have_key(key)
109
+ end
110
+
111
+ expect(json_line).not_to include('_duration')
112
+ end
113
+ end
114
+
115
+ def test_job_end_log(json_line) # rubocop:disable Metrics/MethodLength
116
+ aggregate_failures do
117
+ expect(json_line).to include(
118
+ '_queue' => 'default',
119
+ '_retry' => true,
120
+ '_params' => ['Bob'],
121
+ '_class' => 'MySidekiqWorker',
122
+ '_type' => 'sidekiq',
123
+ '_service.name' => 'hello_world_app',
124
+ '_service.version' => '1.0',
125
+ '_tags' => '',
126
+ 'level' => 6,
127
+ 'version' => '1.1',
128
+ )
129
+
130
+ %w[_created_at _enqueued_at _jid _duration timestamp host].each do |key|
131
+ expect(json_line).to have_key(key)
132
+ end
133
+
134
+ expect(json_line['_duration']).to be < 500
135
+ expect(json_line['short_message']).to match(/MySidekiqWorker with jid:*/)
136
+ end
137
+ end
138
+
139
+ it 'logs the job processing event' do
140
+ MySidekiqWorker.perform_async('Bob')
141
+
142
+ require 'sidekiq/processor'
143
+
144
+ sidekiq_manager = mgr.new
145
+ Sidekiq::Processor.new(sidekiq_manager, sidekiq_manager.options).start
146
+ sleep 0.5
147
+
148
+ test_log_from_worker(read_json_log(line: 1))
149
+ test_job_end_log(read_json_log(line: 2))
150
+
151
+ # This was a bug - the duration was constantly incresing based on when
152
+ # the logger was created. https://github.com/FundingCircle/loga/pull/117
153
+ #
154
+ # Test that after sleeping for few seconds the duration is still under 500ms
155
+ sleep 1
156
+
157
+ MySidekiqWorker.perform_async('Bob')
158
+
159
+ sleep 1
160
+
161
+ test_log_from_worker(read_json_log(line: 3))
162
+ test_job_end_log(read_json_log(line: 4))
163
+ end
164
+ end
@@ -68,7 +68,6 @@ RSpec.describe 'Structured logging with Sinatra', :with_hostname, :timecop do
68
68
  end
69
69
  let(:data) do
70
70
  {
71
- 'status' => 200,
72
71
  'method' => 'GET',
73
72
  'path' => '/ok',
74
73
  'params' => { 'username'=>'yoshi' },
@@ -76,6 +75,7 @@ RSpec.describe 'Structured logging with Sinatra', :with_hostname, :timecop do
76
75
  'request_ip' => '127.0.0.1',
77
76
  'user_agent' => nil,
78
77
  'duration' => 0,
78
+ 'status' => 200,
79
79
  }
80
80
  end
81
81
  let(:data_as_text) { "data=#{{ request: data }.inspect}" }
@@ -1,6 +1,7 @@
1
1
  require 'spec_helper'
2
+ require 'loga/sidekiq5/job_logger'
2
3
 
3
- RSpec.describe Loga::Sidekiq::JobLogger do
4
+ RSpec.describe Loga::Sidekiq5::JobLogger do
4
5
  subject(:job_logger) { described_class.new }
5
6
 
6
7
  let(:target) { StringIO.new }
@@ -0,0 +1,123 @@
1
+ require 'spec_helper'
2
+ require 'loga/sidekiq6/job_logger'
3
+
4
+ RSpec.describe Loga::Sidekiq6::JobLogger do
5
+ subject(:job_logger) { described_class.new }
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
+ before do
15
+ Loga.reset
16
+
17
+ Loga.configure(
18
+ service_name: 'hello_world_app',
19
+ service_version: '1.0',
20
+ device: target,
21
+ format: :gelf,
22
+ )
23
+ end
24
+
25
+ # https://github.com/mperham/sidekiq/blob/v6.1.2/lib/sidekiq/job_logger.rb
26
+ it 'inherits from ::Sidekiq::JobLogger' do
27
+ expect(subject).to be_a(::Sidekiq::JobLogger)
28
+ end
29
+
30
+ describe '#call' do
31
+ context 'when the job passess successfully' do
32
+ let(:item_data) do
33
+ {
34
+ 'class' => 'HardWorker',
35
+ 'args' => ['asd'],
36
+ 'retry' => true,
37
+ 'queue' => 'default',
38
+ 'jid' => '591f6f66ee0d218fb451dfb6',
39
+ 'created_at' => 1_528_799_582.904939,
40
+ 'enqueued_at' => 1_528_799_582.9049861,
41
+ }
42
+ end
43
+
44
+ it 'has the the required attributes on call' do
45
+ job_logger.call(item_data, 'queue') do
46
+ # something
47
+ end
48
+
49
+ expected_body = {
50
+ 'version' => '1.1',
51
+ 'level' => 6,
52
+ '_type' => 'sidekiq',
53
+ '_created_at' => 1_528_799_582.904939,
54
+ '_enqueued_at' => 1_528_799_582.9049861,
55
+ '_jid' => '591f6f66ee0d218fb451dfb6',
56
+ '_retry' => true,
57
+ '_queue' => 'default',
58
+ '_service.name' => 'hello_world_app',
59
+ '_class' => 'HardWorker',
60
+ '_service.version' => '1.0',
61
+ '_tags' => '',
62
+ '_params' => ['asd'],
63
+ }
64
+
65
+ aggregate_failures do
66
+ expect(json_line).to include(expected_body)
67
+ expect(json_line['_duration']).to be_a(Float)
68
+ expect(json_line['timestamp']).to be_a(Float)
69
+ expect(json_line['host']).to be_a(String)
70
+ expect(json_line['short_message']).to match(/HardWorker with jid:*/)
71
+ end
72
+ end
73
+ end
74
+
75
+ context 'when the job fails' do
76
+ let(:item_data) do
77
+ {
78
+ 'class' => 'HardWorker',
79
+ 'args' => ['asd'],
80
+ 'retry' => true,
81
+ 'queue' => 'default',
82
+ 'jid' => '591f6f66ee0d218fb451dfb6',
83
+ 'created_at' => 1_528_799_582.904939,
84
+ 'enqueued_at' => 1_528_799_582.9049861,
85
+ }
86
+ end
87
+
88
+ it 'has the the required attributes on call' do
89
+ failed_job = lambda do
90
+ job_logger.call(item_data, 'queue') do
91
+ raise StandardError
92
+ end
93
+ end
94
+
95
+ expected_body = {
96
+ 'version' => '1.1',
97
+ 'level' => 4,
98
+ '_type' => 'sidekiq',
99
+ '_created_at' => 1_528_799_582.904939,
100
+ '_enqueued_at' => 1_528_799_582.9049861,
101
+ '_jid' => '591f6f66ee0d218fb451dfb6',
102
+ '_retry' => true,
103
+ '_queue' => 'default',
104
+ '_service.name' => 'hello_world_app',
105
+ '_class' => 'HardWorker',
106
+ '_service.version' => '1.0',
107
+ '_tags' => '',
108
+ '_params' => ['asd'],
109
+ '_exception' => 'StandardError',
110
+ }
111
+
112
+ aggregate_failures do
113
+ expect(&failed_job).to raise_error(StandardError)
114
+ expect(json_line['_duration']).to be_a(Float)
115
+ expect(json_line).to include(expected_body)
116
+ expect(json_line['timestamp']).to be_a(Float)
117
+ expect(json_line['host']).to be_a(String)
118
+ expect(json_line['short_message']).to match(/HardWorker with jid:*/)
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
@@ -2,7 +2,7 @@ require 'spec_helper'
2
2
 
3
3
  RSpec.describe Loga::Sidekiq do
4
4
  describe '.configure_logging' do
5
- context 'when sidekiq version is 5.1' do
5
+ context 'when sidekiq is defined' do
6
6
  it 'gets invoked on Loga.configure' do
7
7
  allow(described_class).to receive(:configure_logging)
8
8
 
@@ -18,7 +18,7 @@ RSpec.describe Loga::Sidekiq do
18
18
  expect(described_class).to have_received(:configure_logging)
19
19
  end
20
20
 
21
- it 'assigns our custom sidekiq job logger' do
21
+ it 'assigns our custom sidekiq job logger depending on the sidekiq version' do
22
22
  Loga.reset
23
23
 
24
24
  Loga.configure(
@@ -28,7 +28,14 @@ RSpec.describe Loga::Sidekiq do
28
28
  format: :gelf,
29
29
  )
30
30
 
31
- expect(::Sidekiq.options[:job_logger]).to eq(Loga::Sidekiq::JobLogger)
31
+ m = ENV['BUNDLE_GEMFILE'].match(/sidekiq(?<version>\d+)/)
32
+
33
+ case m['version']
34
+ when '51'
35
+ expect(::Sidekiq.options[:job_logger]).to eq(Loga::Sidekiq5::JobLogger)
36
+ when '6'
37
+ expect(::Sidekiq.options[:job_logger]).to eq(Loga::Sidekiq6::JobLogger)
38
+ end
32
39
  end
33
40
  end
34
41
 
data/spec/spec_helper.rb CHANGED
@@ -29,14 +29,23 @@ when /sinatra/
29
29
  when /unit/
30
30
  rspec_pattern = 'unit/**/*_spec.rb'
31
31
  require 'loga'
32
- when /sidekiq/
33
- sidekiq_specs = [
34
- 'integration/sidekiq_spec.rb',
35
- 'spec/loga/sidekiq/**/*_spec.rb',
36
- 'spec/loga/sidekiq_spec.rb',
37
- ]
38
-
39
- rspec_pattern = sidekiq_specs.join(',')
32
+ when /sidekiq(?<version>\d+)/
33
+ case $LAST_MATCH_INFO['version']
34
+ when '51'
35
+ rspec_pattern = [
36
+ 'spec/integration/sidekiq5_spec.rb',
37
+ 'spec/loga/sidekiq5/**/*_spec.rb',
38
+ 'spec/loga/sidekiq_spec.rb',
39
+ ].join(',')
40
+ when '6', '61'
41
+ rspec_pattern = [
42
+ 'spec/integration/sidekiq6_spec.rb',
43
+ 'spec/loga/sidekiq6/**/*_spec.rb',
44
+ 'spec/loga/sidekiq_spec.rb',
45
+ ].join(',')
46
+ else
47
+ raise 'FIXME: Unknown sidekiq - update this file.'
48
+ end
40
49
 
41
50
  require 'sidekiq'
42
51
  require 'sidekiq/cli'