File Coverage

lib/Log/Localized.pm
Criterion Covered Total %
statement 219 221 99.1
branch 80 88 90.9
condition 12 16 75.0
subroutine 34 34 100.0
pod 1 1 100.0
total 346 360 96.1


line stmt bran cond sub pod time code
1             #################################################################
2             #
3             # Log::Localized - Dispatch log messages depending on local verbosity
4             #
5             # $Id: Localized.pm,v 1.13 2006/05/23 14:03:18 erwan Exp $
6             #
7             # 050909 erwan Created
8             # 060523 erwan Adapt to api change in File::HomeDir
9             #
10              
11             package Log::Localized;
12              
13 20     20   2414815 use 5.006;
  20         88  
  20         811  
14 20     20   114 use strict;
  20         38  
  20         600  
15 20     20   104 use warnings;
  20         48  
  20         573  
16 20     20   9607 use Data::Dumper;
  20         99995  
  20         2078  
17 20     20   148 use Carp qw(confess carp);
  20         55  
  20         1304  
18 20     20   20547 use Config::Tiny;
  20         24240  
  20         662  
19 20     20   20999 use Log::Dispatch;
  20         2733410  
  20         962  
20 20     20   22771 use Log::Dispatch::Config;
  20         461653  
  20         830  
21 20     20   33372 use Log::Dispatch::Screen;
  20         86067  
  20         786  
22 20     20   610 use File::Spec;
  20         45  
  20         463  
23 20     20   25195 use File::HomeDir;
  20         171189  
  20         3005  
24              
25             # TODO: load all Dispatcher plugins? is it necessary?
26              
27             our $VERSION = '0.05';
28              
29             #----------------------------------------------------------------
30             #
31             # configuration parameters. see BEGIN for default values.
32             # may be replaced by the Log::Localized::* rules of the rules file.
33             #
34             #----------------------------------------------------------------
35              
36             my @SEARCH_PATH; # an array of paths at which to search for verbosity or dispatcher file
37             my $FILE_RULES; # name of file containing verbosity rules
38             my $FILE_DISPATCHERS; # name of file containing dispatcher config
39             my $ENV_VERBOSITY; # name of the environment variable containing the global verbosity
40             my $PROGRAM; # name of currently executing program
41             my $LOG_FORMAT; # macro for preformatting log messages before dispatching them
42             my $LLOG_EXPORT_NAME; # name under which llog should be exported
43              
44             #----------------------------------------------------------------
45             #
46             # other parameters
47             #
48             #----------------------------------------------------------------
49              
50             # is logging on?
51             my $LOGGING_ON;
52              
53             # local verbosity level
54 20     20   171 use vars qw($VERBOSITY);
  20         45  
  20         907  
55              
56             # last message level
57 20     20   98 use vars qw($LEVEL);
  20         40  
  20         5388  
