loga 2.8.1 → 2.9.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 +4 -4
- data/.github/workflows/build-and-test.yml +4 -4
- data/.github/workflows/publish-gem.yml +1 -1
- data/.rubocop.yml +6 -11
- data/.rubocop_todo.yml +67 -8
- data/Appraisals +73 -24
- data/CHANGELOG.md +8 -0
- data/Gemfile +10 -8
- data/README.md +1 -1
- data/gemfiles/rails60.gemfile +10 -8
- data/gemfiles/rails61.gemfile +10 -8
- data/gemfiles/rails70.gemfile +11 -8
- data/gemfiles/rails71.gemfile +11 -8
- data/gemfiles/rails72.gemfile +38 -0
- data/gemfiles/rails80.gemfile +38 -0
- data/gemfiles/sidekiq51.gemfile +11 -8
- data/gemfiles/sidekiq60.gemfile +11 -8
- data/gemfiles/sidekiq61.gemfile +11 -8
- data/gemfiles/sidekiq62.gemfile +11 -8
- data/gemfiles/sidekiq63.gemfile +11 -8
- data/gemfiles/sidekiq64.gemfile +11 -8
- data/gemfiles/sidekiq65.gemfile +11 -8
- data/gemfiles/sidekiq70.gemfile +11 -8
- data/gemfiles/sidekiq71.gemfile +11 -8
- data/gemfiles/{sidekiq7.gemfile → sidekiq72.gemfile} +12 -9
- data/gemfiles/sidekiq73.gemfile +36 -0
- data/gemfiles/sidekiq80.gemfile +36 -0
- data/gemfiles/sinatra14.gemfile +11 -8
- data/gemfiles/sinatra2.gemfile +11 -8
- data/gemfiles/sinatra3.gemfile +11 -8
- data/gemfiles/sinatra4.gemfile +11 -8
- data/gemfiles/unit.gemfile +10 -8
- data/lib/loga/parameter_filter.rb +0 -2
- data/lib/loga/rack/request.rb +2 -2
- data/lib/loga/rack/request_id.rb +2 -2
- data/lib/loga/railtie.rb +1 -1
- data/lib/loga/sidekiq.rb +11 -0
- data/lib/loga/sidekiq8/job_logger.rb +13 -0
- data/lib/loga/tagged_logging.rb +2 -2
- data/lib/loga/version.rb +1 -1
- data/spec/fixtures/rails71.rb +1 -1
- data/spec/fixtures/rails72.rb +80 -0
- data/spec/fixtures/rails80.rb +80 -0
- data/spec/integration/sidekiq8_spec.rb +193 -0
- data/spec/integration/sinatra_spec.rb +10 -3
- data/spec/loga/sidekiq_latest/job_logger_spec.rb +138 -0
- data/spec/loga/sidekiq_spec.rb +15 -3
- data/spec/spec_helper.rb +8 -2
- data/spec/support/helpers.rb +8 -0
- data/spec/support/request_spec.rb +8 -1
- data/spec/unit/loga/formatters/gelf_formatter_spec.rb +7 -1
- data/spec/unit/loga/formatters/simple_formatter_spec.rb +20 -5
- metadata +17 -9
- data/spec/loga/sidekiq7/job_logger_spec.rb +0 -127
data/gemfiles/unit.gemfile
CHANGED
@@ -4,24 +4,26 @@ source "https://rubygems.org"
|
|
4
4
|
|
5
5
|
group :development do
|
6
6
|
gem "appraisal"
|
7
|
+
gem "benchmark"
|
8
|
+
gem "bigdecimal"
|
7
9
|
gem "bundler", ">= 1.6"
|
8
10
|
gem "byebug"
|
9
|
-
gem "
|
11
|
+
gem "fakeredis"
|
10
12
|
gem "guard-rspec"
|
11
13
|
gem "guard-rubocop"
|
14
|
+
gem "guard"
|
15
|
+
gem "net-imap"
|
16
|
+
gem "net-pop"
|
17
|
+
gem "net-smtp"
|
18
|
+
gem "ostruct"
|
12
19
|
gem "pry"
|
20
|
+
gem "psych"
|
13
21
|
gem "rack-test"
|
14
22
|
gem "rake"
|
15
|
-
gem "fakeredis"
|
16
23
|
gem "rspec", "~> 3.7"
|
17
|
-
gem "rubocop"
|
18
24
|
gem "rubocop-rspec"
|
25
|
+
gem "rubocop"
|
19
26
|
gem "timecop"
|
20
|
-
gem "psych"
|
21
|
-
gem "net-smtp"
|
22
|
-
gem "net-pop"
|
23
|
-
gem "net-imap"
|
24
|
-
gem "bigdecimal"
|
25
27
|
end
|
26
28
|
|
27
29
|
group :test do
|
data/lib/loga/rack/request.rb
CHANGED
@@ -34,12 +34,12 @@ module Loga
|
|
34
34
|
env['loga.request.original_path']
|
35
35
|
end
|
36
36
|
|
37
|
-
# rubocop:disable
|
37
|
+
# rubocop:disable Layout/LineLength
|
38
38
|
def filtered_full_path
|
39
39
|
@filtered_full_path ||=
|
40
40
|
query_string.empty? ? original_path : "#{original_path}?#{filtered_query_string}"
|
41
41
|
end
|
42
|
-
# rubocop:enable
|
42
|
+
# rubocop:enable Layout/LineLength
|
43
43
|
|
44
44
|
def filtered_parameters
|
45
45
|
@filtered_parameters ||= filtered_query_hash.merge(filtered_form_hash)
|
data/lib/loga/rack/request_id.rb
CHANGED
@@ -3,7 +3,7 @@ require 'securerandom'
|
|
3
3
|
require 'active_support/core_ext/string/access'
|
4
4
|
require 'active_support/core_ext/object/blank'
|
5
5
|
|
6
|
-
# rubocop:disable Lint/AssignmentInCondition,
|
6
|
+
# rubocop:disable Lint/AssignmentInCondition, Layout/LineLength, Style/GuardClause
|
7
7
|
module Loga
|
8
8
|
module Rack
|
9
9
|
# Makes a unique request id available to the action_dispatch.request_id env variable (which is then accessible through
|
@@ -41,4 +41,4 @@ module Loga
|
|
41
41
|
end
|
42
42
|
end
|
43
43
|
end
|
44
|
-
# rubocop:enable Lint/AssignmentInCondition,
|
44
|
+
# rubocop:enable Lint/AssignmentInCondition, Layout/LineLength, Style/GuardClause
|
data/lib/loga/railtie.rb
CHANGED
@@ -125,7 +125,7 @@ module Loga
|
|
125
125
|
def silence_rails_rack_logger
|
126
126
|
case Rails::VERSION::MAJOR
|
127
127
|
when 3 then require 'loga/ext/rails/rack/logger3.rb'
|
128
|
-
when 4..
|
128
|
+
when 4..8 then require 'loga/ext/rails/rack/logger.rb'
|
129
129
|
else
|
130
130
|
raise Loga::ConfigurationError,
|
131
131
|
"Rails #{Rails::VERSION::MAJOR} is unsupported"
|
data/lib/loga/sidekiq.rb
CHANGED
@@ -12,6 +12,8 @@ module Loga
|
|
12
12
|
configure_for_sidekiq6
|
13
13
|
elsif Gem::Version.new(::Sidekiq::VERSION) < Gem::Version.new('8.0')
|
14
14
|
configure_for_sidekiq7
|
15
|
+
elsif Gem::Version.new(::Sidekiq::VERSION) < Gem::Version.new('9.0')
|
16
|
+
configure_for_sidekiq8
|
15
17
|
end
|
16
18
|
end
|
17
19
|
|
@@ -43,5 +45,14 @@ module Loga
|
|
43
45
|
config.logger = Loga.configuration.logger
|
44
46
|
end
|
45
47
|
end
|
48
|
+
|
49
|
+
def self.configure_for_sidekiq8
|
50
|
+
require 'loga/sidekiq8/job_logger'
|
51
|
+
|
52
|
+
::Sidekiq.configure_server do |config|
|
53
|
+
config[:job_logger] = Loga::Sidekiq8::JobLogger
|
54
|
+
config.logger = Loga.configuration.logger
|
55
|
+
end
|
56
|
+
end
|
46
57
|
end
|
47
58
|
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# If a future Sidekiq 8.x release introduces breaking changes that require a
|
4
|
+
# divergent implementation, replace this constant assignment with a dedicated
|
5
|
+
# class (you can copy the Sidekiq7 implementation as a starting point).
|
6
|
+
|
7
|
+
require 'loga/sidekiq7/job_logger'
|
8
|
+
|
9
|
+
module Loga
|
10
|
+
module Sidekiq8
|
11
|
+
JobLogger = Class.new(Loga::Sidekiq7::JobLogger)
|
12
|
+
end
|
13
|
+
end
|
data/lib/loga/tagged_logging.rb
CHANGED
@@ -8,7 +8,7 @@ require 'active_support/core_ext/object/blank'
|
|
8
8
|
require 'logger'
|
9
9
|
|
10
10
|
module Loga
|
11
|
-
# rubocop:disable
|
11
|
+
# rubocop:disable Layout/LineLength
|
12
12
|
# Wraps any standard Logger object to provide tagging capabilities.
|
13
13
|
#
|
14
14
|
# logger = ActiveSupport::TaggedLogging.new(Logger.new(STDOUT))
|
@@ -19,7 +19,7 @@ module Loga
|
|
19
19
|
# This is used by the default Rails.logger as configured by Railties to make
|
20
20
|
# it easy to stamp log lines with subdomains, request ids, and anything else
|
21
21
|
# to aid debugging of multi-user production applications.
|
22
|
-
# rubocop:enable
|
22
|
+
# rubocop:enable Layout/LineLength
|
23
23
|
module TaggedLogging
|
24
24
|
module Formatter # :nodoc:
|
25
25
|
def tagged(*tags)
|
data/lib/loga/version.rb
CHANGED
data/spec/fixtures/rails71.rb
CHANGED
@@ -9,7 +9,7 @@ class Dummy < Rails::Application
|
|
9
9
|
config.eager_load = true
|
10
10
|
config.filter_parameters += [:password]
|
11
11
|
config.secret_key_base = '2624599ca9ab3cf3823626240138a128118a87683bf03ab8f155844c33b3cd8cbbfa3ef5e29db6f5bd182f8bd4776209d9577cfb46ac51bfd232b00ab0136b24'
|
12
|
-
config.session_store :cookie_store, key: '
|
12
|
+
config.session_store :cookie_store, key: '_rails71_session'
|
13
13
|
|
14
14
|
config.log_tags = [:uuid, 'TEST_TAG']
|
15
15
|
config.loga = {
|
@@ -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: '_rails72_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: '_rails80_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,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::Sidekiq8::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
|
@@ -61,7 +61,7 @@ RSpec.describe 'Structured logging with Sinatra', :timecop, :with_hostname do
|
|
61
61
|
end
|
62
62
|
end
|
63
63
|
|
64
|
-
# rubocop:disable
|
64
|
+
# rubocop:disable Layout/LineLength
|
65
65
|
context 'when RACK_ENV is development', if: ENV['RACK_ENV'].eql?('development') do
|
66
66
|
let(:format) { :simple }
|
67
67
|
let(:last_log_entry) do
|
@@ -122,9 +122,16 @@ RSpec.describe 'Structured logging with Sinatra', :timecop, :with_hostname do
|
|
122
122
|
it 'logs the request with the exception' do
|
123
123
|
get '/error', {}, 'HTTP_X_REQUEST_ID' => '700a6a01'
|
124
124
|
|
125
|
-
|
125
|
+
exception_line =
|
126
|
+
if Gem::Version.new(RUBY_VERSION) > Gem::Version.new('3.4.0dev')
|
127
|
+
"E, #{time_pid_tags} GET /error 500 in 0ms type=request #{data_as_text} exception=undefined method 'name' for nil"
|
128
|
+
else
|
129
|
+
"E, #{time_pid_tags} GET /error 500 in 0ms type=request #{data_as_text} exception=undefined method `name' for nil"
|
130
|
+
end
|
131
|
+
|
132
|
+
expect(last_log_entry).to start_with(exception_line)
|
126
133
|
end
|
127
134
|
end
|
128
135
|
end
|
129
|
-
# rubocop:enable
|
136
|
+
# rubocop:enable Layout/LineLength
|
130
137
|
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
require 'loga/sidekiq7/job_logger'
|
5
|
+
require 'loga/sidekiq8/job_logger'
|
6
|
+
|
7
|
+
logger_klass =
|
8
|
+
if Gem::Version.new(Sidekiq::VERSION) >= Gem::Version.new('8.0.0')
|
9
|
+
Loga::Sidekiq8::JobLogger
|
10
|
+
elsif Gem::Version.new(Sidekiq::VERSION) >= Gem::Version.new('7.0.0')
|
11
|
+
Loga::Sidekiq7::JobLogger
|
12
|
+
else
|
13
|
+
raise 'No suitable Sidekiq JobLogger implementation found'
|
14
|
+
end
|
15
|
+
|
16
|
+
RSpec.describe logger_klass, 'unified (latest) Sidekiq JobLogger spec' do
|
17
|
+
# Sidekiq < 7.3: initializer expects a raw logger
|
18
|
+
# Sidekiq >= 7.3: initializer expects a config object responding to #logger and #[] (Sidekiq 8 keeps this)
|
19
|
+
subject(:job_logger) do
|
20
|
+
if Gem::Version.new(Sidekiq::VERSION) >= Gem::Version.new('7.3.0')
|
21
|
+
described_class.new(sidekiq_config)
|
22
|
+
else
|
23
|
+
described_class.new(logger)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
let(:target) { StringIO.new }
|
28
|
+
|
29
|
+
let(:json_line) do
|
30
|
+
target.rewind
|
31
|
+
raw = target.read
|
32
|
+
line = raw.split("\n").last
|
33
|
+
JSON.parse(line) if line
|
34
|
+
end
|
35
|
+
|
36
|
+
let(:logger) { Loga.logger }
|
37
|
+
let(:sidekiq_config) { instance_double(Sidekiq::Config, logger: logger, :[] => nil) }
|
38
|
+
|
39
|
+
before do
|
40
|
+
Loga.reset
|
41
|
+
Loga.configure(
|
42
|
+
service_name: 'hello_world_app',
|
43
|
+
service_version: '1.0',
|
44
|
+
device: target,
|
45
|
+
format: :gelf,
|
46
|
+
)
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'inherits from ::Sidekiq::JobLogger' do
|
50
|
+
expect(job_logger).to be_a(Sidekiq::JobLogger)
|
51
|
+
end
|
52
|
+
|
53
|
+
describe '#call' do
|
54
|
+
let(:item_data) do
|
55
|
+
{
|
56
|
+
'class' => 'HardWorker',
|
57
|
+
'args' => ['asd'],
|
58
|
+
'retry' => true,
|
59
|
+
'queue' => 'default',
|
60
|
+
'jid' => '591f6f66ee0d218fb451dfb6',
|
61
|
+
'created_at' => 1_528_799_582.904939,
|
62
|
+
'enqueued_at' => 1_528_799_582.9049861,
|
63
|
+
}
|
64
|
+
end
|
65
|
+
|
66
|
+
context 'when the job passes successfully' do
|
67
|
+
it 'logs the expected structured event' do
|
68
|
+
job_logger.call(item_data, 'default') do
|
69
|
+
# simulate job body
|
70
|
+
end
|
71
|
+
|
72
|
+
expected = {
|
73
|
+
'version' => '1.1',
|
74
|
+
'level' => 6, # INFO -> 6 (syslog mapping)
|
75
|
+
'_type' => 'sidekiq',
|
76
|
+
'_created_at' => item_data['created_at'],
|
77
|
+
'_enqueued_at' => item_data['enqueued_at'],
|
78
|
+
'_jid' => item_data['jid'],
|
79
|
+
'_retry' => true,
|
80
|
+
'_queue' => 'default',
|
81
|
+
'_service.name' => 'hello_world_app',
|
82
|
+
'_service.version' => '1.0',
|
83
|
+
'_class' => 'HardWorker',
|
84
|
+
'_params' => ['asd'],
|
85
|
+
'_tags' => '',
|
86
|
+
}
|
87
|
+
|
88
|
+
aggregate_failures do
|
89
|
+
expect(json_line).to include(expected)
|
90
|
+
expect(json_line['_duration']).to be_a(Float).or be_a(Integer)
|
91
|
+
expect(json_line['timestamp']).to be_a(Float)
|
92
|
+
expect(json_line['host']).to be_a(String)
|
93
|
+
expect(json_line['short_message'])
|
94
|
+
.to match(/HardWorker with jid: '591f6f66ee0d218fb451dfb6' (done|executed)/)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
context 'when the job fails' do
|
100
|
+
it 'logs the expected structured event with exception info' do
|
101
|
+
failing = lambda do
|
102
|
+
job_logger.call(item_data, 'default') do
|
103
|
+
raise StandardError, 'Boom'
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
expect(&failing).to raise_error(StandardError, 'Boom')
|
108
|
+
|
109
|
+
expected = {
|
110
|
+
'version' => '1.1',
|
111
|
+
# WARN -> syslog 4 (sidekiq job failure logged at warn)
|
112
|
+
'level' => 4,
|
113
|
+
'_type' => 'sidekiq',
|
114
|
+
'_created_at' => item_data['created_at'],
|
115
|
+
'_enqueued_at' => item_data['enqueued_at'],
|
116
|
+
'_jid' => item_data['jid'],
|
117
|
+
'_retry' => true,
|
118
|
+
'_queue' => 'default',
|
119
|
+
'_service.name' => 'hello_world_app',
|
120
|
+
'_service.version' => '1.0',
|
121
|
+
'_class' => 'HardWorker',
|
122
|
+
'_params' => ['asd'],
|
123
|
+
'_tags' => '',
|
124
|
+
'_exception' => 'Boom',
|
125
|
+
}
|
126
|
+
|
127
|
+
aggregate_failures do
|
128
|
+
expect(json_line).to include(expected)
|
129
|
+
expect(json_line['_duration']).to be_a(Float).or be_a(Integer)
|
130
|
+
expect(json_line['timestamp']).to be_a(Float)
|
131
|
+
expect(json_line['host']).to be_a(String)
|
132
|
+
expect(json_line['short_message'])
|
133
|
+
.to match(/HardWorker with jid: '591f6f66ee0d218fb451dfb6' fail/)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|