File Coverage

blib/lib/Package/Base/Devel.pm
Criterion Covered Total %
statement 97 106 91.5
branch 28 38 73.6
condition 17 30 56.6
subroutine 16 16 100.0
pod 6 6 100.0
total 164 196 83.6


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             Package::Base::Devel - Perl extension for blah blah blah
4              
5             =head1 SYNOPSIS
6              
7              
8             #don't use this module directly, but rather inherit from it.
9             package My::Package;
10             use base qw(Package::Base::Devel);
11              
12             #... see Package::Base for details on the new() and init() methods
13             #that are inherited by Package::Base::Devel.
14              
15             #it isn't necessary to use new() or init() in your class hierarchy,
16             #Package::Base::Devel is smart enough to initialize itself the first
17             #time you call an inherited method.
18              
19             =head1 DESCRIPTION
20              
21             Provides the the same base functionality as Package::Base, but
22             additionally provides automatic setup of Log::Log4perl loggging.
23              
24             =head1 TRAPPED SIGNALS: warn() AND die().
25              
26             Package::Base::Devel traps $SIG{__WARN__} and $SIG{__DIE__}. This is
27             so we can log warn() and die() calls and what package they come from. After
28             trapping the signal the default warn() and die() methods (CORE::warn() and
29             CORE::die(), respectively) are called.
30              
31             This signal trapping may not play nicely with other modules that also alter
32             Perl's default exception handling behaviour. If you find this to be the
33             case, please let me know, I do not use and have not tested against these
34             other modules. If there is a way to detect signal handling clashes, please
35             also let me know and I will modify this module appropriately.
36              
37             =head1 CUSTOMIZED LOGGING
38              
39             When the first instance of a class inheriting from Package::Base::Devel
40             is instantiated, a Log::Log4perl::Logger is created. This uses a call
41             to Log::Log4perl->init() for initialization of the logging object. The
42             default Log::Log4perl configuration template used for this by
43             Package::Base::Devel is:
44              
45             log4perl.logger.%s = DEBUG, %s
46             log4perl.appender.%s = Log::Log4perl::Appender::Screen
47             log4perl.appender.%s.stdout = 0
48             log4perl.appender.%s.stderr = 1
49             log4perl.appender.%s.layout = Log::Log4perl::Layout::PatternLayout
50             log4perl.appender.%s.layout.ConversionPattern =[%%d{HH:mm:ss}] %%-5p - %%-30c %%40M() in file .../%%F{2} (%%4L): %%m%%n
51              
52             A few comments on the above configuration stanza:
53              
54             * Notice there are B (seven) %s strings. If you guessed that this
55             stanza is interpreted using an sprintf() call, you are right. The %s are
56             replaced by a mangled version of the namespace of the package for which logging
57             is being set up.
58              
59             * Notice the word DEBUG. This is the default logging level for any
60             class set up with this template. See L for a listing of valid
61             strings here.
62              
63             * This string can have as many %s slots as you like, so you have great flexibility
64             as to how you want to set up your configuration stanza. This string must contain at
65             B %s, as it is the minimum necessary for a valid configuration stanza.
66              
67             To customize the default configuration stanza, just update
68             $Package::Base::Devel::log4perl_template to something else recognizable by
69             Log::Log4perl.
70              
71             B: Any time this string is changed I the first instantiation
72             of a class inheriting from Package::Base::Devel, the instantiation event will
73             cause a full reinitialization of Log::Log4perl using this template for all
74             classes. Therefore it is recommended you set this template once in package main,
75             startup.pl if you're using mod_perl, or in some other high-level package. This
76             will prevent multiple reinitializations of the loggers, and potentially unintended
77             results. Consider the following code:
78              
79             package main;
80             use My::Class::A; #which inherits from Package::Base::Devel
81             use My::Class::B; #also inherits...
82              
83             my $a = My::Class::A->new(some => 'args');
84              
85             $Package::Base::Devel::log4perl_template = "this is an invalid log4perl stanza";
86              
87             $a->log->info('this info STILL goes to the logger with the default config, no problem');
88              
89             my $b = My::Class::B->new(some => 'args');
90             #the loggers were reinitialized with the updated template.
91             $b->log->info('this message fails');
92             $a->log->info('this message also fails');
93              
94             The main thing you're likely to want to change at runtime is the log level. See
95             L or L to learn about log levels. To accomodate this,
96             Package::Base::Devel provides the L shortcut function that allows calls like:
97              
98             my $a1 = My::Class::A->new();
99             my $a2 = My::Class::A->new();
100              
101             $a1->log->debug('debugging message'); #goes to log, DEBUG is the default log level, remember?
102             $a2->log->debug('this goes through to the logger also');
103              
104             $a1->loglevel('FATAL'); #only show fatal messages
105             $a1->log->debug('this does not go through, log level too high');
106             $a2->log->debug('calls from $a2 are now also blocked, '.
107             'as the logger is effectively a class instance');
108              
109             =head1 AUTHOR
110              
111             Allen Day, Eallenday@ucla.eduE
112              
113             =head1 SEE ALSO
114              
115             L.
116              
117             =cut
118              
119             package Package::Base::Devel;
120              
121 2     2   1809 use strict;
  2         4  
  2         71  
