File Coverage

blib/lib/Log/Dispatch/FileShared.pm
Criterion Covered Total %
statement 79 139 56.8
branch 15 56 26.7
condition 3 12 25.0
subroutine 14 16 87.5
pod 2 2 100.0
total 113 225 50.2


line stmt bran cond sub pod time code
1             package Log::Dispatch::FileShared;
2             # $Id: FileShared.pm,v 1.2 2007/02/03 18:24:11 cmanley Exp $
3 1     1   19659 use strict;
  1         3  
  1         43  
4 1     1   7 use Carp;
  1         2  
  1         76  
5 1     1   18 use Fcntl qw(:DEFAULT :flock);
  1         3  
  1         593  
6 1     1   8 use Params::Validate qw(validate_with SCALAR BOOLEAN); Params::Validate::validation_options('allow_extra' => 1);
  1         1  
  1         83  
7 1     1   6 use Scalar::Util ();
  1         2  
  1         17  
8 1     1   1007 use Time::HiRes ();
  1         2095  
  1         34  
9 1     1   7 use base qw( Log::Dispatch::Output );
  1         3  
  1         958  
10             our $VERSION = sprintf '%d.%02d', q|$Revision: 1.2 $| =~ m/ (\d+) \. (\d+) /xg;
11              
12              
13              
14             our $MOD_PERL;
15             unless(defined($MOD_PERL)) {
16             $MOD_PERL = 0; # default == no mod_perl
17             if (exists($ENV{MOD_PERL})) {
18             # mod_perl handlers may run system() on other scripts, so also check %INC.
19             if (exists($ENV{MOD_PERL_API_VERSION}) && ($ENV{MOD_PERL_API_VERSION} == 2) && $INC{'Apache2/RequestRec.pm'}) {
20             $MOD_PERL = 2;
21             } elsif ($INC{'Apache.pm'}) {
22             $MOD_PERL = 1;
23             }
24             }
25             }
26              
27              
28              
29              
30             sub new {
31 1     1 1 709 my $proto = shift;
32 1         5 my %p = @_;
33 1   33     9 my $class = ref($proto) || $proto;
34 1         4 my $self = bless({}, $class);
35 1         9 $self->_basic_init(%p);
36 1         123 $self->_init(%p);
37 1         6 return $self;
38             }
39              
40              
41              
42             sub DESTROY {
43 1     1   3 my $self = shift;
44 1         3 $self->_close_handle();
45             }
46              
47              
48              
49             sub _init {
50 1     1   3 my $self = shift;
51 1         61 my %p = validate_with(
52             'params' => \@_,
53             'spec' => {
54             'filename' => { 'type' => SCALAR },
55             'mode' => { 'type' => SCALAR, 'default' => '>>', 'regex' => qr/^>{1,2}$/ },
56             'perms' => { 'type' => SCALAR, 'default' => 0666 },
57             'umask' => { 'type' => SCALAR, 'optional' => 1 },
58             'flock' => { 'type' => BOOLEAN, 'default' => 1 },
59             'autoflush' => { 'type' => BOOLEAN, 'default' => 1 },
60             'close_after_write' => { 'type' => BOOLEAN, 'default' => 0 },
61             'close_after_modperl_request' => { 'type' => BOOLEAN, 'default' => 0 },
62             },
63             'allow_extra' => 1,
64             );
65 1         14 $self->{'filename'} = $p{'filename'};
66 1         3 $self->{'perms'} = $p{'perms'} | 0200; # Make sure that at least this process can write to the file.
67 1         3 $self->{'umask'} = $p{'umask'};
68 1         1 $self->{'flock'} = $p{'flock'};
69 1         2 $self->{'autoflush'} = $p{'autoflush'};
70 1         2 $self->{'close_after_write'} = $p{'close_after_write'};
71 1 50       4 if ($self->{'close_after_write'}) {
72 0         0 $self->{'mode'} = '>>';
73             }
74             else {
75 1         2 $self->{'mode'} = $p{'mode'};
76             }
77 1 50       5 if ($p{'close_after_modperl_request'}) {
78             {
79 0 0       0 if ($self->{'close_after_write'}) {
  0         0  
80 0         0 local $Carp::CarpLevel = $Carp::CarpLevel + 1;
81 0         0 carp("Option 'close_after_modperl_request' ignored because 'close_after_write' is true.");
82 0         0 last;
83             }
84 0 0       0 unless($MOD_PERL) {
85 0         0 local $Carp::CarpLevel = $Carp::CarpLevel + 1;
86 0         0 carp("Option 'close_after_modperl_request' ignored because mod_perl was not detected.");
87 0         0 last;
88             }
89 0 0       0 if ($MOD_PERL == 2) {
    0          
90             # Check that the request object can be fetched. Requires 'SetHandler perl-script' or 'PerlOptions +GlobalRequest'
91 0         0 eval {
92 0         0 require Apache2::RequestUtil;
93 0         0 Apache2::RequestUtil->request();
94             };
95 0 0       0 if ($@) {
96 0         0 local $Carp::CarpLevel = $Carp::CarpLevel + 1;
97 0         0 croak("Can't use option 'close_after_modperl_request' (requires 'SetHandler perl-script' or 'PerlOptions +GlobalRequest'): $@");
98             }
99             }
100             elsif ($MOD_PERL > 2) {
101 0         0 die("Fix me because I don't support mod_perl $MOD_PERL yet.");
102             }
103              
104             # This is a boolean switch. This is used to check if the handler as already been pushed.
105             # This technique is used instead of get_handlers() because the latter segfaults with anonymous subs (in mod_perl 2.02)
106 0         0 $self->{'modperl_cleanup_handler_pushed'} = 0;
107              
108             # Create cleanup code ref.
109             # Use a weak self reference so that a circular reference memory leak isn't caused.
110             # See also http://www.perl.com/pub/a/2002/08/07/proxyobject.html?page=2
111 0         0 my $weakself = $self;
112 0         0 Scalar::Util::weaken($weakself);
113             $self->{'modperl_cleanup_handler'} = sub {
114 0 0   0   0 if (defined($weakself)) { # Will be undef if $self was garbage collected, which is ok.
115 0         0 $weakself->_close_handle();
116 0         0 $weakself->{'modperl_cleanup_handler_pushed'} = 0;
117             }
118             }
119 0         0 }
120             }
121             }
122              
123              
124              
125              
126             sub log_message {
127 2     2 1 206 my $self = shift;
128 2         5 my %p = @_;
129 2         5 my $h = $self->_get_handle();
130 2         19 my $use_flock = $self->{'flock'};
131 2 50       5 if ($use_flock) {
132 2 50       5 unless($self->_lock_handle($h)) {
133             # Oops failed to aquire lock.
134             # If it was important, then at least it will appear in STDERR.
135 0         0 warn($p{'message'});
136 0         0 return;
137             }
138             }
139 2         79 print $h $p{'message'};
140 2 50       8 if ($self->{'close_after_write'}) {
    50          
141 0         0 $self->_close_handle(); # automatically unlocks too
142             }
143             elsif($use_flock) {
144 2         20 flock($h, LOCK_UN); # automatically flushes too.
145             }
146             }
147              
148              
149              
150              
151             sub _get_handle {
152 2     2   1 my $self = shift;
153 2         3 my $h = $self->{'h'};
154 2 100       5 unless($h) {
155 1         2 my $filename = $self->{'filename'};
156 1         1 my $new_umask = $self->{'umask'};
157 1         2 my $old_umask;
158 1 50       4 if (defined($new_umask)) {
159 0         0 $old_umask = umask($new_umask);
160             }
161 1         1 my $mode = O_WRONLY | O_CREAT;
162 1 50       3 if ($self->{'mode'} eq '>>') {
163 1         2 $mode |= O_APPEND;
164             }
165 1         183 my $rc = sysopen($h, $filename, $mode, $self->{'perms'});
166 1 50       6 if (defined($old_umask)) {
167 0         0 umask($old_umask);
168             }
169 1 50       4 unless($rc) {
170 0         0 die(sprintf('Failed to open("%s%s"): %s', $self->{'mode'}, $filename, $!));
171             }
172 1 50       5 if ($self->{'autoflush'}) {
173 1         4 my $oldh = select($h); $| = 1; select($oldh);
  1         3  
  1         4  
174             }
175 1 0 33     5 if ($MOD_PERL && (my $cleanup_handler = $self->{'modperl_cleanup_handler'}) && !$self->{'modperl_cleanup_handler_pushed'}) {
      33        
176 0 0       0 if ($MOD_PERL == 2) {
    0          
177             # Requires 'SetHandler perl-script' or 'PerlOptions +GlobalRequest'
178 0         0 Apache2::RequestUtil->request()->push_handlers('PerlCleanupHandler' => $cleanup_handler);
179             }
180             elsif ($MOD_PERL == 1) {
181 0         0 Apache->request()->register_cleanup($cleanup_handler);
182             }
183             else {
184 0         0 die("Fix me because I don't support mod_perl $MOD_PERL yet.");
185             }
186 0         0 $self->{'modperl_cleanup_handler_pushed'} = 1;
187             }
188 1         3 $self->{'h'} = $h;
189             }
190 2         5 return $h;
191             }
192              
193              
194              
195             sub _close_handle {
196 1     1   1 my $self = shift;
197 1 50       3 if (my $h = $self->{'h'}) {
198 1         15 close($h);
199 1         7 undef($self->{'h'});
200             }
201             }
202              
203              
204              
205             sub _lock_handle {
206 2     2   3 my $self = shift;
207 2         1 my $h = shift;
208             # First try to get a non-blocking lock.
209             # Only if that fails, try a blocking lock in an eval which is slower.
210 2 50       19 unless(flock($h, LOCK_EX | LOCK_NB)) {
211             {
212 0 0 0     0 if ($SIG{ALRM} && ($SIG{ALRM} ne 'DEFAULT')) {
  0         0  
213             # This is a dilemma.
214             # Not locking could cause the log file to be corrupted.
215             # The caller has probably called alarm(), and calling it here could disrupt the caller's alarm() call.
216             # First try a short loop with 1ms sleeps to get a non-blocking loop.
217             # If that doesn't work, then try a blocking lock with a timeout.
218 0         0 my $locked = 0;
219 0         0 for (my $i=0; $i<5; $i++) {
220 0         0 Time::HiRes::usleep(1000);
221 0 0       0 if (flock($h, LOCK_EX | LOCK_NB)) {
222             # Yippie! It worked.
223 0         0 $locked = 1;
224 0         0 last;
225             }
226             }
227 0 0       0 if ($locked) {
228 0         0 last;
229             }
230 0         0 warn("Setting local \$SIG{ALRM} even though it has been set already.");
231             }
232 0         0 eval {
233 0     0   0 local $SIG{ALRM} = sub { die(__PACKAGE__ . ".ALRM\n"); };
  0         0  
234             # Wait practically long enough, between 1 and 2 seconds.
235             # Any shorter can cause a premature timeout.
236             # Any longer can help cause a DoS if too may processes have to wait too long.
237 0         0 alarm 2;
238 0         0 flock($h, LOCK_EX);
239 0         0 alarm 0;
240             };
241 0         0 alarm 0;
242 0 0       0 if ($@) {
243 0         0 close($h);
244 0 0       0 if ($@ eq __PACKAGE__ . ".ALRM\n") {
245             # This is a dilemma too.
246 0         0 warn(sprintf("Timeout waiting for lock on '%s'.", $self->{'filename'}));
247 0         0 return 0;
248             }
249             else {
250 0         0 die($@);
251             }
252             }
253             }
254             }
255             # Just in case there was an append while we waited for the lock.
256 2         8 seek($h,0,2);
257 2         6 return 1;
258             }
259              
260              
261              
262             1;
263              
264             __END__