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