File Coverage

blib/lib/Log/Deep.pm
Criterion Covered Total %
statement 13 15 86.6
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 18 20 90.0


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   120634 use strict;
  3         5  
  3         88  
10 3     3   10 use warnings;
  3         4  
  3         64  
11 3     3   1237 use version;
  3         4159  
  3         13  
12 3     3   173 use Carp qw/croak longmess/;
  3         3  
  3         141  
13 3     3   150275 use List::MoreUtils qw/any/;
  0            
  0            
14             use Readonly;
15             use Clone qw/clone/;
16             use Data::Dump::Streamer;
17             use POSIX qw/strftime/;
18             use Fcntl qw/SEEK_END/;
19             use English qw/ -no_match_vars /;
20             use base qw/Exporter/;
21              
22             our $VERSION = version->new('0.3.3');
23              
24             Readonly my @LOG_LEVELS => qw/info message debug warn error fatal/;
25              
26             sub new {
27             my $class = shift;
28             my %param = @_;
29             my $self = {};
30              
31             bless $self, $class;
32              
33             $self->{dump} = Data::Dump::Streamer->new()->Indent(0)->Names('DATA');
34              
35             # set up log levels
36             if (!$param{-level}) {
37             $self->level(qw/warn error fatal/);
38             }
39             else {
40             $self->level(ref $param{-level} eq 'ARRAY' ? @{$param{-level}} : $param{-level});
41             }
42              
43             # set up the log file parameters
44             $self->{file} = $param{-file};
45             $self->{log_dir} = $param{-log_dir};
46             $self->{log_name} = $param{-name};
47             $self->{date_fmt} = $param{-date_fmt};
48             $self->{style} = $param{-style} || 'none';
49              
50             # set up the maximum random session id
51             $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             $self->{vars_config} = $param{-vars_config} || {};
56             $self->{vars_config}{ENV} = \%ENV;
57              
58             # runtime varibles - These are recorded with every log message
59             $self->{vars} = $param{-vars} || {};
60              
61             if ($param{-catchwarn}) {
62             $self->catch_warnings(1);
63             }
64              
65             # check if we are starting a session or not
66             if ($param{-nosession}) {
67             $self->{session} = $param{-session_id};
68             }
69             else {
70             $self->session($param{-session_id});
71             }
72              
73             return $self;
74             }
75              
76             sub info {
77             my ($self, @params) = @_;
78              
79             return if !$self->is_info;
80              
81             if (!ref $params[0] || ref $params[0] ne 'HASH') {
82             unshift @params, {};
83             }
84              
85             $params[0]{-level} = 'info';
86              
87             return $self->record(@params);
88             }
89              
90             sub message {
91             my ($self, @params) = @_;
92              
93             return if !$self->is_message;
94              
95             if (!ref $params[0] || ref $params[0] ne 'HASH') {
96             unshift @params, {};
97             }
98              
99             $params[0]{-level} = 'message';
100              
101             return $self->record(@params);
102             }
103              
104             sub debug {
105             my ($self, @params) = @_;
106              
107             return if !$self->is_debug;
108              
109             if (!ref $params[0] || ref $params[0] ne 'HASH') {
110             unshift @params, {};
111             }
112              
113             $params[0]{-level} = 'debug';
114              
115             return $self->record(@params);
116             }
117              
118             sub warn {
119             my ($self, @params) = @_;
120              
121             return if !$self->is_warn;
122              
123             if (!ref $params[0] || ref $params[0] ne 'HASH') {
124             unshift @params, {};
125             }
126              
127             $params[0]{-level} = 'warn';
128              
129             return $self->record(@params);
130             }
131              
132             sub error {
133             my ($self, @params) = @_;
134              
135             return if !$self->is_error;
136              
137             if (!ref $params[0] || ref $params[0] ne 'HASH') {
138             unshift @params, {};
139             }
140              
141             $params[0]{-level} = 'error';
142              
143             my $ans = $self->record(@params);
144             $self->flush;
145              
146             return $ans;
147             }
148              
149             sub fatal {
150             my ($self, @params) = @_;
151              
152             if (!ref $params[0] || ref $params[0] ne 'HASH') {
153             unshift @params, {};
154             }
155              
156             $params[0]{-level} = 'fatal';
157              
158             $self->record(@params);
159              
160             croak join ' ', @params[ 1 .. @params -1 ];
161              
162             return;
163             }
164              
165             sub security {
166             my ($self, @params) = @_;
167              
168             if (!ref $params[0] || ref $params[0] ne 'HASH') {
169             unshift @params, {};
170             }
171              
172             $params[0]{-level} = 'security';
173              
174             return $self->record(@params);
175             }
176              
177             sub record {
178             my ($self, $data, @message) = @_;
179             my $dump = $self->{dump};
180              
181             # check that a session has been created
182             $self->session($data->{-session_id}) if !$self->{session_id};
183              
184             my $level = $data->{-level} || '(none)';
185             delete $data->{-level};
186              
187             my $configs = $data->{-write_configs};
188             delete $data->{-write_configs};
189              
190             my $param = {
191             data => $data,
192             vars => $self->{vars},
193             };
194              
195             # add all the config variables to the variables to be logged
196             if ($configs) {
197             for my $var ( keys %{ $self->{vars_config} } ) {
198             $param->{vars}{$var} = $self->{vars_config}{$var};
199             }
200             }
201              
202             # set up
203             $param->{stack} = substr longmess, 0, 1_000;
204             $param->{stack} =~ s/^\s+[^\n]*Log::Deep::[^\n]*\n//gxms;
205             $param->{stack} =~ s/\A\s at [^\n]*\n\s+//gxms;
206             $param->{stack} =~ s/\n[^\n]+\Z/\n.../xms;
207              
208             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             for my $col (@log) {
218             chomp $col;
219             # quote all back slashes
220             $col =~ s{\\}{\\\\}g;
221             # quote all new lines
222             $col =~ s/\n/\\n/g;
223             }
224              
225             my $log = $self->log_handle();
226             print {$log} join ',', @log;
227             print {$log} "\n";
228              
229             $self->{log_session_count}++;
230              
231             return ;
232             }
233              
234             sub log_handle {
235             my $self = shift;
236              
237             if ( !$self->{handle} ) {
238             $self->{log_dir} ||= $ENV{TMP} || '/tmp';
239             $self->{log_name} ||= (split m{/}, $0)[-1] || 'deep';
240             $self->{date_fmt} ||= '%Y-%m-%d';
241             $self->{log_date} = strftime $self->{date_fmt}, localtime;
242              
243             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             my $missing = 0;
247             if ( !$self->{reopening} && -s $file ) {
248             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             seek $fh, -20, SEEK_END;
250             my $end = <$fh>;
251             $missing = $end =~ /\n$/;
252             close $fh;
253             }
254              
255             open my $fh, '>>', $file or die "Could not open log file $file: $OS_ERROR\n";
256             $self->{file} = $file;
257             $self->{handle} = $fh;
258              
259             if ($missing) {
260             print {$fh} "\n";
261             }
262             }
263              
264             return $self->{handle};
265             }
266              
267             sub session {
268             my ($self, $session_id) = @_;
269              
270             if ( ! defined $session_id ) {
271             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             $self->{session_id} = $session_id || int rand $self->{rand_max};
276              
277             $self->record({ -write_configs => 1 }, '"START"');
278              
279             $self->{log_session_count} = 0;
280              
281             return;
282             }
283              
284             sub level {
285             my ($self, @level) = @_;
286              
287             $self->{level} ||= { map { $_ => 0 } @LOG_LEVELS };
288              
289             # if not called with any parameters return the level hash
290             return clone $self->{level} if !@level;
291              
292             # return log state if asked about that state
293             return $self->{level}{$level[1]} if $level[0] eq '-log';
294              
295             # Set a log state if requested
296             return $self->{level}{$level[1]} = 1 if $level[0] eq '-set';
297              
298             # Unset a log state if requested
299             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             if (@level == 1 && $level[0] =~ /^\d$/) {
303             my $i = 0;
304             for my $log_level (@LOG_LEVELS) {
305             $self->{level}{$log_level} = $i++ >= $level[0] ? 1 : 0;
306             }
307              
308             return clone $self->{level};
309             }
310              
311             # if the is one parameter and it is a string turn on that level and highter
312             if ( @level == 1 && any { $_ eq $level[0] } @LOG_LEVELS ) {
313              
314             # flag that we have found the starting level
315             my $found = 0;
316              
317             for my $log_level (@LOG_LEVELS) {
318              
319             # flag that we have the start level
320             $found = 1 if $log_level eq $level[0];
321              
322             # mark the current level appropriatly
323             $self->{level}{$log_level} = $found ? 1 : 0;
324             }
325              
326             return clone $self->{level};
327             }
328              
329             # set all levels passed in as active levels.
330             for my $level (@level) {
331             $self->{level}{$level} = 1;
332             }
333              
334             return clone $self->{level};
335             }
336              
337             sub enable {
338             my ($self, @levels) = @_;
339              
340             for my $level (@levels) {
341             $self->{level}{$level} = 1;
342             }
343             return;
344             }
345              
346             sub disable {
347             my ($self, @levels) = @_;
348              
349             for my $level (@levels) {
350             $self->{level}{$level} = 0;
351             }
352              
353             return;
354             }
355              
356             sub is_info { return $_[0]->{level}{info} }
357             sub is_message { return $_[0]->{level}{message} }
358             sub is_debug { return $_[0]->{level}{debug} }
359             sub is_warn { return $_[0]->{level}{warn} }
360             sub is_error { return $_[0]->{level}{error} }
361             sub is_fatal { return $_[0]->{level}{fatal} }
362             sub is_security { return 1 }
363              
364             sub file {
365             my ($self) = @_;
366              
367             return $self->{file};
368             }
369              
370             sub catch_warnings {
371             my ($self, $action) = @_;
372              
373             if ( $action == 1 && !$self->{old_warn_handle} ) {
374             # save old handle
375             $self->{old_warn_handle} = $SIG{__WARN__};
376              
377             # install a redirect of all warnings to $self->warn
378             $SIG{__WARN__} = sub {
379             my $data = {};
380             if ( ref $_[0] ) {
381             # record the error reference for better display
382             # using the error in the message just stringifys it
383             $data->{ERROR_OBJ} = $_[0];
384             }
385             $self->warn( $data, $_[0] );
386             }
387             }
388             elsif ( $action == 0 && $self->{old_warn_handle} ) {
389             $SIG{__WARN__} = $self->{old_warn_handle};
390             delete $self->{old_warn_handle};
391             }
392              
393             return $self->{old_warn_handle} && 1;
394             }
395              
396             sub flush {
397             my ($self) = @_;
398              
399             return if ! exists $self->{handle};
400              
401             close $self->{handle};
402             delete $self->{handle};
403             $self->{reopening} = 1;
404              
405             return;
406             }
407              
408             sub DESTROY {
409             my ($self) = @_;
410              
411             if ($self->{handle}) {
412             close $self->{handle};
413             }
414              
415             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.3.
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