File Coverage

blib/lib/Log/Channel.pm
Criterion Covered Total %
statement 28 171 16.3
branch 0 54 0.0
condition 0 20 0.0
subroutine 10 40 25.0
pod n/a
total 38 285 13.3


line stmt bran cond sub pod time code
1             package Log::Channel;
2              
3             =head1 NAME
4              
5             Log::Channel - yet another logging package
6              
7             =head1 SYNOPSIS
8              
9             use Log::Channel;
10             my $log = new Log::Channel("topic");
11             sub mylog { $log->(@_) }
12              
13             $log->("this is a log message, by default going to stderr\n");
14             mylog "this is the same as the above\n";
15             mylog sprintf ("Hello, %s\n", $user);
16              
17             decorate Log::Channel "topic", "%d: (%t) %m\n";
18             mylog "this msg will be prefixed with 'timestamp: (topic) ' and end with newline";
19              
20             use Log::Dispatch::File;
21             Log::Channel::dispatch("topic",
22             new Log::Dispatch::File(name => 'file1',
23             min_level => 'info',
24             filename => 'foo.log',
25             mode => 'append'
26             ));
27             mylog "now the message, with decorations, will go to the file, but not stderr";
28              
29             =head1 DESCRIPTION
30              
31             Allows for code to specify channels for delivery of logging messages,
32             and for users of the code to control the delivery and formatting of
33             the messages.
34              
35             Yes, this is yet another logging module. Some differences between
36             Log::Channel and other logging packages:
37              
38             =over 4
39              
40             =item *
41              
42             Log::Channel does not define a strict classification of logging "levels"
43             (debug, info, warn, crit, etc.). You may define a priority for a channel
44             using set_priority(), but this is optional, and you can name your priority
45             levels anything you want.
46              
47             =item *
48              
49             Able to take over carp and croak events from other modules and route the
50             output according to the Log::Channel configuration.
51              
52             =back
53              
54             =head1 CONFIGURATION
55              
56             If $ENV{LOG_CHANNEL_CONFIG} is set, then this is taken as the name of a
57             file containing log configuration instructions which will be loaded the
58             first time a Log::Channel is created. Config file syntax is XML:
59              
60            
61             /home/user/configs/log_disp.conf
62            
63             One
64            
65             %t %d: %m
66             stderr
67            
68            
69             Two::Three
70             Log::Dispatch
71             crit
72            
73            
74             Four
75            
76             Log::Dispatch
77             crit
78            
79            
80              
81             =over 4
82              
83             =item *
84              
85             If is omitted, logging defaults to STDERR.
86              
87             =item *
88              
89             Logging defaults on for all topics without an explicit or
90             directive. Omitted topics default on as well.
91              
92             =item *
93              
94             To use Log::Dispatch for message dispatch,
95             specify Log::Dispatch for . If a filename is
96             specified in , then the Log::Dispatch module will be
97             configured from that file (see Log::Dispatch::Config), otherwise
98             Log::Dispatch must be initialized explicitly
99              
100             =back
101              
102             =head1 METHODS
103              
104             =over 4
105              
106             =cut
107              
108 11     11   223400 use strict;
  11         28  
  11         453  
109 11     11   54 use vars qw($VERSION);
  11         20  
  11         760  
110             $VERSION = '0.7';
111              
112 11     11   10307 use Log::Dispatch;
  11         327475  
  11         370  
113 11     11   12797 use POSIX qw(strftime);
  11         641484  
  11         81  
