|
From: Pete S. <si...@uc...> - 2004-01-05 22:48:11
|
Thanks for the replies (see below). Basically, Mike Schilli and Kevin
Goess provided the same solution, based on using the "caller" function.
This solution works perfectly for me. Jim Cromie answered in depth, and
expressed concern that the solution would be prohibitively expensive. I
tested the expense and found it to be minor (see below).
Thanks again, guys! I got just what I wanted.
Pete> I'd like log4perl to indent messages to show caller depth in the
Pete> call stack. A sample of this style is
Pete>
Pete> main: starting
Pete> function1: called with arg1, arg2
Pete> function1: returning value1
Pete> function2: called
Pete> function3: called
Pete> function4: called with arg4
Pete> function4: returning
Pete> function3: returning
Pete> function2: returning
Pete> main: ending
Pete>
Pete> For years, I've been doing this by passing around a scalar named
Pete> $CallDepth, and incrementing/decrementing it at function
Pete> entry/exit. I pass around a second scalar named $DbgLevel that
Pete> works sorta like the levels in log4perl, but with more levels.
Mike> If you don't mind the expense of a bunch of caller() calls per
Mike> logged message, here's a solution:
Mike>
Mike> use Log::Log4perl qw(get_logger);
Mike>
Mike> ####################
Mike> sub level {
Mike> ####################
Mike> my $i = 0;
Mike> $i++ while caller($i);
Mike> return $i-6;
Mike> }
Mike>
Mike> my $conf = q(
Mike> log4perl.PatternLayout.cspec.Y = sub { " " x level() }
Mike> log4perl.category = WARN, Logfile
Mike> log4perl.appender.Logfile = Log::Log4perl::Appender::Screen
Mike> log4perl.appender.Logfile.layout = PatternLayout
Mike> log4perl.appender.Logfile.layout.ConversionPattern = %Y%m%n
Mike> );
Mike>
Mike> Log::Log4perl::init(\$conf);
Mike>
Mike> my $logger = get_logger("");
Mike> $logger->error("first");
Mike> bar();
Mike> $logger->error("first");
Mike>
Mike> sub bar { $logger->error("second"); foo(); }
Mike> sub foo { $logger->error("third"); }
Mike>
Mike> __END__
Mike>
Mike> The $i-6 expression might need to be adapted if caller_level is
Mike> set to anything but 0. Here's the output:
Mike>
Mike> first
Mike> second
Mike> third
Mike> first
Mike>
Mike> -- Mike
Thanks!
Kevin> | sub level {
Kevin> | my $i = 0;
Kevin> | $i++ while caller($i);
Kevin> | return $i-6;
Kevin> | }
Kevin>
Kevin> The $i-6 expression might need to be adapted if caller_level is
Kevin> set to anything but 0.
Kevin>
Kevin> It seems like we should make that magic number 6 a log4perl
Kevin> constant or something, doesn't it? It's the number of levels
Kevin> between an application and a cspec, is it?
Kevin>
Kevin> Kevin M. Goess
I agree that the "6" should be a constant. I suggest that the above
subroutine be added to the documentation of Log4perl as an example of
how to use custom cspecs.
Jim> I have several reactions:
Jim>
Jim> 1. Not gonna happen - using caller() on every invocation would be
Jim> the only way to get stack depth, and is horrifically expensive
Jim> for an application-wide logging facility. I once proposed
Jim> that caller(1) be used to provide more info to filtering
Jim> operations, and that was deemed infeasible due to efficiency
Jim> concerns. Your idea would require N times as many (log2(N) if
Jim> you're clever) uses of caller in order to get the depth of the
Jim> stack.
Within reason, I don't care if the application slows down when I turn on
logging.
I wrote a small test: 100000 calls to a function that logs a message and
calls a second function that logs a message. I used a
"Log::Dispatch::File" appender and a PatternLayout of "%m%n". It took
58 seconds on my 1.2Ghz Pentium under Linux with Perl 5.6.1. To make
the same program use caller, I changed the PatternLayout to "%Z%m%n" and
added
Log::Log4perl::Layout::PatternLayout::add_global_cspec('Z',
sub {
my $lvl = 0;
$lvl++ while caller($lvl);
return ' ' x ($lvl-6);
}
);
The output file had the indentation, and it took only 62 seconds to run.
A surprisingly small hit!
Jim> 2. I didn't really like that answer, but it was true. So I went
Jim> digging into optimizer.pm, and eventually wrote
Jim> Log::Log4perl::Autocategorize. In this package, AUTOLOAD
Jim> catches all logging calls, uses caller() to figure out who
Jim> called, consults L4perl to determine whether to log it, and
Jim> does so.
Jim>
Jim> my $predicate = "is_$level";
Jim> eval { $runnable = $log->$predicate() };
Jim>
Jim> Now, here's the tricky part - because I used optimizer to
Jim> munge the method-name into a new, unique name (ex: from debug
Jim> to debug_$somevalue), I could create a custom subroutine to do
Jim> the right thing FOR THAT CALL-POINT. Thereafter - calls to
Jim> that subroutine (ie FROM THAT CALLER) were handled at
Jim> full-speed, and with full customized behavior.
Jim>
Jim> It would not be hard to extend to accommodate your desires,
Jim> WITH SOME CAVEATS
Jim>
Jim> a. Depth must be perfectly correlated to the invoker.
Jim>
Jim> The simplest explanation is by way of a counter-example: a
Jim> debug_print() utility would likely be used from dozens of
Jim> places, and from different depths of the code. These
Jim> differences would not be seen by my package, since the
Jim> logging call is coming from debug_print, not from its many
Jim> users. To amplify this point - indent-level could easily be
Jim> wrong, and undetectably so.
I don't think this is a scheme to pursue, especially since "caller"
seems efficient enough. This adds even more complexity to an
already-complex system.
Jim> b. L4P has no way to express filtering based on call-stack.
Jim>
Jim> I extended the meaning of 'logging category' to carry all
Jim> the info that caller() provides, thus you can filter on
Jim> class, subroutine, line number.
Jim>
Jim> log4perl.category.Frob.nicate.debug = INFO # 2
Jim> log4perl.category.Frob.nicate.debug.118 = DEBUG # 3
Jim> #log4perl.category.Frob.Vernier.twiddle.debug = DEBUG # 4
Jim>
Jim> But this runs out of gas after 1st caller level. There's
Jim> no obvious/clean way to extend this to operate on results
Jim> of caller(N). I discuss some options in my POD, please
Jim> read there, but here's a strawman:
Jim>
Jim> log4perl.caller(2).Frob.nicate.debug = INFO # 2
Jim>
Jim> There are problems with this - its hard (impossible?) to
Jim> know whether is_debug() says yes/no due to an exact match
Jim> or a bubble-up match, hence its hard to know whether a
Jim> level-N filter is even pertinent. And it would be silly to
Jim> invoke caller(42) to check for filters speced at that level
Jim> - on average, there won't be.
I don't think this is a path to pursue.
Jim> c. your idea could/should extend to nested lexical scopes
Jim> within subroutines, but that's a whole nuther can of worms.
I disagree that it could/should be extended, and I agree that to do so
would be a whole nuther can of worms. Lexical scopes are not a runtime
thing, so I think it would get confusing fast.
Jim> d. I'm unwilling to extend Autocat in ways that appear to be
Jim> incompatible with L4P. And since L4P strives to be
Jim> compatible with L4J, Id expect some reluctance there to do
Jim> whats needed to the configs. Hopefully L4J has reserved
Jim> some part of the config-space to accomodate special
Jim> needs/features (such as 'log4perl.caller(2)' above)
Actually, this brings up a problem for me. I have big Java projects and
big Perl projects. I hope to standardize on a logging framework that I
can use in both languages. I have no idea (yet) whether L4J can be
coaxed to offer indented log messages.
Jim> 3. Have you looked at Hook::Scope ? Its more deep magic that I
Jim> done't understand well, but it can hang/hook actions on scope
Jim> entry/exit that you could use in a wrapper class.
I got/unpacked it, and I confess it's deeper magic than I want to
use. I am still struggling with the OO paradigm!
Jim> 4. IIRC, Log::Agent can represent enter, exits via indentation
I used Log::Agent for a while before I learned about Log4perl.
Log::Agent worked fine, and I could do the indenting, but I still had to
manage a $CallDepth variable. Log::Agent has the advantage that it has
simple numeric levels, which I miss in Log4perl - I haven't yet learned
how to translate a simple numeric command-line option into Log4perl
categories. Log::Agent made me think about the difference between
tracing messages, debug messages and log messages, which was good. I'm
now investing in Log4perl because I want to use the same logging
framework in Perl and Java. And there's a book about log4j, which
applies to Log4perl. And it's powerful.
Thanks again!
-- Pete
|