package Log::ger::Manual::Tutorial::69_WritingAFormatPlugin;

# DATE
# VERSION

1;
# ABSTRACT: Writing a format plugin

__END__

=pod

=encoding UTF-8

=head1 NAME

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

=head1 VERSION

version 0.028.004

=head1 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,
L<Log::Any> (and L<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 L<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 L<Log::Dispatchouli>
by using L<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 C<get_hooks> which returns a
hashref of phase names and hook records. For a format plugin, the relevant phase
is C<create_formatter>. This hook will be called when L<Log::ger> wants to
construct a formatter.

The hook record is an arrayref of 3 elements:

 [$key, $prio, $coderef]

C<$key> is usually the name of the module (C<__PACKAGE__>). C<$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. C<$coderef>
is the actual hook. Our hook will receive a hash arguments (C<%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 logging routine. It should return the formatted log message.

=head2 Using different formats for different logging 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 I<log> methods
(C<warn>, C<debug>, C<info>, and so on) and I<logf> (or formatting) methods
(C<warnf>, C<debugf>, C<infof>, and so on). I<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 I<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 C<default>, then use it when constructing
the logging routines. L<Log::ger::Plugin::LogAny> from L<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 [{
                     log_subs    => [map { (["log_$_", $_, "join"], ["log_${_}f", $_, "default"]) }
                                         @$levels],
                     is_subs     => [map { ["log_is_$_", $_] } @$levels],
                     log_methods => [map { (["$_", $_, "join"], ["${_}f", $_, "default"]) }
                                         @$levels],
                     is_methods  => [map { ["is_$_", $_] } @$levels],
                 }, 1];
             }],
     };
 }

The above code create a formatter named "join" which, as its name suggests, only
joins the arguments. The C<create_routine_names> hook then creates the C<log>
methods (C<warn>, C<info>, C<debug>, ...) as well as subroutines (C<log_warn>,
C<log_info>, C<log_debug>, ...) using the C<join> formatter. While the C<logf>
methods (C<warnf>, C<infof>, C<debugf>, ...) and subroutines (C<log_warnf>,
C<log_infof>, C<log_debugf>, ...) use the C<default> formatter. This mimics
Log::Any.

=head1 SEE ALSO

L<Log::ger::Manual::Internals>

=head1 AUTHOR

perlancar <perlancar@cpan.org>

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 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.

=cut
