Requesting feedback on new module: Log::Any
am 07.09.2007 00:49:35 von Jonathan SwartzThis is a proposal for a small package, Log::Any, that provides
modules with a standard log API while leaving the choice of log
framework and configuration to the application.
TOO MANY WAYS TO LOG
It seems as if every CPAN module has its own way of logging debug
information and error conditions. For example:
* LWP - activate by use'ing LWP::Debug; outputs to STDERR
* DBI - activate by calling DBI->trace(); outputs to STDERR or a
file
* Rose::DB - activate by setting various $Debug package variables;
outputs to STDERR
* Encode::* - activate by modifying various DEBUG subroutines to
return 1; outputs using warn()
* Apache::* - activate by setting the Apache log level and
restarting; outputs to the Apache logs
There are no doubt other CPAN modules that have interesting things to
say but choose not to log at all, because they don't want to invent
another logging mechanism or become dependent on an existing one.
This situation is pretty much the opposite of what I want when
developing a large application. I want a single way to turn logging on
and off, and to control where logs get sent, for all of the modules
I'm using.
This being Perl, there are many fine logging frameworks available:
Log::Log4perl, Log::Dispatch, Log::Handler, Log::Agent, Log::Trivial,
etc. So why do CPAN modules eschew the use of these and invent their
own mechanisms that are almost guaranteed to be less powerful?
* The very existence of so many logging modules means that there is
no one standard that a CPAN author would feel comfortable binding
their users to. As usual, TMTOWTDI is a double-edged sword.
* A logging framework can be a significant dependency for a module
to have, easily dwarfing the size of the module itself. For small
modules that want to minimize dependencies, depending on Log4perl (for
example) is a non-starter.
A COMMON LOG API
One thing to notice is that while the logging frameworks all differ in
their configuration and activation API, and the set of features they
support, the API to log messages is generally quite simple. At its
core it consists of
* A set of valid log levels, e.g. debug, info, warn, error, fatal
* Methods to log a message at a particular level, e.g. $log-
>debug()
* Methods to determine if a particular level is activated, e.g.
$log->is_debug()
I expect most CPAN modules would happily stick to this API, and let
the application worry about configuring what's getting logged and
where it's going. Therefore...
PROPOSED MODULE: LOG::ANY
I propose a small module called Log::Any that provides this API, with
no dependencies and no logging implementation of its own. Log::Any
would be designed to be linked by the main application to an existing
logging framework.
A CPAN module would use it like this:
package Foo;
use Log::Any;
my $log = Log::Any->get_logger(category => __PACKAGE__);
$log->debug("a debug message")
if $log->is_debug();
$log->error("yikes!");
As a convenient shorthand, you can use
package Foo;
use Log::Any qw($log);
$log->debug("a debug message")
if $log->is_debug();
where $log is a newly created logger object, with the category
initialized from the package name of the caller. $log is necessarily
package-scoped, not lexically scoped.
By default, methods like $log->debug would be no-ops, and methods like
$log->is_debug() would return false. So you can sprinkle logging
statements liberally throughout your module and applications won't see
them by default.
An application that wished to activate logging would call Log::Any-
>set_logger with a single argument: a subroutine that takes a log
category and returns a logger object implementing the standard logging
API above. The log category is typically the class doing the logging,
and it may be ignored.
For example, to link with Log::Log4perl:
use Log::Any;
use Log::Log4perl;
Log::Log4perl->init("log.conf");
Log::Any->set_logger(sub { Log::Log4perl->get_logger(@_) });
To link with Log::Dispatch, with all categories going to the screen:
use Log::Any;
use Log::Dispatch;
my $dispatcher = Log::Dispatch::Screen->new(...);
Log::Any->set_logger(sub { $dispatcher });
To link with Log::Dispatch, with different categories going to
different dispatchers:
use Log::Any;
use Log::Dispatch;
my $dispatcher_screen = Log::Dispatch::Screen->new(...);
my $dispatcher_file = Log::Dispatch::File->new(...);
sub choose_dispatcher {
my $category = shift;
return $category =~ /DBI|LWP/ ? $dispatcher_file :
$dispatcher_screen;
}
Log::Any->set_logger(\&choose_dispatcher);
set_logger will be implemented so as to take effect on all existing as
well as future loggers. Any $log objects already created inside
modules will automatically be switched when set_logger is called.
(i.e. $log will probably be a thin proxy object.) This avoids imposing
any order on module loading, and allows set_logger to be called more
than once per application.
PROMOTING USE
For Log::Any to be useful, a substantial number of modules -
especially major modules - would have to adopt its use. Fortunately,
with its minimal footprint and standalone nature, authors should not
find Log::Any a difficult dependency to add. Existing logging
mechanisms, such as LWP::Debug and $DBI::tfh, could easily be
converted to write *both* to their existing output streams and to
Log::Any. This would preserve backward compatibility for existing
applications, but allow new applications to benefit from more powerful
logging. I would be willing to submit such patches to major module
authors to get things going.
MODULE NAME
Other potential names for this module:
* Log::Service
* Log::Proxy
* Log::API
However, since many log frameworks themselves have similar "generic"
names (e.g. Log::Dispatcher), I felt that Log::Any was the most
distinct.
FEEDBACK?
Feedback is most welcome. Thanks!
Jon