File Coverage

File:blib/lib/Test/Log/Abstraction.pm
Coverage:99.6%

linestmtbrancondsubtimecode
1package Test::Log::Abstraction;
2
3 - 373
=head1 NAME

Test::Log::Abstraction - Capture log output in tests and assert on it

=head1 VERSION

0.002.0

=head1 SYNOPSIS

=head2 Check what your code logged

    use Test::Most;
    use Test::Log::Abstraction;

    # Give the test logger to the code that you are testing
    my $logger = Test::Log::Abstraction->new();
    my $obj = Some::Class->new(logger => $logger);

    $obj->do_something();

    # Each of these is one TAP test
    $logger->like(qr/updated/, 'do_something() logs that it updated');
    $logger->has_level('error', 'an error was logged');
    $logger->unlike(qr/fatal/, 'nothing fatal was logged');
    is($logger->count(), 3, 'three messages were logged');

    done_testing();

=head2 Check that nothing was logged

    my $logger = Test::Log::Abstraction->new();
    Some::Class->new(logger => $logger)->run();
    $logger->empty('a normal run logs nothing');

=head2 Test several steps with one logger

    my $logger = Test::Log::Abstraction->new();
    my $obj = Some::Class->new(logger => $logger);

    $obj->load('good.csv');
    $logger->empty('good file: no messages');

    $logger->clear();    # forget the messages from the first step
    $obj->load('bad.csv');
    $logger->has_level('warn', 'bad file: a warning');

=head2 Look at the messages yourself

    foreach my $entry (@{ $logger->messages() }) {
        print "$entry->{level}: $entry->{message}\n";
    }

    # Structured fields, from a call such as
    # $logger->info('user logged in', { user => 'alice' })
    is($logger->messages()->[0]->{fields}->{user}, 'alice', 'user field');

=head2 Control what is printed while the test runs

    # Print nothing (the messages are still captured)
    my $quiet = Test::Log::Abstraction->new(diag => 'none');

    # Print everything
    my $loud = Test::Log::Abstraction->new(verbose => 1);

    # Print only errors and more serious messages
    my $errors = Test::Log::Abstraction->new(diag => 'error');

=head2 Test code that checks the log level

    # The code under test does: if($logger->is_debug()) { ... }
    my $logger = Test::Log::Abstraction->new(level => 'warning');
    ok(!$logger->is_debug(), 'debug output is turned off');

=head2 Get this module's own messages in another language

    my $logger = Test::Log::Abstraction->new(lang => 'de');    # German
    my $french = Test::Log::Abstraction->new(country => 'FR');    # French

=head1 DESCRIPTION

=head2 What this module is

Some code writes log messages through a logger object.  In production that
object is usually a L<Log::Abstraction> logger.  In a test, you give the code
a C<Test::Log::Abstraction> object instead.

This object does not write the messages to a file.  It keeps them in a list
in memory.  After the code has run, your test can check the list: was a
message logged, at which level, and what did it say?

It never writes to disk, and it does not load any logging backend.

=head2 Which methods it has

=over 4

=item * B<Log levels.>  The level methods of L<Log::Abstraction>:
C<trace>, C<debug>, C<info>, C<notice>, C<warn>, C<error>, C<fatal>,
C<critical>, C<alert> and C<emergency>.  It also accepts the syslog names
C<warning>, C<err>, C<crit>, C<emerg>, C<panic> and C<informational>.

=item * B<Other logger methods.>  C<level()>, C<is_debug()> and the other
C<is_E<lt>levelE<gt>()> methods, C<messages()> and C<flush()>.  Code under
test may call these, so they work as they do in L<Log::Abstraction>.

=item * B<Test methods.>  C<like>, C<unlike>, C<has_level> and C<empty>.
Each one reports one test result, like C<ok()> in L<Test::More>.

=item * B<Helper methods.>  C<count>, C<clear>, C<verbose> and C<lang>.

=back

=head2 Which messages are printed

