File Coverage

blib/lib/Log/Deep.pm
Criterion Covered Total %
statement 216 220 98.1
branch 61 78 78.2
condition 40 71 56.3
subroutine 39 39 100.0
pod 24 24 100.0
total 380 432 87.9


line stmt bran cond sub pod time code
1             package Log::Deep;
2              
3             # Created on: 2008-10-19 04:44:02
4             # Create by: ivan
5             # $Id$
6             # $Revision$, $HeadURL$, $Date$
7             # $Revision$, $Source$, $Date$
8              
9 3     3   109722 use strict;
  3         5  
  3         92  
10 3     3   12 use warnings;
  3         4  
  3         64  
11 3     3   1187 use version;
  3         4272  
  3         13  
12 3     3   185 use Carp qw/croak longmess/;
  3         4  
  3         172  
13 3     3   1668 use List::MoreUtils qw/any/;
  3         29709  
  3         20  
14 3     3   3332 use Readonly;
  3         7087  
  3         230  
15 3     3   1459 use Clone qw/clone/;
  3         6629  
  3         168  
16 3     3   2454 use Data::Dump::Streamer;
  3         155816  
  3         25  
17 3     3   2176 use POSIX qw/strftime/;
  3         15221  
  3         15  
18 3     3   2369 use Fcntl qw/SEEK_END/;
  3         4  
  3         113  
19 3     3   505 use English qw/ -no_match_vars /;
  3         3230  
  3         20  
20 3     3   987 use base qw/Exporter/;
  3         4  
  3         6118  