122 2     2   10 use base qw(Package::Base);
  2         3  
  2         1230  
123 2     2   14 use Data::Dumper;
  2         6  
  2         102  
124 2     2   2971 use Log::Log4perl qw(get_logger);
  2         145812  
  2         16  
125 2     2   156 use Log::Log4perl::Level;
  2         4  
  2         13  
126 2     2   246 use Carp qw(cluck);
  2         4  
  2         3255  
127              
128             our $VERSION = '0.01';
129              
130             #trap signals
131             $SIG{__DIE__} = \&_die;
132             $SIG{__WARN__} = \&_warn;
133              
134             our %logconfig = (''=>'');
135             our %black = map {$_=>1} qw(
136             Carp
137             ExtUtils::MakeMaker
138             Log::Log4perl::Logger
139             Package::Install
140             ); #don't trap signals from these
141              
142             our %level = ( #these scalars are exported by Log::Log4perl::Level
143             OFF => $OFF,
144             FATAL => $FATAL,
145             ERROR => $ERROR,
146             WARN => $WARN,
147             INFO => $INFO,
148             DEBUG => $DEBUG,
149             ALL => $ALL,
150             );
151              
152             our $log4perl_template = <<_HERE_;
153             log4perl.logger.%s = DEBUG, %s
154             log4perl.appender.%s = Log::Log4perl::Appender::Screen
155             log4perl.appender.%s.stdout = 0
156             log4perl.appender.%s.stderr = 1
157             log4perl.appender.%s.layout = Log::Log4perl::Layout::PatternLayout
158             log4perl.appender.%s.layout.ConversionPattern =[%%d{HH:mm:ss}] %%-5p - %%-30c %%40M() in file .../%%F{2} (%%4L): %%m%%n
159             _HERE_
160              
161             =head1 METHODS
162              
163             =head2 new()
164              
165             Identical functionality to Package::Base::new().
166              
167             =cut
168              
169             sub new {
170 6     6 1 635 my($class,%arg) = @_;
171              
172 6 100       24 if($class eq __PACKAGE__){
173 1         233 cluck( __PACKAGE__." is an abstract base class, and not directly instantiable" );
174 1         4 return undef;
175             }
176              
177 5         37 my $self = $class->SUPER::new(%arg);
178             #superclass calls init() for us
179 5         25 return $self;
180             }
181              
182             =head2 is_initialized()
183              
184             Usage : $boolean = $object->is_initialized();
185             Returns : true or false
186             Args : none
187             Function: check to see if init() has been called or not.
188              
189             =cut
190              
191             sub is_initialized {
192 55     55 1 250 return shift->{'__PackageBaseDevel_init'};
193             }
194              
195             =head2 init()
196              
197             Usage : $object->init(key1 => 'value1', key2 => 'value2');
198             Returns : a reference to the calling object
199             Args : an anonymous hash of object attribute/value pairs.
200             Function: uses anonymous hash parameters to initialize object just as
201             Package::Base. See Package::Base::init() for details.
202              
203             Additionally sets up a Log::Log4perl configuration and logger
204             instance for the object's class (ref($object)) if a Log::Log4perl
205             configuration does not already exist. The Log::Log4perl
206             configuration template is customizable by setting
207             $Package::Base::Devel::log4perl_template, See L.
208              
209             =cut
210              
211             sub init {
212 5     5 1 12 my($self,%arg) = @_;
213 5         23 $self->{'__PackageBaseDevel_init'} = 1;
214              
215 5         36 $self->SUPER::init(%arg);
216              
217             {
218 5         6 my $tmp1 = $Data::Dumper::Maxdepth;
  5         9  
219 5         7 my $tmp2 = $Data::Dumper::Terse;
220 5         6 $Data::Dumper::Maxdepth = 2;
221 5         9 $Data::Dumper::Indent = 1;
222 5 100       30 my $dump = join "\n", grep {$_ if $_ !~ /[\{\}]/} split("\n",Data::Dumper::Dumper(\%arg));
  15         478  
223 5 100       51 my $argstring = keys(%arg) ? ". shallow dump:\n".$dump : '.';
224 5         26 $self->log->info("constructed a new ".ref($self)." object".$argstring);
225 5         12064 $Data::Dumper::Maxdepth = $tmp1;
226 5         13 $Data::Dumper::Terse = $tmp2;
227             }
228              
229              
230             # $self->log();
231 5         18 return $self;
232             }
233              
234             =head2 log()
235              
236             Usage : $object->log->debug('some debugging message');
237             $object->log->info('some info');
238             $object->log->warn('something bad happened');
239             $object->log->fatal('something really bad happened');
240             Returns : a Log::Log4perl::Logger instance
241             Args : none
242             Function: This gives access to the $object's logging instance.
243             L for more details. See L
244             for additional info on how to affect logging behavior
245             for your subclass.
246              
247             =cut
248              
249             sub log {
250 44     44 1 1751 my($self) = @_;
251 44 100 66     160 $self->init() if ref($self) && !$self->is_initialized();
252              
253             #warn get_logger(ref($self)||$self);
254             #warn get_logger("");
255              
256 44   33     105 my $class = ref($self) || $self;
257              
258 44 100       116 $logconfig{$class} = '' if !defined($logconfig{$class});
259              
260 44 100       117 if($logconfig{$class} eq $logconfig{''}){
261 4         12 $self->logconfig( _mangle_package($class) );
262             }
263              
264 44         114 my $logger = get_logger($class);
265 44         1030 return $logger;
266             }
267              
268             =head2 logconfig()
269              
270             Usage : $object->logconfig('a Log4perl configuration stanza');
271             Returns : the configuration stanza for $object
272             Args : a Log4perl configuration stanza. L for details
273             Function: This method allows you to set the configuration parameters for
274             your subclass on a fine-grained level. You can call this method
275             at any time, and all logged packages will have their configurations
276             reloaded.
277              
278             I've created a shortcut method, L to easily update the
279             Log::Log4perl::Logger instance's verbosity level() -- it's a lot
280             easier than rewriting the configuration stanza. If you would like
281             easy access to other logger methods let me know and I'll add them.
282              
283             =cut
284              
285             sub logconfig {
286 8     8 1 19 my($self,$stanza) = @_;
287 8 100 100     40 $self->init() if ref($self) && !$self->is_initialized();
288              
289 8   66     40 my $class = ref($self) || $self;
290              
291 8         15 my $changed = 0;
292              
293             #these declarations silence complaints
294 8   100     34 $stanza ||= '';
295 8   50     20 my $mangle = _mangle_package($class) || '';
296 8   50     45 my $lcf = $logconfig{$class} || '';
297              
298             #initialize the logger, stanza doesn't exist for this package yet
299 8 100 33     67 if($stanza eq $mangle){
    50 33        
300 4         5 $changed = 1;
301 4         7 my $pack = $class;
302 4         16 $pack =~ s/::/./g;
303 4         7 my $name = _mangle_package($class);
304 4         8 my $logformat = $log4perl_template;
305              
306             #warn "********".$logformat;
307              
308 4         33 my @i = $logformat =~ /%s/gs;
309 4         10 my $i = scalar(@i);
310 4 50       15 die qq(invalid \$Package::Base::Devel::log4perl_template must contain at least 2 '%s', contains only $i.) if $i < 2;
311 4         8 my @slots = ();
312 4         37 push @slots, $name for 1..$i-1;
313              
314 4         76 $stanza = sprintf($logformat, $pack, @slots );
315              
316             #got a new stanza, reinitialize the logger
317             } elsif(defined($stanza) and !ref($stanza) and $stanza ne $lcf){
318              
319 0         0 $changed = 2;
320             }
321              
322 8 100       44 if($changed){
    50          
323 4         10 $logconfig{$class} = $stanza;
324 4         34 my $all = join "\n", values %logconfig;
325 4         26 Log::Log4perl->init(\$all);
326              
327 4 50       20752 if($changed == 1){
    0          
328 4         19 get_logger($class)->info("created logger for ".$class);
329             } elsif($changed == 2){
330 0         0 get_logger($class)->info("recreated logger for ".$class);
331             } else {
332 0         0 get_logger($class)->info("what does $changed mean? ".$class);
333             }
334              
335             } elsif(!Log::Log4perl->initialized){
336 4         31 Log::Log4perl->init(); #FIXME
337             }
338              
339 4         2208 return $logconfig{$class};
340             }
341              
342             =head2 loglevel()
343              
344             Usage : $self->loglevel('FATAL'); #only log fatal messages
345             Returns :
346             Args : a level string. valid values, in order of ascending verbosity:
347             OFF, FATAL, ERROR, WARN, INFO, DEBUG, ALL
348             Function: adjust Log::Log4perl::Logger logging level
349              
350              
351             =cut
352              
353             sub loglevel {
354 7     7 1 16 my ($self,$level) = @_;
355 7 50 33     31 $self->init() if ref($self) && !$self->is_initialized();
356              
357 7 50       20 if(defined($level{$level})){
    0          
358 7         14 $self->log->level($level{$level});
359             } elsif(defined($level)) {
360 0         0 $self->log->error("Log level of '$level' is not valid, no action taken. Valid values are: ".join(" ",keys %level));
361 0         0 return undef;
362             }
363              
364 7         4551 foreach my $key (keys %level){
365 28 100       182 return $key if $level{$key} == $self->log->level(); #stringify the numeric level
366             }
367              
368             #unrecognized numeric levels get here. should never happen
369 0         0 $self->log->error("unexpected log level ".$self->log->level.". please inform the author of ".__PACKAGE__);
370 0         0 return $self->log->level;
371             }
372              
373             =head2 _mangle_package()
374              
375             Usage : An internal method, not intended to be called
376             externally.
377             Returns : A mangled package string
378             Args : A package name
379             Function: this is an internal utility method for generating
380             valid Log::Log4perl::Logger names
381              
382             =cut
383              
384             sub _mangle_package {
385 16     16   30 my $pack = shift;
386 16         53 $pack =~ s/::/_/g;
387 16         69 return $pack;
388             }
389              
390             =head2 _make_logger()
391              
392             An internal method. Returns a Log::Log4perl::Logger instance
393              
394             =cut
395              
396             sub _make_logger {
397 6     6   14 my $pack = shift;
398 6 100 66     44 if(!$logconfig{$pack} and !$black{$pack}){
399 4         12 logconfig($pack);
400             }
401 2         14 return get_logger($pack);
402             }
403              
404             =head1 INTERNAL UTILITY METHODS FOR LOGGING AND DIE/WARN TRAPPING
405              
406             =head2 _die()
407              
408             Usage : This is an internal method, not intended to be called
409             externally.
410             Function: logs a fatal message and calls CORE::die().
411             Package::Base::Devel signal-traps die() calls to log
412             them before really dying (via CORE::die()).
413             Returns : never. program exits.
414             Args : An epitaph for your program
415              
416             =cut
417              
418             sub _die {
419 4     4   229 my $pack = (caller())[0];
420              
421 4         14 my $logger = _make_logger($pack);
422 0         0 $logger->fatal(@_);
423              
424 0         0 CORE::die @_; # Now terminate really
425             };
426              
427             =head2 _warn()
428              
429             Usage : This is an internal method, not intended to be called
430             externally.
431             Function: logs a warning message and calls CORE::warn().
432             Package::Base::Devel signal-traps warn() calls to log
433             them before really warning (via CORE::warn()).
434             Returns : 1
435             Args : A warning message for your program.
436              
437             =cut
438              
439             sub _warn {
440 2     2   623 my $pack = (caller())[0];
441              
442 2         5 $Log::Log4perl::caller_depth++;
443              
444 2         9 my $logger = _make_logger($pack);
445 2         328 $logger->warn(@_);
446 2         863 CORE::warn @_; # Now do the real warning
447 2         5 $Log::Log4perl::caller_depth--;
448 2         10 return 1;
449             };
450              
451             1;
452             __END__