| File: | blib/lib/Test/Log/Abstraction.pm |
| Coverage: | 99.6% |
| line | stmt | bran | cond | sub | time | code |
|---|---|---|---|---|---|---|
| 1 | package 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 | ||||||
| 393 | our $VERSION = '0.002.0'; | |||||
| 394 | our $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. | |||||
| 401 | BEGIN { | |||||
| 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 | |||||
| 417 | our %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.) | |||||
| 430 | my %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 | ); | |||||
| 448 | Hash::Util::lock_hash(%SEVERITY); | |||||
| 449 | ||||||
| 450 | # The levels that Log::Abstraction has is_<level>() predicates for | |||||
| 451 | Readonly::Array my @PREDICATES => qw(trace debug info notice warn error critical alert emergency); | |||||
| 452 | ||||||
| 453 | # diag option values that are not level names | |||||
| 454 | Readonly::Scalar my $DIAG_ALL => 'all'; | |||||
| 455 | Readonly::Scalar my $DIAG_NONE => 'none'; | |||||
| 456 | ||||||
| 457 | # lang option value that means "read the POSIX locale variables" | |||||
| 458 | Readonly::Scalar my $LANG_AUTO => 'auto'; | |||||
| 459 | ||||||
| 460 | # Catalogue used when a key has no translation in the chosen language | |||||
| 461 | Readonly::Scalar my $FALLBACK_LANG => 'en'; | |||||
| 462 | ||||||
| 463 | # Locale environment variables, most specific first, as POSIX defines them | |||||
| 464 | Readonly::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 | |||||
| 468 | Readonly::Scalar my $MAX_REASON => 200; | |||||
| 469 | ||||||
| 470 | # How many captured messages a failing assertion lists before summarising | |||||
| 471 | Readonly::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 | |||||
| 475 | Readonly::Scalar my $CYCLE => '(cycle)'; | |||||
| 476 | ||||||
| 477 | # ISO 3166 country code -> catalogue language | |||||
| 478 | Readonly::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 | |||||
| 487 | Readonly::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. | |||||
| 498 | Readonly::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. | |||||
| 602 | Readonly::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 | |||||
| 606 | Readonly::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() | |||||
| 615 | Readonly::Hash my %PATTERN_SCHEMA => ( | |||||
| 616 | pattern => { type => ['regex', 'string'] }, | |||||
| 617 | name => { type => 'string', optional => 1 }, | |||||
| 618 | ); | |||||
| 619 | ||||||
| 620 | # Schema for count() | |||||
| 621 | Readonly::Hash my %COUNT_SCHEMA => ( | |||||
| 622 | level => { type => 'string', optional => 1 }, | |||||
| 623 | ); | |||||
| 624 | ||||||
| 625 | # Schema for empty(): only a test name | |||||
| 626 | Readonly::Hash my %NAME_SCHEMA => ( | |||||
| 627 | name => { type => 'string', optional => 1 }, | |||||
| 628 | ); | |||||
| 629 | ||||||
| 630 | # Schema for has_level() | |||||
| 631 | Readonly::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 | ||||||
| 801 | sub 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. | |||||
| 830 | sub _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. | |||||
| 855 | sub _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. | |||||
| 905 | sub _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. | |||||
| 932 | sub _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. | |||||
| 956 | sub _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. | |||||
| 982 | sub _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. | |||||
| 1009 | sub _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'. | |||||
| 1026 | sub _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. | |||||
| 1062 | sub _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 | |||||
| 1199 | foreach 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 | ||||||
| 1261 | foreach 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. | |||||
| 1280 | sub _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. | |||||
| 1302 | sub _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. | |||||
| 1325 | sub _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(...). | |||||
| 1376 | sub _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. | |||||
| 1415 | sub _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 | ||||||
| 1487 | sub 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 | |||||
| 1501 | sub 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 | ||||||
| 1566 | sub 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 | ||||||
| 1618 | sub 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 | ||||||
| 1684 | sub 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 | ||||||
| 1763 | sub 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 | ||||||
| 1839 | sub 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 | ||||||
| 1918 | sub 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 | ||||||
| 1985 | sub 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. | |||||
| 2002 | sub _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. | |||||
| 2020 | sub _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. | |||||
| 2040 | sub _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. | |||||
| 2059 | sub _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. | |||||
| 2080 | sub _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 | ||||||
| 2150 | sub 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 | ||||||
| 2227 | sub 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 | ||||||
| 2293 | sub 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 | ||||||
| 2344 | sub 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 | ||||||
| 2416 | sub _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 | ||||||
| 2559 | sub 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. | |||||
| 2584 | sub _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. | |||||
| 2615 | sub _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. | |||||
| 2640 | sub _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. | |||||
| 2662 | sub _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. | |||||
| 2700 | sub _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. | |||||
| 2728 | sub _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 | ||||||
| 3117 | 1; | |||||