2023-06-30 00:09:47 +00:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
|
|
|
module ActiveSupport
|
|
|
|
# = Active Support Broadcast Logger
|
|
|
|
#
|
|
|
|
# The Broadcast logger is a logger used to write messages to multiple IO. It is commonly used
|
|
|
|
# in development to display messages on STDOUT and also write them to a file (development.log).
|
|
|
|
# With the Broadcast logger, you can broadcast your logs to a unlimited number of sinks.
|
|
|
|
#
|
|
|
|
# The BroadcastLogger acts as a standard logger and all methods you are used to are available.
|
|
|
|
# However, all the methods on this logger will propagate and be delegated to the other loggers
|
|
|
|
# that are part of the broadcast.
|
|
|
|
#
|
|
|
|
# Broadcasting your logs.
|
|
|
|
#
|
|
|
|
# stdout_logger = Logger.new(STDOUT)
|
|
|
|
# file_logger = Logger.new("development.log")
|
|
|
|
# broadcast = BroadcastLogger.new(stdout_logger, file_logger)
|
|
|
|
#
|
|
|
|
# broadcast.info("Hello world!") # Writes the log to STDOUT and the development.log file.
|
|
|
|
#
|
|
|
|
# Add a logger to the broadcast.
|
|
|
|
#
|
|
|
|
# stdout_logger = Logger.new(STDOUT)
|
|
|
|
# broadcast = BroadcastLogger.new(stdout_logger)
|
|
|
|
# file_logger = Logger.new("development.log")
|
|
|
|
# broadcast.broadcast_to(file_logger)
|
|
|
|
#
|
|
|
|
# broadcast.info("Hello world!") # Writes the log to STDOUT and the development.log file.
|
|
|
|
#
|
|
|
|
# Modifying the log level for all broadcasted loggers.
|
|
|
|
#
|
|
|
|
# stdout_logger = Logger.new(STDOUT)
|
|
|
|
# file_logger = Logger.new("development.log")
|
|
|
|
# broadcast = BroadcastLogger.new(stdout_logger, file_logger)
|
|
|
|
#
|
|
|
|
# broadcast.level = Logger::FATAL # Modify the log level for the whole broadcast.
|
|
|
|
#
|
|
|
|
# Stop broadcasting log to a sink.
|
|
|
|
#
|
|
|
|
# stdout_logger = Logger.new(STDOUT)
|
|
|
|
# file_logger = Logger.new("development.log")
|
|
|
|
# broadcast = BroadcastLogger.new(stdout_logger, file_logger)
|
|
|
|
# broadcast.info("Hello world!") # Writes the log to STDOUT and the development.log file.
|
|
|
|
#
|
|
|
|
# broadcast.stop_broadcasting_to(file_logger)
|
|
|
|
# broadcast.info("Hello world!") # Writes the log *only* to STDOUT.
|
|
|
|
#
|
|
|
|
# At least one sink has to be part of the broadcast. Otherwise, your logs will not
|
|
|
|
# be written anywhere. For instance:
|
|
|
|
#
|
|
|
|
# broadcast = BroadcastLogger.new
|
|
|
|
# broadcast.info("Hello world") # The log message will appear nowhere.
|
2023-10-06 17:07:06 +00:00
|
|
|
#
|
|
|
|
# If you are adding a custom logger with custom methods to the broadcast,
|
|
|
|
# the `BroadcastLogger` will proxy them and return the raw value, or an array
|
|
|
|
# of raw values, depending on how many loggers in the broadcasts responded to
|
|
|
|
# the method:
|
|
|
|
#
|
|
|
|
# class MyLogger < ::Logger
|
|
|
|
# def loggable?
|
|
|
|
# true
|
|
|
|
# end
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# logger = BroadcastLogger.new
|
|
|
|
# logger.loggable? # => A NoMethodError exception is raised because no loggers in the broadcasts could respond.
|
|
|
|
#
|
|
|
|
# logger.broadcast_to(MyLogger.new(STDOUT))
|
|
|
|
# logger.loggable? # => true
|
|
|
|
# logger.broadcast_to(MyLogger.new(STDOUT))
|
|
|
|
# puts logger.broadcasts # => [MyLogger, MyLogger]
|
|
|
|
# logger.loggable? # [true, true]
|
2023-06-30 00:09:47 +00:00
|
|
|
class BroadcastLogger
|
|
|
|
include ActiveSupport::LoggerSilence
|
|
|
|
|
|
|
|
# Returns all the logger that are part of this broadcast.
|
|
|
|
attr_reader :broadcasts
|
|
|
|
attr_reader :formatter
|
|
|
|
attr_accessor :progname
|
|
|
|
|
|
|
|
def initialize(*loggers)
|
|
|
|
@broadcasts = []
|
|
|
|
@progname = "Broadcast"
|
|
|
|
|
|
|
|
broadcast_to(*loggers)
|
|
|
|
end
|
|
|
|
|
|
|
|
# Add logger(s) to the broadcast.
|
|
|
|
#
|
|
|
|
# broadcast_logger = ActiveSupport::BroadcastLogger.new
|
|
|
|
# broadcast_logger.broadcast_to(Logger.new(STDOUT), Logger.new(STDERR))
|
|
|
|
def broadcast_to(*loggers)
|
|
|
|
@broadcasts.concat(loggers)
|
|
|
|
end
|
|
|
|
|
|
|
|
# Remove a logger from the broadcast. When a logger is removed, messages sent to
|
|
|
|
# the broadcast will no longer be written to its sink.
|
|
|
|
#
|
|
|
|
# sink = Logger.new(STDOUT)
|
|
|
|
# broadcast_logger = ActiveSupport::BroadcastLogger.new
|
|
|
|
#
|
|
|
|
# broadcast_logger.stop_broadcasting_to(sink)
|
|
|
|
def stop_broadcasting_to(logger)
|
|
|
|
@broadcasts.delete(logger)
|
|
|
|
end
|
|
|
|
|
|
|
|
def level
|
|
|
|
@broadcasts.map(&:level).min
|
|
|
|
end
|
|
|
|
|
|
|
|
def <<(message)
|
|
|
|
dispatch { |logger| logger.<<(message) }
|
|
|
|
end
|
|
|
|
|
|
|
|
def add(*args, &block)
|
|
|
|
dispatch { |logger| logger.add(*args, &block) }
|
|
|
|
end
|
|
|
|
alias_method :log, :add
|
|
|
|
|
|
|
|
def debug(*args, &block)
|
|
|
|
dispatch { |logger| logger.debug(*args, &block) }
|
|
|
|
end
|
|
|
|
|
|
|
|
def info(*args, &block)
|
|
|
|
dispatch { |logger| logger.info(*args, &block) }
|
|
|
|
end
|
|
|
|
|
|
|
|
def warn(*args, &block)
|
|
|
|
dispatch { |logger| logger.warn(*args, &block) }
|
|
|
|
end
|
|
|
|
|
|
|
|
def error(*args, &block)
|
|
|
|
dispatch { |logger| logger.error(*args, &block) }
|
|
|
|
end
|
|
|
|
|
|
|
|
def fatal(*args, &block)
|
|
|
|
dispatch { |logger| logger.fatal(*args, &block) }
|
|
|
|
end
|
|
|
|
|
|
|
|
def unknown(*args, &block)
|
|
|
|
dispatch { |logger| logger.unknown(*args, &block) }
|
|
|
|
end
|
|
|
|
|
|
|
|
def formatter=(formatter)
|
|
|
|
dispatch { |logger| logger.formatter = formatter }
|
|
|
|
|
|
|
|
@formatter = formatter
|
|
|
|
end
|
|
|
|
|
|
|
|
def level=(level)
|
|
|
|
dispatch { |logger| logger.level = level }
|
|
|
|
end
|
|
|
|
alias_method :sev_threshold=, :level=
|
|
|
|
|
|
|
|
def local_level=(level)
|
|
|
|
dispatch do |logger|
|
|
|
|
logger.local_level = level if logger.respond_to?(:local_level=)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def close
|
|
|
|
dispatch { |logger| logger.close }
|
|
|
|
end
|
|
|
|
|
|
|
|
# +True+ if the log level allows entries with severity Logger::DEBUG to be written
|
|
|
|
# to at least one broadcast. +False+ otherwise.
|
|
|
|
def debug?
|
|
|
|
@broadcasts.any? { |logger| logger.debug? }
|
|
|
|
end
|
|
|
|
|
|
|
|
# Sets the log level to Logger::DEBUG for the whole broadcast.
|
|
|
|
def debug!
|
|
|
|
dispatch { |logger| logger.debug! }
|
|
|
|
end
|
|
|
|
|
|
|
|
# +True+ if the log level allows entries with severity Logger::INFO to be written
|
|
|
|
# to at least one broadcast. +False+ otherwise.
|
|
|
|
def info?
|
|
|
|
@broadcasts.any? { |logger| logger.info? }
|
|
|
|
end
|
|
|
|
|
|
|
|
# Sets the log level to Logger::INFO for the whole broadcast.
|
|
|
|
def info!
|
|
|
|
dispatch { |logger| logger.info! }
|
|
|
|
end
|
|
|
|
|
|
|
|
# +True+ if the log level allows entries with severity Logger::WARN to be written
|
|
|
|
# to at least one broadcast. +False+ otherwise.
|
|
|
|
def warn?
|
|
|
|
@broadcasts.any? { |logger| logger.warn? }
|
|
|
|
end
|
|
|
|
|
|
|
|
# Sets the log level to Logger::WARN for the whole broadcast.
|
|
|
|
def warn!
|
|
|
|
dispatch { |logger| logger.warn! }
|
|
|
|
end
|
|
|
|
|
|
|
|
# +True+ if the log level allows entries with severity Logger::ERROR to be written
|
|
|
|
# to at least one broadcast. +False+ otherwise.
|
|
|
|
def error?
|
|
|
|
@broadcasts.any? { |logger| logger.error? }
|
|
|
|
end
|
|
|
|
|
|
|
|
# Sets the log level to Logger::ERROR for the whole broadcast.
|
|
|
|
def error!
|
|
|
|
dispatch { |logger| logger.error! }
|
|
|
|
end
|
|
|
|
|
|
|
|
# +True+ if the log level allows entries with severity Logger::FATAL to be written
|
|
|
|
# to at least one broadcast. +False+ otherwise.
|
|
|
|
def fatal?
|
|
|
|
@broadcasts.any? { |logger| logger.fatal? }
|
|
|
|
end
|
|
|
|
|
|
|
|
# Sets the log level to Logger::FATAL for the whole broadcast.
|
|
|
|
def fatal!
|
|
|
|
dispatch { |logger| logger.fatal! }
|
|
|
|
end
|
|
|
|
|
2023-10-20 14:26:03 +00:00
|
|
|
def initialize_copy(other)
|
|
|
|
@broadcasts = []
|
|
|
|
@progname = other.progname.dup
|
|
|
|
@formatter = other.formatter.dup
|
|
|
|
|
|
|
|
broadcast_to(*other.broadcasts.map(&:dup))
|
|
|
|
end
|
|
|
|
|
2023-06-30 00:09:47 +00:00
|
|
|
private
|
|
|
|
def dispatch(&block)
|
|
|
|
@broadcasts.each { |logger| block.call(logger) }
|
2024-05-29 14:34:04 +00:00
|
|
|
true
|
2023-06-30 00:09:47 +00:00
|
|
|
end
|
2023-09-28 13:53:28 +00:00
|
|
|
|
2024-01-16 12:02:12 +00:00
|
|
|
def method_missing(name, ...)
|
2023-09-28 13:53:28 +00:00
|
|
|
loggers = @broadcasts.select { |logger| logger.respond_to?(name) }
|
|
|
|
|
|
|
|
if loggers.none?
|
2024-01-16 12:02:12 +00:00
|
|
|
super
|
2023-09-28 13:53:28 +00:00
|
|
|
elsif loggers.one?
|
2024-01-16 12:02:12 +00:00
|
|
|
loggers.first.send(name, ...)
|
2023-09-28 13:53:28 +00:00
|
|
|
else
|
2024-01-16 12:02:12 +00:00
|
|
|
loggers.map { |logger| logger.send(name, ...) }
|
2023-09-28 13:53:28 +00:00
|
|
|
end
|
|
|
|
end
|
2023-10-02 15:35:01 +00:00
|
|
|
|
|
|
|
def respond_to_missing?(method, include_all)
|
|
|
|
@broadcasts.any? { |logger| logger.respond_to?(method, include_all) }
|
|
|
|
end
|
2023-06-30 00:09:47 +00:00
|
|
|
end
|
|
|
|
end
|