module File::Tail

This module can be included in your own File subclasses or used to extend files you want to tail.

Constants

VERSION

File::Tail version

Attributes

break_if_eof[RW]

If this attribute is set to a true value, File::Fail's tail method raises a BreakException if the end of the file is reached.

default_bufsize[RW]

Default buffer size, that is used while going backward from a file's end. This defaults to nil, which means that File::Tail attempts to derive this value from the filesystem block size.

interval[RW]

The start value of the sleep interval. This value goes against max_interval if the tailed file is silent for a sufficient time.

line_separator[RW]

Override the default line separator

max_interval[RW]

The maximum interval File::Tail sleeps, before it tries to take some action like reading the next few lines or reopening the file.

reopen_deleted[RW]

If this attribute is set to a true value, File::Tail persists on reopening a deleted file waiting max_interval seconds between the attempts. This is useful if logfiles are moved away while rotation occurs but are recreated at the same place after a while. It defaults to true.

reopen_suspicious[RW]

If this attribute is set to a true value, File::Tail attempts to reopen it's tailed file after suspicious_interval seconds of silence.

return_if_eof[RW]

If this attribute is set to a true value, File::Fail's tail method just returns if the end of the file is reached.

suspicious_interval[RW]

This attribute is the invterval in seconds before File::Tail gets suspicious that something has happend to it's tailed file and an attempt to reopen it is made.

If the attribute reopen_suspicious is set to a non true value, suspicious_interval is meaningless. It defaults to 60 seconds.

Public Instance Methods

after_reopen(&block) click to toggle source

The callback is called with self as an argument after a reopen has occured. This allows a tailing script to find out, if a logfile has been rotated.

# File lib/file/tail.rb, line 71
def after_reopen(&block)
  @after_reopen = block
end
backward(n = 0, bufsize = nil) click to toggle source

Rewind the last n lines of this file, starting from the end. The default is to start tailing directly from the end of the file.

The additional argument bufsize is used to determine the buffer size that is used to step through the file backwards. It defaults to the block size of the filesystem this file belongs to or 8192 bytes if this cannot be determined.

# File lib/file/tail.rb, line 121
def backward(n = 0, bufsize = nil)
  preset_attributes unless defined? @lines
  if n <= 0
    seek(0, File::SEEK_END)
    return self
  end
  bufsize ||= default_bufsize || stat.blksize || 8192
  size = stat.size
  begin
    if bufsize < size
      seek(0, File::SEEK_END)
      while n > 0 and tell > 0 do
        seek(-bufsize, File::SEEK_CUR)
        buffer = read(bufsize)
        n -= buffer.count(@line_separator)
        seek(-bufsize, File::SEEK_CUR)
      end
    else
      rewind
      buffer = read(size)
      n -= buffer.count(@line_separator)
      rewind
    end
  rescue Errno::EINVAL
    size = tell
    retry
  end
  pos = -1
  while n < 0  # forward if we are too far back
    pos = buffer.index(@line_separator, pos + 1)
    n += 1
  end
  seek(pos + 1, File::SEEK_CUR)
  self
end
forward(n = 0) click to toggle source

Skip the first n lines of this file. The default is to don't skip any lines at all and start at the beginning of this file.

# File lib/file/tail.rb, line 102
def forward(n = 0)
  preset_attributes unless defined? @lines
  rewind
  while n > 0 and not eof?
    readline(@line_separator)
    n -= 1
  end
  self
end
tail(n = nil) { |line| ... } click to toggle source

This method tails this file and yields to the given block for every new line that is read. If no block is given an array of those lines is returned instead. (In this case it's better to use a reasonable value for n or set the return_if_eof or break_if_eof attribute to a true value to stop the method call from blocking.)

If the argument n is given, only the next n lines are read and the method call returns. Otherwise this method call doesn't return, but yields to block for every new line read from this file for ever.

# File lib/file/tail.rb, line 169
def tail(n = nil, &block) # :yields: line
  @n = n
  result = []
  array_result = false
  unless block
    block = lambda { |line| result << line }
    array_result = true
  end
  preset_attributes unless defined? @lines
  loop do
    begin
      restat
      read_line(&block)
      redo
    rescue ReopenException => e
      until eof? || @n == 0
        block.call readline(@line_separator)
        @n -= 1 if @n
      end
      reopen_file(e.mode)
      @after_reopen.call self if defined? @after_reopen
    rescue ReturnException
      return array_result ? result : nil
    end
  end
end

Private Instance Methods

output_debug_information() click to toggle source
# File lib/file/tail.rb, line 292
def output_debug_information
  $DEBUG or return
  STDERR.puts({
    :path     => path,
    :lines    => @lines,
    :interval => @interval,
    :no_read  => @no_read,
    :n        => @n,
  }.inspect)
  self
end
preset_attributes() click to toggle source
# File lib/file/tail.rb, line 225
def preset_attributes
  @reopen_deleted       = true  unless defined? @reopen_deleted
  @reopen_suspicious    = true  unless defined? @reopen_suspicious
  @break_if_eof         = false unless defined? @break_if_eof
  @return_if_eof        = false unless defined? @return_if_eof
  @max_interval         ||= 10
  @line_separator       ||= $/
  @interval             ||= @max_interval
  @suspicious_interval  ||= 60
  @lines                = 0
  @no_read              = 0
  @stat                 = nil
end
read_line(&block) click to toggle source
# File lib/file/tail.rb, line 198
def read_line(&block)
  if @n
    until @n == 0
      block.call readline(@line_separator)
      @lines   += 1
      @no_read = 0
      @n       -= 1
      output_debug_information
    end
    raise ReturnException
  else
    block.call readline(@line_separator)
    @lines   += 1
    @no_read = 0
    output_debug_information
  end
rescue EOFError
  seek(0, File::SEEK_CUR)
  raise ReopenException if @reopen_suspicious and
    @no_read > @suspicious_interval
  raise BreakException if @break_if_eof
  raise ReturnException if @return_if_eof
  sleep_interval
rescue Errno::ENOENT, Errno::ESTALE, Errno::EBADF
  raise ReopenException
end
reopen_file(mode) click to toggle source
# File lib/file/tail.rb, line 274
def reopen_file(mode)
  $DEBUG and $stdout.print "Reopening '#{path}', mode = #{mode}.\n"
  @no_read = 0
  reopen(path)
  if mode == :bottom
    backward
  elsif mode == :top
    forward
  end
rescue Errno::ESTALE, Errno::ENOENT
  if @reopen_deleted
    sleep @max_interval
    retry
  else
    raise DeletedException
  end
end
restat() click to toggle source
# File lib/file/tail.rb, line 239
def restat
  stat = File.stat(path)
  if @stat
    if stat.ino != @stat.ino or stat.dev != @stat.dev
      @stat = nil
      raise ReopenException.new(:top)
    end
    if stat.size < @stat.size
      @stat = nil
      raise ReopenException.new(:top)
    end
  end
  @stat = stat
rescue Errno::ENOENT, Errno::ESTALE
  raise ReopenException
end
sleep_interval() click to toggle source
# File lib/file/tail.rb, line 256
def sleep_interval
  if @lines > 0
    # estimate how much time we will spend on waiting for next line
    @interval = (@interval.to_f / @lines)
    @lines = 0
  else
    # exponential backoff if logfile is quiet
    @interval *= 2
  end
  if @interval > @max_interval
    # max. wait @max_interval
    @interval = @max_interval
  end
  output_debug_information
  sleep @interval
  @no_read += @interval
end