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
14use strict;
15require Log::Agent::Driver;
16
17########################################################################
18package Log::Agent::Driver::File;
19
20use vars qw(@ISA);
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#
49sub make {
50    my $self = bless {}, shift;
51    my (%args) = @_;
52    my $prefix;
53    my $file;
54    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        -rotate     => \$self->{'rotate'},
67    );
68
69    while (my ($arg, $val) = each %args) {
70        my $vset = $set{lc($arg)};
71        unless (ref $vset) {
72            require Carp;
73            Carp::croak("Unknown switch $arg");
74        }
75        $$vset = $val;
76    }
77
78    #
79    # If -file was used, it supersedes -duperr and -channels
80    #
81
82    if (defined $file && length $file) {
83        $self->{'channels'} = {
84            'debug'  => $file,
85            'output' => $file,
86            'error'  => $file,
87        };
88        $self->{'duperr'} = 0;
89    }
90
91    #
92    # and we do something similar for file permissions
93    #
94
95    if (defined $perm && length $perm) {
96        $self->{chanperm} = {
97            debug  => $perm,
98            output => $perm,
99            error  => $perm
100        };
101    }
102
103    $self->_init($prefix, 0);  # 1 is the skip Carp penalty for confess
104
105    $self->{channels}    = {} unless $self->channels;  # No defined channels
106    $self->{chanperm}    = {} unless $self->chanperm;  # No defined perms
107    $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    my $use_rotate = defined($self->rotate) ? 1 : 0;
116    unless ($use_rotate) {
117        foreach my $chan (keys %{$self->channels}) {
118            $use_rotate = 1 if ref $self->channels->{$chan} eq 'ARRAY';
119            last if $use_rotate;
120        }
121    }
122
123    if ($use_rotate) {
124        eval {
125            require Log::Agent::File::Rotate;
126        };
127        if ($@) {
128            warn $@;
129            require Carp;
130            Carp::croak("Must install Log::Agent::Rotate to use rotation");
131        }
132    }
133
134    return $self;
135}
136
137#
138# Attribute access
139#
140
141sub duperr      { $_[0]->{duperr}      }
142sub channels    { $_[0]->{channels}    }
143sub chanperm    { $_[0]->{chanperm}    }
144sub channel_obj { $_[0]->{channel_obj} }
145sub stampfmt    { $_[0]->{stampfmt}    }
146sub showpid     { $_[0]->{showpid}     }
147sub magic_open  { $_[0]->{magic_open}  }
148sub rotate      { $_[0]->{rotate}      }
149
150#
151# ->prefix_msg  -- defined
152#
153# NOP: channel handles prefixing for us.
154#
155sub prefix_msg {
156    my $self = shift;
157    return $_[0];
158}
159
160#
161# ->chanfn
162#
163# Return channel file name.
164#
165sub chanfn {
166    my $self = shift;
167    my ($channel) = @_;
168    my $filename = $self->channels->{$channel};
169    if (ref $filename eq 'ARRAY') {
170        $filename = $filename->[0];
171    }
172    # No channel defined, use 'error'
173    $filename = $self->channels->{'error'} unless
174            defined $filename && length $filename;
175    $filename = '<STDERR>' unless defined $filename;
176
177    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#
190sub channel_eq {
191    my $self = shift;
192    my ($chan1, $chan2) = @_;
193    my $fn1 = $self->chanfn($chan1);
194    my $fn2 = $self->chanfn($chan2);
195    return $fn1 eq $fn2;
196}
197
198#
199# ->write       -- defined
200#
201sub write {
202    my $self = shift;
203    my ($channel, $priority, $logstring) = @_;
204    my $chan = $self->channel($channel);
205    return unless $chan;
206
207    $chan->write($priority, $logstring);
208}
209
210#
211# ->channel
212#
213# Return channel object (one of the Log::Agent::Channel::* objects)
214#
215sub channel {
216    my $self = shift;
217    my ($name) = @_;
218    my $obj = $self->channel_obj->{$name};
219    $obj = $self->open_channel($name) unless $obj;
220    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#
232sub open_channel {
233    my $self = shift;
234    my ($name) = @_;
235    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    my $rotate;        # A Log::Agent::Rotate object
243    if (ref $filename eq 'ARRAY') {
244        ($filename, $rotate) = @$filename;
245    } else {
246        $rotate = $self->rotate;
247    }
248
249    my @common_args = (
250        -prefix   => $self->prefix,
251        -stampfmt => $self->stampfmt,
252        -showpid  => $self->showpid,
253    );
254    my @other_args;
255    my $type;
256
257    #
258    # No channel defined, use 'error', or revert to STDERR
259    #
260
261    unless (defined $filename && length $filename) {
262        $filename = $self->channels->{'error'};
263        ($filename, $rotate) = @$filename if ref $filename eq 'ARRAY';
264    }
265
266    unless (defined $filename && length $filename) {
267        require Log::Agent::Channel::Handle;
268        select((select(main::STDERR), $| = 1)[0]);
269        $type = "Log::Agent::Channel::Handle";
270        @other_args = (-handle => \*main::STDERR);
271    } else {
272        require Log::Agent::Channel::File;
273        $type = "Log::Agent::Channel::File";
274        @other_args = (
275            -filename   => $filename,
276            -magic_open => $self->magic_open,
277            -share      => 1,
278        );
279        push(@other_args, -fileperm   => $self->chanperm->{$name})
280                if $self->chanperm->{$name};
281        push(@other_args, -rotate => $rotate) if ref $rotate;
282    }
283
284    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#
293sub emit_output {
294    my $self = shift;
295    my ($prio, $tag, $str) = @_;
296    my $cstr = $str->clone;       # We're prepending tag on a copy
297    $cstr->prepend("$tag: ");
298    $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#
311sub 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#
324sub logxcroak {
325    my $self = shift;
326    my ($offset, $str) = @_;
327    my $msg = Log::Agent::Message->make(
328        $self->carpmess($offset, $str, \&Carp::shortmess)
329    );
330    $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    $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#
345sub logdie {
346    my $self = shift;
347    my ($str) = @_;
348    $self->emit_output('critical', "FATAL", $str) if $self->duperr;
349    $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#
358sub logerr {
359    my $self = shift;
360    my ($str) = @_;
361    $self->emit_output('error', "ERROR", $str) if $self->duperr;
362    $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#
371sub logconfess {
372    my $self = shift;
373    my ($str) = @_;
374    $self->emit_output('warning', "WARNING", $str) if $self->duperr;
375    $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#
384sub logwarn {
385    my $self = shift;
386    my ($str) = @_;
387    $self->emit_output('warning', "WARNING", $str) if $self->duperr;
388    $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#
397sub logxcarp {
398    my $self = shift;
399    my ($offset, $str) = @_;
400    my $msg = Log::Agent::Message->make(
401        $self->carpmess($offset, $str, \&Carp::shortmess)
402    );
403    $self->emit_output('warning', "WARNING", $msg) if $self->duperr;
404    $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#
412sub DESTROY {
413    my $self = shift;
414    my $channel_obj = $self->channel_obj;
415    return unless defined $channel_obj;
416    foreach my $chan (values %$channel_obj) {
417        $chan->close if defined $chan;
418    }
419}
420
4211;        # for require
422__END__
423
424=head1 NAME
425
426Log::Agent::Driver::File - file logging driver for Log::Agent
427
428=head1 SYNOPSIS
429
430 use Log::Agent;
431 require Log::Agent::Driver::File;
432
433 my $driver = Log::Agent::Driver::File->make(
434     -prefix     => "prefix",
435     -duperr     => 1,
436     -stampfmt   => "own",
437     -showpid    => 1,
438     -magic_open => 0,
439     -channels   => {
440        error   => '/tmp/output.err',
441        output  => 'log.out',
442        debug   => '../appli.debug',
443     },
444     -chanperm   => {
445        error   => 0777,
446        output  => 0666,
447        debug   => 0644
448     }
449 );
450 logconfig(-driver => $driver);
451
452=head1 DESCRIPTION
453
454The file logging driver redirects logxxx() operations to specified files,
455one per channel usually (but channels may go to the same file).
456
457The creation routine make() takes the following arguments:
458
459=over 4
460
461=item C<-channels> => I<hash ref>
462
463Specifies where channels go. The supplied hash maps channel names
464(C<error>, C<output> and C<debug>) to filenames. When C<-magic_open> is
465set to true, filenames are allowed magic processing via perl's open(), so
466this allows things like:
467
468    -channels => {
469        'error'   => '>&FILE',
470        'output'  => '>newlog',   # recreate each time, don't append
471        'debug'  => '|mailx -s whatever user',
472    }
473
474If a channel (e.g. 'output') is not specified, it will go to the 'error'
475channel, and if that one is not specified either, it will go to STDERR instead.
476
477If you have installed the additional C<Log::Agent::Rotate> module, it is
478also possible to override any default rotating policy setup via the C<-rotate>
479argument: instead of supplying the channel as a single string, use an array
480reference where the first item is the channel file, and the second one is
481the C<Log::Agent::Rotate> configuration:
482
483    my $rotate = Log::Agent::Rotate->make(
484        -backlog     => 7,
485        -unzipped    => 2,
486        -max_write   => 100_000,
487        -is_alone    => 1,
488    );
489
490    my $driver = Log::Agent::Driver::File->make(
491        ...
492        -channels => {
493            'error'  => ['errors', $rotate],
494            'output' => ['output, $rotate],
495            'debug'  => ['>&FILE, $rotate],    # WRONG
496        },
497        -magic_open => 1,
498        ...
499    );
500
501In the above example, the rotation policy for the C<debug> channel will
502not be activated, since the channel is opened via a I<magic> method.
503See L<Log::Agent::Rotate> for more details.
504
505=item C<-chanperm> => I<hash ref>
506
507Specifies the file permissions for the channels specified by C<-channels>.
508The arguemtn is a hash ref, indexed by channel name, with numeric values.
509This option is only necessary to override the default permissions used by
510Log::Agent::Channel::File.  It is generally better to leave these
511permissive and rely on the user's umask.
512See L<perlfunc(3)/umask> for more details..
513
514=item C<-duperr> => I<flag>
515
516When true, all messages normally sent to the C<error> channel are also
517copied to the C<output> channel with a prefixing made to clearly mark
518them as such: "FATAL: " for logdie(), logcroak() and logconfess(),
519"ERROR: " for logerr() and "WARNING: " for logwarn().
520
521Note that the "duplicate" is the original error string for logconfess()
522and logcroak(), and is not strictly identical to the message that will be
523logged to the C<error> channel.  This is a an accidental feature.
524
525Default is false.
526
527=item C<-file> => I<file>
528
529This switch supersedes both C<-duperr> and C<-channels> by defining a
530single file for all the channels.
531
532=item C<-perm> => I<perm>
533
534This switch supersedes C<-chanperm> by defining consistent for all
535the channels.
536
537=item C<-magic_open> => I<flag>
538
539When true, channel filenames beginning with '>' or '|' are opened using
540Perl's open(). Otherwise, sysopen() is used, in append mode.
541
542Default is false.
543
544=item C<-prefix> => I<prefix>
545
546The application prefix string to prepend to messages.
547
548=item C<-rotate> => I<object>
549
550This sets a default logfile rotation policy.  You need to install the
551additional C<Log::Agent::Rotate> module to use this switch.
552
553I<object> is the C<Log::Agent::Rotate> instance describing the default
554policy for all the channels.  Only files which are not opened via a
555so-called I<magic open> can be rotated.
556
557=item C<-showpid> => I<flag>
558
559If set to true, the PID of the process will be appended within square
560brackets after the prefix, to all messages.
561
562Default is false.
563
564=item C<-stampfmt> => (I<name> | I<CODE>)
565
566Specifies the time stamp format to use. By default, my "own" format is used.
567The following formats are available:
568
569    date      "[Fri Oct 22 16:23:10 1999]"
570    none
571    own       "99/10/22 16:23:10"
572    syslog    "Oct 22 16:23:10".
573
574You may also specify a CODE ref: that routine will be called every time
575we need to compute a time stamp. It should not expect any parameter, and
576should return a string.
577
578=back
579
580=head1 CHANNELS
581
582All the channels go to the specified files. If a channel is not configured,
583it is redirected to 'error', or STDERR if no 'error' channel was configured
584either.
585
586Two channels not opened via a I<magic> open and whose logfile name is the
587same are effectively I<shared>, i.e. the same file descriptor is used for
588both of them. If you supply distinct rotation policies (e.g. by having a
589default policy, and supplying another policy to one of the channel only),
590then the final rotation policy will depend on which one was opened first.
591So don't do that.
592
593=head1 CAVEAT
594
595Beware of chdir().  If your program uses chdir(), you should always specify
596logfiles by using absolute paths, otherwise you run the risk of having
597your relative paths become invalid: there is no anchoring done at the time
598you specify them.  This is especially true when configured for rotation,
599since the logfiles are recreated as needed and you might end up with many
600logfiles scattered throughout all the directories you chdir()ed to.
601
602Logging channels with the same pathname are shared, i.e. they are only
603opened once by C<Log::Agent::Driver::File>.  Therefore, if you specify
604different rotation policy to such channels, the channel opening order will
605determine which of the policies will be used for all such shared channels.
606Such errors are flagged at runtime with the following message:
607
608 Rotation for 'logfile' may be wrong (shared with distinct policies)
609
610emitted in the logs upon subsequent sharing.
611
612=head1 AUTHORS
613
614Originally written by Raphael Manfredi E<lt>Raphael_Manfredi@pobox.comE<gt>,
615currently maintained by Mark Rogaski E<lt>mrogaski@cpan.orgE<gt>.
616
617Thanks to Joseph Pepin for suggesting the file permissions arguments
618to make().
619
620=head1 LICENSE
621
622Copyright (C) 1999 Raphael Manfredi.
623Copyright (C) 2002 Mark Rogaski; all rights reserved.
624
625See L<Log::Agent(3)> or the README file included with the distribution for
626license information.
627
628=head1 SEE ALSO
629
630Log::Agent::Driver(3), Log::Agent(3), Log::Agent::Rotate(3).
631
632=cut
633