File Coverage

blib/lib/Log/Dynamic.pm
Criterion Covered Total %
statement 82 89 92.1
branch 19 28 67.8
condition 21 30 70.0
subroutine 19 20 95.0
pod 5 6 83.3
total 146 173 84.3


line stmt bran cond sub pod time code
1             package Log::Dynamic;
2              
3             $VERSION = 0.04;
4              
5 10     10   785352 use strict;
  10         20  
  10         399  
6 10     10   51 use warnings;
  10         28  
  10         271  
7              
8 10     10   50 use Carp;
  10         29  
  10         888  
9 10     10   11315 use Data::Dumper;
  10         110604  
  10         6701  
10              
11             my $PKG = __PACKAGE__;
12             my $MODE = '>>'; # By default we append
13             my $UCASE = 1; # By default we force types to upper case
14             my $TYPES = undef;
15              
16             # Constructor
17             sub open {
18 10     10 1 1101 my $class = shift;
19              
20             # Catch an object call
21 10   33     76 $class = ref $class || $class;
22              
23 10         73 return bless _init({@_}), $class;
24             }
25              
26             # Initialize our params
27             sub _init {
28 10     10   17 my $args = shift;
29 10         20 my $fh;
30              
31 10 100       43 unless ($args->{'file'}) {
32 1         191 croak "$PKG: Must supply file: Log::Dynamic->open(file => 'foo')";
33             }
34              
35             # Override append mode to clobber mode if requested
36 9 100 100     96 if (defined $args->{'mode'} && $args->{'mode'} =~ m/^clobber$/) {
37 2         7 $MODE = '>';
38             }
39              
40             # Override ucase mode if requested
41 9 50 66     56 if (exists $args->{'ucase'} && !$args->{'ucase'}) {
42 0         0 $UCASE = 0;
43             }
44              
45 9         64 _init_types($args->{'types'},$args->{'invalid_type'});
46              
47 9 50       55 if ($args->{'file'} =~ /STD(?:OUT|ERR)/i) {
48 0         0 $fh = uc $args->{'file'};
49             } else {
50 9 50       5376020 CORE::open($fh, $MODE, $args->{'file'})
51             or croak "$PKG: Failed to open file '$args->{file}': $!";
52             }
53              
54 9         75 return \$fh;
55             }
56              
57             # Type initialization was a large enough chunk of code that
58             # I felt it should be pulled into its own subroutine.
59             sub _init_types {
60 9   100 9   46 my $types = shift || return;
61 2   100     11 my $handler = shift || \&_invalid_type;
62              
63             # If provided the invalid type handler must be a coderef
64 2 50       8 croak "$PKG: Value for the 'invalid_type' param must be a code ref"
65             unless ref $handler eq 'CODE';
66              
67             # A Smudge of error checking. Non-empty array ref please
68 2 50       6 croak "$PKG: Value for the 'types' param must be an array ref"
69             unless ref $types eq 'ARRAY';
70              
71 2         7 croak "$PKG: Value for the 'types' param must not be an empty list"
72 2 50       3 unless @{ $types };
73              
74             # We have types! Make a hash of types for easy lookup.
75             # Note that we explicitly register dump here as a valid
76             # type. If we didn't the sub that validates types would
77             # crap out when calling dump(). Seems a bit hacky, I know...
78             # but for the time being I think this solution is OK.
79 2         3 $TYPES = { map { $_ => 1 } @{ $types }, 'dump' };
  4         13  
  2         4  
80              
81             # Store our invalid type handler.
82 2         8 $TYPES->{'_handle_invalid'} = $handler;
83             }
84              
85             # For those of you that decide you want to use the standard
86             # constructor notation of new(), here you go.
87 1     1 0 825 sub new { shift->open(@_) }
88              
89             # O'hai. Sry, we closing...
90 16     16 1 1250 sub close { close ${(shift)} }
  16         2162  
91              
92             # Base logging function
93             sub log {
94 9     9 1 206 my $fh = shift; # File handle reference
95 9   50     41 my $type = shift || return; # Message type, REQUIRED
96 9   100     31 my $msg = shift || return; # Message body, REQUIRED
97 8         1110 my $time = scalar localtime; # Formatted timestamp
98              
99 8         41 _validate_type($type);
100              
101             # Formatted caller info. Because custom types are essentially
102             # wrapper functions for log() we need to check up one more
103             # level to get the correct caller information.
104 24 100       253 my $call = join(' ',
105             map {
106 8         29 (caller(1))[$_] # Called using $log->[custom type]()
107             || # - OR -
108             (caller(0))[$_] # Called using $log->log()
109             } 0..2
110             );
111              
112             # Output formatted log entry. We turn off strict refs so that
113             # we can print to STDERR and STDOUT witout perl spitting an
114             # error and dying.
115 10     10   101 no strict 'refs';
  10         42  
  10         3876  
116 8 100       70 print {$$fh} "$time [".($UCASE?uc($type):$type)."] $msg ($call)\n";
  8         218  
117             }
118              
119             sub dump {
120 1     1 1 28 my $log = shift;
121 1         3 my $args = shift;
122              
123 1 50       8 unless (ref $args) {
124 0         0 carp "$PKG: dump() requires a hash ref of arg=>value pairs";
125 0         0 return;
126             }
127              
128 1 50       7 unless ($args->{'data'}) {
129 0         0 carp "$PKG: dump() requires the 'data' argument be supplied";
130 0         0 return;
131             }
132              
133             # Set defaults
134 1   50     12 $args->{'dump_type'} ||= 'dump';
135 1   50     8 $args->{'dump_name'} ||= 'anonymous data';
136 1   50     9 $args->{'begin_msg'} ||= 'BEGIN dump for:';
137 1   50     50 $args->{'end_msg' } ||= 'END dump for:';
138              
139 1         14 $log->log($args->{'dump_type'},
140             $args->{'begin_msg'} . " '$args->{dump_name}'\n" .
141             Dumper($args->{'data'}) .
142             $args->{'end_msg'} . " '$args->{dump_name}'"
143             );
144             }
145              
146             sub AUTOLOAD {
147 8     8   1693 my $log = shift;
148 8         15 my $type = (our $AUTOLOAD = $AUTOLOAD);
149              
150 8 50       30 return if $type =~ /::DESTROY$/;
151 8         59 $type =~ s/.*::(.+)$/$1/;
152              
153 8         23 _validate_type($type);
154              
155             # Define a subroutine for our new type. Since this new
156             # sub just turns around and calls log() with a set value
157             # for the $type variable you can probably lable this a
158             # form of function currying. Weeeeee =)
159             {
160 10     10   57 no strict;
  10         20  
  10         326  
  6         9  
161 10     10   61 no warnings;
  10         17  
  10         2451  
162 6     0   50 *$type = sub { shift->log($type,@_) };
  0         0  
163             }
164              
165             # Log with our new type
166 6         46 $log->log($type,@_);
167             }
168              
169             # Chage the ucase value
170 1   50 1 1 7 sub ucase { shift;$UCASE = shift || 0}
  1         7  
171              
172             # Valid log type
173             sub _validate_type {
174 16     16   50 my $type = shift;
175              
176 16 100 100     87 if (defined $TYPES and not $TYPES->{$type}) {
177 2         6 $TYPES->{'_handle_invalid'}->($type);
178             }
179             }
180              
181             sub _invalid_type {
182 1     1   2 my $type = shift;
183 1         191 croak "$PKG: Type '$type' was not specified as a valid type";
184             }
185              
186             # Cleanup... Just close the file handle
187 9     9   9186 sub DESTROY { shift->close }
188              
189             1;
190              
191             __END__