114              
115             my %Channel;
116             my %Config_by_channel;
117              
118             my $Configuration;
119              
120             =item B
121              
122             my $log_coderef = new Log::Channel "topic";
123              
124             Define a new channel for logging messages. All new logs default to
125             output to stderr. Specifying a dispatcher (see dispatch method below)
126             will override this behavior. Logs default active, but can be disabled.
127              
128             Note that the object returned from the constructor is a coderef,
129             not the usual hashref. This seems to me to be an appropriate use
130             of closures.
131              
132             The channel will remember the topic specified when it was
133             created, prepended by the name of the current package.
134              
135             Suggested usage is
136              
137             sub logme { $log_coderef->(@_) }
138              
139             So that you can write logging entries like
140              
141             logme "This is the message\n";
142              
143             If omitted, topic will default to the name of the current package. A
144             channel must have something for the topic, so the parameter is required
145             for channels created in the main package.
146              
147             =cut
148              
149             sub new {
150 0     0     my $proto = shift;
151 0   0       my $class = ref ($proto) || $proto;
152              
153 0 0         if (!$Configuration) {
154 0           $Configuration = new Log::Channel::Config;
155             }
156              
157 0           my $package = (caller)[0];
158 0 0         if ($package ne "main") {
159 0           unshift @_, $package;
160             }
161 0 0         if (!$Channel{$package}) {
162             # make sure channel exists for the entire package
163 0           $class->_make($package);
164             }
165              
166 0           return $class->_make(@_);
167             }
168              
169             sub _make {
170 0     0     my $proto = shift;
171 0   0       my $class = ref ($proto) || $proto;
172              
173 0           my $topic = join("::", @_);
174 0 0         die "Missing topic for Log::Channel->new" unless $topic;
175              
176 0           my $existing_channel = $Channel{$topic}->{channel};
177 0 0         return $existing_channel if $existing_channel;
178              
179 0           my $config = _config($topic);
180              
181 0           my $self = _makesub($class, $config);
182 0           bless $self, __PACKAGE__;
183              
184 0           $config->{channel} = $self;
185 0           $Channel{$topic} = $config;
186 0           $Config_by_channel{$self} = $config;
187              
188 0 0         $Configuration->configure($config) if $Configuration;
189              
190 0           return $self;
191             }
192              
193             sub _config {
194             # Assumes that caller has verified that there is not an existing
195             # channel for this topic.
196              
197 0     0     my %config;
198 0           $config{topic} = shift;
199              
200 0           return \%config;
201             }
202              
203             sub _makesub {
204 0     0     my ($class, $config) = @_;
205              
206 0           *sym = "${class}::_transmit";
207 0           my $transmit = *sym{CODE};
208              
209             return
210             sub {
211 0 0   0     return if $config->{disabled};
212              
213 0           my $dispatchers = $config->{dispatchers};
214 0 0         if ($dispatchers) {
215 0           foreach my $dispatcher (@$dispatchers) {
216 0   0       $dispatcher->log(level => $config->{priority} || "info",
217             message => _construct($config, @_));
218             }
219             } else {
220 0           $transmit->($config, _construct($config, @_));
221             }
222 0           };
223             }
224              
225             =item B
226              
227             disable Log::Channel "topic";
228              
229             No further log messages will be transmitted on this topic. Any
230             dispatchers configured for the channel will not be closed.
231              
232             A channel can be disabled before it is created.
233              
234             Recursively disables sub-topics.
235              
236             =cut
237              
238             sub disable {
239 0 0   0     shift if $_[0] eq __PACKAGE__;
240              
241 0           my ($topic, $channel_config) = _topic(@_);
242              
243 0           _recurse ($topic, \&disable);
244              
245 0           $channel_config->{disabled} = 1;
246             }
247              
248             =item B
249              
250             enable Log::Channel "topic";
251              
252             Restore transmission of log messages for this topic. Any dispatchers
253             configured for the channel will start receiving the new messages.
254              
255             A channel can be enabled before it is created.
256              
257             Recursively enables sub-topics.
258              
259             =cut
260              
261             sub enable {
262 0 0   0     shift if $_[0] eq __PACKAGE__;
263              
264 0           my ($topic, $channel_config) = _topic(@_);
265              
266 0           _recurse ($topic, \&enable);
267              
268 0           $channel_config->{disabled} = 0;
269             }
270              
271             =item B
272              
273             Log::Channel::commandeer ([$package, $package...]);
274              
275             Take over delivery of 'carp' log messages for specified packages. If
276             no packages are specified, all currently-loaded packages will be
277             commandeered.
278              
279             When a package is taken over in this fashion, messages generated via
280             'carp', 'croak' and so on will be delivered according to the active
281             dispatch instructions. Remember, Log::Channel defaults all message
282             delivery to OFF.
283              
284             Note that the Carp verbose setting should still work correctly.
285              
286             =cut
287              
288             sub commandeer {
289 0 0   0     shift if $_[0] eq __PACKAGE__;
290              
291 0           local $^W = 0; # hide 'subroutine redefined' messages
292              
293 0 0         if (!@_) {
294             # commandeer ALL active modules
295 0           _commandeer_package ("main");
296             } else {
297 0           foreach my $package (@_) {
298 0           _commandeer_package ($package);
299             }
300             }
301             }
302              
303             sub _commandeer_package {
304 0     0     my ($package) = shift;
305              
306 11     11   24902 no strict 'refs';
  11         1323  
  11         31397  
307              
308             # The subroutine-override code here was cribbed from Exporter.pm
309              
310 0           *{"$package\::carp"} = \&{__PACKAGE__ . '::_carp'};
  0            
  0            
311 0           *{"$package\::croak"} = \&{__PACKAGE__ . '::_croak'};
  0            
  0            
312              
313             # Recurse through all sub-packages and commandeer carp in each case.
314             # The package-detection code here was taken from Devel::Symdump.
315              
316 0           while (my ($key,$val) = each %{*{"$package\::"}}) {
  0            
  0            
317 0           local *sym = $val;
318 0 0 0       if (defined $val
      0        
      0        
      0        
319             && defined *sym{HASH}
320             && $key =~ /::$/
321             && $key ne "main::"
322             && $key ne "::") {
323 0           my $subpkg = "$package\::$key";
324 0           $subpkg =~ s/::$//;
325 0           _commandeer_package($subpkg);
326             }
327             }
328             }
329              
330             =item B<_carp>
331              
332             This is the function that is used to supersede the regular Carp::carp
333             whenever Carp is commandeered on a module. Note that we still use
334             Carp::shortmess to generate the actual text, so that if Carp verbose mode
335             is specified, the full verbose text will go to the log channel.
336              
337             =cut
338              
339             sub _carp {
340 0     0     my $topic = (caller)[0];
341              
342 0           my $channel = $Channel{$topic}->{channel};
343 0 0         $channel = Log::Channel->_make($topic) unless $channel;
344              
345 0           $channel->(Carp::shortmess @_);
346             }
347              
348             =item B<_croak>
349              
350             Substitute for Carp::croak. Note that in this case the message will
351             be output to two places - the channel, and STDERR (or whatever die() does).
352              
353             =cut
354              
355             sub _croak {
356 0     0     my $topic = (caller)[0];
357              
358 0           my $channel = $Channel{$topic}->{channel};
359 0 0         $channel = Log::Channel->_make($topic) unless $channel;
360              
361 0           $channel->(Carp::shortmess @_);
362 0           die Carp::shortmess @_;
363             }
364              
365              
366             =item B
367              
368             decorate Log::Channel "topic", "decoration-string";
369              
370             Specify the prefix elements that will be included in each message
371             logged to the channel identified by "topic". The formatting options
372             have been modeled on the log4j system. Options include:
373              
374             %t - channel topic name
375             %d{format} - current timestamp; defaults to 'scalar localtime', but
376             if an optional strftime format may be provided
377             %F - filename where the log message is generated from
378             %L - line number
379             %p - priority string for this channel (see set_priority)
380             %x - context string for this channel (see set_context)
381             %m - log message text
382              
383             Any other textual elements will be transmitted verbatim, eg.
384             e.g. "%t: %m", "(%t) [%d] %m\n", etc.
385              
386             Comment on performance: I haven't benchmarked the string formatting
387             here. s///egx might not be the fastest way to do this.
388              
389             =cut
390              
391             sub decorate {
392 0 0   0     shift if $_[0] eq __PACKAGE__;
393 0           my $decorator = pop;
394 0           my ($topic, $channel_config) = _topic(@_);
395              
396 0           $channel_config->{decoration} = $decorator;
397             }
398              
399              
400             =item B
401              
402             set_context Log::Channel "topic", $context;
403              
404             Associate some information (a string) with a log channel, specified
405             by topic. This string will be included in log messages if the 'context'
406             decoration is activated.
407              
408             This is intended for when messages should include reference info that
409             changes from call to call, such as a current session id, user id,
410             transaction code, etc.
411              
412             =cut
413              
414             sub set_context {
415 0 0   0     shift if $_[0] eq __PACKAGE__;
416 0           my $context = pop;
417 0           my ($topic, $channel_config) = _topic(@_);
418              
419 0           $channel_config->{context} = $context;
420             }
421              
422              
423             # copying the decorator formats from http://jakarta.apache.org/log4j
424              
425             my %decorator_func = (
426             "t" => \&_decorate_topic,
427             "d" => \&_decorate_timestamp,
428             "m" => \&_decorate_message,
429             "F" => \&_decorate_filename,
430             "L" => \&_decorate_lineno,
431             "p" => \&_decorate_priority,
432             "x" => \&_decorate_context,
433             );
434              
435             sub _decorate_topic {
436 0     0     my ($config, $format, $textref) = @_;
437 0           return $config->{topic};
438             }
439             sub _decorate_timestamp {
440 0     0     my ($config, $format, $textref) = @_;
441 0 0         return scalar localtime if !$format;
442 0           return strftime $format, localtime;
443             }
444             sub _decorate_message {
445 0     0     my ($config, $format, $textref) = @_;
446 0           return join("", @$textref);
447             }
448             sub _decorate_filename {
449 0     0     my ($config, $format, $textref) = @_;
450 0           return (caller(3+$format))[1];
451             }
452             sub _decorate_lineno {
453 0     0     my ($config, $format, $textref) = @_;
454 0           return (caller(3+$format))[2];
455             }
456             sub _decorate_priority {
457 0     0     my ($config, $format, $textref) = @_;
458 0           return $config->{priority};
459             }
460             sub _decorate_context {
461 0     0     my ($config, $format, $textref) = @_;
462 0           return $config->{context};
463             }
464              
465             sub _construct {
466 0     0     my ($config) = shift;
467              
468 0 0         my $decoration = $config->{decoration} or return join("", @_);
469              
470 0           $decoration =~
471             s/
472             %((.)(\{([^\}]+)\})*) # decorator directive can have a format string
473             /
474 0           $decorator_func{$2}->($config, $4, \@_);
475             /egx;
476              
477 0           return $decoration;
478             }
479              
480              
481             # internal method - default output destination in stderr
482              
483             sub _transmit {
484 0     0     my ($config) = shift;
485              
486 0           print STDERR @_;
487             }
488              
489              
490             =item B
491              
492             dispatch Log::Channel "topic", (new Log::Dispatch::Xyz(...),...);
493              
494             Map a logging channel to one or more Log::Dispatch dispatchers.
495              
496             Any existing dispatchers for this channel will be closed.
497              
498             Dispatch instructions can be specified for a channel that has not
499             been created.
500              
501             The only requirement for the dispatcher object is that it supports
502             a 'log' method. Every configured dispatcher on a channel will receive
503             all messages on that channel.
504              
505             =cut
506              
507             sub dispatch {
508 0 0   0     shift if $_[0] eq __PACKAGE__;
509 0           my ($topic, $channel_config) = _topic(shift);
510              
511             # input validation
512 0           foreach my $dispatcher (@_) {
513 0 0         _croak "Expected a Log::Dispatch object"
514             unless UNIVERSAL::can($dispatcher, "log");
515             }
516              
517 0           _recurse($topic, \&dispatch, @_);
518              
519 0           $channel_config->{dispatchers} = \@_;
520             }
521              
522             =item B
523              
524             undispatch Log::Channel "topic";
525              
526             Restore a channel to its default destination (ie. STDERR).
527              
528             Any existing dispatchers for this channel will be closed.
529              
530             =cut
531              
532             sub undispatch {
533 0 0   0     shift if $_[0] eq __PACKAGE__;
534 0           my ($topic, $channel_config) = _topic(shift);
535              
536 0           delete $channel_config->{dispatchers};
537 0           _recurse($topic, \&undispatch);
538             }
539              
540             =item B
541              
542             # if we need to be able to associate priority (debug, info, emerg, etc.)
543             # with each log message, this might be enough. It's by channel, though,
544             # not per message. Since the overhead of creating a channel is minimal,
545             # I prefer to associate one priority to all messages on the channel.
546             # This also means that a module developer doesn't have to specify the
547             # priority of a message - a user of the module can set a particular
548             # channel to a different priority.
549             # Valid priority values are not enforced here. These could potentially
550             # vary between dispatchers. UNIX syslog specifies one set of priorities
551             # (emerg, alert, crit, err, warning, notice, info, debug).
552             # The log4j project specifies a smaller set (error, warn, info, debug, log).
553             # Priority ranking is also controlled by the dispatcher, not the channel.
554              
555             =cut
556              
557             sub set_priority {
558 0 0   0     shift if $_[0] eq __PACKAGE__;
559 0           my $priority = pop;
560 0           my ($topic, $channel_config) = _topic(@_);
561              
562 0           $channel_config->{priority} = $priority;
563             }
564              
565              
566             =item B
567              
568             status Log::Channel;
569              
570             Return a blob of information describing the state of all the configured
571             logging channels, including suppression state, decorations, and dispatchers.
572              
573             Currently does nothing.
574              
575             =cut
576              
577             sub status {
578 0     0     return \%Channel;
579             }
580              
581              
582             # _recurse
583             #
584             # Call the specified function on the name of every sub-package
585             # in this package. Used to recursively apply constraints to
586             # sub-packages (disable, enable, commandeer).
587             #
588             sub _recurse {
589 0     0     my $package = shift;
590 0           my $coderef = shift;
591              
592 0           foreach my $topic (keys %Channel) {
593 0 0         if ($topic =~ /^$package\::/) {
594 0           $coderef->($topic, @_);
595             }
596             }
597             }
598              
599             sub _topic {
600 0     0     my ($topic, $channel_config);
601              
602 0 0         if (ref $_[0] eq __PACKAGE__) {
603             # invoked as $channel->disable
604 0           $channel_config = $Config_by_channel{$_[0]};
605 0           $topic = $channel_config->{topic};
606             } else {
607 0           $topic = join("::", @_);
608 0 0         die "Missing topic for Log::Channel->disable" unless $topic;
609 0           $channel_config = $Channel{$topic};
610 0 0         if (!$channel_config) {
611 0           Log::Channel->_make($topic);
612 0           $channel_config = $Channel{$topic};
613             }
614             }
615              
616 0           return ($topic, $channel_config);
617             }
618              
619             =item B
620              
621             $channel->export("subname");
622              
623             Exports a logging subroutine into the calling package's namespace.
624             Does the same thing as
625              
626             sub mylog { $channel->(@_) }
627              
628             =cut
629              
630             sub export {
631 0     0     my ($channel, $subname) = @_;
632              
633 0           my $package = (caller)[0];
634              
635 11     11   91 no strict 'refs';
  11         26  
  11         1244  
636              
637 0     0     *{"$package\::$subname"} = sub { $channel->(@_) };
  0            
  0            
638             }
639              
640             1;
641              
642             =back
643              
644             =head1 TO DO
645              
646             =over 4
647              
648             =item *
649              
650             Syntax-checking on decorator format strings.
651              
652             =item *
653              
654             Status reporting available for what log classes have been initiated,
655             activation status, and where the messages are going.
656              
657             =item *
658              
659             Ability to commandeer "print STDERR". To pick up other types of module
660             logging - and capture die() messages.
661              
662             =back
663              
664             =head1 AUTHOR
665              
666             Jason W. May
667              
668             =head1 COPYRIGHT
669              
670             Copyright (C) 2001,2002 Jason W. May. All rights reserved.
671             This module is free software; you can redistribute it and/or
672             modify it under the same terms as Perl itself.
673              
674             =head1 SEE ALSO
675              
676             Log::Dispatch and Log::Dispatch::Config
677             http://jakarta.apache.org/log4j
678              
679             And many other logging modules:
680             Log::Agent
681             CGI::Log
682             Log::Common
683             Log::ErrLogger
684             Log::Info
685             Log::LogLite
686             Log::Topics
687             Log::TraceMessages
688             Pat::Logger
689             POE::Component::Logger
690             Tie::Log
691             Tie::Syslog
692             Logfile::Rotate
693             Net::Peep::Log
694             Devel::TraceFuncs
695             Devel::TraceMethods
696             Log::AndError
697              
698             =cut
699              
700              
701             package Log::Channel::Config;
702              
703 11     11   55 use strict;
  11         24  
  11         317  
