# ----------------------------------------------------------------------------- # # Sawmill convenience interface # # ----------------------------------------------------------------------------- # 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. # ----------------------------------------------------------------------------- ; # This module is a namespace for Sawmill. # # It also contains some convenience class methods. module Sawmill class << self # Creates a new logger that writes to a single logfile. # You may provide either the path to the logfile, or an IO object to # write to, such as STDOUT. # # You may pass the same options taken by Sawmill::Logger#new and # Sawmill::EntryProcessor::Format#new, which are: # # [:levels] # Use a custom Sawmill::LevelGroup. Normally, you should leave this # set to the default, which is Sawmill::STANDARD_LEVELS. # [:level] # Default level to use for log messages when no level is explicitly # provided. By default, this is set to the level group's default, # which in the case of the standard levels is :INFO. # [:attribute_level] # Default level to use for attributes when no level is explicitly # provided. By default, this is set to the level group's highest, # level, which in the case of the standard levels is :ANY. # [:progname] # Progname to use in log messages. Default is "sawmill". # [:record_progname] # Progname to use in special log entries dealing with log records # (i.e. record delimiters and attribute messages). Default is the # same as the normal progname setting. # [:record_id_generator] # A proc that generates and returns a new record ID if one is not # explicitly passed into begin_record. If you do not provide a # generator, the default one is used, which generates an ID using the # variant 4 (random) UUID standard. # [:include_id] # Write the record ID in every log entry. Default is false. # [:fractional_second_digits] # Number of digits of fractional seconds to write 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. def simple_logger(filepath_=::STDOUT, opts_={}) if filepath_.kind_of?(::String) io_ = ::File.open(filepath_) elsif filepath_.respond_to?(:write) && filepath_.respond_to?(:close) io_ = filepath_ else raise ::ArgumentError, "You must pass a file path or an IO object" end processor_ = EntryProcessor::Format.new(io_, opts_.dup) Logger.new(opts_.merge(:processor => processor_)) end # Creates a new logger that writes to a logfile that rotates # automatically by "shifting". This is a standard rotation strategy # used by many unix tools. # # You must provide the logfile path, a shifting period, and a maximum # file size. # # The period specifies how often to rotate the logfile. Possible values # include :yearly, :monthly, :daily, and # :hourly. You may also specify an integer value, which is # interpreted as a number of seconds. Finally, you may pass nil to # disable checking of the file's age. If you do pass nil, you should # provide a maximum size. # # The maximum size is the maximum file size in bytes. You may provide # a number, or nil to disable checking of the file size. # # You may pass the same options taken by Sawmill::Logger#new, # Sawmill::EntryProcessor::Format#new, Sawmill::Rotater#new, and # Sawmill::Rotater::ShiftingLogFile#new, which are: # # [:levels] # Use a custom Sawmill::LevelGroup. Normally, you should leave this # set to the default, which is Sawmill::STANDARD_LEVELS. # [:level] # Default level to use for log messages when no level is explicitly # provided. By default, this is set to the level group's default, # which in the case of the standard levels is :INFO. # [:attribute_level] # Default level to use for attributes when no level is explicitly # provided. By default, this is set to the level group's highest, # level, which in the case of the standard levels is :ANY. # [:progname] # Progname to use in log messages. Default is "sawmill". # [:record_progname] # Progname to use in special log entries dealing with log records # (i.e. record delimiters and attribute messages). Default is the # same as the normal progname setting. # [:record_id_generator] # A proc that generates and returns a new record ID if one is not # explicitly passed into begin_record. If you do not provide a # generator, the default one is used, which generates an ID using the # variant 4 (random) UUID standard. # [:include_id] # Write the record ID in every log entry. Default is false. # [:fractional_second_digits] # Number of digits of fractional seconds to write 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. # [:omit_directives] # If true, omit standard logfile directives. Default is false. # [:basedir] # The base directory used if the filepath is a relative path. # If not specified, the current working directory is used. # [:history_size] # The maximum number of old logfiles (files with indexes) to # keep. Files beyond this history size will be automatically # deleted. Default is 1. This value must be at least 1. # [:encoding] # Specify an encoding name for file data. (Ruby 1.9 only) # If not specified, uses the default external encoding. def shifting_logfile(filepath_, period_, max_size_, opts_={}) rotater_ = Rotater.new(Rotater::ShiftingLogFile, opts_.merge(:file_path => filepath_, :max_file_size => max_size_, :shift_period => period_)) processor_ = EntryProcessor::Format.new(rotater_, opts_.dup) Logger.new(opts_.merge(:processor => processor_)) end # Creates a new logger that writes to a logfile that rotates # automatically by tagging filenames with a datestamp. # # You must provide the file path prefix, and a turnover frequency. # Possible values for the turnover frequency are :yearly, # :monthly, :daily, :hourly, and # :never. # # You may pass the same options taken by Sawmill::Logger#new, # Sawmill::EntryProcessor::Format#new, Sawmill::Rotater#new, and # Sawmill::Rotater::DateBasedLogFile#new, which are: # # [:levels] # Use a custom Sawmill::LevelGroup. Normally, you should leave this # set to the default, which is Sawmill::STANDARD_LEVELS. # [:level] # Default level to use for log messages when no level is explicitly # provided. By default, this is set to the level group's default, # which in the case of the standard levels is :INFO. # [:attribute_level] # Default level to use for attributes when no level is explicitly # provided. By default, this is set to the level group's highest, # level, which in the case of the standard levels is :ANY. # [:progname] # Progname to use in log messages. Default is "sawmill". # [:record_progname] # Progname to use in special log entries dealing with log records # (i.e. record delimiters and attribute messages). Default is the # same as the normal progname setting. # [:record_id_generator] # A proc that generates and returns a new record ID if one is not # explicitly passed into begin_record. If you do not provide a # generator, the default one is used, which generates an ID using the # variant 4 (random) UUID standard. # [:include_id] # Write the record ID in every log entry. Default is false. # [:fractional_second_digits] # Number of digits of fractional seconds to write 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. # [:omit_directives] # If true, omit standard logfile directives. Default is false. # [:basedir] # The base directory used if the filepath is a relative path. # If not specified, the current working directory is used. # [:name_suffix] # The logfile name suffix. # In the filename "rails.2009-10-11.log", the suffix is ".log". # If not specified, defaults to ".log". # [:local_datestamps] # If true, use the local timezone to create datestamps. # The default is to use UTC. # [:encoding] # Specify an encoding name for file data. (Ruby 1.9 only) # If not specified, uses the default external encoding. def date_based_logfile(filepath_, frequency_, opts_={}) rotater_ = Rotater.new(Rotater::DateBasedLogFile, opts_.merge(:path_prefix => filepath_, :turnover_frequency => frequency_)) processor_ = EntryProcessor::Format.new(rotater_, opts_.dup) Logger.new(opts_.merge(:processor => processor_)) end # Open one or more log files and run them through an entry processor. # The processor is built on the fly using the EntryProcessor DSL. # See EntryProcessor#build for more details. # # You may pass the same options taken by Sawmill::MultiParser#new, # which are: # # [:levels] # Sawmill::LevelGroup to use to parse log levels. # If not specified, Sawmill::STANDARD_LEVELS is used by default. # [:emit_incomplete_records_at_eof] # If set to true, causes any incomplete log records to be emitted # in their incomplete state when EOF is reached on all streams. # # Additionally, these options are recognized: # # [:encoding] # Specify an encoding for file data. (Ruby 1.9 only.) # You may specify an encoding name or an encoding object. # If not specified, reads raw bytes (e.g. defaults to 'ASCII-8BIT'). # Note that the encoding may also be modified by the file itself, # if an appropriate parser directive is encountered. # [:internal_encoding] # Specify an encoding to transcode to. (Ruby 1.9 only.) # You may specify an encoding name or an encoding object. # If not specified, uses the encoding as read from the file. def open_entries(globs_, opts_={}, &block_) processor_ = EntryProcessor.build(&block_) open_files(globs_, processor_, opts_.merge(:finish => true)) end # Open one or more log files and run them through a record processor. # The processor is built on the fly using the RecordProcessor DSL. # See RecordProcessor#build for more details. # # You may pass the same options taken by Sawmill::MultiParser#new, # which are: # # [:levels] # Sawmill::LevelGroup to use to parse log levels. # If not specified, Sawmill::STANDARD_LEVELS is used by default. # [:emit_incomplete_records_at_eof] # If set to true, causes any incomplete log records to be emitted # in their incomplete state when EOF is reached on all streams. # # Additionally, these options are recognized: # # [:encoding] # Specify an encoding for file data. (Ruby 1.9 only.) # You may specify an encoding name or an encoding object. # If not specified, reads raw bytes (e.g. defaults to 'ASCII-8BIT'). # Note that the encoding may also be modified by the file itself, # if an appropriate parser directive is encountered. # [:internal_encoding] # Specify an encoding to transcode to. (Ruby 1.9 only.) # You may specify an encoding name or an encoding object. # If not specified, uses the encoding as read from the file. def open_records(globs_, opts_={}, &block_) processor_ = RecordProcessor.build(&block_) open_files(globs_, processor_, opts_.merge(:finish => true)) end # Open one or more log files and run them through the given # EntryProcessor or RecordProcessor. # # You may pass the same options taken by Sawmill::MultiParser#new, # which are: # # [:levels] # Sawmill::LevelGroup to use to parse log levels. # If not specified, Sawmill::STANDARD_LEVELS is used by default. # [:emit_incomplete_records_at_eof] # If set to true, causes any incomplete log records to be emitted # in their incomplete state when EOF is reached on all streams. # # Additionally, these options are recognized: # # [:finish] # If set to true, the "finish" method is called on the processor # after all files have been parsed, and the return value is returned. # Otherwise, the processor is left open and nil is returned. # [:encoding] # Specify an encoding for file data. (Ruby 1.9 only.) # You may specify an encoding name or an encoding object. # If not specified, reads raw bytes (e.g. defaults to 'ASCII-8BIT'). # Note that the encoding may also be modified by the file itself, # if an appropriate parser directive is encountered. # [:internal_encoding] # Specify an encoding to transcode to. (Ruby 1.9 only.) # You may specify an encoding name or an encoding object. # If not specified, uses the encoding as read from the file. def open_files(globs_, processor_, opts_={}) finish_opt_ = opts_.delete(:finish) encoding_ = opts_.delete(:encoding) internal_encoding_ = opts_.delete(:internal_encoding) mode_ = 'r' if defined?(::Encoding) if !encoding_ encoding_ = ::Encoding::ASCII_8BIT elsif encoding_ && !encoding_.respond_to?(:name) encoding_ = ::Encoding.find(encoding_) end if internal_encoding_ && !internal_encoding_.respond_to?(:name) internal_encoding_ = ::Encoding.find(internal_encoding_) end if encoding_ mode_ << ":#{encoding_.name}" end else encoding_ = nil internal_encoding_ = nil end globs_ = [globs_] unless globs_.kind_of?(::Array) io_array_ = [] encoding_array_ = [] internal_encoding_array_ = [] begin globs_.each do |glob_| ::Dir.glob(glob_).each do |path_| if path_ =~ /\.gz$/ io_ = ::File.open(path_, 'rb') io_array_ << ::Zlib::GzipReader.new(io_) encoding_array_ << encoding_ internal_encoding_array_ << internal_encoding_ else io_array_ << ::File.open(path_, mode_) encoding_array_ << nil internal_encoding_array_ << internal_encoding_ end end end opts_[:encoding_array] = encoding_array_ if encoding_array_.find{ |elem_| elem_ } opts_[:internal_encoding_array] = internal_encoding_array_ if internal_encoding_array_.find{ |elem_| elem_ } MultiParser.new(io_array_, processor_, opts_).parse_all ensure io_array_.each do |io_| io_.close rescue nil end end finish_opt_ ? processor_.finish : nil end end end