The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

Log::ger::Manual::Internals - Log::ger internals

VERSION

version 0.040.000

DESCRIPTION

When an importer package does this:

 use Log::ger;

Basically all Log::ger does is construct logger routines and install them to importer's package (target), e.g. log_warn, log_debug, log_is_debug and so on. Log::ger also records the target name. When a reinit is requested (e.g. due to a change of log level or outputs), Log::ger will again construct logger routines and install them to each target, replacing the old ones.

In addition to installing routines to a package, Log::ger can also target a hash or an object (which is basically the same as installing to a package, but the routines will expect to be called as object methods instead of plain subroutines, i.e. they expect the first argument to be the object).

Structure of a logger routine

This is the logger routine(s) that Log::ger will construct and install to targets, in pseudo-code (the actual constructed code is more streamlined and optimized):

 sub {
     # early exit if there is no output
     if (no output is configured) {
         return 0;
     }

     # get per-message (per-logging statement) configuration
     my $per_msg_conf;
     if (filter is configured) {
         # filter can also decide to cancel logging based on some criteria
         $per_msg_conf = filter(@_);
         return 0 unless $per_msg_conf;
     }
     $per_msg_conf //= { level => $level_of_this_logging_routine };

     # construct formatted message
     my $fmsg;
     if (formatter is configured) {
         # for example, the default formatter might format ("Data is %s", [1])
         # into: "Data is [1]". A custom formatter might format arguments in a
         # different way.

         $fmsg = formatter(@_);
     } else {
         # if no formatter is installed, the arguments are passed as-is. note
         # that most output plugins expect the formatted message to be a string
         # instead of data structure, but some plugins can handle data
         # structure.

         $fmsg = \@_;
     }
     if (layouter is configured) {
         # usually a layouter adds more information to message, e.g. timestamp,
         # PID, etc

         $fmsg = layouter($fmsg, $per_target_conf, $levelnum, $levelstr, $per_msg_conf);
     }

     # send formatted message to an output
     outputter($per_target_conf, $fmsg, $per_msg_conf);
 }

$per_target_conf is the "Per-target configuration". $per_msg_conf is the "Per-message configuration". By default there is no filter; a plugin might supply one. An output plugin will supply the outputter routine. A default formatter is supplied by Log::ger but can also come from one or more plugins. By default there is no layouter, but a plugin can provide one. The actual routine names that will be used will be supplied by Log::ger (the default is log_trace, log_debug, and so on) or can be customized by a plugin via the "create_routine_names" hook.

By default, level filtering is done by Log::ger installing null logger routines (sub {0}) for routine names that are above the current level ($Log::ger::Current_Level). For example, the default level is warn (30) so routine names log_info (40), log_debug (50), log_trace (60) will get installed as a null logger, while the other routines log_warn, log_error (20), log_fatal (10) will get installed the logger routine described above.

Filtering by category or other criteria is usually performed by a filter or the output plugin (e.g. the composite output).

GLOSSARY

Category

A way to, or an attribute by which one can filter log messages. Category by default is set to the Perl package from which a log message is produced, but everytime you log a message, you can assign it an arbitrary string as the category.

The other, most important, way to filter log messages is by its level.

Composite output

An output that simply multiplexes the log message it receives to one or more other outputs.

Implemented in Log::ger::Output::Composite.

Filter

