Log::Any
use Log::Any;
use Log::Any::Adapter::Stdout;
use Log::Any::Adapter::Stderr;
# Basic usage
Log::Any.add( Log::Any::Adapter::Stdout.new, formatter => '\d \m' );
Log::Any.info( 'yolo' );
# OUTPUT: 2017-06-06T11:43:43.067909Z yolo
# Advanced usage
Log::Any.add( Log::Any::Adapter::Stderr.new,
formatter => '\d \m',
filters( [severity => '>=error'] ) );
Log::Any.add( Log::Any::Adapter::Stdout.new, formatter => '\d \m' );
# Will be logged on stderr
Log::Any.error( :category('security'), 'oups' );
# Will be logged on stdout
Log::Any.log( :msg('msg from app'), :category( 'network' ), :severity( 'info' ) );
Log::Any is a library to generate and handle application logs. A log is a message indicating an application status at a given time. It has attributes, like a severity (error, warning, debug, …), a category, a date and a message.
These attributes are used by the Formatter to format the log and can also be used to filter logs and to choose where the log will be handled (via Adapters).
Like the Perl5 implementation (https://metacpan.org/pod/Log::Any), the idea is to split log generation and log management.
The severity is the level of urgence of a log. It can take the following values (based on Syslog):
The category can be seen as a group identifier.
Ex:
Default value : the package name where the log is generated.
The date is generated by Log::Any, and its values is the current date and time (ISO 8601).
A message is a string passed to Log::Any defined by the user. Newlines in message are escaped to prevent logging multi-line.
An adapter handles a log by storing it, or sending it elsewhere. If no adapters are defined, or no one meets the filtering, the message will not be logged.
A few examples:
use Log::Any::Adapter::File;
Log::Any.add( Log::Any::Adapter::File.new( path => '/path/to/file.log' ) );
Starting with 0.9.5 version,, it's now possible to choose the buffering size to use, with out-buffer. This option can takes the same values as described in the perl6 documentation (https://docs.perl6.org/routine/out-buffer).
use Log::Any::Adapter::Stdout;
Log::Any.add( Log::Any::Adapter::Stdout.new );
use Log::Any::Adapter::Stderr;
Log::Any.add( Log::Any::Adapter::Stderr.new );
Often, logs need to be formatted to simplify the storage (time-series databases), or the analysis (grep, log parser).
Formatters will use the attributes of a Log.
Symbol | Signification | Description | Default value |
---|---|---|---|
\d | Date (UTC) | The date on which the log was generated | Current date time |
\c | Category | Can be any anything specified by the user | The current package/module |
\s | Severity | Indicates if it's an information, or an error | none |
\e{key} | Extra Field | Additional fields, used for formatting | none |
\m | Message | Payload, explains what is going on | none |
Note: Depending on the context or the configuration, some values can be empty.
Note: Extra fields uses a key in {} caracters to print associated value.
use Log::Any::Adapter::Stdout;
Log::Any.add( Some::Adapter.new, :formatter( '\d \c \m \e{key1} \e{key2}' ) );
You can of course use variables in the formatter, but since \ is already used in Perl6 strings interpolation, you have to escape them.
use Log::Any::Adapter::Stdout;
my $prefix = 'myapp ';
Log::Any.add( Log::Any::Adapter::Stdout.new, :formatter( "$prefix \\d \\c \\s \\m \\e\{key}" ) );
A formatter can be more complex than the default one by extending the class Formatter.
use Log::Any::Formatter;
class MyOwnFormatter is Log::Any::Formatter {
method format( :$date-time!, :$msg!, :$category!, :$severity!, :%extra-fields ) {
# Returns an Str
}
}
Filters can be used to allow a log to be handled by an adapter, to select which adapter to use or to use a different formatting string. Many fields can be filtered, like the category, the severity or the message.
The easiest way to define a filter is by using the built-in filter giving an array to filter parameter:
Log::Any.add( Some::Adapter.new, :filter( [ <filters fields goes here>] ) );
# Matching by String
Log::Any.add( Some::Adapter.new, :filter( ['category' => 'My::Wonderfull::Lib' ] ) );
# Matching by Regex
Log::Any.add( Some::Adapter.new, :filter( ['category' => /My::*::Lib/ ] ) );
# Matching msg by Regex
Log::Any.add( Some::Adapter.new, :filter( [ 'msg' => /a regex/ ] );
The severity can be considered as levels, so can be traited as numbers.
Filtering on severity can be done by specifying an operator in front of the severity:
filter => [ 'severity' => '<notice' ] # Less
filter => [ 'severity' => '<=notice' ] # Less or equal
filter => [ 'severity' => '==debug' ] # Equality
filter => [ 'severity' => '!=notice' ] # Inequality
filter => [ 'severity' => '>warning' ] # Greater
filter => [ 'severity' => '>=warning' ] # Greater or equal
Matching only several severities is also possible:
filter => [ 'severity' => [ 'notice', 'warning' ] ]
If many filters are specified, all must be valid:
# Use this adapter only if the category is My::Wonderfull::Lib and if the severity is warning or error
[ 'severity' => '>=warning', 'category' => /My::Wonderfull::Lib/ ]
If a more complex filtering is necessary, a class inheriting Log::Any::Filter can be created:
# Use home-made filters
class MyOwnFilter is Log::Any::Filter {
method filter( :$msg, :$severity, :$category, :%extra-fields ) returns Bool {
# Write some complicated tests
return True;
}
}
Log::Any.add( Some::Adapter.new, :filter( MyOwnFilter.new ) );
A filter generally authorize a log to be sent to an Adapter. If there is no defined Adapter, the log will end up in a black hole.
Log::Any.add( :filter( [ severity => '>warning' ] );
# Logs with severity above "warning" does not continue through the pipeline
A pipeline is a set of adapters, filters, formatters and options (asynchronicity) and can be used to define alternatives paths. This allows to handle differently some logs (for example, for security or realtime). If a log is produced with a specific pipeline which is not defined in the log consumers, the default pipeline is used.
Pipelines can be specified when an Adapter is added.
Log::Any.add( :pipeline('security'), Log::Any::Adapter::Example.new );
Log::Any.error( :pipeline('security'), :msg('security error!' ) );
By default, a pipeline is synchronous, but it can be asynchronous:
Log::Any.add( :pipeline( 'async pipeline' ), Log::Any::Pipeline.new( :asynchronous ) );
If a pipeline of the specified name already exists, an exception will be throwed. The overwrite parameter can be specified to force adding a new pipeline:
# Overwrite the default pipeline
Log::Any.add( Log::Any::Pipeline.new( :asynchronous ), :overwrite );
# Overwrite the "other" pipeline
Log::Any.add( Log::Any::Pipeline.new( :asynchronous ), :pipeline('other'), :overwrite );
Asynchronous pipelines can contains messages to handle when a program reaches its end, so these messages will not be logged.
When adding an Adapter to the pipeline, :continue-on-match
option can be specified.
This option tells the pipeline to continue to the next adapter if the Adapter is matched.
The log info log twice
will be dispatched to both adapters.
Log::Any.add( :pipeline('continue-on-match'), SomeAdapterAs.new, :continue-on-match );
Log::Any.add( :pipeline('continue-on-match'), SomeAdapterB.new );
Log::Any.info( :pipeline('continue-on-match'), 'info log twice' );
Check if a log will be handled (to prevent computation of log). It is usefull if you want to log a dump of a complex object which can take time.
This method can takes in parameters the category, the severity and the pipeline to use. Theses parameters are then used to check if the message could pass through the pipelines and will be handled.
msg parameter cannot be tested, so if a filter acts on it, the results will differ between will-log()
and log()
.
if Log::Any.will-log( :severity('debug') ) {
Log::Any.debug( serialize( $some-complex-object ) );
}
Some aliases are defined and provide the severity.
Log::Any.will-debug(); # Alias to will-log( :severity('debug') )
gist method prints a string represention of the internal Log::Any state (defined pipelines with their adapters, filters and formatters). Since many attributes are not public, you cannot recreate a Log::Any stack based on this representation.