#!/usr/bin/env ruby ################### dumplog -- brent@mbari.org ####################### # Copyright (C) 2021 MBARI # MBARI Proprietary Information. All rights reserved. # # Convert specified log file to ASCII # if no argument given, try to find the log file to convert # as current ESP ######################################################################## require 'getoptlong' require 'framework' opts = GetoptLong.new( ["--file", "-f", GetoptLong::REQUIRED_ARGUMENT], ["--follow", "-F", GetoptLong::OPTIONAL_ARGUMENT], ["--tail", "-t", GetoptLong::OPTIONAL_ARGUMENT], ["--init", "-i", GetoptLong::REQUIRED_ARGUMENT], ["--end", "-e", GetoptLong::REQUIRED_ARGUMENT], ["--changeTZ", "-c", GetoptLong::NO_ARGUMENT], ["--date", "-d", GetoptLong::NO_ARGUMENT], ["--source", "-s", GetoptLong::NO_ARGUMENT], ["--backtrace", "-b", GetoptLong::NO_ARGUMENT], ["--help", "-h", GetoptLong::NO_ARGUMENT] ) progName = File.basename $0 logFn = ESP.logFn follow = initString = cleanupString = maxLines = nil q = [] default = filter = 'not (is Log::Msg or is Log::Debug)' opts.each do |opt, arg| case opt when "--file" logFn = arg when "--follow" follow = arg=='' ? 1 : Float(arg) #default to 1 second polling interval when "--tail" maxLines = arg=='' ? 10 : Integer(arg).abs when "--init" initString=arg when "--end" cleanupString=arg when "--changeTZ" $changeTZ=true when "--date" $datestampAll=true when "--source" $sourceStampAll=true when "--backtrace" $backtrace=true else $stderr.puts "\ Dump specified ESP log file as text -- 8/3/21 brent@mbari.org Options: --file={fn} #log file to dump (default=#{logFn}) --follow={secs} #follow log as it is being generated #secs is polling interval (default=1.0) --tail={lines} #output only the last lines (default=10) #may be combined with --follow --init='expression' #(optional, quoted) ruby initialization code --end='expression' #(optional, quoted) ruby cleanup code --changeTZ #change timezone to match log's --date #datestamp all entries --source #thread/source stamp all entries --backtrace #show backtraces for exceptions --help #displays this text Text after options is interpreted as a ruby filter expression run on each LogEntry instance. The default = #{default} Only log entries for which the expression returns true are dumped. The default filter displays all log entries except raw I2C and debug messages The optional init/end expressions run once just as log is opened/closed. Examples: #{progName} @object.is Exception #only Exceptions #{progName} is Log::Comment #only Comments #{progName} source==:Can #canister environment #{progName} is Log::Method or @object.is Exception #methods and exceptions #{progName} \"t > Time.now-3600\" #all entries less than one hour old #{progName} --file=real.log -- true #fully detailed log Note: if you see Log::Reader parse errors, check the ESPpath environment variable " exit end end module ESP module_function def simulation? :dumplog end end require 'logproc' #miscelleneous defs for processing log objects #try to display full resolution moisture sensor data begin Can.unit[:waterAlarm].update :threshold=>0.8, :format=>'%.2f%% wet' rescue Exception=>err STDERR.puts "Omitting high resolution Can moisture because...", err.inspect, err.backtrace end class Object alias_method :is, :is_a? #so simple filters do not require quoting alias_method :only, :is_a? Quit = Log::Reader::Quit LastMatch = Log::Reader::LastMatch end eval initString if initString #after the basics are set up Log::Entry.module_eval "def cleanup; #{cleanupString}; end" if cleanupString filter = ARGV.join ' ' unless ARGV.empty? #unless filter expression specified logFile = $stdin.tty? ? File.new(logFn) : $stdin Log::Entry.module_eval "def logFilter; #{filter}; end" Log::Entry.module_eval "def defaultFilter; #{default}; end" class LogReader < Log::Reader def hitEOF? true end end class LogFollower < Log::Reader #we rely on the fact that the Log::Reader uses only gets #so that is overridden such that is never returns eof? def initialize logFile, wt4eof, pollInterval, atTail, parsers={} raise ArgumentError, "polling interval of #{pollInterval} < 0.01s" if pollInterval < 0.01 @hitEOF = !wt4eof @pollInterval=pollInterval @atTail=atTail super logFile, parsers, $changeTZ end def eof? false end def gets(*args) loop { result=@stream.gets(*args) return result if result #pass thru if not at end of file unless @hitEOF @atTail.call @hitEOF = true end sleep @pollInterval } end def hitEOF? @hitEOF end end Log::Object.backtrace if $backtrace if $datestampAll or $sourceStampAll def putLogEntry e e.log.lastTime=nil if $datestampAll e.log.lastSource=nil if $sourceStampAll puts e.logEntry end else def putLogEntry e puts e.logEntry end end dumpTail = proc do q.each {|entry| putLogEntry entry} q.clear end start = if follow LogFollower.new logFile, maxLines, follow, dumpTail, Targets else if maxLines class LogReader def hitEOF? false end end end LogReader.new logFile, Targets, $changeTZ end reader = start.dup STDERR.print "Following " if follow STDERR.print "Tail of " unless reader.hitEOF? STDERR.puts "ESP log \"#{logFn}\" entries matching:\n "< 0 q.push e q.shift while q.size > maxLines end rescue SystemCallError => ioErr STDERR.puts ioErr.inspect exit ioErr.errno rescue Interrupt, StandardError => err STDERR.puts err.inspect #, err.backtrace, e.inspect; sleep 20 end end end end dumpTail.call rescue Quit => quit dumpTail.call putLogEntry e if quit.is_a? LastMatch puts quit end lastEntry.cleanup if lastEntry.respond_to? :cleanup