# -----------------------------------------------------------------------------
#
# Sawmill log rotation utility
#
# -----------------------------------------------------------------------------
# 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.
# -----------------------------------------------------------------------------
;
require 'thread'
module Sawmill
# The Sawmill Rotater provides log rotation services for logfile
# formatting, supporting several different log rotation strategies.
#
# The formatter implemented by Sawmill::EntryProcessor::Format already
# recognizes a Rotater as a supported destination, and automatically
# interfaces with it to ensure that log records aren't split into
# multiple files by rotation.
#
# You may also interface with a rotater manually. The core of rotater
# usage is the Channel, which lets you ensure that groups of log
# entries end up in the same log file regardless of log rotation.
# Generally, to use a rotater, you obtain one or more channels and
# write formatted entries to them, either telling them explicitly
# where the allowable file breaks are, or by letting the rotater
# break the file anywhere it wants. See the create_channel method
# and the Channel object for more details.
class Rotater
# Create a rotater using the given rotation strategy.
# See Sawmill::Rotater::DateBasedLogFile and
# Sawmill::Rotater::ShiftingLogFile for examples of common strategies.
#
# The rotation strategy can be passed as an object or as a class with a
# set of options that will be used to instantiate the strategy.
# In addition to those options, the following options are recognized:
#
# :omit_directives::
# If true, omit standard logfile directives. Default is false.
def initialize(io_manager_, opts_={})
@omit_directives = opts_.delete(:omit_directives)
if io_manager_.kind_of?(::Class)
@io_manager = io_manager_.new(opts_)
else
@io_manager = io_manager_
end
@handles ||= {}
@mutex ||= ::Monitor.new
end
# Create a new Channel for this Rotater. See Sawmill::Rotater::Channel
# for details on the Channel object.
#
# The following options are recognized:
#
# :auto_rotate::
# Put the channel in auto-rotate mode. In this mode, the rotater is
# allowed to rotate the logfile at any time for that channel. It is
# the equivalent of calling check_rotate on the channel after every
# write. Default is false.
def create_channel(opts_={})
Channel.new(self, opts_)
end
def _obtain_handle # :nodoc:
handle_ = @io_manager.preferred_handle
if @handles.include?(handle_)
@handles[handle_][2] += 1
else
io_ = @io_manager.open_handle(handle_)
unless @omit_directives
io_.write("# sawmill_format: version=1\n")
end
@handles[handle_] = [handle_, io_, 1]
end
handle_
end
def _release_handle(handle_) # :nodoc:
info_ = @handles[handle_]
info_[2] -= 1
if info_[2] == 0
@io_manager.close_handle(handle_, info_[1])
@handles.delete(handle_)
end
nil
end
def _check_rotate_handle(handle_) # :nodoc:
if handle_ != @io_manager.preferred_handle
_release_handle(handle_)
_obtain_handle
else
handle_
end
end
def _do_open # :nodoc:
@mutex.synchronize do
_obtain_handle
end
end
def _do_write(handle_, str_, auto_rotate_) # :nodoc:
@mutex.synchronize do
@io_manager.before_write
if auto_rotate_
handle_ = _check_rotate_handle(handle_)
end
info_ = @handles[handle_]
info_[1].write(str_)
handle_
end
end
def _do_close(handle_) # :nodoc:
@mutex.synchronize do
_release_handle(handle_)
end
end
def _do_check_rotate(handle_) # :nodoc:
@mutex.synchronize do
_check_rotate_handle(handle_)
end
end
# A channel is a lightweight object that responds to the write and close
# methods; that is, it is sufficient for Sawmill::Formatter.
#
# When a channel is opened, it locks down a path to the logfile and
# ensures that the logfile will not rotate out from under it; that is,
# writes to a channel are ensured to end up in the same physical file.
#
# You may choose, at intervals, to explicitly tell the channel that it
# is okay to rotate the logfile now, by calling check_rotate.
#
# You must close a channel when you are done with it. Closing a channel
# does not close the underlying logfile, but instead tells the rotater
# that you are done with this channel and that the logfile is free to
# rotate independent of it.
#
# You may have any number of channels open at any time, each on a
# different rotation schedule. Each may possibly be writing to different
# files in the rotation at any time, but this is all done automatically
# behind the scenes.
class Channel
def initialize(rotater_, opts_={}) # :nodoc:
@rotater = rotater_
@auto_rotate = opts_[:auto_rotate]
@io_handle = @rotater._do_open
end
# Write a string to this channel.
def write(str_)
if @io_handle
@rotater._do_write(@io_handle, str_, @auto_rotate)
end
end
# Close this channel, telling the rotater that this channel no longer
# needs to constrain the log rotation.
def close
if @io_handle
@rotater._do_close(@io_handle)
@io_handle = nil
end
end
# Manually tell the rotater that this channel is at a stopping point
# and that the log file may rotate at this time.
def check_rotate
if @io_handle
@io_handle = @rotater._do_check_rotate(@io_handle)
end
end
end
end
end