Fix logger silencing for broadcasted loggers

Fix #23609

Commit 629efb6 introduced thread safety to logger silencing but it
didn't take into account the fact that the logger can be extended with
broadcasting to other logger.

This commit introduces local_level to broadcasting Module which enables
broadcasted loggers to be properly silenced.
This commit is contained in:
Piotr Jakubowski 2016-02-11 21:00:51 +01:00
parent 8a84f1c047
commit 2518bda97c
4 changed files with 86 additions and 21 deletions

@ -1,8 +1,10 @@
require 'active_support/logger_silence'
require 'active_support/logger_thread_safe_level'
require 'logger'
module ActiveSupport
class Logger < ::Logger
include ActiveSupport::LoggerThreadSafeLevel
include LoggerSilence
# Returns true if the logger destination matches one of the sources
@ -48,6 +50,11 @@ def self.broadcast(logger) # :nodoc:
logger.level = level
super(level)
end
define_method(:local_level=) do |level|
logger.local_level = level if logger.respond_to?(:local_level=)
super(level) if respond_to?(:local_level=)
end
end
end

@ -7,36 +7,19 @@ module LoggerSilence
included do
cattr_accessor :silencer
attr_reader :local_levels
self.silencer = true
end
def after_initialize
@local_levels = Concurrent::Map.new(:initial_capacity => 2)
end
def local_log_id
Thread.current.__id__
end
def level
local_levels[local_log_id] || super
end
# Silences the logger for the duration of the block.
def silence(temporary_level = Logger::ERROR)
if silencer
begin
old_local_level = local_levels[local_log_id]
local_levels[local_log_id] = temporary_level
old_local_level = local_level
self.local_level = temporary_level
yield self
ensure
if old_local_level
local_levels[local_log_id] = old_local_level
else
local_levels.delete(local_log_id)
end
self.local_level = old_local_level
end
else
yield self

@ -0,0 +1,31 @@
require 'active_support/concern'
module ActiveSupport
module LoggerThreadSafeLevel
extend ActiveSupport::Concern
def after_initialize
@local_levels = Concurrent::Map.new(:initial_capacity => 2)
end
def local_log_id
Thread.current.__id__
end
def local_level
@local_levels[local_log_id]
end
def local_level=(level)
if level
@local_levels[local_log_id] = level
else
@local_levels.delete(local_log_id)
end
end
def level
local_level || super
end
end
end

@ -141,6 +141,50 @@ def test_silencing_everything_but_errors
assert @output.string.include?("THIS IS HERE")
end
def test_logger_silencing_works_for_broadcast
another_output = StringIO.new
another_logger = Logger.new(another_output)
@logger.extend Logger.broadcast(another_logger)
@logger.debug "CORRECT DEBUG"
@logger.silence do
@logger.debug "FAILURE"
@logger.error "CORRECT ERROR"
end
assert @output.string.include?("CORRECT DEBUG")
assert @output.string.include?("CORRECT ERROR")
assert_not @output.string.include?("FAILURE")
assert another_output.string.include?("CORRECT DEBUG")
assert another_output.string.include?("CORRECT ERROR")
assert_not another_output.string.include?("FAILURE")
end
def test_broadcast_silencing_does_not_break_plain_ruby_logger
another_output = StringIO.new
another_logger = ::Logger.new(another_output)
@logger.extend Logger.broadcast(another_logger)
@logger.debug "CORRECT DEBUG"
@logger.silence do
@logger.debug "FAILURE"
@logger.error "CORRECT ERROR"
end
assert @output.string.include?("CORRECT DEBUG")
assert @output.string.include?("CORRECT ERROR")
assert_not @output.string.include?("FAILURE")
assert another_output.string.include?("CORRECT DEBUG")
assert another_output.string.include?("CORRECT ERROR")
assert another_output.string.include?("FAILURE")
# We can't silence plain ruby Logger cause with thread safety
# but at least we don't break it
end
def test_logger_level_per_object_thread_safety
logger1 = Logger.new(StringIO.new)
logger2 = Logger.new(StringIO.new)