cmhughes cmhughes - 25 days ago 6
Perl Question

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

Answer Source

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.

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.

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