File Manager
Viewing File: log.rb
#
# Author:: Adam Jacob (<adam@chef.io>)
# Author:: Christopher Brown (<cb@chef.io>)
# 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