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 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300
|
# A module to make logging a bit easier.
require_relative '../../puppet/util/log'
require_relative '../../puppet/error'
module Puppet::Util
module Logging
def send_log(level, message)
Puppet::Util::Log.create({:level => level, :source => log_source, :message => message}.merge(log_metadata))
end
# Create a method for each log level.
Puppet::Util::Log.eachlevel do |level|
# handle debug a special way for performance reasons
next if level == :debug
define_method(level) do |args|
args = args.join(" ") if args.is_a?(Array)
send_log(level, args)
end
end
# Output a debug log message if debugging is on (but only then)
# If the output is anything except a static string, give the debug
# a block - it will be called with all other arguments, and is expected
# to return the single string result.
#
# Use a block at all times for increased performance.
#
# @example This takes 40% of the time compared to not using a block
# Puppet.debug { "This is a string that interpolated #{x} and #{y} }"
#
def debug(*args)
return nil unless Puppet::Util::Log.level == :debug
if block_given?
send_log(:debug, yield(*args))
else
send_log(:debug, args.join(" "))
end
end
# Log an exception via Puppet.err. Will also log the backtrace if Puppet[:trace] is set.
# Parameters:
# [exception] an Exception to log
# [message] an optional String overriding the message to be logged; by default, we log Exception.message.
# If you pass a String here, your string will be logged instead. You may also pass nil if you don't
# wish to log a message at all; in this case it is likely that you are only calling this method in order
# to take advantage of the backtrace logging.
def log_exception(exception, message = :default, options = {})
level = options[:level] || :err
combined_trace = Puppet[:trace] || options[:trace]
puppet_trace = Puppet[:puppet_trace] || options[:puppet_trace]
if message == :default && exception.is_a?(Puppet::ParseErrorWithIssue)
# Retain all detailed info and keep plain message and stacktrace separate
backtrace = build_exception_trace(exception, combined_trace, puppet_trace)
Puppet::Util::Log.create({
:level => level,
:source => log_source,
:message => exception.basic_message,
:issue_code => exception.issue_code,
:backtrace => backtrace.empty? ? nil : backtrace,
:file => exception.file,
:line => exception.line,
:pos => exception.pos,
:environment => exception.environment,
:node => exception.node
}.merge(log_metadata))
else
send_log(level, format_exception(exception, message, combined_trace, puppet_trace))
end
end
def build_exception_trace(exception, combined_trace = true, puppet_trace = false)
built_trace = format_backtrace(exception, combined_trace, puppet_trace)
if exception.respond_to?(:original)
original = exception.original
unless original.nil?
built_trace << _('Wrapped exception:')
built_trace << original.message
built_trace += build_exception_trace(original, combined_trace, puppet_trace)
end
end
built_trace
end
private :build_exception_trace
def format_exception(exception, message = :default, combined_trace = true, puppet_trace = false)
arr = []
case message
when :default
arr << exception.message
when nil
# don't log anything if they passed a nil; they are just calling for the optional backtrace logging
else
arr << message
end
arr += format_backtrace(exception, combined_trace, puppet_trace)
if exception.respond_to?(:original) and exception.original
arr << _("Wrapped exception:")
arr << format_exception(exception.original, :default, combined_trace, puppet_trace)
end
arr.flatten.join("\n")
end
def format_backtrace(exception, combined_trace, puppet_trace)
puppetstack = exception.respond_to?(:puppetstack) ? exception.puppetstack : []
if combined_trace and exception.backtrace
Puppet::Util.format_backtrace_array(exception.backtrace, puppetstack)
elsif puppet_trace && !puppetstack.empty?
Puppet::Util.format_backtrace_array(puppetstack)
else
[]
end
end
def log_and_raise(exception, message)
log_exception(exception, message)
raise exception, message + "\n" + exception.to_s, exception.backtrace
end
class DeprecationWarning < Exception; end
# Logs a warning indicating that the Ruby code path is deprecated. Note that
# this method keeps track of the offending lines of code that triggered the
# deprecation warning, and will only log a warning once per offending line of
# code. It will also stop logging deprecation warnings altogether after 100
# unique deprecation warnings have been logged. Finally, if
# Puppet[:disable_warnings] includes 'deprecations', it will squelch all
# warning calls made via this method.
#
# @param message [String] The message to log (logs via warning)
# @param key [String] Optional key to mark the message as unique. If not
# passed in, the originating call line will be used instead.
def deprecation_warning(message, key = nil)
issue_deprecation_warning(message, key, nil, nil, true)
end
# Logs a warning whose origin comes from Puppet source rather than somewhere
# internal within Puppet. Otherwise the same as deprecation_warning()
#
# @param message [String] The message to log (logs via warning)
# @param options [Hash]
# @option options [String] :file File we are warning from
# @option options [Integer] :line Line number we are warning from
# @option options [String] :key (:file + :line) Alternative key used to mark
# warning as unique
#
# Either :file and :line and/or :key must be passed.
def puppet_deprecation_warning(message, options = {})
key = options[:key]
file = options[:file]
line = options[:line]
#TRANSLATORS the literals ":file", ":line", and ":key" should not be translated
raise Puppet::DevError, _("Need either :file and :line, or :key") if (key.nil?) && (file.nil? || line.nil?)
key ||= "#{file}:#{line}"
issue_deprecation_warning(message, key, file, line, false)
end
# Logs a (non deprecation) warning once for a given key.
#
# @param kind [String] The kind of warning. The
# kind must be one of the defined kinds for the Puppet[:disable_warnings] setting.
# @param message [String] The message to log (logs via warning)
# @param key [String] Key used to make this warning unique
# @param file [String,:default,nil] the File related to the warning
# @param line [Integer,:default,nil] the Line number related to the warning
# warning as unique
# @param level [Symbol] log level to use, defaults to :warning
#
# Either :file and :line and/or :key must be passed.
def warn_once(kind, key, message, file = nil, line = nil, level = :warning)
return if Puppet[:disable_warnings].include?(kind)
$unique_warnings ||= {}
if $unique_warnings.length < 100 then
if (! $unique_warnings.has_key?(key)) then
$unique_warnings[key] = message
call_trace = if file == :default and line == :default
# Suppress the file and line number output
''
else
error_location_str = Puppet::Util::Errors.error_location(file, line)
if error_location_str.empty?
"\n " + _('(file & line not available)')
else
"\n %{error_location}" % { error_location: error_location_str }
end
end
send_log(level, "#{message}#{call_trace}")
end
end
end
def get_deprecation_offender()
# we have to put this in its own method to simplify testing; we need to be able to mock the offender results in
# order to test this class, and our framework does not appear to enjoy it if you try to mock Kernel.caller
#
# let's find the offending line; we need to jump back up the stack a few steps to find the method that called
# the deprecated method
if Puppet[:trace]
caller(3)
else
[caller(3, 1).first]
end
end
def clear_deprecation_warnings
$unique_warnings.clear if $unique_warnings
$deprecation_warnings.clear if $deprecation_warnings
end
# TODO: determine whether there might be a potential use for adding a puppet configuration option that would
# enable this deprecation logging.
# utility method that can be called, e.g., from spec_helper config.after, when tracking down calls to deprecated
# code.
# Parameters:
# [deprecations_file] relative or absolute path of a file to log the deprecations to
# [pattern] (default nil) if specified, will only log deprecations whose message matches the provided pattern
def log_deprecations_to_file(deprecations_file, pattern = nil)
# this method may get called lots and lots of times (e.g., from spec_helper config.after) without the global
# list of deprecation warnings being cleared out. We don't want to keep logging the same offenders over and over,
# so, we need to keep track of what we've logged.
#
# It'd be nice if we could just clear out the list of deprecation warnings, but then the very next spec might
# find the same offender, and we'd end up logging it again.
$logged_deprecation_warnings ||= {}
# Deprecation messages are UTF-8 as they are produced by Ruby
Puppet::FileSystem.open(deprecations_file, nil, "a:UTF-8") do |f|
if ($deprecation_warnings) then
$deprecation_warnings.each do |offender, message|
if (! $logged_deprecation_warnings.has_key?(offender)) then
$logged_deprecation_warnings[offender] = true
if ((pattern.nil?) || (message =~ pattern)) then
f.puts(message)
f.puts(offender)
f.puts()
end
end
end
end
end
end
# Sets up Facter logging.
# This method causes Facter output to be forwarded to Puppet.
def self.setup_facter_logging!
Puppet.runtime[:facter]
true
end
private
def issue_deprecation_warning(message, key, file, line, use_caller)
return if Puppet[:disable_warnings].include?('deprecations')
$deprecation_warnings ||= {}
if $deprecation_warnings.length < 100
key ||= (offender = get_deprecation_offender)
unless $deprecation_warnings.has_key?(key)
$deprecation_warnings[key] = message
# split out to allow translation
call_trace = if use_caller
_("(location: %{location})") % { location: (offender || get_deprecation_offender).join('; ') }
else
Puppet::Util::Errors.error_location_with_unknowns(file, line)
end
warning("%{message}\n %{call_trace}" % { message: message, call_trace: call_trace })
end
end
end
def is_resource?
defined?(Puppet::Type) && is_a?(Puppet::Type)
end
def is_resource_parameter?
defined?(Puppet::Parameter) && is_a?(Puppet::Parameter)
end
def log_metadata
[:file, :line, :tags].inject({}) do |result, attr|
result[attr] = send(attr) if respond_to?(attr)
result
end
end
def log_source
# We need to guard the existence of the constants, since this module is used by the base Puppet module.
(is_resource? or is_resource_parameter?) and respond_to?(:path) and return path.to_s
to_s
end
end
end
|