|
From: <log...@pe...> - 2002-12-10 23:31:24
|
Welcome to the Log::Log4perl recipe of the week. Today:
============================================================
Log::Log4perl Recipe of the Week (#7):
How can I hook up the LWP library with Log::Log4perl?
============================================================
Or, to put it more generally: How can you utilize a
third-party library's embedded logging and debug statements
in Log::Log4perl? How can you make them print to
configurable appenders, turn them on and off, just as if
they were regular Log::Log4perl logging statements?
The easiest solution is to map the third-party library
logging statements to Log::Log4perl's stealth loggers via a
typeglob assignment.
As an example, let's take LWP, one of the most popular Perl
modules, which makes handling WWW requests and responses a
breeze. Internally, LWP uses its own logging and debugging
system, utilizing the following calls inside the LWP code
(from the LWP::Debug man page):
# Function tracing
LWP::Debug::trace('send()');
# High-granular state in functions
LWP::Debug::debug('url ok');
# Data going over the wire
LWP::Debug::conns("read $n bytes: $data");
First, let's assign Log::Log4perl priorities to these
functions: I'd suggest that "debug()" messages have priority
"INFO", "trace()" uses "DEBUG" and "conns()" also logs with
"DEBUG" -- although your mileage may certainly vary.
Now, in order to transpartently hook up LWP::Debug with
Log::Log4perl, all we have to do is say
package LWP::Debug;
use Log::Log4perl qw(:easy);
*trace = *INFO;
*conns = *DEBUG;
*debug = *DEBUG;
package main;
# ... go on with your regular program ...
at the beginning of our program. In this way, every time
the, say, "LWP::UserAgent" module calls
"LWP::Debug::trace()", it will implicitely call INFO(),
which is the "info()" method of a stealth logger defined for
the Log::Log4perl category "LWP::Debug". Is this cool or
what?
Here's a complete program:
use LWP::UserAgent;
use HTTP::Request::Common;
use Log::Log4perl qw(:easy);
Log::Log4perl->easy_init(
{ category => "LWP::Debug",
level => $DEBUG,
layout => "%r %p %M-%L %m%n",
});
package LWP::Debug;
use Log::Log4perl qw(:easy);
*trace = *INFO;
*conns = *DEBUG;
*debug = *DEBUG;
package main;
my $ua = LWP::UserAgent->new();
my $resp = $ua->request(GET "http://amazon.com");
if($resp->is_success()) {
print "Success: Received ",
length($resp->content()), "\n";
} else {
print "Error: ", $resp->code(), "\n";
}
This will generate the following output on STDERR:
174 INFO LWP::UserAgent::new-164 ()
208 INFO LWP::UserAgent::request-436 ()
211 INFO LWP::UserAgent::send_request-294 GET http://amazon.com
212 DEBUG LWP::UserAgent::_need_proxy-1123 Not proxied
405 INFO LWP::Protocol::http::request-122 ()
859 DEBUG LWP::Protocol::collect-206 read 233 bytes
863 DEBUG LWP::UserAgent::request-443 Simple response: Found
869 INFO LWP::UserAgent::request-436 ()
871 INFO LWP::UserAgent::send_request-294
GET http://www.amazon.com:80/exec/obidos/gateway_redirect
872 DEBUG LWP::UserAgent::_need_proxy-1123 Not proxied
873 INFO LWP::Protocol::http::request-122 ()
1016 DEBUG LWP::UserAgent::request-443 Simple response: Found
1020 INFO LWP::UserAgent::request-436 ()
1022 INFO LWP::UserAgent::send_request-294
GET http://www.amazon.com/exec/obidos/subst/home/home.html/
1023 DEBUG LWP::UserAgent::_need_proxy-1123 Not proxied
1024 INFO LWP::Protocol::http::request-122 ()
1382 DEBUG LWP::Protocol::collect-206 read 632 bytes
...
2605 DEBUG LWP::Protocol::collect-206 read 77 bytes
2607 DEBUG LWP::UserAgent::request-443 Simple response: OK
Success: Received 42584
Of course, in this way, the embedded logging and debug
statements within LWP can be utilized in any Log::Log4perl
way you can think of. You can have them sent to different
appenders, block them based on the category and everything
else Log::Log4perl has to offer.
Only drawback of this method: Steering logging behaviour via
category is always based on the "LWP::Debug" package.
Although the logging statements reflect the package name of
the issuing module properly, the stealth loggers in
"LWP::Debug" are all of the category "LWP::Debug". This
implies that you can't control the logging behaviour based
on the package that's *initiating* a log request (e.g.
LWP::UserAgent) but only based on the package that's
actually *executing* the logging statement, "LWP::Debug" in
this case.
To work around this conundrum, we need to write a wrapper
function and plant it into the "LWP::Debug" package. It will
determine the caller and create a logger bound to a category
with the same name as the caller's package:
package LWP::Debug;
use Log::Log4perl qw(:levels get_logger);
sub l4p_wrapper {
my($prio, @message) = @_;
$Log::Log4perl::caller_depth += 2;
get_logger(caller(1))->log($prio, @message);
$Log::Log4perl::caller_depth -= 2;
}
no warnings 'redefine';
*trace = sub { l4p_wrapper($INFO, @_); };
*debug = *conns = sub { l4p_wrapper($DEBUG, @_); };
package main;
# ... go on with your main program ...
This is less performant than the previous approach, because
every log request will request a reference to a logger
first, then call the wrapper, which will in turn call the
appropriate log function.
This hierarchy shift has to be compensated for by increasing
$Log::Log4perl::caller_depth by 2 before calling the log
function and decreasing it by 2 right afterwards. Also, the
"l4p_wrapper" function shown above calls caller(1) which
determines the name of the package *two* levels down the
calling hierarchy (and therefore compensates for both the
wrapper function and the anonymous subroutine calling it).
"no warnings 'redefine'" suppresses a warning Perl would
generate otherwise upon redefining "LWP::Debug"'s "trace()",
"debug()" and "conns()" functions. In case you use a perl
prior to 5.6.x, you need to manipulate $^W instead.
Have fun! Until next week.
-- Mike
###################################
# Mike Schilli #
# log...@pe... #
# http://perlmeister.com #
# http://log4perl.sourceforge.net #
###################################
|