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 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516
|
module Logging
# The +Logger+ class is the primary interface to the +Logging+ framework.
# It provides the logging methods that will be called from user methods,
# and it generates logging events that are sent to the appenders (the
# appenders take care of sending the log events to the logging
# destinations -- files, sockets, etc).
#
# +Logger+ instances are obtained from the +Repository+ and should
# not be directly created by users.
#
# Example:
#
# log = Logging.logger['my logger']
# log.add_appenders( Logging.appenders.stdout ) # append to STDOUT
# log.level = :info # log 'info' and above
#
# log.info 'starting foo operation'
# ...
# log.info 'finishing foo operation'
# ...
# log.fatal 'unknown exception', exception
#
class Logger
@mutex = Mutex.new # :nodoc:
# Returns the root logger.
def self.root
::Logging::Repository.instance[:root]
end
class << self
alias_method :instantiate, :new # `instantiate` becomes the "real" `new`
end
# Overrides the new method such that only one Logger will be created
# for any given logger name.
def self.new( *args )
args.empty? ? super : self[args.shift]
end
# Returns a logger instance for the given name.
def self.[]( name )
repo = ::Logging::Repository.instance
name = repo.to_key(name)
logger = repo[name]
return logger unless logger.nil?
@mutex.synchronize do
logger = repo[name]
return logger unless logger.nil? # thread-safe double checking
logger = instantiate(name)
repo[name] = logger
repo.children(name).each { |child| child.__send__(:parent=, logger) }
logger
end
end
# This is where the actual logging methods are defined. Two methods
# are created for each log level. The first is a query method used to
# determine if that perticular logging level is enabled. The second is
# the actual logging method that accepts a list of objects to be
# logged or a block. If a block is given, then the object returned
# from the block will be logged.
#
# Example
#
# log = Logging::Logger['my logger']
# log.level = :warn
#
# log.info? # => false
# log.warn? # => true
# log.warn 'this is your last warning'
# log.fatal 'I die!', exception
#
# log.debug do
# # expensive method to construct log message
# msg
# end
#
def self.define_log_methods( logger )
code = log_methods_for_level(logger.level)
logger._meta_eval(code, __FILE__, __LINE__)
logger
end
# This generator is used to define the log methods for the given `level`.
# This code is evaluated in the context of a Logger instance.
#
# Returns log methods as a String
def self.log_methods_for_level( level )
code = []
::Logging::LEVELS.each do |name,num|
code << <<-CODE
undef :#{name} if method_defined? :#{name}
undef :#{name}? if method_defined? :#{name}?
CODE
if level > num
code << <<-CODE
def #{name}?( ) false end
def #{name}( data = nil ) false end
CODE
else
code << <<-CODE
def #{name}?( ) true end
def #{name}( data = nil )
data = yield if block_given?
log_event(::Logging::LogEvent.new(@name, #{num}, data, @caller_tracing))
true
end
CODE
end
end
code.join("\n")
end
attr_reader :name, :parent, :additive, :caller_tracing
# call-seq:
# Logger.new( name )
# Logger[name]
#
# Returns the logger identified by _name_.
#
# When _name_ is a +String+ or a +Symbol+ it will be used "as is" to
# retrieve the logger. When _name_ is a +Class+ the class name will be
# used to retrieve the logger. When _name_ is an object the name of the
# object's class will be used to retrieve the logger.
#
# Example:
#
# obj = MyClass.new
#
# log1 = Logger.new(obj)
# log2 = Logger.new(MyClass)
# log3 = Logger['MyClass']
#
# log1.object_id == log2.object_id # => true
# log2.object_id == log3.object_id # => true
#
def initialize( name )
case name
when String
raise(ArgumentError, "logger must have a name") if name.empty?
else raise(ArgumentError, "logger name must be a String") end
repo = ::Logging::Repository.instance
_setup(name, :parent => repo.parent(name))
end
# call-seq:
# log <=> other
#
# Compares this logger by name to another logger. The normal return codes
# for +String+ objects apply.
#
def <=>( other )
case other
when self; 0
when ::Logging::RootLogger; 1
when ::Logging::Logger; @name <=> other.name
else raise ArgumentError, 'expecting a Logger instance' end
end
# call-seq:
# log << "message"
#
# Log the given message without any formatting and without performing any
# level checks. The message is logged to all appenders. The message is
# passed up the logger tree if this logger's additivity is +true+.
#
def <<( msg )
@appenders.each {|a| a << msg}
@parent << msg if @additive
end
alias_method :write, :<<
# call-seq:
# add( severity, message = nil ) {block}
#
# Log a message if the given severity is high enough. This is the generic
# logging method. Users will be more inclined to use #debug, #info, #warn,
# #error, and #fatal.
#
# <b>Message format</b>: +message+ can be any object, but it has to be
# converted to a String in order to log it. The Logging::format_as
# method is used to determine how objects chould be converted to
# strings. Generally, +inspect+ is used.
#
# A special case is an +Exception+ object, which will be printed in
# detail, including message, class, and backtrace.
#
# If a _message_ is not given, then the return value from the block is
# used as the message to log. This is useful when creating the actual
# message is an expensive operation. This allows the logger to check the
# severity against the configured level before actually constructing the
# message.
#
# This method returns +true+ if the message was logged, and +false+ is
# returned if the message was not logged.
#
def add( lvl, data = nil, progname = nil )
lvl = Integer(lvl)
return false if lvl < level
if data.nil?
if block_given?
data = yield
else
data = progname
end
end
log_event(::Logging::LogEvent.new(@name, lvl, data, @caller_tracing))
true
end
# call-seq:
# additive = true
#
# Sets the additivity of the logger. Acceptable values are +true+,
# 'true', +false+, 'false', or +nil+. In this case +nil+ does not
# change the additivity
#
def additive=( val )
@additive = case val
when true, 'true'; true
when false, 'false'; false
when nil; @additive
else raise ArgumentError, 'expecting a boolean' end
end
# call-seq:
# caller_tracing = true
#
# Sets the caller tracing of the logger. Acceptable values are +true+,
# 'true', +false+, 'false', or +nil+. In this case +nil+ does not change
# the tracing.
#
def caller_tracing=( val )
@caller_tracing =
case val
when true, 'true'; true
when false, 'false'; false
when nil; @caller_tracing
else raise ArgumentError, 'expecting a boolean' end
end
# call-seq:
# level => integer
#
# Returns an integer which is the defined log level for this logger.
#
def level
return @level unless @level.nil?
@parent.level
end
# call-seq:
# level = :all
#
# Set the level for this logger. The level can be either a +String+, a
# +Symbol+, or an +Integer+. An +ArgumentError+ is raised if this is not
# the case.
#
# There are two special levels -- "all" and "off". The former will
# enable log messages from this logger. The latter will disable all log
# messages from this logger.
#
# Setting the logger level to +nil+ will cause the parent's logger level
# to be used.
#
# Example:
#
# log.level = :debug
# log.level = "INFO"
# log.level = 4
# log.level = 'off'
# log.level = :all
#
# These produce an +ArgumentError+
#
# log.level = Object
# log.level = -1
# log.level = 1_000_000_000_000
#
def level=( level )
@level =
if level.nil? then level
else
lvl = case level
when String, Symbol; ::Logging::level_num(level)
when Integer; level
else
raise ArgumentError,
"level must be a String, Symbol, or Integer"
end
if lvl.nil? or lvl < 0 or lvl > ::Logging::LEVELS.length
raise ArgumentError, "unknown level was given '#{level}'"
end
lvl
end
define_log_methods(true)
self.level
end
# Returns `true` if the logger has its own level defined.
def has_own_level?
!@level.nil?
end
# Returns the list of appenders.
#
def appenders
@appenders.dup
end
# call-seq:
# appenders = app
#
# Clears the current list of appenders and replaces them with _app_,
# where _app_ can be either a single appender or an array of appenders.
#
def appenders=( args )
@appenders.clear
add_appenders(*args) unless args.nil?
end
# call-seq:
# add_appenders( appenders )
#
# Add the given _appenders_ to the list of appenders, where _appenders_
# can be either a single appender or an array of appenders.
#
def add_appenders( *args )
args.flatten.each do |arg|
o = arg.kind_of?(::Logging::Appender) ? arg : ::Logging::Appenders[arg.to_s]
raise ArgumentError, "unknown appender #{arg.inspect}" if o.nil?
@appenders << o unless @appenders.include?(o)
end
self
end
# call-seq:
# remove_appenders( appenders )
#
# Remove the given _appenders_ from the list of appenders. The appenders
# to remove can be identified either by name using a +String+ or by
# passing the appender instance. _appenders_ can be a single appender or
# an array of appenders.
#
def remove_appenders( *args )
args.flatten.each do |arg|
@appenders.delete_if do |a|
case arg
when String; arg == a.name
when ::Logging::Appender; arg.object_id == a.object_id
else
raise ArgumentError, "#{arg.inspect} is not a 'Logging::Appender'"
end
end
end
self
end
# call-seq:
# clear_appenders
#
# Remove all appenders from this logger.
#
def clear_appenders( ) @appenders.clear end
protected
# call-seq:
# parent = ParentLogger
#
# Set the parent logger for this logger. This method will be invoked by
# the +Repository+ class when a parent or child is added to the
# hierarchy.
#
def parent=( parent ) @parent = parent end
# call-seq:
# log_event( event )
#
# Send the given _event_ to the appenders for logging, and pass the
# _event_ up to the parent if additive mode is enabled. The log level has
# already been checked before this method is called.
#
def log_event( event )
@appenders.each {|a| a.append(event)}
@parent.log_event(event) if @additive
end
# call-seq:
# define_log_methods( force = false )
#
# Define the logging methods for this logger based on the configured log
# level. If the level is nil, then we will ask our parent for it's level
# and define log levels accordingly. The force flag will skip this
# check.
#
# Recursively call this method on all our children loggers.
#
def define_log_methods( force = false, code = nil )
return if has_own_level? and !force
::Logging::Logger._reentrant_mutex.synchronize do
::Logging::Logger.define_log_methods(self)
::Logging::Repository.instance.children(name).each do |child|
child.define_log_methods
end
end
self
end
# :stopdoc:
public
@reentrant_mutex = ReentrantMutex.new
def self._reentrant_mutex
@reentrant_mutex
end
# call-seq:
# _meta_eval( code )
#
# Evaluates the given string of _code_ if the singleton class of this
# Logger object.
#
def _meta_eval( code, file = nil, line = nil )
meta = class << self; self end
meta.class_eval code, file, line
end
# call-seq:
# _setup( name, opts = {} )
#
# Configures internal variables for the logger. This method can be used
# to avoid storing the logger in the repository.
#
def _setup( name, opts = {} )
@name = name
@parent = opts.fetch(:parent, nil)
@appenders = opts.fetch(:appenders, [])
@additive = opts.fetch(:additive, true)
@level = opts.fetch(:level, nil)
@caller_tracing = opts.fetch(:caller_tracing, false)
::Logging::Logger.define_log_methods(self)
end
# call-seq:
# _dump_configuration( io = STDOUT, indent = 0 )
#
# An internal method that is used to dump this logger's configuration to
# the given _io_ stream. The configuration includes the logger's name,
# level, additivity, and caller_tracing settings. The configured appenders
# are also printed to the _io_ stream.
#
def _dump_configuration( indent = 0 )
str, spacer, base = '', ' ', 50
indent_str = indent == 0 ? '' : ' ' * indent
str << indent_str
str << self.name.shrink(base - indent)
if (str.length + spacer.length) < base
str << spacer
str << '.' * (base - str.length)
end
str = str.ljust(base)
str << spacer
level_str = @level.nil? ? '' : '*'
level_str << if level < ::Logging::LEVELS.length
::Logging.levelify(::Logging::LNAMES[level])
else
'off'
end
level_len = ::Logging::MAX_LEVEL_LENGTH + 1
str << sprintf("%#{level_len}s" % level_str)
str << spacer
if self.respond_to?(:additive)
str << (additive ? '+A' : '-A')
else
str << ' '
end
str << spacer
str << (caller_tracing ? '+T' : '-T')
str << "\n"
@appenders.each do |appender|
str << indent_str
str << '- '
str << appender.to_s
str << "\n"
end
return str
end
# :startdoc:
end # Logger
end # Logging
|