21              
22             our $VERSION = version->new('0.3.4');
23              
24             Readonly my @LOG_LEVELS => qw/info message debug warn error fatal/;
25              
26             sub new {
27 4     4 1 842 my $class = shift;
28 4         12 my %param = @_;
29 4         9 my $self = {};
30              
31 4         10 bless $self, $class;
32              
33 4         41 $self->{dump} = Data::Dump::Streamer->new()->Indent(0)->Names('DATA');
34              
35             # set up log levels
36 4 100       359 if (!$param{-level}) {
37 2         11 $self->level(qw/warn error fatal/);
38             }
39             else {
40 2 100       11 $self->level(ref $param{-level} eq 'ARRAY' ? @{$param{-level}} : $param{-level});
  1         4  
41             }
42              
43             # set up the log file parameters
44 4         63 $self->{file} = $param{-file};
45 4         12 $self->{log_dir} = $param{-log_dir};
46 4         10 $self->{log_name} = $param{-name};
47 4         8 $self->{date_fmt} = $param{-date_fmt};
48 4   50     25 $self->{style} = $param{-style} || 'none';
49              
50             # set up the maximum random session id
51 4   50     21 $self->{rand_max} = $param{-rand_max} || 10_000;
52              
53             # set up tracked variables
54             # Configuration variables - These are only recorded with calls to session()
55 4   50     21 $self->{vars_config} = $param{-vars_config} || {};
56 4         12 $self->{vars_config}{ENV} = \%ENV;
57              
58             # runtime varibles - These are recorded with every log message
59 4   50     25 $self->{vars} = $param{-vars} || {};
60              
61 4 50       13 if ($param{-catchwarn}) {
62 0         0 $self->catch_warnings(1);
63             }
64              
65             # check if we are starting a session or not
66 4 50       20 if ($param{-nosession}) {
67 0         0 $self->{session} = $param{-session_id};
68             }
69             else {
70 4         22 $self->session($param{-session_id});
71             }
72              
73 4         23 return $self;
74             }
75              
76             sub info {
77 2     2 1 579 my ($self, @params) = @_;
78              
79 2 100       6 return if !$self->is_info;
80              
81 1 50 33     11 if (!ref $params[0] || ref $params[0] ne 'HASH') {
82 1         2 unshift @params, {};
83             }
84              
85 1         24 $params[0]{-level} = 'info';
86              
87 1         3 return $self->record(@params);
88             }
89              
90             sub message {
91 2     2 1 628 my ($self, @params) = @_;
92              
93 2 100       6 return if !$self->is_message;
94              
95 1 50 33     5 if (!ref $params[0] || ref $params[0] ne 'HASH') {
96 1         2 unshift @params, {};
97             }
98              
99 1         4 $params[0]{-level} = 'message';
100              
101 1         3 return $self->record(@params);
102             }
103              
104             sub debug {
105 2     2 1 537 my ($self, @params) = @_;
106              
107 2 100       4 return if !$self->is_debug;
108              
109 1 50 33     4 if (!ref $params[0] || ref $params[0] ne 'HASH') {
110 1         2 unshift @params, {};
111             }
112              
113 1         3 $params[0]{-level} = 'debug';
114              
115 1         3 return $self->record(@params);
116             }
117              
118             sub warn {
119 2     2 1 526 my ($self, @params) = @_;
120              
121 2 50       11 return if !$self->is_warn;
122              
123 2 100 66     10 if (!ref $params[0] || ref $params[0] ne 'HASH') {
124 1         3 unshift @params, {};
125             }
126              
127 2         6 $params[0]{-level} = 'warn';
128              
129 2         4 return $self->record(@params);
130             }
131              
132             sub error {
133 1     1 1 698 my ($self, @params) = @_;
134              
135 1 50       3 return if !$self->is_error;
136              
137 1 50 33     5 if (!ref $params[0] || ref $params[0] ne 'HASH') {
138 1         2 unshift @params, {};
139             }
140              
141 1         4 $params[0]{-level} = 'error';
142              
143 1         3 my $ans = $self->record(@params);
144 1         2 $self->flush;
145              
146 1         3 return $ans;
147             }
148              
149             sub fatal {
150 1     1 1 744 my ($self, @params) = @_;
151              
152 1 50 33     5 if (!ref $params[0] || ref $params[0] ne 'HASH') {
153 1         2 unshift @params, {};
154             }
155              
156 1         4 $params[0]{-level} = 'fatal';
157              
158 1         3 $self->record(@params);
159              
160 1         21 croak join ' ', @params[ 1 .. @params -1 ];
161              
162 0         0 return;
163             }
164              
165             sub security {
166 1     1 1 541 my ($self, @params) = @_;
167              
168 1 50 33     5 if (!ref $params[0] || ref $params[0] ne 'HASH') {
169 1         3 unshift @params, {};
170             }
171              
172 1         3 $params[0]{-level} = 'security';
173              
174 1         3 return $self->record(@params);
175             }
176              
177             sub record {
178 13     13 1 23 my ($self, $data, @message) = @_;
179 13         19 my $dump = $self->{dump};
180              
181             # check that a session has been created
182 13 50       44 $self->session($data->{-session_id}) if !$self->{session_id};
183              
184 13   100     43 my $level = $data->{-level} || '(none)';
185 13         29 delete $data->{-level};
186              
187 13         19 my $configs = $data->{-write_configs};
188 13         22 delete $data->{-write_configs};
189              
190 13         36 my $param = {
191             data => $data,
192             vars => $self->{vars},
193             };
194              
195             # add all the config variables to the variables to be logged
196 13 100       31 if ($configs) {
197 5         5 for my $var ( keys %{ $self->{vars_config} } ) {
  5         22  
198 5         21 $param->{vars}{$var} = $self->{vars_config}{$var};
199             }
200             }
201              
202             # set up
203 13         590 $param->{stack} = substr longmess, 0, 1_000;
204 13         3214 $param->{stack} =~ s/^\s+[^\n]*Log::Deep::[^\n]*\n//gxms;
205 13         46 $param->{stack} =~ s/\A\s at [^\n]*\n\s+//gxms;
206 13         24 $param->{stack} =~ s/\n[^\n]+\Z/\n.../xms;
207              
208 13         755 my @log = (
209             strftime('%Y-%m-%d %H:%M:%S', localtime),
210             $self->{session_id},
211             $level,
212             (join ' ', @message),
213             $dump->Data($param)->Out(),
214             );
215              
216             # make each part safe for outputting to one line
217 13         72221 for my $col (@log) {
218 65         69 chomp $col;
219             # quote all back slashes
220 65         94 $col =~ s{\\}{\\\\}g;
221             # quote all new lines
222 65         73 $col =~ s/\n/\\n/g;
223             }
224              
225 13         36 my $log = $self->log_handle();
226 13         15 print {$log} join ',', @log;
  13         102  
227 13         17 print {$log} "\n";
  13         17  
228              
229 13         20 $self->{log_session_count}++;
230              
231 13         44 return ;
232             }
233              
234             sub log_handle {
235 13     13 1 16 my $self = shift;
236              
237 13 100       43 if ( !$self->{handle} ) {
238 12   50     67 $self->{log_dir} ||= $ENV{TMP} || '/tmp';
      66        
239 12   50     46 $self->{log_name} ||= (split m{/}, $0)[-1] || 'deep';
      66        
240 12   100     34 $self->{date_fmt} ||= '%Y-%m-%d';
241 12         585 $self->{log_date} = strftime $self->{date_fmt}, localtime;
242              
243 12   66     51 my $file = $self->{file} || "$self->{log_dir}/$self->{log_name}_$self->{log_date}.log";
244              
245             # guarentee that there is a new line before we start writing
246 12         15 my $missing = 0;
247 12 100 66     105 if ( !$self->{reopening} && -s $file ) {
248 4 50       183 open my $fh, '<', $file or die "Could not open the log file $file to check that it ends in a new line: $OS_ERROR\n";
249 4         14 seek $fh, -20, SEEK_END;
250 4         82 my $end = <$fh>;
251 4         22 $missing = $end =~ /\n$/;
252 4         147 close $fh;
253             }
254              
255 12 50       372 open my $fh, '>>', $file or die "Could not open log file $file: $OS_ERROR\n";
256 12         25 $self->{file} = $file;
257 12         28 $self->{handle} = $fh;
258              
259 12 100       29 if ($missing) {
260 4         6 print {$fh} "\n";
  4         33  
261             }
262             }
263              
264 13         27 return $self->{handle};
265             }
266              
267             sub session {
268 5     5 1 803 my ($self, $session_id) = @_;
269              
270 5 100       16 if ( ! defined $session_id ) {
271 4 50 33     16 return if defined $self->{log_session_count} && $self->{log_session_count} == 0;
272             }
273              
274             # use the supplied session id or create a new session id
275 5   33     125 $self->{session_id} = $session_id || int rand $self->{rand_max};
276              
277 5         35 $self->record({ -write_configs => 1 }, '"START"');
278              
279 5         10 $self->{log_session_count} = 0;
280              
281 5         9 return;
282             }
283              
284             sub level {
285 19     19 1 3102 my ($self, @level) = @_;
286              
287 19   100     79 $self->{level} ||= { map { $_ => 0 } @LOG_LEVELS };
  24         138  
288              
289             # if not called with any parameters return the level hash
290 19 100       240 return clone $self->{level} if !@level;
291              
292             # return log state if asked about that state
293 8 50       28 return $self->{level}{$level[1]} if $level[0] eq '-log';
294              
295             # Set a log state if requested
296 8 100       19 return $self->{level}{$level[1]} = 1 if $level[0] eq '-set';
297              
298             # Unset a log state if requested
299 7 100       19 return $self->{level}{$level[1]} = 0 if $level[0] eq '-unset';
300              
301             # if there is only one parameter that is a single digit set the all levels of that digit and higher
302 6 100 100     36 if (@level == 1 && $level[0] =~ /^\d$/) {
303 2         3 my $i = 0;
304 2         6 for my $log_level (@LOG_LEVELS) {
305 12 100       80 $self->{level}{$log_level} = $i++ >= $level[0] ? 1 : 0;
306             }
307              
308 2         39 return clone $self->{level};
309             }
310              
311             # if the is one parameter and it is a string turn on that level and highter
312 4 100 66 3   26 if ( @level == 1 && any { $_ eq $level[0] } @LOG_LEVELS ) {
  3         30  
313              
314             # flag that we have found the starting level
315 1         7 my $found = 0;
316              
317 1         3 for my $log_level (@LOG_LEVELS) {
318              
319             # flag that we have the start level
320 6 100       22 $found = 1 if $log_level eq $level[0];
321              
322             # mark the current level appropriatly
323 6 100       35 $self->{level}{$log_level} = $found ? 1 : 0;
324             }
325              
326 1         23 return clone $self->{level};
327             }
328              
329             # set all levels passed in as active levels.
330 3         8 for my $level (@level) {
331 8         15 $self->{level}{$level} = 1;
332             }
333              
334 3         60 return clone $self->{level};
335             }
336              
337             sub enable {
338 9     9 1 2078 my ($self, @levels) = @_;
339              
340 9         17 for my $level (@levels) {
341 9         29 $self->{level}{$level} = 1;
342             }
343 9         17 return;
344             }
345              
346             sub disable {
347 6     6 1 15 my ($self, @levels) = @_;
348              
349 6         11 for my $level (@levels) {
350 6         110 $self->{level}{$level} = 0;
351             }
352              
353 6         12 return;
354             }
355              
356 4     4 1 19 sub is_info { return $_[0]->{level}{info} }
357 4     4 1 15 sub is_message { return $_[0]->{level}{message} }
358 4     4 1 17 sub is_debug { return $_[0]->{level}{debug} }
359 4     4 1 16 sub is_warn { return $_[0]->{level}{warn} }
360 3     3 1 14 sub is_error { return $_[0]->{level}{error} }
361 2     2 1 8 sub is_fatal { return $_[0]->{level}{fatal} }
362 1     1 1 4 sub is_security { return 1 }
363              
364             sub file {
365 15     15 1 466 my ($self) = @_;
366              
367 15         49 return $self->{file};
368             }
369              
370             sub catch_warnings {
371 2     2 1 1244 my ($self, $action) = @_;
372              
373 2 100 66     20 if ( $action == 1 && !$self->{old_warn_handle} ) {
    50 33        
374             # save old handle
375 1         4 $self->{old_warn_handle} = $SIG{__WARN__};
376              
377             # install a redirect of all warnings to $self->warn
378             $SIG{__WARN__} = sub {
379 1     1   2 my $data = {};
380 1 50       4 if ( ref $_[0] ) {
381             # record the error reference for better display
382             # using the error in the message just stringifys it
383 0         0 $data->{ERROR_OBJ} = $_[0];
384             }
385 1         4 $self->warn( $data, $_[0] );
386             }
387 1         5 }
388             elsif ( $action == 0 && $self->{old_warn_handle} ) {
389 1         3 $SIG{__WARN__} = $self->{old_warn_handle};
390 1         6 delete $self->{old_warn_handle};
391             }
392              
393 2   100     13 return $self->{old_warn_handle} && 1;
394             }
395              
396             sub flush {
397 13     13 1 742 my ($self) = @_;
398              
399 13 100       37 return if ! exists $self->{handle};
400              
401 9         354 close $self->{handle};
402 9         41 delete $self->{handle};
403 9         16 $self->{reopening} = 1;
404              
405 9         18 return;
406             }
407              
408             sub DESTROY {
409 4     4   2228 my ($self) = @_;
410              
411 4 100       16 if ($self->{handle}) {
412 3         171 close $self->{handle};
413             }
414              
415 4         33 return;
416             }
417              
418             1;
419              
420             __END__
421              
422             =head1 NAME
423              
424             Log::Deep - Deep Logging of information about a script state
425              
426             =head1 VERSION
427              
428             This documentation refers to Log::Deep version 0.3.4.
429              
430              
431             =head1 SYNOPSIS
432              
433             use Log::Deep;
434              
435             # create or append a log file with the current users name in the current
436             # directory (if possible) else in the tmp directory. The session id will be
437             # randomly generated.
438             my $log = Log::Deep->new();
439              
440             $log->debug({-data => $object}, 'Message text');
441              
442             =head1 DESCRIPTION
443              
444             C<Log::Deep> creates a object for detailed logging of the state of the running
445             script.
446              
447             =head2 Plugins
448              
449             One of the aims of C<Log::Deep> is to be able to record deeper information
450             about the state of a running script. For example a CGI script (using CGI.pm)
451             has a CGI query object which stores its parameters and cookies, using the
452             CGI plugin this extra information is logged in the data section of the log
453             file.
454              
455             Some plugins add data only when the a logging session starts, others will
456             add data every time a log message is written.
457              
458             =head2 The Log File
459              
460             C<Log::Deep> log file format looks something like
461              
462             iso-timestamp;session id;level;message;caller;data
463              
464             All values are url encoded so that one log line will always represent one log
465             message, the line should be reasonably human readable except for the data
466             section which is a dump of all the deep details logged. A script C<deeper> is
467             provided with C<Log::Deeper> that allows for easier reading/searching of
468             C<Log::Deep> log files.
469              
470             =head1 SUBROUTINES/METHODS
471              
472             =head3 C<new ( %args )>
473              
474             Arg: B<-level> - array ref | string - If an array ref turns on all levels
475             specified, if a string turns on that level and higher
476              
477             Arg: B<-file> - string - The name of the log file to write to
478              
479             Arg: B<-log_dir> - string - The name of the directory that the log file is
480             written to.
481              
482             Arg: B<-name> - string - The name of the file in -log_dir
483              
484             Arg: B<-date_fmt> - string - The date format to use for appending to log
485             file -names
486              
487             Arg: B<-style> - -
488              
489             Arg: B<-rand_max> - -
490              
491             Arg: B<-session_id> - string - A specific session id to use.
492              
493             Return: Log::Deep - A new Log::Deep object
494              
495             Description: This creates a new log object.
496              
497             =head3 C<info ( $var )>
498              
499             Param: C<$> - type -
500              
501             Return: -
502              
503             Description:
504              
505             =head3 C<message ( $var )>
506              
507             Param: C<$> - type -
508              
509             Return: -
510              
511             Description:
512              
513             =head3 C<debug ( $var )>
514              
515             Param: C<$> - type -
516              
517             Return: -
518              
519             Description:
520              
521             =head3 C<warn ( $var )>
522              
523             Param: C<$> - type -
524              
525             Return: -
526              
527             Description:
528              
529             =head3 C<error ( $var )>
530              
531             Param: C<$> - type -
532              
533             Return: -
534              
535             Description:
536              
537             =head3 C<fatal ( $var )>
538              
539             Param: C<$> - type -
540              
541             Return: -
542              
543             Description:
544              
545             =head3 C<security ( $var )>
546              
547             Param: C<$> - type -
548              
549             Return: -
550              
551             Description:
552              
553             =head3 C<record ( $var )>
554              
555             Param: C<$> - type -
556              
557             Return: -
558              
559             Description:
560              
561             =head3 C<log_handle ( $var )>
562              
563             Param: C<$> - type -
564              
565             Return: -
566              
567             Description:
568              
569             =head3 C<session ( $var )>
570              
571             Param: C<$> - type -
572              
573             Return: -
574              
575             Description:
576              
577             =head3 C<level ( $var )>
578              
579             Param: C<$> - type -
580              
581             Return: -
582              
583             Description:
584              
585             =head3 C<enable (@levels)>
586              
587             Param: C<@levels> - strings - The names of levels to enable
588              
589             Description: Enables the supplied levels
590              
591             =head3 C<disable (@levels)>
592              
593             Param: C<@levels> - strings - The names of levels to disable
594              
595             Description: Disables the supplied levels
596              
597             =head3 C<is_info ()>
598              
599             Return: bool - True if the info log level is enabled
600              
601             Description:
602              
603             =head3 C<is_message ()>
604              
605             Return: bool - True if the message log level is enabled
606              
607             Description:
608              
609             =head3 C<is_debug ()>
610              
611             Return: bool - True if the debug log level is enabled
612              
613             Description:
614              
615             =head3 C<is_warn ()>
616              
617             Return: bool - True if the warn log level is enabled
618              
619             Description:
620              
621             =head3 C<is_error ()>
622              
623             Return: bool - True if the error log level is enabled
624              
625             Description:
626              
627             =head3 C<is_fatal ()>
628              
629             Return: bool - True if the fatal log level is enabled
630              
631             Description:
632              
633             =head3 C<is_security ()>
634              
635             Return: bool - True if the security log level is enabled
636              
637             Description:
638              
639             =head3 C<file ( $var )>
640              
641             Return: string - The file name of the currently being written to log file
642              
643             Description: Gets the file name of the current log file
644              
645             =head3 C<catch_warnings ( $action )>
646              
647             Param: C<$action> - 1 | 0 | undef - Set catch warnings (1), unset catch warnings (0) or report state (undef)
648              
649             Return: bool - True if currently catching warnings, false if not
650              
651             Description: Turns on/off catching warnings and/or returns the current warn catching state.
652              
653             =head3 C<flush ()>
654              
655             Description: Calls IO::Handle's flush on the log file handle
656              
657             =head1 DIAGNOSTICS
658              
659             =head1 CONFIGURATION AND ENVIRONMENT
660              
661             =head1 DEPENDENCIES
662              
663             =head1 INCOMPATIBILITIES
664              
665             =head1 BUGS AND LIMITATIONS
666              
667             There are no known bugs in this module.
668              
669             Please report problems to Ivan Wills (ivan.wills@gmail.com).
670              
671             Patches are welcome.
672              
673             =head1 AUTHOR
674              
675             Ivan Wills - (ivan.wills@gmail.com)
676              
677             =head1 LICENSE AND COPYRIGHT
678              
679             Copyright (c) 2009 Ivan Wills (14 Mullion Close, Hornsby Heights, NSW 2077).
680             All rights reserved.
681              
682             This module is free software; you can redistribute it and/or modify it under
683             the same terms as Perl itself. See L<perlartistic>. This program is
684             distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
685             without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
686             PARTICULAR PURPOSE.
687              
688             =cut