3
# Pass feedback to the user. Log levels are modeled after syslog's, and it is
4
# expected that that will be the most common log destination. Supports
5
# multiple destinations, one of which is a remote server.
6
class Puppet::Util::Log
9
@levels = [:debug,:info,:notice,:warning,:err,:alert,:emerg,:crit]
14
# A type of log destination.
24
# Mark the things we're supposed to match.
30
# See whether we match a given thing.
32
# Convert single-word strings into symbols like :console and :syslog
33
if obj.is_a? String and obj =~ /^\w+$/
34
obj = obj.downcase.intern
37
@matches.each do |thing|
38
# Search for direct matches or class matches
39
return true if thing === obj or thing == obj.class.to_s
48
return self.class.name
52
# Set how to handle a message.
53
def self.sethandler(&block)
54
define_method(:handle, &block)
57
# Mark how to initialize our object.
58
def self.setinit(&block)
59
define_method(:initialize, &block)
63
# Create a new destination type.
64
def self.newdesttype(name, options = {}, &block)
65
dest = genclass(name, :parent => Destination, :prefix => "Dest",
68
:attributes => options
79
include Puppet::Util::ClassGen
82
# Reset all logs to basics. Basically just closes all files and undefs
83
# all of the other objects.
84
def Log.close(dest = nil)
86
if @destinations.include?(dest)
87
if @destinations.respond_to?(:close)
88
@destinations[dest].close
90
@destinations.delete(dest)
93
@destinations.each { |name, dest|
94
if dest.respond_to?(:flush)
97
if dest.respond_to?(:close)
105
# Flush any log destinations that support such operations.
107
@destinations.each { |type, dest|
108
if dest.respond_to?(:flush)
114
# Create a new log message. The primary role of this method is to
115
# avoid creating log messages below the loglevel.
117
unless hash.include?(:level)
118
raise Puppet::DevError, "Logs require a level"
120
unless @levels.index(hash[:level])
121
raise Puppet::DevError, "Invalid log level %s" % hash[:level]
123
if @levels.index(hash[:level]) >= @loglevel
124
return Puppet::Util::Log.new(hash)
131
return @destinations.keys
134
# Yield each valid level in turn
136
@levels.each { |level| yield level }
139
# Return the current log level.
141
return @levels[@loglevel]
144
# Set the current log level.
145
def Log.level=(level)
146
unless level.is_a?(Symbol)
150
unless @levels.include?(level)
151
raise Puppet::DevError, "Invalid loglevel %s" % level
154
@loglevel = @levels.index(level)
161
newdesttype :syslog do
171
name = "puppet-#{name}" unless name =~ /puppet/
173
options = Syslog::LOG_PID | Syslog::LOG_NDELAY
175
# XXX This should really be configurable.
176
str = Puppet[:syslogfacility]
178
facility = Syslog.const_get("LOG_#{str.upcase}")
180
raise Puppet::Error, "Invalid syslog facility %s" % str
183
@syslog = Syslog.open(name, options, facility)
187
# XXX Syslog currently has a bug that makes it so you
188
# cannot log a message with a '%' in it. So, we get rid
190
if msg.source == "Puppet"
191
@syslog.send(msg.level, msg.to_s.gsub("%", '%%'))
193
@syslog.send(msg.level, "(%s) %s" %
194
[msg.source.to_s.gsub("%", ""),
195
msg.to_s.gsub("%", '%%')
220
# first make sure the directory exists
221
# We can't just use 'Config.use' here, because they've
222
# specified a "special" destination.
223
unless FileTest.exist?(File.dirname(path))
224
Puppet.recmkdir(File.dirname(path))
225
Puppet.info "Creating log directory %s" % File.dirname(path)
228
# create the log file, if it doesn't already exist
229
file = File.open(path, File::WRONLY|File::CREAT|File::APPEND)
233
@autoflush = Puppet[:autoflush]
237
@file.puts("%s %s (%s): %s" %
238
[msg.time, msg.source, msg.level, msg.to_s])
240
@file.flush if @autoflush
244
newdesttype :console do
247
PINK = {:console => "[0;31m", :html => "FFA0A0"}
248
GREEN = {:console => "[0;32m", :html => "00CD00"}
249
YELLOW = {:console => "[0;33m", :html => "FFFF60"}
250
SLATE = {:console => "[0;34m", :html => "80A0FF"}
251
ORANGE = {:console => "[0;35m", :html => "FFA500"}
252
BLUE = {:console => "[0;36m", :html => "40FFFF"}
253
RESET = {:console => "[0m", :html => ""}
266
def colorize(level, str)
269
when true, :ansi, "ansi": console_color(level, str)
270
when :html, "html": html_color(level, str)
274
def console_color(level, str)
275
@@colormap[level][:console] + str + RESET[:console]
278
def html_color(level, str)
279
%{<span style="color: %s">%s</span>} % [@@colormap[level][:html], str]
283
# Flush output immediately.
288
if msg.source == "Puppet"
289
puts colorize(msg.level, "%s: %s" % [msg.level, msg.to_s])
291
puts colorize(msg.level, "%s: %s: %s" % [msg.level, msg.source, msg.to_s])
298
Puppet.info "Treating %s as a hostname" % host
302
args[:Server] = host.sub(/:\d+/, '')
309
@driver = Puppet::Network::Client::LogClient.new(args)
313
unless msg.is_a?(String) or msg.remote
314
unless defined? @hostname
315
@hostname = Facter["hostname"].value
317
unless defined? @domain
318
@domain = Facter["domain"].value
320
@hostname += "." + @domain
323
if msg.source =~ /^\//
324
msg.source = @hostname + ":" + msg.source
325
elsif msg.source == "Puppet"
326
msg.source = @hostname + " " + msg.source
328
msg.source = @hostname + " " + msg.source
331
#puts "would have sent %s" % msg
332
#puts "would have sent %s" %
333
# CGI.escape(YAML.dump(msg))
335
tmp = CGI.escape(YAML.dump(msg))
337
puts "Could not dump: %s" % detail.to_s
340
# Add the hostname to the source
344
puts detail.backtrace
347
Puppet::Util::Log.close(self)
353
# Log to a transaction report.
354
newdesttype :report do
355
match "Puppet::Transaction::Report"
357
def initialize(report)
362
# Only add messages from objects, since anything else is
363
# probably unrelated to this run.
370
# Log to an array, just for testing.
371
newdesttype :array do
374
def initialize(array)
383
# Create a new log destination.
384
def Log.newdestination(dest)
385
# Each destination can only occur once.
386
if @destinations.find { |name, obj| obj.name == dest }
390
name, type = @desttypes.find do |name, klass|
395
raise Puppet::DevError, "Unknown destination type %s" % dest
399
if type.instance_method(:initialize).arity == 1
400
@destinations[dest] = type.new(dest)
402
@destinations[dest] = type.new()
406
puts detail.backtrace
409
# If this was our only destination, then add the console back in.
410
if @destinations.empty? and (dest != :console and dest != "console")
411
newdestination(:console)
416
# Route the actual message. FIXME There are lots of things this method
417
# should do, like caching, storing messages when there are not yet
418
# destinations, a bit more. It's worth noting that there's a potential
419
# for a loop here, if the machine somehow gets the destination set as
421
def Log.newmessage(msg)
422
if @levels.index(msg.level) < @loglevel
426
@destinations.each do |name, dest|
433
def Log.sendlevel?(level)
434
@levels.index(level) >= @loglevel
437
# Reopen all of our logs.
439
Puppet.notice "Reopening log files"
440
types = @destinations.keys
441
@destinations.each { |type, dest|
442
if dest.respond_to?(:close)
447
# We need to make sure we always end up with some kind of destination
450
Log.newdestination(type)
453
if @destinations.empty?
454
Log.newdestination(:syslog)
455
Puppet.err detail.to_s
460
# Is the passed level a valid log level?
461
def self.validlevel?(level)
462
@levels.include?(level)
465
attr_accessor :level, :message, :time, :tags, :remote
469
unless args.include?(:level) && args.include?(:message)
470
raise Puppet::DevError, "Puppet::Util::Log called incorrectly"
473
if args[:level].class == String
474
@level = args[:level].intern
475
elsif args[:level].class == Symbol
476
@level = args[:level]
478
raise Puppet::DevError,
479
"Level is not a string or symbol: #{args[:level].class}"
482
# Just return unless we're actually at a level we should send
483
#return unless self.class.sendlevel?(@level)
485
@message = args[:message].to_s
487
# this should include the host name, and probly lots of other
488
# stuff, at some point
489
unless self.class.validlevel?(level)
490
raise Puppet::DevError, "Invalid message level #{level}"
493
if args.include?(:tags)
497
if args.include?(:source)
498
self.source = args[:source]
506
# Was the source of this log an object?
508
if defined? @objectsource and @objectsource
515
# If they pass a source in to us, we make sure it is a string, and
516
# we retrieve any tags we can.
518
# We can't store the actual source, we just store the path.
519
# We can't just check for whether it responds to :path, because
520
# plenty of providers respond to that in their normal function.
521
if (source.is_a?(Puppet::Type) or source.is_a?(Puppet::Parameter)) and source.respond_to?(:path)
523
@source = source.path
525
@objectsource = false
526
@source = source.to_s
528
unless defined? @tags and @tags
529
if source.respond_to?(:tags)
536
@tags.detect { |t| t.to_s == tag.to_s }
540
"%s %s (%s): %s" % [self.time, self.source, self.level, self.to_s]
547
Puppet::Log = Puppet::Util::Log