mirror of
https://github.com/mperham/sidekiq.git
synced 2022-11-09 13:52:34 -05:00
Support job-specific log levels
This commit is contained in:
parent
52ccaf82b8
commit
7f68ba8838
6 changed files with 122 additions and 10 deletions
|
@ -5,6 +5,11 @@
|
||||||
HEAD
|
HEAD
|
||||||
---------
|
---------
|
||||||
|
|
||||||
|
- Support job-specific log levels. This will allow you to turn on debugging for
|
||||||
|
problematic workers. [fatkodima, #4287]
|
||||||
|
```ruby
|
||||||
|
MyWorker.set(log_level: :debug).perform_async(...)
|
||||||
|
```
|
||||||
- Support ad-hoc job tags. You can tag your jobs with, e.g, subdomain, tenant, country,
|
- Support ad-hoc job tags. You can tag your jobs with, e.g, subdomain, tenant, country,
|
||||||
locale, application, version, user/client, "alpha/beta/pro/ent", types of jobs,
|
locale, application, version, user/client, "alpha/beta/pro/ent", types of jobs,
|
||||||
teams/people responsible for jobs, additional metadata, etc.
|
teams/people responsible for jobs, additional metadata, etc.
|
||||||
|
|
|
@ -205,9 +205,8 @@ module Sidekiq
|
||||||
return self.logger
|
return self.logger
|
||||||
end
|
end
|
||||||
|
|
||||||
unless logger.is_a?(Sidekiq::LogContext)
|
logger.extend(Sidekiq::LogContext)
|
||||||
logger.extend(Sidekiq::LogContext)
|
logger.extend(Sidekiq::LoggerThreadSafeLevel)
|
||||||
end
|
|
||||||
|
|
||||||
@logger = logger
|
@logger = logger
|
||||||
end
|
end
|
||||||
|
|
|
@ -23,8 +23,15 @@ module Sidekiq
|
||||||
raise
|
raise
|
||||||
end
|
end
|
||||||
|
|
||||||
def with_job_hash_context(job_hash, &block)
|
def with_job_hash_context_and_log_level(job_hash, &block)
|
||||||
@logger.with_context(job_hash_context(job_hash), &block)
|
level = job_hash["log_level"]
|
||||||
|
if level
|
||||||
|
@logger.log_at(level) do
|
||||||
|
@logger.with_context(job_hash_context(job_hash), &block)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
@logger.with_context(job_hash_context(job_hash), &block)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def job_hash_context(job_hash)
|
def job_hash_context(job_hash)
|
||||||
|
|
|
@ -17,8 +17,74 @@ module Sidekiq
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
module LoggerThreadSafeLevel
|
||||||
|
LOG_LEVEL_MAP = Hash[Logger::Severity.constants.map { |c| [c.to_s, Logger::Severity.const_get(c)] }]
|
||||||
|
LOG_LEVEL_MAP.default_proc = proc do |_, level|
|
||||||
|
Sidekiq.logger.info("Invalid log level: #{level.inspect}")
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
Logger::Severity.constants.each do |severity|
|
||||||
|
define_method("#{severity.downcase}?") do
|
||||||
|
Logger.const_get(severity) >= level
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def local_level
|
||||||
|
Thread.current[:sidekiq_log_level]
|
||||||
|
end
|
||||||
|
|
||||||
|
def local_level=(level)
|
||||||
|
case level
|
||||||
|
when Integer
|
||||||
|
Thread.current[:sidekiq_log_level] = level
|
||||||
|
when Symbol, String
|
||||||
|
Thread.current[:sidekiq_log_level] = LOG_LEVEL_MAP[level.to_s.upcase]
|
||||||
|
when nil
|
||||||
|
Thread.current[:sidekiq_log_level] = nil
|
||||||
|
else
|
||||||
|
raise ArgumentError, "Invalid log level: #{level.inspect}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def level
|
||||||
|
local_level || super
|
||||||
|
end
|
||||||
|
|
||||||
|
# Change the thread-local level for the duration of the given block.
|
||||||
|
def log_at(level)
|
||||||
|
old_local_level = local_level
|
||||||
|
self.local_level = level
|
||||||
|
yield
|
||||||
|
ensure
|
||||||
|
self.local_level = old_local_level
|
||||||
|
end
|
||||||
|
|
||||||
|
# Redefined to check severity against #level, and thus the thread-local level, rather than +@level+.
|
||||||
|
# FIXME: Remove when the minimum Ruby version supports overriding Logger#level.
|
||||||
|
def add(severity, message = nil, progname = nil, &block)
|
||||||
|
severity ||= UNKNOWN
|
||||||
|
progname ||= @progname
|
||||||
|
|
||||||
|
return true if @logdev.nil? || severity < level
|
||||||
|
|
||||||
|
if message.nil?
|
||||||
|
if block_given?
|
||||||
|
message = yield
|
||||||
|
else
|
||||||
|
message = progname
|
||||||
|
progname = @progname
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@logdev.write \
|
||||||
|
format_message(format_severity(severity), Time.now, progname, message)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
class Logger < ::Logger
|
class Logger < ::Logger
|
||||||
include LogContext
|
include LogContext
|
||||||
|
include LoggerThreadSafeLevel
|
||||||
|
|
||||||
def initialize(*args)
|
def initialize(*args)
|
||||||
super
|
super
|
||||||
|
|
|
@ -117,7 +117,7 @@ module Sidekiq
|
||||||
# job structure to the Web UI
|
# job structure to the Web UI
|
||||||
pristine = cloned(job_hash)
|
pristine = cloned(job_hash)
|
||||||
|
|
||||||
@job_logger.with_job_hash_context(job_hash) do
|
@job_logger.with_job_hash_context_and_log_level(job_hash) do
|
||||||
@retrier.global(pristine, queue) do
|
@retrier.global(pristine, queue) do
|
||||||
@job_logger.call(job_hash, queue) do
|
@job_logger.call(job_hash, queue) do
|
||||||
stats(pristine, queue) do
|
stats(pristine, queue) do
|
||||||
|
|
|
@ -7,7 +7,8 @@ class TestJobLogger < Minitest::Test
|
||||||
def setup
|
def setup
|
||||||
@old = Sidekiq.logger
|
@old = Sidekiq.logger
|
||||||
@output = StringIO.new
|
@output = StringIO.new
|
||||||
@logger = Sidekiq::Logger.new(@output)
|
@logger = Sidekiq::Logger.new(@output, level: :info)
|
||||||
|
Sidekiq.logger = @logger
|
||||||
|
|
||||||
Thread.current[:sidekiq_context] = nil
|
Thread.current[:sidekiq_context] = nil
|
||||||
Thread.current[:sidekiq_tid] = nil
|
Thread.current[:sidekiq_tid] = nil
|
||||||
|
@ -16,9 +17,9 @@ class TestJobLogger < Minitest::Test
|
||||||
def teardown
|
def teardown
|
||||||
Thread.current[:sidekiq_context] = nil
|
Thread.current[:sidekiq_context] = nil
|
||||||
Thread.current[:sidekiq_tid] = nil
|
Thread.current[:sidekiq_tid] = nil
|
||||||
|
Sidekiq.logger = @old
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
def test_pretty_output
|
def test_pretty_output
|
||||||
jl = Sidekiq::JobLogger.new(@logger)
|
jl = Sidekiq::JobLogger.new(@logger)
|
||||||
|
|
||||||
|
@ -26,7 +27,7 @@ class TestJobLogger < Minitest::Test
|
||||||
p = @logger.formatter = Sidekiq::Logger::Formatters::Pretty.new
|
p = @logger.formatter = Sidekiq::Logger::Formatters::Pretty.new
|
||||||
job = {"jid"=>"1234abc", "wrapped"=>"FooWorker", "class"=>"Wrapper", "tags" => ["bar", "baz"]}
|
job = {"jid"=>"1234abc", "wrapped"=>"FooWorker", "class"=>"Wrapper", "tags" => ["bar", "baz"]}
|
||||||
# this mocks what Processor does
|
# this mocks what Processor does
|
||||||
jl.with_job_hash_context(job) do
|
jl.with_job_hash_context_and_log_level(job) do
|
||||||
jl.call(job, 'queue') {}
|
jl.call(job, 'queue') {}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -46,7 +47,7 @@ class TestJobLogger < Minitest::Test
|
||||||
jl = Sidekiq::JobLogger.new(@logger)
|
jl = Sidekiq::JobLogger.new(@logger)
|
||||||
job = {"jid"=>"1234abc", "wrapped"=>"Wrapper", "class"=>"FooWorker", "bid"=>"b-xyz", "tags" => ["bar", "baz"]}
|
job = {"jid"=>"1234abc", "wrapped"=>"Wrapper", "class"=>"FooWorker", "bid"=>"b-xyz", "tags" => ["bar", "baz"]}
|
||||||
# this mocks what Processor does
|
# this mocks what Processor does
|
||||||
jl.with_job_hash_context(job) do
|
jl.with_job_hash_context_and_log_level(job) do
|
||||||
jl.call(job, 'queue') {}
|
jl.call(job, 'queue') {}
|
||||||
end
|
end
|
||||||
a, b = @output.string.lines
|
a, b = @output.string.lines
|
||||||
|
@ -59,6 +60,40 @@ class TestJobLogger < Minitest::Test
|
||||||
assert_equal(["bid", "class", "jid", "tags"], keys)
|
assert_equal(["bid", "class", "jid", "tags"], keys)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_custom_log_level
|
||||||
|
jl = Sidekiq::JobLogger.new(@logger)
|
||||||
|
job = {"class"=>"FooWorker", "log_level"=>"debug"}
|
||||||
|
|
||||||
|
assert @logger.info?
|
||||||
|
jl.with_job_hash_context_and_log_level(job) do
|
||||||
|
jl.call(job, "queue") do
|
||||||
|
assert @logger.debug?
|
||||||
|
@logger.debug("debug message")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
assert @logger.info?
|
||||||
|
|
||||||
|
a, b, c = @output.string.lines
|
||||||
|
assert_match(/INFO: start/, a)
|
||||||
|
assert_match(/DEBUG: debug message/, b)
|
||||||
|
assert_match(/INFO: done/, c)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_custom_log_level_uses_default_log_level_for_invalid_value
|
||||||
|
jl = Sidekiq::JobLogger.new(@logger)
|
||||||
|
job = {"class"=>"FooWorker", "log_level"=>"non_existent"}
|
||||||
|
|
||||||
|
assert @logger.info?
|
||||||
|
jl.with_job_hash_context_and_log_level(job) do
|
||||||
|
jl.call(job, "queue") do
|
||||||
|
assert @logger.info?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
assert @logger.info?
|
||||||
|
log_level_warning = @output.string.lines[0]
|
||||||
|
assert_match(/INFO: Invalid log level/, log_level_warning)
|
||||||
|
end
|
||||||
|
|
||||||
def reset(io)
|
def reset(io)
|
||||||
io.truncate(0)
|
io.truncate(0)
|
||||||
io.rewind
|
io.rewind
|
||||||
|
|
Loading…
Add table
Reference in a new issue