loga 2.4.0 → 2.5.4

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.
data/lib/loga/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Loga
2
- VERSION = '2.4.0'.freeze
2
+ VERSION = '2.5.4'.freeze
3
3
  end
data/loga.gemspec CHANGED
@@ -22,7 +22,7 @@ Gem::Specification.new do |spec|
22
22
  spec.add_dependency 'rack'
23
23
 
24
24
  spec.add_development_dependency 'appraisal', '~> 2.2.0'
25
- spec.add_development_dependency 'bundler', '~> 1.6'
25
+ spec.add_development_dependency 'bundler', '>= 1.6'
26
26
  spec.add_development_dependency 'byebug'
27
27
  spec.add_development_dependency 'guard', '~> 2.13'
28
28
  spec.add_development_dependency 'guard-rspec', '~> 4.7.3'
@@ -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: '_rails60_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,121 @@
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['timestamp']).to be_a(Float)
68
+ expect(json_line['host']).to be_a(String)
69
+ expect(json_line['short_message']).to match(/HardWorker with jid:*/)
70
+ end
71
+ end
72
+ end
73
+
74
+ context 'when the job fails' do
75
+ let(:item_data) do
76
+ {
77
+ 'class' => 'HardWorker',
78
+ 'args' => ['asd'],
79
+ 'retry' => true,
80
+ 'queue' => 'default',
81
+ 'jid' => '591f6f66ee0d218fb451dfb6',
82
+ 'created_at' => 1_528_799_582.904939,
83
+ 'enqueued_at' => 1_528_799_582.9049861,
84
+ }
85
+ end
86
+
87
+ it 'has the the required attributes on call' do
88
+ failed_job = lambda do
89
+ job_logger.call(item_data, 'queue') do
90
+ raise StandardError
91
+ end
92
+ end
93
+
94
+ expected_body = {
95
+ 'version' => '1.1',
96
+ 'level' => 4,
97
+ '_type' => 'sidekiq',
98
+ '_created_at' => 1_528_799_582.904939,
99
+ '_enqueued_at' => 1_528_799_582.9049861,
100
+ '_jid' => '591f6f66ee0d218fb451dfb6',
101
+ '_retry' => true,
102
+ '_queue' => 'default',
103
+ '_service.name' => 'hello_world_app',
104
+ '_class' => 'HardWorker',
105
+ '_service.version' => '1.0',
106
+ '_tags' => '',
107
+ '_params' => ['asd'],
108
+ '_exception' => 'StandardError',
109
+ }
110
+
111
+ aggregate_failures do
112
+ expect(&failed_job).to raise_error(StandardError)
113
+ expect(json_line).to include(expected_body)
114
+ expect(json_line['timestamp']).to be_a(Float)
115
+ expect(json_line['host']).to be_a(String)
116
+ expect(json_line['short_message']).to match(/HardWorker with jid:*/)
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end