File Coverage

blib/lib/Log/ger/Output/File.pm
Criterion Covered Total %
statement 39 46 84.7
branch 16 30 53.3
condition 8 14 57.1
subroutine 6 7 85.7
pod 0 1 0.0
total 69 98 70.4


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