Every message is always stored.  This section is only about which messages
are also printed in the test output, as TAP comments (lines that start with
C<#>).

By default, C<warning> and more serious levels are printed.  So if a test
causes a warning by accident, you see it.  C<trace>, C<debug>, C<info> and
C<notice> messages are not printed.

Verbose mode prints every message.  Verbose mode is on when you pass
C<< verbose => 1 >> to C<new()>.  If you do not pass C<verbose>, it is on
when the environment variable C<TEST_VERBOSE> is true (C<prove -v> sets
it), or when C<VERBOSE> is true.

To choose the levels, use the C<diag> option of C<new()>:

=over 4

=item * C<'all'> - print every message.

=item * C<'none'> - print nothing (verbose mode still prints everything).

=item * A level name, such as C<'error'> - print that level and every more
serious level.

=item * A list, such as C<['info', 'error']> - print only these levels.

=back

Put exactly: a message is printed when B<any one> of these is true, and
not otherwise:

=over 4

=item 1. Verbose mode is on.

=item 2. C<diag> is C<'all'>.

=item 3. C<diag> is a list, and the message's level name is in it.

=item 4. C<diag> is a level name, the message's level is a known level, and
its number is the same as, or lower than, that level's number.

=back

So a misspelt level (caught by L</AUTOLOAD>) is printed only by rules 1 to
3; and C<'none'> or an empty list means only rule 1 can apply.

When a test method fails, the messages that explain the failure are printed
under it.  So you can see why it failed without running the test again.

=head2 Global variables are left alone

No method changes C<$@>, C<$!> or C<$_>, and none of them touches an
C<alarm()> timer.  So you can log, or test the log, inside an error
handler, and C<$@> still holds the error afterwards.  (A method that stops
with an error does set C<$@>, as every Perl error does.)

=head2 Levels and how serious they are

Each level has a number.  A lower number means a more serious message.
These are the syslog numbers.

    0  emergency, emerg, panic
    1  alert
    2  critical, crit, fatal
    3  error, err
    4  warning, warn
    5  notice
    6  info, informational
    7  debug, trace

=head2 Language of this module's messages

This module has its own messages: error messages, warnings, and the text
that explains a failed test.  They can be in English (C<en>), German
(C<de>), French (C<fr>) or Simplified Chinese (C<zh>).

The language is chosen like this.  The first rule that gives an answer is
used:

=over 4

=item 1. The C<lang> option, for example C<< lang => 'de' >>.

=item 2. The C<country> option, a two-letter country code such as C<'FR'>.

=item 3. Only when C<< lang => 'auto' >>: the environment variables
C<LC_ALL>, C<LC_MESSAGES> and C<LANG>, in that order.

=item 4. C<$Test::Log::Abstraction::config{lang}>, which is C<'en'>.

=back

The default is English, not the language of your computer.  This is on
purpose: the test output is then the same on every computer.

If a message has no translation, the English message is used.  You can add
or change messages with the C<i18n> option of C<new()>.

This only changes this module's own messages.  The messages that your code
logs are never changed.

=head2 Default settings

The defaults for C<new()> are in the hash
C<%Test::Log::Abstraction::config>.  It has the same keys as the options of
C<new()>.  You can change it in a test, or fill it with
L<Object::Configure>:

    $Test::Log::Abstraction::config{'diag'} = 'none';

A change only affects loggers that are created after it.

You can also pass the result of L<Object::Configure> straight to C<new()>.
Settings in environment variables named C<Test__Log__Abstraction__KEY>
then override the arguments; the extra keys that L<Object::Configure>
adds (such as its own C<logger>) are ignored:

    # In the shell: export Test__Log__Abstraction__diag=none
    my $params = Object::Configure::configure('Test::Log::Abstraction', { level => 'error' });
    my $logger = Test::Log::Abstraction->new($params);

=head1 COMMON PITFALLS

=over 4

=item * B<The test methods are tests.>  C<like>, C<unlike>, C<has_level>
and C<empty> each add one test to the TAP output.  If you give a test
plan (C<< tests => 5 >>), count them.  Or use C<done_testing()>.

=item * B<Messages from earlier steps are still there.>  A logger keeps
every message until you call C<clear()>.  If one logger is used for
several steps, C<like> may match a message from an earlier step.

=item * B<A string pattern is a regular expression.>  C<like('a.c')>
matches C<'abc'>, because C<.> means "any character".  To match the text
exactly, use C<qr/\Qa.c\E/>.

=item * B<Different names for one level are counted apart.>  C<warn> and
C<warning> have the same number, but C<count('warn')> and
C<has_level('warn')> do not see messages logged with C<warning()>.  Check
the name that the code under test uses.  C<fatal> is stored as C<fatal>,
but L<Log::Abstraction> stores it as C<error>.

=item * B<C<undef> is not the same as "no value".>

=over 4

=item * An C<undef> argument to a level method becomes the text
C<undef> in the message.  (L<Log::Abstraction> drops it.)

=item * C<< verbose => undef >> turns verbose mode B<off>.  To use the
environment variables instead, do not pass C<verbose> at all.

=item * C<< diag => undef >> and C<< level => undef >> use the default
from C<%config>.

=item * C<count(undef)> counts all messages.  C<level(undef)> returns the
level number and changes nothing.

=item * C<like(undef)>, C<unlike(undef)> and C<has_level(undef)> stop the
test with an error.

=back

=item * B<One hash reference, or a hash reference at the end.>
C<< $logger->info({ a => 1 }) >> logs the text C<{a =E<gt> 1}>.  But
C<< $logger->info('text', { a => 1 }) >> logs the text C<text> and stores
C<< { a => 1 } >> in C<fields>.  An empty hash at the end is dropped.

=item * B<Copies are shallow (only one level deep).>

=over 4

=item * When a message has C<fields>, the fields hash is copied.  But a
hash or array B<inside> the fields is not copied.  If your code changes it
later, the stored message changes too.  And if the fields contain the
logger itself, the logger is never freed (a reference cycle) until you
call C<clear()>.

=item * C<messages()> returns a new list, but the entries in it are the
stored entries.  Do not change them, unless you want to change what was
captured.

=item * C<new()> keeps its own copy of the C<diag> list and the C<i18n>
tables.  Changing your array or hash afterwards does not change the
logger.  (A part of an C<i18n> table that contains itself is left out of
the copy; it would only ever render as an empty string.)

=item * When you clone a logger with C<< $logger->new(%options) >>, each
option replaces the old option completely.  For example,
C<< $logger->new(i18n => { de => {...} }) >> replaces the whole C<i18n>
hash.  The translations in the old hash are not kept.

=back

=item * B<The C<i18n> option is merged message by message.>  You only
need to give the messages that you want to change.  For each message,
this module looks in your C<i18n> hash first, and then in its own list.
So any message that you do not give keeps its normal text.

=item * B<A misspelt method name does not stop the test.>
C<< $logger->wran('x') >> stores the message under the name C<wran> and
prints a notice.  The test still passes, unless you check the messages.

=item * B<C<prove -v> prints everything.>  C<prove -v> sets
C<TEST_VERBOSE>, which turns verbose mode on.  If a test checks what is
printed, set C<< verbose => 0 >> or C<$ENV{TEST_VERBOSE} = 0>.

=item * B<C<level()> does not hide messages.>  It only changes the answers
of the C<is_*> methods.  Every message is still stored.

=back

=head1 ENCODING

This module never changes the text that your code logs.  It stores each
message exactly as it was given.

=over 4

=item * B<Log messages and fields: any text.>  ASCII, other languages and
emoji are all stored safely.  It does not matter if the text is a Perl
character string (decoded, for example with C<use utf8> or
L<Encode/decode>) or a byte string (for example, UTF-8 bytes read from a
file).

=item * B<Matching with C<like> and C<unlike>.>  The pattern is matched
against the stored text as it is.  So the pattern and the message must be
the same kind of string.  A pattern with a character, such as
C<qr/\x{1F600}/>, does not match the same emoji stored as UTF-8 bytes.

=item * B<Printed messages.>  A Perl character string that has any
character above ASCII is printed as UTF-8.  A byte string is printed
unchanged.  Perl cannot always see the difference: a string that was not
decoded, and has no character above 255 (such as C<"caf\x{e9}">), is
treated as bytes.  If the output already has an encoding layer (for example, set
by L<Test2::Plugin::UTF8>), the text is not encoded again.

=item * B<This module's own messages.>  German, French and Chinese messages
are character strings, and they are printed as UTF-8.  There is one
problem case: a translated message that includes a logged message that is
a non-ASCII byte string.  That part of the text is printed wrongly (it is
encoded twice).  English messages do not have this problem.

=item * B<Options.>  C<lang> and C<country> must be ASCII, in the formats
given under L</new>.  Level names are ASCII.  Templates in the C<i18n>
option may contain any characters, but placeholder names must be ASCII
letters, digits or C<_>.

=item * B<Test names.>  Test names are passed to L<Test::Builder>
unchanged.

=back

=cut
374
375
33
33
1185614
45
use 5.014;
376
33
33
33
39
18
161
use strict;
377
33
33
33
33
19
478
use warnings;
378
33
33
33
2397
83107
56
use autodie qw(:all);
379
380
33
33
33
119348
26
456
use Carp qw(carp croak);
381
33
33
33
2623
64352
164
use Encode ();
382
33
33
33
4951
26649
256
use Hash::Util ();
383
33
33
33
70
214
128
use List::Util 1.33 ();
384
33
33
33
36
15
136
use overload ();
385
33
32
32
3403
62028
542
use Params::Get qw(get_params);
386
32
31
31
5403
83109
534
use Params::Validate::Strict qw(validate_strict);
387
31
31
31
56
18
277
use Readonly;
388
31
31
31
32
19
84
use Scalar::Util ();
389
31
30
30
4724
190575
229
use Sub::Private ();
390
30
30
30
4294
17659
61
use Sub::Protected;
391
30
30
30
4014
151185
712
use Test::Builder ();
392
393our $VERSION = '0.002.0';
394our $AUTOLOAD;
395
396# Routines that only this package may call.  Sub::Private's enforce mode is
397# a process-wide setting, so it is localised here: only these names are
398# wrapped, and any other module's choice of mode is left alone.  Wrapping
399# happens at CHECK time (or at the end of this file when it is loaded at run
400# time), by which point every sub named here has been compiled.
401BEGIN {
402
30
28
        local $Sub::Private::config{'mode'} = 'enforce';
403
30
66
        Sub::Private->import(qw(
404                _args _assert _at_level _build _clone _croak _diag_rule _diags
405                _copy_tree _entry _explain _format _interpolate _matching _object _plural _reason _regex
406                _record _resolve_lang _template _validate _variant
407        ));
408}
409
410# _stringify is deliberately not in that list.  It recurses, and Sub::Private
411# re-enters a wrapped sub with goto from inside its own file, where
412# 'recursion' warnings are on, so logging a structure more than 100 levels
413# deep would warn however this file sets its warnings.
414
415# Defaults for new(); a flat hash so Object::Configure, or a test, can
416# override any of them before loggers are created
417our %config = (
418        diag => 'warning',   # print this level and more severe by default
419        lang => 'en',        # deterministic output unless asked otherwise
420        level => 'trace',    # threshold reported by level() and is_*()
421);
422
423# Level -> severity, lower is more severe, following POSIX syslog priorities.
424# trace and debug share a severity, as they do in Log::Abstraction
425# This table is read on every log call.  Readonly 2 makes a read-only hash
426# by tying it, which turns each lookup into a method call; Hash::Util's
427# lock_hash() makes it just as read-only with no tie.  (A locked hash
428# croaks on a lookup of a missing key, so every lookup is guarded by
429# exists(), as it already had to be for Readonly.)
430my %SEVERITY = (
431        emergency => 0,
432        emerg => 0,
433        panic => 0,
434        alert => 1,
435        critical => 2,
436        crit => 2,
437        fatal => 2,
438        error => 3,
439        err => 3,
440        warning => 4,
441        warn => 4,
442        notice => 5,
443        info => 6,
444        informational => 6,
445        debug => 7,
446        trace => 7,
447);
448Hash::Util::lock_hash(%SEVERITY);
449
450# The levels that Log::Abstraction has is_<level>() predicates for
451Readonly::Array my @PREDICATES => qw(trace debug info notice warn error critical alert emergency);
452
453# diag option values that are not level names
454Readonly::Scalar my $DIAG_ALL => 'all';
455Readonly::Scalar my $DIAG_NONE => 'none';
456
457# lang option value that means "read the POSIX locale variables"
458Readonly::Scalar my $LANG_AUTO => 'auto';
459
460# Catalogue used when a key has no translation in the chosen language
461Readonly::Scalar my $FALLBACK_LANG => 'en';
462
463# Locale environment variables, most specific first, as POSIX defines them
464Readonly::Array my @LOCALE_VARS => qw(LC_ALL LC_MESSAGES LANG);
465
466# Longest explanation kept from another module's error, which may repeat
467# a hostile value of any size
468Readonly::Scalar my $MAX_REASON => 200;
469
470# How many captured messages a failing assertion lists before summarising
471Readonly::Scalar my $MAX_EXPLAIN => 20;
472
473# Shown in place of a reference that contains itself, so rendering a
474# self-referential structure cannot recurse forever
475Readonly::Scalar my $CYCLE => '(cycle)';
476
477# ISO 3166 country code -> catalogue language
478Readonly::Hash my %COUNTRY_LANG => (
479        AT => 'de', AU => 'en', BE => 'fr', CA => 'en', CH => 'de',
480        CN => 'zh', DE => 'de', FR => 'fr', GB => 'en', HK => 'zh',
481        IE => 'en', LU => 'fr', NZ => 'en', SG => 'zh', TW => 'zh',
482        UK => 'en', US => 'en',
483);
484
485# CLDR plural categories per language; 'zero' is not a CLDR category for
486# these languages, so templates may add it as an explicit "=0" case
487Readonly::Hash my %PLURAL => (
488        en => sub { ($_[0] == 1) ? 'one' : 'other' },
489        de => sub { ($_[0] == 1) ? 'one' : 'other' },
490        fr => sub { (abs($_[0]) < 2) ? 'one' : 'other' }, # French counts 0 as singular
491        zh => sub { 'other' },       # Chinese has no grammatical plural
492);
493
494# Message catalogues.  Templates use %{name}s-style placeholders (any sprintf
495# conversion may follow the name), and may be a hash keyed by gender or by
496# plural category instead of a string; see i18n().  Non-English text is
497# written with \x{} escapes to keep this source file ASCII.
498Readonly::Hash my %MESSAGES => (
499        en => {
500                class_invocant => '%{class}s: %{method}s() must be called on an object, not on the class',
501                invalid_argument => '%{class}s: invalid argument: %{reason}s',
502                invalid_diag_level => q{%{class}s: invalid diag level '%{level}s'},
503                invalid_diag_type => '%{class}s: diag must be a level name, "all", "none" or an array reference of level names',
504                invalid_level => q{%{class}s: invalid syslog level '%{level}s'},
505                needs_level => '%{class}s: %{method}s() needs a level name',
506                needs_pattern => '%{class}s: %{method}s() needs a pattern',
507                no_method => q{%{class}s: no method '%{method}s'},
508                captured => {
509                        zero => '%{class}s: no messages were captured',
510                        one => '%{class}s: %{count}d message was captured:',
511                        other => '%{class}s: %{count}d messages were captured:',
512                },
513                matched => {
514                        one => '%{class}s: %{count}d message matched:',
515                        other => '%{class}s: %{count}d messages matched:',
516                },
517                entry => '    [%{level}s] %{message}s',
518                truncated => '    ... and %{count}d more',
519        },
520        de => {
521                class_invocant => '%{class}s: %{method}s() muss an einem Objekt aufgerufen werden, nicht an der Klasse',
522                invalid_argument => "%{class}s: ung\x{fc}ltiges Argument: %{reason}s",
523                invalid_diag_level => "%{class}s: ung\x{fc}ltige diag-Stufe '%{level}s'",
524                invalid_diag_type => '%{class}s: diag muss ein Stufenname, "all", "none" oder eine Array-Referenz von Stufennamen sein',
525                invalid_level => "%{class}s: ung\x{fc}ltige Syslog-Stufe '%{level}s'",
526                needs_level => "%{class}s: %{method}s() ben\x{f6}tigt einen Stufennamen",
527                needs_pattern => "%{class}s: %{method}s() ben\x{f6}tigt ein Muster",
528                no_method => q{%{class}s: keine Methode '%{method}s'},
529                captured => {
530                        zero => '%{class}s: es wurden keine Meldungen erfasst',
531                        one => '%{class}s: %{count}d Meldung wurde erfasst:',
532                        other => '%{class}s: %{count}d Meldungen wurden erfasst:',
533                },
534                matched => {
535                        one => '%{class}s: %{count}d Meldung passte:',
536                        other => '%{class}s: %{count}d Meldungen passten:',
537                },
538                truncated => '    ... und %{count}d weitere',
539        },
540        fr => {
541                class_invocant => "%{class}s : %{method}s() doit \x{ea}tre appel\x{e9}e sur un objet, pas sur la classe",
542                invalid_argument => '%{class}s : argument invalide : %{reason}s',
543                invalid_diag_level => q{%{class}s : niveau diag invalide '%{level}s'},
544                invalid_diag_type => "%{class}s : diag doit \x{ea}tre un nom de niveau, \"all\", \"none\" ou une r\x{e9}f\x{e9}rence de tableau de noms de niveaux",
545                invalid_level => q{%{class}s : niveau syslog invalide '%{level}s'},
546                needs_level => "%{class}s : %{method}s() n\x{e9}cessite un nom de niveau",
547                needs_pattern => "%{class}s : %{method}s() n\x{e9}cessite un motif",
548                no_method => "%{class}s : aucune m\x{e9}thode '%{method}s'",
549                captured => {
550                        zero => "%{class}s : aucun message n'a \x{e9}t\x{e9} captur\x{e9}",
551                        one => "%{class}s : %{count}d message a \x{e9}t\x{e9} captur\x{e9} :",
552                        other => "%{class}s : %{count}d messages ont \x{e9}t\x{e9} captur\x{e9}s :",
553                },
554                matched => {
555                        one => '%{class}s : %{count}d message correspond :',
556                        other => '%{class}s : %{count}d messages correspondent :',
557                },
558                truncated => '    ... et %{count}d de plus',
559        },
560        zh => {
561                # "must be called on an object, not on the class"
562                class_invocant => "%{class}s\x{ff1a}%{method}s() \x{5fc5}\x{987b}\x{5728}\x{5bf9}\x{8c61}\x{4e0a}\x{8c03}\x{7528}\x{ff0c}\x{800c}\x{4e0d}\x{662f}\x{5728}\x{7c7b}\x{4e0a}\x{8c03}\x{7528}",
563                # "invalid argument"
564                invalid_argument => "%{class}s\x{ff1a}\x{65e0}\x{6548}\x{7684}\x{53c2}\x{6570}\x{ff1a}%{reason}s",
565                # "invalid diag level"
566                invalid_diag_level => "%{class}s\x{ff1a}\x{65e0}\x{6548}\x{7684} diag \x{7ea7}\x{522b} '%{level}s'",
567                # "diag must be a level name, all, none or an array reference of level names"
568                invalid_diag_type => "%{class}s\x{ff1a}diag \x{5fc5}\x{987b}\x{662f}\x{7ea7}\x{522b}\x{540d}\x{79f0}\x{3001}\"all\"\x{3001}\"none\" \x{6216}\x{7ea7}\x{522b}\x{540d}\x{79f0}\x{7684}\x{6570}\x{7ec4}\x{5f15}\x{7528}",
569                # "invalid syslog level"
570                invalid_level => "%{class}s\x{ff1a}\x{65e0}\x{6548}\x{7684} syslog \x{7ea7}\x{522b} '%{level}s'",
571                # "needs a level name"
572                needs_level => "%{class}s\x{ff1a}%{method}s() \x{9700}\x{8981}\x{4e00}\x{4e2a}\x{7ea7}\x{522b}\x{540d}\x{79f0}",
573                # "needs a pattern"
574                needs_pattern => "%{class}s\x{ff1a}%{method}s() \x{9700}\x{8981}\x{4e00}\x{4e2a}\x{6a21}\x{5f0f}",
575                # "no method"
576                no_method => "%{class}s\x{ff1a}\x{6ca1}\x{6709}\x{65b9}\x{6cd5} '%{method}s'",
577                captured => {
578                        # "no messages were captured"
579                        zero => "%{class}s\x{ff1a}\x{6ca1}\x{6709}\x{6355}\x{83b7}\x{5230}\x{4efb}\x{4f55}\x{6d88}\x{606f}",
580                        # "captured N messages"
581                        other => "%{class}s\x{ff1a}\x{6355}\x{83b7}\x{4e86} %{count}d \x{6761}\x{6d88}\x{606f}\x{ff1a}",
582                },
583                # "N messages matched"
584                matched => {
585                        other => "%{class}s\x{ff1a}%{count}d \x{6761}\x{6d88}\x{606f}\x{5339}\x{914d}\x{ff1a}",
586                },
587                # "... N more"
588                truncated => "    \x{2026}\x{2026}\x{8fd8}\x{6709} %{count}d \x{6761}",
589        },
590);
591
592# What the lang option may look like.  It is written on one line because
593# Params::Validate::Strict quotes it in its error message.  In parts:
594#   \A (?: (?i:auto)              read the locale from the environment
595#     | [A-Za-z]{2,3}             or a language code: 'de', 'eng'
596#       (?: [_.\@-] [\w.\@-]*+ )? then optionally a separator and a
597#                                 territory, codeset or modifier
598#   ) \z
599# The tail is possessive (*+): nothing after it can use characters it gave
600# back, so a long invalid value fails in one pass instead of being retried
601# at every length.
602Readonly::Scalar my $LANG_FORMAT => qr/\A(?:(?i:auto)|[A-Za-z]{2,3}(?:[_.\@-][\w.\@-]*+)?)\z/;
603
604# Schema for new()'s options.  diag is not listed: _diag_rule() validates it,
605# because its messages say what a valid diag is, which a type error cannot
606Readonly::Hash my %NEW_SCHEMA => (
607        verbose => { type => 'scalar', optional => 1 },
608        level => { type => 'string', optional => 1 },
609        lang => { type => 'string', optional => 1, matches => $LANG_FORMAT },
610        country => { type => 'string', optional => 1, matches => qr/\A[A-Za-z]{2}\z/ },
611        i18n => { type => 'hashref', optional => 1 },
612);
613
614# Schema shared by like() and unlike()
615Readonly::Hash my %PATTERN_SCHEMA => (
616        pattern => { type => ['regex', 'string'] },
617        name => { type => 'string', optional => 1 },
618);
619
620# Schema for count()
621Readonly::Hash my %COUNT_SCHEMA => (
622        level => { type => 'string', optional => 1 },
623);
624
625# Schema for empty(): only a test name
626Readonly::Hash my %NAME_SCHEMA => (
627        name => { type => 'string', optional => 1 },
628);
629
630# Schema for has_level()
631Readonly::Hash my %LEVEL_SCHEMA => (
632        level => { type => 'string' },
633        name => { type => 'string', optional => 1 },
634);
635
636 - 799
=head1 METHODS

=head2 new

Create a new test logger.

=head3 Purpose

Make a logger that stores every message it is given, so that your test can
check the messages later.

=head3 Args

All options are optional.  Give them as a list (C<< key => value >>) or as
one hash reference.

=over 4

=item * C<verbose> - true: print every message.  False: use the C<diag>
rule.  If you do not give it, the value of C<$ENV{TEST_VERBOSE}> or
C<$ENV{VERBOSE}> is used.

=item * C<diag> - which messages to print.  C<'all'>, C<'none'>, a level
name (print that level and every more serious level), or an array
reference of level names.  Upper or lower case does not matter.  The
default is C<'warning'>.

=item * C<level> - the level that C<level()> and the C<is_*> methods
report.  The default is C<'trace'>, so every C<is_*> method returns 1, and
the code under test runs all its debug code.  This option does not stop
any message from being stored.

=item * C<lang> - the language of this module's own messages: C<'en'>,
C<'de'>, C<'fr'>, C<'zh'>, a locale name such as C<'de_DE.UTF-8'>, or
C<'auto'> (read the environment).  Must be 2 or 3 ASCII letters, then
optionally C<_>, C<.>, C<@> or C<-> and more text.

=item * C<country> - a two-letter country code such as C<'GB'> or
C<'fr'> (upper or lower case).  Used to choose the language when C<lang>
is not given.

=item * C<i18n> - your own message texts, as
C<< { language => { message_key => template } } >>.  See L</i18n>.

=back

Other options are allowed and ignored.  (A L<Log::Abstraction>
configuration hash can be passed unchanged.)  If you pass an odd number of
arguments, they are all ignored.

=head3 Three ways to call it

=over 4

=item * C<< Test::Log::Abstraction->new(%options) >> - the usual way.

=item * C<< $logger->new(%options) >> - make a B<clone>: a new logger with
the same options, the same C<verbose> and C<level> settings, and a copy of
the stored messages.  The options that you pass replace the old ones.

=item * C<Test::Log::Abstraction::new(%options)> - called as a function.
This works too.

=back

=head3 Returns

The new logger object.

=head3 Side Effects

Reads C<%ENV> to decide on verbose mode, and, with C<< lang => 'auto' >>,
to choose the language.  Nothing else changes.  A clone does not change the
original logger.

=head3 EXAMPLE

    # The usual way
    my $logger = Test::Log::Abstraction->new();

    # Store everything, print nothing
    my $quiet = Test::Log::Abstraction->new(diag => 'none');

    # A hash reference works too; messages in German
    my $german = Test::Log::Abstraction->new({ country => 'DE' });

    # A clone that prints everything; $logger is not changed
    my $loud = $logger->new(diag => 'all');

=head3 API SPECIFICATION

=head4 Input

    {
        verbose => { type => 'scalar', optional => 1 },
        diag => { type => ['string', 'arrayref'], optional => 1 },
        level => { type => 'string', optional => 1 },
        lang => { type => 'string', optional => 1, matches => qr/\A(?:(?i:auto)|[A-Za-z]{2,3}(?:[_.\@-][\w.\@-]*+)?)\z/ },
        country => { type => 'string', optional => 1, matches => qr/\A[A-Za-z]{2}\z/ },
        i18n => { type => 'hashref', optional => 1 },
    }

Domains (valid / invalid / boundaries):

    verbose   valid: any plain value, Perl truth decides ('0', '' and undef
              are off; '00', '0.0' and ' ' are on); absent: from
              TEST_VERBOSE or VERBOSE.  Invalid: any reference.
    diag      valid: 'all' or 'none' (any case); a level name (any case),
              which prints severity 0 (emergency) up to that level's
              number, 7 (trace) being every level; an array reference of
              level names, [] printing nothing and duplicates counting
              once; absent or undef: $config{diag}.  Invalid: any other
              string, including '' and numbers such as '4'; a list
              element that is not a level name (or is 'all'); a hash,
              code or scalar reference.
    level     valid: the 16 level names, any case, setting 0 (emergency)
              to 7 (trace); absent or undef: $config{level}.  Invalid:
              '', numbers (even 0 to 7), any other name.
    lang      valid: 'auto' (any case); 2 or 3 ASCII letters, then
              optionally one of _ . @ - and anything else ('de',
              'eng', 'de_DE.UTF-8', 'zh-Hant').  A code with no catalogue
              ('ja', 'eng') gives 'en'.  Invalid: 1 letter, 4 or more
              letters, '', digits, a non-ASCII letter in the code, any
              whitespace or newline.
    country   valid: exactly 2 ASCII letters, any case; one with no
              mapping ('JP') gives 'en'.  Invalid: 1 or 3 letters, '',
              digits, non-ASCII letters.
    i18n      valid: a hash reference (even empty).  Invalid: any other
              type.

An "invalid argument" explanation, and a level or diag name repeated in an
error, is at most 200 characters; one longer is cut to 200 and ends with
"...".  Control characters in it are shown as C<\xNN>.

=head4 Output

    { type => 'object', isa => 'Test::Log::Abstraction' }

=head3 MESSAGES

All of these stop the program (C<croak>).

    Message                         Meaning                       What to do
    ------------------------------  ----------------------------  ------------------------------
    invalid diag level 'X'          X is not a level name         Use a name from the level table
    diag must be a level name ...   diag is a hash or code ref    Give a string or an array ref
    invalid syslog level 'X'        the level option is unknown   Use a name from the level table
    invalid argument: ...           an option has the wrong type  Fix the option that is named
                                    or format

=head3 PSEUDOCODE

    if new() was called on a logger object:
        options = the object's options, replaced by the new options
        build a logger from the options, with a copy of the messages
        copy verbose and level from the object, unless new values were given
    else:
        if new() was called as a function, the first argument is an option
        turn the arguments into a hash (ignore an odd-length list)
        check the options
        choose the language, then the diag rule, then the level
    return the logger

=cut
800
801sub new {
802
4781
3845544
        my ($class, @args) = @_;
803
804        # Clone form: $logger->new(%overrides), as in Log::Abstraction
805
4781
4123
        return $class->_clone(_args(\@args)) if(Scalar::Util::blessed($class));
806
807        # Function-call form, Test::Log::Abstraction::new(...): whatever was
808        # passed first is an option, not a class name.
809        #   Premise 1: UNIVERSAL::isa() is false, without a warning, for undef,
810        #              '', any unblessed reference and any unrelated name.
811        #   Premise 2: objects were dealt with above.
812        #   So: one isa() test separates "a class name" from "an option".
813
2742
2085
        if(!UNIVERSAL::isa($class, __PACKAGE__)) {
814
12
22
                unshift @args, $class if(defined($class));
815
12
9
                $class = __PACKAGE__;
816        }
817
818
2742
1900
        return $class->_build(_args(\@args), []);
819}
820
821# _args - normalise constructor arguments to a hash reference
822#
823# Purpose:      Accept every argument shape used across the tests that this
824#               module replaces MyLogger in.
825# Entry:        $args - array reference of what the caller passed after the
826#               class or invocant.
827# Exit:         Returns a new hash reference; empty for no arguments or for
828#               an odd-length list that is not a single hash reference.
829# Side effects: None.  The caller's hash is copied, never kept.
830sub _args {
831        my $args = shift;
832
833        # Params::Get croaks on an odd list, and warns on an undefined key; a
834        # stray argument must not break a test run, so only hand it the two
835        # shapes it accepts.  The key check is the costly test, so it runs last,
836        # only for a non-empty, even-length list.
837        my $count = scalar(@{$args});
838        my $usable = (($count == 1) && (ref($args->[0]) eq 'HASH'))
839                || ($count && !($count % 2) && !grep { !defined($args->[$_]) } grep { !($_ % 2) } 0 .. $count - 1);
840        my $params = $usable ? get_params(undef, $args) : undef;
841
842        return { %{$params || {}} };
843}
844
845# _build - construct a logger from validated options
846#
847# Purpose:      The one place a logger's state is set up, shared by new()
848#               and _clone() so the two cannot drift apart.
849# Entry:        $class    - package to bless into.
850#               $options  - hash reference from _args().
851#               $messages - array reference of entries to start with.
852# Exit:         Returns the blessed logger.
853# Side effects: Reads %ENV.  Croaks, in the logger's language, on an
854#               invalid option.
855sub _build {
856        my ($class, $options, $messages) = @_;
857
858        # Keep diag aside: _diag_rule() gives it a better message than a schema
859        my $valid = $class->_validate(\%NEW_SCHEMA, $options);
860        $valid->{'diag'} = $options->{'diag'} if(exists($options->{'diag'}));
861
862        # The diag list and the i18n tables are the caller's structures; copy
863        # them, or changing them later would change this logger (and its
864        # clones).  A diag list holds names, so one level is enough, and keeps
865        # a bad element as it was for _diag_rule() to report.
866        $valid->{'diag'} = [ @{$valid->{'diag'}} ] if(ref($valid->{'diag'}) eq 'ARRAY');
867        $valid->{'i18n'} = _copy_tree($valid->{'i18n'}, {}) if(exists($valid->{'i18n'}));
868
869        # Internal state is kept apart from options, so no option name can
870        # overwrite it (an option called 'messages' once replaced the capture)
871        my $self = bless {
872                messages => $messages,
873                options => $valid,
874                verbose => exists($valid->{'verbose'}) ? ($valid->{'verbose'} ? 1 : 0) : (($ENV{'TEST_VERBOSE'} || $ENV{'VERBOSE'}) ? 1 : 0),
875        }, $class;
876
877        # The language first, so that the checks below report in it
878        $self->{'lang'} = _resolve_lang($valid);
879        # Absent and undef both mean "the default", and _diag_rule() turns undef
880        # into $config{diag}; so the value is passed as it is
881        $self->{'diag_rule'} = $self->_diag_rule($valid->{'diag'});
882
883        # An unknown level at construction is a programming error, so fatal;
884        # level() only carps, as Log::Abstraction's does
885        my $level = defined($valid->{'level'}) ? $valid->{'level'} : $config{'level'};
886        $level = defined($level) ? lc($level) : 'undef';
887        $self->_croak('invalid_level', { level => _shown($level) }) if(!exists($SEVERITY{$level}));
888        $self->{'level'} = $SEVERITY{$level};
889
890        return $self;
891}
892
893# _copy_tree - deep copy of plain hashes and arrays, without cycles
894#
895# Purpose:      Give a logger its own copy of option structures.
896# Entry:        $value - any value.
897#               $path  - hash reference of the addresses of the structures
898#               being copied further up this call chain.
899# Exit:         Returns the copy; anything other than a plain hash or array
900#               (strings, code, objects) is returned as it is.  A reference
901#               back to a structure on the current path becomes undef, so
902#               the copy has no cycles and is freed with the logger.  (A
903#               cyclic template renders as '' either way; see _variant.)
904# Side effects: None on $value.  $path is restored on return.
905sub _copy_tree {
906        my ($value, $path) = @_;
907
908        # Nested option tables are legitimate; Perl's warning at depth 100 is noise
909
30
30
30
66357
30
9352
        no warnings 'recursion';        ## no critic (ProhibitNoWarnings)
910
911        my $type = ref($value);
912        return $value if(($type ne 'HASH') && ($type ne 'ARRAY'));
913
914        my $address = Scalar::Util::refaddr($value);
915        return if($path->{$address});
916        local $path->{$address} = 1;
917
918        return { map { $_ => scalar(_copy_tree($value->{$_}, $path)) } keys %{$value} } if($type eq 'HASH');
919
920        return [ map { scalar(_copy_tree($_, $path)) } @{$value} ];
921}
922
923# _clone - copy a logger, as Log::Abstraction->new does on an object
924#
925# Purpose:      Clone with overrides, re-validating everything so that, for
926#               example, a new diag option really changes the diag rule.
927# Entry:        $self      - logger to copy.
928#               $overrides - hash reference of options to change.
929# Exit:         Returns the clone.
930# Side effects: None on $self; entries are copied so that neither logger can
931#               alter the other's history.
932sub _clone {
933        my ($self, $overrides) = @_;
934
935        my $copy = [ map { +{ %{$_}, ($_->{'fields'} ? (fields => { %{$_->{'fields'}} }) : ()) } } @{$self->{'messages'}} ];
936        my $clone = ref($self)->_build({ %{$self->{'options'}}, %{$overrides} }, $copy);
937
938        # Runtime changes made with verbose() and level() carry over, unless the
939        # caller asked for something else
940        $clone->{'verbose'} = $self->{'verbose'} if(!exists($overrides->{'verbose'}));
941        $clone->{'level'} = $self->{'level'} if(!exists($overrides->{'level'}));
942
943        return $clone;
944}
945
946# _validate - check arguments against a Params::Validate::Strict schema
947#
948# Purpose:      Run validate_strict, reporting failures through i18n so the
949#               message is in the logger's language and blames the caller.
950# Entry:        $self   - logger or class name.
951#               $schema - schema hash reference.
952#               $input  - hash reference of arguments.
953# Exit:         Returns the validated hash reference; unknown keys dropped.
954# Side effects: Croaks with 'invalid_argument' on failure.  Leaves $@ alone
955#               on success.
956sub _validate {
957        my ($self, $schema, $input) = @_;
958
959        # The caller's $@ must survive a successful check
960        local $@;
961        my $valid;
962        if(!eval { $valid = validate_strict(schema => $schema, input => $input, unknown_parameter_handler => 'ignore'); 1 }) {
963                # Keep the validator's explanation, but not its file and line,
964                # which point inside Params::Validate::Strict, not at the caller
965                # The validator may nest its own messages; keep only the innermost
966                (my $reason = $@) =~ s/\A.*validate_strict:\s*//s;
967                $self->_croak('invalid_argument', { reason => _reason($reason) });
968        }
969
970        return $valid || {};
971}
972
973# _reason - strip the location from an error message
974#
975# Purpose:      An error raised inside another module names that module's
976#               file and line; the caller only needs the explanation.
977# Entry:        $error - error text, such as $@.
978# Exit:         Returns the text without a trailing ' at FILE line N.', or
979#               any ' at THIS-FILE line N.' anywhere in it, made safe to
980#               show by _shown().
981# Side effects: None.
982sub _reason {
983        my $error = shift;
984
985        # (?<!\s) lets a match start only where a run of whitespace starts, and
986        # the possessive quantifiers never give characters back; so even a huge
987        # run of spaces is scanned once, not once per space
988        (my $reason = defined($error) ? "$error" : '') =~ s/(?<!\s)\s++at\ \S++\ line\ \d++\.?\s*+\z//sx;
989
990        # Another module reports where this file called it, which means nothing
991        # to our caller, and may do so in the middle of its text
992        $reason =~ s/(?<!\s)\s++at\ \Q${\ __FILE__}\E\ line\ \d++\.?//gx;
993
994        return _shown($reason);
995}
996
997# _shown - make a caller's value safe to repeat in an error message
998#
999# Purpose:      An error that repeats what the caller passed must not let
1000#               that value add lines to the output (a newline followed by
1001#               'ok 5' is a forged TAP result when STDERR is merged into the
1002#               TAP stream), send terminal escape sequences, or make the
1003#               error enormous.
1004# Entry:        $value - the value to show; a string, or undef.
1005# Exit:         Returns the value with control characters written as \xHH,
1006#               cut to $MAX_REASON characters followed by '...' if longer;
1007#               undef is shown as 'undef'.
1008# Side effects: None.
1009sub _shown {
1010
685
983
        my $value = shift;
1011
1012
685
5619
        my $text = defined($value) ? "$value" : 'undef';
1013
685
100014
30480
53823
        $text =~ s/([\x00-\x1F\x7F])/sprintf('\\x%02X', ord($1))/ge;
1014
1015
685
979
        return (length($text) > $MAX_REASON) ? substr($text, 0, $MAX_REASON) . '...' : $text;
1016}
1017
1018# _resolve_lang - choose the catalogue for a logger's own messages
1019#
1020# Purpose:      Apply the documented precedence: lang, country, the POSIX
1021#               locale variables (only for lang => 'auto'), then %config.
1022# Entry:        $options - validated options hash reference.
1023# Exit:         Returns a language tag that has a catalogue (built in, or
1024#               supplied with the i18n option); otherwise $FALLBACK_LANG.
1025# Side effects: Reads %ENV when the language is 'auto'.
1026sub _resolve_lang {
1027        my $options = shift;
1028
1029        my $tag;
1030        if(defined($options->{'lang'}) && (lc($options->{'lang'}) ne $LANG_AUTO)) {
1031                $tag = $options->{'lang'};
1032        } elsif(defined($options->{'country'})) {
1033                # exists() first: a Readonly hash croaks on autovivification
1034                my $country = uc($options->{'country'});
1035                $tag = exists($COUNTRY_LANG{$country}) ? $COUNTRY_LANG{$country} : $FALLBACK_LANG;
1036        } else {
1037                # lang => 'auto' given, or the configured default
1038                my $wanted = defined($options->{'lang'}) ? $options->{'lang'} : $config{'lang'};
1039                my $auto = defined($wanted) && (lc($wanted) eq $LANG_AUTO);
1040
1041                # The first set variable wins, as in POSIX setlocale()
1042                ($tag) = $auto ? (grep { defined($_) && length($_) } map { $ENV{$_} } @LOCALE_VARS) : ($wanted);
1043        }
1044
1045        # 'de_DE.UTF-8' -> 'de'; the C and POSIX locales are English
1046        my ($lang) = (lc(defined($tag) ? $tag : '') =~ /\A([a-z]{2,3})(?![a-z])/);
1047        my $known = defined($lang) && (exists($MESSAGES{$lang}) || ($options->{'i18n'} && $options->{'i18n'}->{$lang}));
1048
1049        return $known ? $lang : $FALLBACK_LANG;
1050}
1051
1052# _diag_rule - normalise the diag option into an internal rule
1053#
1054# Purpose:      Validate the diag option once, at construction time.
1055# Entry:        $self - the logger being built (for its language).
1056#               $spec - 'all', 'none', a level name, or an array reference
1057#               of level names.
1058# Exit:         Returns { all => 1 }, { levels => {...} },
1059#               { threshold => $severity }, or {} for 'none'.
1060# Side effects: Croaks on anything unrecognised: a silent typo in a test's
1061#               diag option would otherwise hide log output forever.
1062sub _diag_rule {
1063        my ($self, $spec) = @_;
1064
1065        $spec = $config{'diag'} if(!defined($spec));
1066
1067        if(ref($spec) eq 'ARRAY') {
1068                my %levels;
1069                foreach my $level (@{$spec}) {
1070                        # 'undef' is not a level name, so an undefined element fails the
1071                        # same test as any other bad name, and is reported as 'undef'
1072                        my $shown = defined($level) ? $level : 'undef';
1073                        $self->_croak('invalid_diag_level', { level => _shown($shown) }) if(!exists($SEVERITY{lc($shown)}));
1074                        $levels{lc($shown)} = 1;
1075                }
1076                return { levels => \%levels };
1077        }
1078
1079        $self->_croak('invalid_diag_type') if(ref($spec));
1080
1081        # 'all' and 'none' are matched without regard to case, as level names
1082        # are.  An undefined default is shown as 'undef', which is neither 'all',
1083        # 'none' nor a level name, so it falls through to the error below.
1084        my $shown = defined($spec) ? $spec : 'undef';
1085        my $name = lc($shown);
1086        return {} if($name eq $DIAG_NONE);
1087        return { all => 1 } if($name eq $DIAG_ALL);
1088        $self->_croak('invalid_diag_level', { level => _shown($shown) }) if(!exists($SEVERITY{$name}));
1089
1090        return { threshold => $SEVERITY{$name} };
1091}
1092
1093 - 1195
=head2 trace, debug, info, notice, warn, error, fatal, critical, alert, emergency

Store a message at this level.

=head3 Purpose

These are the methods that the code under test calls to log something.
There is one method for each L<Log::Abstraction> level, and one for each
syslog name: C<warning>, C<err>, C<crit>, C<emerg>, C<panic> and
C<informational>.  Each method stores the message under the name that was
called, and may print it (see L</Which messages are printed>).

By default:

=over 4

=item * C<trace>, C<debug>, C<info>, C<informational>, C<notice> - stored,
not printed.

=item * C<warn>, C<warning>, C<error>, C<err>, C<critical>, C<crit>,
C<fatal>, C<alert>, C<emergency>, C<emerg>, C<panic> - stored and printed.

=back

=head3 Args

Any list of values.  They are turned into one message like this:

=over 4

=item * All the values are joined together, with nothing between them.

=item * One newline at the end is removed.

=item * If the only value is an array reference, its items are the parts
of the message.

=item * If there are two or more values and the last one is a hash
reference, that hash is not part of the message.  A copy of it is stored as
C<fields>.  An empty hash is dropped.

=item * A hash reference in the message is written as
C<{key =E<gt> value, ...}>, with the keys sorted.  An array reference is
written as C<[a, b]>.  So you can match their contents.

=item * C<undef> is written as the text C<undef>.  There is no warning.

=item * An object is written as Perl normally writes it.  If the object
has its own text form (overloaded C<"">), that form is used.

=item * A structure that contains itself is written as C<(cycle)> at the
point where it repeats.

=back

=head3 Returns

The logger, as in L<Log::Abstraction>.  So you can chain calls:
C<< $logger->info('a')->info('b') >>.

=head3 Side Effects

Adds one entry to the stored messages.  May print the message.  The
variables C<$@> and C<$!> are not changed.  So you can log inside an error
handler without losing the error.

=head3 EXAMPLE

    $logger->warn('something looks wrong');
    $logger->warn('file ', $name, ' is empty');          # joined: one message
    $logger->info('started', { pid => $$ });             # message + fields
    $logger->error({ error => 'cannot open file' });     # hash as the message
    $logger->debug(['part 1, ', 'part 2']);              # array of parts

=head3 API SPECIFICATION

=head4 Input

    {
        messages => { type => 'arrayref', position => 0, slurp => 1 },
    }

Domains: any number of arguments (0 gives an empty message) of any
kind and length.  Exactly one trailing newline is removed (so "m\n\n"
keeps one).  A trailing hash after one or more values is fields; an empty
one is dropped; a blessed hash is part of the message.  Text may be ASCII,
decoded text in any script (accents, CJK, emoji and emoji sequences,
combining marks, right-to-left text, zero-width characters) or bytes; it
is stored with its length unchanged.  See L</ENCODING> for how it is
printed.

=head4 Output

    { type => 'object', isa => 'Test::Log::Abstraction' }

=head3 MESSAGES

    Message                         Meaning                       What to do
    ------------------------------  ----------------------------  ------------------------------
    X() must be called on an        not called on a logger        Call it on a logger object
    object, not on the class        (croak)

=cut
1196
1197# Generate the level methods.  Each is a thin wrapper over _record, and they
1198# exist as real methods so that AUTOLOAD only sees genuinely unknown names
1199foreach my $level (keys %SEVERITY) {
1200
30
30
30
48
34
825
        no strict 'refs';       ## no critic (ProhibitNoStrict)
1201        *{$level} = sub {
1202
115971
830679
                my $self = shift;
1203
115971
48851
                return _object($self, $level)->_record($level, \@_);
1204        };
1205}
1206
1207 - 1259
=head2 is_trace, is_debug, is_info, is_notice, is_warn, is_error, is_critical, is_alert, is_emergency

Ask if a level is turned on.

=head3 Purpose

Some code only builds a log message if the level is turned on, for
example C<< if($logger->is_debug()) { ... } >>.  These methods answer that
question, as L<Log::Abstraction> does.

=head3 Args

None.

=head3 Returns

1 if the level is turned on, otherwise 0.  A level is turned on when its
number is the same as, or lower than, the logger's level (see L</level>).
The default level is C<trace>, so all these methods return 1.

=head3 Side Effects

None.

=head3 EXAMPLE

    $logger->level('warning');
    $logger->is_warn();     # 1
    $logger->is_error();    # 1 (more serious than warning)
    $logger->is_info();     # 0 (less serious than warning)

=head3 API SPECIFICATION

=head4 Input

    {}

Domains: no arguments.  Each predicate is 1 exactly when its level's
number is at or below the logger's level: at level 0 (emergency) only
is_emergency is 1; at level 7 (trace) all are 1.

=head4 Output

    { type => 'boolean' }

=head3 MESSAGES

    Message                         Meaning                       What to do
    ------------------------------  ----------------------------  ------------------------------
    is_X() must be called on an     not called on a logger        Call it on a logger object
    object, not on the class        (croak)

=cut
1260
1261foreach my $level (@PREDICATES) {
1262        my $method = "is_$level";
1263
30
30
30
31
18
3960
        no strict 'refs';       ## no critic (ProhibitNoStrict)
1264        *{$method} = sub {
1265
302
25405
                my $self = shift;
1266
302
170
                return (_object($self, $method)->{'level'} >= $SEVERITY{$level}) ? 1 : 0;
1267        };
1268}
1269
1270# _object - insist on an object invocant
1271#
1272# Purpose:      Turn a class-method call such as
1273#               Test::Log::Abstraction->warn() into a clear error, rather
1274#               than Perl's "Can't use string as a HASH ref".
1275# Entry:        $self   - the invocant.
1276#               $method - the public method's name, for the message.
1277# Exit:         Returns $self, for chaining.
1278# Side effects: Croaks with 'class_invocant' unless $self is an object of
1279#               this class or a subclass.
1280sub _object {   ## no critic (RequireFinalReturn): ends in _croak(), which never returns
1281        my ($self, $method) = @_;
1282
1283        # Called as a function, because the invocant may be undef, an unblessed
1284        # reference or another class's object, none of which can call methods
1285        return $self if(Scalar::Util::blessed($self) && $self->isa(__PACKAGE__));
1286
1287        # Only on failure: which class to name in the message
1288        my $class = (defined($self) && !ref($self) && UNIVERSAL::isa($self, __PACKAGE__)) ? $self : __PACKAGE__;
1289        _croak($class, 'class_invocant', { method => _shown($method) });
1290}
1291
1292# _record - capture one logged message
1293#
1294# Purpose:      The single place every level method ends up.
1295# Entry:        $self  - this logger.
1296#               $level - level name as called by the code under test.
1297#               $args  - array reference of the log call's arguments.
1298# Exit:         Returns $self, for chaining.
1299# Side effects: Pushes an entry onto the capture and prints it when the
1300#               diag rule allows.  Localises $@ and $! so that the caller's
1301#               error state survives, as Log::Abstraction does.
1302sub _record {
1303        my ($self, $level, $args) = @_;
1304
1305        local ($@, $!);
1306        my $entry = _entry(lc($level), $args);
1307        push @{$self->{'messages'}}, $entry;
1308        $self->_emit($entry->{'message'}) if($self->_diags($entry->{'level'}));
1309
1310        return $self;
1311}
1312
1313# _entry - turn a log call's arguments into a captured entry
1314#
1315# Purpose:      Mirror Log::Abstraction's argument handling, but render
1316#               references readably instead of as HASH(0x...).
1317# Entry:        $level - lower-cased level name.
1318#               $args  - array reference of the arguments.
1319# Exit:         Returns { level, message } plus 'fields' when a non-empty
1320#               hash reference followed one or more other arguments.
1321# Side effects: None; the caller's arrays and hashes are not modified, and
1322#               the fields hash is copied so that later changes to it by the
1323#               caller cannot rewrite the captured history.  Never dies, and
1324#               leaves $@ alone.
1325sub _entry {
1326        my ($level, $args) = @_;
1327
1328        my @args = @{$args};
1329        my $entry = { level => $level };
1330
1331        # A trailing plain hash after the message is structured fields
1332        if((@args >= 2) && (ref($args[-1]) eq 'HASH')) {
1333                my $fields = pop @args;
1334                $entry->{'fields'} = { %{$fields} } if(%{$fields});
1335        }
1336
1337        # A lone array reference is a list of message parts
1338        @args = @{$args[0]} if((@args == 1) && (ref($args[0]) eq 'ARRAY'));
1339
1340        # A part that cannot be rendered at all (a tied hash whose FETCH dies,
1341        # say) is shown in Perl's plain form, so that logging never dies
1342        # Only a reference can fail to render (overloading, a tied container);
1343        # a plain value is already a copy, read once, so it needs no eval
1344        local $@;
1345        my $message = join('', map {
1346                my $part = $_;
1347                !ref($part) ? (defined($part) ? $part : 'undef')
1348                        : do { my $text = eval { _stringify($part, {}) }; defined($text) ? $text : overload::StrVal($part) }
1349        } @args);
1350
1351        # Remove one trailing newline.  chomp() obeys $/, which the code under
1352        # test may have changed, so it is fixed here; and unlike s/// or
1353        # substr(), chomp() does not warn on a malformed UTF-8 string in Perl
1354        # 5.36 and earlier
1355        {
1356                local $/ = "\n";
1357                chomp($message);
1358        }
1359        $entry->{'message'} = $message;
1360
1361        return $entry;
1362}
1363
1364# _stringify - render any value as a readable string
1365#
1366# Purpose:      Stringify log arguments without warnings or fatals.
1367# Entry:        $value - scalar, undef, or any reference.
1368#               $seen  - hash reference of reference addresses being
1369#               rendered further up this call chain.
1370# Exit:         Returns a string: 'undef' for undef, '{k => v}' for a plain
1371#               hash, '[a, b]' for a plain array, $CYCLE for a reference
1372#               that contains itself, and Perl's normal stringification
1373#               otherwise (objects honour overloading).
1374# Side effects: None.  $seen is restored on return.  Never dies or warns:
1375#               an object whose overloaded "" dies is shown as Class=HASH(...).
1376sub _stringify {
1377
15131
10384
        my ($value, $seen) = @_;
1378
1379        # Deeply nested data is legitimate; Perl's warning at depth 100 is noise
1380
30
30
30
27
26
633
        no warnings 'recursion';        ## no critic (ProhibitNoWarnings)
1381
1382
15131
5740
        return 'undef' if(!defined($value));
1383
1384        # Plain values have no overloading and cannot die when turned into text,
1385        # so they need no eval.  Objects and other references: Perl's own form.  An
1386        # object's overloaded "" may die or return undef; neither may break a
1387        # log call, so fall back to the form that ignores overloading
1388
15126
5333
        my $type = ref($value);
1389
15126
22687
        return "$value" if(!$type);
1390
1391
5090
2905
        if(($type ne 'HASH') && ($type ne 'ARRAY')) {
1392
37
13
                local $@;       # the caught error must not reach the caller
1393
30
30
30
37
37
32
39
16
8493
20
114
91
                my $text = eval { no warnings 'uninitialized'; my $string = "$value"; $string };      ## no critic (ProhibitNoWarnings)
1394
37
68
                return defined($text) ? $text : overload::StrVal($value);
1395        }
1396
1397        # A structure that contains itself would otherwise recurse forever
1398
5053
2189
        my $address = Scalar::Util::refaddr($value);
1399
5053
2877
        return $CYCLE if($seen->{$address});
1400
5043
3206
        local $seen->{$address} = 1;
1401
1402
5043
22
21
2053
32
28
        return '{' . join(', ', map { "$_ => " . _stringify($value->{$_}, $seen) } sort keys %{$value}) . '}' if($type eq 'HASH');
1403
1404
5022
15031
5022
1921
8319
2701
        return '[' . join(', ', map { _stringify($_, $seen) } @{$value}) . ']';
1405}
1406
1407# _diags - should this level be printed?
1408#
1409# Purpose:      One decision per message, from the diag rule and verbosity.
1410# Entry:        $self  - this logger.
1411#               $level - the lower-cased level name.
1412# Exit:         Returns 1 if the message should go to TAP diagnostics, else 0.
1413# Side effects: None.  An unknown level has no severity, so a threshold rule
1414#               never prints it; AUTOLOAD prints its own notice for those.
1415sub _diags {
1416        my ($self, $level) = @_;
1417
1418        my $rule = $self->{'diag_rule'};
1419
1420        # Any one of four reasons is enough to print, so they are joined with
1421        # || and stop at the first that holds: verbose or 'all' print everything;
1422        # a list prints just its levels; a threshold prints known levels at least
1423        # that severe.  This runs for every message, so the cheap tests go first.
1424        return ($self->{'verbose'}
1425                || $rule->{'all'}
1426                || ($rule->{'levels'} && $rule->{'levels'}->{$level})
1427                || (defined($rule->{'threshold'}) && exists($SEVERITY{$level}) && ($SEVERITY{$level} <= $rule->{'threshold'}))) ? 1 : 0;
1428}
1429
1430 - 1485
=head2 AUTOLOAD

Handle a call to a method that does not exist.

=head3 Purpose

Perl calls this when the code under test calls a method that this class
does not have - usually a misspelt level, such as C<wran>.  Any name
works, even an empty one (C<< $logger->$name() >> with C<$name = ''>).  Instead of
stopping the test, the message is stored under the name that was called,
and a notice is printed.  The notice is always printed, whatever the
C<diag> setting, so the mistake is not hidden.

=head3 Args

The same as a level method.

=head3 Returns

The logger.

=head3 Side Effects

Adds one entry, with the called name as its level.  Prints
C<no method 'name'>.

=head3 EXAMPLE

    $logger->wran('oops');    # stored at level 'wran'; a notice is printed
    is($logger->count('wran'), 1, 'the misspelt call was stored');

=head3 API SPECIFICATION

=head4 Input

    {
        messages => { type => 'arrayref', position => 0, slurp => 1 },
    }

Domains: any method name, including '', non-ASCII names and very long
names.  The message is stored under the name in lower case.

=head4 Output

    { type => 'object', isa => 'Test::Log::Abstraction' }

=head3 MESSAGES

    Message                         Meaning                       What to do
    ------------------------------  ----------------------------  ------------------------------
    no method 'X'                   X is not a method or a level  Fix the method name
                                    (notice; the test goes on)
    X() must be called on an        an unknown method was called  Call it on a logger object
    object, not on the class        on the class name (croak)

=cut
1486
1487sub AUTOLOAD {
1488
2124
27853
        my ($self, @args) = @_;
1489
1490        # A method name can be anything, even empty: $logger->$name() with ''.
1491        #   Premise 1: Perl always sets $AUTOLOAD to "Package::name".
1492        #   Premise 2: ([^:]*) matches any name after the last '::', even ''.
1493        #   So: $name is always defined, and needs no fallback.
1494
2124
4789
        my ($name) = ($AUTOLOAD =~ /::([^:]*)\z/);
1495
2124
1359
        _object($self, $name)->_record($name, \@args);
1496
1497
2112
1822
        return $self->_emit($self->i18n('no_method', { method => $name }));
1498}
1499
1500# Defined so that object destruction never reaches AUTOLOAD
1501sub DESTROY { }
1502
1503 - 1564
=head2 messages

Get the stored messages.

=head3 Purpose

Let your test look at everything that was logged.

=head3 Args

None.

=head3 Returns

A reference to a new array.  It has one hash reference for each message,
oldest first.  Each hash has these keys:

=over 4

=item * C<level> - the level name, in lower case, as it was called.

=item * C<message> - the message text.

=item * C<fields> - only when fields were given: a hash reference.

=back

The array is a copy, as in L<Log::Abstraction>.  Adding or removing items
in it does not change the stored messages.  But the hashes in it are the
stored hashes (see L</COMMON PITFALLS>).

=head3 Side Effects

None.

=head3 EXAMPLE

    foreach my $entry (@{ $logger->messages() }) {
        diag("$entry->{level}: $entry->{message}");
    }

    my $first = $logger->messages()->[0];
    is($first->{level}, 'warn', 'the first message is a warning');

=head3 API SPECIFICATION

=head4 Input

    {}

=head4 Output

    { type => 'arrayref' }

=head3 MESSAGES

    Message                         Meaning                       What to do
    ------------------------------  ----------------------------  ------------------------------
    messages() must be called on    not called on a logger        Call it on a logger object
    an object, not on the class     (croak)

=cut
1565
1566sub messages {
1567
446
22752
        my $self = shift;
1568
1569
446
446
184
241
        return [ @{_object($self, 'messages')->{'messages'}} ];
1570}
1571
1572 - 1616
=head2 clear

Delete all stored messages.

=head3 Purpose

Start again with an empty list, for example between two steps of a test.

=head3 Args

None.

=head3 Returns

The logger, so you can chain calls.

=head3 Side Effects

All stored messages are deleted.  The settings (C<verbose>, C<level>,
C<diag>, language) do not change.  On a logger with no messages, it does
nothing, so it is safe to call before every step.

=head3 EXAMPLE

    $logger->clear();
    $logger->clear()->empty('nothing logged yet');

=head3 API SPECIFICATION

=head4 Input

    {}

=head4 Output

    { type => 'object', isa => 'Test::Log::Abstraction' }

=head3 MESSAGES

    Message                         Meaning                       What to do
    ------------------------------  ----------------------------  ------------------------------
    clear() must be called on an    not called on a logger        Call it on a logger object
    object, not on the class        (croak)

=cut
1617
1618sub clear {
1619
2152
24400
        my $self = shift;
1620
1621
2152
2152
864
1239
        @{_object($self, 'clear')->{'messages'}} = ();
1622
1623
2138
2298
        return $self;
1624}
1625
1626 - 1682
=head2 count

Count the stored messages.

=head3 Purpose

Check how much was logged, in total or at one level.

=head3 Args

=over 4

=item * C<$level> - optional.  Count only messages at this level.  Upper or
lower case does not matter.  Different names for the same level are counted
apart: C<count('warn')> does not count C<warning()> calls.

=back

=head3 Returns

The number of messages: 0 or more.

=head3 Side Effects

None.

=head3 EXAMPLE

    is($logger->count(), 3, 'three messages in total');
    is($logger->count('error'), 1, 'one of them is an error');

=head3 API SPECIFICATION

=head4 Input

    {
        level => { type => 'string', optional => 1, position => 0 },
    }

Domains: undef counts every message; a string counts messages at that
level, any case ('', '0' and unknown names give 0; aliases are separate
names).  Invalid: any reference.

=head4 Output

    { type => 'integer', min => 0 }

=head3 MESSAGES

    Message                         Meaning                       What to do
    ------------------------------  ----------------------------  ------------------------------
    count() must be called on an    not called on a logger        Call it on a logger object
    object, not on the class        (croak)
    invalid argument: ...           the level is not a string     Give a level name
                                    (croak)

=cut
1683
1684sub count {
1685
438
42204
        my ($self, $level) = @_;
1686
1687
438
378
        _object($self, 'count');
1688
1689        # Without a level every message counts; with one, it is checked once and
1690        # then trusted (the schema has proved it is a string)
1691
424
464
        my $messages = defined($level) ? $self->_at_level($self->_validate(\%COUNT_SCHEMA, { level => $level })->{'level'}) : $self->{'messages'};
1692
1693
404
404
224
414
        return scalar(@{$messages});
1694}
1695
1696 - 1761
=head2 like

Test that a stored message matches a pattern.

=head3 Purpose

The test passes if at least one stored message matches the pattern.

=head3 Args

=over 4

=item * C<$pattern> - required.  A C<qr//> regular expression, or a string.
A string is also used as a regular expression.

=item * C<$name> - optional.  The name of the test.

=back

=head3 Returns

True if the test passed, false if it failed.

=head3 Side Effects

Adds one test result to the TAP output.  If the test fails, all stored
messages are printed under it (at most 20, then a count of the others).

=head3 EXAMPLE

    $logger->like(qr/updated/, 'the update was logged');
    $logger->like(qr/^Cannot open/i, 'the open error was logged');

=head3 API SPECIFICATION

=head4 Input

    {
        pattern => { type => ['regex', 'string'], position => 0 },
        name => { type => 'string', optional => 1, position => 1 },
    }

Domains: pattern - a qr// or a string, which is compiled as a regular
expression ('' matches every message).  Invalid: undef, any other
reference, a string that does not compile or can never match.  name -
undef, or any string (including '' and non-ASCII text).  Invalid: a
reference.

=head4 Output

    { type => 'boolean' }

=head3 MESSAGES

    Message                         Meaning                       What to do
    ------------------------------  ----------------------------  ------------------------------
    like() needs a pattern          no pattern was given (croak)  Give a qr// or a string
    invalid argument: ...           the pattern is not a qr// or  Give a qr// or a string
                                    a string, does not compile,   that is a valid regex
                                    or can never match (croak)
    N messages were captured:       the test failed; the stored   Compare them with the pattern
                                    messages follow (output)
    like() must be called on        not called on a logger        Call it on a logger object
    an object, not on the class     (croak)

=cut
1762
1763sub like {
1764
2247
173683
        my ($self, $pattern, $name) = @_;
1765
1766
2247
1444
        _object($self, 'like');
1767
2236
2483
        $self->_croak('needs_pattern', { method => 'like' }) if(!defined($pattern));
1768
206
297
        my $valid = $self->_validate(\%PATTERN_SCHEMA, { pattern => $pattern, (defined($name) ? (name => $name) : ()) });
1769
197
271
        my $regex = $self->_regex($valid->{'pattern'});
1770
1771        # Only whether one message matches matters, so stop at the first
1772
182
100306
182
225
59701
764
        return $self->_assert((List::Util::any { $_->{'message'} =~ $regex } @{$self->{'messages'}}) ? 1 : 0, $name, 'captured', $self->{'messages'});
1773}
1774
1775 - 1837
=head2 unlike

Test that no stored message matches a pattern.

=head3 Purpose

The test passes if no stored message matches the pattern.  It also passes
when there are no messages.

=head3 Args

=over 4

=item * C<$pattern> - required.  A C<qr//> regular expression, or a string.
A string is also used as a regular expression.

=item * C<$name> - optional.  The name of the test.

=back

=head3 Returns

True if the test passed, false if it failed.

=head3 Side Effects

Adds one test result to the TAP output.  If the test fails, the messages
that matched are printed under it.

=head3 EXAMPLE

    $logger->unlike(qr/fatal/i, 'nothing fatal was logged');

=head3 API SPECIFICATION

=head4 Input

    {
        pattern => { type => ['regex', 'string'], position => 0 },
        name => { type => 'string', optional => 1, position => 1 },
    }

Domains: as for L</like>.  Note that '' matches every message, so
unlike('') fails whenever anything was logged.

=head4 Output

    { type => 'boolean' }

=head3 MESSAGES

    Message                         Meaning                       What to do
    ------------------------------  ----------------------------  ------------------------------
    unlike() needs a pattern        no pattern was given (croak)  Give a qr// or a string
    invalid argument: ...           the pattern is not a qr// or  Give a qr// or a string
                                    a string, does not compile,   that is a valid regex
                                    or can never match (croak)
    N messages matched:             the test failed; the          Look at the listed messages
                                    matching messages follow
    unlike() must be called on      not called on a logger        Call it on a logger object
    an object, not on the class     (croak)

=cut
1838
1839sub unlike {
1840
60
11206
        my ($self, $pattern, $name) = @_;
1841
1842
60
49
        _object($self, 'unlike');
1843
50
53
        $self->_croak('needs_pattern', { method => 'unlike' }) if(!defined($pattern));
1844
40
67
        my $valid = $self->_validate(\%PATTERN_SCHEMA, { pattern => $pattern, (defined($name) ? (name => $name) : ()) });
1845
33
41
        my $regex = $self->_regex($valid->{'pattern'});
1846
1847        # Stop at the first match; the full list is wanted only to explain a
1848        # failure, so it is gathered only then
1849
27
1034
27
46
1210
32
        my $matched = List::Util::any { $_->{'message'} =~ $regex } @{$self->{'messages'}};
1850
1851
27
75
        return $self->_assert(!$matched, $name, 'matched', $matched ? $self->_matching($regex) : []);
1852}
1853
1854 - 1916
=head2 has_level

Test that something was logged at a level.

=head3 Purpose

The test passes if at least one message was stored at this level.

=head3 Args

=over 4

=item * C<$level> - required.  The level name.  Upper or lower case does
not matter.  Different names for the same level are different:
C<has_level('warn')> does not see C<warning()> calls.

=item * C<$name> - optional.  The name of the test.

=back

=head3 Returns

True if the test passed, false if it failed.

=head3 Side Effects

Adds one test result to the TAP output.  If the test fails, all stored
messages are printed under it, so you can see which levels were used.

=head3 EXAMPLE

    $logger->has_level('error', 'the failure was logged');

=head3 API SPECIFICATION

=head4 Input

    {
        level => { type => 'string', position => 0 },
        name => { type => 'string', optional => 1, position => 1 },
    }

Domains: level - a string, any case ('' and unknown names match
nothing; aliases are separate names).  Invalid: undef, any reference.
name - as for L</like>.

=head4 Output

    { type => 'boolean' }

=head3 MESSAGES

    Message                         Meaning                       What to do
    ------------------------------  ----------------------------  ------------------------------
    has_level() needs a level name  no level was given (croak)    Give a level name
    invalid argument: ...           the level is not a string     Give a level name
                                    (croak)
    N messages were captured:       the test failed; the stored   Look at the listed levels
                                    messages follow (output)
    has_level() must be called on   not called on a logger        Call it on a logger object
    an object, not on the class     (croak)

=cut
1917
1918sub has_level {
1919
61
11284
        my ($self, $level, $name) = @_;
1920
1921
61
60
        _object($self, 'has_level');
1922
50
63
        $self->_croak('needs_level', { method => 'has_level' }) if(!defined($level));
1923
42
55
        my $valid = $self->_validate(\%LEVEL_SCHEMA, { level => $level, (defined($name) ? (name => $name) : ()) });
1924
30
35
        my $wanted = lc($valid->{'level'});
1925
1926        # Only whether one message is at that level matters, so stop at the first
1927
30
33
30
38
55
46
        return $self->_assert((List::Util::any { $_->{'level'} eq $wanted } @{$self->{'messages'}}) ? 1 : 0, $name, 'captured', $self->{'messages'});
1928}
1929
1930 - 1983
=head2 empty

Test that nothing was logged.

=head3 Purpose

The test passes if there are no stored messages.  Use it to check that a
normal run logs nothing.

=head3 Args

=over 4

=item * C<$name> - optional.  The name of the test.

=back

=head3 Returns

True if the test passed, false if it failed.

=head3 Side Effects

Adds one test result to the TAP output.  If the test fails, the stored
messages are printed under it.

=head3 EXAMPLE

    $logger->empty('a normal run logs nothing');

=head3 API SPECIFICATION

=head4 Input

    {
        name => { type => 'string', optional => 1, position => 0 },
    }

Domains: name - undef, or any string.  Invalid: a reference.

=head4 Output

    { type => 'boolean' }

=head3 MESSAGES

    Message                         Meaning                       What to do
    ------------------------------  ----------------------------  ------------------------------
    N messages were captured:       the test failed; the stored   Look at the listed messages
                                    messages follow (output)
    empty() must be called on       not called on a logger        Call it on a logger object
    an object, not on the class     (croak)

=cut
1984
1985sub empty {
1986
39
5846
        my ($self, $name) = @_;
1987
1988
39
33
        my $messages = _object($self, 'empty')->{'messages'};
1989
28
46
        $self->_validate(\%NAME_SCHEMA, { name => $name }) if(defined($name));
1990
1991
23
23
23
27
        return $self->_assert(!@{$messages}, $name, 'captured', $messages);
1992}
1993
1994# _matching - captured entries whose message matches a pattern
1995#
1996# Purpose:      Shared by like() and unlike().
1997# Entry:        $self    - this logger.
1998#               $pattern - qr// or string.
1999# Exit:         Returns an array reference of matching entries, in order.
2000# Side effects: Croaks with 'invalid_argument' if the pattern does not
2001#               compile.
2002sub _matching {
2003        my ($self, $pattern) = @_;
2004
2005        my $regex = $self->_regex($pattern);
2006
2007        return [ grep { $_->{'message'} =~ $regex } @{$self->{'messages'}} ];
2008}
2009
2010# _regex - compile a pattern once
2011#
2012# Purpose:      Turn a qr// or a string into a compiled regular expression,
2013#               so a search runs it without compiling it again.
2014# Entry:        $self    - this logger (for the error's language).
2015#               $pattern - qr// or string.
2016# Exit:         Returns the compiled regular expression.
2017# Side effects: Croaks with 'invalid_argument' if the pattern does not
2018#               compile, contains code, or can never match.  Leaves $@ alone
2019#               otherwise.
2020sub _regex {
2021        my ($self, $pattern) = @_;
2022
2023        # A string is compiled here, so that a malformed one, one with code in
2024        # it (not allowed at run time), or one Perl warns can never match, is
2025        # the caller's error and not a crash or a stray warning
2026        local $@;       # the caller's $@ must survive a good pattern
2027
30
30
30
39
15
6079
        my $regex = eval { use warnings FATAL => 'regexp'; qr/$pattern/ };
2028        $self->_croak('invalid_argument', { reason => _reason($@) }) if(!defined($regex));
2029
2030        return $regex;
2031}
2032
2033# _at_level - captured entries at one level
2034#
2035# Purpose:      Shared by count() and has_level().
2036# Entry:        $self  - this logger.
2037#               $level - level name, any case.
2038# Exit:         Returns an array reference of entries at that level.
2039# Side effects: None.
2040sub _at_level {
2041        my ($self, $level) = @_;
2042
2043        my $wanted = lc($level);
2044
2045        return [ grep { $_->{'level'} eq $wanted } @{$self->{'messages'}} ];
2046}
2047
2048# _assert - report an assertion as a TAP test
2049#
2050# Purpose:      Shared by every assertion, so they report identically.
2051# Entry:        $self    - this logger.
2052#               $ok      - truth of the assertion.
2053#               $name    - test name, or undef.
2054#               $key     - i18n key heading the failure diagnostic.
2055#               $entries - array reference of entries to list on failure.
2056# Exit:         Returns Test::Builder's result.
2057# Side effects: Emits one TAP test; on failure, diagnostics.  The failure is
2058#               reported at the line that called the public assertion.
2059sub _assert {
2060        my ($self, $ok, $name, $key, $entries) = @_;
2061
2062        # Skip this frame and the public assertion's; and keep the caller's
2063        # $@ and $!, which reporting may change
2064        local $Test::Builder::Level = $Test::Builder::Level + 2;
2065        local ($@, $!);
2066        my $result = Test::Builder->new()->ok($ok, $name);
2067        $self->_explain($key, $entries) if(!$ok);
2068
2069        return $result;
2070}
2071
2072# _explain - list the entries that explain a failed assertion
2073#
2074# Purpose:      Make a failure self-explanatory in the TAP output.
2075# Entry:        $self    - this logger.
2076#               $key     - i18n key for the heading ('captured' or 'matched').
2077#               $entries - array reference of entries.
2078# Exit:         Returns $self.
2079# Side effects: Prints at most $MAX_EXPLAIN entries plus a summary line.
2080sub _explain {
2081        my ($self, $key, $entries) = @_;
2082
2083        my $total = scalar(@{$entries});
2084        $self->_emit($self->i18n($key, { count => $total }));
2085
2086        my $last = ($total > $MAX_EXPLAIN) ? $MAX_EXPLAIN : $total;
2087        foreach my $entry (@{$entries}[0 .. $last - 1]) {
2088                $self->_emit($self->i18n('entry', { level => $entry->{'level'}, message => $entry->{'message'} }));
2089        }
2090        $self->_emit($self->i18n('truncated', { count => $total - $last })) if($total > $last);
2091
2092        return $self;
2093}
2094
2095 - 2148
=head2 verbose

Get or change verbose mode.

=head3 Purpose

In verbose mode, every message is printed, whatever the C<diag> rule says.
This helps when you are finding out why a test fails.

=head3 Args

=over 4

=item * C<$value> - optional.  True turns verbose mode on, false turns it
off.  Without an argument, nothing changes.

=back

=head3 Returns

The setting after the call: 1 (on) or 0 (off).

=head3 Side Effects

Changes the setting, when you give an argument.

=head3 EXAMPLE

    $logger->verbose(1);           # print everything from now on
    my $on = $logger->verbose();   # 1

=head3 API SPECIFICATION

=head4 Input

    {
        value => { type => 'scalar', optional => 1, position => 0 },
    }

Domains: absent - get only; otherwise Perl truth decides ('0', '' and
undef are off; '0.0' is on).

=head4 Output

    { type => 'integer', min => 0, max => 1 }

=head3 MESSAGES

    Message                         Meaning                       What to do
    ------------------------------  ----------------------------  ------------------------------
    verbose() must be called on an  not called on a logger        Call it on a logger object
    object, not on the class        (croak)

=cut
2149
2150sub verbose {
2151
212
6051
        my ($self, @value) = @_;
2152
2153
212
137
        _object($self, 'verbose');
2154
198
152
        $self->{'verbose'} = ($value[0] ? 1 : 0) if(@value);
2155
2156
198
178
        return $self->{'verbose'};
2157}
2158
2159 - 2225
=head2 level

Get or change the logger's level.

=head3 Purpose

Code under test may read or change the level, as it can with
L<Log::Abstraction/level>.  The level only changes the answers of the
C<is_*> methods.  Every message is still stored.

=head3 Args

=over 4

=item * C<$name> - optional.  A level name.  Upper or lower case does not
matter.

=back

=head3 Returns

=over 4

=item * No argument: the level's number, from 0 to 7.

=item * A known level name: the logger, so you can chain calls.

=item * An unknown level name: C<undef>, as in L<Log::Abstraction>.

=back

=head3 Side Effects

With a known name, the level changes.  With an unknown name, nothing
changes and a warning is printed.

=head3 EXAMPLE

    $logger->level('error');
    print $logger->level(), "\n";    # 3

=head3 API SPECIFICATION

=head4 Input

    {
        name => { type => 'string', optional => 1, position => 0 },
    }

Domains: absent or undef - get only; one of the 16 level names, any
case - set (0 for emergency to 7 for trace).  Anything else - '', numbers
such as '0' or '8', unknown names - warns and returns undef.

=head4 Output

    { type => ['integer', 'object'], optional => 1 }

=head3 MESSAGES

    Message                         Meaning                       What to do
    ------------------------------  ----------------------------  ------------------------------
    invalid syslog level 'X'        X is not a level name         Use a name from the level table
                                    (warning; returns undef)
    level() must be called on       not called on a logger        Call it on a logger object
    an object, not on the class     (croak)

=cut
2226
2227sub level {
2228
338
9216
        my ($self, $name) = @_;
2229
2230
338
266
        _object($self, 'level');
2231
2232        # Exactly one of three cases holds: no name (get), a level name (set),
2233        # or anything else (warn).  Each sets the result once; the last leaves
2234        # it undef, as Log::Abstraction returns.
2235
323
166
        my $result;
2236
323
3893
        if(!defined($name)) {
2237
188
105
                $result = $self->{'level'};
2238        } elsif(exists($SEVERITY{lc($name)})) {
2239
39
36
                $self->{'level'} = $SEVERITY{lc($name)};
2240
39
26
                $result = $self;
2241        } else {
2242
96
90
                carp($self->i18n('invalid_level', { level => _shown($name) }));
2243        }
2244
2245
320
2328
        return $result;
2246}
2247
2248 - 2291
=head2 flush

Do nothing.

=head3 Purpose

In L<Log::Abstraction>, C<flush()> sends e-mail messages that are waiting.
The test logger never sends e-mail, but the code under test may still call
C<flush()>, so it exists.

=head3 Args

None.

=head3 Returns

The logger, so you can chain calls.

=head3 Side Effects

None.

=head3 EXAMPLE

    $logger->flush();

=head3 API SPECIFICATION

=head4 Input

    {}

=head4 Output

    { type => 'object', isa => 'Test::Log::Abstraction' }

=head3 MESSAGES

    Message                         Meaning                       What to do
    ------------------------------  ----------------------------  ------------------------------
    flush() must be called on an    not called on a logger        Call it on a logger object
    object, not on the class        (croak)

=cut
2292
2293sub flush {
2294
29
3015
        my $self = shift;
2295
2296
29
24
        return _object($self, 'flush');
2297}
2298
2299 - 2342
=head2 lang

Get the language of this module's messages.

=head3 Purpose

Let a test check which language was chosen from the C<lang> and C<country>
options or the environment.

=head3 Args

None.

=head3 Returns

A language code: C<en>, C<de>, C<fr>, C<zh>, or a code that you gave in the
C<i18n> option.

=head3 Side Effects

None.

=head3 EXAMPLE

    my $lang = Test::Log::Abstraction->new(country => 'FR')->lang();    # 'fr'

=head3 API SPECIFICATION

=head4 Input

    {}

=head4 Output

    { type => 'string', matches => qr/\A[a-z]{2,3}\z/ }

=head3 MESSAGES

    Message                         Meaning                       What to do
    ------------------------------  ----------------------------  ------------------------------
    lang() must be called on an     not called on a logger        Call it on a logger object
    object, not on the class        (croak)

=cut
2343
2344sub lang {
2345
205
4363
        my $self = shift;
2346
2347
205
143
        return _object($self, 'lang')->{'lang'};
2348}
2349
2350 - 2414
=head1 PROTECTED METHODS

Only this class, and classes that inherit from it, may call these methods
(L<Sub::Protected> checks this).  A subclass may replace them.

=head2 _emit

Print one line of test output.

=head3 Purpose

Every line that this module prints goes through this method.  A subclass
can replace it to send the lines somewhere else.

=head3 Args

=over 4

=item * C<$text> - the line to print.

=back

=head3 Returns

The logger.

=head3 Side Effects

Prints the line as a TAP comment, with L<Test::Builder/diag>.  This works
even if the test did not load L<Test::More>.  A Perl character string is
encoded as UTF-8 first, unless the output already has an encoding layer
(see L</ENCODING>).

=head3 EXAMPLE

    package My::Logger;
    use parent -norequire, 'Test::Log::Abstraction';

    # Send the lines to STDERR instead of the TAP output
    sub _emit {
        my ($self, $text) = @_;
        print STDERR "$text\n";
        return $self;
    }

=head3 API SPECIFICATION

=head4 Input

    {
        text => { type => 'string', position => 0 },
    }

=head4 Output

    { type => 'object', isa => 'Test::Log::Abstraction' }

=head3 MESSAGES

    Message                         Meaning                       What to do
    ------------------------------  ----------------------------  ------------------------------
    _emit() is a protected method   called from outside the class Call it from a subclass only
    ... (croak)                     and its subclasses

=cut
2415
2416sub _emit :Protected {
2417        my ($self, $text) = @_;
2418
2419        # Output can fail (a full disk sets $! to ENOSPC); that must not leak
2420        # into the caller, which the POD promises
2421        local ($@, $!);
2422        my $tb = Test::Builder->new();
2423        my $handle = $tb->in_todo() ? $tb->todo_output() : $tb->failure_output();
2424        # Test::Builder may have no handle at all; then nothing encodes for us
2425        my $layered = defined($handle) && grep { defined($_) && /\A(?:utf8|encoding)/ } PerlIO::get_layers($handle);
2426        # A flagged string is characters, which a raw handle needs as UTF-8;
2427        # an unflagged string is already bytes and is printed untouched
2428        $text = Encode::encode('UTF-8', $text) if(!$layered && utf8::is_utf8($text));
2429        $tb->diag($text);
2430
2431        return $self;
2432
30
30
30
40
30
439
}
2433
2434 - 2557
=head2 i18n

Make one of this module's messages, in the logger's language.

=head3 Purpose

All the text that this module shows to people comes from here.  So the
text can be translated, or changed, without changing the code.

=head3 Args

=over 4

=item * C<$key> - the name of the message, such as C<needs_pattern>.

=item * C<\%args> - optional.  Values for the placeholders in the message.
C<class> is filled in for you (the logger's class).  C<count> chooses the
singular or plural form.  C<gender> chooses a gender form.

=back

=head3 How a message template works

A template is a string.  C<%{name}s> is replaced by the value called
C<name>.  After the name you can use any C<sprintf> format letter, for
example C<%{count}d> or C<%{ratio}.2f>.  C<%%> gives one C<%>.  A value
that is missing becomes the text C<undef>, with no warning.

A template can also be a hash.  The keys are gender names (such as
C<male>, C<female>) or plural forms (C<zero>, C<one>, C<two>, C<few>,
C<many>, C<other>).  The values are templates, so they can be hashes too.
C<other> is used when nothing else fits.  C<zero> is used for a count of
0, if it is there.

    {
        zero  => 'no messages',
        one   => '%{count}d message',
        other => '%{count}d messages',
    }

=head3 Where the template is found

The first one found is used:

=over 4

=item 1. Your C<i18n> option, in the logger's language.

=item 2. This module's messages, in the logger's language.

=item 3. Your C<i18n> option, in English.

=item 4. This module's messages, in English.

=back

If the key is not found anywhere, the key itself is returned.

=head3 Returns

The finished message, as a Perl character string.

=head3 Side Effects

None.

=head3 EXAMPLE

    # Inside a subclass
    my $text = $self->i18n('needs_pattern', { method => 'like' });
    # "Test::Log::Abstraction: like() needs a pattern"

    # Your own message, with gender and plural forms.  i18n() is
    # protected, so call it from a method of your subclass.
    my $logger = My::Logger->new(i18n => {
        en => {
            logged => {
                male => { one => 'He logged %{count}d line', other => 'He logged %{count}d lines' },
                other => 'They logged %{count}d lines',
            },
        },
    });
    # In a method of My::Logger:
    $self->i18n('logged', { gender => 'male', count => 2 });    # 'He logged 2 lines'

=head3 API SPECIFICATION

=head4 Input

    {
        key => { type => 'string', position => 0 },
        args => { type => 'hashref', optional => 1, position => 1 },
    }

Domains: key - any string; undef renders as '', a reference as its
string form, and an unknown key is returned as it is.  args - a hash
reference; anything else is ignored.  count - a number selects a plural
form (0 uses 'zero' when present); anything else uses 'other'.

=head4 Output

    { type => 'string' }

=head3 MESSAGES

It never fails for any key or arguments.  The only error is about who may
call it:

    Message                         Meaning                       What to do
    ------------------------------  ----------------------------  ------------------------------
    i18n() is a protected method    called from outside the class Call it from a subclass only
    ... (croak)                     and its subclasses

=head3 PSEUDOCODE

    language = the logger's language (for a class name: the configured one)
    template = the first one found in the four places listed above
    if no template was found, return the key
    while the template is a hash:
        choose by gender, else by plural form, else 'other'
    replace each %{name}format with sprintf(format, the value of name)
    return the text

=cut
2558
2559sub i18n :Protected {
2560        my ($self, $key, $args) = @_;
2561
2562        # Documented always to return a string: an undefined key renders as '',
2563        # a reference key as its string form, and arguments that are not a hash
2564        # are ignored
2565        $key = defined($key) ? "$key" : '';
2566        my $class = ref($self) || $self;
2567        my %args = (class => $class, ((ref($args) eq 'HASH') ? %{$args} : ()));
2568        my $lang = ref($self) ? $self->{'lang'} : _resolve_lang({});
2569        my $template = _template($self, $lang, $key);
2570
2571        return defined($template) ? _interpolate(_variant($template, \%args, $lang), \%args) : $key;
2572
30
30
30
7514
17
140
}
2573
2574# _template - find a message template
2575#
2576# Purpose:      Look a key up in the logger's overrides and the built-in
2577#               catalogue, in the requested language and then English.
2578# Entry:        $self - logger or class name.
2579#               $lang - language tag.
2580#               $key  - message key.
2581# Exit:         Returns the template (string or hash reference), or undef.
2582# Side effects: None.  exists() guards every level, as a Readonly hash
2583#               croaks if a lookup tries to autovivify into it.
2584sub _template {
2585        my ($self, $lang, $key) = @_;
2586
2587        my $overrides = (ref($self) && $self->{'options'}->{'i18n'}) || {};
2588
2589        # Overrides before built-ins, the logger's language before English
2590        my $found;
2591        # Searching English twice can find nothing new, so an English logger
2592        # searches once
2593        SEARCH: foreach my $tag (($lang eq $FALLBACK_LANG) ? ($lang) : ($lang, $FALLBACK_LANG)) {
2594                foreach my $source ($overrides, \%MESSAGES) {
2595                        my $table = exists($source->{$tag}) ? $source->{$tag} : undef;
2596                        next if((ref($table) ne 'HASH') || !exists($table->{$key}));
2597                        $found = $table->{$key};
2598                        last SEARCH;
2599                }
2600        }
2601
2602        return $found;
2603}
2604
2605# _variant - pick a gender or plural form of a template
2606#
2607# Purpose:      Resolve a hash-shaped template to a string.
2608# Entry:        $template - string or (nested) hash reference.
2609#               $args     - placeholder values; 'gender' and 'count' are
2610#               consulted.
2611#               $lang     - language, for its plural rules.
2612# Exit:         Returns a string; '' if no form applies.
2613# Side effects: None.  Each step descends one level, and a hash already
2614#               visited ends the walk, so even a cyclic template terminates.
2615sub _variant {
2616        my ($template, $args, $lang) = @_;
2617
2618        my %visited;
2619        while(ref($template) eq 'HASH') {
2620                # A template from the i18n option may contain itself
2621                return '' if($visited{Scalar::Util::refaddr($template)}++);
2622                my $gender = $args->{'gender'};
2623                my $form = (defined($gender) && exists($template->{$gender})) ? $gender : _plural($template, $args->{'count'}, $lang);
2624                $template = exists($template->{$form}) ? $template->{$form} : '';
2625        }
2626
2627        # A form given as undef in the i18n option renders as nothing
2628        return defined($template) ? $template : '';
2629}
2630
2631# _plural - choose a plural category for a count
2632#
2633# Purpose:      Apply the explicit 'zero' case, then the language's rule.
2634# Entry:        $template - hash reference of forms.
2635#               $count    - the count, or undef.
2636#               $lang     - language tag.
2637# Exit:         Returns the key to use: 'zero', a CLDR category present in
2638#               $template, or 'other'.
2639# Side effects: None.
2640sub _plural {
2641        my ($template, $count, $lang) = @_;
2642
2643        # Without a numeric count there is nothing to pluralise on
2644        my $numeric = defined($count) && !ref($count) && Scalar::Util::looks_like_number($count);
2645        my $form = !$numeric ? 'other'
2646                : (($count == 0) && exists($template->{'zero'})) ? 'zero'
2647                : ((defined($lang) && exists($PLURAL{$lang})) ? $PLURAL{$lang} : $PLURAL{$FALLBACK_LANG})->($count);
2648
2649        return exists($template->{$form}) ? $form : 'other';
2650}
2651
2652# _interpolate - fill a template's %{name}s placeholders
2653#
2654# Purpose:      Named sprintf, so translators can reorder placeholders.
2655# Entry:        $template - string.
2656#               $args     - hash reference of values.
2657# Exit:         Returns the rendered string; a non-ASCII template yields a
2658#               character (UTF-8 flagged) string.
2659# Side effects: None.  Missing values become 'undef' without warning, and a
2660#               non-numeric value for a numeric conversion is rendered as
2661#               it is rather than as 0.
2662sub _interpolate {
2663        my ($template, $args) = @_;
2664
2665        # Mark a translated template as characters, so _emit() encodes it even
2666        # when every character is below 0x100, as in French and German
2667        utf8::upgrade($template) if($template =~ /[^\x00-\x7F]/);
2668
2669        # Only data conversions are allowed: %n and vectors have no place in a
2670        # message, and an unknown conversion is left as literal text
2671        $template =~ s{
2672                %
2673                (?:
2674                        (%)                             # $1: '%%', a literal percent sign
2675                |
2676                        \{ (\w+) \}                     # $2: the placeholder's name
2677                        (                               # $3: the sprintf conversion:
2678                                [-+\ 0\#]*+             #     flags, possessive: the width
2679                                                        #     below is optional, so never
2680                                                        #     needs a '0' back from here
2681                                \d{0,3}                 #     width, at most 3 digits
2682                                (?: \. \d{1,3} )?               #     precision, at most 3 digits
2683                                [sdiufeEgGxXobc]        #     a data conversion only
2684                        )
2685                )
2686        }{_format($1, $3, defined($2) ? $args->{$2} : undef)}gex;
2687
2688        return $template;
2689}
2690
2691# _format - render one placeholder of _interpolate()
2692#
2693# Purpose:      Keep the substitution free of nested matches, which would
2694#               reset $1..$3 in the middle of the replacement.
2695# Entry:        $percent    - '%' for a '%%' escape, else undef.
2696#               $conversion - sprintf conversion, such as 's' or '.2f'.
2697#               $value      - the value to render, or undef.
2698# Exit:         Returns the rendered text.
2699# Side effects: None; never warns or dies, and leaves $@ alone.
2700sub _format {
2701        my ($percent, $conversion, $value) = @_;
2702
2703        # Infinity and NaN look like numbers, but %d would print them as -1.
2704        # References are never numbers: looks_like_number() would call an
2705        # object's overloading, which may die
2706        return '%' if(defined($percent));
2707        return 'undef' if(!defined($value));
2708
2709        my $numeric = !ref($value) && Scalar::Util::looks_like_number($value) && ($value == $value) && (abs($value) != 9**9**9);
2710        # An object whose overloaded "" dies is shown in plain form, as in
2711        # _stringify(), so a message can always be built
2712        local $@;
2713
30
30
30
10181
17
1562
        my $text = ($numeric || ($conversion =~ /s\z/)) ? eval { no warnings qw(uninitialized); sprintf("%$conversion", $value) }     ## no critic (ProhibitNoWarnings)
2714                : $value;
2715
2716        return defined($text) ? $text : overload::StrVal($value);
2717}
2718
2719# _croak - throw a translated error from the caller's point of view
2720#
2721# Purpose:      The one way this module dies, so every fatal message is
2722#               translatable and reported at the caller's file and line.
2723# Entry:        $self - logger or class name.
2724#               $key  - message key.
2725#               $args - optional placeholder values.
2726# Exit:         Does not return.
2727# Side effects: Croaks.
2728sub _croak {
2729        my ($self, $key, $args) = @_;
2730
2731        croak($self->i18n($key, $args));
2732}
2733
2734 - 3115
=head1 LIMITATIONS

=over 4

=item * B<It accepts more than the real logger.>  The syslog names
(C<warning>, C<err>, C<crit>, C<emerg>, C<panic>, C<informational>) are
methods here, but not in L<Log::Abstraction> 0.39.  Code that calls them
passes its tests, and then stops with an error in production.
A C<strict> option, which allows only the
real L<Log::Abstraction> methods, would be safer.

=item * B<Messages are stored under the name that was called.>  See
L</COMMON PITFALLS>.  Your test must use the same name as the code under
test.

=item * B<C<level()> does not hide messages.>  L<Log::Abstraction> drops
messages below its level.  This module stores them all, which is usually
what a test wants.

=item * B<Message text is not always the same as in L<Log::Abstraction>.>
C<undef> becomes C<undef> instead of being dropped, and hashes and arrays
are written out as data.  A test that compares the exact text may give a
different result with the real logger.

=item * B<Mixed encodings in translated output.>  See L</ENCODING>.

=item * B<The access checks are off under C<prove>.>  L<Sub::Private> and
L<Sub::Protected> do not check anything when C<$ENV{HARNESS_ACTIVE}> is
set, and C<prove> always sets it.  They do check under a plain
C<perl t/foo.t>.  Turning this off would change a setting that is shared
by every module, and that would break the tests of other modules.

=item * B<Many modules are needed.>  L<Params::Validate::Strict>,
L<Sub::Private>, L<Sub::Protected>, L<Readonly>, and L<autodie> (with
L<IPC::System::Simple>) must be installed, for what is a small test
helper.  L<Object::Configure> is not used, because it loads
L<Log::Abstraction>, and a test logger should not need that.

=item * B<Simple translation system.>  L<Locale::Maketext> uses numbered
placeholders and has no gender forms.  Modules based on gettext need
compiled files.  Neither fits a small table with named placeholders, so
this module has its own.  Native speakers have not yet checked the
German, French and Chinese texts.

=item * B<Slow patterns are not stopped.>  C<like> and C<unlike> run the
pattern against every stored message.  A pattern with nested quantifiers,
such as C<qr/(a+)+$/>, can take a very long time on some messages.  This
module does not limit the time.

=item * B<How long things take.>  C<like()> and C<has_level()> stop at
the first message that matches.  C<unlike()> that passes, C<count()> with
a level, and C<messages()> look at every stored message, so their time
grows with the number of messages (about 0.1 to 0.25 microseconds a
message on a typical machine).  A log call costs about 15 microseconds;
roughly 40% of that is L<Sub::Private> checking who called each internal
routine.  For tests that log hundreds of thousands of messages, call
C<clear()> between phases.

=item * B<One process only.>  Messages are kept in the memory of the
logger object.  Messages logged in a child process (after C<fork>) are not
seen by the parent.

=back

=head1 DIAGNOSTICS

Each method lists its messages under C<MESSAGES>.  All messages can be
translated or changed; see L</i18n>.

The text after C<invalid argument:> explains what was wrong, and may show
the value that was given.  Other errors and warnings repeat a name you gave:
a level name, a diag name, or a method name.  Every value repeated like this
is cut to 200 characters, and control characters in it (such as a newline,
or the escape character that starts a terminal control sequence) are shown
as C<\xNN>.  So a hostile value cannot make an error message enormous, add
a forged C<ok> line to the output, or control your terminal.

=head1 SEE ALSO

L<Log::Abstraction>, L<Test::Builder>, L<Test::Most>

=head1 AUTHOR

Nigel Horne, C<< <njh at nigelhorne.com> >>

=encoding utf8

=head1 FORMAL SPECIFICATION

This section describes each method in the Z notation.  You do not need it
to use the module.  It is here so that the behaviour is exact.

=head2 State

    [TEXT, NAME, KEY, TEMPLATE]
    BOOL ::= true | false
    LANG == { en, de, fr, zh }
    SEVERITY == 0 .. 7

    severity : NAME ⇸ SEVERITY
    catalogue : LANG ⇸ (KEY ⇸ TEMPLATE)

    Entry ≙ [ level : NAME; message : TEXT; fields : TEXT ⇸ TEXT ]

    Logger
      log       : seq Entry
      verbose   : BOOL
      threshold : SEVERITY
      lang      : LANG

C<severity> is the level table under L</Levels and how serious they are>.
C<catalogue> is the built-in message table.  C<ΔLogger> means the method
may change the state; C<ΞLogger> means it does not.

=head2 new

    New
      Logger'
      verbose? : BOOL
      level? : NAME
      lang? : LANG
      â”€â”€â”€â”€â”€â”€â”€â”€â”€
      level? ∈ dom severity
      log' = ⟨⟩
      verbose' = verbose?
      threshold' = severity level?
      lang' = lang?

    Clone
      ÎžLogger
      clone! : Logger
      â”€â”€â”€â”€â”€â”€â”€â”€â”€
      clone!.log = log
      clone!.verbose = verbose
      clone!.threshold = threshold
      clone!.lang = lang

C<Clone> is C<< $logger->new() >> with no options.  Options that are given
replace the matching values, as in C<New>.

=head2 trace, debug, info, notice, warn, error, fatal, critical, alert, emergency

    Log
      Î”Logger
      name? : NAME
      text? : TEXT
      â”€â”€â”€â”€â”€â”€â”€â”€â”€
      name? ∈ dom severity
      log' = log ⁀ ⟨⟨ level ↦ name?, message ↦ text? ⟩⟩
      verbose' = verbose ∧ threshold' = threshold ∧ lang' = lang

=head2 is_trace, is_debug, is_info, is_notice, is_warn, is_error, is_critical, is_alert, is_emergency

    IsLevel
      ÎžLogger
      name? : NAME
      result! : BOOL
      â”€â”€â”€â”€â”€â”€â”€â”€â”€
      name? ∈ dom severity
      result! = true ⇔ severity name? ≤ threshold

=head2 AUTOLOAD

    Unknown
      Î”Logger
      name? : NAME
      text? : TEXT
      â”€â”€â”€â”€â”€â”€â”€â”€â”€
      name? ∉ dom severity
      log' = log ⁀ ⟨⟨ level ↦ name?, message ↦ text? ⟩⟩
      verbose' = verbose ∧ threshold' = threshold ∧ lang' = lang

=head2 messages

    Messages
      ÎžLogger
      result! : seq Entry
      â”€â”€â”€â”€â”€â”€â”€â”€â”€
      result! = log

=head2 clear

    Clear
      Î”Logger
      â”€â”€â”€â”€â”€â”€â”€â”€â”€
      log' = ⟨⟩
      verbose' = verbose ∧ threshold' = threshold ∧ lang' = lang

=head2 count

    Count
      ÎžLogger
      level? : NAME
      result! : â„•
      â”€â”€â”€â”€â”€â”€â”€â”€â”€
      result! = # (log ↾ { e : Entry | e.level = level? })

    CountAll
      ÎžLogger
      result! : â„•
      â”€â”€â”€â”€â”€â”€â”€â”€â”€
      result! = # log

=head2 like

    Like
      ÎžLogger
      pattern? : â„™ TEXT
      result! : BOOL
      â”€â”€â”€â”€â”€â”€â”€â”€â”€
      result! = true ⇔ (∃ e : ran log • e.message ∈ pattern?)

=head2 unlike

    Unlike
      ÎžLogger
      pattern? : â„™ TEXT
      result! : BOOL
      â”€â”€â”€â”€â”€â”€â”€â”€â”€
      result! = true ⇔ (∀ e : ran log • e.message ∉ pattern?)

=head2 has_level

    HasLevel
      ÎžLogger
      level? : NAME
      result! : BOOL
      â”€â”€â”€â”€â”€â”€â”€â”€â”€
      result! = true ⇔ (∃ e : ran log • e.level = level?)

=head2 empty

    Empty
      ÎžLogger
      result! : BOOL
      â”€â”€â”€â”€â”€â”€â”€â”€â”€
      result! = true ⇔ log = ⟨⟩

=head2 verbose

    SetVerbose
      Î”Logger
      value? : BOOL
      result! : BOOL
      â”€â”€â”€â”€â”€â”€â”€â”€â”€
      verbose' = value?
      result! = verbose'
      log' = log ∧ threshold' = threshold ∧ lang' = lang

    GetVerbose
      ÎžLogger
      result! : BOOL
      â”€â”€â”€â”€â”€â”€â”€â”€â”€
      result! = verbose

=head2 level

    SetLevel
      Î”Logger
      name? : NAME
      â”€â”€â”€â”€â”€â”€â”€â”€â”€
      name? ∈ dom severity ⇒ threshold' = severity name?
      name? ∉ dom severity ⇒ threshold' = threshold
      log' = log ∧ verbose' = verbose ∧ lang' = lang

    GetLevel
      ÎžLogger
      result! : SEVERITY
      â”€â”€â”€â”€â”€â”€â”€â”€â”€
      result! = threshold

=head2 flush

    Flush
      ÎžLogger

=head2 lang

    Lang
      ÎžLogger
      result! : LANG
      â”€â”€â”€â”€â”€â”€â”€â”€â”€
      result! = lang

=head2 i18n

    I18n
      ÎžLogger
      key? : KEY
      result! : TEXT
      â”€â”€â”€â”€â”€â”€â”€â”€â”€
      key? ∈ dom (catalogue lang) ⇒ result! = render (catalogue lang key?)
      key? ∉ dom (catalogue lang) ∧ key? ∈ dom (catalogue en) ⇒
          result! = render (catalogue en key?)
      key? ∉ dom (catalogue lang) ∪ dom (catalogue en) ⇒ result! = key?

C<render> fills in the placeholders.  The C<i18n> option is searched
before C<catalogue> in each language.

=head1 STATE DIAGRAM

A logger has two main states: B<EMPTY> (no stored messages) and
B<CAPTURING> (one or more stored messages).  Two settings, B<verbose> and
B<level>, can change in either state; they do not move the logger between
states.  Methods that only read or test (C<like>, C<count>, C<is_debug>,
and so on) never change the state.

                  new(%options)
                  [check options; choose language,
                   diag rule and level]
                        |
                        | invalid option
                        +----------------------> croak, no logger made
                        |
                        v
    +-----------------------------------------+
    |                 EMPTY                   |<----------------+
    |  messages = ()                          |                 |
    +-----------------------------------------+                 |
         |       ^                                              |
         |       | clear()                                      |
         +-------+ [nothing to delete; return the logger]       |
         |                                                      |
         | trace() ... emergency(), warning() ... panic()       | clear()
         | [store the message; print it if the diag rule        | [delete all
         |  or verbose allows; $@ and $! are kept]              |  messages;
         |                                                      |  return the
         | wran() or any unknown method (AUTOLOAD)              |  logger]
         | [store under that name; always print                 |
         |  "no method 'wran'"]                                 |
         v                                                      |
    +-----------------------------------------+                 |
    |               CAPTURING                 |-----------------+
    |  messages = (m1, m2, ...)               |
    +-----------------------------------------+
         |       ^
         |       | any level method, or an unknown method
         +-------+ [store one more message; maybe print it]

    Changes allowed in BOTH states (the state stays the same):

      verbose(1) / verbose(0)   verbose on / off
                                [from now on: print every message / use
                                 the diag rule]
      level('error')            level number = 3
                                [the is_* answers change; nothing is
                                 hidden]
      level('bogus')            no change [warning; returns undef]

    Read-only calls in BOTH states (the state stays the same):

      like, unlike, has_level, empty
                                [one TAP result; on failure, print the
                                 messages that explain it]
      count, messages, lang, is_trace ... is_emergency, flush
                                [return a value only]
      like(undef), has_level(undef), a bad argument, or any method
      called on something that is not a logger (the class name, undef,
      a plain reference, or another class's object)
                                [croak; no change]

    Copying (the original logger does not change):

      EMPTY     --- $logger->new(%options) ---> a new logger in EMPTY
      CAPTURING --- $logger->new(%options) ---> a new logger in CAPTURING
                    [the new logger has copies of the messages, and the
                     same verbose and level, unless new values are given]

    End:

      EMPTY or CAPTURING --- the last reference goes away ---> destroyed
                    [DESTROY does nothing; nothing is stored or printed]

=head1 LICENCE AND COPYRIGHT

Copyright 2026 Nigel Horne.

Usage is subject to the GPL2 licence terms.
If you use it,
please let me know.

=cut
3116
31171;