1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137
|
# frozen_string_literal: true
module Lumberjack
# A logging device that forwards log entries to another logger instance.
# This device enables hierarchical logging architectures and broadcasting scenarios
# where log entries need to be distributed to multiple loggers or processed through
# different logging pipelines.
#
# The device is particularly useful when combined with Device::Multi to create
# master loggers that can simultaneously write to multiple destinations (files,
# databases, external services) while maintaining consistent formatting and
# attribute handling across all targets.
#
# Unlike other devices that write directly to output streams, this device delegates
# to another logger's processing pipeline, allowing for complex logging topologies
# and reuse of existing logger configurations.
#
# @example Basic logger forwarding
# file_logger = Lumberjack::Logger.new("/var/log/app.log")
# logger_device = Lumberjack::Device::LoggerWrapper.new(file_logger)
#
# @example Broadcasting with Multi device
# main_logger = Lumberjack::Logger.new("/var/log/main.log")
# error_logger = Lumberjack::Logger.new("/var/log/errors.log")
#
# broadcast_device = Lumberjack::Device::Multi.new([
# Lumberjack::Device::LoggerWrapper.new(main_logger),
# Lumberjack::Device::LoggerWrapper.new(error_logger)
# ])
#
# master_logger = Lumberjack::Logger.new(broadcast_device)
#
# @example Hierarchical logging with filtering
# # Main application logger
# app_logger = Lumberjack::Logger.new("/var/log/app.log")
#
# # Focused error logs
# error_logger = Lumberjack::Logger.new("/var/log/error.log")
# error_logger.level = Logger::WARN
#
# # Route all logs to app, error logs to error logger
# multi_device = Lumberjack::Device::Multi.new([
# Lumberjack::Device::LoggerWrapper.new(app_logger),
# Lumberjack::Device::LoggerWrapper.new(error_logger)
# ])
#
# @see Device::Multi
# @see Lumberjack::Logger
# @see Lumberjack::ContextLogger
class Device::LoggerWrapper < Device
# @!attribute [r] logger
# @return [Lumberjack::ContextLogger] The target logger that will receive forwarded log entries
attr_reader :logger
# Initialize a new Logger device that forwards entries to the specified logger.
# The target logger must be a Lumberjack logger that supports the ContextLogger
# interface to ensure proper entry handling and attribute processing.
#
# @param logger [Lumberjack::ContextLogger, ::Logger] The target logger to receive forwarded entries.
# Must be a Lumberjack logger instance (Logger, ForkedLogger, etc.) that includes
# the ContextLogger mixin for proper entry processing.
#
# @raise [ArgumentError] If the provided logger is not a Lumberjack::ContextLogger
def initialize(logger)
unless logger.is_a?(Lumberjack::ContextLogger) || logger.is_a?(::Logger)
raise ArgumentError.new("Logger must be a Lumberjack logger")
end
@logger = logger
end
# Forward a log entry to the target logger for processing. This method extracts
# the entry components and delegates to the target logger's add_entry method,
# ensuring that all attributes, formatting, and processing logic of the target
# logger are properly applied.
#
# The forwarded entry maintains all original metadata including severity,
# timestamp, program name, and custom attributes, allowing the target logger
# to process it as if it were generated directly.
#
# @param entry [Lumberjack::LogEntry] The log entry to forward to the target logger
# @return [void]
def write(entry)
if @logger.is_a?(Lumberjack::ContextLogger)
@logger.add_entry(entry.severity, entry.message, entry.progname, entry.attributes)
else
message = entry.message
if entry.attributes && !entry.attributes.empty?
message_attributes = []
entry.attributes.each do |key, value|
next if value.nil? || (value.respond_to?(:empty?) && value.empty?)
value = value.join(",") if value.is_a?(Enumerable)
message_attributes << "[#{key}=#{value}]"
end
message = "#{message} #{message_attributes.join(" ")}" unless message_attributes.empty?
end
@logger.add(entry.severity, message, entry.progname)
end
end
# Closes the target logger to release any resources or finalize log output.
# This method delegates to the target logger's +close+ method.
#
# @return [void]
def close
@logger.close
end
# Reopen the underlying logger device. This is typically used to reopen log files
# after log rotation or to refresh the logger's output stream.
#
# Delegates to the target logger's +reopen+ method.
#
# @return [void]
def reopen
@logger.reopen
end
# Flushes the target logger, ensuring that any buffered log entries are written out.
# This delegates to the target logger's flush method, which may flush buffers to disk,
# external services, or other destinations depending on the logger's configuration.
#
# @return [void]
def flush
@logger.flush
end
# Expose the underlying stream if any.
#
# @return [IO, Lumberjack::Device, nil]
# @api private
def dev
@logger.device&.dev
end
end
end
|