File Coverage

blib/lib/Log/Agent/Driver/File.pm
Criterion Covered Total %
statement 101 132 76.5
branch 35 60 58.3
condition 8 15 53.3
subroutine 21 25 84.0
pod 10 22 45.4
total 175 254 68.9


line stmt bran cond sub pod time code
1             ###########################################################################
2             #
3             # File.pm
4             #
5             # Copyright (C) 1999 Raphael Manfredi.
6             # Copyright (C) 2002-2017 Mark Rogaski, mrogaski@cpan.org;
7             # all rights reserved.
8             #
9             # See the README file included with the
10             # distribution for license information.
11             #
12             ##########################################################################
13              
14 8     8   8412 use strict;
  8         19  
  8         382  
15             require Log::Agent::Driver;
16              
17             ########################################################################
18             package Log::Agent::Driver::File;
19              
20 8     8   43 use vars qw(@ISA);
  8         18  
  8         14689  
21              
22             @ISA = qw(Log::Agent::Driver);
23              
24             #
25             # ->make -- defined
26             #
27             # Creation routine.
28             #
29             # Attributes (and switches that set them):
30             #
31             # prefix the application name
32             # duperr whether to duplicate "error" channels to "output"
33             # stampfmt stamping format ("syslog", "date", "own", "none") or closure
34             # showpid whether to show pid after prefix in []
35             # channels where each channel ("error", "output", "debug") goes
36             # chanperm what permissions each channel ("error", "output", "debug") has
37             # magic_open flag to tell whether ">>file" or "|proc" are allowed filenames
38             # rotate default rotating policy for logfiles
39             #
40             # Additional switches:
41             #
42             # file sole channel, implies -duperr = 0 and supersedes -channels
43             # perm file permissions that supersedes all channel permissions
44             #
45             # Other attributes:
46             #
47             # channel_obj opened channel objects
48             #
49             sub make {
50 21     21 1 17897 my $self = bless {}, shift;
51 21         95 my (%args) = @_;
52 21         67 my $prefix;
53             my $file;
54 21         0 my $perm;
55              
56             my %set = (
57             -prefix => \$prefix, # Handled by parent via _init
58             -duperr => \$self->{'duperr'},
59             -channels => \$self->{'channels'},
60             -chanperm => \$self->{'chanperm'},
61             -stampfmt => \$self->{'stampfmt'},
62             -showpid => \$self->{'showpid'},
63             -magic_open => \$self->{'magic_open'},
64             -file => \$file,
65             -perm => \$perm,
66 21         209 -rotate => \$self->{'rotate'},
67             );
68              
69 21         109 while (my ($arg, $val) = each %args) {
70 52         109 my $vset = $set{lc($arg)};
71 52 50       115 unless (ref $vset) {
72 0         0 require Carp;
73 0         0 Carp::croak("Unknown switch $arg");
74             }
75 52         173 $$vset = $val;
76             }
77              
78             #
79             # If -file was used, it supersedes -duperr and -channels
80             #
81              
82 21 100 66     74 if (defined $file && length $file) {
83 6         22 $self->{'channels'} = {
84             'debug' => $file,
85             'output' => $file,
86             'error' => $file,
87             };
88 6         10 $self->{'duperr'} = 0;
89             }
90              
91             #
92             # and we do something similar for file permissions
93             #
94              
95 21 100 66     67 if (defined $perm && length $perm) {
96             $self->{chanperm} = {
97 6         18 debug => $perm,
98             output => $perm,
99             error => $perm
100             };
101             }
102              
103 21         93 $self->_init($prefix, 0); # 1 is the skip Carp penalty for confess
104              
105 21 100       49 $self->{channels} = {} unless $self->channels; # No defined channels
106 21 100       45 $self->{chanperm} = {} unless $self->chanperm; # No defined perms
107 21         46 $self->{channel_obj} = {}; # No opened files
108              
109             #
110             # Check for logfile rotation, which can be specified on a global or
111             # file by file basis. Since Log::Agent::Rotate is a separate extension,
112             # it may not be installed.
113             #
114              
115 21 50       48 my $use_rotate = defined($self->rotate) ? 1 : 0;
116 21 50       56 unless ($use_rotate) {
117 21         31 foreach my $chan (keys %{$self->channels}) {
  21         43  
118 47 50       84 $use_rotate = 1 if ref $self->channels->{$chan} eq 'ARRAY';
119 47 50       106 last if $use_rotate;
120             }
121             }
122              
123 21 50       52 if ($use_rotate) {
124 0         0 eval {
125 0         0 require Log::Agent::File::Rotate;
126             };
127 0 0       0 if ($@) {
128 0         0 warn $@;
129 0         0 require Carp;
130 0         0 Carp::croak("Must install Log::Agent::Rotate to use rotation");
131             }
132             }
133              
134 21         116 return $self;
135             }
136              
137             #
138             # Attribute access
139             #
140              
141 37     37 0 117 sub duperr { $_[0]->{duperr} }
142 128     128 0 337 sub channels { $_[0]->{channels} }
143 66     66 0 199 sub chanperm { $_[0]->{chanperm} }
144 125     125 0 331 sub channel_obj { $_[0]->{channel_obj} }
145 35     35 0 78 sub stampfmt { $_[0]->{stampfmt} }
146 35     35 0 95 sub showpid { $_[0]->{showpid} }
147 33     33 0 162 sub magic_open { $_[0]->{magic_open} }
148 56     56 0 111 sub rotate { $_[0]->{rotate} }
149              
150             #
151             # ->prefix_msg -- defined
152             #
153             # NOP: channel handles prefixing for us.
154             #
155             sub prefix_msg {
156 70     70 1 110 my $self = shift;
157 70         188 return $_[0];
158             }
159              
160             #
161             # ->chanfn
162             #
163             # Return channel file name.
164             #
165             sub chanfn {
166 0     0 0 0 my $self = shift;
167 0         0 my ($channel) = @_;
168 0         0 my $filename = $self->channels->{$channel};
169 0 0       0 if (ref $filename eq 'ARRAY') {
170 0         0 $filename = $filename->[0];
171             }
172             # No channel defined, use 'error'
173 0 0 0     0 $filename = $self->channels->{'error'} unless
174             defined $filename && length $filename;
175 0 0       0 $filename = '' unless defined $filename;
176              
177 0         0 return $filename;
178             }
179              
180             #
181             # ->channel_eq -- defined
182             #
183             # Compare two channels.
184             #
185             # It's hard to know for certain that two channels are equivalent, so we
186             # compare filenames. This is not correct, of course, but it will do for
187             # what we're trying to achieve here, namely avoid duplicates if possible
188             # when traces are remapped to Carp::Datum.
189             #
190             sub channel_eq {
191 0     0 1 0 my $self = shift;
192 0         0 my ($chan1, $chan2) = @_;
193 0         0 my $fn1 = $self->chanfn($chan1);
194 0         0 my $fn2 = $self->chanfn($chan2);
195 0         0 return $fn1 eq $fn2;
196             }
197              
198             #
199             # ->write -- defined
200             #
201             sub write {
202 77     77 1 103 my $self = shift;
203 77         139 my ($channel, $priority, $logstring) = @_;
204 77         480 my $chan = $self->channel($channel);
205 77 50       172 return unless $chan;
206              
207 77         205 $chan->write($priority, $logstring);
208             }
209              
210             #
211             # ->channel
212             #
213             # Return channel object (one of the Log::Agent::Channel::* objects)
214             #
215             sub channel {
216 77     77 0 114 my $self = shift;
217 77         128 my ($name) = @_;
218 77         159 my $obj = $self->channel_obj->{$name};
219 77 100       208 $obj = $self->open_channel($name) unless $obj;
220 77         143 return $obj;
221             }
222              
223              
224             #
225             # ->open_channel
226             #
227             # Open given channel according to the configured channel description and
228             # return the object file descriptor.
229             #
230             # If no channel of that name was defined, use 'error' or STDERR.
231             #
232             sub open_channel {
233 35     35 0 58 my $self = shift;
234 35         82 my ($name) = @_;
235 35         83 my $filename = $self->channels->{$name};
236              
237             #
238             # Handle possible logfile rotation, which may be defined globally
239             # or on a file by file basis.
240             #
241              
242 35         53 my $rotate; # A Log::Agent::Rotate object
243 35 50       93 if (ref $filename eq 'ARRAY') {
244 0         0 ($filename, $rotate) = @$filename;
245             } else {
246 35         76 $rotate = $self->rotate;
247             }
248              
249 35         117 my @common_args = (
250             -prefix => $self->prefix,
251             -stampfmt => $self->stampfmt,
252             -showpid => $self->showpid,
253             );
254 35         76 my @other_args;
255             my $type;
256              
257             #
258             # No channel defined, use 'error', or revert to STDERR
259             #
260              
261 35 100 66     162 unless (defined $filename && length $filename) {
262 4         8 $filename = $self->channels->{'error'};
263 4 50       11 ($filename, $rotate) = @$filename if ref $filename eq 'ARRAY';
264             }
265              
266 35 100 66     136 unless (defined $filename && length $filename) {
267 2         598 require Log::Agent::Channel::Handle;
268 2         11 select((select(main::STDERR), $| = 1)[0]);
269 2         4 $type = "Log::Agent::Channel::Handle";
270 2         5 @other_args = (-handle => \*main::STDERR);
271             } else {
272 33         4471 require Log::Agent::Channel::File;
273 33         68 $type = "Log::Agent::Channel::File";
274 33         83 @other_args = (
275             -filename => $filename,
276             -magic_open => $self->magic_open,
277             -share => 1,
278             );
279             push(@other_args, -fileperm => $self->chanperm->{$name})
280 33 100       69 if $self->chanperm->{$name};
281 33 50       84 push(@other_args, -rotate => $rotate) if ref $rotate;
282             }
283              
284 35         175 return $self->channel_obj->{$name} =
285             $type->make(@common_args, @other_args);
286             }
287              
288             #
289             # ->emit_output
290             #
291             # Force error message to the regular 'output' channel with a specified tag.
292             #
293             sub emit_output {
294 6     6 0 9 my $self = shift;
295 6         13 my ($prio, $tag, $str) = @_;
296 6         27 my $cstr = $str->clone; # We're prepending tag on a copy
297 6         25 $cstr->prepend("$tag: ");
298 6         15 $self->write('output', $prio, $cstr);
299             }
300              
301             ###
302             ### Redefined routines to handle duperr
303             ###
304              
305             #
306             # ->logconfess
307             #
308             # When `duperr' is true, emit message on the 'output' channel prefixed
309             # with FATAL.
310             #
311             sub logconfess {
312             my $self = shift;
313             my ($str) = @_;
314             $self->emit_output('critical', "FATAL", $str) if $self->duperr;
315             $self->SUPER::logconfess($str); # Carp strips calls within hierarchy
316             }
317              
318             #
319             # ->logxcroak
320             #
321             # When `duperr' is true, emit message on the 'output' channel prefixed
322             # with FATAL.
323             #
324             sub logxcroak {
325 0     0 1 0 my $self = shift;
326 0         0 my ($offset, $str) = @_;
327 0         0 my $msg = Log::Agent::Message->make(
328             $self->carpmess($offset, $str, \&Carp::shortmess)
329             );
330 0 0       0 $self->emit_output('critical', "FATAL", $msg) if $self->duperr;
331              
332             #
333             # Carp strips calls within hierarchy, so that new call should not show,
334             # there's no need to adjust the frame offset.
335             #
336 0         0 $self->SUPER::logdie($msg);
337             }
338              
339             #
340             # ->logdie
341             #
342             # When `duperr' is true, emit message on the 'output' channel prefixed
343             # with FATAL.
344             #
345             sub logdie {
346 2     2 1 4 my $self = shift;
347 2         3 my ($str) = @_;
348 2 100       6 $self->emit_output('critical', "FATAL", $str) if $self->duperr;
349 2         11 $self->SUPER::logdie($str);
350             }
351              
352             #
353             # ->logerr
354             #
355             # When `duperr' is true, emit message on the 'output' channel prefixed
356             # with ERROR.
357             #
358             sub logerr {
359 12     12 1 20 my $self = shift;
360 12         23 my ($str) = @_;
361 12 100       42 $self->emit_output('error', "ERROR", $str) if $self->duperr;
362 12         64 $self->SUPER::logerr($str);
363             }
364              
365             #
366             # ->logcluck
367             #
368             # When `duperr' is true, emit message on the 'output' channel prefixed
369             # with WARNING.
370             #
371             sub logconfess {
372 0     0 1 0 my $self = shift;
373 0         0 my ($str) = @_;
374 0 0       0 $self->emit_output('warning', "WARNING", $str) if $self->duperr;
375 0         0 $self->SUPER::logcluck($str); # Carp strips calls within hierarchy
376             }
377              
378             #
379             # ->logwarn
380             #
381             # When `duperr' is true, emit message on the 'output' channel prefixed
382             # with WARNING.
383             #
384             sub logwarn {
385 4     4 1 8 my $self = shift;
386 4         11 my ($str) = @_;
387 4 100       12 $self->emit_output('warning', "WARNING", $str) if $self->duperr;
388 4         33 $self->SUPER::logwarn($str);
389             }
390              
391             #
392             # ->logxcarp
393             #
394             # When `duperr' is true, emit message on the 'output' channel prefixed
395             # with WARNING.
396             #
397             sub logxcarp {
398 13     13 1 21 my $self = shift;
399 13         27 my ($offset, $str) = @_;
400 13         65 my $msg = Log::Agent::Message->make(
401             $self->carpmess($offset, $str, \&Carp::shortmess)
402             );
403 13 50       34 $self->emit_output('warning', "WARNING", $msg) if $self->duperr;
404 13         46 $self->SUPER::logwarn($msg);
405             }
406              
407             #
408             # ->DESTROY
409             #
410             # Close all opened channels, so they may be removed from the common pool.
411             #
412             sub DESTROY {
413 13     13   24 my $self = shift;
414 13         26 my $channel_obj = $self->channel_obj;
415 13 50       36 return unless defined $channel_obj;
416 13         36 foreach my $chan (values %$channel_obj) {
417 21 50       76 $chan->close if defined $chan;
418             }
419             }
420              
421             1; # for require
422             __END__