Commit 6c49e80c authored by Sean McGivern's avatar Sean McGivern

Merge branch 'sh-handle-exceptions-sidekiq' into 'master'

Log Sidekiq exceptions properly in JSON format

Closes #29425

See merge request gitlab-org/gitlab!17412
parents 7fe17542 a27d4616
---
title: Log Sidekiq exceptions properly in JSON format
merge_request: 17412
author:
type: fixed
...@@ -50,6 +50,10 @@ Sidekiq.configure_server do |config| ...@@ -50,6 +50,10 @@ Sidekiq.configure_server do |config|
if enable_json_logs if enable_json_logs
Sidekiq.logger.formatter = Gitlab::SidekiqLogging::JSONFormatter.new Sidekiq.logger.formatter = Gitlab::SidekiqLogging::JSONFormatter.new
config.options[:job_logger] = Gitlab::SidekiqLogging::StructuredLogger config.options[:job_logger] = Gitlab::SidekiqLogging::StructuredLogger
# Remove the default-provided handler
config.error_handlers.reject! { |handler| handler.is_a?(Sidekiq::ExceptionHandler::Logger) }
config.error_handlers << Gitlab::SidekiqLogging::ExceptionHandler.new
end end
config.client_middleware do |chain| config.client_middleware do |chain|
......
# frozen_string_literal: true
module Gitlab
module SidekiqLogging
class ExceptionHandler
def call(job_exception, context)
data = {
error_class: job_exception.class.name,
error_message: job_exception.message
}
if context.is_a?(Hash)
data.merge!(context)
# correlation_id, jid, and class are available inside the job
# Hash, so promote these arguments to the root tree so that
# can be searched alongside other Sidekiq log messages.
job_data = data.delete(:job)
data.merge!(job_data) if job_data.present?
end
data[:error_backtrace] = Gitlab::Profiler.clean_backtrace(job_exception.backtrace) if job_exception.backtrace.present?
Sidekiq.logger.warn(data)
end
end
end
end
...@@ -58,8 +58,7 @@ module Gitlab ...@@ -58,8 +58,7 @@ module Gitlab
payload['message'] = "#{message}: fail: #{payload['duration']} sec" payload['message'] = "#{message}: fail: #{payload['duration']} sec"
payload['job_status'] = 'fail' payload['job_status'] = 'fail'
payload['error_message'] = job_exception.message payload['error_message'] = job_exception.message
payload['error'] = job_exception.class payload['error_class'] = job_exception.class.name
payload['error_backtrace'] = backtrace_cleaner.clean(job_exception.backtrace)
else else
payload['message'] = "#{message}: done: #{payload['duration']} sec" payload['message'] = "#{message}: done: #{payload['duration']} sec"
payload['job_status'] = 'done' payload['job_status'] = 'done'
...@@ -127,10 +126,6 @@ module Gitlab ...@@ -127,10 +126,6 @@ module Gitlab
Gitlab::Metrics::System.monotonic_time Gitlab::Metrics::System.monotonic_time
end end
def backtrace_cleaner
@backtrace_cleaner ||= ActiveSupport::BacktraceCleaner.new
end
def format_time(timestamp) def format_time(timestamp)
return timestamp if timestamp.is_a?(String) return timestamp if timestamp.is_a?(String)
......
# frozen_string_literal: true
require 'spec_helper'
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: Gitlab::Profiler.clean_backtrace(backtrace)
)
expect(logger).to receive(:warn).with(expected_data)
subject
end
end
end
...@@ -47,7 +47,7 @@ describe Gitlab::SidekiqLogging::StructuredLogger do ...@@ -47,7 +47,7 @@ describe Gitlab::SidekiqLogging::StructuredLogger do
end_payload.merge( end_payload.merge(
'message' => 'TestWorker JID-da883554ee4fe414012f5f42: fail: 0.0 sec', 'message' => 'TestWorker JID-da883554ee4fe414012f5f42: fail: 0.0 sec',
'job_status' => 'fail', 'job_status' => 'fail',
'error' => ArgumentError, 'error_class' => 'ArgumentError',
'error_message' => 'some exception' 'error_message' => 'some exception'
) )
end end
...@@ -86,7 +86,6 @@ describe Gitlab::SidekiqLogging::StructuredLogger do ...@@ -86,7 +86,6 @@ describe Gitlab::SidekiqLogging::StructuredLogger do
it 'logs an exception in job' do it 'logs an exception in job' do
Timecop.freeze(timestamp) do Timecop.freeze(timestamp) do
expect(logger).to receive(:info).with(start_payload) expect(logger).to receive(:info).with(start_payload)
# This excludes the exception_backtrace
expect(logger).to receive(:warn).with(hash_including(exception_payload)) expect(logger).to receive(:warn).with(hash_including(exception_payload))
expect(subject).to receive(:log_job_start).and_call_original expect(subject).to receive(:log_job_start).and_call_original
expect(subject).to receive(:log_job_done).and_call_original expect(subject).to receive(:log_job_done).and_call_original
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment