1
0
Fork 0
mirror of https://github.com/mperham/sidekiq.git synced 2022-11-09 13:52:34 -05:00

JSON Logger Formatter (#4050)

* Check Config File Existence (#4054)

* Check config file existence

* Eager config file check

* Parse expanded path to default sidekiq.yml config file in Rails app

* Cleanup

* Add minitest-around

* Extract context from formatter

* Add JSON logger formatter

* Adjust job logger to handle elapsed time within context

* Add tid test

* Rename processor logger

* Enforce global state reset in logging tests

* Add warning about upcoming refactoring to Sidekiq::Logging

* Replace around hook with explicit stub inside test

It's implemented with fibers, which means Thread.current returns different values in JRuby.

* Fix typo

* Concise JSON formatter keys

* Add logger_formatter option

* Shift context from array of strings to hash

Allows more flexibly format context in the different formatters.

* Adjust warning message regarding context type change

* Add "Formatter" suffix to classes

* Fix CLI specs

* Replace Sidekiq::Logging with Sidekiq::Logger

* Namespace logger formatters

* Remove rails 4 appraisal
This commit is contained in:
Andrew Babichev 2018-12-29 00:05:51 +01:00 committed by Mike Perham
parent 70216520c1
commit 3845832c20
15 changed files with 687 additions and 430 deletions

View file

@ -1,8 +1,3 @@
appraise "rails-4" do
gem "rails", "~> 4.2"
gem 'activerecord-jdbcsqlite3-adapter', '< 50', platforms: :jruby
end
appraise "rails-5" do appraise "rails-5" do
gem "rails", "~> 5.2" gem "rails", "~> 5.2"
gem 'activerecord-jdbcsqlite3-adapter', '>= 50', platforms: :jruby gem 'activerecord-jdbcsqlite3-adapter', '>= 50', platforms: :jruby

View file

@ -14,6 +14,7 @@ end
group :test do group :test do
gem 'minitest' gem 'minitest'
gem 'minitest-around'
gem 'minitest-focus' gem 'minitest-focus'
gem 'minitest-reporters' gem 'minitest-reporters'
gem 'simplecov' gem 'simplecov'

View file

@ -14,6 +14,7 @@ end
group :test do group :test do
gem "minitest" gem "minitest"
gem "minitest-around"
gem "minitest-focus" gem "minitest-focus"
gem "minitest-reporters" gem "minitest-reporters"
gem "simplecov" gem "simplecov"

View file

@ -3,7 +3,7 @@
require 'sidekiq/version' require 'sidekiq/version'
fail "Sidekiq #{Sidekiq::VERSION} does not support Ruby versions below 2.4.0." if RUBY_PLATFORM != 'java' && Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.4.0') fail "Sidekiq #{Sidekiq::VERSION} does not support Ruby versions below 2.4.0." if RUBY_PLATFORM != 'java' && Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.4.0')
require 'sidekiq/logging' require 'sidekiq/logger'
require 'sidekiq/client' require 'sidekiq/client'
require 'sidekiq/worker' require 'sidekiq/worker'
require 'sidekiq/redis_connection' require 'sidekiq/redis_connection'
@ -178,11 +178,16 @@ module Sidekiq
JSON.generate(object) JSON.generate(object)
end end
def self.logger class << self
Sidekiq::Logging.logger attr_accessor :logger_formatter
end end
def self.logger=(log)
Sidekiq::Logging.logger = log def self.logger
@logger ||= Sidekiq::Logger.new(STDOUT, level: Logger::INFO)
end
def self.logger=(logger)
@logger = logger
end end
# How frequently Redis should be checked by a random Sidekiq process for # How frequently Redis should be checked by a random Sidekiq process for

View file

@ -8,8 +8,8 @@ require 'erb'
require 'fileutils' require 'fileutils'
require 'sidekiq' require 'sidekiq'
require 'sidekiq/util'
require 'sidekiq/launcher' require 'sidekiq/launcher'
require 'sidekiq/util'
module Sidekiq module Sidekiq
class CLI class CLI
@ -339,16 +339,11 @@ module Sidekiq
end end
def initialize_logger def initialize_logger
Sidekiq::Logging.initialize_logger
Sidekiq.logger.level = ::Logger::DEBUG if options[:verbose] Sidekiq.logger.level = ::Logger::DEBUG if options[:verbose]
end end
def parse_config(cfile) def parse_config(path)
opts = {} opts = YAML.load(ERB.new(File.read(path)).result) || {}
if File.exist?(cfile)
opts = YAML.load(ERB.new(IO.read(cfile)).result) || opts
end
if opts.respond_to? :deep_symbolize_keys! if opts.respond_to? :deep_symbolize_keys!
opts.deep_symbolize_keys! opts.deep_symbolize_keys!

View file

@ -1,25 +1,52 @@
# frozen_string_literal: true # frozen_string_literal: true
module Sidekiq module Sidekiq
class JobLogger class JobLogger
def call(item, queue) def call(item, queue)
start = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) start = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
logger.info("start")
Sidekiq.logger.info("start")
yield yield
logger.info("done: #{elapsed(start)} sec")
with_elapsed_time_context(start) do
Sidekiq.logger.info("done")
end
rescue Exception rescue Exception
logger.info("fail: #{elapsed(start)} sec") with_elapsed_time_context(start) do
Sidekiq.logger.info("fail")
end
raise raise
end end
def with_job_hash_context(job_hash, &block)
Sidekiq.logger.with_context(job_hash_context(job_hash), &block)
end
def job_hash_context(job_hash)
# If we're using a wrapper class, like ActiveJob, use the "wrapped"
# attribute to expose the underlying thing.
{
class: job_hash['wrapped'] || job_hash["class"],
jid: job_hash['jid'],
bid: job_hash['bid']
}
end
def with_elapsed_time_context(start, &block)
Sidekiq.logger.with_context(elapsed_time_context(start), &block)
end
def elapsed_time_context(start)
{ elapsed: "#{elapsed(start)} sec" }
end
private private
def elapsed(start) def elapsed(start)
(::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - start).round(3) (::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - start).round(3)
end end
def logger
Sidekiq.logger
end
end end
end end

70
lib/sidekiq/logger.rb Normal file
View file

@ -0,0 +1,70 @@
# frozen_string_literal: true
require 'logger'
require 'time'
module Sidekiq
class Logger < ::Logger
def initialize(*args)
super
formatter_class = case Sidekiq.logger_formatter
when :json
Formatters::JSON
else
ENV['DYNO'] ? Formatters::WithoutTimestamp : Formatters::Pretty
end
self.formatter = formatter_class.new
end
def tid
Thread.current['sidekiq_tid'] ||= (Thread.current.object_id ^ ::Process.pid).to_s(36)
end
def context
Thread.current[:sidekiq_context] ||= {}
end
def with_context(hash)
context.merge!(hash)
yield
ensure
hash.keys.each { |key| context.delete(key) }
end
module Formatters
class Pretty < Logger::Formatter
def call(severity, time, program_name, message)
"#{time.utc.iso8601(3)} #{::Process.pid} TID-#{Sidekiq.logger.tid}#{format_context(Sidekiq.logger.context)} #{severity}: #{message}\n"
end
private
def format_context(context)
' ' + context.compact.map { |k, v| "#{k.upcase}=#{v}" }.join(' ') if context.any?
end
end
class WithoutTimestamp < Pretty
def call(severity, time, program_name, message)
"#{::Process.pid} TID-#{Sidekiq.logger.tid}#{format_context(Sidekiq.logger.context)} #{severity}: #{message}\n"
end
end
class JSON < Logger::Formatter
def call(severity, time, program_name, message)
Sidekiq.dump_json(
ts: time.utc.iso8601(3),
pid: ::Process.pid,
tid: Sidekiq.logger.tid,
ctx: Sidekiq.logger.context,
sev: severity,
msg: message
)
end
end
end
end
end

View file

@ -1,72 +0,0 @@
# frozen_string_literal: true
require 'time'
require 'logger'
module Sidekiq
module Logging
class Pretty < Logger::Formatter
SPACE = " "
# Provide a call() method that returns the formatted message.
def call(severity, time, program_name, message)
"#{time.utc.iso8601(3)} #{::Process.pid} TID-#{Sidekiq::Logging.tid}#{context} #{severity}: #{message}\n"
end
def context
c = Thread.current[:sidekiq_context]
" #{c.join(SPACE)}" if c && c.any?
end
end
class WithoutTimestamp < Pretty
def call(severity, time, program_name, message)
"#{::Process.pid} TID-#{Sidekiq::Logging.tid}#{context} #{severity}: #{message}\n"
end
end
def self.tid
Thread.current['sidekiq_tid'] ||= (Thread.current.object_id ^ ::Process.pid).to_s(36)
end
def self.job_hash_context(job_hash)
# If we're using a wrapper class, like ActiveJob, use the "wrapped"
# attribute to expose the underlying thing.
klass = job_hash['wrapped'] || job_hash["class"]
bid = job_hash['bid']
"#{klass} JID-#{job_hash['jid']}#{" BID-#{bid}" if bid}"
end
def self.with_job_hash_context(job_hash, &block)
with_context(job_hash_context(job_hash), &block)
end
def self.with_context(msg)
Thread.current[:sidekiq_context] ||= []
Thread.current[:sidekiq_context] << msg
yield
ensure
Thread.current[:sidekiq_context].pop
end
def self.initialize_logger
return @logger if defined?(@logger)
@logger = Logger.new(STDOUT)
@logger.level = Logger::INFO
@logger.formatter = ENV['DYNO'] ? WithoutTimestamp.new : Pretty.new
@logger
end
def self.logger
defined?(@logger) ? @logger : initialize_logger
end
def self.logger=(log)
@logger = (log ? log : Logger.new(File::NULL))
end
def logger
Sidekiq::Logging.logger
end
end
end

View file

