class Indy::Source

A StringIO interface to the underlying log source.

Attributes

connection[R]

log source connection string (cmd, filename or log data)

io[R]

the StringIO object

log_definition[RW]

log definition

type[R]

log source type. :cmd, :file, or :string

Public Class Methods

new(param,log_definition=nil) click to toggle source

Creates a Source object.

@param [String, Hash] param The source content String, filepath String, or :cmd => 'command' Hash

# File lib/indy/source.rb, line 31
def initialize(param,log_definition=nil)
  raise Indy::Source::Invalid, "No source specified." if param.nil?
  self.log_definition = log_definition || LogDefinition.new()
  return discover_connection(param) unless param.respond_to?(:keys)
  if param[:cmd]
    set_connection(:cmd, param[:cmd])
  elsif param[:file]
    set_connection(:file, open_or_return_file(param[:file]))
  elsif param[:string]
    set_connection(:string, param[:string])
  end
end

Public Instance Methods

discover_connection(param) click to toggle source

Support source being passed in without key indicating type

# File lib/indy/source.rb, line 47
def discover_connection(param)
  if param.respond_to?(:read) and param.respond_to?(:rewind)
    set_connection(:file, param)
  elsif param.respond_to?(:to_s) and param.respond_to?(:length)
    set_connection(:string, param)
  else
    raise Indy::Source::Invalid
  end
end
entries() click to toggle source

array of log lines from source

# File lib/indy/source.rb, line 216
def entries
  load_data unless @entries
  @entries
end
exec_command(command_string) click to toggle source

Execute the source's connection string, returning an IO object

@param [String] command_string string of command that will return log contents

# File lib/indy/source.rb, line 199
def exec_command(command_string)
  io = IO.popen(command_string)
  raise Indy::Source::Invalid, "No data returned from command string execution" if io.eof?
  io
end
find(boundary,value,start,stop) click to toggle source

Binary search for a time condition

# File lib/indy/source.rb, line 180
def find(boundary,value,start,stop)
  return start if start == stop
  mid_index, mid_time = find_middle(start,stop)
  if mid_time == value
    find_adjacent(boundary,value,start,stop,mid_index)
  elsif mid_time > value
    mid_index -= 1 if mid_index == stop
    find(boundary, value, start, mid_index)
  elsif mid_time < value
    mid_index += 1 if mid_index == start
    find(boundary, value, mid_index, stop)
  end
end
find_adjacent(boundary,value,start,stop,mid_index) click to toggle source

Step forward or backward by one, looking for the boundary of the value

# File lib/indy/source.rb, line 148
def find_adjacent(boundary,value,start,stop,mid_index)
  case boundary
  when :first
    (time_at(mid_index,-1) == value) ? find_first(value,start-1,stop) : mid_index
  when :last
    (time_at(mid_index,1) == value) ? find_last(value,start,stop+1) : mid_index
  end
end
find_first(value,start,stop) click to toggle source

find index of first record to match value

# File lib/indy/source.rb, line 123
def find_first(value,start,stop)
  return start if time_at(start) > value
  find(:first,value,start,stop)
end
find_last(value,start,stop) click to toggle source

find index of last record to match value

# File lib/indy/source.rb, line 131
def find_last(value,start,stop)
  return stop if time_at(stop) < value
  find(:last,value,start,stop)
end
find_middle(start, stop) click to toggle source

Find index and time at mid point

# File lib/indy/source.rb, line 139
def find_middle(start, stop)
  index = ((stop - start) / 2) + start
  time = time_at(index)
  [index, time]
end
load_data() click to toggle source

read source data and populate instance variables

# File lib/indy/source.rb, line 224
def load_data
  self.open if @io.nil?
  if @log_definition.multiline
    entire_log = @io.read
    @entries = entire_log.scan(@log_definition.entry_regexp).map{|matchdata|matchdata[0]}
  else
    @entries = @io.readlines
  end
  @io.rewind
  @entries.delete_if {|entry| entry.match(/^\s*$/)}
  @num_entries = @entries.count
end
num_entries() click to toggle source

the number of lines in the source

# File lib/indy/source.rb, line 208
def num_entries
  load_data unless @num_entries
  @num_entries
end
open(time_boundaries=nil) click to toggle source

Return a StringIO object to provide access to the underlying log source

# File lib/indy/source.rb, line 75
def open(time_boundaries=nil)
  begin
    open_method = ('open_' + @type.to_s).intern
    self.send(open_method)
  rescue Exception => e
    raise Indy::Source::Invalid, "Unable to open log source. (#{e.message})"
  end
  load_data
  scope_by_time(time_boundaries) if time_boundaries
  @entries
end
open_cmd() click to toggle source
# File lib/indy/source.rb, line 87
def open_cmd
  @io = StringIO.new(exec_command(@connection).read)
  raise "Failed to execute command (#{@connection.inspect})" if @io.nil?
end
open_file() click to toggle source
# File lib/indy/source.rb, line 92
def open_file
  @connection.rewind
  @io = StringIO.new(@connection.read)
  raise "Failed to open file: #{@connection.inspect}" if @io.nil?
end
open_or_return_file(param) click to toggle source
# File lib/indy/source.rb, line 65
def open_or_return_file(param)
  return param if param.respond_to? :pos
  file = File.open(param, 'r')
  raise ArgumentError, "Unable to open file parameter: '#{file}'" unless file.respond_to? :pos
  file
end
open_string() click to toggle source
# File lib/indy/source.rb, line 98
def open_string
  @io = StringIO.new(@connection)
  raise "Failed to create StringIO from source (#{@connection.inspect})" if @io.nil?
end
scope_by_time(time_boundaries) click to toggle source

Return entries that meet time criteria

# File lib/indy/source.rb, line 107
def scope_by_time(time_boundaries)
  start_time, end_time = time_boundaries
  scope_end = num_entries - 1
  # short circuit the search if possible
  if (time_at(0) > end_time) or (time_at(-1) < start_time)
    @entries = []
    return @entries
  end
  scope_begin = find_first(start_time, 0, scope_end)
  scope_end = find_last(end_time, scope_begin, scope_end)
  @entries = @entries[scope_begin..scope_end]
end
set_connection(type, value) click to toggle source

set the source connection type and connection_string

# File lib/indy/source.rb, line 60
def set_connection(type, value)
  @type = type
  @connection = value
end
time_at(index, delta=0) click to toggle source

Return the time of a log entry index, with an optional offset

# File lib/indy/source.rb, line 160
def time_at(index, delta=0)
  begin
    entry = @entries[index + delta]
    time = @log_definition.parse_entry(entry)[:time]
    result = Indy::Time.parse_date(time, @log_definition.time_format)
  rescue FieldMismatchException => fme
    raise
  rescue Exception => e
    msg = "Unable to parse time from entry. Time value was #{time.inspect}. Original exception was:\n#{e.class}\n"
    raise Indy::Time::ParseException, msg + e.message
  end
  if result.nil?
    raise Indy::Time::ParseException, "Unable to parse datetime. Raw value was #{time.inspect}. Entry was #{entry}."
  end
  result
end