This is a routine that takes arguments supplied by the user in the logging statement (e.g. log(level=>"info", message=>"blah", category=>"foo", ...) and return a false value or a per-message configuration. This routine will be used by the constructed logger routine.

If false is returned, logging is cancelled. Otherwise, the per-message configuration hash is passed to the layouter and outputter.

Filter is optional and can be used to do custom filtering based on some criteria specified in the arguments, as well as to extract the per-message configuration in the arguments to be passed to the outputter routine (usually provided by an output plugin). For example, a screen output might want to colorize a log message based on the category passed in the argument.

Regardless of whether a filter is provided, logging will still be filtered by level.

Filter is the way a "multilevel logger routine" is implemented, i.e. instead of a dedicated logger routine per level, a general logger routine is used for all levels where the level information is passed as an argument. For example: log("info", "blah") or log(level=>"info", message=>"blah").

See also: "Formatter", "Layouter", "Outputter".

Formatter

A routine that takes arguments supplied by the user to the logger routine (e.g. log_warn("blah %s", $args, ...) and converts it to the message (usually string) that is sent to the output (or the layouter, if there is one). This routine will be used by the constructed logger routine.

See also "Filter", "Layouter", "Outputter".

Hook

A code (provided by plugins) that is called in various points of time (phase). Please see "HOOKS AND PLUGINS" for more details.

Hook priority

A way by which different hooks that register at the same phase are ordered for execution. Implemented as a number between 0 to 100 where 0 means very high (executed first) and 100 means very low (executed last). Hooks that do not care about the order in which they are executed with regard to other hooks should set their priority to 50 (normal).

Init

The process of constructing logger routines and installing them to targets. See "INIT" for more details about the process.

Layouter

A routine that takes the formatted message (usually a string) and converts it to the final message string that is sent to output. Usually a layouter is used to add additional information along with the log message itself, e.g. timestamp, source file/line number, PID (process ID), etc. This routine will be used by the constructed logger routine.

See also "Formatter", "Outputter", "Filter".

Level

The main way by which log messages are filtered. Level refers to the important or "severity" of a message. Log producer chooses to log a message with a certain level, e.g.:

    log_trace "This is a low importance log message";
    $log->log("error", "This is a highly important log message!");

while log consumer chooses only to "see" log messages "above" certain level only.

Another way to filter log messages is by its category.

Level checker routine

A type of routines that are constructed and installed to target. Its function is to check for a certain level. When logging level is at a certain level or more severe, the associated level checker routine will return true; and vice versa. This is an efficient way to avoid doing work when unneeded.

The default level checker routine names are "log_is_<LEVEL>", e.g. "log_is_trace", "log_is_debug", and so on. An example of using a level checker routine:

    use Log::ger;

    sub foo {
        if (log_is_trace) {
            # perform some possibly quite expensive calculation
            ...
            log_trace "Result of calculation: ...";
        }
    }

The other type of routines that are constructed and installed to target is logger routines.

Log consumer

A log consumer is a process (Perl application/script) which configures an output. This causes all log messages that are produced (either by the script itself or the modules that the script uses) to be directed to the specified output.

Log producer

Any code (typically a Perl module) that uses Log::ger and invokes one or more log routines. For example, this tiny module is a producer:

 package MyPackage;
 use Log::ger;

 sub foo {
     log_trace "Entering foo";
     ...
     log_trace "Leaving foo";
 }

 1;

When foo() is used somewhere else, it will produce two log messages at the trace level.

Logger routine

Or logger routine. A type of routines that are constructed and installed to target which produces a log message.

The default "log routine" names are "log_<LEVEL>", e.g. "log_trace", "log_debug", and so on. An example of using a "log routine":

    use Log::ger;

    sub foo {
        log_trace "Entering foo";
        ...
        log_trace "Leaving foo";
    }

Log routines is the main type of routines that you will typically use when logging. The other type of routines that are constructed and installed to target is level checker routines.

Output

Destination for the formatted (see "Formatter"), laid out (see "Layouter") log message. Only log messages that have the sufficient level and matching category get sent to the output. Examples of output include: null (no where), screen (terminal), file, syslog.

In Log::ger, log messages are sent to a single output by the outputter. But there is a multiplexer output that (re)sends the log messages to other output(s).

Outputter

A routine, usually supplied by an output plugin, that will actually send formatted log message to an output (e.g. screen, file, syslog). This routine will be used by the constructed logger routine.

Outputter will receive these arguments:

 ($per_target_conf, $fmsg, $per_msg_conf)

$per_target_conf is the "Per-target configuration". $fmsg is the message already formatted by the formatter and layouter, if they are available, or just the raw arguments specified in the logging statement. $per_msg_conf is the optional "Per-message configuration".

See also: "Formatter", "Filter", "Layouter".

Per-message configuration

A hash that is produced by filter, usually from information provided in the logging statement's arguments. For example, this logging statement:

 log(level => 'warn', message => 'blah', ...);

might be converted by the filter to this per-message configuration:

 {level=>'warn', ...}

The per-message configuration, if available, will be passed to the logger routine, so the logger routine can customize logging based on some criteria specified in the logging statement's arguments.

See also: "Per-target configuration".

Per-target configuration

A hash that is supplied when a log producer calls get_logger():

 my $logger = Log::ger->get_logger(category => "blah", ...);

or via import() arguments to Log::ger:

 use Log::ger category => 'blah', ...;

This hash will be used by the logger routine so the logger routine can customize logging based on category or other criteria.

Currently, the main use of per-target configuration is to supply category, which by default is set to the caller's package if not specified.

See also: "Per-message configuration".

Phase

The order in which hooks are executed. For more details, see "HOOKS AND PLUGINS".

Plugin

A Perl module that supplies hooks. For more details, see "HOOKS AND PLUGINS".

Target

A package that will be installed with logger routines. Aside from package, Log::ger can also install routines to a hash or an object.

Installing to a hash is usually for internal testing purposes. As a Log::ger user, you will very rarely need to target a hash.

Installing to an object is essentially the same as installing to a package: Log::ger will pick a "random" package for the object and install the routines there.

HOOKS AND PLUGINS

Hooks are how Log::ger provides its flexibility. At various times (phases), Log::ger will turn to running hooks to get some behavior or result. For example when wanting to construct a logger routine or formatting routine or before/after installing logger routines. Plugins, which are modules in the Log::ger::{Plugin,Output,Format,Layout,...} namespaces, can supply these hooks.

Hooks are stored in the %Global_Hooks variable, where the key is phase name and the value an array of hook records. There are also %Per_Package_Hooks, %Per_Object_Hooks, and %Per_Hash_Hooks to store per-target hooks that will only be used for specific targets. This way, the logger routines for each package and object/hash can be customized.

Each hook record is in the form of:

 [$key, $prio, $coderef]

where $key is (plugin) package name, $prio is a number between 0-100 (the lower the number, the higher the priority and the earlier it is run), $coderef is the actual hook routine. A plugin is supposed to put only at most one hook per phase.

Expected return value of hook

A hook routine is passed a hash argument and is expected to return an array:

 [$result, $flow_control, ...]

By default each hook will be executed in order of its priority. $flow_control can be set to 1 by a hook to stop immediately after this hook instead of continuing to the next. Some phases will nevertheless stop after the first hook that returns non-undef $result. A hook that returns undef is effectively declining and causing Log::ger to move to the next hook in the chain.

Some phases might return extra elements.

Arguments passed to hook

Aguments received by hook: target_type (str, can be package if installing to a package, or hash or object), target_name (str, when target_type is package, will be the package name; when target_type is hash will be the hash; when target_type is object will be the object), per_target_conf (hash, arguments passed to Log::ger when importing, e.g. {category => 'My::Package'}; it also serves as a per-target stash which survives reinit, by convention you can put stuffs here under keys that start with _). In some phases, hook will receive more arguments (see phase documentation below).

Phases

Available phases:

  • create_filter

    Used to construct filter routine.

    It should return:

     [\&filter, $flow_control]

    The &filter routine will be passed the logger routine arguments and should return a false value (to signal that the logging is to be cancelled) or a hashref of per-message configuration (usually extracted from the arguments). This per-message configuraion hashref will be passed to the layouter and the logger provided by an output plugin.

  • create_formatter

    Used to construct formatter routine.

    It should return:

     [\&formatter, $flow_control, $formatter_name]

    $formatter_name is optional and defaults to default, which is the default formatter used for all logger routines.

    &formatter will be passed the logger routine arguments and is responsible for returning the formatted message (usually a string). For example, the default Log::ger formatter either accepts a single argument (the message, which will be passed as-is as the formatted message) or multiple arguments (the first argument as template and for the rest of the arguments as variables, which will be processed a la sprintf() to produce the formatted string).

  • create_layouter

    Used to construct layouter routine.

    It should return:

     [\&layouter, $flow_control]

    &layouter will be called with arguments:

     ($fmsg, \%per_target_conf, $lnum, $lname, \%per_msg_conf)

    where $fmsg is formatted message from the formatter, %per_target_conf are arguments given to Log::ger->get_logger or to Log::ger's import(), $lnum is numeric level, $lname is string level, %per_msg_conf is information provided by the filter if there is one. The layouter must return the laid-out message (usually a string).

  • create_routine_names

    Used to construct routine names. Hook must return this (all keys are optional):

     [{
       logger_subs           => [ [NAME, STR_LEVEL, FMT_NAME, PER_TARGET_CONF, FLT_NAME], ... ],
       logger_methods        => [ [NAME, STR_LEVEL, FMT_NAME, PER_TARGET_CONF, FLT_NAME], ... ],
    
       level_checker_subs    => [ [NAME, STR_LEVEL, FMT_NAME, PER_TARGET_CONF, FLT_NAME], ... ],
       level_checker_methods => [ [NAME, STR_LEVEL, FMT_NAME, PER_TARGET_CONF, FLT_NAME], ... ],
     }, ...]

    Where logger_subs and logger_methods are names of per-level logger routines, level_checker_subs an level_checker_methods are names of per-level level checker routines.

    FMT_NAME is optional and defaults to default (the default formatter routine). Can be set if the routine should use a custom formatter routine.

    PER_TARGET_CONF is an optional. hash. Can be set if the routine should use a custom per-target configuration (e.g. different category).

    FLT_NAME is optional. Can be set if the routine should use a custom filter routine.

  • create_outputter

    Used to create the outputter routine used inside the logger routine (see "Structure of a logger routine").

    It should return:

     [\&outputter, $flow_control]

    &outputter will be called with:

     (\%per_target_conf, $msg, \%per_msg_conf)

    for each routine name specified in the log_subs (or log_methods) in the routine names (see documentation on create_routine_names phase). Extra arguments received by hook: routine_name (routine name), level (numeric level), str_level.

  • create_level_checker

    Used to create per-level "log_is_level" routines.

    It should return:

     [\&level_checker, $flow_control]

    &level_checker will be called for each routine specified in the level_checker_subs (or level_checker_methods) in the routine names (see documentation on create_routine_names phase). Extra Arguments received by hooks: routine_name (routine name), level (numeric level), str_level.

  • before_install_routines

    Will be called before routines are installed to target.

    Extra arguments received by hooks: routines (array of routines to install), formatters (hashref of formatter routines, if any), layouter (layouter routine, if any).

    routine is in the form of:

     [
       [$coderef, $name, $num_level, $type],
       ...
     ]

    Where $type is either logger_sub, logger_method, level_checker_sub, level_checker_method.

  • after_install_routines

    Will be called after routines are installed to target.

    Extra arguments received by hooks: routines.

Aside from the global hooks, there are also per-target hooks, which are stored in %Per_Package_Hooks, %Per_Hash_Hooks, %Per_Object_Hooks.

INIT

This section describes what init_target(), which is the routine used to initialize a target, does.

First, hooks in the create_formatter phase are run. This will collect one or more formatters. In the most common case, only the default formatter will be constructed. In some cases, like Log::ger::Like::LogAny, we want to use different formatters for method like warn and debug (arguments simply joined by space) and for methods like warnf and debugf (sprintf-style with data structure dumping, the default formatter used by Log::ger).

Next, hooks in the create_layouter phase are run. This will create a layouter (multiple layouters might be supported in the future, but for now we only use one layouter per target).

Next, hooks in the create_routine_names phase are run. This will produce a list of subroutine/method names to create, along with what formatter to use for each (the default is default). Plugins that want to mimic other interfaces like Log::ger::Like::LogAny or Log::ger::Like::Log4perl will want to add their hook here to provide names other than Log::ger's default. For example, Log4perl has uppercase subroutine names like WARN or DEBUG.

There are two types of routines that will be created, logger routines (like log_warn, log_debug) and level checker routines (like log_is_warn, log_is_debug).

Next, the outputter routines (to be used in the final logger routines) are created by running the create_outputter hook, for each log level. The code from the hooks (usually from output plugins) are then combined with the formatter and layouter to form the final logger routine ready to be installed.

Likewise, the level checker routines are created by running the create_level_checker hooks for each log level.

Before installing the routines, we give a chance to plugins to do stuffs in the before_install_routines phase. Some plugins use this phase to, e.g. fixup prototypes.

After installing the routines, we likewise give a chance to plugins to do stuffs in the after_install_routines phase. Some plugins like Log::ger::Plugin::OptAway this phase to actually replace the routines that are not needed with a no-op.

TARGETS

Log::ger can install logger routines to a package, or an object (which is similar to installing to a package), or a hash (usually for testing). The routines that are installed into package target are of type *_subs. The routines that are installed into object target package are of type *_methods. The routines that are installed into hash target are of type *_methods but they will expect to be called as a normal subroutine (i.e. no object reference as first $self argument).

AUTHOR

perlancar <perlancar@cpan.org>

COPYRIGHT AND LICENSE

This software is copyright (c) 2022, 2020, 2019, 2018, 2017 by perlancar <perlancar@cpan.org>.

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.