@ -37,7 +37,7 @@ module Sidekiq
@thread = nil @thread = nil
@strategy = (mgr.options[:fetch] || Sidekiq::BasicFetch).new(mgr.options) @strategy = (mgr.options[:fetch] || Sidekiq::BasicFetch).new(mgr.options)
@reloader = Sidekiq.options[:reloader] @reloader = Sidekiq.options[:reloader]
@logging = (mgr.options[:job_logger] || Sidekiq::JobLogger).new @job_logger = (mgr.options[:job_logger] || Sidekiq::JobLogger).new
@retrier = Sidekiq::JobRetry.new @retrier = Sidekiq::JobRetry.new
end end
@ -121,9 +121,9 @@ module Sidekiq
# job structure to the Web UI # job structure to the Web UI
pristine = cloned(job_hash) pristine = cloned(job_hash)
Sidekiq::Logging.with_job_hash_context(job_hash) do @job_logger.with_job_hash_context(job_hash) do
@retrier.global(pristine, queue) do @retrier.global(pristine, queue) do
@logging.call(job_hash, queue) do @job_logger.call(job_hash, queue) do
stats(pristine, queue) do stats(pristine, queue) do
# Rails 5 requires a Reloader to wrap code execution. In order to # Rails 5 requires a Reloader to wrap code execution. In order to
# constantize the worker and instantiate an instance, we have to call # constantize the worker and instantiate an instance, we have to call
@ -236,7 +236,8 @@ module Sidekiq
WORKER_STATE = SharedWorkerState.new WORKER_STATE = SharedWorkerState.new
def stats(job_hash, queue) def stats(job_hash, queue)
tid = Sidekiq::Logging.tid tid = Sidekiq.logger.tid
WORKER_STATE.set(tid, {:queue => queue, :payload => job_hash, :run_at => Time.now.to_i }) WORKER_STATE.set(tid, {:queue => queue, :payload => job_hash, :run_at => Time.now.to_i })
begin begin
@ -267,6 +268,5 @@ module Sidekiq
constant.const_defined?(name, false) ? constant.const_get(name, false) : constant.const_missing(name) constant.const_defined?(name, false) ? constant.const_get(name, false) : constant.const_missing(name)
end end
end end
end end
end end

View file

@ -24,7 +24,7 @@ module Sidekiq
# next one. # next one.
if conn.zrem(sorted_set, job) if conn.zrem(sorted_set, job)
Sidekiq::Client.push(Sidekiq.load_json(job)) Sidekiq::Client.push(Sidekiq.load_json(job))
Sidekiq::Logging.logger.debug { "enqueued #{sorted_set}: #{job}" } Sidekiq.logger.debug { "enqueued #{sorted_set}: #{job}" }
end end
end end
end end

View file

