File Coverage

blib/lib/Crane/Logger.pm
Criterion Covered Total %
statement 51 52 98.0
branch 16 22 72.7
condition 1 3 33.3
subroutine 14 14 100.0
pod 1 1 100.0
total 83 92 90.2


line stmt bran cond sub pod time code
1             # -*- coding: utf-8 -*-
2              
3              
4             package Crane::Logger;
5              
6              
7 2     2   649 use Crane::Base qw( Exporter );
  2         4  
  2         14  
8 2     2   589 use Crane::Options;
  2         6  
  2         253  
9 2     2   1451 use Crane::Config;
  2         6  
  2         240  
10              
11 2     2   576415 use Data::Dumper;
  2         25284  
  2         207  
12 2     2   22 use Fcntl qw( :flock );
  2         5  
  2         348  
13 2     2   2310 use POSIX qw( strftime );
  2         17383  
  2         19  
14              
15              
16             our @EXPORT = qw(
17             &log_fatal
18             &log_error
19             &log_warning
20             &log_info
21             &log_debug
22             &log_verbose
23             );
24              
25             our @EXPORT_LEVELS = qw(
26             $LOG_FATAL
27             $LOG_ERROR
28             $LOG_WARNING
29             $LOG_INFO
30             $LOG_DEBUG
31             $LOG_VERBOSE
32             );
33              
34             our @EXPORT_OK = ( @EXPORT_LEVELS );
35              
36             our %EXPORT_TAGS = (
37             'levels' => \@EXPORT_LEVELS,
38             );
39              
40              
41             Readonly::Scalar(our $LOG_FATAL => 1);
42             Readonly::Scalar(our $LOG_ERROR => 2);
43             Readonly::Scalar(our $LOG_WARNING => 3);
44             Readonly::Scalar(our $LOG_INFO => 4);
45             Readonly::Scalar(our $LOG_DEBUG => 5);
46             Readonly::Scalar(our $LOG_VERBOSE => 6);
47              
48             Readonly::Hash(our %LOG_LEVELS => (
49             $LOG_FATAL => $LOG_FATAL, 'fatal' => $LOG_FATAL,
50             $LOG_ERROR => $LOG_ERROR, 'error' => $LOG_ERROR, 'err' => $LOG_ERROR,
51             $LOG_WARNING => $LOG_WARNING, 'warning' => $LOG_WARNING, 'warn' => $LOG_WARNING,
52             $LOG_INFO => $LOG_INFO, 'info' => $LOG_INFO,
53             $LOG_DEBUG => $LOG_DEBUG, 'debug' => $LOG_DEBUG,
54             $LOG_VERBOSE => $LOG_VERBOSE, 'verbose' => $LOG_VERBOSE
55             ));
56              
57              
58             # Log level
59             our $LOG_LEVEL = $LOG_LEVELS{ config->{'log'}->{'level'} // $LOG_INFO };
60              
61             if ( options->{'debug'} ) {
62             $LOG_LEVEL = $LOG_DEBUG;
63             } elsif ( options->{'verbose'} ) {
64             $LOG_LEVEL = $LOG_VERBOSE;
65             }
66              
67              
68             # Log file handle
69             our $MESSAGES_FH = *STDOUT;
70              
71             if ( my $log_filename = options->{'log'} // config->{'log'}->{'filename'} ) {
72             if ( -e $log_filename ) {
73             open $MESSAGES_FH, '>>:encoding(UTF-8)', $log_filename or confess("Unable to update log '$log_filename': $OS_ERROR");
74             }
75             }
76              
77              
78             # Error log file handle
79             our $ERRORS_FH = *STDERR;
80              
81             if ( my $log_error_filename = options->{'log-error'} // config->{'log'}->{'error_filename'} ) {
82             if ( -e $log_error_filename ) {
83             open $ERRORS_FH, '>>:encoding(UTF-8)', $log_error_filename or confess("Unable to update log '$log_error_filename': $OS_ERROR");
84             }
85             }
86              
87              
88             # Close file handles on exit
89             END {
90            
91 2 50   2   9282 close $MESSAGES_FH or confess($OS_ERROR);
92 2 50       0 close $ERRORS_FH or confess($OS_ERROR);
93            
94             }
95              
96              
97             sub log_fatal {
98            
99 12 50   12   85285 if ( $LOG_LEVEL >= $LOG_FATAL ) {
100 12         41 write_to_fh($ERRORS_FH, @_);
101             }
102            
103 12         29 return;
104            
105             }
106              
107              
108             sub log_error {
109            
110 12 100   12   72 if ( $LOG_LEVEL >= $LOG_ERROR ) {
111 10         28 write_to_fh($ERRORS_FH, @_);
112             }
113            
114 12         24 return;
115            
116             }
117              
118              
119             sub log_warning {
120            
121 12 100   12   79 if ( $LOG_LEVEL >= $LOG_WARNING ) {
122 8         21 write_to_fh($ERRORS_FH, @_);
123             }
124            
125 12         21 return;
126            
127             }
128              
129              
130             sub log_info {
131            
132 12 100   12   70 if ( $LOG_LEVEL >= $LOG_INFO ) {
133 6         19 write_to_fh($MESSAGES_FH, @_);
134             }
135            
136 12         25 return;
137            
138             }
139              
140              
141             sub log_debug {
142            
143 12 100   12   65 if ( $LOG_LEVEL >= $LOG_DEBUG ) {
144 4         12 write_to_fh($MESSAGES_FH, @_);
145             }
146            
147 12         23 return;
148            
149             }
150              
151              
152             sub log_verbose {
153            
154 12 100   12   63 if ( $LOG_LEVEL >= $LOG_VERBOSE ) {
155 2         6 write_to_fh($MESSAGES_FH, @_);
156             }
157            
158 12         27 return;
159            
160             }
161              
162              
163             sub write_to_fh {
164            
165 42     42 1 95 my ( $fh, @messages ) = @_;
166            
167 42 50       109 if ( not defined $fh ) {
168 0         0 confess('Invalid file handle');
169             }
170            
171 42         943 local $Data::Dumper::Indent = 1;
172 42         66 local $Data::Dumper::Purity = 0;
173 42         51 local $Data::Dumper::Terse = 1;
174            
175 42         286 flock $fh, LOCK_EX;
176            
177 42         3286 my $datetime = strftime(q{%Y-%m-%d %H:%M:%S %z %s}, localtime);
178            
179 42         150 foreach my $message ( @messages ) {
180 42 50 33     564 foreach my $line ( split m{$INPUT_RECORD_SEPARATOR}osi, ( not defined $message or ref $message ) ? Dumper($message) : $message ) {
181 42 50       50 print { $fh } "[$datetime] $line\n" or confess($OS_ERROR);
  42         318  
182             }
183             }
184            
185 42         1651 flock $fh, LOCK_UN;
186            
187 42         139 return;
188            
189             }
190              
191              
192             1;
193              
194              
195             =head1 NAME
196              
197             Crane::Logger - Log manager
198              
199              
200             =head1 SYNOPSIS
201              
202             use Crane::Logger;
203            
204             log_fatal('Fatal message', caller);
205             log_error('Error message');
206             log_warning('Warning message', $ref);
207             log_info("First line\nSecond line\n");
208             log_debug($ref);
209             log_verbose('First line', 'Second line');
210              
211              
212             =head1 DESCRIPTION
213              
214             Simple log manager with six log levels. Supports auto split messages by "end of
215             line" and dump references using L.
216              
217              
218             =head2 Log entry
219              
220             Each log entry looks like ...
221              
222             [2013-12-30 02:36:22 +0400 1388356582] Hello, world!
223              
224             ... and contains:
225              
226             =over
227              
228             =item Date
229              
230             Date in ISO format: YYYY-MM-DD.
231              
232             2013-12-30
233              
234             =item Time
235              
236             Time in ISO format: hh:mm:ss.
237              
238             02:36:22
239              
240             =item Time zone
241              
242             Time zone in ISO format: ±hhmm.
243              
244             +0400
245              
246             =item Unix time
247              
248             Unix time.
249              
250             1388356582
251              
252             =item Message
253              
254             Log message.
255              
256             Hello, world!
257              
258             =back
259              
260             In case of log reference, each line will contain "header" (date and times):
261              
262             [2013-12-30 02:36:22 +0400 1388356582] {
263             [2013-12-30 02:36:22 +0400 1388356582] 'room' => 'Sitting room',
264             [2013-12-30 02:36:22 +0400 1388356582] 'colors' => [
265             [2013-12-30 02:36:22 +0400 1388356582] 'orange',
266             [2013-12-30 02:36:22 +0400 1388356582] 'purple',
267             [2013-12-30 02:36:22 +0400 1388356582] 'black'
268             [2013-12-30 02:36:22 +0400 1388356582] ]
269             [2013-12-30 02:36:22 +0400 1388356582] }
270              
271              
272             =head2 Log levels
273              
274             =over
275              
276             =item B
277              
278             Logs messages at a B level only.
279              
280             =item B
281              
282             Logs messages classified as B and B.
283              
284             =item B
285              
286             Logs messages classified as B, B and B.
287              
288             =item B
289              
290             Logs messages classified as B, B, B and B.
291              
292             =item B
293              
294             Logs messages classified as B, B, B, B and
295             B.
296              
297             =item B
298              
299             Logs messages classified as B, B, B, B, B
300             and B.
301              
302             =back
303              
304             Messages on levels: B, B and B go to error log; B,
305             B and B go to messages log.
306              
307              
308             =head1 EXPORTED FUNCTIONS
309              
310             =over
311              
312             =item B (I<@messages>)
313              
314             Logs I<@messages> with level L.
315              
316             =item B (I<@messages>)
317              
318             Logs I<@messages> with level L.
319              
320             =item B (I<@messages>)
321              
322             Logs I<@messages> with level L.
323              
324             =item B (I<@messages>)
325              
326             Logs I<@messages> with level L.
327              
328             =item B (I<@messages>)
329              
330             Logs I<@messages> with level L.
331              
332             =item B (I<@messages>)
333              
334             Logs I<@messages> with level L.
335              
336             =back
337              
338              
339             =head1 FUNCTIONS
340              
341             =over
342              
343             =item B (I<$fh>, I<@messages>)
344              
345             Write I<@messages> to file handle I<$fh>.
346              
347             =back
348              
349              
350             =head1 ERRORS
351              
352             =over
353              
354             =item Unable to update log 'I<%s>': I<%s>
355              
356             Where I<%s> is log filename and I<%s> is reason message.
357              
358             Fires when unable to open or write to log file.
359              
360             =item Invalid file handle
361              
362             Fires when call L with invalid file
363             handle.
364              
365             =back
366              
367              
368             =head1 FILES
369              
370             =over
371              
372             =item F
373              
374             Default log file with messages.
375              
376             =item F
377              
378             Default log file with errors.
379              
380             =back
381              
382              
383             =head1 BUGS
384              
385             Please report any bugs or feature requests to
386             L or to
387             L.
388              
389              
390             =head1 AUTHOR
391              
392             Tema Novikov,
393              
394              
395             =head1 COPYRIGHT AND LICENSE
396              
397             Copyright (C) 2013-2014 Tema Novikov.
398              
399             This library is free software; you can redistribute it and/or modify it under
400             the terms of the Artistic License 2.0. For details, see the full text of the
401             license in the file LICENSE.
402              
403              
404             =head1 SEE ALSO
405              
406             =over
407              
408             =item * B
409              
410             L
411              
412             =item * B
413              
414             L
415              
416             =back