diff options
Diffstat (limited to 'spec/lib/gitlab/sidekiq_logging')
-rw-r--r-- | spec/lib/gitlab/sidekiq_logging/exception_handler_spec.rb | 44 | ||||
-rw-r--r-- | spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb | 178 |
2 files changed, 152 insertions, 70 deletions
diff --git a/spec/lib/gitlab/sidekiq_logging/exception_handler_spec.rb b/spec/lib/gitlab/sidekiq_logging/exception_handler_spec.rb deleted file mode 100644 index 94dcf6f9b9a..00000000000 --- a/spec/lib/gitlab/sidekiq_logging/exception_handler_spec.rb +++ /dev/null @@ -1,44 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Gitlab::SidekiqLogging::ExceptionHandler do - describe '#call' do - let(:job) do - { - "class" => "TestWorker", - "args" => [1234, 'hello'], - "retry" => false, - "queue" => "cronjob:test_queue", - "queue_namespace" => "cronjob", - "jid" => "da883554ee4fe414012f5f42", - "correlation_id" => 'cid' - } - end - - let(:exception_message) { 'An error was thrown' } - let(:backtrace) { caller } - let(:exception) { RuntimeError.new(exception_message) } - let(:logger) { double } - - before do - allow(Sidekiq).to receive(:logger).and_return(logger) - allow(exception).to receive(:backtrace).and_return(backtrace) - end - - subject { described_class.new.call(exception, { context: 'Test', job: job }) } - - it 'logs job data into root tree' do - expected_data = job.merge( - error_class: 'RuntimeError', - error_message: exception_message, - context: 'Test', - error_backtrace: Rails.backtrace_cleaner.clean(backtrace) - ) - - expect(logger).to receive(:warn).with(expected_data) - - subject - end - end -end diff --git a/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb b/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb index b99a5352717..3e8e117ec71 100644 --- a/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb +++ b/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb @@ -3,7 +3,13 @@ require 'spec_helper' RSpec.describe Gitlab::SidekiqLogging::StructuredLogger do - describe '#call' do + before do + # We disable a memory instrumentation feature + # as this requires a special patched Ruby + allow(Gitlab::Memory::Instrumentation).to receive(:available?) { false } + end + + describe '#call', :request_store do let(:timestamp) { Time.iso8601('2018-01-01T12:00:00.000Z') } let(:created_at) { timestamp - 1.second } let(:scheduling_latency_s) { 1.0 } @@ -21,14 +27,13 @@ RSpec.describe Gitlab::SidekiqLogging::StructuredLogger do "correlation_id" => 'cid', "error_message" => "wrong number of arguments (2 for 3)", "error_class" => "ArgumentError", - "error_backtrace" => [], - "db_count" => 1, - "db_write_count" => 0, - "db_cached_count" => 0 + "error_backtrace" => [] } end let(:logger) { double } + let(:clock_realtime_start) { 0.222222299 } + let(:clock_realtime_end) { 1.333333799 } let(:clock_thread_cputime_start) { 0.222222299 } let(:clock_thread_cputime_end) { 1.333333799 } let(:start_payload) do @@ -38,7 +43,8 @@ RSpec.describe Gitlab::SidekiqLogging::StructuredLogger do 'pid' => Process.pid, 'created_at' => created_at.to_f, 'enqueued_at' => created_at.to_f, - 'scheduling_latency_s' => scheduling_latency_s + 'scheduling_latency_s' => scheduling_latency_s, + 'job_size_bytes' => be > 0 ) end @@ -49,7 +55,10 @@ RSpec.describe Gitlab::SidekiqLogging::StructuredLogger do 'duration_s' => 0.0, 'completed_at' => timestamp.to_f, 'cpu_s' => 1.111112, - 'db_duration_s' => 0.0 + 'db_duration_s' => 0.0, + 'db_cached_count' => 0, + 'db_count' => 0, + 'db_write_count' => 0 ) end @@ -58,7 +67,8 @@ RSpec.describe Gitlab::SidekiqLogging::StructuredLogger do 'message' => 'TestWorker JID-da883554ee4fe414012f5f42: fail: 0.0 sec', 'job_status' => 'fail', 'error_class' => 'ArgumentError', - 'error_message' => 'some exception' + 'error_message' => 'Something went wrong', + 'error_backtrace' => be_a(Array).and(be_present) ) end @@ -67,7 +77,10 @@ RSpec.describe Gitlab::SidekiqLogging::StructuredLogger do allow(subject).to receive(:current_time).and_return(timestamp.to_f) - allow(Process).to receive(:clock_gettime).with(Process::CLOCK_THREAD_CPUTIME_ID).and_return(clock_thread_cputime_start, clock_thread_cputime_end) + allow(Process).to receive(:clock_gettime).with(Process::CLOCK_REALTIME, :float_second) + .and_return(clock_realtime_start, clock_realtime_end) + allow(Process).to receive(:clock_gettime).with(Process::CLOCK_THREAD_CPUTIME_ID, :float_second) + .and_return(clock_thread_cputime_start, clock_thread_cputime_end) end subject { described_class.new } @@ -84,25 +97,97 @@ RSpec.describe Gitlab::SidekiqLogging::StructuredLogger do expect(subject).to receive(:log_job_start).and_call_original expect(subject).to receive(:log_job_done).and_call_original - subject.call(job, 'test_queue') { } + call_subject(job, 'test_queue') { } + end + end + + it 'logs real job wrapped by active job worker' do + wrapped_job = job.merge( + "class" => "ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper", + "wrapped" => "TestWorker" + ) + + Timecop.freeze(timestamp) do + expect(logger).to receive(:info).with(start_payload).ordered + expect(logger).to receive(:info).with(end_payload).ordered + expect(subject).to receive(:log_job_start).and_call_original + expect(subject).to receive(:log_job_done).and_call_original + + call_subject(wrapped_job, 'test_queue') { } end end it 'logs an exception in job' do Timecop.freeze(timestamp) do expect(logger).to receive(:info).with(start_payload) - expect(logger).to receive(:warn).with(hash_including(exception_payload)) + expect(logger).to receive(:warn).with(include(exception_payload)) expect(subject).to receive(:log_job_start).and_call_original expect(subject).to receive(:log_job_done).and_call_original expect do - subject.call(job, 'test_queue') do - raise ArgumentError, 'some exception' + call_subject(job, 'test_queue') do + raise ArgumentError, 'Something went wrong' end end.to raise_error(ArgumentError) end end + it 'logs the root cause of an Sidekiq::JobRetry::Skip exception in the job' do + Timecop.freeze(timestamp) do + expect(logger).to receive(:info).with(start_payload) + expect(logger).to receive(:warn).with(include(exception_payload)) + expect(subject).to receive(:log_job_start).and_call_original + expect(subject).to receive(:log_job_done).and_call_original + + expect do + call_subject(job, 'test_queue') do + raise ArgumentError, 'Something went wrong' + rescue + raise Sidekiq::JobRetry::Skip + end + end.to raise_error(Sidekiq::JobRetry::Skip) + end + end + + it 'logs the root cause of an Sidekiq::JobRetry::Handled exception in the job' do + Timecop.freeze(timestamp) do + expect(logger).to receive(:info).with(start_payload) + expect(logger).to receive(:warn).with(include(exception_payload)) + expect(subject).to receive(:log_job_start).and_call_original + expect(subject).to receive(:log_job_done).and_call_original + + expect do + call_subject(job, 'test_queue') do + raise ArgumentError, 'Something went wrong' + rescue + raise Sidekiq::JobRetry::Handled + end + end.to raise_error(Sidekiq::JobRetry::Handled) + end + end + + it 'keeps Sidekiq::JobRetry::Handled exception if the cause does not exist' do + Timecop.freeze(timestamp) do + expect(logger).to receive(:info).with(start_payload) + expect(logger).to receive(:warn).with( + include( + 'message' => 'TestWorker JID-da883554ee4fe414012f5f42: fail: 0.0 sec', + 'job_status' => 'fail', + 'error_class' => 'Sidekiq::JobRetry::Skip', + 'error_message' => 'Sidekiq::JobRetry::Skip' + ) + ) + expect(subject).to receive(:log_job_start).and_call_original + expect(subject).to receive(:log_job_done).and_call_original + + expect do + call_subject(job, 'test_queue') do + raise Sidekiq::JobRetry::Skip + end + end.to raise_error(Sidekiq::JobRetry::Skip) + end + end + it 'does not modify the job' do Timecop.freeze(timestamp) do job_copy = job.deep_dup @@ -111,11 +196,29 @@ RSpec.describe Gitlab::SidekiqLogging::StructuredLogger do allow(subject).to receive(:log_job_start).and_call_original allow(subject).to receive(:log_job_done).and_call_original - subject.call(job, 'test_queue') do + call_subject(job, 'test_queue') do expect(job).to eq(job_copy) end end end + + it 'does not modify the wrapped job' do + Timecop.freeze(timestamp) do + wrapped_job = job.merge( + "class" => "ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper", + "wrapped" => "TestWorker" + ) + job_copy = wrapped_job.deep_dup + + allow(logger).to receive(:info) + allow(subject).to receive(:log_job_start).and_call_original + allow(subject).to receive(:log_job_done).and_call_original + + call_subject(wrapped_job, 'test_queue') do + expect(wrapped_job).to eq(job_copy) + end + end + end end context 'with SIDEKIQ_LOG_ARGUMENTS disabled' do @@ -130,7 +233,7 @@ RSpec.describe Gitlab::SidekiqLogging::StructuredLogger do expect(subject).to receive(:log_job_start).and_call_original expect(subject).to receive(:log_job_done).and_call_original - subject.call(job, 'test_queue') { } + call_subject(job, 'test_queue') { } end end @@ -143,7 +246,7 @@ RSpec.describe Gitlab::SidekiqLogging::StructuredLogger do expect(subject).to receive(:log_job_start).and_call_original expect(subject).to receive(:log_job_done).and_call_original - subject.call(job.except("created_at", "enqueued_at"), 'test_queue') { } + call_subject(job.except("created_at", "enqueued_at"), 'test_queue') { } end end end @@ -159,7 +262,7 @@ RSpec.describe Gitlab::SidekiqLogging::StructuredLogger do expect(subject).to receive(:log_job_start).and_call_original expect(subject).to receive(:log_job_done).and_call_original - subject.call(job, 'test_queue') { } + call_subject(job, 'test_queue') { } end end end @@ -177,7 +280,7 @@ RSpec.describe Gitlab::SidekiqLogging::StructuredLogger do end let(:expected_end_payload) do - end_payload.merge(timing_data) + end_payload.merge(timing_data.stringify_keys) end it 'logs with Gitaly and Rugged timing data' do @@ -185,7 +288,7 @@ RSpec.describe Gitlab::SidekiqLogging::StructuredLogger do expect(logger).to receive(:info).with(start_payload).ordered expect(logger).to receive(:info).with(expected_end_payload).ordered - subject.call(job, 'test_queue') do + call_subject(job, 'test_queue') do job.merge!(timing_data) end end @@ -207,7 +310,7 @@ RSpec.describe Gitlab::SidekiqLogging::StructuredLogger do let(:expected_end_payload_with_db) do expected_end_payload.merge( 'db_duration_s' => a_value >= 0.1, - 'db_count' => 1, + 'db_count' => a_value >= 1, 'db_cached_count' => 0, 'db_write_count' => 0 ) @@ -217,7 +320,9 @@ RSpec.describe Gitlab::SidekiqLogging::StructuredLogger do expect(logger).to receive(:info).with(expected_start_payload).ordered expect(logger).to receive(:info).with(expected_end_payload_with_db).ordered - subject.call(job, 'test_queue') { ActiveRecord::Base.connection.execute('SELECT pg_sleep(0.1);') } + call_subject(job, 'test_queue') do + ActiveRecord::Base.connection.execute('SELECT pg_sleep(0.1);') + end end it 'prevents database time from leaking to the next job' do @@ -226,8 +331,13 @@ RSpec.describe Gitlab::SidekiqLogging::StructuredLogger do expect(logger).to receive(:info).with(expected_start_payload).ordered expect(logger).to receive(:info).with(expected_end_payload).ordered - subject.call(job, 'test_queue') { ActiveRecord::Base.connection.execute('SELECT pg_sleep(0.1);') } - subject.call(job, 'test_queue') { } + call_subject(job.dup, 'test_queue') do + ActiveRecord::Base.connection.execute('SELECT pg_sleep(0.1);') + end + + Gitlab::SafeRequestStore.clear! + + call_subject(job.dup, 'test_queue') { } end end @@ -243,7 +353,7 @@ RSpec.describe Gitlab::SidekiqLogging::StructuredLogger do expect(logger).to receive(:info).with(expected_start_payload).ordered expect(logger).to receive(:info).with(expected_end_payload).ordered - subject.call(job, 'test_queue') do + call_subject(job, 'test_queue') do job["#{ApplicationWorker::LOGGING_EXTRA_KEY}.key1"] = 15 job["#{ApplicationWorker::LOGGING_EXTRA_KEY}.key2"] = 16 job['key that will be ignored because it does not start with extra.'] = 17 @@ -251,13 +361,29 @@ RSpec.describe Gitlab::SidekiqLogging::StructuredLogger do end end end + + def call_subject(job, queue) + # This structured logger strongly depends on execution of `InstrumentationLogger` + subject.call(job, queue) do + ::Gitlab::SidekiqMiddleware::InstrumentationLogger.new.call('worker', job, queue) do + yield + end + end + end end describe '#add_time_keys!' do - let(:time) { { duration: 0.1231234, cputime: 1.2342345 } } + let(:time) { { duration: 0.1231234 } } let(:payload) { { 'class' => 'my-class', 'message' => 'my-message', 'job_status' => 'my-job-status' } } let(:current_utc_time) { Time.now.utc } - let(:payload_with_time_keys) { { 'class' => 'my-class', 'message' => 'my-message', 'job_status' => 'my-job-status', 'duration_s' => 0.123123, 'cpu_s' => 1.234235, 'completed_at' => current_utc_time.to_f } } + + let(:payload_with_time_keys) do + { 'class' => 'my-class', + 'message' => 'my-message', + 'job_status' => 'my-job-status', + 'duration_s' => 0.123123, + 'completed_at' => current_utc_time.to_f } + end subject { described_class.new } |