@ -4,23 +4,24 @@ require_relative 'helper'
require 'sidekiq/cli' require 'sidekiq/cli'
class TestCLI < Minitest::Test class TestCLI < Minitest::Test
describe Sidekiq::CLI do
subject { Sidekiq::CLI.new }
let(:logdev) { StringIO.new }
around do |test|
Sidekiq.stub :options, Sidekiq::DEFAULTS.dup do
Sidekiq.stub :logger, Sidekiq::Logger.new(logdev) do
test.call
end
end
end
describe '#parse' do describe '#parse' do
before do
Sidekiq.options = Sidekiq::DEFAULTS.dup
@logger = Sidekiq.logger
@logdev = StringIO.new
Sidekiq.logger = Logger.new(@logdev)
@cli = Sidekiq::CLI.new
end
after do
Sidekiq.logger = @logger
end
describe 'options' do describe 'options' do
describe 'require' do describe 'require' do
it 'accepts with -r' do it 'accepts with -r' do
@cli.parse(%w[sidekiq -r ./test/fake_env.rb]) subject.parse(%w[sidekiq -r ./test/fake_env.rb])
assert_equal './test/fake_env.rb', Sidekiq.options[:require] assert_equal './test/fake_env.rb', Sidekiq.options[:require]
end end
@ -28,7 +29,7 @@ class TestCLI < Minitest::Test
describe 'concurrency' do describe 'concurrency' do
it 'accepts with -c' do it 'accepts with -c' do
@cli.parse(%w[sidekiq -c 60 -r ./test/fake_env.rb]) subject.parse(%w[sidekiq -c 60 -r ./test/fake_env.rb])
assert_equal 60, Sidekiq.options[:concurrency] assert_equal 60, Sidekiq.options[:concurrency]
end end
@ -43,13 +44,13 @@ class TestCLI < Minitest::Test
end end
it 'sets concurrency from RAILS_MAX_THREADS env var' do it 'sets concurrency from RAILS_MAX_THREADS env var' do
@cli.parse(%w[sidekiq -r ./test/fake_env.rb]) subject.parse(%w[sidekiq -r ./test/fake_env.rb])
assert_equal 9, Sidekiq.options[:concurrency] assert_equal 9, Sidekiq.options[:concurrency]
end end
it 'option overrides RAILS_MAX_THREADS env var' do it 'option overrides RAILS_MAX_THREADS env var' do
@cli.parse(%w[sidekiq -c 60 -r ./test/fake_env.rb]) subject.parse(%w[sidekiq -c 60 -r ./test/fake_env.rb])
assert_equal 60, Sidekiq.options[:concurrency] assert_equal 60, Sidekiq.options[:concurrency]
end end
@ -58,20 +59,20 @@ class TestCLI < Minitest::Test
describe 'queues' do describe 'queues' do
it 'accepts with -q' do it 'accepts with -q' do
@cli.parse(%w[sidekiq -q foo -r ./test/fake_env.rb]) subject.parse(%w[sidekiq -q foo -r ./test/fake_env.rb])
assert_equal ['foo'], Sidekiq.options[:queues] assert_equal ['foo'], Sidekiq.options[:queues]
end end
describe 'when weights are not present' do describe 'when weights are not present' do
it 'accepts queues without weights' do it 'accepts queues without weights' do
@cli.parse(%w[sidekiq -q foo -q bar -r ./test/fake_env.rb]) subject.parse(%w[sidekiq -q foo -q bar -r ./test/fake_env.rb])
assert_equal ['foo', 'bar'], Sidekiq.options[:queues] assert_equal ['foo', 'bar'], Sidekiq.options[:queues]
end end
it 'sets strictly ordered queues' do it 'sets strictly ordered queues' do
@cli.parse(%w[sidekiq -q foo -q bar -r ./test/fake_env.rb]) subject.parse(%w[sidekiq -q foo -q bar -r ./test/fake_env.rb])
assert_equal true, !!Sidekiq.options[:strict] assert_equal true, !!Sidekiq.options[:strict]
end end
@ -79,40 +80,40 @@ class TestCLI < Minitest::Test
describe 'when weights are present' do describe 'when weights are present' do
it 'accepts queues with weights' do it 'accepts queues with weights' do
@cli.parse(%w[sidekiq -q foo,3 -q bar -r ./test/fake_env.rb]) subject.parse(%w[sidekiq -q foo,3 -q bar -r ./test/fake_env.rb])
assert_equal ['foo', 'foo', 'foo', 'bar'], Sidekiq.options[:queues] assert_equal ['foo', 'foo', 'foo', 'bar'], Sidekiq.options[:queues]
end end
it 'does not set strictly ordered queues' do it 'does not set strictly ordered queues' do
@cli.parse(%w[sidekiq -q foo,3 -q bar -r ./test/fake_env.rb]) subject.parse(%w[sidekiq -q foo,3 -q bar -r ./test/fake_env.rb])
assert_equal false, !!Sidekiq.options[:strict] assert_equal false, !!Sidekiq.options[:strict]
end end
end end
it 'accepts queues with multi-word names' do it 'accepts queues with multi-word names' do
@cli.parse(%w[sidekiq -q queue_one -q queue-two -r ./test/fake_env.rb]) subject.parse(%w[sidekiq -q queue_one -q queue-two -r ./test/fake_env.rb])
assert_equal ['queue_one', 'queue-two'], Sidekiq.options[:queues] assert_equal ['queue_one', 'queue-two'], Sidekiq.options[:queues]
end end
it 'accepts queues with dots in the name' do it 'accepts queues with dots in the name' do
@cli.parse(%w[sidekiq -q foo.bar -r ./test/fake_env.rb]) subject.parse(%w[sidekiq -q foo.bar -r ./test/fake_env.rb])
assert_equal ['foo.bar'], Sidekiq.options[:queues] assert_equal ['foo.bar'], Sidekiq.options[:queues]
end end
describe 'when duplicate queue names' do describe 'when duplicate queue names' do
it 'raises an argument error' do it 'raises an argument error' do
assert_raises(ArgumentError) { @cli.parse(%w[sidekiq -q foo -q foo -r ./test/fake_env.rb]) } assert_raises(ArgumentError) { subject.parse(%w[sidekiq -q foo -q foo -r ./test/fake_env.rb]) }
assert_raises(ArgumentError) { @cli.parse(%w[sidekiq -q foo,3 -q foo,1 -r ./test/fake_env.rb]) } assert_raises(ArgumentError) { subject.parse(%w[sidekiq -q foo,3 -q foo,1 -r ./test/fake_env.rb]) }
end end
end end
describe 'when queues are empty' do describe 'when queues are empty' do
it "sets 'default' queue" do it "sets 'default' queue" do
@cli.parse(%w[sidekiq -r ./test/fake_env.rb]) subject.parse(%w[sidekiq -r ./test/fake_env.rb])
assert_equal ['default'], Sidekiq.options[:queues] assert_equal ['default'], Sidekiq.options[:queues]
end end
@ -121,7 +122,7 @@ class TestCLI < Minitest::Test
describe 'timeout' do describe 'timeout' do
it 'accepts with -t' do it 'accepts with -t' do
@cli.parse(%w[sidekiq -t 30 -r ./test/fake_env.rb]) subject.parse(%w[sidekiq -t 30 -r ./test/fake_env.rb])
assert_equal 30, Sidekiq.options[:timeout] assert_equal 30, Sidekiq.options[:timeout]
end end
@ -129,7 +130,7 @@ class TestCLI < Minitest::Test
describe 'verbose' do describe 'verbose' do
it 'accepts with -v' do it 'accepts with -v' do
@cli.parse(%w[sidekiq -v -r ./test/fake_env.rb]) subject.parse(%w[sidekiq -v -r ./test/fake_env.rb])
assert_equal Logger::DEBUG, Sidekiq.logger.level assert_equal Logger::DEBUG, Sidekiq.logger.level
end end
@ -137,7 +138,7 @@ class TestCLI < Minitest::Test
describe 'config file' do describe 'config file' do
it 'accepts with -C' do it 'accepts with -C' do
@cli.parse(%w[sidekiq -C ./test/config.yml]) subject.parse(%w[sidekiq -C ./test/config.yml])
assert_equal './test/config.yml', Sidekiq.options[:config_file] assert_equal './test/config.yml', Sidekiq.options[:config_file]
refute Sidekiq.options[:verbose] refute Sidekiq.options[:verbose]
@ -149,7 +150,7 @@ class TestCLI < Minitest::Test
end end
it 'accepts stringy keys' do it 'accepts stringy keys' do
@cli.parse(%w[sidekiq -C ./test/config_string.yml]) subject.parse(%w[sidekiq -C ./test/config_string.yml])
assert_equal './test/config_string.yml', Sidekiq.options[:config_file] assert_equal './test/config_string.yml', Sidekiq.options[:config_file]
refute Sidekiq.options[:verbose] refute Sidekiq.options[:verbose]
@ -161,7 +162,7 @@ class TestCLI < Minitest::Test
end end
it 'accepts environment specific config' do it 'accepts environment specific config' do
@cli.parse(%w[sidekiq -e staging -C ./test/config_environment.yml]) subject.parse(%w[sidekiq -e staging -C ./test/config_environment.yml])
assert_equal './test/config_environment.yml', Sidekiq.options[:config_file] assert_equal './test/config_environment.yml', Sidekiq.options[:config_file]
refute Sidekiq.options[:verbose] refute Sidekiq.options[:verbose]
@ -174,7 +175,7 @@ class TestCLI < Minitest::Test
describe 'when config file is empty' do describe 'when config file is empty' do
it 'sets default options' do it 'sets default options' do
@cli.parse(%w[sidekiq -C ./test/config_empty.yml -r ./test/fake_env.rb]) subject.parse(%w[sidekiq -C ./test/config_empty.yml -r ./test/fake_env.rb])
assert_equal './test/config_empty.yml', Sidekiq.options[:config_file] assert_equal './test/config_empty.yml', Sidekiq.options[:config_file]
refute Sidekiq.options[:verbose] refute Sidekiq.options[:verbose]
@ -187,7 +188,7 @@ class TestCLI < Minitest::Test
describe 'when config file and flags' do describe 'when config file and flags' do
it 'merges options' do it 'merges options' do
@cli.parse(%w[sidekiq -C ./test/config.yml subject.parse(%w[sidekiq -C ./test/config.yml
-e snoop -e snoop
-c 100 -c 100
-r ./test/fake_env.rb -r ./test/fake_env.rb
@ -207,7 +208,7 @@ class TestCLI < Minitest::Test
describe 'default config file' do describe 'default config file' do
describe 'when required path is a directory' do describe 'when required path is a directory' do
it 'tries config/sidekiq.yml' do it 'tries config/sidekiq.yml' do
@cli.parse(%w[sidekiq -r ./test/dummy]) subject.parse(%w[sidekiq -r ./test/dummy])
assert_equal 'sidekiq.yml', File.basename(Sidekiq.options[:config_file]) assert_equal 'sidekiq.yml', File.basename(Sidekiq.options[:config_file])
assert_equal 25, Sidekiq.options[:concurrency] assert_equal 25, Sidekiq.options[:concurrency]
@ -220,21 +221,21 @@ class TestCLI < Minitest::Test
describe 'validation' do describe 'validation' do
describe 'when required application path does not exist' do describe 'when required application path does not exist' do
it 'exits with status 1' do it 'exits with status 1' do
exit = assert_raises(SystemExit) { @cli.parse(%w[sidekiq -r /non/existent/path]) } exit = assert_raises(SystemExit) { subject.parse(%w[sidekiq -r /non/existent/path]) }
assert_equal 1, exit.status assert_equal 1, exit.status
end end
end end
describe 'when required path is a directory without config/application.rb' do describe 'when required path is a directory without config/application.rb' do
it 'exits with status 1' do it 'exits with status 1' do
exit = assert_raises(SystemExit) { @cli.parse(%w[sidekiq -r ./test/fixtures]) } exit = assert_raises(SystemExit) { subject.parse(%w[sidekiq -r ./test/fixtures]) }
assert_equal 1, exit.status assert_equal 1, exit.status
end end
describe 'when config file path does not exist' do describe 'when config file path does not exist' do
it 'raises argument error' do it 'raises argument error' do
assert_raises(ArgumentError) do assert_raises(ArgumentError) do
@cli.parse(%w[sidekiq -r ./test/fake_env.rb -C /non/existent/path]) subject.parse(%w[sidekiq -r ./test/fake_env.rb -C /non/existent/path])
end end
end end
end end
@ -244,28 +245,19 @@ class TestCLI < Minitest::Test
describe '#run' do describe '#run' do
before do before do
Sidekiq.options = Sidekiq::DEFAULTS.dup
Sidekiq.options[:require] = './test/fake_env.rb' Sidekiq.options[:require] = './test/fake_env.rb'
@logger = Sidekiq.logger
@logdev = StringIO.new
Sidekiq.logger = Logger.new(@logdev)
@cli = Sidekiq::CLI.new
end
after do
Sidekiq.logger = @logger
end end
describe 'require workers' do describe 'require workers' do
describe 'when path is a rails directory' do describe 'when path is a rails directory' do
before do before do
Sidekiq.options[:require] = './test/dummy' Sidekiq.options[:require] = './test/dummy'
@cli.environment = 'test' subject.environment = 'test'
end end
it 'requires sidekiq railtie and rails application with environment' do it 'requires sidekiq railtie and rails application with environment' do
@cli.stub(:launch, nil) do subject.stub(:launch, nil) do
@cli.run subject.run
end end
assert defined?(Sidekiq::Rails) assert defined?(Sidekiq::Rails)
@ -273,8 +265,8 @@ class TestCLI < Minitest::Test
end end
it 'tags with the app directory name' do it 'tags with the app directory name' do
@cli.stub(:launch, nil) do subject.stub(:launch, nil) do
@cli.run subject.run
end end
assert_equal 'dummy', Sidekiq.options[:tag] assert_equal 'dummy', Sidekiq.options[:tag]
@ -283,8 +275,8 @@ class TestCLI < Minitest::Test
describe 'when path is file' do describe 'when path is file' do
it 'requires application' do it 'requires application' do
@cli.stub(:launch, nil) do subject.stub(:launch, nil) do
@cli.run subject.run
end end
assert $LOADED_FEATURES.any? { |x| x =~ /test\/fake_env/ } assert $LOADED_FEATURES.any? { |x| x =~ /test\/fake_env/ }
@ -294,11 +286,11 @@ class TestCLI < Minitest::Test
describe 'when development environment and stdout tty' do describe 'when development environment and stdout tty' do
it 'prints banner' do it 'prints banner' do
@cli.stub(:environment, 'development') do subject.stub(:environment, 'development') do
assert_output(/#{Regexp.escape(Sidekiq::CLI.banner)}/) do assert_output(/#{Regexp.escape(Sidekiq::CLI.banner)}/) do
$stdout.stub(:tty?, true) do $stdout.stub(:tty?, true) do
@cli.stub(:launch, nil) do subject.stub(:launch, nil) do
@cli.run subject.run
end end
end end
end end
@ -308,23 +300,11 @@ class TestCLI < Minitest::Test
end end
describe 'signal handling' do describe 'signal handling' do
before do
@cli = Sidekiq::CLI.new
Sidekiq.options = Sidekiq::DEFAULTS.dup
@logger = Sidekiq.logger
@logdev = StringIO.new
Sidekiq.logger = Logger.new(@logdev)
end
after do
Sidekiq.logger = @logger
end
%w(INT TERM).each do |sig| %w(INT TERM).each do |sig|
describe sig do describe sig do
it 'raises interrupt error' do it 'raises interrupt error' do
assert_raises Interrupt do assert_raises Interrupt do
@cli.handle_signal(sig) subject.handle_signal(sig)
end end
end end
end end
@ -339,11 +319,10 @@ class TestCLI < Minitest::Test
quiet = true quiet = true
end end
@cli.parse(%w[sidekiq -r ./test/fake_env.rb]) subject.launcher = Sidekiq::Launcher.new(Sidekiq.options)
@cli.launcher = Sidekiq::Launcher.new(Sidekiq.options) subject.handle_signal(sig)
@cli.handle_signal(sig)
assert_match(/Got #{sig} signal/, @logdev.string) assert_match(/Got #{sig} signal/, logdev.string)
assert_equal true, quiet assert_equal true, quiet
end end
end end
@ -351,21 +330,21 @@ class TestCLI < Minitest::Test
describe 'TTIN' do describe 'TTIN' do
it 'prints backtraces for all threads in the process to the logfile' do it 'prints backtraces for all threads in the process to the logfile' do
@cli.parse(%w[sidekiq -r ./test/fake_env.rb]) subject.handle_signal('TTIN')
@cli.handle_signal('TTIN')
assert_match(/Got TTIN signal/, @logdev.string) assert_match(/Got TTIN signal/, logdev.string)
assert_match(/\bbacktrace\b/, @logdev.string) assert_match(/\bbacktrace\b/, logdev.string)
end end
end end
describe 'UNKNOWN' do describe 'UNKNOWN' do
it 'logs about' do it 'logs about' do
@cli.parse(%w[sidekiq -r ./test/fake_env.rb]) # subject.parse(%w[sidekiq -r ./test/fake_env.rb])
@cli.handle_signal('UNKNOWN') subject.handle_signal('UNKNOWN')
assert_match(/Got UNKNOWN signal/, @logdev.string) assert_match(/Got UNKNOWN signal/, logdev.string)
assert_match(/No signal handler for UNKNOWN/, @logdev.string) assert_match(/No signal handler for UNKNOWN/, logdev.string)
end
end end
end end
end end

56
test/test_job_logger.rb Normal file
View file

@ -0,0 +1,56 @@
# frozen_string_literal: true
require_relative 'helper'
require 'sidekiq/job_logger'
class TestJobLogger < Minitest::Test
describe Sidekiq::JobLogger do
subject { Sidekiq::JobLogger.new }
let(:logdev) { StringIO.new }
around do |test|
Sidekiq.stub :logger, Sidekiq::Logger.new(logdev) do
Sidekiq.logger.stub :tid, 'ouy7z76mx' do
Process.stub :pid, 4710 do
Time.stub :now, Time.utc(2020, 1, 1) do
test.call
end
end
end
end
end
after do
Thread.current[:sidekiq_context] = nil
end
describe '#call' do
describe 'when pretty formatter' do
before do
Sidekiq.logger.formatter = Sidekiq::Logger::Formatters::Pretty.new
end
it 'logs elapsed time as context' do
subject.call('item', 'queue') {}
assert_match(/2020-01-01T00:00:00\.000Z 4710 TID-ouy7z76mx INFO: start/, logdev.string)
assert_match(/2020-01-01T00:00:00\.000Z 4710 TID-ouy7z76mx ELAPSED=.+ sec INFO: done/, logdev.string)
end
end
describe 'when json formatter' do
before do
Sidekiq.logger.formatter = Sidekiq::Logger::Formatters::JSON.new
end
it 'logs elapsed time as context' do
subject.call('item', 'queue') {}
assert_match(/ctx.+msg.+start/, logdev.string)
assert_match(/ctx.+elapsed.+sec.+msg.+done/, logdev.string)
end
end
end
end
end

235
test/test_logger.rb Normal file
View file

@ -0,0 +1,235 @@
# frozen_string_literal: true
require_relative 'helper'
require 'sidekiq/logger'
class TestLogger < Minitest::Test
describe Sidekiq::Logger do
let(:logdev) { StringIO.new }
subject { Sidekiq::Logger.new(logdev) }
before do
Thread.current[:sidekiq_context] = nil
Thread.current[:sidekiq_tid] = nil
end
after do
Thread.current[:sidekiq_context] = nil
Thread.current[:sidekiq_tid] = nil
end
describe 'initialization' do
describe 'formatter' do
subject { Sidekiq::Logger.new(logdev).formatter }
describe 'default formatter' do
it 'sets pretty formatter' do
assert_kind_of Sidekiq::Logger::Formatters::Pretty, subject
end
end
describe 'when DYNO env var is present' do
around do |test|
ENV['DYNO'] = 'dyno identifier'
test.call
ENV['DYNO'] = nil
end
it 'sets without timestamp formatter' do
assert_kind_of Sidekiq::Logger::Formatters::WithoutTimestamp, subject
end
end
describe 'when logger formatter :json' do
around do |test|
Sidekiq.stub :logger_formatter, :json do
test.call
end
end
it 'sets json formatter' do
assert_kind_of Sidekiq::Logger::Formatters::JSON, subject
end
end
end
end
describe '#tid' do
describe 'default' do
it 'returns formatted thread id' do
Thread.current.stub :object_id, 70286338772540 do
Process.stub :pid, 6363 do
assert_equal 'owx3jd7mv', subject.tid
end
end
end
end
describe 'memoization' do
before do
Thread.current[:sidekiq_tid] = 'abcdefjhi'
end
it 'current thread :sidekiq_tid attribute reference' do
Thread.current.stub :object_id, 70286338772540 do
Process.stub :pid, 6363 do
assert_equal 'abcdefjhi', subject.tid
end
end
end
end
end
describe '#context' do
describe 'default' do
it 'returns empty hash' do
assert_equal({}, subject.context)
end
end
describe 'memoization' do
before do
Thread.current[:sidekiq_context] = { a: 1 }
end
it 'returns current thread :sidekiq_context attribute reference' do
assert_equal({ a: 1 }, subject.context)
end
end
end
describe '#with_context' do
it 'adds context to the current thread' do
assert_equal({}, subject.context)
subject.with_context(a: 1) do
assert_equal({ a: 1 }, subject.context)
end
assert_equal({}, subject.context)
end
describe 'nested contexts' do
it 'adds multiple contexts to the current thread' do
assert_equal({}, subject.context)
subject.with_context(a: 1) do
assert_equal({ a: 1 }, subject.context)
subject.with_context(b: 2, c: 3) do
assert_equal({ a: 1, b: 2, c: 3 }, subject.context)
end
assert_equal({ a: 1 }, subject.context)
end
assert_equal({}, subject.context)
end
end
end
describe 'formatters' do
let(:severity) { 'INFO' }
let(:utc_time) { Time.utc(2020, 1, 1) }
let(:prg) { 'sidekiq' }
let(:msg) { 'Old pond frog jumps in sound of water' }
around do |test|
Process.stub :pid, 4710 do
Sidekiq.logger.stub :tid, 'ouy7z76mx' do
test.call
end
end
end
describe 'with context' do
subject { Sidekiq::Logger::Formatters::Pretty.new.call(severity, utc_time, prg, msg) }
let(:context) { { class: 'HaikuWorker', bid: nil } }
around do |test|
Sidekiq.logger.stub :context, context do
test.call
end
end
it 'skips context with nil values' do
assert_equal "2020-01-01T00:00:00.000Z 4710 TID-ouy7z76mx CLASS=HaikuWorker INFO: Old pond frog jumps in sound of water\n", subject
end
end
describe Sidekiq::Logger::Formatters::Pretty do
describe '#call' do
subject { Sidekiq::Logger::Formatters::Pretty.new.call(severity, utc_time, prg, msg) }
it 'formats with timestamp, pid, tid, severity, message' do
assert_equal "2020-01-01T00:00:00.000Z 4710 TID-ouy7z76mx INFO: Old pond frog jumps in sound of water\n", subject
end
describe 'with context' do
let(:context) { { class: 'HaikuWorker', jid: 'dac39c70844dc0ee3f157ced' } }
around do |test|
Sidekiq.logger.stub :context, context do
test.call
end
end
it 'formats with timestamp, pid, tid, context, severity, message' do
assert_equal "2020-01-01T00:00:00.000Z 4710 TID-ouy7z76mx CLASS=HaikuWorker JID=dac39c70844dc0ee3f157ced INFO: Old pond frog jumps in sound of water\n", subject
end
end
end
end
describe Sidekiq::Logger::Formatters::WithoutTimestamp do
describe '#call' do
subject { Sidekiq::Logger::Formatters::WithoutTimestamp.new.call(severity, utc_time, prg, msg) }
it 'formats with pid, tid, severity, message' do
assert_equal "4710 TID-ouy7z76mx INFO: Old pond frog jumps in sound of water\n", subject
end
describe 'with context' do
let(:context) { { class: 'HaikuWorker', jid: 'dac39c70844dc0ee3f157ced' } }
around do |test|
Sidekiq.logger.stub :context, context do
test.call
end
end
it 'formats with pid, tid, context, severity, message' do
assert_equal "4710 TID-ouy7z76mx CLASS=HaikuWorker JID=dac39c70844dc0ee3f157ced INFO: Old pond frog jumps in sound of water\n", subject
end
end
end
end
describe Sidekiq::Logger::Formatters::JSON do
describe '#call' do
subject { Sidekiq::Logger::Formatters::JSON.new.call(severity, utc_time, prg, msg) }
it 'formats with pid, tid, severity, message' do
assert_equal %q|{"ts":"2020-01-01T00:00:00.000Z","pid":4710,"tid":"ouy7z76mx","ctx":{},"sev":"INFO","msg":"Old pond frog jumps in sound of water"}|, subject
end
describe 'with context' do
let(:context) { { class: 'HaikuWorker', jid: 'dac39c70844dc0ee3f157ced' } }
around do |test|
Sidekiq.logger.stub :context, context do
test.call
end
end
it 'formats with pid, tid, context, severity, message' do
assert_equal %q|{"ts":"2020-01-01T00:00:00.000Z","pid":4710,"tid":"ouy7z76mx","ctx":{"class":"HaikuWorker","jid":"dac39c70844dc0ee3f157ced"},"sev":"INFO","msg":"Old pond frog jumps in sound of water"}|, subject
end
end
end
end
end
end
end

View file

@ -1,35 +0,0 @@
# frozen_string_literal: true
require_relative 'helper'
require 'sidekiq/logging'
class TestLogging < Minitest::Test
describe Sidekiq::Logging do
describe "#with_context" do
def ctx
Sidekiq::Logging.logger.formatter.context
end
it "has no context by default" do
assert_nil ctx
end
it "can add a context" do
Sidekiq::Logging.with_context "xx" do
assert_equal " xx", ctx
end
assert_nil ctx
end
it "can use multiple contexts" do
Sidekiq::Logging.with_context "xx" do
assert_equal " xx", ctx
Sidekiq::Logging.with_context "yy" do
assert_equal " xx yy", ctx
end
assert_equal " xx", ctx
end
assert_nil ctx
end
end
end
end

View file

@ -338,7 +338,7 @@ class TestProcessor < Minitest::Test
end end
describe 'custom job logger class' do describe 'custom job logger class' do
class CustomJobLogger class CustomJobLogger < Sidekiq::JobLogger
def call(item, queue) def call(item, queue)
yield yield
rescue Exception rescue Exception
@ -348,9 +348,9 @@ class TestProcessor < Minitest::Test
before do before do
@mgr = Minitest::Mock.new @mgr = Minitest::Mock.new
@mgr.expect(:options, {:queues => ['default'], :job_logger => CustomJobLogger}) @mgr.expect(:options, {:queues => ['default'], job_logger: CustomJobLogger})
@mgr.expect(:options, {:queues => ['default'], :job_logger => CustomJobLogger}) @mgr.expect(:options, {:queues => ['default'], job_logger: CustomJobLogger})
@mgr.expect(:options, {:queues => ['default'], :job_logger => CustomJobLogger}) @mgr.expect(:options, {:queues => ['default'], job_logger: CustomJobLogger})
@processor = ::Sidekiq::Processor.new(@mgr) @processor = ::Sidekiq::Processor.new(@mgr)
end end