| Log-Localized documentation | Contained in the Log-Localized distribution. |
Log::Localized - Localize your logging
What you most probably want to do is something like:
package Foo;
use Log::Localized;
sub bar {
# this message will be displayed if method bar's verbosity is >= 1
llog(1,"running bar()");
}
# this message will be displayed if package Foo's verbosity is >= 3
llog(3,"loaded package Foo");
Then paste the following local verbosity rules in a file called 'verbosity.conf', in the same directory as your program:
# log everything from wherever inside Foo and its subclasses, up to level 3
Foo:: = 3
# except for function Foo::foo who shall have verbosity 0
Foo::bar = 0
In a program accepting command line arguments, you may want to do:
use Getopt::Long;
use Log::Localized log => 1;
GetOptions("verbose|v+" => sub { $Log::Localized::VERBOSITY++; } );
llog(1,"you used -v");
llog(2,"you used -v -v");
You may alter local verbosity from within the running code:
package Foo;
use Log::Localized log => 1;
# verbosity level is 0 by default
{
# set verbosity locally in this block
local $Log::Debug::VERBOSITY = 5;
llog(5,"this will be logged");
}
debug(5,"but this won't");
If you want to import 'llog' under another name in the calling module:
package Foo;
use Log::Localized rename => "my_log";
# call Log::Localized::llog()
my_log(1,"renamed llog()");
See the examples directory in the module distribution for more real life examples.
Log::Localized provides you with an interface for defining dynamically exactly which part of your code should log messages and with which verbosity.
Log::Localized addresses one issue of traditional logging: in very large systems, a slight increase in logging verbosity usually generates insane amounts of logs. Hence the need of being able to turn on verbosity selectively in some areas of code only, in a localized way.
Log::Localized is based on the concept of local verbosity. Each package and each function in a package has its own local verbosity, set to 0 by default. With Log::Localized you can change the local verbosity in just a function, just a package or just a class hierarchy via a so called verbosity rule. Verbosity rules are passed to Log::Localized either via a configuration file or via an import parameter. By changing verbosity rules according to the needs of the moment, you can alter your program's logging flow in a very fine-grained way, and get logs from only the code areas you are interested in.
Log::Localized comes with default settings that make it usable 'out of the box', but its configuration options will let you redefine pretty much everything in its behavior.
The actual logging in Log::Localized is handled by Log::Dispatch.
Log::Localized provides 4 mechanisms to affect local verbosity. They are, in order of precedence:
If the environment variable LOG_LOCALIZED_VERBOSITY is set, logging is turned on and Log::Localized will log everywhere in the code to the level of verbosity set in LOG_LOCALIZED_VERBOSITY. LOG_LOCALIZED_VERBOSITY's value must be a positive integer. This overrides even the verbosity level eventually set vith verbosity rules. Note that the name of this environment variable can be changed vith the appropriate option.
Verbosity rules can be loaded at 'use' time via the import parameter 'rules'. If Log::Localized is passed rules in that way, those rules will be merged with the rules eventually loaded from a rules file at compile time, and logging is turned on.
If at compilation time there exists a file called 'verbosity.conf' in the search path, the rules it contains are loaded and logging is turned on. Note that you can modify both the default search path and rules file name with the appropriate options.
You can also set verbosity locally in the code, by locally setting the class variable $Log::Localized::VERBOSITY. See the examples in SYNOPSIS. Verbosity set in this way will be overriden by any verbosity defined via the environment variable or via rules.
Verbosity rules follow the Tiny::Config syntax. Read its pod before continuing.
Below is an example of verbosity rules:
# default block
main:: = 4
Foo::* = 3
# additional rules specific to program 'test.pl'
[test.pl]
main:: = 0
Foo::bar = 0
Those rules read like this: set local verbosity to 4 everywhere in package 'main' and all subpackages of package 'main'. Set local verbosity everywhere in package 'Foo' to 3. But if the running program is 'test.pl', use the same rules, except that local verbosity should be set to 0 everywhere in package 'main' and all its subpackages, and verbosity should be set to 0 for function bar in package 'Foo'.
See Config::Tiny. The default heading block contains default verbosity rules that apply to all programs. Following configuration blocks are named with, in brackets, the name of a program to which the block's rules apply. Rules in a named block apply only to the program having the same name, and will override any default rules with similar keys.
A verbosity rule is a key-value pair of the form 'key = value', where value must be an integer. Those pairs can have one of the following syntaxes:
See 'LOGGING ALGORITHM' for details about rule precedence.
When feeding verbosity rules to Log::Localized via a rules file or via the import parameter 'rules', you can alter the default settings of Log::Localized by declaring some of the following special rules:
Upon calling 'llog($level,$message)', llog does the following to find out the local verbosity.
If the global verbosity environment variable is set, its value is used as the local verbosity.
Otherwise, llog identifies the calling function and its module and checks whether there exists a rule with the key 'module::function'. If not it searches for a rule with the key 'module::*'. If not, it searches for a rule with the key 'module::', then with keys corresponding to parent packages of 'module'. For example, if module is 'Foo::Bar::Naph', the following keys will be successively searched for: 'Foo::Bar:Naph::', 'Foo::Bar::' then 'Foo::'. This explain the 'package and all subpackages' mechanism named previously.
Finally, if no matching verbosity rule was found, llog uses the value of the class variable $Log::Localized::VERBOSITY, defaulting to 0.
If the message level is less or equal to the local verbosity, llog will log the message.
In that case, if the message was a closure, llog executes it and takes its result as the message. llog pre-formats the message, then dispatch it to its Log::Dispatch object.
use Log::Localized rules => "Log::Localized::search_path = / \n".
"Log::Localized::dispatchers = /opt/conf/dispatch.conf \n".
"Log::Localized::use_rules = /opt/conf/rules.conf \n";
use Log::Localized log => 1;
Where $level is an integer, and $message is either a string or a reference to a function that returns a string.
llog dispatches the message $message if $level is inferior or equal to the local verbosity in the code where llog is called. llog has no return value.
See 'LOGGING ALGORITHM' for details on how llog works.
By convention, messages with a $level of 0 are always logged (if logging is on), since the default local verbosity is 0 too.
If $message is a function reference instead of a string, this function is executed and the string it returns is used as the log message. Note that the function is executed only if the message level is <= to the local verbosity. Indeed, a call like:
llog(4,"content of this hugely complex object: ".Dumper($obj));
will slow things down in code where speed matters, since it executes Dumper even when the local verbosity is lower than 4. If you want Dumper to execute only when the message would actually be logged, wrap it in an anonymous function:
llog(4, sub { "content of this hugely complex object: ".Dumper($obj); } );
See 'LOGGING ALGORITHM' for details and 'SYNOPSIS' for examples.
Contains the level of the message last received by llog. This class variable is made to be used by closures that are passed to llog. See the example in the 'examples' directory.
See Log::Dispatch, Log::Log4perl.
Please report any bugs or feature requests to
bug-log-localized@rt.cpan.org, or through the web interface at
http://rt.cpan.org.
By the way, Log::Localized llogs itself :) To get a glimpse of Log::Localized's internals, 'use Log::Localized;', then dump the rule 'Log::Localized:: = 3' in a local file called 'verbosity.conf'...
Written by Erwan Lemonnier <erwan@cpan.org>
and co-designed by Claes Jacobsson <claesjac@cpan.org>.
Copyright (C) 2005 by Erwan Lemonnier <erwan@cpan.org>
This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See perlartistic.
BECAUSE THIS SOFTWARE IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE SOFTWARE "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE SOFTWARE IS WITH YOU. SHOULD THE SOFTWARE PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR, OR CORRECTION.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE SOFTWARE AS PERMITTED BY THE ABOVE LICENCE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE SOFTWARE (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
| Log-Localized documentation | Contained in the Log-Localized distribution. |
################################################################# # # Log::Localized - Dispatch log messages depending on local verbosity # # $Id: Localized.pm,v 1.13 2006/05/23 14:03:18 erwan Exp $ # # 050909 erwan Created # 060523 erwan Adapt to api change in File::HomeDir # package Log::Localized; use 5.006; use strict; use warnings; use Data::Dumper; use Carp qw(confess carp); use Config::Tiny; use Log::Dispatch; use Log::Dispatch::Config; use Log::Dispatch::Screen; use File::Spec; use File::HomeDir; # TODO: load all Dispatcher plugins? is it necessary? our $VERSION = '0.05'; #---------------------------------------------------------------- # # configuration parameters. see BEGIN for default values. # may be replaced by the Log::Localized::* rules of the rules file. # #---------------------------------------------------------------- my @SEARCH_PATH; # an array of paths at which to search for verbosity or dispatcher file my $FILE_RULES; # name of file containing verbosity rules my $FILE_DISPATCHERS; # name of file containing dispatcher config my $ENV_VERBOSITY; # name of the environment variable containing the global verbosity my $PROGRAM; # name of currently executing program my $LOG_FORMAT; # macro for preformatting log messages before dispatching them my $LLOG_EXPORT_NAME; # name under which llog should be exported #---------------------------------------------------------------- # # other parameters # #---------------------------------------------------------------- # is logging on? my $LOGGING_ON; # local verbosity level use vars qw($VERBOSITY); # last message level use vars qw($LEVEL); # verbosity per namespace and function my %VERBOSITY_RULES; # the Log::Dispatch handling all logging my $DISPATCHER; #---------------------------------------------------------------- # # import - disable or export 'llog' function, eventually under a different name # sub import { shift; my %args = @_; my $pkg = caller(0); # switch logging on or off if (defined $args{log}) { if ($args{log} =~ /^[01]$/) { $LOGGING_ON = $args{log}; } else { confess "ERROR: log => ".$args{log}." is not a valid value. use 0 or 1.\n"; } } # load rules file via 'use' if (defined $args{rules}) { # merge import rules with those from file (if any) my $config = Config::Tiny->read_string($args{rules}); _load_verbosity_rules($config); _init_dispatchers(); } # rename my $export = $LLOG_EXPORT_NAME; if (exists $args{rename}) { $export = $args{rename}; } # check ENV_VERBOSITY here too. people may use Log::Localized, then call import alone again later... if (exists $ENV{$ENV_VERBOSITY}) { $LOGGING_ON = 1; } # is logging turned on? if ($LOGGING_ON) { # export log function to calling module no strict 'refs'; *{"${pkg}::$export"} = \&llog; } else { # disable logging in calling module no strict 'refs'; *{"${pkg}::$export"} = sub {}; } } ################################################################# # # # TEST UTILITIES - functions for testing purpose only # # ################################################################# sub _test_verbosity_rules { return %VERBOSITY_RULES; }; sub _test_program { return $PROGRAM; }; ################################################################# # # # RULES FILE PARSING AND INITIALISATION # # ################################################################# #---------------------------------------------------------------- # # _get_rules - try to find a rules file in the search path # sub _get_rules { foreach my $path (@SEARCH_PATH) { my $file = File::Spec->catfile($path,$FILE_RULES); if (-f $file) { my $config = Config::Tiny->read($file); llog(1,"loaded verbosity rules from file [$file]"); return $config; } } llog(1,"found no verbosity rules file in search path [".join(",",@SEARCH_PATH)."]"); return undef; } #---------------------------------------------------------------- # # _load_verbosity_rules - parse Log::Localized rules and configuration # sub _load_verbosity_rules { my $config = shift; if (defined $config) { # found rules file => logging is on $LOGGING_ON = 1; my $reload = 0; # be sure to load default rules first if (exists $config->{'_'}) { $reload = _load_config_block($config->{'_'}); } # then, rules specific to the running program, if any if (exists $config->{$PROGRAM}) { $reload = _load_config_block($config->{$PROGRAM}); } # reload rules if a Log::Localized::use_rules was set if ($reload) { llog(1,"reloading rules"); %VERBOSITY_RULES = (); _load_verbosity_rules(_get_rules()); } } # is global logging on? (may have been redefined by rules options) if (defined $ENV{$ENV_VERBOSITY}) { $LOGGING_ON = 1; } } #---------------------------------------------------------------- # # _load_config_block - load rules from a block in the Tiny::Config object # sub _load_config_block { my $block = shift; # true if need to reload rules, ie if 'use_rules' option used my $reload = 0; # define how to parse Log::Localized options my $OPTIONS = { # option_name => closure loading option search_path => sub { @SEARCH_PATH = _get_search_path($_[0]); }, use_rules => sub { $reload = 1; $FILE_RULES = shift; }, rename => sub { $LLOG_EXPORT_NAME = shift; }, dispatchers => sub { $FILE_DISPATCHERS = shift; }, format => sub { $LOG_FORMAT = shift; }, global_verbosity => sub { $ENV_VERBOSITY = shift; }, }; if (ref $block eq 'HASH') { foreach my $path (keys %$block) { my $value = $block->{$path}; # Log::Localized's own configuration if ($path =~ /^log::localized::(.+)$/i) { my $option = $1; $option = lc $option; # is this a known option? otherwise assume it's a verbosity rule if (exists $OPTIONS->{$option}) { llog(1,"setting option [$option]"); my $fnc = $OPTIONS->{$option}; &$fnc($value); next; } } # verbosity rules if ($value !~ /^\d+$/) { carp "WARNING: invalid verbosity rules for [$path]. [$value] should be an integer. Ignoring it."; } else { if ($path !~ /::/) { # assuming it's a function name in main:: $VERBOSITY_RULES{"main::${path}"} = $value; llog(1,"loading rule [main::${path} = $value]"); } else { # rem: implies that 'A::B' will be mistaken for a method called 'B' in module 'A' # while 'A::B::' will be rightly understood as *all methods* in A::B $VERBOSITY_RULES{$path} = $value; llog(1,"loading rule [${path} = $value]"); } } } } return $reload; } #---------------------------------------------------------------- # # _init_dispatchers - create Log::Dispatch dispatchers for Log::Localized # sub _init_dispatchers { if (defined $FILE_DISPATCHERS) { foreach my $path (@SEARCH_PATH) { my $file = File::Spec->catfile($path,$FILE_DISPATCHERS); if (-f $file) { # TODO: eventually use configure_and_watch here... Log::Dispatch::Config->configure($file); $DISPATCHER = Log::Dispatch::Config->instance; llog(1,"loaded dispatchers from file [$file]"); return; } } carp "WARNING: no dispatcher definition file [$FILE_DISPATCHERS] found in [".join(",",@SEARCH_PATH)."]. using defaults."; } # by default, dispatch to stdout and add a newline $DISPATCHER = Log::Dispatch->new; $DISPATCHER->add(Log::Dispatch::Screen->new(name => 'screen', min_level => 'debug', stderr => 1, callbacks => sub { my %hash = @_; return $hash{message}."\n"; }, )); llog(1,"using default dispatcher to stdout"); } #---------------------------------------------------------------- # # _get_search_path - do keyword substitutiob in search path # sub _get_search_path { my $strpath = shift; # path in usual unix style path1:path2:... my $home = home() or confess "ERROR: your system does not seem to support home directories"; my @search_path = (); foreach my $path (split(":",$strpath)) { $path =~ s/\~/$home/g; # look for environment variables my %pathenv; while ($path =~ /\$([^\/\:]+)/gm) { $pathenv{$1} = 1; } # and substitute them foreach my $env (keys %pathenv) { if (exists $ENV{$env}) { my $value = $ENV{$env}; $path =~ s/\$$env/$ENV{$env}/g; } } push @search_path, $path; } return @search_path; } ################################################################# # # # LOGGING FUNCTIONS # # ################################################################# #---------------------------------------------------------------- # # _get_local_verbosity - find out the local verbosity in the code currently executed # sub _get_local_verbosity { my $pkg = (caller(1))[0]; my $fnc = (caller(2))[3] || ""; # _get_local_verbosity logs itself. $log is required to avoid infinite recursion, my $log = 1; $log = 0 if ($fnc eq "Log::Localized::_get_local_verbosity"); llog(5,"the function calling llog() is [$fnc] in package [$pkg]") if $log; #-------------------------------------------------------------- # # 1. check ENV_LOG_VERBOSITY # if (defined $ENV{$ENV_VERBOSITY}) { my $v = $ENV{$ENV_VERBOSITY}; if ($v !~ /^\d+$/) { carp "WARNING: environment variable $ENV_VERBOSITY must be an integer. ignoring it."; } else { llog(5,"local verbosity is [$v]. (set by $ENV_VERBOSITY)") if $log; return $v; } } #-------------------------------------------------------------- # # 2. check verbosity rules # my $v; if (exists $VERBOSITY_RULES{$fnc}) { $v = $VERBOSITY_RULES{$fnc}; llog(5,"local verbosity is [$v]. (set by verbosity rule file, rule [$fnc])") if $log; } elsif (exists $VERBOSITY_RULES{$pkg."::*"}) { $v = $VERBOSITY_RULES{$pkg."::*"}; llog(5,"local verbosity is [$v]. (set by verbosity rule file, rule [$pkg\::*])") if $log; } else { # lookup parent packages to see if any in rules file my @names = split(/::/, $pkg); while (@names) { my $subpkg = join("::",@names)."::"; if (exists $VERBOSITY_RULES{$subpkg}) { $v = $VERBOSITY_RULES{$subpkg}; llog(5,"local verbosity is [$v]. (set by verbosity rule file, rule [".$subpkg."])") if $log; last; } pop @names; } } if (defined $v) { return $v; } #-------------------------------------------------------------- # # 3. check local $VERBOSITY # if (defined $VERBOSITY) { if ($VERBOSITY !~ /^\d+$/) { confess "BUG: some code has set VERBOSITY to a non integer value [$VERBOSITY].\n"; } llog(5,"local verbosity is [$VERBOSITY]. (set locally in calling code)") if $log; return $VERBOSITY; } # do not log anything by default return -1; } #---------------------------------------------------------------- # # llog - display a debug message if local verbosity is high enough # # a buffer in which llog stores messages until dispatchers are defined my @LOG_QUEUE; sub llog { my($level,$msg) = @_; # check out arguments confess "BUG: llog() expects 2 arguments, but got [".Dumper(@_)."]" unless (@_ == 2); confess "BUG: llog() expects either a string or a code reference as second argument, but got [".ref($msg)."] [".Dumper($msg)."]" unless (defined $msg && (ref($msg) eq "" || ref($msg) eq "CODE")); # if dispatchers not yet available if (!defined $DISPATCHER) { push @LOG_QUEUE,$level,$msg; return; } # now dispatchers are defined. before proceeding, can we empty the queue? if (scalar @LOG_QUEUE && (caller(1))[3] ne 'llog') { while (scalar(@LOG_QUEUE)) { my $lvl = shift @LOG_QUEUE; my $msg = shift @LOG_QUEUE; llog($lvl,$msg); } } # should we log this message according to current verbosity? if ($level <= _get_local_verbosity()) { $LEVEL = $level ; # did we get a message, or a reference to some code generating this message? if (ref($msg) eq "CODE") { # TODO: run $msg() in eval and die if crashed my $txt = &$msg($level); confess "BUG: llog() was passed a function reference that did not return a valid string [".Dumper($txt)."]" unless (defined $txt && ref($txt) eq ""); $msg = "$txt"; } # format message to display my($pkg,$line) = (caller(0))[0,2]; my $fnc = (caller(1))[3] || "main"; $fnc =~ s/.+:://g; my $txt = $LOG_FORMAT; $txt =~ s/\%PKG/$pkg/g; $txt =~ s/\%FNC/$fnc/g; $txt =~ s/\%LIN/$line/g; $txt =~ s/\%LVL/$level/g; $txt =~ s/\%MSG/$msg/g; $DISPATCHER->log(level => 'info', message => $txt); } } ################################################################# # # # BEGIN TIME # # ################################################################# # This BEGIN block executes before import(). # Many globals have to be initialized here... BEGIN { # default settings @SEARCH_PATH = _get_search_path(".:~:/"); $FILE_RULES = 'verbosity.conf'; $LOG_FORMAT = '# [%PKG::%FNC() l.%LIN] [LEVEL %LVL]: %MSG'; $LLOG_EXPORT_NAME = "llog"; $VERBOSITY = 0; $ENV_VERBOSITY = 'LOG_LOCALIZED_VERBOSITY'; # figure out running program's name $PROGRAM = $0; $PROGRAM =~ s/(.*\/)//g; if (!defined $PROGRAM) { confess "ERROR: failed to parse name of running program out of [$0]"; } llog(2,"running program is [$PROGRAM]"); # set up everything _load_verbosity_rules(_get_rules()); _init_dispatchers(); } 1; __END__