NAME
Log::ger::Manual::Tutorial::690_WritingAFormatPlugin - Writing a format plugin
VERSION
version 0.040.000
DESCRIPTION
The goal of a format plugin is to allow log producers to log using the style that they are most comfortable with, with regard to arguments. This is one aspect where logging frameworks are different from one another. For example, Log::Any (and Log::ger, by default) use sprintf style and let you dump data structure as well:
$log->warnf("Foo is larger than 100: %5d", $foo); # in Log::Any
log_debug("The contents of data structure: %s", $data); # in Log::ger
Other framework like Log::Contextual uses block style:
log_warn { "foo is larger than 100: " . $foo };
log_debug { require Data::Dump; "Contents of data structure: ".Data::Dump::dump($data) };
Apart from preference, some style offers advantages over the other. The block style, for example, defers potentially heavy calculation until the log message is actually produced. Log::ger lets you choose a style which you prefer, even lets you log using different styles in different packages, by using a different format plugin for each package.
package MyApp::Module1;
use Log::ger;
sub foo {
log_debug("The contents of data structure: %s", $data);
}
package MyApp::Module2;
use Log::ger::Format 'Block';
use Log::ger;
sub bar {
log_debug { require Data::Dump; "Contents of data structure: ".Data::Dump::dump($data) };
}
Creating a format plugin is easy. Its task is to take arguments and produce the formatted log message. The formatted log message can be further decorated with additional information like timestamp or program location (source path and line number), but this is task of the layout plugin.
Here's an example of a format plugin to let you block a la Log::Dispatchouli by using String::Flogger.
# in lib/Log/ger/Format/Flogger.pm
package Log::ger::Format::Flogger;
use strict;
use warnings;
use String::Flogger qw(flog);
sub meta { +{
v => 2,
} }
sub get_hooks {
my %conf = @_;
return {
create_formatter => [
__PACKAGE__, # key
50, # priority
sub { # hook
my %hook_args = @_;
my $formatter = \&flog;
[$formatter];
}],
};
}
1;
First of all, the plugin needs to define meta()
that returns a hashref where the required key is v
set to 2. This is a way to do API versioning so Log::ger can reject plugins with incompatible API version.
Basically, in the format plugin you need to define get_hooks
which returns a hashref of phase names and hook records. For a format plugin, the relevant phase is create_formatter
. This hook will be called when Log::ger wants to construct a formatter.
The hook record is an arrayref of 3 elements:
[$key, $prio, $coderef]
$key
is usually the name of the module (__PACKAGE__
). $prio
is priority for ordering when there are multiple plugins for the same hook, a number between 0-100 (the lower the number, the higher the priority), normally 50. $coderef
is the actual hook. Our hook will receive a hash arguments (%hook_args
) and is expected to return the result:
[$formatter, ...]
The formatter is another coderef which will be passed the arguments that are passed to a logger routine. It should return the formatted log message.
Using different formats for different logger routines
Log::ger allows using different formats for different logging methods. This allows it to mimic Log::Any, for example. In Log::Any, there are log methods (warn
, debug
, info
, and so on) and logf (or formatting) methods (warnf
, debugf
, infof
, and so on). log methods simply join their arguments to form the formatted log message, e.g.:
$log->warn("The user", $user, "has not logged in for more than 30 days");
The formatted log message will be the arguments joined by a single space, e.g.:
The user budi has not logged in for more than 30 days
The logf methods, like in Log::ger, treats the first argument as the sprintf-style format string and the rest of the arguments as the values to fill the format, e.g.:
$log->warnf("The user %s has not logged in for more than %d days", $user, 30);
To allow Log::ger to emulate this Log::Any behavior, you can create a formatter and name it as something other than default
, then use it when constructing the logger routines. Log::ger::Plugin::LogAny from Log::ger::Like::LogAny distribution does this:
package Log::ger::Plugin::LogAny;
use strict;
use warnings;
use Log::ger ();
sub get_hooks {
my %conf = @_;
return {
create_formatter => [
__PACKAGE__, 50,
sub {
my $formatter = sub {
return join " ", @_;
};
return [$formatter, 0, 'join'];
},
],
create_routine_names => [
__PACKAGE__, 50,
sub {
my %args = @_;
my $levels = [keys %Log::ger::Levels];
return [{
logger_subs => [map { (["log_$_", $_, "join"], ["log_${_}f", $_, "default"]) } @$levels],
logger_methods => [map { (["$_" , $_, "join"], ["${_}f" , $_, "default"]) } @$levels],
level_checker_subs => [map { ["log_is_$_", $_] } @$levels],
level_checker_methods => [map { ["is_$_", $_] } @$levels],
}, 1];
}],
};
}
The above code create a formatter named "join" which, as its name suggests, only joins the arguments. The create_routine_names
hook then creates the log
methods (warn
, info
, debug
, ...) as well as subroutines (log_warn
, log_info
, log_debug
, ...) using the join
formatter. While the logf
methods (warnf
, infof
, debugf
, ...) and subroutines (log_warnf
, log_infof
, log_debugf
, ...) use the default
formatter. This mimics Log::Any.
SEE ALSO
AUTHOR
perlancar <perlancar@cpan.org>
COPYRIGHT AND LICENSE
This software is copyright (c) 2022, 2020, 2019, 2018, 2017 by perlancar <perlancar@cpan.org>.
This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.