# frozen_string_literal: true require 'logger' module LogTribe class Loggers < ::Logger attr_reader :loggers, :tag_name # Logging severity threshold (e.g. ::Logger::INFO). attr_reader :level # See #level def level=(value) @level = value @loggers.each do |logger| next unless logger logger.level = value if logger.respond_to?(:level) end end # Program name to include in log messages. attr_reader :progname # See #progname def progname=(value) @progname = value @loggers.each do |logger| next unless logger logger.progname = value if logger.respond_to?(:progname) end end # Returns the date format being used. See #datetime_format= attr_reader :datetime_format # Set date-time format. # # +datetime_format+:: A string suitable for passing to +strftime+. def datetime_format=(datetime_format) @datetime_format = datetime_format @loggers.each do |logger| next unless logger logger.datetime_format = datetime_format if logger.respond_to?(:datetime_format) end end # Logging formatter, as a +Proc+ that will take four arguments and # return the formatted message. The arguments are: # # +severity+:: The Severity of the log message. # +time+:: A Time instance representing when the message was logged. # +progname+:: The #progname configured, or passed to the logger method. # +msg+:: The _Object_ the user passed to the log message; not necessarily a # String. # # The block should return an Object that can be written to the logging # device via +write+. The default formatter is used when no formatter is # set. attr_reader :formatter # See #formatter def formatter=(callable) @formatter = callable @loggers.each do |logger| next unless logger logger.formatter = callable if logger.respond_to?(:formatter) end end # See ::Logger#Formatter attr_reader :default_formatter # :call-seq: # LogTribe::Loggers.new(log_or_logs_array, options) # # Create a multi log objects manager in order to send message through multiple destination loggers. # # === Attributes # # * +log_or_logs_array+ - A simple log object or an array of logger objects. # * +options+ - Options used in logger objects which makes sense. # # === Options # # * +:tag_name+ - Name of the tag for logger like FluentLogger (Fluentd). # # === Examples # # Build log's tribe: # # LogTribe::Loggers.new([Logger.new(STDOUT), Fluent::Logger::FluentLogger.new(nil, host: 'srv', port: 10_010)], { tag_name: 'app_name.app_type' }) # LogTribe::Loggers.new(Logger.new(STDOUT)) # LogTribe::Loggers.new(Fluent::Logger::FluentLogger.new(nil, host: 'srv', port: 10_010), { tag_name: 'app_name.app_type' }) # LogTribe::Loggers.new(Fluent::Logger::FluentLogger.new(nil, host: 'srv', port: 10_010)) # def initialize(log_or_logs_array, options = {}) @loggers = Array(log_or_logs_array) @tag_name = options.delete(:tag_name) @level = DEBUG @progname = nil @datetime_format = nil @formatter = nil @default_formatter = Formatter.new end # # :call-seq: # Logger#add(severity, message = nil, progname = nil) { ... } # # === Args # # +severity+:: # Severity. Constants are defined in Logger namespace: +DEBUG+, +INFO+, # +WARN+, +ERROR+, +FATAL+, or +UNKNOWN+. # +message+:: # The log message. A String or Exception. # +progname+:: # Program name string. Can be omitted. Treated as a message if no # +message+ and +block+ are given. # +block+:: # Can be omitted. Called to get a message string if +message+ is nil. # # === Return # # When the given severity is not high enough (for this particular logger), # log no message, and return +true+. # # === Description # # Log a message if the given severity is high enough. This is the generic # logging method. Users will be more inclined to use #debug, #info, #warn, # #error, and #fatal. # # Message format: +message+ can be any object, but it has to be # converted to a String in order to log it. Generally, +inspect+ is used # if the given object is not a String. # A special case is an +Exception+ object, which will be printed in detail, # including message, class, and backtrace. See #msg2str for the # implementation if required. # # === Bugs # # * Logfile is not locked. # * Append open does not need to lock file. # * If the OS supports multi I/O, records possibly may be mixed. # def add(severity, message = nil, progname = nil, &block) @loggers.each do |logger| next unless logger if logger.respond_to?(:add) logger.add(severity, message, progname, &block) elsif defined?(::Fluent::Logger) && logger.respond_to?(:post) # FluentLogger logger.post(@tag_name || 'none'.freeze, message: format_message(format_severity(severity), Time.now, progname, message) ) end end end # alias_method :log, :add # # Dump given message to the log device without any formatting. If no log # device exists, return +nil+. # def <<(msg) @loggers.each do |logger| next unless logger if logger.respond_to?(:<<) logger << msg elsif defined?(::Fluent::Logger) && logger.respond_to?(:post) # FluentLogger logger.post(@tag_name || 'none'.freeze, message: msg) end end end # # Close the logging devices. # def close @loggers.each do |logger| next unless logger log_device = logger.instance_variable_get('@logdev') next unless log_device log_device.flush if log_device.respond_to?(:flush) logger.close if log_device.respond_to?(:stat) end end end end