Added PID, ACCESS, ERROR files and logrotation.
authorHeiko Schlittermann <hs@schlittermann.de>
Thu, 29 Jan 2009 06:01:08 +0100
changeset 23 a72544991225
parent 22 044bd52da735
child 24 1523d46da1d2
Added PID, ACCESS, ERROR files and logrotation.
hlog
--- a/hlog	Thu Jan 29 04:32:12 2009 +0100
+++ b/hlog	Thu Jan 29 06:01:08 2009 +0100
@@ -23,6 +23,7 @@
 use Getopt::Long;
 use IO::Socket::INET;
 use Pod::Usage;
+use File::Basename;
 use POSIX qw(:sys_wait_h setsid);
 use Cwd;
 
@@ -30,13 +31,31 @@
 my $opt_port   = 8080;
 my $opt_lines  = 10;
 my $opt_daemon = 1;
-my $logfile    = "hlog.log";
-my %FILE;
+my $opt_kill   = 0;
+
+my $ME         = basename $0;
+my $rundir     = [ "/var/run/$ME", "$ENV{HOME}/.$ME" ];
+my $logdir     = [ "/var/log/$ME", "$ENV{HOME}/.$ME" ];
+my $access     = \"%s/access.log";
+my $errors     = \"%s/error.log";
+my $pidfile    = \"%s/%s.%s.pid";                         # %dir/%ip.%port
+my $maxlogsize = 1000_000_000;                            # ca 1 MByte
+
+END {
+    unlink $pidfile 
+    if defined $pidfile and not ref $pidfile;
+}
+
 
 sub handle_request($);
 sub date1123(;$);
 sub http($@);
 sub bad_request();
+sub log_open($);
+sub log_write($);
+sub find_writable_dir(@);
+
+my %FILE;
 
 MAIN: {
 
@@ -44,13 +63,19 @@
         "addr=s"  => \$opt_addr,
         "port=i"  => \$opt_port,
         "lines=i" => \$opt_lines,
+        "daemon!" => \$opt_daemon,
+	"kill"	  => \$opt_kill,
         "help"    => sub { pod2usage(-verbose => 1, -exitval => 0) },
         "man"     => sub { pod2usage(-verbose => 2, -exitval => 0) },
-        "daemon!" => \$opt_daemon,
     ) or pod2usage();
 
-    open(LOG, ">>$logfile");
-    print LOG localtime() . " started\n";
+    if (defined($logdir = find_writable_dir(@$logdir))) {
+        $access = sprintf $$access, $logdir;
+        $errors = sprintf $$errors, $logdir;
+        log_open($access);
+    }
+
+    $rundir = find_writable_dir(@$rundir);
 
     pod2usage() if not @ARGV;
 
@@ -71,28 +96,56 @@
         ReuseAddr => 1,
     ) or die "Can't create listener socket: $!\n";
 
