--- loncom/loncron 2004/05/11 19:12:50 1.46
+++ loncom/loncron 2018/10/29 02:46:41 1.111
@@ -1,47 +1,54 @@
#!/usr/bin/perl
-# The LearningOnline Network
-# Housekeeping program, started by cron
+# Housekeeping program, started by cron, loncontrol and loncron.pl
#
-# (TCP networking package
-# 6/1/99,6/2,6/10,6/11,6/12,6/14,6/26,6/28,6/29,6/30,
-# 7/1,7/2,7/9,7/10,7/12 Gerd Kortemeyer)
+# $Id: loncron,v 1.111 2018/10/29 02:46:41 raeburn Exp $
+#
+# Copyright Michigan State University Board of Trustees
+#
+# This file is part of the LearningOnline Network with CAPA (LON-CAPA).
+#
+# LON-CAPA is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# LON-CAPA is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with LON-CAPA; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+# /home/httpd/html/adm/gpl.txt
+#
+# http://www.lon-capa.org/
#
-# 7/14,7/15,7/19,7/21,7/22,11/18,
-# 2/8 Gerd Kortemeyer
-# 12/23 Gerd Kortemeyer
-# YEAR=2001
-# 09/04,09/06,11/26 Gerd Kortemeyer
$|=1;
+use strict;
use lib '/home/httpd/lib/perl/';
use LONCAPA::Configuration;
+use LONCAPA::Checksumming;
+use LONCAPA;
+use Apache::lonnet;
+use Apache::loncommon;
use IO::File;
use IO::Socket;
-use strict;
+use HTML::Entities;
+use Getopt::Long;
+use GDBM_File;
+use Storable qw(thaw);
#globals
use vars qw (%perlvar %simplestatus $errors $warnings $notices $totalcount);
my $statusdir="/home/httpd/html/lon-status";
-# -------------------------------------------------- Non-critical communication
-sub reply {
- my ($cmd,$server)=@_;
- my $peerfile="$perlvar{'lonSockDir'}/$server";
- my $client=IO::Socket::UNIX->new(Peer =>"$peerfile",
- Type => SOCK_STREAM,
- Timeout => 10)
- or return "con_lost";
- print $client "$cmd\n";
- my $answer=<$client>;
- chomp($answer);
- if (!$answer) { $answer="con_lost"; }
- return $answer;
-}
-
# --------------------------------------------------------- Output error status
sub log {
@@ -52,56 +59,74 @@ sub log {
sub errout {
my $fh=shift;
&log($fh,(<
+ Rotating $description ... ");
+ &log($fh," Seems like it started ... ");
+ &log($fh," Seems like that did not work! ');
+ printf("%-15s ",$daemon);
+ if ($fh) {
+ if (-e "$perlvar{'lonDaemons'}/logs/$daemon.log"){
+ if (open(DFH,"tail -n25 $perlvar{'lonDaemons'}/logs/$daemon.log|")) {
+ while (my $line= ");
+ &log($fh," Give it one more try ... ");
+ &log($fh," Unable to start $daemon
+
ENDERROUT
}
+sub rotate_logfile {
+ my ($file,$fh,$description) = @_;
+ my $size=(stat($file))[7];
+ if ($size>40000) {
+ &log($fh,"
Notices $notices Warnings $warnings
- Errors $errors '.$daemon.'
Log
');
- printf("%-10s ",$daemon);
- if (-e "$perlvar{'lonDaemons'}/logs/$daemon.log"){
- open (DFH,"tail -n25 $perlvar{'lonDaemons'}/logs/$daemon.log|");
- while (my $line=
");
my $pidfile="$perlvar{'lonDaemons'}/logs/$daemon.pid";
@@ -111,13 +136,20 @@ sub checkon_daemon {
my $lfh=IO::File->new("$pidfile");
$daemonpid=<$lfh>;
chomp($daemonpid);
- if (kill 0 => $daemonpid) {
+ if ($daemonpid =~ /^\d+$/ && kill 0 => $daemonpid) {
&log($fh,"'.$daemon.'
Log
$daemon at pid $daemonpid responding");
- if ($sendusr1) { &log($fh,", sending USR1"); }
+ if ($send) { &log($fh,", sending $send"); }
&log($fh,"
");
- if ($sendusr1) { kill USR1 => $daemonpid; }
+ if ($send eq 'USR1') { kill USR1 => $daemonpid; }
+ if ($send eq 'USR2') { kill USR2 => $daemonpid; }
$restartflag=0;
- print "running\n";
+ if ($send eq 'USR2') {
+ $result = 'reloaded';
+ print "reloaded\n";
+ } else {
+ $result = 'running';
+ print "running\n";
+ }
} else {
$errors++;
&log($fh,"$daemon at pid $daemonpid not responding
");
@@ -128,70 +160,70 @@ sub checkon_daemon {
if ($restartflag==1) {
$simplestatus{$daemon}='off';
$errors++;
- &log($fh,'
Killall '.$daemon.': '.
- `killall $daemon 2>&1`.' - ');
- sleep 2;
+ my $kadaemon=$daemon;
+ if ($kadaemon eq 'lonmemcached') { $kadaemon='memcached'; }
+ &log($fh,'
Killall '.$daemon.': '.
+ `killall $kadaemon 2>&1`.' - ');
+ sleep 1;
&log($fh,unlink($pidfile).' - '.
- `killall -9 $daemon 2>&1`.
- '
');
+ `killall -9 $kadaemon 2>&1`.
+ '
');
+ if ($kadaemon eq 'loncnew') {
+ &clean_lonc_childpids();
+ }
&log($fh,"$daemon not running, trying to start
");
-
- if (&start_daemon($fh,$daemon,$pidfile)) {
+
+ if (&start_daemon($fh,$daemon,$pidfile,$args)) {
&log($fh,"$daemon at pid $daemonpid responding
");
$simplestatus{$daemon}='restarted';
+ $result = 'started';
print "started\n";
} else {
$errors++;
&log($fh,"$daemon at pid $daemonpid not responding
");
- &log($fh,"Give it one more try ...$daemon at pid $daemonpid responding
");
$simplestatus{$daemon}='restarted';
+ $result = 'started';
print "started\n";
} else {
+ $result = 'failed';
print " failed\n";
$simplestatus{$daemon}='failed';
$errors++; $errors++;
&log($fh,"$daemon at pid $daemonpid not responding
");
- &log($fh,"Unable to start $daemon");
- open (DFH,"tail -n100 $perlvar{'lonDaemons'}/logs/$daemon.log|");
- while (my $line=
");
+ if ($fh) {
+ if (-e "$perlvar{'lonDaemons'}/logs/$daemon.log"){
+ &log($fh,"");
+ if (open(DFH,"tail -n100 $perlvar{'lonDaemons'}/logs/$daemon.log|")) {
+ while (my $line=
"); - rename("$fname.2","$fname.3"); - rename("$fname.1","$fname.2"); - rename("$fname","$fname.1"); - } + &rotate_logfile($fname,$fh,'logs'); &errout($fh); + return $result; } # --------------------------------------------------------------------- Machine sub log_machine_info { my ($fh)=@_; - &log($fh,'
"); my $psproc=0; - open (PSH,"ps -aux --cols 140 |"); + open (PSH,"ps aux --cols 140 |"); while (my $line=) { - &log($fh,"$line"); + &log($fh,&encode_entities($line,'<>&"')); $psproc++; } close (PSH); @@ -247,11 +279,15 @@ sub log_machine_info { if ($psproc>200) { $notices++; } if ($psproc>250) { $notices++; } + &log($fh," distprobe
"); + &log($fh,""); + &log($fh,&encode_entities(&LONCAPA::distro(),'<>&"')); + &log($fh,""); + &errout($fh); } sub start_logging { - my ($hostdom,$hostrole,$hostname,$spareid)=@_; my $fh=IO::File->new(">$statusdir/newstatus.html"); my %simplestatus=(); my $now=time; @@ -259,91 +295,200 @@ sub start_logging { &log($fh,(<+ + LON Status Report $perlvar{'lonHostID'} + - +LON Status Report $perlvar{'lonHostID'}
$date ($now)
-
-- Configuration -
- Machine Information -
- Temporary Files -
- Session Tokens -
- httpd -
- lonsql -
- lond -
- lonc -
- lonhttpd -
- lonnet -
- Connections -
- Delayed Messages -
- Error Coindex.html.unt +
- Configuration
+- Machine Information
+- Temporary Files
+- Session Tokens
+- WebDAV Session Tokens
+- httpd
+- lonsql
+- lond
+- lonc
+- lonnet
+- Connections
+- Delayed Messages
+- Error Count
- +
+Configuration
PerlVars
-+
ENDHEADERS foreach my $varname (sort(keys(%perlvar))) { - &log($fh,"
\n"); + &log($fh," $varname $perlvar{$varname} \n"); } - &log($fh," $varname ". + &encode_entities($perlvar{$varname},'<>&"')." Hosts
"); - foreach my $id (sort(keys(%{$hostname}))) { + &log($fh,"
Hosts
"); + my %hostname = &Apache::lonnet::all_hostnames(); + foreach my $id (sort(keys(%hostname))) { + my $role = (&Apache::lonnet::is_library($id) ? 'library' + : 'access'); &log($fh, - "
\n"); - } - &log($fh," $id ".$hostdom->{$id}. - " ".$hostrole->{$id}. - " ".$hostname->{$id}." Spare Hosts
"); - foreach my $id (sort(keys(%{$spareid}))) { - &log($fh,"
- $id\n"); + "
\n"); + } + &log($fh," $id ".&Apache::lonnet::host_domain($id). + " ".$role. + " ".&Apache::lonnet::hostname($id)." Spare Hosts
"); + if (keys(%Apache::lonnet::spareid) > 0) { + &log($fh,""); + foreach my $type (sort(keys(%Apache::lonnet::spareid))) { + &log($fh,"
\n"); + } else { + &log($fh,"No spare hosts specified- $type\n
\n"); + } + &log($fh,""); + foreach my $id (@{ $Apache::lonnet::spareid{$type} }) { + &log($fh,"
\n- $id
\n"); + } + &log($fh,"
\n"); } - &log($fh,"\n"); return $fh; } # --------------------------------------------------------------- clean out tmp sub clean_tmp { my ($fh)=@_; - &log($fh,'Temporary Files
'); - my $cleaned=0; - my $old=0; - while (my $fname=<$perlvar{'lonDaemons'}/tmp/*>) { - my ($dev,$ino,$mode,$nlink, - $uid,$gid,$rdev,$size, - $atime,$mtime,$ctime, - $blksize,$blocks)=stat($fname); - my $now=time; - my $since=$now-$mtime; - if ($since>$perlvar{'lonExpire'}) { - my $line=''; - if (open(PROBE,$fname)) { - $line=; - close(PROBE); - } - unless ($line=~/^CHECKOUTTOKEN\&/) { - $cleaned++; - unlink("$fname"); - } else { - if ($since>365*$perlvar{'lonExpire'}) { - $cleaned++; - unlink("$fname"); - } else { $old++; } - } - } + &log($fh,' Temporary Files
'); + my ($cleaned,$old,$removed) = (0,0,0); + my %errors = ( + dir => [], + file => [], + failopen => [], + ); + my %error_titles = ( + dir => 'failed to remove empty directory:', + file => 'failed to unlike stale file', + failopen => 'failed to open file or directory' + ); + ($cleaned,$old,$removed) = &recursive_clean_tmp('',$cleaned,$old,$removed,\%errors); + &log($fh,"Cleaned up: ".$cleaned." files; removed: $removed empty directories; (found: $old old checkout tokens)"); + foreach my $key (sort(keys(%errors))) { + if (ref($errors{$key}) eq 'ARRAY') { + if (@{$errors{$key}} > 0) { + &log($fh,"Error during cleanup ($error_titles{$key}):
- ". + join('
- ',@{$errors{$key}}).'
'); + } + } + } +} + +sub recursive_clean_tmp { + my ($subdir,$cleaned,$old,$removed,$errors) = @_; + my $base = "$perlvar{'lonDaemons'}/tmp"; + my $path = $base; + next if ($subdir =~ m{\.\./}); + next unless (ref($errors) eq 'HASH'); + unless ($subdir eq '') { + $path .= '/'.$subdir; + } + if (opendir(my $dh,"$path")) { + while (my $file = readdir($dh)) { + next if ($file =~ /^\.\.?$/); + my $fname = "$path/$file"; + if (-d $fname) { + my $innerdir; + if ($subdir eq '') { + $innerdir = $file; + } else { + $innerdir = $subdir.'/'.$file; + } + ($cleaned,$old,$removed) = + &recursive_clean_tmp($innerdir,$cleaned,$old,$removed,$errors); + my @doms = &Apache::lonnet::current_machine_domains(); + + if (open(my $dirhandle,$fname)) { + unless (($innerdir eq 'helprequests') || + (($innerdir =~ /^addcourse/) && ($innerdir !~ m{/\d+$}))) { + my @contents = grep {!/^\.\.?$/} readdir($dirhandle); + join('&&',@contents)."\n"; + if (scalar(grep {!/^\.\.?$/} readdir($dirhandle)) == 0) { + closedir($dirhandle); + if ($fname =~ m{^\Q$perlvar{'lonDaemons'}\E/tmp/}) { + if (rmdir($fname)) { + $removed ++; + } elsif (ref($errors->{dir}) eq 'ARRAY') { + push(@{$errors->{dir}},$fname); + } + } + } + } else { + closedir($dirhandle); + } + } + } else { + my ($dev,$ino,$mode,$nlink, + $uid,$gid,$rdev,$size, + $atime,$mtime,$ctime, + $blksize,$blocks)=stat($fname); + my $now=time; + my $since=$now-$mtime; + if ($since>$perlvar{'lonExpire'}) { + if ($subdir eq '') { + my $line=''; + if ($fname =~ /\.db$/) { + if (unlink($fname)) { + $cleaned++; + } elsif (ref($errors->{file}) eq 'ARRAY') { + push(@{$errors->{file}},$fname); + } + } elsif (open(PROBE,$fname)) { + my $line=''; + $line=; + close(PROBE); + if ($line=~/^CHECKOUTTOKEN\&/) { + if ($since>365*$perlvar{'lonExpire'}) { + if (unlink($fname)) { + $cleaned++; + } elsif (ref($errors->{file}) eq 'ARRAY') { + push(@{$errors->{file}},$fname); + } + } else { + $old++; + } + } else { + if (unlink($fname)) { + $cleaned++; + } elsif (ref($errors->{file}) eq 'ARRAY') { + push(@{$errors->{file}},$fname); + } + } + } elsif (ref($errors->{failopen}) eq 'ARRAY') { + push(@{$errors->{failopen}},$fname); + } + } else { + if (unlink($fname)) { + $cleaned++; + } elsif (ref($errors->{file}) eq 'ARRAY') { + push(@{$errors->{file}},$fname); + } + } + } + } + } + closedir($dh); + } elsif (ref($errors->{failopen}) eq 'ARRAY') { + push(@{$errors->{failopen}},$path); } - &log($fh,"Cleaned up ".$cleaned." files (".$old." old checkout tokens)."); + return ($cleaned,$old,$removed); } # ------------------------------------------------------------ clean out lonIDs sub clean_lonIDs { my ($fh)=@_; - &log($fh,' Session Tokens
'); + &log($fh,'Session Tokens
'); my $cleaned=0; my $active=0; while (my $fname=<$perlvar{'lonIDsDir'}/*>) { @@ -355,48 +500,81 @@ sub clean_lonIDs { my $since=$now-$mtime; if ($since>$perlvar{'lonExpire'}) { $cleaned++; - &log($fh,"Unlinking $fname
"); + &log($fh,"Unlinking $fname
"); unlink("$fname"); } else { $active++; } } - &log($fh,"Cleaned up ".$cleaned." stale session token(s)."); + &log($fh,"
Cleaned up ".$cleaned." stale session token(s).
"); &log($fh,"$active open session(s)
"); } +# ------------------------------------------------ clean out webDAV Session IDs +sub clean_webDAV_sessionIDs { + my ($fh)=@_; + if ($perlvar{'lonRole'} eq 'library') { + &log($fh,'WebDAV Session Tokens
'); + my $cleaned=0; + my $active=0; + my $now = time; + if (-d $perlvar{'lonDAVsessDir'}) { + while (my $fname=<$perlvar{'lonDAVsessDir'}/*>) { + my @stats = stat($fname); + my $since=$now-$stats[9]; + if ($since>$perlvar{'lonExpire'}) { + $cleaned++; + &log($fh,"Unlinking $fname
"); + unlink("$fname"); + } else { + $active++; + } + } + &log($fh,"Cleaned up ".$cleaned." stale webDAV session token(s).
"); + &log($fh,"$active open webDAV session(s)
"); + } + } +} + +# ----------------------------------------------------------- clean out sockets +sub clean_sockets { + my ($fh)=@_; + my $cleaned=0; + opendir(SOCKETS,$perlvar{'lonSockDir'}); + while (my $fname=readdir(SOCKETS)) { + next if (-d $fname + || $fname=~/(mysqlsock|maximasock|rsock|\Q$perlvar{'lonSockDir'}\E)/); + $cleaned++; + &log($fh,"Unlinking $fname
"); + unlink("/home/httpd/sockets/$fname"); + } + &log($fh,"Cleaned up ".$cleaned." stale sockets.
"); +} + # ----------------------------------------------------------------------- httpd sub check_httpd_logs { my ($fh)=@_; - &log($fh,'httpd
Access Log
'); - - open (DFH,"tail -n25 /etc/httpd/logs/access_log|"); - while (my $line=) { &log($fh,"$line") }; - close (DFH); - - &log($fh," Error Log
"); - - open (DFH,"tail -n25 /etc/httpd/logs/error_log|"); - while (my $line="); &errout($fh); } # ---------------------------------------------------------------------- lonnet -sub rotate_logs { +sub rotate_lonnet_logs { my ($fh)=@_; - &log($fh,') { - &log($fh,"$line"); - if ($line=~/\[error\]/) { $notices++; } + if (open(PIPE,"./lchttpdlogs|")) { + while (my $line= ) { + &log($fh,$line); + if ($line=~/\[error\]/) { $notices++; } + } + close(PIPE); } - close (DFH); - &log($fh," lonnet
Temp Log
'); - print "checking logs\n"; + &log($fh,'lonnet
Temp Log
'); + print "Checking logs.\n"; if (-e "$perlvar{'lonDaemons'}/logs/lonnet.log"){ open (DFH,"tail -n50 $perlvar{'lonDaemons'}/logs/lonnet.log|"); while (my $line="); &errout($fh); } +sub rotate_other_logs { + my ($fh) = @_; + my %logs = ( + autoenroll => 'Auto Enroll log', + autocreate => 'Create Course log', + searchcat => 'Search Cataloguing log', + autoupdate => 'Auto Update log', + refreshcourseids_db => 'Refresh CourseIDs db log', + ); + foreach my $item (keys(%logs)) { + my $fname=$perlvar{'lonDaemons'}.'/logs/'.$item.'.log'; + &rotate_logfile($fname,$fh,$logs{$item}); + } +} + # ----------------------------------------------------------------- Connections sub test_connections { - my ($fh,$hostname)=@_; - &log($fh,') { - &log($fh,"$line"); + &log($fh,&encode_entities($line,'<>&"')); } close (DFH); } @@ -405,51 +583,63 @@ sub rotate_logs { if (-e "$perlvar{'lonDaemons'}/logs/lonnet.perm.log") { open(DFH,"tail -n10 $perlvar{'lonDaemons'}/logs/lonnet.perm.log|"); while (my $line= ) { - &log($fh,"$line"); + &log($fh,&encode_entities($line,'<>&"')); } close (DFH); } else { &log($fh,"No perm log\n") } my $fname="$perlvar{'lonDaemons'}/logs/lonnet.log"; - - my ($dev,$ino,$mode,$nlink, - $uid,$gid,$rdev,$size, - $atime,$mtime,$ctime, - $blksize,$blocks)=stat($fname); - - if ($size>40000) { - &log($fh,"Rotating logs ... "); - rename("$fname.2","$fname.3"); - rename("$fname.1","$fname.2"); - rename("$fname","$fname.1"); - } + &rotate_logfile($fname,$fh,'lonnet log'); &log($fh,"
Connections
'); - print "testing connections\n"; - &log($fh,""); - foreach my $tryserver (sort(keys(%{$hostname}))) { + my ($fh)=@_; + &log($fh,'
Connections
'); + print "Testing connections.\n"; + &log($fh,""); + my ($good,$bad)=(0,0); + my %hostname = &Apache::lonnet::all_hostnames(); + foreach my $tryserver (sort(keys(%hostname))) { print("."); my $result; - my $answer=reply("pong",$tryserver); + my $answer=&Apache::lonnet::reply("ping",$tryserver); if ($answer eq "$tryserver:$perlvar{'lonHostID'}") { $result="ok"; + $good++; } else { $result=$answer; $warnings++; - if ($answer eq 'con_lost') { $warnings++; } + if ($answer eq 'con_lost') { + $bad++; + $warnings++; + } else { + $good++; #self connection + } } if ($answer =~ /con_lost/) { print(" $tryserver down\n"); } &log($fh,"
"); - + print "\n$good good, $bad bad connections\n"; &errout($fh); } @@ -457,48 +647,81 @@ sub test_connections { # ------------------------------------------------------------ Delayed messages sub check_delayed_msg { my ($fh)=@_; - &log($fh,'\n"); } &log($fh," $tryserver $result Delayed Messages
'); - print "checking buffers\n"; + &log($fh,'Delayed Messages
'); + print "Checking buffers.\n"; &log($fh,'Scanning Permanent Log
'); my $unsend=0; + my %hostname = &Apache::lonnet::all_hostnames(); + my $numhosts = scalar(keys(%hostname)); + my $dfh=IO::File->new("$perlvar{'lonDaemons'}/logs/lonnet.perm.log"); while (my $line=<$dfh>) { my ($time,$sdf,$dserv,$dcmd)=split(/:/,$line); + if ($numhosts) { + next unless ($hostname{$dserv}); + } if ($sdf eq 'F') { my $local=localtime($time); - &log($fh,"Failed: $time, $dserv, $dcmd
"); + &log($fh,"Failed: $time, $dserv, $dcmd
"); $warnings++; } if ($sdf eq 'S') { $unsend--; } if ($sdf eq 'D') { $unsend++; } } - &log($fh,"Total unsend messages: $unsend\n"); - $warnings=$warnings+5*$unsend; + &log($fh,"
Total unsend messages: $unsend
\n"); + if ($unsend > 0) { + $warnings=$warnings+5*$unsend; + } if ($unsend) { $simplestatus{'unsend'}=$unsend; } - &log($fh,"Outgoing Buffer
"); - + &log($fh,"Outgoing Buffer
\n"); +# list directory with delayed messages and remember offline servers + my %servers=(); open (DFH,"ls -lF $perlvar{'lonSockDir'}/delayed|"); - while (my $line=\n"); close (DFH); +# pong to all servers that have delayed messages +# this will trigger a reverse connection, which should flush the buffers + foreach my $tryserver (sort(keys(%servers))) { + if ($hostname{$tryserver} || !$numhosts) { + my $answer; + eval { + local $SIG{ ALRM } = sub { die "TIMEOUT" }; + alarm(20); + $answer = &Apache::lonnet::reply("pong",$tryserver); + alarm(0); + }; + if ($@ && $@ =~ m/TIMEOUT/) { + &log($fh,"Attempted pong to $tryserver timed out) { - &log($fh,"$line
"); + while (my $line=) { + my ($server)=($line=~/\.(\w+)$/); + if ($server) { $servers{$server}=1; } + &log($fh,&encode_entities($line,'<>&"')); } + &log($fh,"
"); + print "Time out while contacting: $tryserver for pong.\n"; + } else { + &log($fh,"Pong to $tryserver: $answer
"); + } + } else { + &log($fh,"$tryserver has delayed messages, but is not part of the cluster -- skipping 'Pong'.
"); + } + } } sub finish_logging { my ($fh)=@_; - &log($fh,"\n"); + &log($fh,"\n"); $totalcount=$notices+4*$warnings+100*$errors; &errout($fh); &log($fh,"Total Error Count: $totalcount
"); my $now=time; my $date=localtime($now); - &log($fh,"
$date ($now)\n"); - print "lon-status webpage updated\n"; + &log($fh,"
$date ($now)