File Coverage

blib/lib/Log/ger/Output/File.pm
Criterion Covered Total %
statement 38 45 84.4
branch 16 30 53.3
condition 8 14 57.1
subroutine 6 7 85.7
pod 0 1 0.0
total 68 97 70.1


line stmt bran cond sub pod time code
1             package Log::ger::Output::File;
2              
3             our $DATE = '2019-09-10'; # DATE
4             our $VERSION = '0.009'; # VERSION
5              
6             ## no critic (InputOutput::RequireBriefOpen)
7              
8 1     1   2207 use strict;
  1         2  
  1         26  
9 1     1   4 use warnings;
  1         1  
  1         21  
10              
11             # supply object methods for filehandles, required for older perls e.g. 5.10
12 1     1   365 use FileHandle;
  1         1955  
  1         4  
13              
14             our %lock_handles;
15              
16             sub get_hooks {
17 5     5 0 11434 my %conf = @_;
18              
19 5         9 my $lazy = $conf{lazy};
20 5 50       7 my $autoflush = $conf{autoflush}; $autoflush = 1 unless defined $autoflush;
  5         12  
21 5   50     19 my $lock_mode = $conf{lock_mode} || 'none';
22              
23 5 100 100     22 (defined($conf{path}) || $conf{handle}) or
24             die "Please specify 'path' or 'handle'";
25 4 50       17 $lock_mode =~ /\A(none|write|exclusive)\z/ or
26             die "Invalid lock_mode, please choose none|write|exclusive";
27             $lock_mode ne 'none' && $conf{handle} and
28 4 0 33     8 die "Locking using handle not supported for now";
29              
30             my $code_lock = sub {
31 0     0   0 require File::Flock::Retry;
32 0 0       0 my $key = defined($conf{path}) ? ":$conf{path}" : $conf{handle};
33 0 0       0 if ($lock_handles{$key}) {
34 0         0 return $lock_handles{$key};
35             }
36 0         0 $lock_handles{$key} = File::Flock::Retry->lock("$conf{path}.lck");
37             #Scalar::Util::weaken($lock_handles{$key});
38             # XXX how do we purge old %lock_handles keys?
39 0         0 return $lock_handles{$key};
40 4         15 };
41              
42 4         6 my $fh;
43             my $code_open = sub {
44 4 50   4   7 return if $fh;
45 4 100       8 if (defined(my $path = $conf{path})) {
46 3 50       147 open $fh, ">>", $path or die "Can't open log file '$path': $!";
47             } else {
48 1         2 $fh = $conf{handle};
49             }
50 4         9 $fh;
51 4         9 };
52              
53 4 50       8 if ($lock_mode eq 'exclusive') {
54 0         0 $code_lock->();
55             }
56              
57 4 100       12 $code_open->() unless $lazy;
58              
59             return {
60             create_log_routine => [
61             __PACKAGE__, 50,
62             sub {
63 12     12   2575 my %args = @_;
64              
65             my $logger = sub {
66 4         3270 my $lock_handle;
67 4 100 66     13 $code_open->() if $lazy && !$fh;
68 4 50       17 $lock_handle = $code_lock->() if $lock_mode eq 'write';
69 4         26 print $fh $_[1];
70 4 50       14 print $fh "\n" unless $_[1] =~ /\R\z/;
71 4 50 33     108 $fh->flush if $autoflush || $lock_handle;
72 4         15 undef $lock_handle;
73 12         38 };
74 12         34 [$logger];
75 4         25 }],
76             };
77             }
78              
79             1;
80             # ABSTRACT: Send logs to file
81              
82             __END__
83              
84             =pod
85              
86             =encoding UTF-8
87              
88             =head1 NAME
89              
90             Log::ger::Output::File - Send logs to file
91              
92             =head1 VERSION
93              
94             version 0.009
95              
96             =head1 SYNOPSIS
97              
98             use Log::ger::Output 'File' => (
99             path => '/path/to/file.log', # or handle => $fh
100             lazy => 1, # optional, default 0
101             );
102             use Log::ger;
103              
104             log_warn "blah ...";
105              
106             =head1 DESCRIPTION
107              
108             This is a plugin to send logs to a file, with some options. File will be opened
109             with append mode. A lock can be requested at every write, or when opening the
110             file. By default, filehandle will be flushed after each log.
111              
112             =for Pod::Coverage ^(.+)$
113              
114             =head1 CONFIGURATION
115              
116             =head2 path => filename
117              
118             Specify filename to open. File will be opened in append mode.
119              
120             =head2 handle => glob|obj
121              
122             Alternatively, you can provide an already opened filehandle.
123              
124             =head2 autoflush => bool (default: 1)
125              
126             Can be turned off if you need more speed, but note that under the absence of
127             autoflush, partial log messages might be written.
128              
129             =head2 lazy => bool (default: 0)
130              
131             If set to true, will only open the file right before we need to log the message
132             (instead of during output initialization). If you have lots of applications that
133             use file logging, this can avoid the proliferation of zero-sized log files. On
134             the other hand, the application bears an additional risk of failing to open a
135             log file in the middle of the run.
136              
137             =head2 lock_mode => str (none|write|exclusive, default: none)
138              
139             If you set this to C<none> (the default), no locking is done. When there are
140             several applications/processes that output log to the same file, messages from
141             applications might get jumbled, e.g. partial message from application 1 is
142             followed by message from application 2 and 3, then continued by the rest of
143             message from application 1, and so on.
144              
145             If you set this to C<write>, an attempt to acquire an exclusive lock to C<<
146             <PATH>.lck >> will be made. If all logger processes use locking, this makes it
147             safe to log to the same file. However, this increases the overhead of writing
148             the log which will become non-negligible once you log to files at the rate of
149             thousands per second. Also, when a locking attempt fails after 60 seconds, this
150             module will die. C<autoflush> is automatically turned on under this locking
151             mode.
152              
153             If you set this to C<exclusive>, locking will be attempted only once during the
154             output initialization.
155              
156             =head1 TODO
157              
158             When C<lock_mode> is set to C<exclusive>, and user switches output, we have not
159             released the lock.
160              
161             =head1 SEE ALSO
162              
163             L<Log::ger>
164              
165             L<Log::ger::Output::SimpleFile> is a simpler output plugin: no locking,
166             autoflush, or lazy options.
167              
168             L<Log::ger::Output::FileWriteRotate> offers autorotation feature.
169              
170             =head1 AUTHOR
171              
172             perlancar <perlancar@cpan.org>
173              
174             =head1 COPYRIGHT AND LICENSE
175              
176             This software is copyright (c) 2019, 2017 by perlancar@cpan.org.
177              
178             This is free software; you can redistribute it and/or modify it under
179             the same terms as the Perl 5 programming language system itself.
180              
181             =cut