module Lumberjack
# A template converts entries to strings. Templates can contain the following place holders to
# reference log entry values:
#
# * :time
# * :severity
# * :progname
# * :unit_of_work_id
# * :message
class Template
TEMPLATE_ARGUMENT_ORDER = %w(:time :severity :progname :pid :unit_of_work_id :message).freeze
DEFAULT_TIME_FORMAT = "%Y-%m-%dT%H:%M:%S."
MILLISECOND_FORMAT = "%03d"
MICROSECOND_FORMAT = "%06d"
# Create a new template from the markup. The +first_line+ argument is used to format only the first
# line of a message. Additional lines will be added to the message unformatted. If you wish to format
# the additional lines, use the :additional_lines options to specify a template. Note that you'll need
# to provide the line separator character in this template if you want to keep the message on multiple lines.
#
# The time will be formatted as YYYY-MM-DDTHH:MM:SSS.SSS by default. If you wish to change the format, you
# can specify the :time_format option which can be either a time format template as documented in
# +Time#strftime+ or the values +:milliseconds+ or +:microseconds+ to use the standard format with the
# specified precision.
#
# Messages will have white space stripped from both ends.
def initialize(first_line, options = {})
@first_line_template = compile(first_line)
additional_lines = options[:additional_lines] || "#{Lumberjack::LINE_SEPARATOR}:message"
@additional_line_template = compile(additional_lines)
# Formatting the time is relatively expensive, so only do it if it will be used
@template_include_time = first_line.include?(":time") || additional_lines.include?(":time")
@time_format = options[:time_format] || :milliseconds
end
# Convert an entry into a string using the template.
def call(entry)
lines = entry.message.strip.split(Lumberjack::LINE_SEPARATOR)
formatted_time = format_time(entry.time) if @template_include_time
message = @first_line_template % [formatted_time, entry.severity_label, entry.progname, entry.pid, entry.unit_of_work_id, lines.shift]
lines.each do |line|
message << @additional_line_template % [formatted_time, entry.severity_label, entry.progname, entry.pid, entry.unit_of_work_id, line]
end
message
end
private
def format_time(time) #:nodoc:
if @time_format.is_a?(String)
time.strftime(@time_format)
elsif @time_format == :milliseconds
time.strftime(DEFAULT_TIME_FORMAT) << MILLISECOND_FORMAT % (time.usec / 1000.0).round
else
time.strftime(DEFAULT_TIME_FORMAT) << MICROSECOND_FORMAT % time.usec
end
end
# Compile the template string into a value that can be used with sprintf.
def compile(template) #:nodoc:
template.gsub(/:[a-z0-9_]+/) do |match|
position = TEMPLATE_ARGUMENT_ORDER.index(match)
if position
"%#{position + 1}$s"
else
match
end
end
end
end
end