193 lines
5.8 KiB
Ruby
193 lines
5.8 KiB
Ruby
module Yell #:nodoc:
|
|
module Adapters #:nodoc:
|
|
|
|
# The +Datefile+ adapter is similar to the +File+ adapter. However, it
|
|
# rotates the file at midnight (by default).
|
|
class Datefile < Yell::Adapters::File
|
|
|
|
# The default date pattern, e.g. "19820114" (14 Jan 1982)
|
|
DefaultDatePattern = "%Y%m%d"
|
|
|
|
# Metadata
|
|
Header = lambda { |date, pattern| "# -*- #{date.iso8601} (#{date.to_f}) [#{pattern}] -*-" }
|
|
HeaderRegexp = /^# -\*- (.+) \((\d+\.\d+)\) \[(.+)\] -\*-$/
|
|
|
|
# The pattern to be used for the files
|
|
#
|
|
# @example
|
|
# date_pattern = "%Y%m%d" # default
|
|
# date_pattern = "%Y-week-%V"
|
|
attr_accessor :date_pattern
|
|
|
|
# Tell the adapter to create a symlink onto the currently
|
|
# active (timestamped) file. Upon rollover, the symlink is
|
|
# set to the newly created file, and so on.
|
|
#
|
|
# @example
|
|
# symlink = true
|
|
attr_accessor :symlink
|
|
|
|
# Set the amount of logfiles to keep when rolling over.
|
|
# By default, no files will be cleaned up.
|
|
#
|
|
# @example Keep the last 5 logfiles
|
|
# keep = 5
|
|
# keep = '10'
|
|
#
|
|
# @example Do not clean up any files
|
|
# keep = 0
|
|
attr_accessor :keep
|
|
|
|
# You can suppress the first line of the logfile that contains
|
|
# the metadata. This is important upon rollover, because on *nix
|
|
# systems, it is not possible to determine the creation time of a file,
|
|
# on the last access time. The header compensates this.
|
|
#
|
|
# @example
|
|
# header = false
|
|
attr_accessor :header
|
|
|
|
|
|
private
|
|
|
|
# @overload setup!( options )
|
|
def setup!( options )
|
|
self.header = Yell.__fetch__(options, :header, default: true)
|
|
self.date_pattern = Yell.__fetch__(options, :date_pattern, default: DefaultDatePattern)
|
|
self.keep = Yell.__fetch__(options, :keep, default: false)
|
|
self.symlink = Yell.__fetch__(options, :symlink, default: true)
|
|
|
|
@original_filename = ::File.expand_path(Yell.__fetch__(options, :filename, default: default_filename))
|
|
options[:filename] = @original_filename
|
|
|
|
@date = Time.now
|
|
@date_strftime = @date.strftime(date_pattern)
|
|
|
|
super
|
|
end
|
|
|
|
# @overload write!( event )
|
|
def write!( event )
|
|
# do nothing when not closing
|
|
return super unless close?
|
|
close
|
|
|
|
# exit when file ready present
|
|
return super if ::File.exist?(@filename)
|
|
|
|
header! if header?
|
|
symlink! if symlink?
|
|
cleanup! if cleanup?
|
|
|
|
super
|
|
end
|
|
|
|
# @overload close!
|
|
def close!
|
|
@filename = filename_for(@date)
|
|
|
|
super
|
|
end
|
|
|
|
# Determine whether to close the file handle or not.
|
|
#
|
|
# It is based on the `:date_pattern` (can be passed as option upon initialize).
|
|
# If the current time hits the pattern, it closes the file stream.
|
|
#
|
|
# @return [Boolean] true or false
|
|
def close?
|
|
_date = Time.now
|
|
_date_strftime = _date.strftime(date_pattern)
|
|
|
|
if @stream.nil? or _date_strftime != @date_strftime
|
|
@date, @date_strftime = _date, _date_strftime
|
|
|
|
return true
|
|
end
|
|
|
|
false
|
|
end
|
|
|
|
# Removes old logfiles of the same date pattern.
|
|
#
|
|
# By reading the header of the files that match the date pattern, the
|
|
# adapter determines whether to remove them or not. If no header is present,
|
|
# it makes the best guess by checking the last access time (which may result
|
|
# in false cleanups).
|
|
def cleanup!
|
|
files = Dir[ @original_filename.sub(/(\.\w+)?$/, ".*\\1") ].sort.select do |file|
|
|
_, pattern = header_from(file)
|
|
|
|
# Select if the date pattern is nil (no header info available within the file) or
|
|
# when the pattern matches.
|
|
pattern.nil? || pattern == self.date_pattern
|
|
end
|
|
|
|
::File.unlink( *files[0..-keep-1] )
|
|
end
|
|
|
|
# Cleanup old logfiles?
|
|
#
|
|
# @return [Boolean] true or false
|
|
def cleanup?
|
|
!!keep && keep.to_i > 0
|
|
end
|
|
|
|
# Symlink the current filename to the original one.
|
|
def symlink!
|
|
# do nothing, because symlink is already correct
|
|
return if ::File.symlink?(@original_filename) && ::File.readlink(@original_filename) == @filename
|
|
|
|
::File.unlink(@original_filename) if ::File.exist?(@original_filename) || ::File.symlink?(@original_filename)
|
|
::File.symlink(@filename, @original_filename)
|
|
end
|
|
|
|
# Symlink the original filename?
|
|
#
|
|
# @return [Boolean] true or false
|
|
def symlink?
|
|
!!symlink
|
|
end
|
|
|
|
# Write the header information into the file
|
|
def header!
|
|
stream.puts( Header.call(@date, date_pattern) )
|
|
end
|
|
|
|
# Write header into the file?
|
|
#
|
|
# @return [Boolean] true or false
|
|
def header?
|
|
!!header
|
|
end
|
|
|
|
# Sets the filename with the `:date_pattern` appended to it.
|
|
def filename_for( date )
|
|
@original_filename.sub(/(\.\w+)?$/, ".#{date.strftime(date_pattern)}\\1")
|
|
end
|
|
|
|
# Fetch the header form the file
|
|
def header_from( file )
|
|
if m = ::File.open(file, &:readline).match(HeaderRegexp)
|
|
# in case there is a Header present, we can just read from it
|
|
[ Time.at(m[2].to_f), m[3] ]
|
|
else
|
|
# In case there is no header: we need to take a good guess
|
|
#
|
|
# Since the pattern can not be determined, we will just return the Posix ctime.
|
|
# That is NOT the creatint time, so the value will potentially be wrong!
|
|
[::File.ctime(file), nil]
|
|
end
|
|
end
|
|
|
|
# @overload inspectables
|
|
def inspectables
|
|
super.concat %i[date_pattern header keep symlink]
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
end
|
|
|