NAME

Log::ger::Manual::Tutorial::690_WritingAFormatPlugin - Writing a format plugin

VERSION

version 0.033.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 get_hooks {
     my %conf = @_;

     return {
         create_formatter => [
             __PACKAGE__, # key
             50,          # priority
             sub {        # hook
                 my %hook_args = @_;

                 my $formatter = \&flog;
                 [$formatter];
             }],
     };
 }
 1;

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

Log::ger::Manual::Internals

AUTHOR

perlancar <perlancar@cpan.org>

COPYRIGHT AND LICENSE

This software is copyright (c) 2020, 2019, 2018, 2017 by 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.