58              
59             # verbosity per namespace and function
60             my %VERBOSITY_RULES;
61              
62             # the Log::Dispatch handling all logging
63             my $DISPATCHER;
64              
65             #----------------------------------------------------------------
66             #
67             # import - disable or export 'llog' function, eventually under a different name
68             #
69              
70             sub import {
71 38     38   19808 shift;
72 38         100 my %args = @_;
73 38         90 my $pkg = caller(0);
74              
75             # switch logging on or off
76 38 100       2994 if (defined $args{log}) {
77 7 100       33 if ($args{log} =~ /^[01]$/) {
78 4         9 $LOGGING_ON = $args{log};
79             } else {
80 3         855 confess "ERROR: log => ".$args{log}." is not a valid value. use 0 or 1.\n";
81             }
82             }
83              
84             # load rules file via 'use'
85 35 100       3379 if (defined $args{rules}) {
86             # merge import rules with those from file (if any)
87 8         1436 my $config = Config::Tiny->read_string($args{rules});
88 8         2052 _load_verbosity_rules($config);
89 8         19 _init_dispatchers();
90             }
91              
92             # rename
93 35         64 my $export = $LLOG_EXPORT_NAME;
94 35 100       2847 if (exists $args{rename}) {
95 1         2 $export = $args{rename};
96             }
97              
98             # check ENV_VERBOSITY here too. people may use Log::Localized, then call import alone again later...
99 35 100       133 if (exists $ENV{$ENV_VERBOSITY}) {
100 9         14 $LOGGING_ON = 1;
101             }
102              
103             # is logging turned on?
104 35 100       92 if ($LOGGING_ON) {
105             # export log function to calling module
106 20     20   180 no strict 'refs';
  20         41  
  20         1273  
107 29         56 *{"${pkg}::$export"} = \&llog;
  29         1457  
108              
109             } else {
110             # disable logging in calling module
111 20     20   109 no strict 'refs';
  20         50  
  20         59902  
112 6     14   26 *{"${pkg}::$export"} = sub {};
  6         295  
  14         9074  
113             }
114             }
115              
116             #################################################################
117             #
118             #
119             # TEST UTILITIES - functions for testing purpose only
120             #
121             #
122             #################################################################
123              
124 14     14   272 sub _test_verbosity_rules { return %VERBOSITY_RULES; };
125 1     1   14 sub _test_program { return $PROGRAM; };
126              
127             #################################################################
128             #
129             #
130             # RULES FILE PARSING AND INITIALISATION
131             #
132             #
133             #################################################################
134              
135             #----------------------------------------------------------------
136             #
137             # _get_rules - try to find a rules file in the search path
138             #
139              
140             sub _get_rules {
141 22     22   59 foreach my $path (@SEARCH_PATH) {
142 54         721 my $file = File::Spec->catfile($path,$FILE_RULES);
143 54 100       815 if (-f $file) {
144 7         77 my $config = Config::Tiny->read($file);
145 7         1729 llog(1,"loaded verbosity rules from file [$file]");
146 7         43 return $config;
147             }
148             }
149 15         102 llog(1,"found no verbosity rules file in search path [".join(",",@SEARCH_PATH)."]");
150 15         51 return undef;
151             }
152              
153             #----------------------------------------------------------------
154             #
155             # _load_verbosity_rules - parse Log::Localized rules and configuration
156             #
157              
158             sub _load_verbosity_rules {
159 30     30   63 my $config = shift;
160              
161 30 100       117 if (defined $config) {
162              
163             # found rules file => logging is on
164 15         59 $LOGGING_ON = 1;
165              
166 15         26 my $reload = 0;
167            
168             # be sure to load default rules first
169 15 100       1691 if (exists $config->{'_'}) {
170 14         55 $reload = _load_config_block($config->{'_'});
171             }
172              
173             # then, rules specific to the running program, if any
174 15 100       67 if (exists $config->{$PROGRAM}) {
175 2         9 $reload = _load_config_block($config->{$PROGRAM});
176             }
177            
178             # reload rules if a Log::Localized::use_rules was set
179 15 100       53 if ($reload) {
180 2         5 llog(1,"reloading rules");
181 2         6 %VERBOSITY_RULES = ();
182 2         8 _load_verbosity_rules(_get_rules());
183             }
184             }
185              
186             # is global logging on? (may have been redefined by rules options)
187 30 100       268 if (defined $ENV{$ENV_VERBOSITY}) {
188 5         11 $LOGGING_ON = 1;
189             }
190             }
191              
192             #----------------------------------------------------------------
193             #
194             # _load_config_block - load rules from a block in the Tiny::Config object
195             #
196              
197             sub _load_config_block {
198 16     16   32 my $block = shift;
199              
200             # true if need to reload rules, ie if 'use_rules' option used
201 16         28 my $reload = 0;
202              
203             # define how to parse Log::Localized options
204             my $OPTIONS = {
205             # option_name => closure loading option
206 1     1   4 search_path => sub { @SEARCH_PATH = _get_search_path($_[0]); },
207 2     2   5 use_rules => sub { $reload = 1; $FILE_RULES = shift; },
  2         5  
208 4     4   8 rename => sub { $LLOG_EXPORT_NAME = shift; },
209 3     3   6 dispatchers => sub { $FILE_DISPATCHERS = shift; },
210 1     1   2 format => sub { $LOG_FORMAT = shift; },
211 1     1   2 global_verbosity => sub { $ENV_VERBOSITY = shift; },
212 16         289 };
213            
214 16 50       101 if (ref $block eq 'HASH') {
215 16         79 foreach my $path (keys %$block) {
216 46         94 my $value = $block->{$path};
217            
218             # Log::Localized's own configuration
219 46 100       154 if ($path =~ /^log::localized::(.+)$/i) {
220 12         47 my $option = $1;
221 12         30 $option = lc $option;
222            
223             # is this a known option? otherwise assume it's a verbosity rule
224 12 50       45 if (exists $OPTIONS->{$option}) {
225 12         57 llog(1,"setting option [$option]");
226 12         26 my $fnc = $OPTIONS->{$option};
227 12         36 &$fnc($value);
228 12         30 next;
229             }
230             }
231              
232             # verbosity rules
233 34 100       223 if ($value !~ /^\d+$/) {
234 1         12 carp "WARNING: invalid verbosity rules for [$path]. [$value] should be an integer. Ignoring it.";
235             } else {
236 33 100       98 if ($path !~ /::/) {
237             # assuming it's a function name in main::
238 2         6 $VERBOSITY_RULES{"main::${path}"} = $value;
239 2         7 llog(1,"loading rule [main::${path} = $value]");
240             } else {
241             # rem: implies that 'A::B' will be mistaken for a method called 'B' in module 'A'
242             # while 'A::B::' will be rightly understood as *all methods* in A::B
243 31         103 $VERBOSITY_RULES{$path} = $value;
244 31         113 llog(1,"loading rule [${path} = $value]");
245             }
246             }
247             }
248             }
249              
250 16         816 return $reload;
251             }
252              
253             #----------------------------------------------------------------
254             #
255             # _init_dispatchers - create Log::Dispatch dispatchers for Log::Localized
256             #
257              
258             sub _init_dispatchers {
259              
260 28 100   28   104 if (defined $FILE_DISPATCHERS) {
261 3         7 foreach my $path (@SEARCH_PATH) {
262 7         185 my $file = File::Spec->catfile($path,$FILE_DISPATCHERS);
263 7 100       133 if (-f $file) {
264             # TODO: eventually use configure_and_watch here...
265 2         71 Log::Dispatch::Config->configure($file);
266 2         44249 $DISPATCHER = Log::Dispatch::Config->instance;
267 2         6986 llog(1,"loaded dispatchers from file [$file]");
268 2         148 return;
269             }
270             }
271 1         12 carp "WARNING: no dispatcher definition file [$FILE_DISPATCHERS] found in [".join(",",@SEARCH_PATH)."]. using defaults.";
272             }
273              
274             # by default, dispatch to stdout and add a newline
275 26         778 $DISPATCHER = Log::Dispatch->new;
276             $DISPATCHER->add(Log::Dispatch::Screen->new(name => 'screen',
277             min_level => 'debug',
278             stderr => 1,
279             callbacks => sub {
280 91     91   14991 my %hash = @_;
281 91         438 return $hash{message}."\n";
282             },
283 26         2667 ));
284 26         6852 llog(1,"using default dispatcher to stdout");
285             }
286              
287             #----------------------------------------------------------------
288             #
289             # _get_search_path - do keyword substitutiob in search path
290             #
291              
292             sub _get_search_path {
293 27     27   9324 my $strpath = shift; # path in usual unix style path1:path2:...
294 27 50       171 my $home = home() or confess "ERROR: your system does not seem to support home directories";
295 27         1812 my @search_path = ();
296 27         165 foreach my $path (split(":",$strpath)) {
297 76         217 $path =~ s/\~/$home/g;
298              
299             # look for environment variables
300 76         111 my %pathenv;
301 76         502 while ($path =~ /\$([^\/\:]+)/gm) {
302 10         50 $pathenv{$1} = 1;
303             }
304              
305             # and substitute them
306 76         295 foreach my $env (keys %pathenv) {
307 8 100       26 if (exists $ENV{$env}) {
308 6         15 my $value = $ENV{$env};
309 6         1104 $path =~ s/\$$env/$ENV{$env}/g;
310             }
311             }
312              
313 76         256 push @search_path, $path;
314              
315             }
316              
317 27         419 return @search_path;
318             }
319              
320             #################################################################
321             #
322             #
323             # LOGGING FUNCTIONS
324             #
325             #
326             #################################################################
327              
328             #----------------------------------------------------------------
329             #
330             # _get_local_verbosity - find out the local verbosity in the code currently executed
331             #
332              
333             sub _get_local_verbosity {
334 838     838   4525 my $pkg = (caller(1))[0];
335 838   100     5306 my $fnc = (caller(2))[3] || "";
336            
337             # _get_local_verbosity logs itself. $log is required to avoid infinite recursion,
338 838         2097 my $log = 1;
339 838 100       1855 $log = 0 if ($fnc eq "Log::Localized::_get_local_verbosity");
340            
341 838 100       8865 llog(5,"the function calling llog() is [$fnc] in package [$pkg]") if $log;
342              
343             #--------------------------------------------------------------
344             #
345             # 1. check ENV_LOG_VERBOSITY
346             #
347              
348 836 100       2262 if (defined $ENV{$ENV_VERBOSITY}) {
349 228         407 my $v = $ENV{$ENV_VERBOSITY};
350 228 100       861 if ($v !~ /^\d+$/) {
351 3         50 carp "WARNING: environment variable $ENV_VERBOSITY must be an integer. ignoring it.";
352             } else {
353 225 100       552 llog(5,"local verbosity is [$v]. (set by $ENV_VERBOSITY)") if $log;
354 225         834 return $v;
355             }
356             }
357            
358             #--------------------------------------------------------------
359             #
360             # 2. check verbosity rules
361             #
362              
363 611         2764 my $v;
364 611 100       1856 if (exists $VERBOSITY_RULES{$fnc}) {
    100          
365 24         40 $v = $VERBOSITY_RULES{$fnc};
366 24 50       103 llog(5,"local verbosity is [$v]. (set by verbosity rule file, rule [$fnc])") if $log;
367             } elsif (exists $VERBOSITY_RULES{$pkg."::*"}) {
368 16         36 $v = $VERBOSITY_RULES{$pkg."::*"};
369 16 50       70 llog(5,"local verbosity is [$v]. (set by verbosity rule file, rule [$pkg\::*])") if $log;
370             } else {
371             # lookup parent packages to see if any in rules file
372 571         1827 my @names = split(/::/, $pkg);
373 571         1317 while (@names) {
374 1087         2225 my $subpkg = join("::",@names)."::";
375 1087 100       3535 if (exists $VERBOSITY_RULES{$subpkg}) {
376 31         51 $v = $VERBOSITY_RULES{$subpkg};
377 31 50       133 llog(5,"local verbosity is [$v]. (set by verbosity rule file, rule [".$subpkg."])") if $log;
378 31         69 last;
379             }
380 1056         2739 pop @names;
381             }
382             }
383            
384 611 100       1257 if (defined $v) {
385 71         430 return $v;
386             }
387            
388             #--------------------------------------------------------------
389             #
390             # 3. check local $VERBOSITY
391             #
392              
393 540 50       1221 if (defined $VERBOSITY) {
394 540 100       1996 if ($VERBOSITY !~ /^\d+$/) {
395 2         749 confess "BUG: some code has set VERBOSITY to a non integer value [$VERBOSITY].\n";
396             }
397 538 100       1239 llog(5,"local verbosity is [$VERBOSITY]. (set locally in calling code)") if $log;
398 538         2753 return $VERBOSITY;
399             }
400            
401             # do not log anything by default
402 0         0 return -1;
403             }
404              
405             #----------------------------------------------------------------
406             #
407             # llog - display a debug message if local verbosity is high enough
408             #
409              
410             # a buffer in which llog stores messages until dispatchers are defined
411             my @LOG_QUEUE;
412              
413             sub llog {
414 900     900 1 101778 my($level,$msg) = @_;
415              
416             # check out arguments
417 900 100       2480 confess "BUG: llog() expects 2 arguments, but got [".Dumper(@_)."]"
418             unless (@_ == 2);
419            
420 898 100 100     5611 confess "BUG: llog() expects either a string or a code reference as second argument, but got [".ref($msg)."] [".Dumper($msg)."]"
      33        
421             unless (defined $msg && (ref($msg) eq "" || ref($msg) eq "CODE"));
422              
423             # if dispatchers not yet available
424 897 100       2192 if (!defined $DISPATCHER) {
425 59         132 push @LOG_QUEUE,$level,$msg;
426 59         209 return;
427             }
428              
429             # now dispatchers are defined. before proceeding, can we empty the queue?
430 838 100 66     2654 if (scalar @LOG_QUEUE && (caller(1))[3] ne 'llog') {
431 59         182 while (scalar(@LOG_QUEUE)) {
432 59         103 my $lvl = shift @LOG_QUEUE;
433 59         1263 my $msg = shift @LOG_QUEUE;
434 59         376 llog($lvl,$msg);
435             }
436             }
437              
438             # should we log this message according to current verbosity?
439 838 100       2436 if ($level <= _get_local_verbosity()) {
440              
441 95         144 $LEVEL = $level ;
442            
443             # did we get a message, or a reference to some code generating this message?
444 95 100       359 if (ref($msg) eq "CODE") {
445             # TODO: run $msg() in eval and die if crashed
446 83         330 my $txt = &$msg($level);
447 83 100 66     2744 confess "BUG: llog() was passed a function reference that did not return a valid string [".Dumper($txt)."]"
448             unless (defined $txt && ref($txt) eq "");
449 82         154 $msg = "$txt";
450             }
451            
452             # format message to display
453 94         611 my($pkg,$line) = (caller(0))[0,2];
454 94   100     704 my $fnc = (caller(1))[3] || "main";
455 94         514 $fnc =~ s/.+:://g;
456              
457 94         151 my $txt = $LOG_FORMAT;
458 94         307 $txt =~ s/\%PKG/$pkg/g;
459 94         306 $txt =~ s/\%FNC/$fnc/g;
460 94         335 $txt =~ s/\%LIN/$line/g;
461 94         301 $txt =~ s/\%LVL/$level/g;
462 94         306 $txt =~ s/\%MSG/$msg/g;
463            
464 94         434 $DISPATCHER->log(level => 'info', message => $txt);
465             }
466             }
467            
468             #################################################################
469             #
470             #
471             # BEGIN TIME
472             #
473             #
474             #################################################################
475              
476             # This BEGIN block executes before import().
477             # Many globals have to be initialized here...
478              
479             BEGIN {
480              
481             # default settings
482 20     20   94 @SEARCH_PATH = _get_search_path(".:~:/");
483 20         49 $FILE_RULES = 'verbosity.conf';
484 20         52 $LOG_FORMAT = '# [%PKG::%FNC() l.%LIN] [LEVEL %LVL]: %MSG';
485 20         37 $LLOG_EXPORT_NAME = "llog";
486 20         40 $VERBOSITY = 0;
487 20         41 $ENV_VERBOSITY = 'LOG_LOCALIZED_VERBOSITY';
488              
489             # figure out running program's name
490 20         71 $PROGRAM = $0;
491 20         156 $PROGRAM =~ s/(.*\/)//g;
492            
493 20 50       118 if (!defined $PROGRAM) {
494 0         0 confess "ERROR: failed to parse name of running program out of [$0]";
495             }
496              
497 20         131 llog(2,"running program is [$PROGRAM]");
498              
499             # set up everything
500 20         58 _load_verbosity_rules(_get_rules());
501 20         107 _init_dispatchers();
502             }
503              
504             1;
505              
506             __END__