We definitely want more GUI recipes, but I don't know what techniques
they should illustrate. PDF creation, SQLite integration, XML->YAML
mapping, specific web service integrations, etc. are all great ideas.
I'd like to have a section on system administration tasks, but I'm not
a sysadmin, so I'd like to hear from Ruby-using sysadmins what recipes
they'd like to see or share.
I'm a sysadmin who uses Ruby :O) How about a logfile reader. Here's
mine, although I'm sure the turd could be polished quite heavily!
------ cut config --------
mailhost: mailhost
tmout: 3600
grab: 1
patterns:
- !ruby/struct:LoganStruct
pattern: "correctable error detected"
grab: 5
email: (e-mail address removed)
post: "-r CRITICAL -m 'Logan(thishost): correctable error detected - Co=
ntac
t Unix Admin Team' hostname=3Dthishost general_use SCRIPTS"
- !ruby/struct:LoganStruct
pattern: "Error Level: Retryable"
except: QUANTUM
grab: 5
email: (e-mail address removed)
post: "-r CRITICAL -m 'Logan(thishost): Retryable disk error detected -=
Con
tact Unix Admin Team' hostname=3Dthishost general_use SCRIPTS"
- !ruby/struct:LoganStruct
pattern: "NFS getattr failed for server"
email: (e-mail address removed)
#post: "-r CRITICAL -m 'Logan(thishost): NFS getattr failed - Contact U=
nix A
dmin Team' hostname=3Dthishost general_use SCRIPTS"
- !ruby/struct:LoganStruct
pattern: "logan.* Config file error"
email: (e-mail address removed)
tmout: 0
------------- end config ---------------
------------- start script ----------------
#!/usr/local/bin/ruby
# @(#)logan=091.4 06/15/05
#
# Program Name: logan
# Date Created: 06/01/05
# Creator: Mike Wilson
#
# SYNOPSIS
# logan [-d] -c config_file -l logfile
#
# DESCRIPTION
# Pattern matches patterns specified in config_file (YAML) against logfi=
le.
#
require "yaml"
require "socket"
require "syslog"
require "net/smtp"
require "file/tail"
require "getoptlong"
$config =3D {}
$progname =3D File.basename($0)
thishost =3D Socket.gethostname
tivenv =3D "/etc/Tivoli/setup_env.sh"
usetiv =3D false
daemon =3D false
logfile =3D nil
config_file =3D nil
opts =3D GetoptLong.new(
["--config-file", "-c", GetoptLong::REQUIRED_ARGUMENT],
["--daemon", "-d", GetoptLong::NO_ARGUMENT],
["--logfile", "-l", GetoptLong::REQUIRED_ARGUMENT]
)
logan_struct =3D Struct::new("LoganStruct",
attern, :tmout, :grab, :except, :mailhost, :email,
ost, :expire)
def daemonize()
fork and exit
Process.setsid
fork and exit
File.open("/dev/null", "r+") do |devnull|
$stdin.reopen(devnull)
$stdout.reopen(devnull)
$stderr.reopen(devnull)
end
Dir.chdir("/")
end
def source(file)
response_match =3D Regexp.new(/^([^=3D]*)=3D(.*)/)
`#{ENV['SHELL']} -c ". #{file}; set"`.each_line do |line|
if response_match.match(line)
k, v =3D line.gsub(/^([^=3D]*)=3D(.*)/, '\1 \2').split
ENV[k] =3D v
end
end
end
def logConfigLoadError(config_file)
Syslog.open($progname, Syslog::LOG_PID, Syslog::LOG_DAEMON) do |syslog|
syslog.log(Syslog::LOG_ALERT, "Config file error in #{config_file}"=
)
end
end
def parseYamlConfig(file)
tmpconfig =3D {}
begin
tmpconfig =3D YAML::load(File.open(file))
rescue =3D> err
if $config.size =3D=3D 0
raise err
else
logConfigLoadError(file)
end
else
$config =3D tmpconfig
end
p $config if $DEBUG
$config
end
def convertToRegexp(topat)
pat =3D nil
case topat
when String
pat =3D Regexp.new(topat)
when Regexp
pat =3D topat
end
pat
end
def setDefault(check, master, lastchance)
if not check
if master
check =3D master
else
check =3D lastchance
end
end
check
end
def setDefaultConfigVals(tmout =3D 3600, mailhost =3D "mailhost", grab =3D =
1)
$config["patterns"].each do |pat|
pat.expire =3D 0
pat.pattern =3D convertToRegexp(pat.pattern)
pat.except =3D convertToRegexp(pat.except)
pat.mailhost =3D setDefault(pat.mailhost, $config["mailhost"], mail=
host)
pat.tmout =3D setDefault(pat.tmout, $config["tmout"], tmout)
pat.grab =3D setDefault(pat.grab, $config["grab"], grab)
end
end
def lookForPatternsInLogfile(file)
if File.exists?(file) and File.stat(file).readable?
File::Tail::Logfile.open(file, :rewind=3D>0) do |log|
lines =3D []
pattern =3D nil
grab =3D 0
log.tail do |line|
if grab > 1
lines << line
grab -=3D 1
if grab =3D=3D 1
yield pattern, lines
lines =3D []
end
end
$config["patterns"].each do |pat|
if pat.pattern.match(line)
if pat.grab > 1
lines << line
pattern =3D pat
grab =3D pat.grab
else
yield pat, line
end
end
end
end
end
end
end
def sendMail(email, alert, host, mailhost =3D "mailhost")
message =3D <<EndOfMsg
Subject: Log Analyzer Alert for #{host}
To: #{email}
From: #{$progname.upcase}
The following pattern match was made on #{host} at #{Time.now}
#{alert}
EndOfMsg
Net::SMTP.start(mailhost, 25) do |smtp|
smtp.send_mail(message, '"#{$progname.upcase}"', email)
end
end
def postMessage(msg)
system("postemsg #{msg} > /dev/null 2>&1")
end
opts.each do |opt, arg|
case opt
when "--daemon"
daemon =3D true
when "--logfile"
logfile =3D File.expand_path(arg)
when "--config-file"
config_file =3D File.expand_path(arg)
end
end
if not logfile or not config_file
fail "Usage: #{$progname} [-d] -c config_file -l logfile"
end
hup_proc =3D proc { |*a|
puts "Received signal to reload config"
parseYamlConfig(config_file) or fail $!
setDefaultConfigVals
trap("HUP", hup_proc)
}
trap("HUP", hup_proc)
trap("INT") { exit }
trap("TERM") { exit }
parseYamlConfig(config_file) or fail $!
setDefaultConfigVals
daemonize if daemon
if File.exists?(tivenv)
source(tivenv)
usetiv =3D true
end
lookForPatternsInLogfile(logfile) do |pat, msg|
if pat.expire <=3D Time.now.to_i
pat.expire =3D 0
end
if not pat.except or not pat.except.match(msg.to_s)
if pat.expire =3D=3D 0
pat.expire =3D Time.now.to_i + pat.tmout
if pat.email
pat.email.each do |e|
sendMail(e, msg, thishost, pat.mailhost)
end
end
if pat.post
postMessage(pat.post) if usetiv
end
puts msg
end
end
end
------------------ end script ------------