Log::Contextual(3)
π NAME π
Bestie, meet Log::Contextual(3) β a super simple logging interface with a contextual log. No cap.
π VERSION π
version 0.009001 β that's the whole section, periodt.
π SYNOPSIS π
Okay so here's the starter pack, exactly as written:
use Log::Contextual qw( :log :dlog set_logger with_logger );
use Log::Contextual::SimpleLogger;
use Log::Log4perl ':easy';
Log::Log4perl->easy_init($DEBUG);
my $logger = Log::Log4perl->get_logger;
set_logger $logger;
log_debug { 'program started' };
sub foo {
my $minilogger = Log::Contextual::SimpleLogger->new({
levels => [qw( trace debug )]
});
my @args = @_;
with_logger $minilogger => sub {
log_trace { 'foo entered' };
my ($foo, $bar) = Dlog_trace { "params for foo: $_" } @args;
# ...
slog_trace 'foo left';
};
}
foo();
Beginning with version 1.008 Log::Dispatchouli also works out of the box with Log::Contextual:
use Log::Contextual qw( :log :dlog set_logger );
use Log::Dispatchouli;
my $ld = Log::Dispatchouli->new({
ident => 'slrtbrfst',
to_stderr => 1,
debug => 1,
});
set_logger $ld;
log_debug { 'program started' };
β¨ DESCRIPTION β¨
Major benefits, bestie:
- Efficient β the lazy-girl win we love. The default logging functions take blocks, so if a log level is disabled, the block will not run:
# the following won't run if debug is off
log_debug { "the new count in the database is " . $rs->count }
Similarly, the D prefixed methods only Dumper the input if the level is enabled.
- Handy β the logging functions return their arguments, so you can stick them in the middle of expressions:
for (log_debug { "downloading:\n" . join qq(\n), @_ } @urls) { ... }
- Generic β
Log::Contextualis an interface for all major loggers. If you log throughLog::Contextualyou will be able to swap underlying loggers later. - Powerful β
Log::Contextualchooses which logger to use based on user definedCodeRefs. Normally you don't need to know this, but you can take advantage of it when you need to later. - Scalable β if you just want to add logging to your basic application, start with
Log::Contextual::SimpleLoggerand then as your needs grow you can switch toLog::DispatchouliorLog::DispatchorLog::Log4perlor whatever else.
This module is a simple interface to extensible logging. It exists to abstract your logging interface so that logging is as painless as possible, while still allowing you to switch from one logger to another.
It is bundled with a really basic logger, Log::Contextual::SimpleLogger, but in general you should use a real logger instead. For something more serious but not overly complicated, try Log::Dispatchouli (see SYNOPSIS for example.)
π A WORK IN PROGRESS π
Okay real talk: this module is certainly not complete, but we will not break the interface lightly, so I would say it's safe to use in production code. The main result from that at this point is that doing:
use Log::Contextual;
will die as we do not yet know what the defaults should be. If it turns out that nearly everyone uses the :log tag and :dlog is really rare, we'll probably make :log the default. But only time and usage will tell.
π¦ IMPORT OPTIONS π¦
See SETTING DEFAULT IMPORT OPTIONS for information on setting these project wide.
πΈ -logger
When you import this module you may use -logger as a shortcut for set_logger, for example:
use Log::Contextual::SimpleLogger;
use Log::Contextual qw( :dlog ),
-logger => Log::Contextual::SimpleLogger->new({ levels => [qw( debug )] });
Sometimes you might want to have the logger handy for other stuff, in which case you might try something like the following:
my $var_log;
BEGIN { $var_log = VarLogger->new }
use Log::Contextual qw( :dlog ), -logger => $var_log;
π -levels
The -levels import option allows you to define exactly which levels your logger supports. So the default, [qw(debug trace warn info error fatal)], works great for Log::Log4perl, but it doesn't support the levels for Log::Dispatch. But supporting those levels is as easy as doing:
use Log::Contextual
-levels => [qw( debug info notice warning error critical alert emergency )];
π -package_logger
The -package_logger import option is similar to the -logger import option except -package_logger sets the logger for the current package.
Unlike -default_logger, -package_logger cannot be overridden with set_logger or with_logger.
package My::Package;
use Log::Contextual::SimpleLogger;
use Log::Contextual qw( :log ),
-package_logger => Log::Contextual::WarnLogger->new({
env_prefix => 'MY_PACKAGE'
});
If you are interested in using this package for a module you are putting on CPAN we recommend Log::Contextual::WarnLogger for your package logger.
π«Ά -default_logger
The -default_logger import option is similar to the -logger import option except -default_logger sets the default logger for the current package.
Basically it sets the logger to be used if set_logger is never called; so:
package My::Package;
use Log::Contextual::SimpleLogger;
use Log::Contextual qw( :log ),
-default_logger => Log::Contextual::WarnLogger->new({
env_prefix => 'MY_PACKAGE'
});
π SETTING DEFAULT IMPORT OPTIONS π
Eventually you will get tired of writing the following in every single one of your packages:
use Log::Log4perl;
use Log::Log4perl ':easy';
BEGIN { Log::Log4perl->easy_init($DEBUG) }
use Log::Contextual -logger => Log::Log4perl->get_logger;
You can set any of the import options for your whole project if you define your own Log::Contextual subclass as follows:
package MyApp::Log::Contextual;
use parent 'Log::Contextual';
use Log::Log4perl ':easy';
Log::Log4perl->easy_init($DEBUG)
sub arg_default_logger { $_[1] || Log::Log4perl->get_logger }
sub arg_levels { [qw(debug trace warn info error fatal custom_level)] }
sub default_import { ':log' }
# or maybe instead of default_logger
sub arg_package_logger { $_[1] }
# and almost definitely not this, which is only here for completeness
sub arg_logger { $_[1] }
Note the $_[1] || in arg_default_logger. All of these methods are passed the values passed in from the arguments to the subclass, so you can either throw them away, honor them, die on usage, etc. To be clear, if you define your subclass, and someone uses it as follows:
use MyApp::Log::Contextual -default_logger => $foo,
-levels => [qw(bar baz biff)];
Your arg_default_logger method will get $foo and your arg_levels will get [qw(bar baz biff)];
Additionally, the default_import method is what happens if a user tries to use your subclass with no arguments. The default just dies, but if you'd like to change the default to import a tag merely return the tags you'd like to import. So the following will all work:
sub default_import { ':log' }
sub default_import { ':dlog' }
sub default_import { qw(:dlog :log ) }
See Log::Contextual::Easy::Default for an example of a subclass of Log::Contextual that makes use of default import options.
πΈ FUNCTIONS πΈ
π« set_logger
my $logger = WarnLogger->new;
set_logger $logger;
Arguments: LOGGER CODEREF
set_logger literally just sets the current logger to whatever you hand it. It expects a CodeRef, but if you pass it something else it'll wrap it in a CodeRef for you, bestie, no judgment. set_logger is really only meant to be called from a top-level script β like, that's its whole lane. And to stop you from foot-shooting yourself, the function will warn if you call it more than once. It's looking out for you π«Ά
π« with_logger
my $logger = WarnLogger->new;
with_logger $logger => sub {
if (1 == 0) {
log_fatal { 'Non Logical Universe Detected' };
} else {
log_info { 'All is good' };
}
};
Arguments: LOGGER CODEREF, CodeRef $to_execute
with_logger sets the logger for the scope of the CodeRef called $to_execute. And same energy as set_logger: with_logger will wrap $returning_logger with a CodeRef if it needs to. Slay.
π« has_logger
my $logger = WarnLogger->new;
set_logger $logger unless has_logger;
Arguments: none
has_logger returns true if a logger has already been set. That's it. That's the whole function. Iconic minimalism π
π« log_$level
Import Tag: :log
Arguments: CodeRef $returning_message, @args
The log_$level functions all work exactly the same way, except that a different method gets called on the underlying $logger object. So the basic pattern is:
sub log_$level (&@) {
if ($logger->is_$level) {
$logger->$level(shift->(@_));
}
@_
}
Okay so note that the function returns its arguments. There are like a bunch of ways you can use that, but honestly the most convenient one is partial inspection of passthrough data:
my @friends = log_trace {
'friends list being generated, data from first friend: ' .
Dumper($_[0]->TO_JSON)
} generate_friend_list();
If you want complete inspection of your passthrough data, go look at the Dlog_$level functions instead β they ate that job.
Which functions get exported depends on what you passed to -levels. The default (i.e. no -levels option passed at all) would export:
log_tracelog_debuglog_infolog_warnlog_errorlog_fatal
β οΈ Note:log_fataldoes not calldiefor you β go readEXCEPTIONS AND ERROR HANDLING, fr fr.
π« slog_$level
Mostly the same as log_$level, but it expects a string as its first argument, not a block. Arguments are passed through exactly the same way, but since it's just a string, you have to interpolate arguments into it manually. Your hands, your job π
my @friends = slog_trace 'friends list being generated.', generate_friend_list();
π« logS_$level
Import Tag: :log
Arguments: CodeRef $returning_message, Item $arg
This is honestly just a special case of the log_$level functions. It forces scalar context when that's what you need. Other than that it works exactly the same:
my $friend = logS_trace {
'I only have one friend: ' . Dumper($_[0]->TO_JSON)
} friend();
See also: DlogS_$level β¨
π« slogS_$level
Mostly the same as logS_$level, but it expects a string as its first argument, not a block. Arguments pass through just the same, but since it's just a string, interpolating arguments into it is on you, manually, every time:
my $friend = slogS_trace 'I only have one friend.', friend();
π« Dlog_$level
Import Tag: :dlog
Arguments: CodeRef $returning_message, @args
All six of the following functions work the same as their log_$level brethren, except they return what was passed into them and they stick the stringified (with Data::Dumper::Concise) version of their args into $_. Which means you can do genuinely cool things like this:
my @nicks = Dlog_debug { "names: $_" } map $_->value, $frew->names->all;
and the output might look something like:
names: "fREW"
"fRIOUX"
"fROOH"
"fRUE"
"fiSMBoC"
Which functions get exported depends on what was passed to -levels. The default (no -levels option passed) would export:
Dlog_traceDlog_debugDlog_infoDlog_warnDlog_errorDlog_fatal
β οΈ Note:Dlog_fataldoes not calldiefor you β seeEXCEPTIONS AND ERROR HANDLING. Same rule, different function name π
π« Dslog_$level
Mostly the same as Dlog_$level, but it expects a string as first argument, not a block. Arguments are passed through just the same, but since it's just a string there's no interpolation point you can use β instead the Dumper output gets appended.
my @nicks = Dslog_debug "names: ", map $_->value, $frew->names->all;
π« DlogS_$level
Import Tag: :dlog
Arguments: CodeRef $returning_message, Item $arg
Like logS_$level, these functions are a special case of Dlog_$level. They only take a single scalar after the $returning_message instead of slurping up (and also setting wantarray) all the @args:
my $pals_rs = DlogS_debug { "pals resultset: $_" }
$schema->resultset('Pals')->search({ perlers => 1 });
π« DslogS_$level
Mostly the same as DlogS_$level, but it expects a string as first argument, not a block. Arguments pass through just the same, but since it's just a string, no interpolation point can be used β instead the Dumper output is appended.
my $pals_rs = DslogS_debug "pals resultset: ",
$schema->resultset('Pals')->search({ perlers => 1 });
π¦ LOGGER CODEREF π¦
Anywhere a logger object can be passed, a coderef is accepted. The whole reason for this is so you can use different logger objects based on runtime information. The logger coderef gets passed the package of the caller, plus the caller level the coderef needs to use if it wants more caller info. That second one is bundled up in a hashref so more options can be added in the future. Forward thinking, we love to see it π
Here's a basic example of a logger that exploits caller to reproduce the output of warn with a logger:
my @caller_info;
my $var_log = Log::Contextual::SimpleLogger->new({
levels => [qw(trace debug info warn error fatal)],
coderef => sub { chomp($_[0]); warn "$_[0] at $caller_info[1] line $caller_info[2].\n" }
});
my $warn_faker = sub {
my ($package, $args) = @_;
@caller_info = caller($args->{caller_level});
$var_log
};
set_logger($warn_faker);
log_debug { 'test' };
And this next one uses the information passed to the logger coderef. It sets the global logger to $l3, the logger for the A1 package to $l1 β except the lol method in A1, which uses the $l2 logger β and lastly the logger for the A2 package to $l2.
Note that it increases the caller level as it dispatches, based on where the caller of the log function is, not the log function itself. Sneaky but correct π
my $complex_dispatcher = do {
my $l1 = ...;
my $l2 = ...;
my $l3 = ...;
my %registry = (
-logger => $l3,
A1 => {
-logger => $l1,
lol => $l2,
},
A2 => { -logger => $l2 },
);
sub {
my ( $package, $info ) = @_;
my $logger = $registry{'-logger'};
if (my $r = $registry{$package}) {
$logger = $r->{'-logger'} if $r->{'-logger'};
my (undef, undef, undef, $sub) = caller($info->{caller_level} + 1);
$sub =~ s/^\Q$package\E:://g;
$logger = $r->{$sub} if $r->{$sub};
}
return $logger;
}
};
set_logger $complex_dispatcher;
π LOGGER INTERFACE π
Because this module is ultimately pretty-looking glue (glittery, even?) with the awesome benefit of the Contextual part, people will constantly want to make their fave logger work with it. So here are the methods that should be implemented in the logger:
is_traceis_debugis_infois_warnis_erroris_fataltracedebuginfowarnerrorfatal
The first six just need to return true if that level is enabled. The latter six take the results of whatever the user returned from their coderef and log them. For a basic example, go peek at Log::Contextual::SimpleLogger π
β LOG ROUTING β
Sitting in between the loggers and the log functions is a log router, and its whole job is finding a logger to handle the log event and then passing the log information along to that logger. This relationship is described in the documentation for Log::Contextual::Role::Router.
Log::Contextual β and any packages that extend it β will by default share a router singleton that implements the with_logger() and set_logger() functions, and that also respects the -logger, -package_logger, and -default_logger import options along with their associated default value functions. The router singleton is available as the return value of the router() function. Users of Log::Contextual may overload router() to return instances of custom log routers that could, for example, work with loggers that use a different interface. Main character flexibility β¨
π₯ EXCEPTIONS AND ERROR HANDLING π₯
Log::Contextual, by design, does not intentionally invoke die on your behalf (see footnote) for log_fatal.
Logging events are characterized as information, not flow control, and conflating the two results in negative design anti-patterns. Periodt.
So as such, log_fatal would be better used to communicate information about a future failure, for example:
if ( condition ) {
log_fatal { "Bad Condition is true" };
die My::Exception->new();
}
This has a number of benefits:
- You're more likely to want to use useful Exception Objects and flow control instead of cheating with log messages.
- You're less likely to run a risk of losing what the actual problem was when some error occurs in your creation of the Exception Object.
- You're less likely to run the risk of losing important log context due to exceptions occurring midway through
dieunwinding andexitglobal destruction.
If you're still too lazy to use exceptions, then you can do what you probably want as follows:
if ( ... ) {
log_fatal { "Bad condition is true" };
die "Bad condtion is true";
}
Or for :dlog style:
use Data::Dumper::Consise qw( Dumper );
if ( ... ) {
# Dlog_fatal but not
my $reason = "Bad condtion is true because: " . Dumper($thing);
log_fatal { $reason };
die $reason;
}
π footnote π
The underlying behaviour of log_fatal is dependent on the backing library. Do not forget this, bestie.
All the Loggers shipping with Log::Contextual behave this way, as do many of the supported loggers, like Log::Log4perl. However, not all loggers work this way, and one must be careful π
Log::Dispatchdoesn't support implementinglog_fatalat allLog::Dispatchouliimplementslog_fatalusingdie(via Carp)
π DESIGNER π
mst - Matt S. Trout <mst@shadowcat.co.uk>
π BUGS π
okay so if you find a bug or you're like "bestie this needs a feature" β please report it on the bugtracker website, it's right here:
https://github.com/haarg/Log-Contextual/issues
and listen, when you submit a bug or a request, please include a test-file or a patch to an existing test-file that illustrates the bug or the desired feature. fr fr, this is the whole vibe check β a reproducer makes everything slay so much faster. π«Ά
π CONTRIBUTORS π
shoutout to the icons who contributed, no cap, literally the main characters of this repo:
- Christian Walde
<walde.christian@gmail.com> - Dan Book
<grinnz@grinnz.com> - Florian Schlichtin
<fsfs@debian.org> - Graham Knop
<haarg@haarg.org> - Jakob Voss
<voss@gbv.de> - Karen Etheridge
<ether@cpan.org> - Kent Fredric
<kentfredric@gmail.com> - Matt S Trout
<mst@shadowcat.co.uk> - Peter Rabbitson
<ribasushi@cpan.org> - Philippe Bruhat (BooK)
<book@cpan.org> - Tyler Riddle
<t.riddle@shadowcat.co.uk> - Wes Malone
<wes@mitsi.com>
absolute legends, every single one π
β¨ AUTHOR β¨
Arthur Axel "fREW" Schmidt <frioux+cpan@gmail.com> β the author, the blueprint, the moment π
π COPYRIGHT AND LICENSE π
This software is copyright (c) 2024 by Arthur Axel "fREW" Schmidt.
This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. so yes bestie, it's free software, go off, remix it, redistribute it, just keep those same terms π
and that's the whole page, we did it π the footer says this is perl v5.42.0, dated 2024-06-23, and yup, it's Log::Contextual(3) β read it, love it, log responsibly ππ¦π
π¦ where this page comes from
- opensuse-leap 16.0: perl-Log-Contextual 0.009001-bp160.1.11 Β· Artistic-1.0 OR GPL-1.0-or-later
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash-1017 on : a language model's rewrite of the original, under its terms