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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
|
#
# 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
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
@logger
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
|