Help language development. Donate to The Perl Foundation
use Log::Async; logger.send-to($*OUT); trace 'how'; debug 'now'; warning 'brown'; info 'cow'; 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 Perl 6. 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-taps
Close all the taps.
logger.done
Tell the supplier it is done, then wait for the supply to be done. This is automatically called in the END phase.
logger.untapped-ok = True
This 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 hi
Because 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ć