cmhughes
cmhughes

Reputation: 923

log4perl: grouping messages

I'm using log4perl to log messages from a perl script. With mwe.pl as below, then I receive the following (desired) output in test.log

INFO: some information
      more information

My current implementation uses:

my $logmessage = "some information\n";
$logmessage .= "more information";
$logger->info($logmessage);

Notice in particular that I have specified the line break manually using \n, which I'd like to avoid.

Is there a way that I can achieve my desired output (test.log), without having to scaffold my logging input?

mwe.pl

#!/usr/bin/env perl
use strict;
use warnings;
use Log::Log4perl qw(get_logger :levels);

my $logger = get_logger();
$logger->level($INFO);

my $layout = Log::Log4perl::Layout::PatternLayout->new("%p: %m{indent}%n");
my $appender = Log::Log4perl::Appender->new(
    "Log::Dispatch::File",
    filename => "test.log",
    mode     => "write",
);

$appender->layout($layout);
$logger->add_appender($appender);

my $logmessage = "some information\n";
$logmessage .= "more information";
$logger->info($logmessage);

exit(0);

Upvotes: 3

Views: 319

Answers (1)

zdim
zdim

Reputation: 66881

One way is to add custom "cspecs" to your PatternLayout. Using an API

Log::Log4perl::Layout::PatternLayout::add_global_cspec(
    'A', sub { ... }
);                    # can now use %A

where this needs to come before the call to new which can then use the %A specifier.

This can be set up in configuration instead, as shown in linked docs. Or add_global_cspec method can be called on the $layout object (but I couldn't figure out the interface.)

The anonymous sub receives

($layout, $message, $category, $priority, $caller_level)  

layout: the PatternLayout object that called it
message: the logging message (%m)
category: e.g. groceries.beverages.adult.beer.schlitz
priority: e.g. DEBUG|WARN|INFO|ERROR|FATAL
caller_level: how many levels back up the call stack you have to go to find the caller

what can be used to implement criteria for formatting the prints.

Here is a simple example custom-specifying the whole format

use strict;
use warnings;
use Log::Log4perl qw(get_logger :levels);

my $logger = get_logger();
$logger->level($INFO);

Log::Log4perl::Layout::PatternLayout::add_global_cspec( 
    'A', sub { return ( 
        $_[1] !~ /^more/                 # /^more/ taken to indicate 
           ?  "$_[3]: "                  # the continuation criterion,
           :  ' ' x length $_[3] . '  '  # or start with 'INFO: '
    ) . $_[1]
});

my $layout = Log::Log4perl::Layout::PatternLayout->new("%A%n");

my $appender = Log::Log4perl::Appender->new(
    "Log::Dispatch::File",
    filename => "new_test.log",
    mode     => "write",
);

$logger->info('some info');
$logger->info('more info');

$logger->info('info');
$logger->info('more and more info');

which prints

INFO: some info
      more info
INFO: info
      more and more info

Such a custom specifier can of course be combined with the provided ones.

Since the list in info(...) is joined by the logger into a string that is passed along to appender(s) we can decide on the heading in the caller with an apparent interface

$logger->info('*', "... message ...");  # * for heading (add INFO:)

where the first string above is whatever the regex in our cspec looks for.

This formats each log line based on its content. A more rounded option is to write your own appender (FAQ), which is a rather simple class where you can keep and manipulate lines as needed. See an example of bundling messages (FAQ), for instance.

Finally, a proper way to fine-tune how messages are selected is by adding a category. Then you can pull a new logger and configure it to display INFO: (for the header line), while the rest of messages in that group go by the other logger, configured to not display it. See this post for a simple example.

The downside is that there is now a lot more to do with loggers, appenders, and layout, and all that for only a small tweak in this case.

If these don't fit the bill please clarify how you decide which prints are to be grouped.

Upvotes: 3

Related Questions