#!/usr/bin/perl -w
##########################################################################
# Filename: probe.pm
# Version: 2.1
# Date: 06/01/2012
# Author(s): Matt Feenstra
# Last Updated by: <>
#
# INFO: This package contains the modules necessary to connect to
# and perform remote SSH commands on remote hosts with logging.
#
# SUBROUTINES:
#
probe()
#
probe_mq()
#
probe_disk()
#
probe_mq_maxchl()
#
# USAGE:
#
# NOTES:
#
Modified 07/20/12
#
Matt Feenstra
#
##########################################################################
use strict;
package probe;
use Net::SSH::Perl;
my($CONFIG_FILE, $HOSTS_FILE, @CONFIG_KEYS, @CONFIG_VALUES);
sub init {
my $argz = shift;
probe::check_params($argz);
probe::set_config();
probe::log("[INFO] BEGIN $0");
}
sub set_config {
#$CONFIG_FILE = shift;
if(!(-T $CONFIG_FILE)) { die "$CONFIG_FILE is not a text file. $!"; }
@CONFIG_KEYS = ();
@CONFIG_VALUES = ();
open(CFGFILE, "<$CONFIG_FILE") || die "could not open $CONFIG_FILE. $!";
while(<CFGFILE>) {
if($_ =~ /^([\w\d\_\-\.]+)\s+(.+)\n$/) {
my($key,$val) = ($1, $2);
chomp($key);
chomp($val);
push(@CONFIG_KEYS, $1);
push(@CONFIG_VALUES, $2);
}
}
close(CFGFILE);
}
sub get_value {
my $keyname = shift;
my $numkeys = @CONFIG_KEYS;
for(my $i = 0; $i < $numkeys; $i++) {
if($CONFIG_KEYS[$i] =~ /^$keyname/) {
my $retvalue = $CONFIG_VALUES[$i];
if($keyname =~ /runCommand/) {
splice(@CONFIG_VALUES, $i, 1);
splice(@CONFIG_KEYS, $i, 1);
}
return($retvalue);
}
}
#die("CONFIG KEY NOT FOUND: $keyname $!");
}
sub print_config {
if(@CONFIG_KEYS) {
my $numkeys = @CONFIG_KEYS;
print "config file: $CONFIG_FILE\n";
for(my $i = 0; $i < $numkeys; $i++) {
print "index $i:\tkey: \"$CONFIG_KEYS[$i]\"\tvalue: \"$CONFIG_VALUES[$i]\"\n";
}
}
else {
print "CONFIG_KEYS not defined\n";
}
}
sub check_params {
my $paramref = shift;
my @params = @$paramref;
my $paramsize = @params;
if($paramsize != 2) {
die "incorrect number of parameters $paramsize\nusage:\n\t\t$0 <config file> <hosts file>\n";
}
if( (-e $params[0]) && (-T $params[0]) && (-e $params[1]) && (-T $params[1]) ) {
$CONFIG_FILE = $params[0];
$HOSTS_FILE = $params[1];
}
else {
die "invalid config or hosts file ($params[0], $params[1])";
}
}
# REQUIRES:
hosts file name
sub get_urls {
my $urlsfile = $HOSTS_FILE;
print "reading hosts file $urlsfile..\n";
probe::log("[INFO] reading hosts file $urlsfile");
open(URLLIST, "<$urlsfile") || die "$! $urlsfile";
my @url_list = <URLLIST>;
close(URLLIST);
chomp(@url_list);
my (@username, @hostname, @port);
my $num_lines = @url_list;
for(my $i = 0; $i < $num_lines; $i++) {
if(!($url_list[$i] =~ /^#/) && $url_list[$i] =~ /^(.+)?@(.+)?\:(\d+)?$/) {
#print "\t\tget_urls - $1 - $2 - $3\n";
push(@username, $1);
push(@hostname, $2);
push(@port, $3);
}
else {
my $linenum = $i + 1;
print "ignoring line $linenum in $urlsfile\n";
}
}
return(\@username, \@hostname, \@port);
}
sub connect {
my $user = $_[0];
my $host = $_[1];
my $port = $_[2];
my $cmdref = $_[3];
my @cmds = @$cmdref;
my $connect_timeout = probe::get_value("connectTimeout");
my $private_key = probe::get_value("privateKey");
my $login_timeout = probe::get_value("loginTimeout");
my @priv_key_ary;
$priv_key_ary[0] = $private_key;
print "*** creating SSH handle for $user\@$host:$port\n";
probe::log("[INFO] creating SSH handle for $user\@$host:$port");
my %params = ( priviledge => '0',
#protocol => '2,1',
protocol => '2',
ciphers => 'RC4',
port => $port,
identity_files => \@priv_key_ary,
#debug => 1,
#interactive => 1,
);
my $ssh;
my $ret = 0;
$SIG{ALRM} = \&timed_out;
eval {
alarm($connect_timeout);
$ssh = Net::SSH::Perl->new($host, %params);
alarm(0);
};
if($@) {
chomp($@);
print "[ERROR] connect failure on $host as $user because $@ Reason: $!\n";
probe::log("[ERROR] connect failure on $host as $user because $@ Reason: $!");
die "$@, $!";
}
eval {
print "*** logging in as $user ...\n";
alarm($login_timeout);
$ret = $ssh->login($user);
alarm(0);
};
if($@) {
chomp($@);
print "[ERROR] login failure for $user on $host because $@ Reason: $!\n";
probe::log("[ERROR] login failure for $user on $host because $@ Reason: $!");
die "$@, $!";
}
if($ret) {
print "*** login complete.\n";
probe::log("[INFO] return on ssh->login($user) $ret host $host");
}
else {
print "*** login problem, return $ret.\n";
probe::log("[WARNING] login problem, return $ret");
}
### Logged in -- Run commands here
my(@stdouts, @stderrs, @returns);
my $numcmds = @cmds;
# time to wait for getting hostname
my $hostname_timeout = probe::get_value("hostnameTimeout");
$hostname_timeout = $hostname_timeout * 1;
# assign alarm signal to the timed_out function so we can timeout on commands
print "*** querying hostname ..\n";
#my $hostname = "unknown";
my($hostname, $stderr2, $return2);
eval {
alarm($hostname_timeout);
($hostname, $stderr2, $return2) = $ssh->cmd("hostname");
alarm(0);
};
if($@) {
chomp($@);
print "[ERROR] could not retrieve hostname for $host: $@, $!\n";
probe::log("[ERROR] could not retrieve hostname for $host: $@, $!");
die "could not retrieve hostname for $host";
}
if(defined($hostname)) { chomp($hostname); }
if(!defined($hostname)) { $hostname = "unknown"; }
# run each of the commands in the cmds array
# seconds to wait for command to return
my $timeout_secs = probe::get_value("commandTimeout");
$timeout_secs = $timeout_secs * 1;
for(my $i = 0; $i < $numcmds; $i++) {
print "*** remote executing -> $cmds[$i]\n";
eval {
alarm($timeout_secs);
($stdouts[$i],$stderrs[$i],$returns[$i]) = $ssh->cmd("$cmds[$i]");
alarm(0);
};
if($@ =~ /TIMEOUT/) {
print("[WARNING] [$hostname] execution of \"$cmds[$i]\" left to run after $timeout_secs secs, moving on\n");
probe::log("[WARNING] [$hostname] execution of \"$cmds[$i]\" left to run after $timeout_secs secs, moving on");
}
elsif($@ && $!) {
chomp($@);
print "[ERROR] problem executing \"$cmds[$i]\" $@, $!\n";
probe::log("[ERROR] problem executing \"$cmds[$i]\" $@, $!");
}
elsif($@) {
chomp($@);
print("[WARNING] [$hostname] execution of \"$cmds[$i]\" incomplete\n");
probe::log("[WARNING] [$hostname] execution of \"$cmds[$i]\" incomplete");
}
}
undef $ssh;
return($hostname, \@stdouts, \@stderrs, \@returns);
}
sub timed_out {
die "TIMEOUT";
}
sub probe {
my @cmds = ();
while(my $cmd_value = probe::get_value("runCommand")) {
push(@cmds, $cmd_value);
undef $cmd_value;
}
if(@cmds == 0) { die "[ERROR] no runCommand(s) listed in $CONFIG_FILE"; }
# Read in a urls file and fill the arrays with host connect info
my($userref, $hostref, $portref) = probe::get_urls($HOSTS_FILE);
my @users = @$userref;
my @hosts = @$hostref;
my @ports = @$portref;
my $num_hosts = @hosts;
my(@stdouts, @stderrs, @returns);
for(my $i = 0; $i < $num_hosts; $i++) {
my $linenum = $i + 1;
probe::log("---");
print "\n---\n";
probe::log("[INFO] STARTING ($HOSTS_FILE) [ line: $linenum ] $hosts[$i] port $ports[$i] as $users[$i]");
print("*** ($HOSTS_FILE) [ line: $linenum ] connecting to host $hosts[$i]\n");
my($host, $ref1, $ref2, $ref3);
eval {
($host, $ref1, $ref2, $ref3) = probe::connect($users[$i],
$hosts[$i], $ports[$i], \@cmds);
};
if($@) {
# the connect subroutine should have printed the [ERROR]
chomp($@);
print "[WARNING] unable to connect to $hosts[$i]:$ports[$i] because $@";
probe::log("[WARNING] unable to connect to $hosts[$i]:$ports[$i] because $@");
}
# we have connected ok
my(@stdouts, @stderrs, @returns) = ();
if(defined($host) && $host ne "unknown") {
print("*** found hostname: $host\n\n");
if(defined(@$ref1)) {
@stdouts = @$ref1;
@stderrs = @$ref2;
@returns = @$ref3;
}
my $num_outputs = @returns;
for(my $y = 0; $y < $num_outputs; $y++) {
if(defined($stdouts[$y])) { print "\n### stdout ($cmds[$y]) (ret $returns[$y]):\n\n$stdouts[$y]\n"; }
if(defined($stderrs[$y])) { print "+++ stderr:\n$stderrs[$y]\n+++\n"; }
probe::log("[INFO] [$host] [COMMAND] [$cmds[$y]]");
probe::log("[INFO] [$host] [Return Code] [$returns[$y]]");
if(defined($stdouts[$y])) {
my @stdlines = split(/\n/,$stdouts[$y]);
foreach(@stdlines) {
probe::log("[INFO] [$host] [stdout] $_"); }
}
else {
probe::log("[INFO] [$host] [stdout] NONE");
}
# add some check logic here
# we are at $hosts[$i]
if(defined($stderrs[$y])) {
my @stderrlines = split(/\n/, $stderrs[$y]);
foreach(@stderrlines) {
probe::log("[WARNING] [$host] [stderr] $_"); }
}
}
} # end if
} # end for
}
sub probe_mq {
my @cmds = ();
while(my $cmd_value = probe::get_value("runCommand")) {
push(@cmds, $cmd_value);
undef $cmd_value;
}
if(@cmds == 0) { die "[ERROR] no runCommand(s) listed in $CONFIG_FILE"; }
# Read in a urls file and fill the arrays with host connect info
my($userref, $hostref, $portref) = probe::get_urls($HOSTS_FILE);
my @users = @$userref;
my @hosts = @$hostref;
my @ports = @$portref;
my $num_hosts = @hosts;
my(@stdouts, @stderrs, @returns);
for(my $i = 0; $i < $num_hosts; $i++) {
my $linenum = $i + 1;
probe::log("---");
print "\n---\n";
probe::log("[INFO] STARTING ($HOSTS_FILE) [ line: $linenum ] $hosts[$i] port $ports[$i] as $users[$i]");
print("*** ($HOSTS_FILE) [ line: $linenum ] connecting to host $hosts[$i]\n");
my($host, $ref1, $ref2, $ref3);
eval {
($host, $ref1, $ref2, $ref3) = probe::connect($users[$i],
$hosts[$i], $ports[$i], \@cmds);
};
if($@) {
chomp($@);
print "[WARNING] unable to connect to $hosts[$i]:$ports[$i] because $@";
probe::log("[WARNING] unable to connect to $hosts[$i]:$ports[$i] because $@");
}
# we have connected ok
if(defined($host) && $host ne "unknown") {
print("connect complete, found host: $host\n");
my @stdouts = @$ref1;
my @stderrs = @$ref2;
my @returns = @$ref3;
my $num_outputs = @returns;
for(my $y = 0; $y < $num_outputs; $y++) {
if(defined($stdouts[$y])) { print "\n### stdout ($cmds[$y]) (ret $returns[$y]):\n\n$stdouts[$y]\n"; }
if(defined($stderrs[$y])) { print "+++ stderr:\n$stderrs[$y]\n+++\n"; }
probe::log("[INFO] [$host] [COMMAND] [$cmds[$y]]");
probe::log("[INFO] [$host] [Return Code] [$returns[$y]]");
if(defined($stdouts[$y])) {
my @stdlines = split(/\n/,$stdouts[$y]);
foreach(@stdlines) {
probe::log("[INFO] [$host] [stdout] $_"); }
}
else {
probe::log("[INFO] [$host] [stdout] NONE");
}
# add some check logic here
# we are at $hosts[$i]
check_mq_chs($host, $stdouts[$y]);
if(defined($stderrs[$y])) {
my @stderrlines = split(/\n/, $stderrs[$y]);
foreach(@stderrlines) {
probe::log("[WARNING] [$host] [stderr] $_"); }
}
}
} # end if
} # end for
}
sub check_mq_chs {
my $host = shift;
my $stdout = shift;
my @lines;
if(defined($stdout)) { @lines = split(/\n/,$stdout); }
my $num_lines = @lines;
for(my $i = 0; $i < $num_lines; $i++) {
#my($channel, $type, $conname, $port, $rqmname, $status, $substate);
my $channel = "null";
my $type = "null";
my $conname = "null";
my $port = "1414";
my $rqmname = "null";
my $status = "null";
my $substate = "null";
# easy case -- no channels running
if($lines[$i] =~ /^AMQ8420/) {
print "[$host] [RESULT] ok - channels inactive\n";
probe::log("[INFO] [$host] [RESULT] OK - channels inactive");
}
# need to get channel report blocks starting with channel name line
if($lines[$i] =~ /CHANNEL\((.+)?\).+?CHLTYPE\((.+)?\)/) {
#print "found\tchannel\t$1\tchltype\t$2\n";
$channel = $1;
$type = $2;
if($lines[$i+1] =~ /CONNAME\((.+)?\((.+)?\)\)/ ||
$lines[$i+1] =~ /CONNAME\((.+)?\)/ ) {
$conname = $1;
if($2) { $port = $2; }
#print "\tfound\tconname\t$1\tport\t$port\n";
if($lines[$i+2] =~ /RQMNAME\((.+)?\).+STATUS\((.+)?\)/) {
#print "\t\tfound\trqmname\t$1\tstatus\t$2\n";
$rqmname = $1;
$status = $2
}
if($lines[$i+3] =~ /SUBSTATE\((.+)?\).+XMITQ/) {
#print "\t\t\tfound\tsubstate\t$1\n";
$substate = $1;
}
if($status =~ /RUNNING/) {
probe::log("[INFO] [$host] [RESULT] OK - $channel is $status");
print "[INFO] [$host] [RESULT] ok - $channel is $status\n";
}
else {
probe::log("[WARNING] [$host] [RESULT] Warning - $channel is $status in $substate");
print "[WARNING] [$host] [RESULT] warning - $channel is $status in $substate\n";
}
if($status =~ /RETRYING/) {
probe::log("[ERROR] [$host] [RESULT] Error - $channel to $conname:$port is $status");
print "[ERROR] [$host] [RESULT] Error - $channel to $conname:$port is $status\n";
}
}
}
}
}
sub probe_disk {
my @cmds = ();
while(my $cmd_value = probe::get_value("runCommand")) {
push(@cmds, $cmd_value);
undef $cmd_value;
}
if(@cmds == 0) { die "[ERROR] no runCommand(s) listed in $CONFIG_FILE"; }
# Read in a urls file and fill the arrays with host connect info
my($userref, $hostref, $portref) = probe::get_urls($HOSTS_FILE);
my @users = @$userref;
my @hosts = @$hostref;
my @ports = @$portref;
my $num_hosts = @hosts;
my(@stdouts, @stderrs, @returns);
for(my $i = 0; $i < $num_hosts; $i++) {
my $linenum = $i + 1;
probe::log("---");
print "\n---\n";
probe::log("[INFO] STARTING ($HOSTS_FILE) [ line: $linenum ] $hosts[$i] port $ports[$i] as $users[$i]");
print("*** ($HOSTS_FILE) [ line: $linenum ] connecting to host $hosts[$i]\n");
my($host, $ref1, $ref2, $ref3);
eval {
($host, $ref1, $ref2, $ref3) = probe::connect($users[$i],
$hosts[$i], $ports[$i], \@cmds);
};
if($@) {
chomp($@);
print "[WARNING] unable to connect to $hosts[$i]:$ports[$i] because $@";
probe::log("[WARNING] unable to connect to $hosts[$i]:$ports[$i] because $@");
}
# we have connected ok
my(@stdouts, @stderrs, @returns) = ();
if(defined($host) && $host ne "unknown") {
print("*** found hostname: $host\n\n");
if(defined(@$ref1)) {
@stdouts = @$ref1;
@stderrs = @$ref2;
@returns = @$ref3;
}
my $num_outputs = @returns;
for(my $y = 0; $y < $num_outputs; $y++) {
if(defined($stdouts[$y])) { print "\n### stdout ($cmds[$y]) (ret $returns[$y]):\n\n$stdouts[$y]\n"; }
if(defined($stderrs[$y])) { print "+++ stderr:\n$stderrs[$y]\n+++\n"; }
probe::log("[INFO] [$host] [COMMAND] [$cmds[$y]]");
probe::log("[INFO] [$host] [Return Code] [$returns[$y]]");
if(defined($stdouts[$y])) {
my @stdlines = split(/\n/,$stdouts[$y]);
foreach(@stdlines) {
probe::log("[INFO] [$host] [stdout] $_"); }
}
else {
probe::log("[INFO] [$host] [stdout] NONE");
}
# add some check logic here
check_disk($host, $hosts[$i], $stdouts[$y]);
if(defined($stderrs[$y])) {
my @stderrlines = split(/\n/, $stderrs[$y]);
foreach(@stderrlines) {
probe::log("[WARNING] [$host] [stderr] $_"); }
}
}
} # end if
} # end for
}
sub check_disk {
# anything less than this amount free will generate an [ERROR]. Value should be 49 pct or less
my $pct_threshold = probe::get_value("diskcheckPctThreshold");
# if we found an error we change the return code to 1 (true)
my $retcode = 0;
my $host = shift;
my $ipname = shift;
my $stdout = shift;
my @lines;
if(defined($stdout)) { @lines = split(/\n/,$stdout); }
my $num_lines = @lines;
for(my $i = 0; $i < $num_lines; $i++) {
if(!($lines[$i] =~ /^Filesystem/) && $lines[$i] =~ /(\S+)?\s+(\S+)?\s+(\S+)?\s+(\S+)?\s+(\S+)?\s+(\S+)/) {
my($filesystem, $size, $used, $avail, $use, $mounted) = ($1, $2, $3, $4, $5, $6);
my($pct_used, $pct_free);
if($use =~ /(\d+)?\%/) {
$pct_used = $1;
}
$pct_free = 100 - $pct_used;
if(!defined($pct_free)) {
print "[WARNING] [$host] [RESULT] disk percent free not calculated, undefined\n";
probe::log("[WARNING] [$host] [RESULT] disk percent free not calculated, undefined");
}
if($pct_free > 50) {
print "[INFO] [$host] [RESULT] disk ok, free space $pct_free% on $mounted\n";
probe::log("[INFO] [$host] [RESULT] disk ok, free space $pct_free% on $mounted");
}
if(($pct_free <= 50) && ($pct_free >= $pct_threshold)) {
print "[WARNING] [$host] [RESULT] host $ipname disk in trouble, only $pct_free% available on $mounted\n";
probe::log("[WARNING] [$host] [RESULT] host $ipname disk in trouble, only $pct_free% available on $mounted");
}
if($pct_free < $pct_threshold) {
print "[ERROR] [$host] [RESULT] host $ipname disk is filling up! $pct_free% available on $mounted\n";
probe::log("[ERROR] [$host] [RESULT] host $ipname disk is filling up! $pct_free% available on $mounted");
$retcode++;
}
}
}
return $retcode;
}
sub probe_mq_maxchl {
my @cmds = ();
while(my $cmd_value = probe::get_value("runCommand")) {
push(@cmds, $cmd_value);
undef $cmd_value;
}
if(@cmds == 0) { die "[ERROR] no runCommand(s) listed in $CONFIG_FILE"; }
# Read in a urls file and fill the arrays with host connect info
my($userref, $hostref, $portref) = probe::get_urls($HOSTS_FILE);
my @users = @$userref;
my @hosts = @$hostref;
my @ports = @$portref;
my $num_hosts = @hosts;
my(@stdouts, @stderrs, @returns);
for(my $i = 0; $i < $num_hosts; $i++) {
my $linenum = $i + 1;
probe::log("---");
print "\n---\n";
probe::log("[INFO] STARTING ($HOSTS_FILE) [ line: $linenum ] $hosts[$i] port $ports[$i] as $users[$i]");
print("*** ($HOSTS_FILE) [ line: $linenum ] connecting to host $hosts[$i]\n");
my($host, $ref1, $ref2, $ref3);
eval {
($host, $ref1, $ref2, $ref3) = probe::connect($users[$i],
$hosts[$i], $ports[$i], \@cmds);
};
if($@) {
# the connect subroutine should have printed the [ERROR]
chomp($@);
print "[WARNING] unable to connect to $hosts[$i]:$ports[$i] because $@";
probe::log("[WARNING] unable to connect to $hosts[$i]:$ports[$i] because $@");
}
# we have connected ok
my(@stdouts, @stderrs, @returns) = ();
if(defined($host) && $host ne "unknown") {
print("*** found hostname: $host\n\n");
if(defined(@$ref1)) {
@stdouts = @$ref1;
@stderrs = @$ref2;
@returns = @$ref3;
}
my $num_outputs = @returns;
for(my $y = 0; $y < $num_outputs; $y++) {
if(defined($stdouts[$y])) { print "\n### stdout ($cmds[$y]) (ret $returns[$y]):\n\n$stdouts[$y]\n"; }
if(defined($stderrs[$y])) { print "+++ stderr:\n$stderrs[$y]\n+++\n"; }
probe::log("[INFO] [$host] [COMMAND] [$cmds[$y]]");
probe::log("[INFO] [$host] [Return Code] [$returns[$y]]");
if(defined($stdouts[$y])) {
my @stdlines = split(/\n/,$stdouts[$y]);
foreach(@stdlines) {
probe::log("[INFO] [$host] [stdout] $_"); }
}
else {
probe::log("[INFO] [$host] [stdout] NONE");
}
# add some check logic here
# we are at $hosts[$i]
check_mq_maxchl($host, $hosts[$i], $stdouts[$y]);
if(defined($stderrs[$y])) {
my @stderrlines = split(/\n/, $stderrs[$y]);
foreach(@stderrlines) {
probe::log("[WARNING] [$host] [stderr] $_"); }
}
}
} # end if
} # end for
}
sub check_mq_maxchl {
# if we found an error we change the return code to 1 (true)
my $host = shift;
my $ipname = shift;
my $stdout = shift;
my @lines;
my $retcode = 0;
if(defined($stdout)) { @lines = split(/\n/,$stdout); }
my $times = $lines[0] * 1;
if($times > 0) {
print "[ERROR] [$host] [RESULT] Max number of channels reached $times times on $ipname\n";
probe::log("[ERROR] [$host] [RESULT] Max number of channels reached $times times on $ipname");
$retcode++;
}
else {
print "[INFO] [$host] [RESULT] MQ channel count on $host is OK.\n";
probe::log("[INFO] [$host] [RESULT] MQ channel count on $host is OK.");
}
return $retcode;
}
sub log {
my $text = shift;
my ($logsec, $logmin, $loghour, $logmday, $logmon, $logyear, $logwday, $logyday, $logisdst)=localtime(time);
my $timestamp = sprintf("[%02d-%02d-%4d %02d:%02d:%02d]",$logmon+1,$logmday,$logyear+1900,$loghour,$logmin,$logsec);
$logmon++;
my $logdir = probe::get_value("logDirectory");
my $logpostfix = probe::get_value("logPostfix");
$logyear = $logyear + 1900;
my $logfile="$logdir\/$logmon-$logmday-$logyear-$logpostfix.log";
eval {
open(LOGOUT, ">>$logfile") || die "$logfile";
};
if($@) {
chomp($@);
print "[ERROR] could not write logfile \"$logfile\" $!\n";
print "[WARNING] log text was \"$text\"\n";
}
else {
my $latest_symlink = "$logdir" . "/" . "$logpostfix.latest";
system("rm -rf $latest_symlink 1>>/dev/null 2>>/dev/null; ln -s $logfile $latest_symlink 1>>/dev/null 2>>/dev/null");
}
print LOGOUT "$timestamp $text\n";
close(LOGOUT);
}
1;