###################################################################### # Synchronized.pm -- 2003, Mike Schilli ###################################################################### # Special appender employing a locking strategy to synchronize # access. ###################################################################### ########################################### package Log::Log4perl::Appender::Synchronized; ########################################### use strict; use warnings; our @ISA = qw(Log::Log4perl::Appender); use IPC::Shareable qw(:lock); use IPC::Semaphore; our $CVSVERSION = '$Revision: 1.9 $'; our ($VERSION) = ($CVSVERSION =~ /(\d+\.\d+)/); ########################################### sub new { ########################################### my($class, %options) = @_; my $self = { appender=> undef, key => '_l4p', options => { create => 1, destroy => 1 }, level => 0, %options, }; # Pass back the appender to be synchronized as a dependency # to the configuration file parser push @{$options{l4p_depends_on}}, $self->{appender}; # Blow away lingering semaphores nuke_sem($self->{key}); #warn "$$: IPCshareable created with $self->{key} $self->{options}\n"; $self->{ipc_shareable} = tie $self->{ipc_shareable_var}, 'IPC::Shareable', $self->{key}, $self->{options} or die "tie failed: $!"; $self->{ipc_shareable}->shunlock(); # Run our post_init method in the configurator after # all appenders have been defined to make sure the # appender we're syncronizing really exists push @{$options{l4p_post_config_subs}}, sub { $self->post_init() }; bless $self, $class; } ########################################### sub log { ########################################### my($self, %params) = @_; $self->{ipc_shareable}->shlock(); #warn "pid $$ entered\n"; # Relay that to the SUPER class which needs to render the # message according to the appender's layout, first. $Log::Log4perl::caller_depth +=2; $self->{app}->SUPER::log(\%params, $params{log4p_category}, $params{log4p_level}); $Log::Log4perl::caller_depth -=2; #warn "pid $$ leaves\n"; $self->{ipc_shareable}->shunlock(); } ########################################### sub post_init { ########################################### my($self) = @_; if(! exists $self->{appender}) { die "No appender defined for " . __PACKAGE__; } my $appenders = Log::Log4perl->appenders(); my $appender = Log::Log4perl->appenders()->{$self->{appender}}; if(! defined $appender) { die "Appender $self->{appender} not defined (yet) when " . __PACKAGE__ . " needed it"; } $self->{app} = $appender; } ########################################### sub DESTROY { ########################################### my($self) = @_; no warnings; delete $self->{ipc_shareable}; untie $self->{ipc_shareable_var}; } ########################################### sub nuke_sem { ########################################### # This function nukes a semaphore previously # allocated by IPC::Shareable, which seems to # hang in its tie() function if an old semaphore # is still lingering around. ########################################### my($key) = @_; $key = pack A4 => $key; $key = unpack i => $key; my $sem = IPC::Semaphore->new($key, 3, 0); # Didn't exist unless(defined $sem) { return undef; } $sem->remove() || die "Cannot remove semaphore $key ($!)"; return 1; } 1; __END__ =head1 NAME Log::Log4perl::Appender::Synchronized - Synchronizing other appenders =head1 SYNOPSIS use Log::Log4perl qw(:easy); my $conf = qq( log4perl.category = WARN, Syncer # File appender (unsynchronized) log4perl.appender.Logfile = Log::Log4perl::Appender::File log4perl.appender.Logfile.autoflush = 1 log4perl.appender.Logfile.filename = test.log log4perl.appender.Logfile.mode = truncate log4perl.appender.Logfile.layout = SimpleLayout # Synchronizing appender, using the file appender above log4perl.appender.Syncer = Log::Log4perl::Appender::Synchronized log4perl.appender.Syncer.appender = Logfile ); Log::Log4perl->init(\$conf); WARN("This message is guaranteed to be complete."); =head1 DESCRIPTION If multiple processes are using the same C appender without synchronization, overwrites might happen. A typical scenario for this would be a process spawning children, each of which inherits the parent's Log::Log4perl configuration. Usually, you should avoid this scenario and have each child have its own Log::Log4perl configuration, ensuring that each e.g. writes to a different logfile. In cases where you need additional synchronization, however, use C as a gateway between your loggers and your appenders. An appender itself, C just takes two additional arguments: =over 4 =item C Specifies the name of the appender it synchronizes access to. The appender specified must be defined somewhere in the configuration file, not necessarily before the definition of C. =item C This optional argument specifies the key for the semaphore that C uses internally to ensure atomic operations. It defaults to C<_l4p>. If you define more than one C appender, it is important to specify different keys for them, as otherwise every new C appender will nuke previously defined semaphores. The maximum key length is four characters, longer keys will be truncated to 4 characters -- C and C are interpreted to be the same: C (thanks to David Viner Edviner@yahoo-inc.comE for pointing this out). =back C uses C internally to perform locking with semaphores provided by the operating system used. =head2 Performance tips The C serializes access to a protected resource globally, slowing down actions otherwise performed in parallel. Unless specified otherwise, all instances of C objects in the system will use the same global IPC key C<_l4p>. To control access to different appender instances, it often makes sense to define different keys for different synchronizing appenders. In this way, Log::Log4perl serializes access to each appender instance separately: log4perl.category = WARN, Syncer1, Syncer2 # File appender 1 (unsynchronized) log4perl.appender.Logfile1 = Log::Log4perl::Appender::File log4perl.appender.Logfile1.filename = test1.log log4perl.appender.Logfile1.layout = SimpleLayout # File appender 2 (unsynchronized) log4perl.appender.Logfile2 = Log::Log4perl::Appender::File log4perl.appender.Logfile2.filename = test2.log log4perl.appender.Logfile2.layout = SimpleLayout # Synchronizing appender, using the file appender above log4perl.appender.Syncer1 = Log::Log4perl::Appender::Synchronized log4perl.appender.Syncer1.appender = Logfile1 log4perl.appender.Syncer1.key = l4p1 # Synchronizing appender, using the file appender above log4perl.appender.Syncer2 = Log::Log4perl::Appender::Synchronized log4perl.appender.Syncer2.appender = Logfile2 log4perl.appender.Syncer2.key = l4p2 Without the C<.key = l4p1> and C<.key = l4p2> lines, both Synchronized appenders would be using the default C<_l4p> key, causing unnecessary serialization of output written to different files. =head2 Advanced configuration To configure the underlying IPC::Shareable module, use the I property and hand it a reference to a hash of options. By default, the setting is equal to { create=>1, destroy=>1 } which has the appender create the semaphore at startup and remove it at shutdown. In order to add another option, e.g. a setting of C<0775> for IPC::Shareable's C parameter, add log4perl.appender.Syncer1.options = \ sub { { create=>1, destroy=>1, mode=>0775 } } to the Log4perl configuration file. =head1 DEVELOPMENT NOTES C is a I appender. Unlike other appenders, it doesn't log any messages, it just passes them on to its attached sub-appender. For this reason, it doesn't need a layout (contrary to regular appenders). If it defines none, messages are passed on unaltered. Custom filters are also applied to the composite appender only. They are I applied to the sub-appender. Same applies to appender thresholds. This behaviour might change in the future. =head1 LEGALESE Copyright 2003 by Mike Schilli, all rights reserved. This program is free software, you can redistribute it and/or modify it under the same terms as Perl itself. =head1 AUTHOR 2003, Mike Schilli