ALox  V. 2402 R. 0
Home ALox for C++ ALox for C# ALox for Java Download
03 - Terminology and Key Concepts
Attention
In respect to the C++ Version of ALox, this manual is outdated. C++ Users, please visit ALib C++ Library.
The deep-link (we hope this still works, otherwise please quickly find it yourself from the above library link above) should be this.

This chapter introduces to the major concepts of ALox.

Before we explain those, we want to start with defining terms used by this manual and by the reference documentation (C++, C#, Java).

1. Terminology

1.1 Loggers

ALox is designed to support different log streams. A log stream is a destination of log messages and can be the IDE console, the terminal console, a file, a web service or anything that accepts data streams. Abstract class Logger (C++, C#, Java) represents such a log stream. While some implementations of class Logger are provided with the ALox package, the system allows custom implementations of Loggers that log information to any 'data drain' in any format you like.

When using ALox, there is not much interaction with classes of type Logger, though. The reason for this is, that the data that is logged, should optionally be directed into multiple Loggers at once. Therefore, the API interface for executing the logging itself, is residing somewhere else and internally, ALox dispatches the log data to the list of Loggers in question.

The interaction with a class Logger (and its derived types) is therefore limited to the following:

  • Creation of a Logger
  • Optional configuration of a Logger (e.g. details on the output format, etc.)
  • Registration of the Logger with ALox.

1.2. Logables

What can be described as "the data that is logged", is called a 'Logable' or, as it may be more than one, the Logables in ALox. While most logging APIs are restricted to logging text messages, ALox is not. Still, the usual case is logging text messages, but the architecture of ALox allows to log an arbitrary amount of objects of arbitrary type.

For technical reasons, the interfaces to pass the Logables to ALox are slightly different in the different language versions:

  • ALox for Java:
    Here, parameters of Logables are of type Object. Loggers that process such object are using runtime-type information capabilities of Java (e.g keyword instanceof) to interpret what was dispatched to them as a Logable.
    Many methods use variadic arguments (ellipsis syntax, '...') to be able to accept an arbitrary amount of Logables. In some cases this is not possible (e.g. due to overloaded methods and ambiguities with those), only one Logable of type Object is accepted. In this case, the object passed may be of type Object[] which is then internally "flattened" into a list of Logables as if each object of the list was passed.
  • ALox for C#:
    Similar to Java, in C#, parameters accepting Logables are of type Object. However, in C# it is not possible to use variadic arguments (ellipsis syntax, '...') to allow an arbitrary amount of logables. The reason is that most interface methods have "hidden" parameters that are filled automatically by the compiler with caller information (the actual source code, method name and line number). Therefore, the number of parameters that denote the Logables is limited to four.
    The good news is, that any of these four parameters may be of type Object[]. Arrays are "flattened" internally and added to the list of Logables. With this "trick", ALox for C# manages to provide a simple interface, where only in the case that more than four logables are to be passed those have to be placed in an array. Such placement is usually done "inline", right within the invocation parameter list.
  • ALox for C++:
    For C++, the situation is slightly different due to the fact that this language (for good reasons) does not support "runtime type information" and no unique parent class Object exists that is the natural origin of all classes. Nevertheless, the logging interface provides the same seamless and natural way of passing any type of argument as provided in the other language implementations!

    This is reached by using ALib Boxing. This (underlying) library uses template meta programming to automatically wrap whatever a user passes as an argument into an object of type Box (e.g. char, int, double or pointers, values, references to and even arrays of objects of any type). Once a parameter is boxed, it can be passed further (in our case to the Loggers) and then processed in a type-safe way. Besides the ability of getting type information from the "boxes" and to "unbox" their values, ALib Boxing allows to define and invoke some sort of "virtual interface methods" on boxed values. And all this can be done on arbitrary types, without modifying (preparing) the types for that.

    In short, ALib Boxing allows a user of ALox to pass just any type of objects to the logging system without prior conversion. Also it is irrelevant if pointers or references are passed, just anything may be "thrown in"! An ALox user does not need to know how this works, it is all hidden behind the scenes. Only for very advanced use, the knowledge of the details of ALib Boxing may become relevant.

In the case of textual logging (the standard case, including 'simple' debug logging), all Logables are processed by instances of derived types of class Formatter (C++, C#, Java). This abstract class is defined in underlying library ALib. This is why ALox logging optionally allows to pass format strings which include placeholders and formatting options, like field width, alignment, tabulators, quotes, and much more. If no format string is detected, string representations of the Logables will be just concatenated and logged. This way, formatting the output is just an option.

And the flexibility goes even beyond that: By default, two built-in incarnations of this class are used: FormatterPythonStyle (C++, C#, Java) and FormatterJavaStyle (C++, C#, Java). The two types are used in parallel! This means, that the already optional and auto-detected format strings may follow either the Python String Fromat Syntax, which is also similar to what C# offers, or the formatting standards of the Java language, which in turn are a little like good old printf, but of-course much more powerful and also type-safe.

It can be concluded that ALox logs just anything a user passes to it as Logables: Textual or binary data and if textual, optionally accepting auto-detected formatting strings of different syntactical type!

1.3. Log Statements

When this manual and the reference documentation of ALox are referring to a 'Log Statement', this means a piece of (user) code that invokes an ALox interface method that potentially performs log output.

Of-course, each Log Statement incorporates the provision of one or more Logables to ALox. Hence, those interface methods of the ALox API that carry a parameter named logable (or a list of those, named logables can easily be identified as methods that comprise Log Statement.

1.4. Log Domains

The term 'Log Domain' in ALox denotes a sort of 'key' that is used to group Log Statements into different sets. Each Log Statement belongs to exactly one specific set of Log Statements by having a Log Domain associated.

Such association can be made by using an optional parameter named domain which is available in each interface method of the ALox API which comprises a Log Statement.

Note
To be precise: There are no such things as 'optional parameters' in Java and also in C# and C++ parameters that are followed by non-optional parameters can not have a default value in a methods declaration and therefore can not be 'optional' in the sense of the language definition. Therefore, when we talk about 'optional parameters', this means that an alternative, overloaded method with the same name exists, but excluding this parameter from the list.
Furthermore, important Log Statements do not have an explicit domain parameter. Instead ALox uses a mechanism to auto-detect Log Domains at the first position in the the list of parameters that otherwise are the Logables of the statement. This is done to further ease the use of log statements.

A typical sample for a Log Domain might using name "UI" with all Log Statements that concern the user interface of an application. A developer may switch Log Statements of Log Domain "UI" completely off in the moment he/she is not interested in UI development. Alternatively, warning and error messages might get allowed. Later, when working on UI related code again, that Log Domain may be re-enabled. If a team of developers is working on a project, each team member may configure ALox to enable those Log Domains that he/she is currently working on.

Although parameter domain is optional, still each Log Statement is associated with a Log Domain. The way how ALox 'magically' associates Log Statements with 'the right' Log Domain, without the need for the user to be explicit about it, is one of the features that makes ALox quite unique. More about this will be explained in later chapters of this manual.

1.5. Verbosity

It is very common for logging eco-systems, to implement the concept of 'verbosity levels', and ALox is no exclamation to this. First of all, the term Verbosity denotes an attribute of Log Statements. The values of this attribute are defined in enum class Verbosity (C++, C#, Java)

Some of the interface methods that comprise Log Statements carry the Verbosity in their method name, e.g. warning() or verbose(). Other, specialized methods offer a parameter named verbosity of type Verbosity.

Besides Log Statements, Verbosity is also an attribute of Log Domains. As every Log Statement has a Log Domain and a Verbosity associated with it, ALox is able to match both values against each other to decide whether a Log Statement is executed or not.

When you think about this for a second, what is gained by this concept becomes obvious: The overall verbosity of the log output is not controlled globally but on a 'per Log Domain basis'. This allows to just switch the topic that an application is logging about. When interested in user interface things, a Log Domain called "UI" might be set to a 'more verbose' level, while other Log Domains are switched off or are reduced to real error messages.

As described later in this manual, the possibilities of ALox to filter the log output are even more granular and flexible.

1.6. Scopes

The 'Scope' is a next attribute any Log Statement possesses. The different types of scopes are nested into each other and some of the possible values are similar to what the programming languages that ALox is written in provide. For example Scope.Method is similar to what a variable that is defined in a method has as a scope from a language perspective: it is visible only in exactly this method. But still, in ALox, Scopes are different and the main reason is that ALox is not able to detect scopes in the same way as a compiler of a programming language is. Therefore, we are talking about language related scopes. In C++ and C# those are:

  • Scope.Path
    The file path that a source file which contains the Log Statement in question is located in. As this can be also parent directories, this Scope type represents in fact on its own already a nested set of scopes!
  • Scope.Filename
    The name of the source file in which a Log Statement is located in
  • Scope.Method
    The name of a class method (in C++ also 'global' function names are detected) that a Log Statement is located in.

In Java, they are slightly different and named 'Package', 'Class' and 'Method'.

Besides these language-related Scopes, ALox in addition defines thread-related Scopes. Those are relating to the thread that is executing a Log Statement. There are two of them, one defined as an 'outer' Scope of the language-related set of Scopes, the other is an 'inner' Scope of those.

All information about this topic is found in chapter 05 - Scopes in ALox. The corresponding enum type in the reference documentation is Scope (C++, C#, Java).

1.7. Scope Domains

Attention: now it becomes tricky! Each Log Statement belongs to a Scope, precisely, even to a set of nested Scope values. The grouping of the statements into Scopes is done automatically by ALox. The Scope is detected from the position of the Log Statement within the source code and from the thread that is executing it. Now, a Scope Domain is a default domain set by the user of ALox for a specific Scope. Once such default value is set, ALox uses this domain for each Log Statement located within that Scope. This way, Log Statements get their Log Domain associated 'automatically'.

As a sample, a Scope Domain "DB" could be set as the default Log Domain for Scope.Filename (In ALox for Java: Scope.CLASS) that contains code that stores and retrieves data from a database management system. Now, all Log Statement within this source file (class) get this Log Domain automatically associated, without explicitly specifying "DB" with every Log Statement. Therefore - although each Log Statement needs to refer to a Log Domain - such domain is not needed to be added to each statement into the source code. This has several advantages: less typing, less code clutter by Log Statements, copied Log Statements magically switch their domain when 'pasted' into a different scope, and so forth.

As you see, there are two ways to assign a Scope Domain to a Log Statement: Either by providing optional parameter domain with a Log Statement, or by setting a Scope Domain and omitting the parameter.

1.8. Tree of Log Domains and Domain Path

By having Scope Domains which associate a 'default domain' with a Log Statement that resides in a certain Scope and knowing that the Scopes are nested into each other, the question is what happens if multiple Scope Domains apply to the same Log Statement? Or, a similar question: what happens if a Scope Domain is set for a Scope that a Log Statement resides in and in addition, the Log Statement uses optional parameter domain to explicitly specify a Log Domain?

The answer is: ALox concatenates all Scope Domain to a Domain Path, separated by character '/'. This means that ALox organizes Log Domains hierarchically, hence this can be seen as a tree of Log Domains. The concatenation starts from the most 'outer' Scope and ends at the most 'inner'. The value of optional parameter domain is appended close to the end - but not completely at the end.

Besides 'mixing' Scope Domains and parameter domain, ALox also allows to 'overwrite' Scope Domains with parameter domain.

Using the techniques in the right manner, is one of the keys to efficiently use ALox. The details of how this is done is explained in a dedicated chapter: 04 - Log Domains.

2. Class Lox - Managing it all

The definitions and terminology that were introduced in this chapter so far should be quickly summarized. We have:

  • Loggers and Logables:
    Loggers are responsible for writing the contents of Logables to dedicated output 'drains' like the console, a text file or a remote server. Multiple Loggers might exist even in quite simple applications.
  • Log Statements and associated attributes:
    A Log Statement is the user code that invokes the interface API of ALox and pass a Logable, e.g. a text message to ALox. Each Log Statement has three attributes besides the Logable:
    1. A Verbosity, defining the 'importance' or 'severeness' of the statement.
    2. A Log Domain that makes the Log Statement belong to a certain set of Log Statements. Log Domains can be considered to specify the 'topic' that a Log Statement is 'talking' about.
    3. A Scope, which gives a different, automatic way of grouping Log Statements.
  • Scope Domains: Those are acting as 'default domains' and are collected and concatenated by ALox to form, together with parameter domain of a Log Statement, a 'domain path' identifying the resulting, final Log Domain of a Log Statement.

Now, the most prominent class of ALox which acts almost like a "single point of contact" comes into the game: Class Lox (C++, C#, Java).

This class keeps it all together! This is what class Lox does:

  • It provides the most important parts of the ALox API, especially those interface methods that comprise Log Statements.
  • It manages a set of Loggers which write the Logables of Log Statements.
  • It maintains a tree of hierarchically organized Log Domains.
  • It stores a Verbosity value for Log Domains, which is the counter-value of a Log Statements' Verbosity and determines if a Log Statement is executed or not. This is done on a per-Logger basis.
  • It automatically determines the Scope of a Log Statement and manages the associated nested Scope Domains.
  • It provides other nice features related to Scopes, like 06 - Lox.Once(), 07 - Prefix Logables or 08 - Log Data (Debug Variables)
  • It collects some meta information like timestamps or counters.
  • It provides a dictionary to translate thread IDs in human readable (logable) thread names.

It becomes clear that this class is an ALox users' main interface into logging. After ALox was set-up once (probably in the bootstrap section of a software), and Loggers are created, configured and attached to a Lox, this class is almost all that is needed across all other parts of a software. All main ALox functionality, especially the interface for the logging statements themselves is comprised in this class.

One important detail of the internal management of class Lox is the fact that it associates a separated Verbosity value for each combination of Log Domain and Logger.
The rational behind that is easy to understand: An application that supports different Loggers at once (which happens quite quickly when using ALox), might want to log different subsets of the log messages with a different Verbosity to each of theses Loggers. For example, a Logger dedicated for debug-logging that streams into an output window of an IDE, would be desired to be generally more verbose and also switch Verbosity more frequently, than a Logger that in parallel logs into a file which is storing also logs of earlier debug sessions.

2.1 Prefix Logables

While those interface methods in class Lox (C++, C#, Java) which comprise a Log Statement already accept an arbitrary amount of Logables as parameters, this list can even be extended by further objects which are then all together passed to method Logger.Log (C++, C#, Java) of each Logger attached.

Such additional objects are called Prefix Logables. In short, ALox allows to associate Logables to Scopes. This way, all Log Statements 'collect' such Prefix Logables which were defined to any of the nested Scopes in a list and passes them to the Logger. We could have named them Scope Logables or Context Logables, however, the word 'prefix' makes perfect sense with the most important type of Logables, namely strings! With logging text messages, Prefix Logables act like a prefix to a log message. All about this topic is found in chapter 07 - Prefix Logables.

2.2 Log Once

Another feature of ALox which leverages the concept of Scopes, is found with overloaded methods Lox.Once (C++, C#, Java).

They are really 'heavily' overloaded, therefore the most simple version just accepts a Logable. With this version ALox hides the use of Scopes and offers what you would expect from the methods' name: logging a statement only the first time it is executed. The different parameters allow to cover more complex uses cases than this. All about this Log Statement is found in chapter 06 - Lox.Once().

2.2 Log Data

As being able to 'prune' ALox debug-Log Statements from release code, a low hanging fruit for the ALox feature list is to offer a concept for storing and using data, that otherwise would be temporary debug variables during the development process. Again, ALox Scopes are leveraged which makes the otherwise simple methods Lox.Store (C++, C#, Java) and Lox.Retrieve (C++, C#, Java) quite powerful.

All about Log Data is found in chapter 08 - Log Data (Debug Variables).

3. Using Multiple Lox Instances

While ALox claims to be lean, easy and Bauhaus code style, besides these concepts explained in this chapter, it was decided that a next level of complexity is supported. The good news is: for simple use case scenarios, you do not need to know about that.

So, this new 'level of complexity' is simply introduced by the fact that it is possible, and sometimes very attractive, to create and use more than one instance of class Lox in a software. Each class is populated with Loggers and of-course uses an own dedicated tree of domains.

The following paragraphs gives use cases and further background on using multiple Loggers.

3.1 A Dedicated Lox Singleton for Debug Logging

There are two fundamental logging scenarios that we call debug logging and release logging. (For information, see What do the terms "debug logging" and "release logging" mean?.)

For various reasons (performance, code size, security, etc), debug Log Statements should be disabled and removed (pruned) from the release version of a software.

To achieve all goals and provide a very simple interface into debug logging, the ALox ecosystem provides class Log (C++, C#, Java) This class is a 100% static interface into class Lox. In other words, class Log creates and holds exactly one static instance of class Lox and mimics the complete interface of that instance into corresponding static methods.

The assumption (restriction) that is taken here, is that debug logging is implemented by using one dedicated Lox. This should be sufficient for most scenarios, because, as described above, within that Lox instance various Loggers with own Log Domain settings will provide a lot of flexibility to log different types of messages into different streams and manipulate the verbosity for each stream accordingly.

Note
Differences of language implementations:
  • In C# and Java, the only reason to introduce class Log and to mimic the interface of Lox with it, was that this opened the possibility for efficient pruning of debug Log Statements.
  • In C++, pruning is realized by preprocessor macros. Hence, class Log there does not mimic any interface method of Lox as said above. Still class Log exists and provides the remaining methods specific to debug-logging.
For more details on pruning and the use of class Log, consult the language specific documentation: C++, C# and Java.
For a more details on debug and release logging see chapter 10 - Differences of Debug- and Release-Logging.

3.2 Separating Mission Critical Log statements

Another motivation for using separated instances of class Lox may be understood when thinking about use cases where things start getting critical from different point of views. For example:

  • A Lox is supposed to collect runtime data from the field, hence metrics, which are transferred using a tailored Logger that contacts a metrics server at runtime. The team that implements such metrics collection, may, for good reason, not want to share 'their' Lox with other parts of a software maintained by another team. Accidental mis-configuration of the Lox and its domain may lead to uncontrolled server communication.
  • A Lox is supposed to collect critical runtime errors from deployed software. Such log information should be separated from other log streams, e.g. much more 'verbose' standard release-logging that goes to a rolling log file
  • A local software want to support writing messages to the Linux or Windows OS specific system journal. Also in this case, a mis-configured Lox might 'spam' into such system journals in an inappropriate manner, and it would be advised to use a separated Lox that is not touched outside its scope of activity.

3.3 Multiple Registration of a Logger

It is explicitly allowed to attach a Logger to more than one Lox object. class Logger implements a mechanism to protect against race conditions in multi-threaded environments as soon as such double-registration occurs. The obvious use case is again the mission-critical, separated Lox described in the previous paragraphs. A Logger that is responsible for 'standard' release logging, e.g. logging into a rolling release log file, can be attached to the 'mission-critical' Lox which holds the corresponding 'mission-critical' Logger. Now, the standard release log automatically receives a copy of the 'mission-critical' Log Statements.

4. Class ALox and Registration of Lox Instances

Pure static class ALox (C++, C#, Java) keeps a list of all instances of class Lox that were registered with it. Registration is done by default when constructing a Lox. The advantage of that registration is that a Lox can be retrieved statically using ALox.Get (C#, Java). This is convenient because this way, references of a Lox do not be passed around or otherwise made public.

But there might be situations, when the this 'public availability' of a Lox instance is not wanted. For this case, optional parameter doRegister may be set to false when invoking constructor Lox.Lox (C++, C#, Java).

Note
Descriptions of other 'protection' mechanisms against unwanted manipulation of class Lox are described in:

Next chapter: 04 - Log Domains
Back to index