Rand Stats

Log::Any

github:jsimonet

Build Status Build status

NAME

Log::Any

SYNOPSIS

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' ) );

DESCRIPTION

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.

SEVERITY

The severity is the level of urgence of a log. It can take the following values (based on Syslog):

CATEGORY

The category can be seen as a group identifier.

Ex:

Default value : the package name where the log is generated.

DATE

The date is generated by Log::Any, and its values is the current date and time (ISO 8601).

MESSAGE

A message is a string passed to Log::Any defined by the user. Newlines in message are escaped to prevent logging multi-line.

ADAPTERS

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:

Provided adapters

File

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).

Stdout

use Log::Any::Adapter::Stdout;
Log::Any.add( Log::Any::Adapter::Stdout.new );

Stderr

use Log::Any::Adapter::Stderr;
Log::Any.add( Log::Any::Adapter::Stderr.new );

FORMATTERS

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.

SymbolSignificationDescriptionDefault value
\dDate (UTC)The date on which the log was generatedCurrent date time
\cCategoryCan be any anything specified by the userThe current package/module
\sSeverityIndicates if it's an information, or an errornone
\e{key}Extra FieldAdditional fields, used for formattingnone
\mMessagePayload, explains what is going onnone

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

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>] ) );

Filtering on category or message

# 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/ ] );

Filtering on severity

The severity can be considered as levels, so can be traited as numbers.

  1. trace
  2. debug
  3. info
  4. notice
  5. warning
  6. error
  7. critical
  8. alert
  9. emergency

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' ] ]

Several filters

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/ ]

Write your own filter

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 ) );

Filters acting like barrier

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

PIPELINES

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!' ) );

ASYNCHRONICITY

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.

Continue on match

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' );

INTROSPECTION

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.

will-log method

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 ) );
}

will-log aliases methods

Some aliases are defined and provide the severity.

Log::Any.will-debug(); # Alias to will-log( :severity('debug') )

DEBUGGING

gist method

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.

TODO

TODO page