Subject: | Feature request: Maintain a current symlink to latest log file |
First off, thanks for this module. We've used to generate many many gigs of logs.
However, most of the other logging systems we've used maintain a ".current" symlink to the latest log file. This is handy because you can do "tail -F app.current" and see an uninterrupted tail of the logs without worrying about rollovers.
I would really appreciate it if Log::Dispatch::File::Rolling could do something similar so I'm attaching a patch that adds this feature.
If you feel that this is at the wrong layer, no worries. I'll make a module that sub-classes your module. It feels like it makes sense here though since it's related to the "rolling" aspect of the logging.
Cheers,
Doug
Subject: | current-symlink.patch |
diff --git a/lib/Log/Dispatch/File/Rolling.pm b/lib/Log/Dispatch/File/Rolling.pm
index 2928c1e..957deba 100755
--- a/lib/Log/Dispatch/File/Rolling.pm
+++ b/lib/Log/Dispatch/File/Rolling.pm
@@ -62,6 +62,10 @@ sub new {
$self->{filename} = $self->_createFilename();
}
+ if (exists $p{current_symlink}) {
+ $self->{current_symlink} = $p{current_symlink};
+ }
+
$self->{rolling_fh_pid} = $$;
$self->_make_handle();
@@ -79,7 +83,7 @@ sub log_message { # parts borrowed from Log::Dispatch::FileRotate, Thanks!
}
if ( $self->{close} ) {
- $self->_open_file;
+ $self->_rolling_open_file;
$self->_lock();
my $fh = $self->{fh};
print $fh $p{message};
@@ -90,7 +94,9 @@ sub log_message { # parts borrowed from Log::Dispatch::FileRotate, Thanks!
my $inode = (stat($self->{fh}))[1]; # get real inode
my $finode = (stat($self->{filename}))[1]; # Stat the name for comparision
if(!defined($finode) || $inode != $finode) { # Oops someone moved things on us. So just reopen our log
- $self->_open_file;
+ $self->_rolling_open_file;
+ } elsif (!$self->{current_symlink_inited}) {
+ $self->_update_current_symlink;
}
$self->_lock();
my $fh = $self->{fh};
@@ -98,7 +104,7 @@ sub log_message { # parts borrowed from Log::Dispatch::FileRotate, Thanks!
$self->_unlock();
} else {
$self->{rolling_fh_pid} = $$;
- $self->_open_file;
+ $self->_rolling_open_file;
$self->_lock();
my $fh = $self->{fh};
print $fh $p{message};
@@ -106,6 +112,37 @@ sub log_message { # parts borrowed from Log::Dispatch::FileRotate, Thanks!
}
}
+sub _rolling_open_file {
+ my $self = shift;
+
+ $self->_open_file;
+
+ $self->_update_current_symlink;
+}
+
+sub _update_current_symlink {
+ my $self = shift;
+
+ return if !exists $self->{current_symlink};
+
+ my $current_symlink_value = readlink($self->{current_symlink});
+
+ if (!defined $current_symlink_value || $current_symlink_value ne $self->{filename}) {
+ my $temp_symlink_file = "$self->{current_symlink}.temp$$";
+ unlink($temp_symlink_file);
+
+ symlink($self->{filename}, $temp_symlink_file)
+ || die "unable to create symlink '$temp_symlink_file': $!";
+
+ if (!rename($temp_symlink_file, $self->{current_symlink})) {
+ unlink($temp_symlink_file);
+ die "unable to overwrite symlink '$self->{current_symlink}': $!";
+ }
+ }
+
+ $self->{current_symlink_inited} = 1;
+}
+
sub _lock { # borrowed from Log::Dispatch::FileRotate, Thanks!
my $self = shift;
flock($self->{fh},LOCK_EX);
@@ -207,6 +244,13 @@ needed regularly as this module also supports logfile sharing between
processes, but if you've got a high load on your logfile or a system
that doesn't support flock()...
+=item current symlinks
+
+If you pass in C<current_symlink> to the constructor, it will create a
+symlink at your provided filename. This symlink will always link to the
+most recent log file. You can then use C<tail -F> to monitor an application's
+logs with no interruptions even when the filename rolls over.
+
=back
=head1 METHODS
diff --git a/t/6currentsymlink.t b/t/6currentsymlink.t
new file mode 100755
index 0000000..ce81c4e
--- /dev/null
+++ b/t/6currentsymlink.t
@@ -0,0 +1,89 @@
+# Before `make install' is performed this script should be runnable with
+# `make test'. After `make install' it should work as `perl 6currentsymlink.t'
+
+#########################
+
+# change 'tests => 1' to 'tests => last_test_to_print';
+
+use Test;
+BEGIN { plan tests => 9 };
+use Log::Dispatch;
+use Log::Dispatch::File::Rolling;
+ok(1); # If we made it this far, we're ok.
+
+#########################1
+
+my $dispatcher = Log::Dispatch->new;
+ok($dispatcher);
+
+#########################2
+
+my $curr_symlink_filename = 'currsym';
+
+my %params = (
+ name => 'file',
+ min_level => 'debug',
+ filename => 'logfile',
+ current_symlink => $curr_symlink_filename,
+);
+
+my $Rolling = Log::Dispatch::File::Rolling->new(%params);
+ok($Rolling);
+
+#########################3
+
+$dispatcher->add($Rolling);
+
+ok(1);
+
+#########################4
+
+my $message = 'logtest id ' . int(rand(9999));
+
+$dispatcher->log( level => 'info', message => $message );
+
+ok(1);
+
+#########################5
+
+$dispatcher = $Rolling = undef;
+
+ok(1);
+
+#########################6
+
+my @logfiles = glob("logfile.2*");
+
+ok(scalar(@logfiles) == 1 or scalar(@logfiles) == 2);
+
+#########################7
+
+my $content = '';
+
+foreach my $file (@logfiles) {
+ open F, '<', $file;
+ local $/ = undef;
+ $content .= <F>;
+ close F;
+}
+
+ok($content =~ /$message/);
+
+my $content2 = '';
+
+{
+ open F, '<', $curr_symlink_filename;
+ local $/ = undef;
+ $content2 .= <F>;
+ close F;
+}
+
+ok($content2 =~ /$message/);
+
+foreach my $file (@logfiles) {
+ unlink $file;
+}
+
+unlink $curr_symlink_filename;
+
+#########################8