Log::Localized - Localize your logging


Log-Localized documentation Contained in the Log-Localized distribution.

Index


Code Index:

NAME

Top

Log::Localized - Localize your logging

SYNOPSIS

Top

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

SYNOPSIS - ADVANCED

Top

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.

DESCRIPTION

Top

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.

DEFAULT SETTINGS

Top

DEFAULT VERBOSITY The local verbosity is everywhere 0 by default.
DEFAULT DISPATCHER Log::Localized dispatches its log to STDOUT by default and with no Log::Dispatch preformatting of the log messages. You can change the default dispatcher with the option Log::Localized::dispatchers.
DEFAULT SEARCH PATH The default search path in which Log::Localized will search for verbosity rules files or dispatcher config files is (in this order) the local directory, the user's home directory and the root directory (to enable the use of absolute paths in options). The default search path can be overriden with the Log::Localized::search_path option.
DEFAULT VERBOSITY RULES FILE NAME By default, the verbosity rules file should be called 'verbosity.conf'. This name can be changed with the option Log::Localized::use_rules.
DEFAULT LOG FUNCTION NAME Log::Localized's logging function is by default exported under the name llog, but this can be changed with the option Log::Localized::rename or via the import parameter rename.
DEFAULT GLOBAL VERBOSITY ENVIRONMENT VARIABLE NAME The environment variable setting the global verbosity level is by default 'LOG_LOCALIZED_VERBOSITY'. This can be changed with the option Log::Localized::global_verbosity.
DEFAULT FORMATTING Log::Localized pre-formats log messages by default. The message 'whatever' coming from function test in module Foo::Bar and logged at line 25 with level 2 will be logged as '# [Foo::Bar::test() l.25] [LEVEL 2]: whatever'. This default pre-formatting can be changed with the Log:Localized::format option.

CONFIGURATION

Top

Log::Localized provides 4 mechanisms to affect local verbosity. They are, in order of precedence:

ENVIRONMENT VARIABLE

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 VIA IMPORT PARAMETERS

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.

VERBOSITY RULES IN A FILE

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.

LOCAL VERBOSITY

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

Top

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'.

CONFIG BLOCKS

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.

VERBOSITY RULES

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:

namespace:: = value where namespace is a Perl namespace, such as 'main' or 'Foo::Bar'. This rule reads 'set local verbosity to value everywhere in package namespace and all its sub-packages'. Everywhere means in all functions, blocks, methods...
namespace::* = value reads 'set local verbosity to value everywhere in package namespace (but not in its sub-packages)'.
namespace::function = value reads 'set local verbosity to value in function function from package namespace'.
function = value is the same as 'main::function = value'.

See 'LOGGING ALGORITHM' for details about rule precedence.

LOG::LOCALIZED OPTIONS

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:

Log::Localized::rename = $name instructs Log::Localized to export llog under the name $name.
Log::Localized::dispatchers = $file instructs Log::Localized to use the dispatchers defined in the Log::Dispatch::Config file called $file located in the search path. Those dispatchers replace Log::Localized's default dispatcher (ie STDOUT).
Log::Localized::format = $format instructs Log::Localized to pre-format log messages according to the macro in $format. The default format macro is '# [%PKG::%FNC() l.%LIN] [LEVEL %LVL]: %MSG'. %MSG is replaced by the log message, %FNC by the name of the function calling llog and %PKG by the name of this function's package, %LIN by the line code number where llog is called and %LVL with the level of the message passed to llog. The $format string can be any string containing some of these 5 keywords.
Log::Localized::global_verbosity = $name instructs Log::Localized to use $name as the name of the global verbosity environment variable.
Log::Localized::use_rules = $rules indicates the name of a verbosity rules file from which all rules should be reloaded. The use_rule option makes that all rules loaded so far are droped and that new rules are searched in a file called $rules located in the search path. This option allows you to change the default name for rules files ('verbosity.conf'). You may want to combine it with the search_path option to specify the directory in which the new rules file is located.
Log::Localized::search_path = $pathlist specifies the search path in which to search for the use_rules and dispatchers files. $pathlist is a traditional UNIX style search path made of a colon separated list of paths (ex: "~/config/:/opt/log/:$SYSTEM_ROOT/conf/"). Log::Localized will substitute any occurence of '~' with the path of the current user's home directory (see File::HomeDir), and any name starting with a dollar sign by the content of the environment variable having the same name, if this variable exists. If the variable does not exist, the path is left unchanged.

LOGGING ALGORITHM

Top

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.

IMPORT PARAMETERS

Top

"rename => $name" exports llog into the calling module under the name $name.
"rules => $rules" lets you inject verbosity rules into Log::Localized. The syntax of $rules is the same as for the verbosity rules file. This is particularly usefull for passing configuration options to Log::Localized. Note though that option changes will apply globally to all modules using Log::Localized, and not only to the one calling Log::Localized with the import parameter 'rules'. Example:
    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";

"log => [0|1]" turns logging on or off. llog is exported into the calling module only if logging is turned on. Logging is turned on if a rule verbosity file was found at compilation time or passed via the 'rules' parameter, or if the global verbosity environment variable is defined upon importing. If neither exists, but you still want to log, you have to use the 'log' parameter with the value 0 (off) or 1 (on):
    use Log::Localized log => 1;

EXPORTED FUNCTIONS

Top

llog($level,$message)

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); } );

CLASS VARIABLES

Top

$Log::Localized::VERBOSITY

See 'LOGGING ALGORITHM' for details and 'SYNOPSIS' for examples.

$Log::Localized::LEVEL

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.

WARNING

Top

Forking, threading Log::Localized is not designed to be used simultaneously by multiple threads or processes.
Monitoring rule changes Log::Localized loads the verbosity rules only once, at 'use' time, and will not notice later changes of the rules file during runtime. This may be implemented in the future.

SEE ALSO

Top

See Log::Dispatch, Log::Log4perl.

BUGS AND LIMITATIONS

Top

Please report any bugs or feature requests to bug-log-localized@rt.cpan.org, or through the web interface at http://rt.cpan.org.

FOR FUN

Top

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'...

AUTHOR

Top

Written by Erwan Lemonnier <erwan@cpan.org> and co-designed by Claes Jacobsson <claesjac@cpan.org>.

COPYRIGHT AND LICENSE

Top

DISCLAIMER OF WARRANTY

Top

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__