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

			  Stem Logging Design

The Stem logging subsystem is designed to be very flexible, powerful and
yet simple to use. Log data comes into the system via a Log Entry which
is submitted to Logical Logs. Entries can be submitted to multiple
Logical Logs which can be local to the current Hub or on remote
Hubs. Each Logical Log processes the Entry which can be filtered,
redirected and written to physical log files.  Logical Log filter rules
can match the text or label with regular expressions, test the range or
value of the level, check the time of day or do any boolean combination
of those. If an Entry passes a set of rules, then it is passed to a set
of actions which can execute a wide range of operations upon it
including printing the entry to a file, sending it via email or to a
pager, printing it to stderr, or the entry can be forwarded to other
Logical Logs. The full set of filter rules and actions are described
below.

Log Entries are constructed with the 'new' method of the
Stem::Log::Entry class. The caller can set the entry's text, label, and
a severity level and the timestamp is automatically stored in the
entry. If any Logical Logs are specified the entry is submitted to
them. In any case, the entry object is returned and it can be submitted
to Logical Logs with its submit method. The class Stem::Log::Entry is
registered as a Cell so Log Entries that are forwarded from remote Hubs
can be handled by this class.  Log Entries can be created by created and
submitted by code anywhere in Stem. Many Cells can be configured to
submit Log Entries which contain data or status information. The
Debug/Trace subsystem also generates Log Entries as do any monitoring
modules such as Stem::LogTail.


A Logical Log is constructed by the 'new' method of the Stem::Log
class. They are typically created by external configurations but some
modules create them internally for their own use. Each Logical Log on a
Hub must have a unique name and that is the name used to submit Log
Entries. Remote Logical Logs are referred to by a string of the form
'Hub:LogName'. Any place where you can specify a Logical Log name, you
can also use a remote Log name.

When a Log Entry is submitted to a Logical Log it gets filtered and
processed. The Logical Log is configured with optional physical file and
filter attributes. If there is no filter in a Logical Log, its default
is to print any submitted Entries to its file (if there is one). Logical
Logs don't need to have a physical file attribute as they can filter and
print their Entries to many other possible destinations (see below for a
list of actions and Entry destinations).

The 'path' attribute of a Logical Log specifies its file. Other
attributes control the long term management of the file. They include
when to rotate the log file, the format of the timestamp suffix of the
rotated files, any compression to be performed, where to move archived
logs, eternal programs to be called to process the log file, etc. These
log file handling attributes and their code support are under development.

The filter attribute of a Logical Log consists of a set of key/value
pairs which are called filter operations. When an Entry is submitted to
a Logical Log which has a filter, a private hash copy of all of its data
is made and a special boolean called the filter flag is set in that
hash. All of the filter operations are processed sequentially and work
with that flag. The operations can be grouped into 3 types, flag
operations, rules and actions. Flag operations directly modify the
filter flag and its behavior which is used to control the rules and
actions of this filter. Rules are boolean tests that check the submitted
entry for some condition and can set or clear the filter flag. Actions
print or forward the submitted Entry only if the filter flag is
currently true. The filter flag is initialized to true so all actions
and rules will be executed until some rule or flag operation clears it.

Flag operations are always executed regardless of the current value of
the filter flag. The current value of the filter flag can be set,
cleared or inverted. Also the boolean operation that is used with the
rules can be selected. It defaults to 'and' which causes each rule's
boolean result to be 'and'ed with the filter flag and stored there. If
the flag operator is set to 'or', then the rule result is or'ed with the
flag and stored back into it. The boolean test of the filter flag can be
inverted with the 'invert_test' flag operation. By combining the flag
operations and the negated prefix of rules (see below) you can get any
boolean combination of rules. If you want multiple sets of rules each
with their own set of actions in a filter, just set the filter flag to
true before each set of rules and follow them by their associated
actions. If you want to execute some actions if any of a set of rules is
true, set the filter flag to false, set the flag operation to 'or' and
set the test to inverted. The next rules will execute since the test in
inverted and the flags is false. If any rule returns true, it will will
set the flag since it is 'or'ed with it. The rest of the rules will be
skipped. Then the normal_test operation should be executed. The actions
that follow will only be executed if any rule was true.

Filter rules are only executed if the filter flag is currently true (or
false when the inverted_flag operation is in effect). Each filter rule
name can be prefixed with 'not_' which will invert the results of the
rule. There are many builtin rules which are grouped into three
categories. The first group matches either an Entry label or text with a
regular expression. The second group compares the Entry severity level
with an integer. The third group compares the Entry severity level with
a global value in the %Stem::Vars::Env hash. Those hash values can
be set on the command line, from environment variables and by code. This
allows for fine control of how Entries get filtered by level. Examples
of using that facility are to enable debug/trace calls to output to
stderr or be forwarded to a remote Logical Log.

Filter actions, like filter rules are only executed if the filter flag
is currently true (or false when the inverted_flag operation is in
effect). But actions cannot affect the value of the filter flag and are
meant to send Log Entries to different destinations. The builtin actions
can print Log Entries to stdout, stderr or the controlling TTY. Entries
can be emailed, sent to a pager, written to the console with the wall or
write commands, or forwarded to other Logical Logs. Of course they also
can be written the to physical file associated with this Logical Log.

WARNING: Currently forwarding loops can be created with Log filter
actions. There are plans to detect them with either storing in the Log
Entry a hop count or a history of which Logical Logs it has seen.

Custom filter rules and actions can also be created. Any module can have
them and they are called by their name which is the value of the
'custom' operation. The difference between a custom rule and action is
that the rules return a defined boolean value while the actions return
the undefined value (a plain return does that).

When a Log Entry needs to printed by an action (which all builtin ones
except forwarding does), it must format the Entry. This is controlled by
the 'format' attribute of the Logical Log. The format value is similar
to sprintf and uses % as a field marker. It can print the Entry text
(%T), label (%L), level (%l), timestamp (%l) and original Logical Log
name (%N) (so forwarded Log Entries can say where they came from). The
default Log Entry format is %T which will just print the text. Also the
timestamp which is normally printed as an integer (Unix Epoch time) can
be printed with the %f marker in a strftime format. The attribute which
controls the time format is 'strftime'.  The default strftime format is
%C which will print the time as the command 'date' will.