# -----------------------------------------------------------------------------
#
# Sawmill entry processor that formats for log files
#
# -----------------------------------------------------------------------------
# Copyright 2009 Daniel Azuma
#
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of the copyright holder, nor the names of any other
# contributors to this software, may be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
# -----------------------------------------------------------------------------
;
module Sawmill
module EntryProcessor
# This processor formats log entries and writes them to a destination.
class Format < Base
# Create a formatter.
#
# The destination can be a ruby IO object, a Sawmill::Rotater, or any
# object that responds to the "write" and "close" methods as defined
# by the ruby IO class.
#
# Recognized options include:
#
# [:include_id]
# Include the record ID in every log entry. Default is false.
# [:fractional_second_digits]
# Number of digits of fractional seconds to display in timestamps.
# Default is 2. Accepted values are 0 to 6.
# [:level_width]
# Column width of the level field.
# [:local_time]
# If true, outputs local time with the timezone offset indicator.
# If false (the default), outputs UTC.
# [:iso_8601_time]
# If true, outputs time in strict ISO 8601 format.
# If false (the default), outputs a slightly more readable format.
# [:length_limit]
# Limit to the entry length. Entries are truncated to this length
# when written. If not specified, entries are not truncated.
def initialize(destination_, opts_={})
if destination_.kind_of?(Rotater)
@rotater = destination_
@channels = {}
@standby_channel = nil
elsif destination_.respond_to?(:close) && destination_.respond_to?(:write)
@io = destination_
else
raise ::ArgumentError, "Unknown destination type"
end
@include_id = opts_[:include_id]
@fractional_second_digits = (opts_[:fractional_second_digits] || 2).to_i
@fractional_second_digits = 0 if @fractional_second_digits < 0
@fractional_second_digits = 6 if @fractional_second_digits > 6
@usec_factor = 1
(6 - @fractional_second_digits).times{ @usec_factor *= 10 }
@level_width = opts_[:level_width]
@length_limit = opts_[:length_limit]
end
def begin_record(entry_)
return false unless @io || @rotater
record_id_ = entry_.record_id
if @rotater
if @standby_channel
@standby_channel.check_rotate
io_ = @standby_channel
@standby_channel = nil
else
io_ = @rotater.create_channel
end
@channels[record_id_] = io_
else
io_ = @io
end
io_.write(_format_entry(entry_, '^', "BEGIN #{record_id_}"))
true
end
def end_record(entry_)
return false unless @io || @rotater
record_id_ = entry_.record_id
str_ = _format_entry(entry_, '$', "END #{record_id_}")
if @rotater
if (channel_ = @channels.delete(record_id_))
@standby_channel.close if @standby_channel
@standby_channel = channel_
else
@standby_channel ||= @rotater.create_channel
end
@standby_channel.write(str_)
@standby_channel.check_rotate
else
@io.write(str_)
end
true
end
def message(entry_)
return false unless @io || @rotater
_write_str(_format_entry(entry_, '.', entry_.message), entry_.record_id)
true
end
def attribute(entry_)
return false unless @io || @rotater
opcode_ = entry_.operation == :append ? '+' : '='
str_ = _format_entry(entry_, '=', "#{entry_.key} #{opcode_} #{entry_.value}")
_write_str(str_, entry_.record_id)
true
end
def unknown_data(entry_)
return false unless @io || @rotater
_write_str(entry_.line+"\n", nil)
true
end
def finish
if @rotater
@default_channel.close
@channels.values.each{ |channel_| channel_.close }
@rotater = nil
elsif @io
@io.close
@io = nil
end
nil
end
private
def _write_str(str_, record_id_) # :nodoc:
if @rotater
io_ = @channels[record_id_]
if io_
io_.write(str_)
else
@standby_channel ||= @rotater.create_channel
@standby_channel.check_rotate
@standby_channel.write(str_)
end
else
@io.write(str_)
end
end
def _format_entry(entry_, marker_, str_) # :nodoc:
id_ = @include_id ? entry_.record_id : nil
id_ = id_ ? ' '+id_ : ''
time_ = entry_.timestamp.getutc
str_ = str_.split("\n", -1).map do |line_|
if line_ =~ /(\\+)$/
"#{$`}#{$1}#{$1}"
else
line_
end
end.join("\\\n")
time_ = entry_.timestamp
if @local_time
time_ = time_.getlocal
else
time_ = time_.getutc
end
if @iso_8601_time
timestr_ = time_.strftime('%Y-%m-%dT%H:%M:%S')
else
timestr_ = time_.strftime('%Y-%m-%d %H:%M:%S')
end
if @fractional_second_digits > 0
timestr_ << (".%0#{@fractional_second_digits}d" % (time_.usec / @usec_factor))
end
if @local_time
offset_ = time_.utc_offset
neg_ = offset_ < 0
offset_ = -offset_ if neg_
offsetstr_ = "%s%02d%02d" % [(neg_ ? '-' : '+'), offset_ / 3600, (offset_ % 3600) / 60]
if @iso_8601_time
timestr_ << offsetstr_
else
timestr_ << ' ' << offsetstr_
end
elsif @iso_8601_time
timestr_ << 'Z'
end
levelstr_ = entry_.level.name.to_s
levelstr_ = levelstr_.rjust(@level_width) if @level_width
if @length_limit && @length_limit < str_.length
str_ = str_[0, @length_limit] + "... (and #{str_.length-@length_limit} more characters) ..."
end
"[#{levelstr_} #{timestr_} #{entry_.progname}#{id_} #{marker_}] #{str_}\n"
end
end
end
# Sawmill::Formatter is an alternate name for
# Sawmill::EntryProcessor::Format
Formatter = EntryProcessor::Format
end