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   8375 use strict;
  8         20  
  8         403  
15             require Log::Agent::Driver;
16            
17             ########################################################################
18             package Log::Agent::Driver::File;
19            
20 8     8   47 use vars qw(@ISA);
  8         15  
  8         14191  
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 16170 my $self = bless {}, shift;
51 21         100 my (%args) = @_;
52 21         61 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         230 -rotate => \$self->{'rotate'},
67             );
68            
69 21         116 while (my ($arg, $val) = each %args) {
70 52         116 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         185 $$vset = $val;
76             }
77            
78             #
79             # If -file was used, it supersedes -duperr and -channels
80             #
81            
82 21 100 66     85 if (defined $file && length $file) {
83 6         20 $self->{'channels'} = {
84             'debug' => $file,
85             'output' => $file,
86             'error' => $file,
87             };
88 6         13 $self->{'duperr'} = 0;
89             }
90            
91             #
92             # and we do something similar for file permissions
93             #
94            
95 21 100 66     73 if (defined $perm && length $perm) {
96             $self->{chanperm} = {
97 6         17 debug => $perm,
98             output => $perm,
99             error => $perm
100             };
101             }
102            
103 21         132 $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       52 $self->{chanperm} = {} unless $self->chanperm; # No defined perms
107 21         44 $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       55 my $use_rotate = defined($self->rotate) ? 1 : 0;
116 21 50       56 unless ($use_rotate) {
117 21         36 foreach my $chan (keys %{$self->channels}) {
  21         51  
118 47 50       81 $use_rotate = 1 if ref $self->channels->{$chan} eq 'ARRAY';
119 47 50       111 last if $use_rotate;
120             }
121             }
122            
123 21 50       51 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         113 return $self;
135             }
136            
137             #
138             # Attribute access
139             #
140            
141 35     35 0 113 sub duperr { $_[0]->{duperr} }
142 128     128 0 342 sub channels { $_[0]->{channels} }
143 66     66 0 184 sub chanperm { $_[0]->{chanperm} }
144 119     119 0 357 sub channel_obj { $_[0]->{channel_obj} }
145 35     35 0 93 sub stampfmt { $_[0]->{stampfmt} }
146 35     35 0 111 sub showpid { $_[0]->{showpid} }
147 33     33 0 166 sub magic_open { $_[0]->{magic_open} }
148 56     56 0 117 sub rotate { $_[0]->{rotate} }
149            
150             #
151             # ->prefix_msg -- defined
152             #
153             # NOP: channel handles prefixing for us.
154             #
155             sub prefix_msg {
156 65     65 1 94 my $self = shift;
157 65         192 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 71     71 1 105 my $self = shift;
203 71         140 my ($channel, $priority, $logstring) = @_;
204 71         162 my $chan = $self->channel($channel);
205 71 50       150 return unless $chan;
206            
207 71         197 $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 71     71 0 112 my $self = shift;
217 71         118 my ($name) = @_;
218 71         137 my $obj = $self->channel_obj->{$name};
219 71 100       223 $obj = $self->open_channel($name) unless $obj;
220 71         133 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         66 my ($name) = @_;
235 35         74 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         54 my $rotate; # A Log::Agent::Rotate object
243 35 50       87 if (ref $filename eq 'ARRAY') {
244 0         0 ($filename, $rotate) = @$filename;
245             } else {
246 35         73 $rotate = $self->rotate;
247             }
248            
249 35         128 my @common_args = (
250             -prefix => $self->prefix,
251             -stampfmt => $self->stampfmt,
252             -showpid => $self->showpid,
253             );
254 35         62 my @other_args;
255             my $type;
256            
257             #
258             # No channel defined, use 'error', or revert to STDERR
259             #
260            
261 35 100 66     172 unless (defined $filename && length $filename) {
262 4         11 $filename = $self->channels->{'error'};
263 4 50       13 ($filename, $rotate) = @$filename if ref $filename eq 'ARRAY';
264             }
265            
266 35 100 66     163 unless (defined $filename && length $filename) {
267 2         613 require Log::Agent::Channel::Handle;
268 2         14 select((select(main::STDERR), $| = 1)[0]);
269 2         4 $type = "Log::Agent::Channel::Handle";
270 2         6 @other_args = (-handle => \*main::STDERR);
271             } else {
272 33         4487 require Log::Agent::Channel::File;
273 33         65 $type = "Log::Agent::Channel::File";
274 33         110 @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       81 if $self->chanperm->{$name};
281 33 50       88 push(@other_args, -rotate => $rotate) if ref $rotate;
282             }
283            
284 35         192 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 10 my $self = shift;
295 6         15 my ($prio, $tag, $str) = @_;
296 6         22 my $cstr = $str->clone; # We're prepending tag on a copy
297 6         29 $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 0     0 1 0 my $self = shift;
313 0         0 my ($str) = @_;
314 0 0       0 $self->emit_output('critical', "FATAL", $str) if $self->duperr;
315 0         0 $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 5 my $self = shift;
347 2         3 my ($str) = @_;
348 2 100       5 $self->emit_output('critical', "FATAL", $str) if $self->duperr;
349 2         12 $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 26 my $self = shift;
360 12         36 my ($str) = @_;
361 12 100       35 $self->emit_output('error', "ERROR", $str) if $self->duperr;
362 12         68 $self->SUPER::logerr($str);
363             }
364            
365             #
366             # ->logwarn
367             #
368             # When `duperr' is true, emit message on the 'output' channel prefixed
369             # with WARNING.
370             #
371             sub logwarn {
372 4     4 1 7 my $self = shift;
373 4         11 my ($str) = @_;
374 4 100       26 $self->emit_output('warning', "WARNING", $str) if $self->duperr;
375 4         34 $self->SUPER::logwarn($str);
376             }
377            
378             #
379             # ->logxcarp
380             #
381             # When `duperr' is true, emit message on the 'output' channel prefixed
382             # with WARNING.
383             #
384             sub logxcarp {
385 13     13 1 22 my $self = shift;
386 13         26 my ($offset, $str) = @_;
387 13         63 my $msg = Log::Agent::Message->make(
388             $self->carpmess($offset, $str, \&Carp::shortmess)
389             );
390 13 50       34 $self->emit_output('warning', "WARNING", $msg) if $self->duperr;
391 13         48 $self->SUPER::logwarn($msg);
392             }
393            
394             #
395             # ->DESTROY
396             #
397             # Close all opened channels, so they may be removed from the common pool.
398             #
399             sub DESTROY {
400 13     13   28 my $self = shift;
401 13         29 my $channel_obj = $self->channel_obj;
402 13 50       35 return unless defined $channel_obj;
403 13         36 foreach my $chan (values %$channel_obj) {
404 21 50       81 $chan->close if defined $chan;
405             }
406             }
407            
408             1; # for require
409             __END__