# 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. 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 private def dispatch(&block) @broadcasts.each { |logger| block.call(logger) } end end end