File Coverage

blib/lib/Log/Agent/Channel/File.pm
Criterion Covered Total %
statement 81 98 82.6
branch 29 54 53.7
condition 6 17 35.2
subroutine 15 16 93.7
pod 3 11 27.2
total 134 196 68.3


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   66 use strict;
  8         16  
  8         466  
15             require Log::Agent::Channel;
16             require Log::Agent::Prefixer;
17              
18             ########################################################################
19             package Log::Agent::Channel::File;
20              
21 8     8   49 use vars qw(@ISA);
  8         15  
  8         480  
22              
23             @ISA = qw(Log::Agent::Channel Log::Agent::Prefixer);
24              
25 8     8   4176 use Symbol;
  8         6839  
  8         543  
26 8     8   57 use Fcntl;
  8         14  
  8         1918  
27 8     8   3326 use Log::Agent::Stamping;
  8         18  
  8         10115  
28              
29             #
30             # ->make -- defined
31             #
32             # Creation routine.
33             #
34             # Attributes (and switches that set them):
35             #
36             # prefix the application name
37             # stampfmt stamping format ("syslog", "date", "own", "none") or closure
38             # showpid whether to show pid after prefix in []
39             # filename file name to open (magical open needs -magic_open)
40             # fileperm permissions to open file with
41             # magic_open flag to tell whether ">>file" or "|proc" are allowed filenames
42             # rotate rotating policy for this file
43             # share true implies that non-magic filenames share the same fd object
44             # no_ucfirst don't capitalize first letter of message when no prefix
45             # no_prefixing don't prefix logs
46             # no_newline never append any newline character at the end of messages
47             #
48             # Other attributes:
49             #
50             # fd records Log::Agent::File::* objects
51             # crlf the new-line marker for this OS ("\n" on UNIX)
52             # warned records calls made to hardwired warn() to only do them once
53             #
54             sub make {
55 33     33 1 81 my $self = bless {}, shift;
56 33         189 my (%args) = @_;
57              
58             my %set = (
59             -prefix => \$self->{'prefix'},
60             -stampfmt => \$self->{'stampfmt'},
61             -showpid => \$self->{'showpid'},
62             -magic_open => \$self->{'magic_open'},
63             -filename => \$self->{'filename'},
64             -fileperm => \$self->{'fileperm'},
65             -rotate => \$self->{'rotate'},
66             -no_ucfirst => \$self->{'no_ucfirst'},
67             -no_prefixing => \$self->{'no_prefixing'},
68             -no_newline => \$self->{'no_newline'},
69 33         333 -share => \$self->{'share'},
70             );
71              
72 33         131 while (my ($arg, $val) = each %args) {
73 210         351 my $vset = $set{lc($arg)};
74 210 50       367 unless (ref $vset) {
75 0         0 require Carp;
76 0         0 Carp::croak("Unknown switch $arg");
77             }
78 210         549 $$vset = $val;
79             }
80              
81             #
82             # Initialize proper time-stamping routine.
83             #
84              
85 33 100       118 $self->{'stampfmt'} = stamping_fn($self->stampfmt)
86             unless ref $self->stampfmt eq 'CODE';
87              
88 33         65 $self->{'fd'} = undef;
89 33 50       194 $self->{'crlf'} = $^O =~ /^dos|win/i ? "\r\n" : "\n";
90 33         65 $self->{'warned'} = {};
91              
92 33 50       70 if ($self->rotate) {
93 0         0 eval {
94 0         0 require Log::Agent::File::Rotate;
95             };
96 0 0       0 if ($@) {
97 0         0 warn $@;
98 0         0 require Carp;
99 0         0 Carp::croak("Must install Log::Agent::Rotate to use rotation");
100             }
101             }
102              
103 33         176 return $self;
104             }
105              
106             #
107             # Attribute access
108             #
109              
110 2     2 0 9 sub magic_open { $_[0]->{'magic_open'} }
111 64     64 0 188 sub rotate { $_[0]->{'rotate'} }
112 52     52 0 114 sub filename { $_[0]->{'filename'} }
113 44     44 0 2393 sub fileperm { $_[0]->{'fileperm'} }
114 19     19 0 33 sub fd { $_[0]->{'fd'} }
115 81     81 0 286 sub share { $_[0]->{'share'} }
116 0     0 0 0 sub warned { $_[0]->{'warned'} }
117              
118             #
119             # ->write -- defined
120             #
121             # Write logstring to the file.
122             # Priority is ignored by this channel.
123             #
124             sub write {
125 75     75 1 110 my $self = shift;
126 75         137 my ($priority, $logstring) = @_;
127              
128             #
129             # This routine is called often...
130             # Bypass the attribute access routines.
131             #
132              
133 75         128 my $fd = $self->{fd};
134 75 100       174 $fd = $self->open unless $fd;
135 75 50       177 return unless ref $fd;
136              
137 75         122 my $prefix = '';
138             $prefix = $self->prefixing_string(\$logstring)
139 75 50       292 unless $self->{no_prefixing};
140              
141 75         147 my $crlf = '';
142 75 50       197 $crlf = $self->{crlf} unless $self->{no_newline};
143              
144             #
145             # The innocent-looking ->print statement below is NOT a polymorphic call.
146             #
147             # It can be targetted on various Log::Agent::File::* objects, which
148             # all happen to provide a print() feature with the same signature.
149             # However, those clases have no inheritance relationship because Perl
150             # is not typed, and the ancestor would be a deferred class anyway.
151             #
152              
153 75         289 $fd->print($prefix, $logstring, $crlf);
154 75         370 return;
155             }
156              
157             #
158             # ->open
159             #
160             # Open channel, and return the opened file descriptor.
161             # Also record opened file within $self->fd.
162             #
163             sub open {
164 33     33 0 47 my $self = shift;
165 33         70 my $filename = $self->filename;
166              
167 33         3323 require Log::Agent::File::Native;
168              
169 33         63 my $fobj;
170             my $note;
171              
172             #
173             # They may use ">file" or "|proc" as channel files if -magic_open
174             #
175              
176 33 100 66     152 if ($filename =~ /^\s*[>|]/ && $self->magic_open) {
177              
178             # restrict the permissions
179 2         19 my $mask = umask;
180 2 50       9 umask($mask | 0666 ^ $self->fileperm) if defined $self->fileperm;
181              
182             # open the file
183 2         7 my $h = gensym;
184 2 50       138 $fobj = Log::Agent::File::Native->make($h) if open($h, $filename);
185              
186             # restore the permissions
187 2         13 umask $mask;
188              
189             } else {
190             #
191             # If the file is already opened, and the current channel can be
192             # shared, do not re-open it: share the same Log::Agent::File::* object,
193             # along with its rotation policy.
194             #
195              
196 31         74 my $rotate = $self->rotate; # A Log::Agent::Rotate object
197 31         59 my $pool;
198              
199 31 50       66 if ($self->share) {
200 31         3808 require Log::Agent::File_Pool;
201 31         110 $pool = Log::Agent::File_Pool::file_pool();
202 31         80 my ($eobj, $erot) = $pool->get($filename);
203              
204 31 100       83 if (defined $eobj) {
205 1         2 $fobj = $eobj; # Reuse same object
206 1 0 33     4 $note = "rotation for '$filename' may be wrong" .
      33        
207             " (shared with distinct policies)" if
208             defined $erot && defined $rotate &&
209             !$erot->is_same($rotate);
210             }
211             }
212              
213 31 100       68 unless (defined $fobj) {
214 30 50       52 if (defined $rotate) {
215 0         0 $fobj = Log::Agent::File::Rotate->make($filename, $rotate);
216             } else {
217 30         74 my $h = gensym;
218 30 100       421 $fobj = Log::Agent::File::Native->make($h)
    50          
219             if sysopen($h, $filename, O_CREAT|O_APPEND|O_WRONLY,
220             defined $self->fileperm ? $self->fileperm : 0666);
221             }
222             }
223              
224             #
225             # Record object in pool if shared, even if already present.
226             # We maintain a refcount of all the shared items.
227             #
228              
229 31 50 33     139 $pool->put($filename, $fobj, $rotate)
230             if defined $fobj && $self->share;
231             }
232              
233             #
234             # If an error occurred, we have no choice but to emit a warning via warn().
235             # Otherwise, the error would disappear, and we know they don't want to
236             # silence us, or they would not try to open a logfile.
237             #
238             # Warn only once per filename though.
239             #
240              
241 33 50       126 unless (defined $fobj) {
242 0   0     0 my $prefix = $self->prefixing_string() || "$0: ";
243             warn "${prefix}can't open logfile \"$filename\": $!\n"
244 0 0       0 unless $self->warned->{$filename}++;
245 0         0 return undef;
246             }
247              
248 33   50     125 $self->{fd} = $fobj || 1; # Avoid recursion in open if not defined
249              
250             #
251             # Print the note, using ->write() now that $self->fd is recorded.
252             #
253              
254 33 50       71 if (defined $note) {
255 0 0       0 $note .= $self->crlf if $self->no_newline;
256 0         0 $self->write(undef, $note);
257             }
258              
259 33         58 return $fobj;
260             }
261              
262             #
263             # ->close -- defined
264             #
265             sub close {
266 19     19 1 32 my $self = shift;
267 19         40 my $fd = $self->fd;
268 19 50       44 return unless ref $fd;
269              
270 19         30 $self->{fd} = 1; # Prevents further opening from ->write
271 19 50       38 unless ($self->share) {
272 0         0 $fd->close;
273 0         0 return;
274             }
275              
276             #
277             # A shared file is physically closed only when the last reference
278             # to it is removed.
279             #
280              
281 19         51 my $pool = Log::Agent::File_Pool::file_pool();
282 19 50       40 $fd->close if $pool->remove($self->filename);
283 19         106 return;
284             }
285              
286             1; # for require
287             __END__