From: Bill Date: 2004-10-17T07:24:26+09:00 Subject: Re: Ruby programming styles and new program? Okay--I think this is the last revision unless there's a feature request. Thanks to all who helped with the Ruby/Smalltalk idioms. ========================== #!/usr/bin/ruby -w ######################################################### # # log_CID.rb # # Ruby CID logging script # # Logs all activity at a modem set to decipher CID # (Caller ID) information. Archives the daily logs as well. # ########################################################## require 'zip/zip' require 'serialport/serialport.so' require 'yaml' require 'getoptlong' ################################# class ModemCIDMonitor def initialize(config, logger) @port_name = config.port_name @MAX_PORT_ERRORS = config.max_port_errors @debug = config.debug @log_blank_lines = config.log_blank_lines @modem_init_string = config.modem_init_string @logger = logger @port_err_count = 0 @port = SerialPort.new(@port_name) @port.read_timeout = 0 @port.puts(@modem_init_string) end def log(txt) print txt if @debug @logger.log_text(txt) end def run print "Starting run with port ", @port_name, " and logging to dir ", @logger.archive_dir, "\n" loop do @port.each_line do | text | next unless text =~ /\S/ or @log_blank_lines # squeeze double \r, etc to just \n text.sub!(/[\r\n]+/, "\n") log(text) end msg = "#{Time.now.to_s}: dropped out of system call, restarting loop.\n" log(msg) if @debug @port_err_count += 1 if(@port_err_count > MAX_PORT_ERRORS) errmsg = "Too many port errors...ending run\n" log(errmsg) return errmsg end end end end class CID_Config attr_reader :config_file, :as_hash, :defaults def initialize(config_file) @config_file = config_file @as_hash = YAML::load( File.open(@config_file) ) @defaults = { 'archive_dir' => './', 'base_log_name' => 'CID', 'debug' => false, 'archive_days_interval' => 1, 'archive_zip_filename' => 'CID_archive.zip', 'port_name' => 'COM1', 'max_port_errors' => 100, 'log_blank_lines' => false, 'modem_init_string' => "AT+VCID=1\r\n", } @defaults.each do | k, v | @as_hash[k] = v unless @as_hash.has_key?(k) end end def method_missing(method, *args) method_key = method.to_s if args.length == 0 and @as_hash.has_key?(method_key) return @as_hash[method_key] else super end end end class DailyLogWithArchive attr_reader :archive_dir, :base_log_name, :archive_zip_filename, :archive_days_secs, :archive_days_interval attr_accessor :debug def initialize(config) @archive_dir = config.archive_dir @base_log_name = config.base_log_name @debug = config.debug @archive_days_interval = config.archive_days_interval @archive_zip_filename = config.archive_zip_filename @last_archive_day = -1 @secs_before_archive = 60 * 60 * 24 * config.days_before_archive end def current_fname "#{archive_dir}/#{base_log_name}#{Time.now.strftime('%Y%m%d')}.log" end def logfile_needs_moving(logfile) # check if the logfile mtime is old enough to archive the file return false unless logfile.index(@base_log_name) == 0 return false unless Time.now > File.stat(logfile).mtime + @secs_before_archive return true end def archiving_needed # check if we should do periodic archiving (up to daily) yday = Time.now.yday return false unless yday != @last_archive_day return false unless yday >= @last_archive_day + @archive_days_interval or yday == 0 return true end def archive_old_to_zip moved = 0 Dir.chdir(@archive_dir) do dir = Dir.open('.') moved = dir.inject(0) do | move_count, logfile | next unless logfile_needs_moving(logfile) next unless move_to_archive(logfile) log_text("LOGGER: Archiving file " + logfile + "\n") if @debug next move_count + 1 end end return moved end def move_to_archive(fname) Zip::ZipFile.open(@archive_zip_filename, 1) { | zfile | return nil if zfile.exist?(fname) zfile.add(fname, fname) } rc = File.delete(fname) return true if rc and rc == 1 return nil end def log_text(txt) Dir.chdir(@archive_dir) do File.open(current_fname, "a") do | logfile | logfile.print(txt) end if(archiving_needed) archive_old_to_zip @last_archive_day = Time.now.yday end end end end ############################### config_yaml = 'log_CID.yml' # default config file opts = GetoptLong.new( [ "--conf", "-c", GetoptLong::REQUIRED_ARGUMENT ] ) opts.each do | opt, arg | if(opt == "--conf" and arg.length > 0) config_yaml = arg last end end config = CID_Config.new(config_yaml) logger = DailyLogWithArchive.new(config) monitor = ModemCIDMonitor.new(config, logger) # no return from run unless abort on port timeouts or errors errmsg = monitor.run print errmsg return 0 ========================== ############################################ # # log_CID YAML config file. # # name of archive file--daily logs are moved to this archive archive_zip_filename: "CID_Data.zip" # modem initialization string. # need to set to log verbose caller ID information (+VCID=1 or #CID=1, etc) # also need to set to NOT answer, just monitor line (usually the default) modem_init_string: "AT+VCID=1\r\n" # directory to kep log files archive_dir: "c:/modemlog" # base log name for daily log files # daily log file name is this, plus YYYYMMDD date, plus .log extension # eg. CID20041004.log base_log_name: CID # the comm port having the CID-capable modem port_name: 'COM1:' # days that a daily log file is kept prior to archiving the file days_before_archive: 7 # maximum port read errors allowed before aborting run max_port_errors: 3000 # whether to log whitespace-only lines log_blank_lines: false # debug on or off? debug: true # # end YAML config file. # #####################################################