704              
705 11     11   48 use Carp;
  11         40  
  11         954  
706 11     11   150 use Log::Channel;
  11         16  
  11         640  
707 11     11   22164 use XML::Simple;
  0            
  0            
708             use Log::Dispatch::Config;
709              
710             sub new {
711             my $proto = shift;
712             my $class = ref ($proto) || $proto;
713              
714             my $config_file = $ENV{LOG_CHANNEL_CONFIG};
715             return if !$config_file; # this is not an error condition
716              
717             my $config = XMLin($config_file);
718              
719             if ($config) {
720             # validate configuration
721             my $dispatcher;
722             if ($config->{dispatch_config}) {
723             Log::Dispatch::Config->configure($config->{dispatch_config});
724             }
725              
726             foreach my $channel_config (@{$config->{channel}}) {
727             $config->{topic}->{$channel_config->{topic}} = $channel_config;
728             }
729             }
730              
731             bless $config, $class;
732             return $config;
733             }
734              
735             sub configure {
736             my ($self, $channel_config) = @_;
737              
738             my $topic = $channel_config->{topic};
739             while ($topic) {
740             if ($self->{topic}->{$topic}) {
741             _configure($self->{topic}->{$topic}, $channel_config->{channel});
742             return;
743             }
744             # climb the hierarchy
745             ($topic) = $topic =~ /(.*)::\w+$/;
746             }
747             # if we get here, there's no configuration for this topic, use defaults
748             }
749              
750             sub _configure {
751             my ($config, $channel) = @_;
752              
753             if ($config->{suppress}) {
754             disable $channel;
755             } else {
756             # default is enabled
757             enable $channel;
758             }
759              
760             decorate $channel ($config->{decoration})
761             if $config->{decoration};
762              
763             if (defined $config->{dispatch}
764             && $config->{dispatch} =~ /Log::Dispatch/oi) {
765             dispatch $channel (Log::Dispatch::Config->instance);
766             }
767              
768             $channel->set_priority($config->{priority})
769             if $config->{priority};
770             }
771              
772             1;