--- 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