# ----------------------------------------------------------------------------- # # 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