Thread-safe asynchronous logging using supplies.
This module can be used in a few different ways:
use Log::Async <trace>; # send all logs to stderr
use Log::Async <cli trace>; # send logs to stder, parse command line options
use Log::Async <cli trace color>; # in color
use Log::Async <warning color>; # only send warning, error and fatal to stderr
use Log::Async <info>; # only send info, warn, error and fatalOr don't import anything and set the destinations and levels to log yourself:
use Log::Async;
logger.send-to($*OUT);
trace 'how';
debug 'now';
info 'brown';
warning 'cow';
error 'wow';
fatal 'ow';
(start debug 'one')
.then({ debug 'two' });
(start debug 'buckle')
.then({ debug 'my shoe' });
sleep 1;
my $when = now + 1;
for ^100 {
Promise.at($when)
.then({ debug "come together"})
.then({ debug "right now"})
.then({ debug "over me"});
}
logger.send-to("/var/log/hal.errors", :level(ERROR));
error "I'm sorry Dave, I'm afraid I can't do that";Log::Async provides asynchronous logging using
the supply and tap semantics of Raku. Log messages
are emitted asynchronously to a supply. Taps are
only executed by one thread at a time.
By default a single tap is created which prints the timestamp, level and message to stdout.
trace, debug, info, warning, error, fatal: each of these asynchronously emits a message at that level.
enum Loglevels: TRACE DEBUG INFO WARNING ERROR FATAL
class Log::Async: Does the real work.
sub logger: return or create a logger singleton.
sub set-logger: set a new logger singleton.
my $tap = logger.add-tap({ say $^m<msg> ~ '!!!!!' }, :level(FATAL));
logger.add-tap({ $*ERR.say $^m<msg> }, :level(DEBUG | ERROR));
logger.add-tap({ say "# $^m<msg>"}, :level(* < ERROR) );
logger.add-tap({ say "meow: " ~ $^m<msg> }, :msg(rx/cat/));
logger.add-tap(-> $m { say "thread { $m<THREAD>.id } says $m<msg>" });
logger.add-tap(-> $m { say "$m<when> {$m<frame>.file} {$m<frame>.line} $m<level>: $m<msg>" });
logger.add-tap(-> $m { say "{ $m<when>.utc } ($m<level>) $m<msg>",
:level(INFO..WARNING) });Add a tap, optionally filtering by the level or by the message.
$code receives a hash with the keys msg (a string), level (a
Loglevel), when (a DateTime), THREAD (the caller's $*THREAD),
frame (the current callframe), and possibly ctx (the context, see below).
$level and $msg are filters: they will be smartmatched against
the level and msg keys respectively.
add-tap returns a tap, which can be sent to remove-tap to turn
it off.
logger.remove-tap($tap)Closes and removes a tap.
send-to(IO::Handle $handle)
send-to(IO::Path $path)
logger.send-to('/tmp/out.log');
logger.send-to('/tmp/out.log', :level( * >= ERROR));
logger.send-to('/tmp/out.log', formatter => -> $m, :$fh { $fh.say: "{$m<level>.lc}: $m<msg>" });
logger.send-to($*OUT,
formatter => -> $m, :$fh {
$fh.say: "{ $m<frame>.file } { $m<frame>.line } { $m<frame>.code.name }: $m<msg>"
});Add a tap that prints timestamp, level and message to a file or filehandle.
formatter is a Code argument which takes $m (see above), as well as
the named argument :$fh -- an open filehandle for the destination.
Additional args (filters) are sent to add-tap.
logger.close-tapsClose all the taps.
logger.doneTell the supplier it is done, then wait for the supply to be done. This is automatically called in the END phase.
logger.untapped-ok = TrueThis will suppress warnings about sending a log message before any taps are added.
To display stack trace information, logging can be initialized with add-context.
This sends a stack trace with every log request (so may be expensive). Once add-context
has been called, a ctx element will be passed which is a Log::Async::Context
object. This has a stack method which returns an array of backtrace frames.
logger.add-context;
logger.send-to('/var/log/debug.out',
formatter => -> $m, :$fh {
$fh.say: "file { $m<ctx>.file}, line { $m<ctx>.line }, message { $m<msg> }"
}
);
logger.send-to('/var/log/trace.out',
formatter => -> $m, :$fh {
$fh.say: $m<msg>;
$fh.say: "file { .file}, line { .line }" for $m<ctx>.stack;
}
);A custom context object can be used as an argument to add-context. This
object should have a generate method. generate will be called to
generate context whenever a log message is sent.
For instance:
my $context = Log::Async::Context.new but role {
method generate { ... }
method custom-method { ... }
};
logger.add-context($context);
# later
logger.add-tap(-> $m { say $m.ctx.custom-method } )
logger.send-to($*OUT,:level(DEBUG));logger.send-to('/var/log/error.log',:level(* >= WARNING));logger.send-to($*OUT,
formatter => -> $m, :$fh {
$fh.say: "{ $m<when>.utc } ({ $m<frame>.file } +{ $m<frame>.line }) $m<level> $m<msg>"
});
trace 'hi';
# output:
2017-02-20T14:00:00.961447Z (eg/out.raku +10) TRACE hiBecause messages are emitted asynchronously, the order in which they are emitted depends on the scheduler. Taps are executed in the same order in which they are emitted. Therefore timestamps in the log might not be in chronological order.
Brian Duggan
Bahtiar Gadimov
Curt Tilmes
Marcel Timmerman
Juan Julián Merelo Guervós
Slobodan Mišković