module Ramaze module Logger # A customized logger (based on Informer) that creates multiple log files based on time class RotatingInformer include Logging attr_accessor :time_format, :log_levels attr_reader :base_dir # parameter for Time.now.strftime trait :timestamp => "%Y-%m-%d %H:%M:%S" # This is how the final output is arranged. trait :format => "[%time] %prefix %text" # Create a new instance of RotatingInformer. # # base_dir is the directory where all log files will be stored # # time_format is the time format used to name the log files. # Possible formats are identical to those # accepted by Time.strftime # # log_levelse is an array describing what kind of messages # that the log receives. The array may contain # any or all of the symbols :debug, :error, :info and/or :warn # # Examples: # RotatingInformer.new('logs') # #=> Creates logs in directory called logs. # The generated filenames will be in the # form YYYY-MM-DD.log # RotatingInformer.new('logs', '%Y-%m.txt') # #=> Creates logs in directory called logs. # The generated filenames will be in the # form YYYY-MM.txt # RotatingInformer.new('logs', '%Y-%m.txt', [:error]) # #=> Creates logs in directory called logs. # The generated filenames will be in the # form YYYY-MM.txt. Only errors will be # logged to the files. def initialize(base_dir, time_format = '%Y-%m-%d.log', log_levels = [:debug, :error, :info, :warn]) # Verify and set base directory send :base_dir=, base_dir, true @time_format = time_format @log_levels = log_levels # Keep track of log shutdown (to prevent StackErrors due to recursion) @in_shutdown = false end # Set the base directory for log files # # If this method is called with the raise_exception # parameter set to true the method will raise an exception # if the specified directory does not exist or is unwritable. # # If raise_exception is set to false, the method will just # silently fail if the specified directory does not exist # or is unwritable def base_dir=(directory, raise_exception = false) # Expand directory path base_dir = File.expand_path(directory) # Verify that directory path exists if File.exist?(base_dir) # Verify that directory path is a directory if File.directory?(base_dir) # Verify that directory path is writable if File.writable?(base_dir) @base_dir = base_dir else raise Exception.new("#{base_dir} is not writable") if raise_exception end else raise Exception.new("#{base_dir} is not a directory") if raise_exception end else raise Exception.new("#{base_dir} does not exist.") if raise_exception end end # Close the file we log to if it isn't closed already. def shutdown if @out.respond_to?(:close) unless @in_shutdown @in_shutdown = true Log.debug("close, #{@out.inspect}") @in_shutdown = false end @out.close end end # Integration to Logging. def log tag, *messages return unless @log_levels.include?(tag) # Update current log update_current_log messages.flatten! prefix = tag.to_s.upcase.ljust(5) messages.each do |message| @out.puts(log_interpolate(prefix, message)) end @out.flush if @out.respond_to?(:flush) end # Takes the prefix (tag), text and timestamp and applies it to # the :format trait. def log_interpolate prefix, text, time = timestamp message = class_trait[:format].dup vars = { '%time' => time, '%prefix' => prefix, '%text' => text } vars.each{|from, to| message.gsub!(from, to) } message end # This uses Global.inform_timestamp or a date in the format of # %Y-%m-%d %H:%M:%S # # => "2007-01-19 21:09:32" def timestamp mask = class_trait[:timestamp] Time.now.strftime(mask || "%Y-%m-%d %H:%M:%S") end # is @out closed? def closed? @out.respond_to?(:closed?) && @out.closed? end private # Checks whether current filename is still valid. # If not, update the current log to point at the new # filename def update_current_log out = File.join(@base_dir, Time.now.strftime(@time_format)) if @out.nil? || @out.path != out # Close old log if necessary shutdown unless @out.nil? || closed? # Start new log @out = File.open(out, 'ab+') end end end end end