+    if ($rundir) {
+        $pidfile = sprintf $$pidfile, $rundir,
+          $listener->sockhost,
+          $listener->sockport;
+    }
+    else { $pidfile = undef }
+
     # go daemon
     chdir("/") or die "Can't chdir to /: $!\n";
 
     if ($opt_daemon) {
-        open(STDIN,  "/dev/null")  or die "Can't read /dev/null: $!\n";
-        open(STDOUT, ">/dev/null") or die "Can't write to /dev/null: $!\n";
+
         defined(my $pid = fork()) or die "Can't fork: $!\n";
 
+        # parent
         if ($pid) {
-            warn "listener $pid $opt_addr:$opt_port\n";
+            print "listener $pid "
+              . $listener->sockhost . ":"
+              . $listener->sockport . "\n";
+	    undef $pidfile;
             exit 0;
         }
 
         # child
+        setsid() or die "Can't start a new session: $!\n";
+        open(STDIN,  "/dev/null")  or die "Can't read /dev/null: $!\n";
+        open(STDOUT, ">/dev/null") or die "Can't write to /dev/null: $!\n";
 
-        setsid() or die "Can't start a new session: $!\n";
-        open(STDERR, ">&STDOUT") or die "Can't dup stdout: $!\n";
+        if (defined $logdir) {
+            open(STDERR, $_ = ">>$errors") or warn "Can't open $_: $!\n";
+        }
+        else {
+            open(STDERR, ">&STDOUT") or die "Can't dup stdout: $!\n";
+        }
+
+    }
+
+    $SIG{INT} = $SIG{TERM} = sub { warn "Got signal $_[0]\n"; exit 0 };
+    $SIG{__DIE__} = sub { print STDERR @_; exit $? };
+
+    if (defined $pidfile) {
+        open(PID, ">$pidfile")
+          or die "Can't open $pidfile: $!\n";
+
+        print PID "$$\n";
+        close PID;
     }
 
     $SIG{CHLD} = sub {
-        while ((my $pid = waitpid(-1, WNOHANG)) > 0) {
-            print LOG localtime() . " child $pid died\n";
+        while (waitpid(-1, WNOHANG) > 0) {
         }
     };
 
@@ -100,12 +153,6 @@
         my $client = $listener->accept;
         next if not defined $client;    # may be because of signal
 
-        print LOG $_ =
-            localtime()
-          . " access from "
-          . $client->peerhost . ":"
-          . $client->peerport . "\n";
-
         my $pid = fork();
         die "Can't fork: $!\n" if not defined $pid;
         if ($pid == 0) {
@@ -115,6 +162,38 @@
             exit 0;
         }
         $client->close;
+
+        # maintenance of logfiles
+        if (-s $access > $maxlogsize) {
+            rename $access, "$access.1";
+            log_open($access);
+        }
+
+        if (-s $errors > $maxlogsize) {
+            rename $errors, "$errors.1";
+            open(STDERR, ">>$errors");
+        }
+    }
+
+}
+
+sub find_writable_dir(@) {
+    foreach (@_) {
+        return $_ if -d and -w _;
+        return $_ if mkdir $_, 0755;
+    }
+    return undef;
+}
+
+{
+    my $fh;
+
+    sub log_open($) {
+        open($fh, $_ = ">>$_[0]") or die "Can't open $_: $!\n";
+    }
+
+    sub log_write($) {
+        $fh->print(localtime() . " $_[0]\n");
     }
 
 }
@@ -144,17 +223,18 @@
     if (not exists $FILE{$tag}) {
         $client->print(http "500 unknown file tag",
             "Sorry, unknown file tag \"$tag\"");
-        print LOG " unknown tag $tag";
+        log_write("unknown tag $tag");
         return;
     }
 
     my %file = analyze($FILE{$tag});
     if (!%file) {
         $client->print(http "500 internal error", "internal error");
-        print LOG $@;
         return;
     }
 
+    log_write($client->peerhost . ":" . $client->peerport . " $tag ($lines)");
+
     seek($file{fh}, -($lines + 1) * $file{avglen}, 2);
     $file{fh}->getline;
 
@@ -259,6 +339,18 @@
 This script should run as a server providing access to 
 the last lines of a logfile. It should understand basic HTTP/1.x.
 
+B<hlog> looks for a writable "log" directory (or tries to create on) as
+F</var/log/hlog> and F<$HOME/.hlog> and opens there an F<access.log> and
+an F<error.log>.  It is not fatal if it doesn't find a writable
+directory. Regularly (currently at each incoming request) the size of
+these logs is checked and the files are renamed to "*.1" whenever they
+are bigger than about 1MB.
+
+B<hlog> looks for a writable "run" directory (or tries to create on)
+as F</var/run/hlog> and F<$HOME/.hlog> and writes there the PID-file
+as I<addr>.I<port>.F<pid>. It's not fatal if it can't write the
+PID-file.
+
 =head1 OPTIONS
 
 =over