# # Author:: Adam Jacob () # Author:: Christopher Brown () # Copyright:: Copyright (c) 2008-2016 Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "logger" require "mixlib/log/version" require "mixlib/log/formatter" module Mixlib module Log @logger, @loggers = nil LEVELS = { :debug => Logger::DEBUG, :info => Logger::INFO, :warn => Logger::WARN, :error => Logger::ERROR, :fatal => Logger::FATAL }.freeze LEVEL_NAMES = LEVELS.invert.freeze def reset! close! @logger, @loggers = nil, nil end # An Array of log devices that will be logged to. Defaults to just the default # @logger log device, but you can push to this array to add more devices. def loggers @loggers ||= [logger] end ## # init always returns a configured logger # and creates a new one if it doesn't yet exist ## def logger @logger || init end # Sets the log device to +new_log_device+. Any additional loggers # that had been added to the +loggers+ array will be cleared. def logger=(new_log_device) reset! @logger = new_log_device end def use_log_devices(other) if other.respond_to?(:loggers) && other.respond_to?(:logger) @loggers = other.loggers @logger = other.logger elsif other.kind_of?(Array) @loggers = other @logger = other.first else msg = "#use_log_devices takes a Mixlib::Log object or array of log devices. " << "You gave: #{other.inspect}" raise ArgumentError, msg end @configured = true end # Use Mixlib::Log.init when you want to set up the logger manually. Arguments to this method # get passed directly to Logger.new, so check out the documentation for the standard Logger class # to understand what to do here. # # If this method is called with no arguments, it will log to STDOUT at the :warn level. # # It also configures the Logger instance it creates to use the custom Mixlib::Log::Formatter class. def init(*opts) reset! @logger = logger_for(*opts) @logger.formatter = Mixlib::Log::Formatter.new() if @logger.respond_to?(:formatter=) @logger.level = Logger::WARN @configured = true @logger end # Let the application query if logging objects have been set up def configured? @configured end # Sets the level for the Logger object by symbol. Valid arguments are: # # :debug # :info # :warn # :error # :fatal # # Throws an ArgumentError if you feed it a bogus log level. def level=(new_level) level_int = LEVEL_NAMES.key?(new_level) ? new_level : LEVELS[new_level] raise ArgumentError, "Log level must be one of :debug, :info, :warn, :error, or :fatal" if level_int.nil? loggers.each { |l| l.level = level_int } end def level(new_level = nil) if new_level.nil? LEVEL_NAMES[logger.level] else self.level = (new_level) end end # Define the standard logger methods on this class programmatically. # No need to incur method_missing overhead on every log call. [:debug, :info, :warn, :error, :fatal].each do |method_name| class_eval(<<-METHOD_DEFN, __FILE__, __LINE__) def #{method_name}(msg=nil, &block) loggers.each {|l| l.#{method_name}(msg, &block) } end METHOD_DEFN end # Define the methods to interrogate the logger for the current log level. # Note that we *only* query the default logger (@logger) and not any other # loggers that may have been added, even though it is possible to configure # two (or more) loggers at different log levels. [:debug?, :info?, :warn?, :error?, :fatal?].each do |method_name| class_eval(<<-METHOD_DEFN, __FILE__, __LINE__) def #{method_name} logger.#{method_name} end METHOD_DEFN end def <<(msg) loggers.each { |l| l << msg } end def add(severity, message = nil, progname = nil, &block) loggers.each { |l| l.add(severity, message, progname, &block) } end alias :log :add # Passes any other method calls on directly to the underlying Logger object created with init. If # this method gets hit before a call to Mixlib::Logger.init has been made, it will call # Mixlib::Logger.init() with no arguments. def method_missing(method_symbol, *args, &block) loggers.each { |l| l.send(method_symbol, *args, &block) } end private def logger_for(*opts) if opts.empty? Logger.new(STDOUT) elsif LEVELS.keys.inject(true) { |quacks, level| quacks && opts.first.respond_to?(level) } opts.first else Logger.new(*opts) end end def all_loggers [@logger, *@loggers].uniq end # select all loggers with File log devices def loggers_to_close loggers_to_close = [] all_loggers.each do |logger| # unfortunately Logger does not provide access to the logdev # via public API. In order to reduce amount of impact and # handle only File type log devices I had to use this method # to get access to it. next unless logdev = logger.instance_variable_get(:"@logdev") loggers_to_close << logger if logdev.filename end loggers_to_close end def close! # try to close all file loggers loggers_to_close.each do |l| l.close rescue nil end end end end