# :include: ../rdoc/patternformatter
#
# == Other Info
#
# Version:: $Id$
require "log4r/formatter/formatter"
require "log4r/GDC"
require "log4r/MDC"
require "log4r/NDC"
module Log4r
# See log4r/formatter/patternformatter.rb
class PatternFormatter < BasicFormatter
# Arguments to sprintf keyed to directive letters
# %c - event short name
# %C - event fullname
# %d - date
# %g - Global Diagnostic Context (GDC)
# %t - trace
# %m - message
# %h - thread name
# %p - process ID aka PID
# %M - formatted message
# %l - Level in string form
# %x - Nested Diagnostic Context (NDC)
# %X - Mapped Diagnostic Context (MDC), syntax is "%X{key}"
# %% - Insert a %
DirectiveTable = {
"c" => 'event.name',
"C" => 'event.fullname',
"d" => 'format_date',
"g" => 'Log4r::GDC.get()',
"t" => '(event.tracer.nil? ? "no trace" : event.tracer[0])',
"T" => '(event.tracer.nil? ? "no trace" : event.tracer[0].split(File::SEPARATOR)[-1])',
"m" => 'event.data',
"h" => '(Thread.current[:name] or Thread.current.to_s)',
"p" => 'Process.pid.to_s',
"M" => 'format_object(event.data)',
"l" => 'LNAMES[event.level]',
"x" => 'Log4r::NDC.get()',
"X" => 'Log4r::MDC.get("DTR_REPLACE")',
"%" => '"%"'
}
# Matches the first directive encountered and the stuff around it.
#
# * $1 is the stuff before directive or "" if not applicable
# * $2 is the directive group or nil if there's none
# * $3 is the %#.# match within directive group
# * $4 is the .# match which we don't use (it's there to match properly)
# * $5 is the directive letter
# * $6 is the stuff after the directive or "" if not applicable
# * $7 is the remainder
DirectiveRegexp = /([^%]*)((%-?\d*(\.\d+)?)([cCdgtTmhpMlxX%]))?(\{.+?\})?(.*)/
# default date format
ISO8601 = "%Y-%m-%d %H:%M:%S"
attr_reader :pattern, :date_pattern, :date_method
# Accepts the following hash arguments (either a string or a symbol):
#
# [pattern] A pattern format string.
# [date_pattern] A Time#strftime format string. See the
# Ruby Time class for details.
# [+date_method+]
# As an option to date_pattern, specify which
# Time.now method to call. For
# example, +usec+ or +to_s+.
# Specify it as a String or Symbol.
#
# The default date format is ISO8601, which looks like this:
#
# yyyy-mm-dd hh:mm:ss => 2001-01-12 13:15:50
def initialize(hash={})
super(hash)
@pattern = (hash['pattern'] or hash[:pattern] or nil)
@date_pattern = (hash['date_pattern'] or hash[:date_pattern] or nil)
@date_method = (hash['date_method'] or hash[:date_method] or nil)
@date_pattern = ISO8601 if @date_pattern.nil? and @date_method.nil?
PatternFormatter.create_format_methods(self)
end
# PatternFormatter works by dynamically defining a format method
# based on the supplied pattern format. This method contains a call to
# Kernel#sptrintf with arguments containing the data requested in
# the pattern format.
#
# How is this magic accomplished? First, we visit each directive
# and change the %#.# component to %#.#s. The directive letter is then
# used to cull an appropriate entry from the DirectiveTable for the
# sprintf argument list. After assembling the method definition, we
# run module_eval on it, and voila.
def PatternFormatter.create_format_methods(pf) #:nodoc:
# first, define the format_date method
if pf.date_method
module_eval "def pf.format_date; Time.now.#{pf.date_method}; end"
else
module_eval <<-EOS
def pf.format_date
Time.now.strftime "#{pf.date_pattern}"
end
EOS
end
# and now the main format method
ebuff = "def pf.format(event)\n sprintf(\""
_pattern = pf.pattern.dup
args = [] # the args to sprintf which we'll append to ebuff lastly
while true # work on each match in turn
match = DirectiveRegexp.match _pattern
ebuff << match[1] unless match[1].empty?
break if match[2].nil?
# deal with the directive by inserting a %#.#s where %#.# is copied
# directy from the match
ebuff << match[3] + "s"
if ( match[5] == 'X' && match[6] != nil ) then
# MDC matches, need to be able to handle String, Symbol or Number
match6sub = /[\{\}\"]/
mdcmatches = match[6].match /\{(:?)(\d*)(.*)\}/
if ( mdcmatches[1] == "" && mdcmatches[2] == "" )
match6sub = /[\{\}]/ # don't remove surrounding "'s if String
end
args <<
DirectiveTable[match[5]].gsub("DTR_REPLACE", match[6]).gsub(match6sub,'')
else
args << DirectiveTable[match[5]] # cull the data for our argument list
end
break if match[7].empty?
_pattern = match[7]
end
ebuff << '\n", ' + args.join(', ') + ")\n"
ebuff << "end\n"
module_eval ebuff
end
end
end