ALib C++ Library
Library Version: 2402 R1
Documentation generated by doxygen
Loading...
Searching...
No Matches
ALib Module ALox - Programmer's Manual

Table of Contents

Attention
This Programmer's Manual grew to roughly 70 pages. The good news is: ALox is simple - it only can get complex, when you want it all of it.
Therefore, for new users, please check out the
            !!! ALox TUTORIAL !!!
instead of reading here!

1 About ALox

ALox is a module of the general purpose C++ library ALib C++ Library . Nowadays it is fully integrated in ALib , and has the status of what we call an ALib Camp . Historically, since 2013, ALox was developed in parallel for programming languages C# and Java likewise. However, the JAVA and C# versions are not maintained anymore since 2018, but are still available and found with this link: ALox for C# and Java, while the C++ version is and will be continuously maintained.

Within ALib , module (or "Camp") ALox provides an ecosystem to support structured and well organized log output. The library attempts to make debug logging as easy and lightweight as possible. One of the goals we want to achieve is that developers, once using the library, just stop to put any temporary debug output statements into their code, but do it all with nice, readable, configurable, reusable logging statements that can be temporarily or long-term switched off and that are furthermore automatically pruned out of release versions of the application.

On the other end of the list, we want to enable release logging for applications in production systems (release versions) to collect mission critical metrics and data from the field. Basic support for release logging is here today already. The introduction of new features and concepts in the area of release logging will also support debug logging aspects of ALox . Therefore the natural path of evolution of ALox is "debug features first, release features next".

The rather simple class structure and architecture of the library allows users to easily extend ALox . If you extended ALox by new Loggers or features, you are welcome to contribute your innovations to this open source project.

Attention
While the ALox class hierarchie looks quite simple and while you might expect a very simple manual, because all you wanted is "just logging", be a little warned:
The concepts of ALox , when investigated deeper, are maybe more complex than what you would expect from a typical logging library. Due to the "orthogonality" of the features, which means you can just omit the complex stuff and don't even notice that it exists, ALox can be used in a very simple fashion though. Therefore, when you are searching for a simple, quick logging library, you have found it. In this case, instead of reading this manual, use the tutorial and just get started. Read this manual only when you want to understand ALox in full!

2 Features and Limitations

  • Free software, published under Boost Software License.
  • Jump-start Tutorial available.
  • Formatted, colorful, configurable log output with support for ANSI compatible consoles and Windows OS consoles.
  • Log lines 'clickable' in IDE to jump to the corresponding source code line (depending on IDE).
  • Automatic collection of meta information like time stamp, time-difference to previous log, executing thread or executing source scope information.
  • Automatic removal, aka 'pruning', of debug-log statements from release code mostly without the need of pre-processor #if/#endif code cluttering.
  • Minimal intrusiveness in respect to impact on your code and what you need to make it really work.
  • Use of external configuration (INI-files, command line parameters, environment variables, etc.) which allows developer-specific local settings and greatly supports working in teams.
  • Hierarchical 'Log-Domains', (like for example "MYAPP/UI", "MYAPP/DATABASE" that allow the classification of different Log Statements into user defined topics. Verbosity is controlled per domain to allow the programmer to concentrate on the current area of interest.
  • Defaulting 'Log-Domains' for source scopes, like file, class or method, which allows to omit the log-domain parameter from the Log Statements (again: leads to short, clean and readable Log Statements).
  • Defaulting 'Log-Domains' for thread scopes, which allows to control the verbosity of the log output on a per thread basis.
  • Very fast and minimal runtime overhead.
  • Multiple parallel log streams with a different Verbosity, e.g. for logging to the console and in parallel into a file.
  • Extensible with own loggers, which enables dedicated "data drains" (e.g. databases or Internet services) and custom output formatting. ALox provides a simple programming interface and internal architecture that is easy to learn.
  • Logging of arbitrary objects which means the whole ALox architecture builds on logging ' objects' instead of just string messages. Only the logger instances which are attached at runtime, decide how logged data might be converted to human readable strings.
  • Scope dependent Log Data to add debug variables that automatically get pruned like the debug-logging statements are.
  • Conditional logging (similar to assertions).
  • Log Once to restrict a log message or a set of log messages to appear only once (or up to n-times).
  • Support for Recursive logging, which is the correct execution of log statements during the evaluation of parameters of another log statement.
  • Mapping of thread IDs to readable thread names
  • Extensible to log custom types in custom way.
  • ALox is in a stable state since years and continuously maintained. You're invited to contribute!

3 Terminology and Key Concepts

3.1. Terminology

3.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 detail::Logger 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, can 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 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 .

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

Because C++ is a very strictly typed language, a lot of prerequisites need to be in place before it is possible to (safely) drop any sort of object into a logging interface. These prerequisites are created in other ALib Modules , that ALox builds upon. The most important of them are:

In short, ALib Boxing is "std::any on steroids", allowing to pass any sort of object and "box" it together with runtime type information. Interested users might consult the Programmer's Manuals, tutorials and reference documentation of the ALib Modules linked above. For now it is enough to know: ALox accepts any sort of object to be dropped as Logables into the logging system and internally can access the data in a type safe way, just as it is true with higher-level programming languages like Java or Python. And, 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 methods" on boxed values (this is why this concept is much more than what std::any provides.

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 . 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 and FormatterJavaStyle . 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!

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

3.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 the same 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: Most ALox 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 list of Logables of the statement.

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

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

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.

3.1.6. Scopes

The 'Scope' is a next attribute any Log Statement possesses. The different types of scopes are nested into each other and are a little similar to what C++ names a "scope". For example Scope::Method is similar to a local variable's scope from a C++ perspective: it is visible only in method it is declared. 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 the following language related scopes:

  • 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 classes' method (or of a function) that a Log Statement is located in.

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 5 Scopes in ALox. The corresponding enum type in the reference documentation is Scope .

3.1.7. Scope Domains

Attention: now it becomes tricky! Each Log Statement belongs to a Scope, precisely, even to a set of nested Scopes. 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 that contains code that stores and retrieves data from a database management system. Now, all Log Statements within this source file 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 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.

3.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: 4 Log Domains.

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

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 Lox::Once, Prefix Logables or associated Log Data.
  • 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.

3.2.1 Prefix Logables

While those interface methods in class Lox 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 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 7 Prefix Logables.

2.2 Log Once

Another feature of ALox which leverages the concept of Scopes, is found with overloaded methods Lox::Once .

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 6 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 and Lox::Retrieve quite powerful.

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

3.3. Using Multiple Lox Instances

While ALox claims to be lean and easy, 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 Loxes.

3.3.1 A Dedicated Lox Singleton for Debug Logging

There are two fundamental logging scenarios that we call debug logging and release logging.

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 a specific purely static class Log which provides access to one instance of class Lox and to a few other static objects.

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.

To achieve efficient and complete pruning, ALox relies on a set of preprocessor macros. These macros are doubly defined:

  • One set starting with prefix Log_, and
  • one set starting with prefix Lox_.

The first set performs debug logging, hence addresses the objects of static class Log (through preprocessor symbol LOG_LOX), while the second set performs release logging, operating on the Lox (through preprocessor symbol LOX_LOX).

3.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:

  • ALox 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 software wants 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.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.

3.4. Class ALox and Registration of Lox Instances

The ALib Camp singleton class ALox , instantiated in global variable alib::ALOX, 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 by its name using ALox::Get . This is convenient because this way, references of a Lox do not have to be passed around or otherwise made public, which would clutter your own software's codebase, especially your header files.

But there might also 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 .

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

4 Log Domains

This chapter provides all details on Log Domains. Before reading it, be sure that you have read and understood the chapter 3 Terminology and Key Concepts.

4.1 Why Log Domains?

Let us quickly recapture what was explained already on Log Domains in the previous chapter and the tutorials. It is very common to logging libraries to provide a system that allows to control the "verboseness" of the log output, by matching what ALox calls a Verbosity associated to Log Statements. ALox allows to divide the overall set of Log Statements into sub-sets that belong to different topics or 'areas of interest'. These topics are called Log Domains and each Log Statement has one associated. Furthermore, ALox allows to have multiple different Loggers performing each Log Statement in parallel. Now, ALox stores the Verbosity per combination of Log Domain and Logger.

The result is, that the Verbosity can be different not only in respect to each Log Domain but also, the same Log Domain may have a different Verbosity set for different Loggers.

The method to set such Verbosity which is found (in different, overloaded versions) in class Lox is simply called SetVerbosity(). As you see in the reference documentation of Lox::SetVerbosity , this method as a minimum requires a value for the Logger that is affected and the Verbosity that is to be set. The third parameter, the Log Domain defaults to "/" if not given. Omitting parameter domain with this method tells ALox to set all Log Domains to the same Verbosity.

Note
The fourth parameter of method SetVerbosity found in the reference documentation is not covered in this chapter. The impatient reader might refer to 13 External Verbosity Configuration.

This probably sounds more complex than it is. If you think about it for a second, it should become obvious. As a sample, this mechanism allows to log warning and error messages of an application of just any Log Domain to a file to have that log available across different debug-sessions, while the IDEs' console output is much more verbose in respect to those Log Domains a programmer currently is interested in.
Or, as another example, we could consider a Logger that sends alert Emails to system administrators. Of-course, Emails should be sent only for very special events, e.g. when something really severe happens. You may collect these severe errors in a special domain "/ADMIN" (real-time administration errors) and exclusively activate this domain for this Email-sending Logger.

4.1.1 Allowed Characters

Domain names may only consist of the following characters:

  • Upper case letters 'A' to 'Z',
  • Numbers '0' to '9' and
  • Hyphen ('-') and underscore ('_').

If one of this characters is used, ALox replaces this character with # (which, as stated above is otherwise equally not allowed) without further notice. (Also, no internal log statement is given in this case). As a result, the explicit provision of illegal domain names "ABCa$)" or "ABC<=>" will each result in the same domain name "ABC###".

4.2 Hierarchical Log Domains

One of the major design goals of ALox is simplicity. By nature simplicity is sometimes in in competition with flexibility or feature richness of an API. One way to resolve such conflicting goals is to provide features that - if not wanted or needed or not even known - are invisible to the user. The concept of hierarchical Log Domains is a good sample of how this was achieved in the ALox API design.

Consider an application with a user interface. Within the code that implements the UI, a lot of Log Statements using different Verbosities may be embedded using Log Domain 'UI'. Depending on the complexity of an application, switching domain 'UI' to a more verbose level (by setting the Verbosity of Log Domain 'UI'), might already lead to a huge amount of Log Statements. Of-course, the solution is to split that domain into several ones, e.g. one for UI dialogs, one for manipulations of the main window, one for menu events and one for mouse interactions, etc. Therefore, splitting domains in sub-topics is a very common use case as soon as code becomes more complex. To support this use case, ALox organizes Log Domains hierarchically. The sample would lead to:

With every method in ALox that accepts a Log Domain as a parameter, instead of just one single domain name, a domain path can be used. Domains in a path are separated by a slash '/'. As soon as domain parameters contain path separators, the hierarchical domain system of ALox takes action.
The Log Domains' paths in our sample would be named:

  • '/UI'
  • '/UI/DIALOGS'
  • '/UI/WINDOW'
  • '/UI/MENU'
  • '/UI/MOUSE'

Setting the Verbosity of a Log Domain is always done recursively for all its sub-domains (and sub-sub-domains). Let us look at a sample, how an ALox user would setup the domain tree, when debugging.

First, the setting should allow only error messages "in general", which means over all domains. Hence we provide the root domain '/', which is anyhow the default value for parameter domain :

Log_SetVerbosity( Log::DebugLogger, Verbosity::Error, "/" ) // could also just omit parameter "/"

As the interest is currently in domain '/UI/DIALOGS', we first switch '/UI' to a Verbosity that logs error, warning and info statements and then switch sub-domain '/UI/DIALOGS/' to the most verbose level:

Log_SetVerbosity( Log::DebugLogger, Verbosity::Info, "/UI" )
Log_SetVerbosity( Log::DebugLogger, Verbosity::Verbose, "/UI/DIALOGS" )

As such setting is always recursive, the order of setting domains is important. If the statements above were performed in reverse order:

Log_SetVerbosity( Log::DebugLogger, Verbosity::Verbose, "/UI/DIALOGS" )
Log_SetVerbosity( Log::DebugLogger, Verbosity::Info, "/UI" )

the setting for domain '/UI/DIALOGS' to All would be overwritten by the setting of parent domain '/UI'.

Note
In some situations, it might be wanted to be able to protect a domain setting and make it 'immune' against subsequent settings of parent domains. This is especially important when configuration is done 'outside' the source code, hence using configuration files, command line parameters and such, but could be also helpful otherwise. This protection mechanism is explained in chapter 13 External Verbosity Configuration.

The advantages of hierarchical domains so far are:

  • You can control a whole set of domains with one statement
  • If parts of a software that is out of your control (other team, code in a library, etc.) introduces new Sub-Log Domains, no changes of Verbosity settings in your code parts need to be done (as long as you are not interested in directly manipulating this new sub-domain).

But the real reason, why hierarchical domains drive the flexibility of ALox really to a next level, becomes only obvious in the next chapter!

4.3 Scope Domains

People might argue that providing a Log Domain to each Log Statement is an overhead of typing. That is true. For this reason - and many more - ALox provides the concept of Scope Domains.

Scope Domains are set using method Lox::SetDomain(const NString&, Scope) . This method, along with parameter scopeDomain , requires a parameter of enumeration type Scope that determines the 'Scope' that the given domain path should default to.

Such Scope is assembled of two parts: one part that is taken from the source code information that ALox collects at compile-time with each Log Statement. The other part is related to the thread that is executing the Log Statement, hence is evaluated at runtime.

Attention
For this chapter, explaining the benefit of being able to assign Scope Domains to Scopes, we are simplifying ALox Scope features to a bare minimum. We are deferring the full explanation of possible Scope settings to chapter 5 Scopes in ALox.

We start really simple and look at Scope::Method which denotes that the Scope Domain given with method Lox::SetDomain(const NString&, Scope) should apply to all Log Statements of the current method. Here is a sample:

void MyMethod()
{
Log_SetDomain( "/MYDOM", Scope::Method )
Log_Info( "This log statment uses domain /MYDOM" )
}

This sets the domain "/MYDOM" as the Scope Domain for this method. As a result, a Log Statement that does not provide a domain uses the Scope Domain. The output could be:

ut_dox_tutorial.cpp:106:MyMethod [0.000 +224 µs][PROCESS]     [/MYDOM]#001: This log statment uses domain /MYDOM

We have already gained some advantages from this most simple use of a Scope Domain

  1. Less typing (we can omit the Log Domain)
  2. Less 'clutter' in the code, so better readability (Log Statements serve as comment lines)
  3. When the Log Domain should be changed, this has to be done only at one point in the code
  4. When code including Log Statements is copied and pasted into a different Scope, the Log Domain adjusts to the destination Scopes' Scope Domain.

4.4 Absolute and Relative Domain Paths

As we have learned above, the following two Log Statements are the same when a Scope Domain was set for Scope::Method:

void MyMethod()
{
Log_SetDomain( "/MYDOM", Scope::Method )
Log_Info( "This log statment uses domain '/MYDOM'" )
Log_Info( "/MYDOM", "Of-course we can still use domains explicitly" )
}

But we have to be careful! The following two Log Statements are not using the same Log Domain:

void MyMethod()
{
Log_SetDomain( "/MYDOM", Scope::Method )
Log_Info( "This log statment uses domain 'MYDOM'" )
Log_Info( "MYDOM", "Oooops, this goes to '/MYDOM/MYDOM'!" )
}

As can bee seen in the output:

ut_dox_tutorial.cpp:134:    MyMethod    [0.000 +019 µs][PROCESS]     [/MYDOM      ] #003: This log statment uses domain 'MYDOM'
ut_dox_tutorial.cpp:135:    MyMethod    [0.000 +001 µs][PROCESS]     [/MYDOM/MYDOM] #004: Oooops, this goes to '/MYDOM/MYDOM'!

The difference of the sample is that while we previously always used a '/' at the beginning of domains, now for parameter domain of the last Log Statement this was omitted. This caused ALox to concatenate the domain provided in the Log Statement with the Scope Domain set, to a new domain path.

This mechanism does not need too much further explanation, as this is similar to file system path usage: A Log Domain path starting with '/' is called an absolute path and one that omits the '/' character is called a relative path.

Same as with file system paths, a relative path can also be expressed with a single dot. As a Sample, domain paths "DOM" and "./DOM" are the same. The use of a leading extra 'dot'-domain in the path to signal relative domains is optional and probably a matter of taste.
More important is that ALox Log Domains also support two dots '..' in relative path names for addressing a parent domain. Later in this chapter, a sample for addressing a parent domain is shown.

While the previous sample code looked more like an erroneous use of a domain (resulting in domain '/MYDOM/MYDOM'), the following sample shows a use case of a relative domain path which absolutely makes sense:

void ReadChangeAndWriteBack()
{
Log_SetDomain( "/IO", Scope::Method )
// Reading file
Log_Info( "READ", "Reading file" )
// ...
// ...
// ...
// Process file
Log_Info( "PROCESS", "Processing data" )
// ...
// ...
// ...
// Writing file
Log_Info( "./WRITE", "Writing file" ) // note relative-path-prefix "./", same as if omitted (!)
// ...
// ...
// ...
Log_Info( "Success!" )
}

The output would be:

ut_dox_tutorial.cpp:223:    ReadChangeAndWriteBack    [0.000 +012 µs][PROCESS]     [/IO/READ    ] #006: Reading file
ut_dox_tutorial.cpp:229:    ReadChangeAndWriteBack    [0.000 +001 µs][PROCESS]     [/IO/PROCESS ] #007: Processing data
ut_dox_tutorial.cpp:235:    ReadChangeAndWriteBack    [0.000 +001 µs][PROCESS]     [/IO/WRITE   ] #008: Writing file
ut_dox_tutorial.cpp:240:    ReadChangeAndWriteBack    [0.000 +001 µs][PROCESS]     [/IO         ] #009: Success!

4.5 Inner and Outer Scopes

The previous samples used Scope::Method. Another important Scope 'level' is Scope::Filename .

As mentioned before, we are not going into the details of the extended Scope definition of ALox (those are given in 5 Scopes in ALox), but what is very important to understand is that scopes are nested into each other. We talk about outer scopes and inner scopes. As you might guess, Scope::Filename is an outer Scope of Scope::Method.

Now, Scope Domains which can be set per Scope level, are concatenated to a complete domain path. ALox starts from the 'most inner' Scope and then prepends (!) the paths of outer Scopes as long as relative domain paths are given. In other words, as soon as an absolute path is found as a Scope Domain for a scope, the further concatenation of outer Scopes' Scope Domains is stopped.

We are using the same sample as above, but this time, the public interface method internally is split into different parts:

struct IO
{
// constructor setting the Scope Domain for this file (class) once
IO()
{
Log_SetDomain( "IO", Scope::Filename )
}
// interface
void ReadChangeAndWriteBack()
{
checkSetup();
read();
process();
write();
writeStats();
}
// private methods
private:
void checkSetup()
{
Log_SetDomain( "/CHECKS", Scope::Method )
Log_Info( "Setup OK!" )
}
void read()
{
Log_SetDomain( "READ", Scope::Method )
Log_Info("Reading file" )
}
void process()
{
Log_SetDomain( "PROCESS", Scope::Method )
Log_Info( "Processing data" )
}
void write()
{
Log_SetDomain( "./WRITE", Scope::Method )
Log_Info( "Writing file" )
}
void writeStats()
{
Log_SetDomain( "../STATS", Scope::Method )
Log_Info( "Statistics" )
}
};

In the constructor fo the class, Scope Domain "IO" is set for the entire class by using Scope::Filename.

Note
If more than one objects of type IO is created, the statement is repeatedly executed. This is no problem. However, it is always good to find a place for setting Scope::Filename that is executed a) early and b) seldom. The reasons should be obvious.

When sample method ReadChangeAndWriteBack() is executed, this output is generated:

ut_dox_tutorial.cpp:279: checkSetup    [0.000 +015 µs][PROCESS]     [/CHECKS    ] #011: Setup OK!
ut_dox_tutorial.cpp:287: read          [0.000 +007 µs][PROCESS]     [/IO/READ   ] #012: Reading file
ut_dox_tutorial.cpp:294: process       [0.000 +007 µs][PROCESS]     [/IO/PROCESS] #013: Processing data
ut_dox_tutorial.cpp:301: write         [0.000 +007 µs][PROCESS]     [/IO/WRITE  ] #014: Writing file
ut_dox_tutorial.cpp:308: writeStats    [0.000 +007 µs][PROCESS]     [/STATS     ] #015: Statistics

With the takeaways from the features explained already in this chapter, the code is quite self-explanatory. Some remarks:

  • As explained and observable in the output, relative domains are concatenated from inner scopes to outer scopes. Samples are the domains '/IO/READ', '/IO/PROCESS' and '/IO/WRITE'
  • Like in the previous sample, method write() volunteers to use prefix "./" to signal that the domain path is relative. This is a matter of taste and the result is the same as if this prefix was omitted like in methods read() and process().
  • Method checkSetup() uses an absolute domain path for its Scope. This tells ALox to stop prepending any Scope Domains of outer Scopes.
  • Method writeStats() uses a relative domain pathstarting addressing the "../". parent domain. The effect is that the outer Scope domain "IO" is removed. The effective path is: '/IO/../STATS' which ALox of-course shortens to just '/STATS'.
    Hence 'STATS' is a sub-domain of the root-domain, the same as domain 'CHECKS' is. Note that, if there were other Scope Domains in Scopes that are 'more outer' than Scope::Filename, the 'depth' of both domains would be different.

4.6 Further Information and Wrap-Up

4.6.1 Where to set the Verbosity

The usual way to use ALox is to separate the code that sets the Verbosity of Log Domains into a single location, normally somewhere in the 'boot-strapping part of a process. This is probably the same location where the Loggers used and also Lox objects are created and configured.

It is of-course tempting to quickly (temporily) increase the Verbosity of the Log Domain(s) that a code unit uses while working on that code unit. Method Lox::SetVerbosity properly uses the current Scope, so that a statement like this:

Log_SetVerbosity( Log::DebugLogger, Verbosity::Verbose, "./" )

properly sets the evaluated Scope Domain. However, if such code lines are inserted, they should be duly marked as 'temporary' and 'to-be-removed'.

Especially when working in a team, it is even more convenient to not even set the Verbosity of any domain from within the code, but by using external configuration data.

Note
To keep this chapter as simple as possible, all samples herein exclusively used the method Lox::SetVerbosity and you may not even have heard yet about other possibilities. Information about how ALox is configured externally, is completely separated into an own chapter of this manual: 13 External Verbosity Configuration.

The separation has good reasons: A portion of the code that logs using a certain set of domains should not modify these Log Domain's Verbosity. One day such code could be moved into a library and then such changes are merely irreversible (well, in-fact this misbehavior can be 'healed', but this is also only explained in chapter 13 External Verbosity Configuration).

4.6.2 Why Does Verbosity Setting Always Work Recursively?

It was explained in section Hierarchical Log Domains, that setting the Verbosity is always done recursively for all sub-domains of the given Log Domain. The reason for this is to avoid 'random' settings as much as possible.

For convenience, Log Domains do not need to be 'registered' with ALox . As soon as an unknown Log Domain is used in a Log Statement, ALox creates a reference to this Log Domain and for each Logger in the corresponding Lox the Verbosity of the parent Log Domain is inherited.

Now, if a Log Domain setting was allowed to be non-recursive, the setting of a sub-domain could have different values:

  • If it was known to ALox already (because it was used in a Log Statement), it would keep the Verbosity that it previously received by implicitly inheriting from its parent.
  • If it was implicitly registered with ALox after such non-recursive setting, it would still inherit its parents setting.

Therefore, the order of the use of a Log Domain would decide about its Verbosity.

One of ALoxs' design goals is to hide features that are not used and which are unnecessarily increasing complexity of using ALox if they are not needed (Further explained in Appendix B: Auto-Configuration and Orthogonality). For this reason, within this chapter of the manual (which is all about understand what domains Log Domain just are), a parameter of method Lox::SetVerbosity that can be omitted because it has a default value, was not even named. Using this parameter allows to restrict the setting of the Verbosity of to a domain and and allows to include arbitrary sets of sub-domains or not, by assigning a priority to each Verbosity setting.

Apart from lifting the always recursive Verbosity setting, the main motivation for introducing this concept resulted from the general need to prioritize conflicting settings which may come from different sources of configuration data. Therefore, if non-recursive settings are needed for any reason, refer to chapter 13 External Verbosity Configuration for explanations about how to achieve this.

4.6.3 Absolute or Relative Domain Paths?

As we learned, ALox allows to use absolute or relative Log Domain paths. This is true for Scope Domains as well as for parameter domain in a Log Statement.

The general recommendation is to use relative paths. There are several advantages which all arise from the fact that ALox assembles the Scope Domain path by looping through all Scopes from the most inner to the most outer. As soon as an absolute path is found, the loop is aborted and concatenation stops. The benefits of keeping all Log Domain paths relative are highlighted in the next chapter (5 Scopes in ALox) when all about Scopes is explained - including an important use case that justifies absolute Log Domain paths!

4.6.4 ALox Internal Log Domains

ALox uses 'itself' to log information about its use. This is helpful e.g. to observe where and when a Log Domain is used for the first time or where and when a Verbosity setting is performed.

To keep the 'tree of Log Domains' clean and reserved exclusively for application specific Log Domains and to avoid accidental activation of internal log messages, ALox manages the internal Log Domains in a separated tree.

Information about how to 'address' this tree and when to use it, is explained in chapter 11 Internal Logging.

5 Scopes in ALox

5.1 Introduction

ALox uses the concept of Scopes in different ways. The most important and prominent use is to set so called Scope Domains. Those can be registered and then are used as a 'default' domain path with each Log Statement placed within the according Scope. The concept of Scope Domains has been explained already in the previous chapter (see 4 Log Domains). But to keep it simple, the full variety of ALox Scopes was not yet, explained there. This is what this chapter aims to do.

So, be sure, that you have read and understood chapter 4 Log Domains, before working yourself through this chapter. Using the more complex possibilities of scopes is optional when using ALox , so you can consider this chapter as an advanced topic.

Furthermore, there are other features in ALox that use Scopes. Those are elaborated in chapters

ALox Scopes are enumerated in enum-class Scope . The Scopes found here are separated in two sets:

  • Scopes related to the programming language:
    These scopes are identified 'automatically' at compile time with each invocation to a method of class Lox using the preprocessor macros.
  • Scopes related to the execution thread:
    These scopes are identified at runtime, by examining the thread that is executing a method of class Lox.

5.2 Language-Related Scopes

Scopes are nested into each other. We talk about 'outer' and 'inner' scopes. For example the Scope of a method is nested into the Scope of the file name that the method is implemented in.

The language-related Scopes that ALox supports are (from outer to inner):

  • Scope::Global,
  • Scope::Path,
  • Scope::Filename, and
  • Scope::Method.

Apart from Scope::Global, to evaluate the actual Scope of an invocation of a member of class Lox, ALox needs to 'automatically' collect information of the calling entity. In ALox for C++ is this is achieved using preprocessor macros for all Log Statements.

As explained in detail in chapter 10 Differences of Debug- and Release-Logging, for release-logging, such automatic collection is not wanted. Without repeating the reasons for this here, let us emphasize the consequence:

Attention
The Scopes.Path, Scope::Filename (in Java Scope.CLASS) and Scope::Method can be used for debug-logging only! While it is possible to set Scope Domains (and use other features of ALox which rely on Scopes) in release-logging, scope information values will be empty and hence not distinguishable from each other.

The good news is: This is absolutely OK! The rational for claiming this is:

  • In respect to Scope Domains:
    Release-logging statements are quite rare compared to those of debug logging. Furthermore, they should be very well thought, clear and meaningful. It is advisable to not use Scope Domains in release logging anyhow (apart from thread-related and Scope::Global as explained below). Instead, Log Statements that comprise release-logging should specify the Log Domains they refer to explicitly.
  • In respect to Log Data:
    Well, the whole concept of Log Data provided by ALox is merely a tool to support the process of debugging, e.g. to explore the location of an exception in the log output. It is not deemed to be used to implement any functionality of an application, for example to store thread-local information.
  • In respect to method Lox::Once :
    For release logging, the optional parameter group certainly does the job. It should be used because, this makes release logging statements to be more explicit and readable.
  • In respect to Prefix Logables:
    The most important use case for them are to make the output better readable, e.g. by adding recursive indentation. Again, something that should not be too important for release logging. If it still is, a thread-related Scope can be used.

The following sections describe each of the language-related Scopes in detail.

5.2.1 Scope::Global

As the name of this Scope indicates, this is the 'most outer' Scope of ALox . It is always there and only one single 'instance' of it exists per Lox. In other words, all Log Statements or other invocations of ALox are executed 'within' this singleton scope.

Note
Because scopes are managed by class Lox, each instance of this class provides its own Global scope, the same as a Lox has different Loggers, Log Domains, Scope Domains, Log Data, etc. Well, and this makes perfect sense!

When setting Scope Domains for the Global Scope, all Log Statements have such domain as their root-domain. (Of-course, unless one inner Scope Domain or the statement itself is using an absolute domain path!).

One use case of setting a Scope Domain for Scope::Global could be described as follows: An instance of class Lox is used for release-logging of special and sparse Log Statements (e.g. logging into the operating systems' journal, aka event log). Now, in debug versions of the executable a debug-Logger is attached to the release lox (in addition to attaching it to the debug-lox), within this debug log output, all of these special Log Statements would be nicely sorted into the Scope Domain of Scope::Global, while non of the Log Statements to this release lox need to specify that domain path.

Another use case are special debug situations. Imagine a dubious behavior of a software is observed rather seldom. A programmer could register a debug Logger with Verbosity All for domain '/DEBUG'. Now, when a certain first indication for the start of the dubious behavior occurs, a setting of the Scope Domain '/DEBUG' for Scope::Global can be activated. From this point in time, all Log Statements would be activated, because all Log Statements of all code would be gathered beyond that temporary Log Domains '/DEBUG' and all sub-domains inherit its Verbosity.

5.2.2 Scope::Path

This scope adresses the directory structure that a C++ source code file is located in. Unfortunately, ALox is not able to automatically gather information about the namespace that a Log Statement resides in, but if the source codes are stored in accordance to the namespaces they implement, a quite congruent behaviour to the namespace scopes of C++ can be achieved with ALox Scopes.

For example, a directory tree could look like this:

    /home/user/myproject/src/main.cpp
    /home/user/myproject/src/ui/menu/menu.hpp
    /home/user/myproject/src/ui/menu/menu.cpp
    /home/user/myproject/src/ui/dialogs/about.hpp
    /home/user/myproject/src/ui/dialogs/about.cpp
    /home/user/myproject/src/io/sockets/http.hpp
    /home/user/myproject/src/io/sockets/http.cpp
    /home/user/myproject/src/io/database/mysql.hpp
    /home/user/myproject/src/io/database/mysql.cpp

Using method Lox::SetDomain(const NString&, Scope) with Scope::Path instructs ALox to use the direct parent directory of a source file as the scope for the given Scope Domain. Thus, this domain is activated for all Log Statements residing in any source file of of this directory.
Now, method Lox::SetDomain allows to add an integral value to the enumeration value Scope::Path and call the method with argument Scope::Path + N. This tells ALox to cut N directories from the path and hence to refer to an 'outer directory'. This way, Scope::Path can be used to denote a whole set of nested scopes! When executing a Log Statement within a source file, all Scope Domains which are set to any parent directory of the source file are applied!

Note
In general, the organization of the source code in a well structured tree of directories corresponding to the tree of nested namespaces, is a good idea.
If a project using ALox is not organized this way, and there is no possibility to restructure the source tree for this purpose, then the Scope::Path still can be used. It just will not match and reflect the namespace but (quite as its name indicates) the structure of the source tree.
However, if all sources of a project simply reside just in one single directory, then the use of Scope::Path is not advisable. Its effect would be similar to Scope::Global, with the only difference that a thread-related Scope is applied after Scope::Path but before Scope::Global. (Thread-related Scopes are discussed later in this chapter).
Note
Because ALox is designed to tolerate errors as much as possible, adding values to enum value Scope::Path that are higher than the number of parent-directories available to be cut, does not result in an error. Instead, an empty path name is registered and consequently, a registration with, lets say Scope::Path + 42 would simply be overwritten by a subsequent registration with level Scope::Path + 43 and the effect would be nearly similar to using Scope::Global.
On Windows OS, still the drive letter would remain and such setting would apply to all source files residing on the corresponding drive.

5.2.3 Scope::Filename

Scope::Filename does not really need to be explained. All Log Statement within the same source file share the same Scope::File. It is an inner scope of Scope::Path. Files with the same name, but residing in different directories have different scopes.

C++ source file names typically have an extension like .cpp, .hpp, .h, .hxx, etc. Such extension is ignored by design. This way, by setting a Scope Domain of Scope::Filename e.g. from within file 'mydialog.cpp', such setting also applies to Log Statements occurring in the corresponding header 'mydialog.hpp'. Of-course, this is only true if both files reside in the same directory.

5.2.4 Scope::Method

Scope::Method comprises all Log Statements which are residing in a certain method or function. This is the 'most inner' of the language-related set of Scopes supported by ALox . But it is not the most inner of all ALox Scopes, as we will learn later in this chapter.

As Scope::Method is 'language-related', its behavior is like in the programming language in respect to nested method calls: Same as a method variable is not 'visible' within other methods that are invoked from this method, a Scope Domain set in a method is not 'active' within nested, invoked methods. This of-course makes a lot of sense, otherwise Scope Domains of methods would be overwritten or at least redirected by Scope Domains of a calling method.

Note
ALox also provides a feature of adding a Scope Domain for a method and all methods it invokes! The way how to achieve this is described later in this chapter.
Attention
Unfortunately, ALox receives just the name of a class method or a function. While this name is associated with the file name, and thus two or functions or methods with the same name that reside in different files, do not share the scope (as expected), those that are implemented in the same header file or compilation unit, do share the same scope. This is not what a user would expect but there is no technical way to avoid that.
Therefore, if two or more functions (in different namespaces) or class methods share the same name and reside in the same file, the behaviour of ALox might be different to what a user expects!

5.2.5 Anonymous Scopes

C++ allows to open and close 'anonymous scopes' using curly braces '{' and '}'. For example, a variable declared in such anonymous scope is not visible to the rest of the method after the anonymous scope is closed. Unfortunately, these anonymous scopes can not be 'detected' by ALox automatically. In C++, with the help of its concept of strict 'stack-unwinding', it would be possible to extend ALox to support inner Scopes for nested blocks that automatically get unset as soon as program execution leaves an anonymous scope. In favor of keeping the different language versions compatible (and also in favor to not overcomplicate things!), this is not offered by ALox .

But there is an obvious way to reach the goal of setting sub-domains for Log Statements within a block of code: Simply equip each Log Statement of an anonymous source scope with a relative path using parameter domain . Such relative domain paths provided with a Log Statement are placed by ALox within the evaluated, resulting domain path, as if they resulted from a Scope Domain setting of an inner Scope of Scope::Method.

5.2.6 How To Set Scope Domains for Language-Related Scopes

When reading earlier chapter 4 Log Domains and the later chapters (6 Lox::Once(), 7 Prefix Logables and 8 Log Data (Debug Variables)), you might be surprised, that the only way to manipulate a specific Scope is to do this with an invocation from within that Scope itself.

Why does ALox not provide alternative interfaces that makes it possible to explicitly address a Scope with an invocation from 'outside' of this scope? E.g. why is it not possible to set the scope for a method by naming the path, file and method?

The reason is to avoid ambiguities and misconfigurations. The source files' paths may be quite volatile things. When working in a team, or already when one person is working in parallel on two different machines (at work and at home) the paths may vary. Furthermore any sort of code refactoring in any respect would enforce a 'manual' change of scope specifications.
The errors and hassle that would quickly occur when the explicit naming of Scopes was supported by ALox would not justify the benefits.

But we do not consider this as a huge restriction. The responsibility for Log Domains names is deemed to rely in 'the hands' of the code that is defining the Log Statements using these Log Domains. The maintainer of a certain subset of a code within a project should know best which domains and sub-domains are to be used. As an exclamation, the use of rather 'global' domains that collect certain information, e.g. "/CONFIG_ERRORS", should be aligned across the team. Usually those domains are addressed using an absolute path within a Log Statement - and hence are not impacted by the Scope and potentially associated Scope Domains anyhow.

Note
There is one obvious use case, that might let you think about changing Scope Domains set in scopes not reachable by your code: This is when for some reason you want to change the domains that a library (or part of the project you do not have access to) uses. To do this, ALox provides a concept called Domain Substitution, described in 15 Log Domain Substitution. The huge advantage of this approach is, that this feature substitutes all Log Domains, regardless whether they are evaluated by ALox from Scope Domains or if they are given as parameters of the Log Statement.

Having said this, it is agreed and and understood that the definition of Scope Domains of language-related Scopes has to appear in source code within the Scope itself - optionally within an 'inner' Scope of the Scope. For example, within a method of a class, both Scope::Method and Scope::Filename can be set.

What should be avoided are Contrary settings. If the same Scope is set with different values, the second invocation just replaces the first one. Therefore, some random behavior might appear when the settings of Scope Domains are contrary. For example, a Scope Domain for a source folder could be set from within different classes residing in or below this source folder. As a rule of thumb (to avoid double definitions), it is advised to put the registration code to the most central (important) class of such directory.

A snapshot of all current settings can be logged using Lox::State to investigate which settings have been performed. Alternatively, if the snapshot is not enough to understand what is set, overwritten and used where, a live log of ALox ' internal messages can be activated to identify exactly which code lines are doing what in respect to Scope Domains. See 11 Internal Logging for more information about how to enable internal log messages.

5.3 Thread-Related Scopes

This section adds two new Scope 'levels', named:

to the ALox Scope feature. As the name indicates, these Scopes create a reference to the thread that is executing a statement that is using values associated with such Scope.

Note
Even if your application is single-threaded, you should continue reading!

Looking at Scope Domains, of-course, they are used to add an additional component to the overall evaluated Log Domains path of a Log Statement. For Scope::ThreadOuter, such addition is performed at the beginning of the evaluated Log Domains path, directly after Scope::Global. For Scope::ThreadInner, the Scope Domain set is appended at the very end of the evaluated Log Domains path. The term 'very end' is important: This is not only the most 'inner' of all Scopes, it is appended to the assembled Log Domains path even after the optional parameter domain of a Log Statement. In other words, it could be said, that Scope::ThreadInner is even more 'inner' than the local, optional parameter domain of a Log Statement.

The whole list of Scope Domains, together with the parameter domain , which are all concatenated (as long as none of them is an absolute path) results to:

  1. [L] Scope::Global
  2. [T] Scope::ThreadOuter
  3. [L] Scope::Path
  4. [L] Scope::Filename
  5. [L] Scope::Method
  6. Parameter domain of a Log Statement
  7. [T] Scope::ThreadInner

Remark: [L] and [T] here indicate language-related and thread-related Scopes.

5.3.1 Use Cases for Scope::ThreadOuter

An important use case for Scope Domains of Scope::ThreadOuter is useful in single-threaded applications, the same as in multi-threaded. If a Scope Domain is set for Scope::ThreadOuter prior to invoking a method (and removed right after the invocation), all subsequent Log Statements are 'redirected' to the domain path specified, covering the whole call stack of nested method calls. This way, a portion of the program execution can be controlled in respect to the Verbosity of Log Statements easily. You can consider this use as being similar to Scope::Method but lasting not only for the method itself but for all statements of recursively invoked methods as well.

In multi-threaded applications, Scope::ThreadOuter is typically used in situations where the log output of different threads should be separately controlled in respect to the Verbosity of their log output. Imagine a background thread that causes trouble but uses the same code and libraries that the rest of the application does. If you now would increase the Verbosity of such Log Domains where the problems occurred, the log output would be 'cluttered' with a lot of Log Statements caused by any thread of the process. Setting Scope::ThreadOuter allows to 'redirect' all such log-output of the thread in question to a dedicated root domain. Now, controlling the Verbosity of the sub-domains of this thread-specific root domain allows to investigate directly what is happening there. This sample addresses debugging and probably a temporary 'redirect' of domains that is removed when a problem is fixed.

But there are also samples where a permanent setting of a Scope::ThreadOuter makes sense. Most operating systems/programming environments are using a dedicated thread implementing the user interface. Handlers of UI-events like mouse clicks are installed and executed on a per event basis. If now, with the very first UI event firing into the user code, (e.g. signaling that the application is now running, or the main window was created), a Scope Domain like 'UI' is registered with Scope::ThreadOuter, all UI related code magically logs into this domain path. As a consequence, no UI-related code like classes for dialog boxes, menu handlers, etc, need to set such domain themselves (e.g. using Scope::Path in a constructor).
Furthermore, it becomes very obvious from just looking at the sub-domains that get created, when the UI thread is tasked with things that rather should be moved to a different thread to avoid blocking the application for too long.

Note
This last sample nicely shows, how the use of ALox for all debug-logging tasks, leads to new insights of a software, that simple "debug log statements" do not provide!

5.3.2 Use Cases for Scope::ThreadInner

While technically Scope::ThreadInner is very similar to Scope::ThreadOuter, the effect and use cases still differs slightly. Instead of 'redirecting' just all log output of a thread into a new sub-tree of Log Domains, Scope::ThreadInner splits all 'leafs' of the overall Log Domain tree by adding a thread-dependent Log Domain to those leafs.

When we think about this for a minute, the obvious use case is to filter the log output of specific Sub-Log Domains by thread. First, when a Scope Domain of Scope::ThreadInner is set, the Verbosity of the new sub-domains will not change. This is true, because all new domains that are created by this thread are sub-domains of those Log Domains used before. And such sub-domains just inherit the setting as long as they are not controlled explicitly (as explained in 4.6.2 Why Does Verbosity Setting Always Work Recursively?). From here, specifically for this thread, the Verbosity of certain domains can now be tweaked until the right set of Log Statements appear.

Imagine a very general class providing a very general feature, hence frequently used by different parts of a software. Increasing the Verbosity of a Log Domains of such class might increase the overall log output too much. Now, by splitting such Log Domains using a Scope Domain for Scope::ThreadInner it becomes possible to either decrease the Verbosity for threads that are not of current interest or by only increasing the Verbosity of the thread of interest.

Finally it is noteworthy to mention the impact of Scope::ThreadInner being the most inner Scope Domain that is evaluated:

  1. A Log Statement that provides an absolute domain path directly in its statement using optional parameter domain , can still be split by Scope Domains of Scope::ThreadInner.
  2. If an absolute domain path is provided for a Scope Domain of Scope::ThreadInner, then this 'flattens' all log output into exactly this single domain. (Even parameter domain of a Log Statement will not be used!)

5.3.3 Multiple Use of Thread-Related Scopes

We learned in section 5.2.2 Scope::Path, that this Scope through the use of different parent directories may be seen as whole set of nested Scopes itself.

The same is true for Scope::ThreadOuter and Scope::ThreadInner! If multiple Scope Domains are set for one of both Scopes, instead of overwriting the previous setting (as done with language-related scopes), such Scope Domains are added to the ones that were previously set.
This is important for almost all use cases described in the previous sections.

Hereby, subsequent settings are 'inner' Scopes of the previous ones. This means, that during program execution the first Scope Domain that is set, results in a higher level within the domain tree. Subsequent Scope Domains set result in direct sub-domains of the former ones.
ALox , when passing a nulled string with parameter scopeDomain of method Lox::SetDomain removes the most recently set Scope Domain first. But also an out-of-order removal of thread-related Scopes is possible. More details on setting and removing Scope Domains for thread-related Scopes is given in the next section.

5.3.4 How To Set Scope Domains for Thread-Related Scopes

The same method, Lox::SetDomain(const NString&, Scope) which is used for language-related Scopes is used to set and remove thread-related Scopes.

If a domain path is given with parameter scopeDomain and either Scope::ThreadOuter or Scope::ThreadInner for parameter scope , then this domain path is added to the list of corresponding domains set. The list reflects a set of nested Scopes for itself.

To remove the most recently added Scope Domain, it is sufficient to do the same call, with an empty or nulled parameter scopeDomain . Again, this is the same as with removing or 'un-setting' Scope Domains of other Scope types.

For the case that the reverse order of adding and removing thread-related Scope Domains can not be guaranteed, class Lox offers method Lox::RemoveThreadDomain which accepts the domain path to be removed explicitly as a parameter.

It was discussed in 5.2.6 How To Set Scope Domains for Language-Related Scopes, that those types of Scopes can only be set from 'within' the Scope to be set (the same or an inner Scope). This is different with thread-related Scopes. Method Lox::SetDomain(const NString&, Scope) as well as Lox::RemoveThreadDomain accept an optional parameter thread which allows to explicitly provide the thread object to associate a thread-related Scope Domain to. Of-course, if this parameter is omitted, the 'actual Scope', hence the current thread, is used.

Note
While ALib provides class Thread , usually, a software using would use a different library to create threads. As long as such library is creating 'native' threads of the underlying operating system, this is no issue. To refer to a thread created with a different library, the approach is as follows:
  • from within the thread that is created, static method Thread::GetCurrent has to be invoked.
  • The pointer to the Thread object received has to be passed to the scope of the method that is supposed to set a thread-related Scope value for referencing the thread in question.

When things get more complicated, same as with language related scopes, a snapshot of all current settings can be logged using Lox::State to investigate which settings have been performed.

Alternatively, if the snapshot is not enough to understand what is set, removed and used where, a live log of ALox ' internal messages can be activated to identify exactly what the code is doing in respect to Scope Domains. See 11 Internal Logging for more information about how to enable internal log messages.

5.4 Wrap up

We want to summarize the takeaways of this chapter:

  • Scopes are used for different features of ALox , as documented in 4 Log Domains, 6 Lox::Once(), 7 Prefix Logables and 8 Log Data (Debug Variables).
  • Most of the samples and explanations in this chapter are related to Scope Domains.
  • Scopes are nested, we have inner and outer Scopes.
  • Four (programming-)language-related Scopes were introduced. They work similar to scopes of the programming languages.
  • Two thread-related Scopes were introduced. They are runtime Scopes and associated with the thread executing a Log Statement.
  • The two thread-related Scopes differ only in the 'position' within the hierarchy of Scopes.
  • It was explained, that language-related Scopes are set exclusively by statements that are placed within the according Scope itself...
  • ...while thread-related Scopes can also be set from within other threads, by providing the associated object of type Thread explicitly.
  • Repetitive settings of language-related Scopes are overwriting previous settings.
  • Repetitive settings of thread-related Scopes are each acting as a nested, inner scope of the previous setting.
  • Removing Scope Domains is performed by passing a nulled or empty domain path to method Lox::SetDomain. In the case of thread-related Scopes this removes the most recently added Scope Domains. Therefore, if out-of-order removals are needed, method Lox::RemoveThreadDomain is to be used for removal.
  • To investigate into settings of a Lox, two options exist. Either by creating a snapshot of the current setting (using method Lox::State) or by activating internal log messages and observing which setting is made at which position in the source code and which point in time when running a process.

Finally we want to express an important thought: The three concepts of ALox , namely

  1. Hierarchically organized Log Domains,
  2. Nested Scopes and
  3. Scope Domains,

align very nicely. Clever use of them may lead to true "emergence": Suddenly, log output provides more information than the single Log Statements' messages itself. (Similar to water, which has different "emerged" properties than the pure sum of the properties of each of its molecules.)

But, it should not be forgotten what the main purpose of Log Domains is: It is the control of the Verbosity of subsets of Log Statements. In other words, the main purpose of Log Domains is not to understand and analyze the calling hierarchy (call stack) of a piece of code. While ALox may be used to help here quite nicely, there are other software tools and techniques available for accomplishing this.
Therefore our recommendation is: Do not overuse the concept of Scope Domains. With too many Scope Domains set, the original purpose of Log Domains may become harder to achieve. Already the maintenance of Verbosities may start causing some unwanted effort.

6 Lox::Once()

6.1 Introduction

For a better understanding of what is explained in this chapter, it might be advisable to have read chapter 5 Scopes in ALox before continuing with this one. In short, at that place it is explained how ALox defines language-related Scopes (including the 'global' Scope) and thread-related Scopes. Both types are 'interwoven' and form the complete set, as denoted in enum-class Scope .

The most important use of Scopes in ALox is for setting and using Scope Domains which helps sorting and controlling the log output tremendously. Further information on this topic is found in chapter 4 Log Domains. This chapter provides details about a feature that we simple call Lox::Once.

Using ALox , you have probably come across method Lox::Once , with its various parameters and overloaded versions which omit and default the parameters.

As the name indicates, this method is used to execute a Log Statement only once. A first use case of such a Log Statement is obvious: Whenever it is interesting to know whether a piece of code was executed at least once, this Log Statement:

Log_Once( "Kilroy was here!" )

is the one to go. No further (debug) code needs to be added and pruned using #if / #endif clutter. Its nice and readable, just as Log Statements should be.

The concept of method Lox::Once goes quite beyond this simple sample. Before we go to the real interesting things, lets first quickly introduce other parameters that are optional to all other variations of this method:

  • As with all Log Statements, a domain path can be given:

    Log_Once( "IO", Verbosity::Error, "Directory given in config.ini not found. Using default." )

    Note that there is no overload available that accepts a Domain Path and defaults the verbosity to Verbosity::Info. This has technical reasons: The overload resolution would become ambiguous.

    This sample above indicates a next use case besides the one asking for "was a piece of code reached once". This use case is: "Log problems or facts, which will not change in the future only once". Here, the configuration file "config.ini" contains wrong information. It is what it is. Do not tell me this twice and do not clutter the log output in subsequent execution of a software.

  • At this point in time, it might be worth to mention, that the counter is increased with each Lox::Once statement independently from the Verbosity setting of the effective Log Domain such statement is associated with. In other words, even if the domain was switched off (using Verbosity.Off), the counter is still increased. On the same token, if more than one Logger (or none) is attached to the Lox, still the counter is increased by exactly 1.

    Then, although the method is named 'Once', parameter quantity allows to specify a different number of occurrences:

    Log_Once( "This is logged 10 times. After that, never again.", 10 )

    Yes, yes, not really true: when the integral value of the internal counter overflows, you'll see an next 10 log statements. The counter is implemented as int.

  • Finally setting the parameter to a negative value, lets ALox perform the log the first time it is invoked and after that, every n-th time (to be precise, every (-quantity)-th time).

    Log_Once( "This is logged the first time and then every 100th invocation.", -100 )

6.2 Grouping

In the introduction we have seen use cases like:

  • Is a certain piece of code reached?
  • A log message results from a non-changeable fact, and hence it should only be logged once.

These and similar use cases are now extended from concerning one log message to a set of log messages. Let us Stick to the sample with the mis-configured config.ini:

Log_Once( "IO", Verbosity::Error, "Directory given in config.ini not found. Using default." )

This might be observed in two methods: when data is written or when it is read:

void ReadData()
{
//...
// directory not found
Log_Once( "IO", Verbosity::Warning,
"Directory given in config.ini not found. Using default." )
//...
}
void WriteData()
{
//...
// directory not found
Log_Once( "IO", Verbosity::Warning,
"Directory given in config.ini not found. Using default." )
//...
}

While this works well and we could be happy as it is, the little drawback here, is that, if both methods get executed, each Log Statement is executed once and hence, this sums up to two. For a debug-log output on a IDE console, this is not really dramatic. But there are other usage scenarios of logging (logging to a system journal, logging over the air, etc.) where ALox should be asked to avoid this.

And it can be very easily avoided:

void ReadData()
{
//...
// directory not found
Log_Once( "IO", Verbosity::Warning,
"Directory given in config.ini not found. Using default.",
A_CHAR("INI_DIR_ERROR") )
//...
}
void WriteData()
{
//...
// directory not found
Log_Once( "IO", Verbosity::Warning,
"Directory given in config.ini not found. Using default.",
A_CHAR("INI_DIR_ERROR") )
//...
}

We provide optional parameter group , both valued "INI_DIR_ERROR". Because both log statements share the same Group 'key', ALox shares its internal counter for the number of already performed logs between them.

Note
Of-course, such Group keys do not need to be registered and their use is not limited. As always, ALox just manages internally what the user feeds in. Even if parameter quantity is provided and it differs between Lox::Once statements that belong to the same group, ALox does what you might expect:
  • The counter is shared and increased with each executed (!) statement.
  • Each Lox::Once statement checks the counter against what is provided with quantity . In other words: While one statement of a group may be already disabled, others might continue logging. Please do not ask us for a use case of this!

In summary, parameter group is used to group a number of Lox::Once statements which are located in arbitrary places of a software together and count the number of overall executed logs.

For a short time, this is all to know about using Groups. But we will come back to Groups after the next section.

6.3 Lox::Once and Scopes

Instead of 'grouping' a set of Lox::Once statements by manually assigning them a group name, ALox Scopes can 'automatically' group statements which in a 'natural' way belong together. If the methods ReadData and WriteData from the previous sample reside in the same source file, the very same that was achieved using Groups, can be achieved using Scopes:

class MyIOManager
{
MyIOManager()
{
// bind all log statements of this file to domain path 'IO'
Log_SetDomain( "IO", Scope::Filename )
}
void ReadData()
{
//...
// directory not found
Log_Once( Verbosity::Warning,
"Directory given in config.ini not found. Using default.",
Scope::Filename )
//...
}
void WriteData()
{
//...
// directory not found
Log_Once( Verbosity::Warning,
"Directory given in config.ini not found. Using default.",
Scope::Filename )
//...
}
}; // class

Comparing the samples, it can be observed, that parameter group of type String was replaced by parameter scope of type Scope, with value Scope::Filename. For other possible values, see chapter 5 Scopes in ALox.

From a birds' perspective, the advantages of Scopes are:

  • No need to 'invent' a group name and no risk of accidentally using the same name twice (e.g. in a library that a user does not even have access to).
  • No need to double-check what Group key was used with other statements in the set.
  • Copy and pasting of the Log Statements (into a different Scope), 'automatically' adjust the 'grouping key'.

Their biggest disadvantage: There is only one Lox::Once counter per Scope. The sample above does not support two different sets of methods that independently from each other get counted.

On the other hand, the advantage of Groups is: Statements from completely different Scopes can be grouped and there is an unlimited number of Groups possible.

There is one thing, that can only be achieved with Scopes, namely with Scope::ThreadOuter or Scope::ThreadInner. This attaches the Lox::Once counter to a thread that is executing one of the statements. This opens the new use case for ALox :

  • Log one or one set of messages, up to n-times per execution thread.

As we see, using groups or using scopes have their proper use case and both have advantages and disadvantages, so why not combining them?

6.4 Combining Groups and Scopes

The parameter list of Lox::Once and some of its overloaded variants, allow to provide both, a Group name and a Scope. To understand the consequence, its best to explain how ALox internally handles Groups.
Groups of-course are implemented with hash-tables. Their key is a String, the Group name, and their value contains the counter. Now, ALox (to be precise, class Lox), creates a hash-table for Group keys for each 'instance of a scope' where Lox::Once is used.
When parameter scope is omitted with Lox::Once, it simply defaults to Scope::Global, which is, as explained in 5 Scopes in ALox, a singleton for each Lox. Consequently, each and every statement belongs to Scope::Global and this is why Groups of default Scope::Global seem to work independently from any scope.
The other way round, if parameter group is omitted, then there are two options for ALox : If parameter scope is given and is not equal to Scope::Global, then ALox uses a default Group key. Because this is the same for all such invocations, the statement is bound to the given Scope and to nothing else. In the second case, when Scope::Global is given (still with no Group), ALox switches to Scope::Filename and creates the Group key from the line number of the invocation. This way, the 'parameterless' invocation of LogOnce, results in binding the counter exclusively to this single Lox::Once statement.

The truth therefore is, that ALox always combines Groups and Scopes, we just have not noticed that, yet. After having understood this, the only question is: What new use cases do we get when using Groups with Scopes other than Scope::Global? So far, we had:

  • Is a certain piece of code reached?
  • Don't spoil my log-output, so stop after n-messages of the same type.
  • A log message results from a non-changeable fact, and hence it should only be logged once.
  • A set of log messages result from a non-changeable fact, and hence only one of them should be logged once (or together n-times).
  • Log one or a set of messages, up to n-times per execution thread.

With using Groups in Scopes other than Scope::Global all of the above get combined and a little more. Just for example:

  • Log only the first n statements which belong to the same group and are placed within any method of
    • a source file
    • a directory of source files
    • a parent directory of source files and all sources recursively
  • Log only the first n statements of a group of statements executed by a specific thread.
  • Have an arbitrary number of different Groups of Lox::Once statements per Thread.

It is up to the reader of this manual and user of ALox to adopt his own use cases to this list.

6.5 Passing Multiple Logables To Lox::Once

Unlike other methods of class Lox that comprise Log Statements which accept an arbitrary amount of logables, method Once and its overloaded variants accept only one logable. This restriction is caused by the otherwise complicated set of parameters and overloads of this method. There is an easy way out!

To supply more than one Logable, in C++ a container of type Boxes may be passed with parameter logables :

// passing an array
{
Log_Prune( Box logables[3]= { "One - {} - {}!", "two", 3 }; )
Log_Once( logables )
}
// passing a vector of boxes (less efficient than above, if the container object is used only once)
{
Log_Prune( Boxes logables; )
Log_Prune( logables.Add("One - {} - {}!", "two", 3 ) );
Log_Once( logables )
}

6.6 Wrap-Up

Some combinations of Scope 'levels' and using Groups probably do not make too much sense. For example: Grouping different LogOnce statements together within Scope::Method? Well, you rather think about splitting a huge method into a bunch of smaller ones, as your method seems to have become a little complex.

If this gets all to complicated for you being new to ALox , here are some hints:

  • Just don't overuse the combination of Groups and Scopes with Lox::Once. You will probably get along without any of it! A simple:
    Log_Once( "This is what happened: ..." )
    is mostly what is needed for debug-logging.
  • Like with other features of ALox , if parameters are omitted, you don't even see the more complex options and you are not bothered too much. See Appendix B: Auto-Configuration and Orthogonality for a summary of this.
  • If, especially in decent release-logging scenarios, a more complex setup troubles you, switch on ALox internal logging and see exactly what is happening where, when and why! Information on how to do this is found in 11 Internal Logging.

7 Prefix Logables

7.1 Introduction

The feature of ALox called Prefix Logables covered in this chapter, builds upon the ALox concept of Scopes in a similar way as feature Scope Domains does. Therefore it is advisable to have read and understood chapters

This chapter will not repeat every detail covered already in the aforementioned chapters.

Logables in ALox are of type Box . Class Lox forwards them to its attached loggers with each Log Statement. In addition to those provided with the statement, one or more boxes might be added to the beginning of the list. We call these addable objects Prefix Logables.

Before we explain the use cases for Prefix Logables, let us begin to elaborate how those are set by the user of ALox and how ALox processes them.

7.2 Setting Prefix Logables

7.2.1 Setting Prefix Logables According to the Scope

With the first way of setting and removing Prefix Logables method SetPrefix(const Box&, Scope) is used. The method and its overloaded versions is very similar to method Lox::SetDomain(const NString&, Scope) used for setting Scope Domains. Besides the difference in the name, the only difference is the first parameter, which is a Logable instead of a domain path string.

All that was said about setting Scope Domains in Chapter 4 Log Domains is true for setting Prefix Logables and this should not be repeated here. The same rules for Scopes apply, including the fact that with Scope::ThreadInner and Scope::ThreadOuter, a subsequent setting of a Prefix Logable is added to a list of Prefix Logables for these Scopes, while for other Scopes, the current Prefix Logable is replaced.

Passing nullptr as parameter logable , removes the Prefix Logable from the given Scope, respectively, in the case of thread-related Scopes, removes the Prefix Logable most recently set.

The only small difference to the interface for setting Log Domains is that there is no method available corresponding to Lox::RemoveThreadDomain , which provides a little extra flexibility of maintaining Scope Domains in contrast to maintaining Prefix Logables.

7.2.2 Setting Prefix Logables According to the Log Domain

Besides binding Prefix Logables to a Scope, ALox provides an alternative and this is binding Prefix Logables to a Log Domain. The method for doing this is FunctionEntry::Signature .

The method accepts a Log Domain path which may be absolute or relative. If relative the normal evaluation of a resulting domain path taking Scope Domains into account applies.
A third optional parameter allows to make the setting exclusive in respect to Prefix Logables which are set according to a Scope. By default, the exclusivity is not set.

Note
Alternatively, Prefix Logables bound to a Log Domain can be set using configuration variable ALOX_LOXNAME_PREFIXES. Of-course, this allows only Prefix Logables of string type to be set. More on this is found in chapter Appendix A: Configuration Variables.

7.2.3 Setting More than one Prefix Logable at a Time

While the Log Statements accept an arbitrary amount of objects, the methods to set Prefix Logables have only one parameter. If more than one Prefix Logable is to be set, the first alternative is to set them one by one:

Log_SetPrefix( ">>> " )
Log_Info( "With prefix" )
Log_SetPrefix( lox::ESC::MAGENTA )
Log_Info( "With prefix, now also in magenta" )

The second alternative is to wrap them in an object of class Boxes . This is derived from std::vector<aworx::Box>. If this is done, ALox will "flatten" the given arrays when the Prefix Logables are passed to the Loggers. This means, instead of adding the array to the overall list of Logables, the single objects contained in the array are added:

Log_Prune( Boxes myPrefixes;
myPrefixes.Add( "Never forget: 6 * 7 = " );
myPrefixes.Add( 6 * 7 );
myPrefixes.Add( ": " ); )
Log_SetPrefix( myPrefixes )
//...
// Somewhere else:
Log_Info( "Let's hope the lifecycle of the myPrefixes encloses this log statement!" )
Attention
We had seen in a previous chapter how a list of logables was created and passed to Lox::Once:
// passing an array
{
Log_Prune( Box logables[3]= { "One - {} - {}!", "two", 3 }; )
Log_Once( logables )
}
// passing a vector of boxes (less efficient than above, if the container object is used only once)
{
Log_Prune( Boxes logables; )
Log_Prune( logables.Add("One - {} - {}!", "two", 3 ) );
Log_Once( logables )
}

These two code snippets must not be used with Lox::SetPrefix. The problem is that the boxes array, respectively vector, is a local variable and will be removed from the stack, as soon as the compound of the statement is closed. (With the closing brace '}').

Attention
Please consider later chapter 7.5 Lifecycle Management of Prefix Logables in ALox for C++ for more information.

7.3 How ALox Processes Prefix Logables

With any sort of Log Statement in ALox , the Prefix Logables are collected according to the Scope and the Log Domain of that Log Statement. In the same fashion as Scope Domains are concatenated, ALox adds Prefix Logables to the list of Logables that are passed to each Logger instance. Consequently, the list that a Logger receives is filled as follows:

  1. Prefix Logable of Scope::Global
  2. Prefix Logables of Scope::ThreadOuter (can be more than one)
  3. Prefix Logable of Scope::Path
  4. Prefix Logable of Scope::Filename
  5. Prefix Logable of Scope::Method
  6. The Prefix Logables of the parent Log Domain in the order of there setting (recursively prepended!)
  7. The Prefix Logables of the Log Domain in the order of there setting
  8. The Logable of the Log Statement itself
  9. Prefix Logables of Scope::ThreadInner (can be more than one)

If in 6. or 7. a Prefix Logable was passed with optional parameter otherPLs valued Inclusion::Exclude , then after adding this Logable, the collection of further Prefix Logables is stopped. Because all objects are collected in reverse order, starting with objects of Scope::ThreadInner, this means that objects otherwise collected in 1. to 5. (respectively 6.) are not added. This allows to have a setting of a Prefix Logable which is bound to a domain to 'overwrite' those bound to a Scope.

As with any 'normal' Logable that is passed to the Logger, it is completely up to the Logger what to do with this data.

Those Logables passed with Scope::ThreadInner are appended to the list after the Log Statements' Logable and therefore should be considered a 'suffix', not a prefix. You might wonder why this whole feature is named 'prefix', especially as this term is not applicable to objects in an ordered array. The answer to this is given in the next section.

7.4 Use cases of Prefix Logables

Talking about the use cases of feature Prefix Logables, we have to distinguish between logging arbitrary objects, what ALox supports and logging textual (string) messages, what is by far the most widely application for ALox .

7.4.1 Textual Logging

When logging textual messages (more precisely: when using Loggers derived from abstract class TextLogger , just as all textual Loggers delivered with ALox are), the impact of Prefix Logable is simple. Class TextLogger just passes all objects found in the list of Logables to its ObjectConverter which in turn (in its default implementation) passes them to the formatters found in field StandardConverter::Formatters . This list by default, has two objects, namely of types FormatterPythonStyle and FormatterJavaStyle attached. This way, TextLogger is well prepared to assemble a nicely formatted log output, by default accepting Python formatter strings as well as the corresponding Java syntax.

Note
As already explained in this manual and in the documentation of class format::Formatter , there is an important feature that supports the concept of Prefix Logables very nicely: While usually (in other libraries and languages) such formatting classes accept one format string and an arbitrary amount of objects, with this class the format string is included in the object list. The first object may or may not be a format string. If it is not, the object is just "applied" (appended in textual representation) to the log output. As soon as a format string with placeholders is detected, the formatting process starts. All arguments consumed by the format string are then skipped in the list and - if still arguments exist - the algorithm continues from the start.
As a consequence, prefix logables can contain a format string and arguments, while still the Logables, which are collected from the Log Statement, can themselves contain a format string and corresponding arguments.
For more information on Loggers and TextLogger, see chapters 16 Colorful Loggers and 17 Loggers and Implementing Custom Types.

This explains the term 'prefix': Apart from Prefix Logables of Scope::ThreadInner, all Prefix Logables are prefixes to the 'log message'. Those of Scope::ThreadInner are suffixes. For the architects of the ALox API it was just too teasing to name the whole concept Prefix Logables and this way being able to have - for the most obvious use case - the user code look like this:

Log_SetPrefix( "Data File: ", Scope::Filename )
//...
//...
Log_Info( "Opened." )
//...
//...
Log_Info( "Read." )
//...
//...
Log_Info( "Closed." )

The output will look similar to this:

ut_dox_tutorial.cpp:450:LogSetPrefix [0.001 +063 µs][PROCESS]     [/IO]#020: Data File: Opened.
ut_dox_tutorial.cpp:453:LogSetPrefix [0.001 +001 µs][PROCESS]     [/IO]#021: Data File: Read.
ut_dox_tutorial.cpp:456:LogSetPrefix [0.001 +001 µs][PROCESS]     [/IO]#022: Data File: Closed.

7.4.2 Recursive Logging and Indentation

A next use case is recursively increasing 'indentation' of the log messages, as demonstrated here:

RecursiveDataType* Search( String name )
{
Log_SetPrefix( " ", Scope::ThreadOuter ) // add indent
Log_Info( "Inspecting object: ", Name )
if ( Name.Equals ( name ) )
{
Log_SetPrefix( nullptr, Scope::ThreadOuter ) // remove indent
return this;
}
// recursion
RecursiveDataType* returnValue= nullptr;
for( RecursiveDataType& child : Children )
if( (returnValue= child.Search( name )) != nullptr )
break;
Log_SetPrefix( nullptr, Scope::ThreadOuter ) // remove indent
return returnValue;
}

Note that this sample is using Scope::ThreadOuter. If it was using Scope::Method it would fail, because only the thread-related Scopes allow to add multiple objects. With thread-related Scopes, this works like a 'push and pull' mechanism. Luckily, with using the thread-related Scopes, the whole indentation is automatically thread-safe!

Indentation can also be useful when adding prefixes for different language-related Scopes. For example classes whose source files are residing in nested directories (alike namespaces), might be considered core, helper tools that usually have a low Verbosity setting. It might be a good option to indent all their logging by setting a prefix for their namespace. If they need to be debugged, and their Verbosity is increased, Log Statement of those are due to the indentation still very easily distinguishable from the rest of the log output. Such structured log output can help to increase the readability of a debug-log tremendously.

As an alternative to 'indentation', think about using the escape codes found in class alib::lox::ESC. Prefixing those instead of normal strings or spaces, leads to nicely colorized or bold or italic log output, at least with text-loggers supporting such styles. (ALox provides such Loggers e.g. for ANSI consoles or Windows OS command windows.)

7.4.3 Displaying Current Application States

Use cases are depending on the application and situation. Let us touch a last one here: Consider an application that causes errors in certain situations. Let's say, a phone app seems to start logging errors 'randomly' which means, you do not know when. You suspect it happens when the network connection drops. A first quick investigation could be to add a Prefix Logable "Online: ", respectively "Offline: " as soon as the devices' OS signals a change. You simply set this using Scope::Global, or alternatively for the Log Domain where the error occurs. In the next debug-runs, you have all messages prefixed with the current state. You do not need to follow your log output 'backward' to find the most recent log message giving you information about that status. Generally spoken: Prefix Logables allow to add status information to log lines providing information collected elsewhere.

7.4.4 Arbitrary Object Logging

The situation with Loggers designed to log arbitrary objects is different. (How to create such custom, application specific Loggers is described in 17 Loggers and Implementing Custom Types).

If only arbitrary objects were supported in ALox and the standard textual logging would not exist as the predominant use-case, then the whole feature probably would have been named Context Logables. Instead of providing the 'context' with each Log Statement to a custom Logger, or setting it explicitly using a custom interface method of such custom Logger, arbitrary context data can be used leveraging the various Scope options.

Imagine for example a custom Logger that logs into a database. A 'context' in this case could be the database table to use. Log Statements of different Scopes would then 'automatically' direct their Logables to different tables in the database, if different Prefix Logables had been set for the Scopes.

Another sample could be logging application metrics to an online metrics-server. The parameters and information passed to the server are probably encoded in a URL. Now, the bigger parts of such parameters do not change within a context (aka Scope). Those would be passed only once per Scope to ALox using the feature of Prefix Logables. The metrics-Log Statements themselves would only carry the rest of the detailed information specific to the metrics information that are supposed to be sent.

Use cases are endless and can not be named here, they depend the field of application that ALox is used to support.

7.5 Lifecycle Management of Prefix Logables in ALox for C++

One of the design goals of the ALox Logging Library is to avoid code clutter when using it. In a perfect world, Log Statements would be as clear and easy to read as comment lines. C++ does not provide life-cycle management for allocated data and this causes a potential problem when using Prefix Logables.

When logging arbitrary objects, the use cases touched in the previous section make it obvious that ALox can not be responsible for life-cycle management of Prefix Logables. Therefore, if data is used as Prefix Logable which is exclusively created for that purpose (and are no general long-living objects), there is no way to avoid some extra code that creates and deletes such objects, probably enclosed by

    #if defined(ALOX_DBG_LOG) // alternatively ALOX_REL_LOG, or both
    ...
    #endif

or embedded in macro

    Log_Prune( ... )    // alternatively Lox_Prune()

We think with release logging and binary object logging, both considered a 'heavy' use of ALox anyhow, extra code should not be anything to be concerned about.

With textual logging, especially in the case of debug logging, this is different. Here, the designers of ALox are concerned about extra code which increases the 'intrusiveness' of ALox ! Therefore, the following rule applies. For string-type Logables, precisely such of box types nchar[], wchar[] and xchar[], ALox internally creates copy of the string provided. Of-course, when such Prefix Logable is removed, ALox deletes this internal buffer. The benefit of this is huge: A user of ALox does not need to care about keeping string-type Prefix Logables 'alive' after setting them. This means, any locally assembled, short-living string can be passed to method Lox::SetPrefix and right afterwards, it can be deleted or removed by C++ from the stack if the corresponding C++ scope is left.

It is important to understand the impact:

  • With string-type Prefix Logables, you do not need to worry about the life cycle of the string passed.
  • With Prefix Logables of arbitrary type, it is the users' responsibility to keep objects intact as long as any Log Statement may be invoked that gets such Prefix Logable passed.
  • Unlike, with ALox for C# and Java, setting an AString as Prefix Logable and afterwards changing the instance, such change is not reflected in the prefix object! This is because the contents of the AString is copied.

The latter is of-course a disadvantage of this design: The Prefix Logables becomes a static object that does not reflect changes of its origin object! But there is an easy way out. Remember that only boxed objects of character array types are copied. The trick to have changes of an AString instantly reflected in the logging, is to pass it wrapped in an object of type std::reference_wrapper. If this is done, the contents is not copied. Instead a reference to the AString is boxed and any change of this object is reflected in the Prefix Logable.

The following code and its output demonstrate what was just said:

// Adding a string buffer as prefix
Log_Prune( AString prefix( "Orig: " ); )
Log_SetPrefix( prefix )
Log_Info( "Testlog before change of AString" )
// changing the buffer, does NOT change the prefix, because ALib volunteered
// to copy the string buffer.
Log_Prune( prefix.Reset("Changed: "); )
Log_Info( "Testlog after change of AString (was not effecitve)" )
// remove the previous prefix and add it again wrapped as reference
Log_SetPrefix( nullptr )
Log_Prune( prefix.Reset("Orig: "); )
Log_SetPrefix( std::reference_wrapper(prefix) )
Log_Info( "Testlog before change of AString" )
// now changing the buffer, does also change the prefix.
// But: We are now responsible that the lifecycle of our string buffer
// supersedes all log statements!
Log_Prune( prefix.Reset("Changed: "); )
Log_Info( "Testlog after change of AString (now it is effecitve!)" )
ut_dox_tutorial.cpp:172:PrefixLogablesLifecycle [0.001 +072 µs][PROCESS]     [/IO]#016: Orig: Testlog before change of AString
ut_dox_tutorial.cpp:177:PrefixLogablesLifecycle [0.001 +001 µs][PROCESS]     [/IO]#017: Orig: Testlog after change of AString (was not effecitve)
ut_dox_tutorial.cpp:183:PrefixLogablesLifecycle [0.001 +016 µs][PROCESS]     [/IO]#018: Orig: Testlog before change of AString
ut_dox_tutorial.cpp:189:PrefixLogablesLifecycle [0.001 +001 µs][PROCESS]     [/IO]#019: Changed: Testlog after change of AString (now it is effecitve!)
Note
This approach is not only applicable to class AString but to any custom string type that by default gets boxed to a character array. The only precondition is that along with the setup of module ALib Boxing in respect to the custom type, the type std::reference_wrapper<CustomString> has to be equipped with boxing interface FAppend .
How to adopt custom string types to support boxing, including this "trick" is described in the documentation of ALib Boxing in chapter 10. Boxing Character Strings.
Furthermore, in compatibility headers, the following functions are found which perform that task during bootstrap for 3rd-party libraries:

7.6 Wrap-Up

This is what this chapter has covered in respect to Prefix Logables:

  • Prefix Logables are Logables that can be set according to the Scope mechanisms featured by ALox , or according to a Log Domain.
  • With every Log Statement executed by ALox , all applicable Prefix Logables are collected in a list and passed to each Logger.
  • The most prominent use case for Prefix Logables is adding a prefix, a color or (optionally recursive) indentation to textual log messages.
  • With custom Loggers using arbitrary types of Logables, the use cases are different but not less powerful. Consider the feature to be named Context Logables rather than Prefix Logables.
  • In %ALox for C++ a copy of any string-type Prefix Logable set is created. Therefore, a user must not worry about the life-cycle of such Prefix Logables. If arbitrary objects are used, the user of ALox has to ensure that Prefix Logables survive until the last corresponding Log Statement is executed.

As with other features using ALox Scopes, on the first sight, this seems to be a little complicated. Especially when looking at the list given in chapter 7.3 How ALox Processes Prefix Logables. But when you look at the use cases, it becomes clear, that from the nine options of that list, mostly one is used in parallel, seldom two. Once the concept of Scope Domains is fully understood, the use of this feature and of others that leverage ALox Scopes, should quickly become very intuitive.

8 Log Data (Debug Variables)

8.1 Prerequisites

For a better understanding of what is explained in this chapter, it might be advisable to have read:

  • Chapter 5 Scopes in ALox. In short, at that place it is explained how ALox defines language-related Scopes (including the 'global' Scope) and thread-related Scopes. Both types are 'interwoven' and form the complete set, as denoted in enum-class Scope
  • Chapter 6 Lox::Once()
Attention
Both chapters are prerequisites for understanding what is explained here. Especially the use cases that arise from using and combining the different parameters of method Lox::Once , which are explained in a high degree of detail in the above mentioned chapter, is not repeated here. If all aspects of using Lox::Once are understood, those can be quite easily adopted to what is described in this section!

8.2 Introduction

The concept of Log Data is a feature used for debugging. The goal that is aimed here is similar to the goal of debug-Log Statements themselves. As a reminder, those are:

  • Have a process log out debug messages, that help to understand a software during the implementation phase.
  • Avoid adding temporary 'debug output statements'. Instead use permanent code, which can be disabled and preserved for later use, when needed.

Corresponding goals are aimed with Log Data. ALox provides this feature to enable the insertion of variables and data into the code that otherwise would be temporarily added for debug purposes and removed afterwards. The same as debug-logging is pruned from release executables, such variables and their use gets pruned. Therefore, such variables and use of them can stay in the code forever, and once a problem in the corresponding code section appears, no need to reinvent temporary code is needed.

A typical example for using Log Data is debug log output written by an exception handler. The code that may cause an exception, could store status information according to Scope::Method. When an exception occurs and the program execution abruptly continues within the exception handler, such information can be retrieved and corresponding log output can be generated giving more information about where and why the exception occurred.

Attention
The whole concept of Log Data provided by ALox is merely a tool to support the process of debugging and debug code. Code using ALox Log Data should be pruned from release executables. In other words: It is not advised to use ALox Log Data to implement any functionality of an application, for example storing thread-local data used outside of Log Statements. There are other, standardized, reliable and efficient ways to store thread-local data.

8.3 Using Log Data

The interface for setting and retrieving Log Data is provided with methods Lox::Store and Lox::Retrieve .

The type of data stored is Box in %ALox for C++. (What else did you expect?)

While in Chapter 6 Lox::Once() of this manual, it was explained in detail how parameters group and scope of method Lox::Once can be omitted, how one of them can be used and what happens when both are used, we do not want to repeat these details in this chapter.

Instead we want to discuss the differences:

  • Instead of storing a counter (in Lox::Once), with Lox::Store, the data object is stored.
  • Parameter group of method Lox::Once is named key in methods Lox::Store / Lox::Retrieve.
  • If both parameters (group and scope ) are omitted, Lox::Once defaults to the 'most inner Scope possible' (by switching the Scope::Filename and creating a unique key from the line number), which is the single log line itself. In contrast to this, Lox::Store / Lox::Retrieve in the parameterless version refers to the 'most outer Scope possible', hence a global singleton object (singleton in respect to the instance of class Lox used).

As a result, the use of Log Data can be summarized as follows:

  • An otherwise parameterless invocation the methods Lox::Store / Lox::Retrieve stores and retrieves a global anonymous Log Data object.
  • If parameter key is provided while parameter scope is omitted (or set to Scope::Global), a named Log Data object is stored and can be retrieved using the same key.
  • If parameter scope is provided while parameter key is omitted (or nulled or empty), an anonymous Log Data object is stored and can be retrieved (only) from within the same Scope as they were set.
  • If both parameters, key and scope are provided, then the key is bound to the provided Scope and for this scope a named Log Data object is stored. Consequently, it can be retrieved (only) from within the same Scope as it was set and with using the same key .

For clarification, this means, that different Log Data objects are stored in different scopes even if parameters scope and key are the same. For example, an object stored using Scope .Method can not be retrieved from a different method and will also not be overwritten within a different method. Again, the consequences and use cases of this design may become clearer when reading chapter Lox::Once.

8.4 Lifecycle Management of Log Data+

It is important to understand that in ALox for C++, Log Data objects have to be kept in memory, if they are passed as pointers. The question if data is passed as a pointer or as a value is answered in the Programmer's Manual of module ALib Boxing .

In short, it can be said, that typically all fundamental C++ types (int, char, double, etc.) as well as pointers to those, are stored as values. The same is true for supported string types. However, in the case of strings, the "value" passed is the pointer to the start of the string in the memory, along with its length. Therefore, this memory has to be kept valid. In the case of storing an AString, the memory stored might become invalid, if the string is extended after it was stored. Be sure to understand this constraint when using this feature.

9 Multi-Threaded Processes

9.1 Introduction

What does "logging" have to do with multi-threaded applications? Quite a bit, as we will see in this chapter.

ALox is using C++ std::thread library to identify threads through dependent module ALib Threads . Basically, that module provides just a little wrapping code around what C++ offers since language version C++ 11:

  • classes Thread and Runnable which have a similar interface to the classes found with the Java language.
  • Each Thread instance created by the module is hashed in a static table for future reference.
  • Static method Threads::CurrentThread uses std::this_thread::get_id() to identify the current thread. If no corresponding Thread instance is found in the aforementioned static table of threads, then a new object is created, added to the table and returned.

As a consequence, ALox detects all those threads rightfully that are detected (detectable!) by std::this_thread::get_id(). In general this works with all major combinations of modern operating systems, C++ libraries and thread libraries. Should threads not be detected, check out whether the thread library you are using is compatible with thread tools found with C++ library "std". The other way round: if you are using a threading library that creates threads that are detected by C++ "std", then you can use ALox thread features without any further action. If not, you should consider either to switch your software to usign something that is compatible with this modern standard, or use the thread classes provided with ALib (if you use ALox , you have ALib available). But remember the latter is a quite simplified limited piece of art - enough for simple applications, but not more!

9.2 Mapping Thread Names

ALox collects information about the thread that was executing a Log Statement. For textual logging, class MetaInfo , which is a plugged-in tool class of class TextLogger , writes the name of the executing thread by default. This default is defined with substring "%tN" in field MetaInfo::Format . Now, if you want to change the name (without changing your applications' true thread name), then method Lox::MapThreadName does the job. With this method, you do not rename a thread, you just tell the Lox to provide a different name to the Loggers in future Log Statements. Thread names can be mapped for the current thread or for threads passed via optional parameter thread , respectively id .

A sample for using this method is given in the tutorial chapter 14. Name Your Threads.

9.3 Thread-Related Scopes

With the fundamental concept of having Scopes in ALox , and in particular with the fact that ALox "interweaves" so called Scope::ThreadInner and Scope::ThreadOuter with other language-related scopes (e.g. Scope::Method) and the global Scope, ALox supports much more of multi-threaded applications than just mapping new names to threads!

This is a complex topic and there is a whole bunch of chapters we have to refer you to:

To name just a few "applications" of the features described in the chapters above:

  • Execute a Log Statement only if executed (or just not executed) by a certain thread.
  • Separate Log Statements depending on the thread they are executed by, and control the Verbosity independently from each other. By mixing thread-related Scope Domains and language related Scope Domains with each other, a user of ALox is enabled to fine-tune the log output very granular, even for logging code that he has no access to.
  • Execute Log Statements once per thread.
  • Assign prefix strings (respectively arbitrary Logables) to each Log Statement executed by a certain thread.
  • Store and retrieve named Log Domain objects (debug-information) associated with threads.

10 Differences of Debug- and Release-Logging

The ALox - Tutorial and most sample code in this manual, provide information about debug logging. The main goal of this chapter is to explain how to implement release logging. This might be easiest to explain when comparing both types of logging and listing the differences. (For a general definition see What do the terms "debug logging" and "release logging" mean?).

As previous chapters described, any logging with ALox is roughly performed as follows:

  • A dedicated instance of class Lox is created.
  • This Lox object is configured, mainly by populating it with one or more Loggers which get Verbosity set for different Log Domains.
  • Then, anywhere in the source code, Log Statements which perform the logging exclusively through the interface of that dedicated object of class Lox are inserted.

10.1 Debug Logging

Note
A-Worx (the maker of ALox ) is a consultant to the software industry and one area of competence is code style and code cleanness. We emphasize our clients to stop using temporary debug print lines in their sources. Instead, using ALox (or a similar tool) all debug output lines should be implemented tidy and clean using a nice language for the messages. With ALox , such debug output should never be temporary again and as such never be removed from the code! Programmers often remove debug output after they think a piece of code works. But if problems arise or if the code is further extended at a later stage, similar lines are inserted and re-written. This is obviously a waste of time. With ALox , you just disable certain Log Domains for which you think the code is fine and be able to reactivate them (with different verbose levels) in the moment your interest moves back to it!

Now, with debug logging, there are some specialties that do not apply in release logging:

  1. A dedicated, pre-created, static object of class Lox is provided for the purpose of debug logging, which is publicly accessible from virtually anywhere in the code (including external library components).
  2. For being able to prune debug logging statements from the code, the interface to this dedicated Lox singleton is slightly different than just using the interface of the Lox object itself. An own set of preprocessor macros for each type of logging exist (documented here). The almost only difference between the two sets is that those macros used for debug logging (that log into the debug-Lox singleton and get pruned in release compilations), are prefixed Log_, while those that are used for release logging are prefixed Lox_.
  3. For convenience, ALox does not only provide a pre-configured Lox singleton for debug logging, but it also creates an appropriate debug Logger (or even two of them for certain IDEs) in the case that no other Logger was explicitly created prior to invoking the first log call.
    This way, ALox allows to just start using debug logging with no 'bootstrap efforts' as shown in Tutorial: Hello ALox.

10.2 Release Logging

From the previous discussion about the specialties of debug logging, we can now quite easily identify how release logging differs and derive a guideline on how to implement it:

  1. An object of type Lox has to be created. (In more complex scenarios two or even more of such objects might be created and used for different use cases of release logging.)
    It has to be assured that each code entity that should be enabled perform release logging on this Lox object has proper access to it.
  2. One or more objects derived from (abstract) type detail::Logger have to be created and attached to the release-Lox object(s), usually with setting the right Verbosity for different Log Domains.
    (Both actions are usually performed at the beginning of the life-cycle of a software process, what we call the bootstrapping section of a software.)
  3. The interface of the lox object itself is directly used for logging. The Log Statements work and look similar to debug logging, because as already explained above, an own set of preprocessor macros each type of logging exist that differ only in their prefixes Log_ vs. Lox_ (see ALib Preprocessor Macros).
    Each code entity has to set the preprocessor symbol LOX_LOX prior to using release logging macros, because this symbol is used inside all preprocessor macros. This can be done in a general header file of the software, (e.g. the same that exposes the release-Lox object to that source), or, in more complex scenarios with more than one release-Lox object, at any other appropriate source location.
  4. The language-related Scopes are not usable with release logging. It is elaborated in Language-Related Scopes, why this is not considered as a big penalty. Release executables just should not contain information about source code directories and files. In Java, it is a good advice to obfuscate release software to make reverse engineering much harder this way. For security reasons and to protect a companies or programmers intellectual property.

10.3 Further Thoughts

10.3.1 Pruning Release Logging

Probably just because it was easy to implement (!), the C++ version of ALox supports the optional pruning of release logging statements as well (see ALOX_REL_LOG). This might sound confusing in the first moment, but it allows to create different versions of release-executables, which is not completely uncommon. It is optional and just provides a next level of flexibility to adopt ALox to complex use cases.

In contrast to this, ALox also allows to include caller information with release logging statements, by passing symbol ALOX_REL_LOG_CI to the build system. With that, information about source file names, their location, and function and method names will be included in the release executable. As this is usually not wanted, such setting is not defaultet. However, if done, language-related scopes can be used with release logging.

10.3.2 More Complex Use Cases

ALox allows to be used in various fashions. By having the concept of different Loggers aggregated in one or more Lox objects, using an hierarchical structure of Log Domains, which by default inherit their Verbosity from their parent domain (and this way automatically adjust to probably unknown sub-domains), we think that a good balance of "ease of use" and "unlimited extensibility" was found with the ALox architecture.

The simple and common scenarios incorporate:

  • Debug logging with
    • An appropriate Console Logger and probably an IDE specific Logger when run in an debugger
    • Optionally a simple text Logger that allows to review the output of 'historic' debug sessions
    • A few root-level Log Domains like "UI", "TCPIP", "DB", "IO", etc. having a maximum of one sub domains
  • Optionally release logging e.g. for collecting severe errors or collecting statistical summaries from 'the field'

These scenarios can be addressed with a few lines of bootstrap code and a little discipline when inserting the Log Statements.

Various ways of matching use cases of complex scenarios with ALox exist. By purpose (to keep things simple) ALox is not providing extended classes, for example to allow pruning release logging as explained in the 'recipes' of the previous paragraph. However, once the basic concepts of ALox are understood, it is easy to build such extensions, tailored to the complex case itself.

10.3.3 Using ALox and Libraries

Using ALox with libraries, is basically the same as using ALox in a single code entity. However, we recommend the following principles:

  • For release logging, a library should provide an initialization method that receives a reference to the dedicated release-Lox instance it is supposed to log into. If such object is not provided (which means the library is not duly initialized), the library should use ALox debug logging to notify that.
  • A library should not create and set any Loggers by itself. The creation of Loggers should generally be controlled by the main source entity of a process.
  • A library should document which Log Domains it is using. Furthermore it is helpful to also disclose which Verbosity is used by Log Statements per domain and maybe roughly what log output might be expected. This is especially important for rather uncommon log messages, like severe warnings and errors.
  • A library should expose a root-level Log Domain with optionally different sub-domains.
  • A library should (by nature of all library code) make thoughtful and reasonable use of combinations Verbosity and Log Domains. This allows to control the verbosity of the library in granular way.

10.3.4 The ALib Report Writer

ALib , the general purpose C++ library that ALox is one module of many, provides a concept for writing reports. This mechanism is used to report problems in debug-versions of ALib and optionally of software using ALib. With class lox::ALoxReportWriter , ALox implements such mechanism to direct ALib reports to an ALox Logger. This is all transparently set up and destructed with methods Log::AddDebugLogger and Log::RemoveDebugLogger .

Applications that do not use debug logging may want to use methods Log::AddALibReportWriter and Log::RemoveALibReportWriter with debug builds.

It is important to set the verbosity of the (internal) domain provided with method ALoxReportWriter::LogDomain to a proper level, to enable the logging of any reports.

A demonstration of how to do this is found in the ALox release logging sample code.

Note
ALox for C++ does not provide a corresponding preprocessor macro. Instead, macro Log_Prune (not Lox_Prune!) should be used to perform the the invocation of Log::AddALibReportWriter and Log::RemoveALibReportWriter with debug builds. The following code samples a proper bootstrap. First the following header has to be included by the compilation unit:
Then this code adds the report writer:
Log_Prune( Log::AddALibReportWriter( &LOX_LOX ); )
Log_Prune( Lox_SetVerbosity( myReleaseLogger, Verbosity::Info, lox::ALoxReportWriter::LogDomain() ); )
Upon termination, such report writer is to be removed as sampled here:
Log_Prune( Log::RemoveALibReportWriter() );

10.3.5 Other differences of Debug and Release Logging

Appendix reference chapter Appendix B: Auto-Configuration and Orthogonality might hint to other differences of debug and release logging.

11 Internal Logging

Well, it seems like a miracle that C++ compilers are written in C++ and looking behind the scenes it is really fine piece of software art that you discover when start bootstrapping such a system from scratch!

Well, with ALox it was much more simple: We implemented ALox and when this was done, we just afterwards added ALox Log lines into ALox code. Easy!

So, this chapter explains how these internal logs are organized and how an ALox user can work with them. A first introduction to this is given in the language-specific tutorials, chapter 16. ALox Configuration Information and Internal Log Messages.

11.1 The Internal Domain Tree

Log Domains are hierarchically organized in ALox . All Log Domains together constitute a tree of Log Domains. Class Lox maintains the tree, inserts Log Domains 'on the fly' and manages the Verbosity on a per Log Domain and Logger basis.

For internal Log Statements, ALox uses a second, separate domain tree. The separation is made to keep the main tree of Log Domains 'free' for custom domains. If this was different, then setting the root domain as in the following sample:

Log_SetVerbosity( "DEBUG_LOGGER", Verbosity::Verbose, "/" )

would affect internal log messages as well. Because of the separation, they are not. To set or modify the Verbosity of internal Log Domains, static field lox::Lox::InternalDomains has to be used as follows:

Log_SetVerbosity( "DEBUG_LOGGER", Verbosity::Verbose, Lox::InternalDomains )

When you check out the reference documentation as linked above, you will see that this field is a simple string "$/". Therefore, the code:

Log_SetVerbosity( "DEBUG_LOGGER", Verbosity::Verbose, "$/" )

is equivalent to the previous line.

Note
Of-course it is advised to use the static field instead of 'hard-coding' "$/" to be compatible with future versions of ALox (...or at least get a compiler error if this would be changed to something different than a string code.).

This piece of information is all you need to control Verbosity of ALox internal messages.

Please Note that method Log::AddDebugLogger sets the Verbosity.Warning for the internal domain. This way, when using ALox , your debug-logger will show you errors and warnings if something goes wrong inside of ALox (e.g. when you pass wrong parameters to ALox , etc.).

11.2 Sub-Domains used by ALox

For different topics, ALox uses different sub-domains for its internal logging. As usual, this allows to control the Verbosity of the internal log output more granular then by setting the internal root domain "$/". For example, if a software uses wrong Log Domains evaluated from a more complex configuration of Scope Domains, then, to investigate into the problem, sub-domain 'DMN' could be set to more verbose, while suppressing other internal messages:

Log_SetVerbosity( "DEBUG_LOGGER", Verbosity::Off , Lox::InternalDomains )
Log_SetVerbosity( "DEBUG_LOGGER", Verbosity::Verbose, NString64(Lox::InternalDomains) << "DMN" )

The list of sub-domains used by ALox is given in the following table:

Sub-Domain Description
LGR Used when Loggers are registered, retrieved or removed from a Lox and when the Verbosity of a Log Domain for a Logger is changed.
In addition used with method Lox::SetStartTime.
DMN Used when Log Domains are registered (on first use), when Scope Domains are set or removed and when Domain Substitution Rules are set.
PFX Used when Prefix Logables are set or removed.
THR Used with method Lox::MapThreadName.
LGD Used with storing and retrieving Log Data objects.
VAR Used when parsing and processing external variables.

11.3 External Use of Internal Domains

It is not 'forbidden' to have custom Log Statements using the internal domain tree:

Log_Info( NString64(Lox::InternalDomains) << "MYDOM", "Hello, this logs 'on' internal domain '$/MYDOM'!" )

Of-course it is advisable to do so only if there are good reasons for that. There is one occasion where this is already done, and that is the ALoxReportWriter described in previous chapter 10.3.4 The ALib Report Writer. The report writer logs to internal domain '$/REPORT' and this makes a lot of sense, because this way, internal ALib reports are directed to the same domain tree as internal ALox logging, hence all internal logging of ALib Modules go to the internal tree.

11.4 Final Remarks

It is absolutely OK, to register a Logger exclusively for internal domains. Methods Lox::RemoveLogger always removes the logger from both, internal and standard Log Domains.

As an alternative to internal logging, method Lox::State provides an actual snapshot of all (or selected) settings made for a Lox and its Loggers attached. Depending on the situation, each way to disclose what is going on inside ALox might help when something is irritating. As an alternative to (temporarily) adding an invocation of Lox::State to your code, ALox provides configuration variable ALOX_LOXNAME_DUMP_STATE_ON_EXIT. This allows to enable an automatic dump of the state using a command line parameter or other external configuration variable sources.

12 External Configuration

12.1 Introduction

With the term "external configuration", we are addressing any type of configuration data that an application might have access to. The most common types of such configuration data are:

  • Command line parameters
  • Environment variables and
  • Configuration files, e.g. "INI-files"

For reading such configuration data, ALox relies on the facilities provided by underlying utility library ALib . Configuration mechanics of ALib are gathered in module ALib Configuration . It is advisable to read its Programmer's Manual.

In short, the features of module ALib Configuration are:

  • A public instance of class Configuration is found in field Camp::config and with that in singleton alib::ALOX, which is derived from class Camp. ALox always tries to read configuration data from this object. If no data is found, it falls back to defaults.
  • Class Configuration supports 'plug-ins' that read configuration data from different sources.
  • Default plug-ins are those for reading command-line parameters and environment variables. In addition, a class to read and write INI-files is provided and an instance of that class can easily be created and attached.
    Note
    ALib supports to use your own configuration standards and source. to attach custom configuration techniques, a small plug-in that translates requests to read and write configuration variables from and into your custom configuration system, needs to be written and attached.
  • The different plug-ins are asked for configuration data in a sequence defined by their priority. For example, command-line parameters have a higher priority than variables defined in an INI-file. This way, command-line parameters 'overwrite' INI-file entries.
  • Variables can contain references to other variables. This concept named variable substitution, allows even more flexible configuration possibilities .

12.2 Relationship of ALox, Applications and Configuration Data

It may be very obvious to the reader, but lets spend a minute and think about the relationship of ALox and applications using it.

  • ALox is embedded as a library in applications, they live in one process.
  • ALox longs to read external configuration data
  • The application itself probably does this as well and probably disposes of a system to maintain and read such data already
  • ALox may want to write default values into the configuration. (If a variable was not found, the variable should be written, so that a user sees it and learns that is there and can be modified!)

In most use cases, the goal now is to enable ALox to access (read and write) the configuration that is already in place with the application. The huge benefit of this approach is, that no second configuration file or database is needed. And this is exactly what ALib - and therewith ALox - allows to achieve.

Then, other use cases and variants are:

  • Having separated configuration systems for the application and ALox . For documentation on how to do this, see chapter 4.6.2 Using Custom Resources and/or Configuration Plug-ins of the ALib Programmer's Manual.
  • Have no configuration system for ALox : The most obvious reason why this might be wanted is that a developer of an ALox enabled software might not want to allow the end user to reconfigure ALox (i.e. switching a Logger off that collects telemetry data).
  • A mixed approach: Some ALox features should be configurable (from either configuration system) while other features should not be configurable from outside.
  • Similar to this, some default values (that ALox creates on the fly) should automatically appear and be saved in the configuration. The benefit of this is that the user gets a fully filled configuration with maybe automatically commented entries that hints him/her to what is adjustable - all without reading manuals. Other of such ALox options probably should not automatically be saved. If not saved, these are regenerated as default values on the next run and again not be saved. A simple reason to disallow saving default values might be to avoid that the external configuration is being 'bloated' with ALox options that the user of an application is not interested in.

Finally, as ALox is a library, there might be more than one ALox enabled software running on the same system. Now, for configuration sources like INI-files and command line parameters, this is not problematic. In the case of environment variables, it might become a problem: A variable setting here could influence more than one application if variables are not renamed on a per application basis.

12.3 How ALox Uses the ALib Configuration System

Note
For details consult the Programmer's Manual of module ALib Configuration . All configuration variables are declared in one place with enumeration Variables .

Because ALox is a sibling module of ALib Configuration , ALox uses the configuration system exactly as this module expect its usage!

In particular, when using interface method Configuration::Store to write configuration data, new variables are exclusively stored in a plug-in at (or below) priority Priorities::DefaultValues . With default bootstrapping of ALib , this plug-in is of type InMemoryPlugin , hence does not store values, yet.

Then, ALox leverages the flexibility that the priorities of the ALib configuration system brings by exposing parameter priority in methods Lox::SetVerbosity and Lox::SetSourcePathTrimRule . These methods are fully aligned with ALib Configuration , because parameter priority defaults to Priorities::DefaultValues .

This way, what is set 'in code' is (by default) overruled by any type of external configuration! But if wanted, a programmer may specify a different priority and this way either allow only certain, higher prioritized external sources to overwrite, or just completely disallow any external change.

Note
Of-course, by just writing the corresponding values into the plug-in found with priority Priorities::ProtectedValues such protection could be reached as well, but by exposing the parameter with the methods, an alternative, maybe more natural and less error prone way of implementing such protection is offered.

For the user of ALox , the benefits of this approach towards external configuration data are:

  • ALox does not impose new requirements module ALib Configuration . In respect to the plug-ins installed, everything remains untouched.
  • ALib allows to have its external variables modified in the following ways:
    • Variable names can be changed
    • Category names can be changed: either for a subset of the variables or for all.
    • Default values of ALox variables can be changed
    • Variable comments can be changed. Especially, those that may come from a pool of externalized strings.
  • Single ALox variables (or all) can be excluded from the possibility to be modified from outside.
  • ALox will not bloat an applications' external configuration system with own variables, unless explicitly demanded:
  • A custom configuration plug-in will not only work in respect to prioritizing values of external variables, but also in respect to consistency with ALox verbosity settings.

12.4 Concrete Steps on Integrating ALox Configuration in your Software

After the discussion of the previous sections it should be quite straightforward to understand how ALox is to be integrated to your software in respect to external configuration data. Here are some hints:

  • Create a custom plug-in (using your custom config system) and attach it to the configuration used with module ALib Configuration :
    • Create a class that has access to your config system
    • implement the interface ConfigurationPlugin within this class
    • Plug it in
  • Alternatively: create an instance of IniFile and attach this instead.
  • During bootstrapping of ALib, patch the resources of module ALox , to change variable config category names to your application name or shortcut. This is to avoid clashing of environment variables when two ALib enabled applications exist.
  • If wanted, change the definitions of the various configuration variables, like:
  • Set values that result from own application logic. A simple sample is command line parameter --verbose which might lead to 'programatically' changes in the domains' verbosities.
  • Protect values that must not be changed from outside by storing a fixed value with priority Priorities::ProtectedValues .
  • Apply the previous two steps to one variable to redefine configuration behavior. Let's stick to the sample of having the --verbose switch. Imagine this option should get three possible values: 'silent', 'normal' and 'verbose'. The approach to take is the following:

    • Programmatically add three new custom variables stored as protected values. Their names might be MYAPP_VERBOSITY_1[2|3] . These custom variables contain the verbosity settings for all Log Domains for each of the three switchable verbosities
    • Depending on the command line switch, set the value of variable VERBOSITY to "$MYAPP_VERBOSITY_1[2|3]", also in protected mode. The '$' sign here substitutes the variables' value with the contents of named variable.

    What is achieved here, is that there are custom 'presets' defined for the verbosities and those are all that the end-user can set!

    Furthermore, if the substitution would be set to "$MYAPP_UNDOCUMENTED_SWITCH ; $MYAPP_VERBOSITY_1[2|3]" then, in addition, a hidden new configuration variable is exposed. This might be used in "the field" when something goes wrong with a deployed application and more logging is wanted, because with this new undocumented variable each Log Domain is switchable again.

  • Finally, before the termination of a process, copy those auto-generated ALox variables that you are interested in to your write-enabled configuration plug-in. This way, the user sees variables with comments and learns how to leverage those. For this, utility method Configuration::FetchFromDefault is provided. This method copies all values not found in the given plug-in from the default values plug-in. Instead of using this simple method, a selective approach can be taken to copy exactly the values wanted.

13 External Verbosity Configuration

13.1 Introduction

This Chapter brings us back to talking about Log Domains and their attribute Verbosity which are associated with pairs of a Log Domains and a Logger.

Note
Please make sure that you have read and understood 4 Log Domains before continue reading.

Setting such Verbosity by invoking Lox::SetVerbosity is easy and straightforward. However, when working in a team, different team members, working on different parts of a software, naturally are interested in different information. Now, when the Verbosity is defined in the source (preferably the bootstrap section of a software), then these would be randomly changed when checking source files in and out of a source control system. Therefore, ALox supports to read Verbosity settings from the command line, from environment variables, from an INI-file or from any other custom data source. This way, all personal changes are outside the code - good for your team spirit!

But before we can explain how this is done, we first have to talk about priorities of Verbosity settings. This is an important prerequisite to understanding how external configuration works in ALox .

13.2 Priorities of Verbosity Settings

In chapter 4 Log Domains of the ALox tutorial, in chapter 4 Log Domains and elsewhere in the ALox documentation, so far, we have just not mentioned an optional parameter priority of the overloaded set of methods Lox::SetVerbosity . We did so, because this parameter brings a new new level of complexity to the table. The good news is that (as explained in Appendix B: Auto-Configuration and Orthogonality) when the parameter is omitted, ALox behaves as if this feature was not existing.

So, what can we do with it? The parameter is of enum type config::Priorities . From the namespace you can already tell that we are using an enumeration of sibling module ALib Configuration here! The parameter defaults to its enum element Priorities::DefaultValues . This tells us something: If we do not touch the parameter, the domain is set with a priority that otherwise "default values" would have. All we are doing in our code is only setting default values. That sounds like a quite low priority, doesn't it?

The value of the constant is 10,000. When passing a higher value here, subsequent invocations for the Log Domain in question - or for one of its parent domains! - which again uses the default value, will be ignored. Let's look at the following sample:

// switching on with default priority
Log_SetVerbosity( "DEBUG_LOGGER", Verbosity::Info, "/MYDOM" )
Log_Info( "MYDOM", "This line will be logged" )
// switching off with default priority
Log_SetVerbosity( "DEBUG_LOGGER", Verbosity::Off, "/MYDOM" )
Log_Info( "MYDOM", "This line will not be logged" )
// switching on with higher priority
Log_SetVerbosity( "DEBUG_LOGGER", Verbosity::Info, "/MYDOM", config::Priorities::DefaultValues +1 )
Log_Info( "MYDOM", "This line will be logged" )
// switching off with default priority
Log_SetVerbosity( "DEBUG_LOGGER", Verbosity::Off, "/MYDOM" )
Log_Info( "MYDOM", "This line will still be logged. Domain was not switched off!" )

And its output:

ut_dox_tutorial.cpp:1428:TestBody [0.000 +292 µs][PROCESS]     [/MYDOM]#005: This line will be logged
ut_dox_tutorial.cpp:1436:TestBody [0.000 +014 µs][PROCESS]     [/MYDOM]#006: This line will be logged
ut_dox_tutorial.cpp:1440:TestBody [0.000 +007 µs][PROCESS]     [/MYDOM]#007: This line will still be logged. Domain was not switched off!

As you see, the fourth invocation of Lox::SetVerbosity is ignored, because the third had a higher priority.
With ALox internal logging (see 11 Internal Logging) activated, ALox nicely reports what is going on. We add line:

Log_SetVerbosity( "DEBUG_LOGGER", Verbosity::Verbose, Lox::InternalDomains )

to the top of the sample. Now the output is:

ut_dox_tutorial.cpp:1451:TestBody [0.000 +285 µs][PROCESS]     [$/LGR]#008: Logger "DEBUG_LOGGER": '$/'     = Verbosity::Verbose(DefaultValues).
ut_dox_tutorial.cpp:1454:TestBody [0.001 +002 µs][PROCESS]     [$/LGR]#009: Logger "DEBUG_LOGGER": '/MYDOM' = Verbosity::Info   (DefaultValues).
ut_dox_tutorial.cpp:1455:TestBody [0.001 +001 µs][PROCESS]     [/MYDOM] #010: This line will be logged
ut_dox_tutorial.cpp:1458:TestBody [0.001 +001 µs][PROCESS]     [$/LGR ] #011: Logger "DEBUG_LOGGER": '/MYDOM' = Verbosity::Off    (DefaultValues).
ut_dox_tutorial.cpp:1462:TestBody [0.001 +005 µs][PROCESS]     [$/LGR ] #012: Logger "DEBUG_LOGGER": '/MYDOM' = Verbosity::Info   (10001).
ut_dox_tutorial.cpp:1463:TestBody [0.001 +001 µs][PROCESS]     [/MYDOM] #013: This line will be logged
ut_dox_tutorial.cpp:1466:TestBody [0.001 +001 µs][PROCESS]     [$/LGR ] #014: Logger "DEBUG_LOGGER": '/MYDOM' = Verbosity::Off    (DefaultValues). Lower priority (DefaultValues < 10001). Remains Info.
ut_dox_tutorial.cpp:1467:TestBody [0.001 +001 µs][PROCESS]     [/MYDOM] #015: This line will still be logged. Domain was not switched off!

ALox in its internal log messages tells us explicitly, that the fourth request was ignored due to the higher priority level that the domain setting had before!

Consider the following Log Domain tree:

    /UI
    /UI/MOUSE
    /UI/DIALOGS
    /UI/DIALOGS/MOUSE
    /UI/DIALOGS/KEYS

Imagine you are currently frequently switching the Verbosity of sub-domains of Log Domain '/UI' because you are currently working on the UI. Now you have a random problem with mouse event handling. As long as this problem occurs, Log Statements related to mouse event should stay Verbose. Therefore, you enable them 'permanently' by invoking

Log_SetVerbosity( "DEBUG_LOGGER", Verbosity::Verbose, "/UI/MOUSE" , config::Priorities::DefaultValues + 1 )
Log_SetVerbosity( "DEBUG_LOGGER", Verbosity::Verbose, "/UI/DIALOGS/MOUSE", config::Priorities::DefaultValues + 1 )

After that, any changes in parent domains, even for root-domain, will not disable Log Statements of mouse events any more. When the mouse event handling is fixed, these settings can be deleted again.

Note
Once a priority of a Verbosity setting was increased, for the rest of the life-cycle of a process, this priority can not be changed back to a lower one. ALox does not provide an interface for that. The reason is simply, that the designers of ALox do not see any need in it. Honestly, if it was not so helpful for external configuration, the priority-feature of Verbosity settings would not even have been implemented with ALox .

13.3 Priorities of External Configuration

We learned from the previous chapter, that

  1. using hidden parameter priority with Verbosity settings, allows ALox to ignore verbosity settings if a previous setting was made with a higher priority already, and
  2. the priority enumeration used, stems from sibling module ALib Configuration .

Next, we take a look at the other enumeration elements of config::Priorities :

Constant Value
ProtectedValues <max int>
CLI 40000
Environment 30000
Standard 20000
DefaultValues 10000
AutoDetected 500

Module ALib Configuration uses these priorities to decide which setting to take if one isdoubly set. From the table above you see:

  • Auto detected values, which are those that have not been set at all and thus are "detected" or just set when nothing else is set, have lowest priority.
  • Auto detected values are overruled by values that have been "hard-coded" as default values and thus are set during the run of a software. In case of ALox those are hard-coded verbosity settings of specific domains.
  • Hard coded values in turn are overruled by "standard" configuration values. A sample for a standard configuration source is a configuration file that comes along with a software. Most prominent here are "INI-Files".
  • Next, INI-File settings are overruled by settings found in environment variables.
  • And finally, settings that are given with the invocation of a software, namely via command line interface arguments (CLI), overrule them all.

With this prioritization, module ALib Configuration provides a very natural behavior. This should be quickly discussed on a sample:

  • A team works together on different areas of one executable software.
  • Each team member adds "hard-coded" verbosity settings to the code, during the day, just as he needs and wishes.
  • Next morning, when everybody checks out the current codebase, all team members get theses hard coded domain settings. However: Their logging does not change, because they have either

    • a private INI-file, that is not managed by the source control system.
    • a second INI-file, which in contrast to the one which is updated with the source control system is private and attached to the ALib Configuration with a higher priority (e.g. 20000+1) than the normal INI-file
    • an environment variable set, which overrules the INI-File coming from the source control system.
    • command line parameters set, for example in the IDE settings of the debug-configuration

    All of these options, prevent that other team members are bothered with hard-coded domain settings. Of-course, it is recommendable that every team member anyhow uses a "private" method to change verbosity settings, instead of hard coding them. (But we all know how it is in practice.)

  • What can also happen: Next morning everybody checks out the current codebase and every team member sees some new log statements in a new Log Domain, that not existed the day before. OK, here everybody is bothered with log entries that he is not interested in. But these new domains will automatically show up in the INI-File after the first run. (This is shown below.) This way, it is easy for all team members to quickly shut these domains of without touching the code. All that they have to do is open the INI-file, search for the newly created domain name which automatically appeared in their INI-file and change it's setting down to "Off", "Error" or "Warning".

There are many other use cases. Also for release logging. Imagine a situation where a remote customer has a problem. He sends you a log-file of the problemeatic run. Unfortunately, the log file misses important verbose Log Statements. You send the customer an email and ask him to run the software again, but providing a command line parameter that enables verbose logging for a tree of domains...

This manual stops here with use cases. They are really huge. The "marriage" of sibling modules ALib Configuration with ALox through hidden parameter priority of method Lox::SetVerbosity , which is directly "lent" from the configuration system, just heavily pays off!

Note
In case you want to disable the whole thing and just deny changes made to a certain Log Domain by any user of your software, you can do this easily: Just add a hard-coded call to Lox::SetVerbosity providing the hidden parameter priority with level ProtectedValues . From there on, from nowhere lese but from within the source code a future change can be made. Only a next statement with the same maximum priority can overrule this setting.
While hard-coding things is generally bad, there are use cases for it: Logging may become a critical key feature of an application. For example when collecting metrics from the field with deployed applications. In this case, a programmer may want to disallow to disable such logging.

13.4 Implementation Details

When a Verbosity setting is read by ALox from external configuration, the priority of the plug-in that provided the data is used as the priority of the setting, just as if this value was passed to method Lox::SetVerbosity using optional parameter priority .

As a programmer, you need nothing special to do. The same as ALox creates Log Domains on the fly as soon as you are using them, the same it reads configuration data for these Log Domains without having an explicit mandate for that. It just always does. If no configuration plug-ins are set, nothing is read, ALox will not complain. However, if it finds configuration data that influences the Verbosity setting of Log Domains you are using, then you will get noted by ALox as soon as you have activated Internal Logging.

13.4.1 Activating Configuration Files

Adding own plug-ins to the configuration systems, which for example read and INI-file or attach to any other configuration source, is all explained in the Programmer's Manual of module ALib Configuration . Furthermore, it is very helpful to have a basic understanding how ALib and its ALib Camps are "bootstrapped". During bootstrapping it is decided whether your application shares the configuration files with ALib or if you application wants to divert ALib and ALox configuration to separated files, and may other things like that. ALib bootstrapping is discussed in full length in chapter 4. Bootstrapping And Shutting Down ALib of the foundational Programmer's Manual of ALib .

13.4.2 Variable LOXNAME_LOGGERNAME_VERBOSITY

This was a lot of theory, before we now finally come to the definition of the configuration variable itself. When a Logger is registered with an instance of class Lox, ALox tries to read configuration variable ALOX_LOXNAME_LOGGERNAME_VERBOSITY. (This is done also whenever a new Log Domain is used and hence created on the fly.)

The portions 'LOXNAME' and 'LOGGERNAME' of the variable name have to be replaced by the names of the instances of Lox and Logger in question.

Its format is:

ALOX_<LOXNAME>_<LOGGERNAME>_VERBOSITY = [ writeback [ VAR_NAME ] ; ] [*]domainpath[*] = verbosity [ ; … ]

Let quickly forget about the optional argument 'writeback' (we come back to this in the next section) and just concentrate on:

[*]domainpath[*] = verbosity

which can appear repeatedly separated by a semicolon ';'. The pairs of domainpath and verbosity have the following meaning:

  • domainpath denotes the path of the Log Domain whose Verbosity is to be set. Optionally the value may contain wildcard character '*' at the beginning or end of the string or at both places.
  • Parameter verbosity
    The verbosity value as listed in enum class Verbosity . The names given here are treated case insensitive and can be shortend to their bare minimum, for example "i" instead of "Info".

With having the ability to provide wildcards, the possibilities to define the Verbosity is a little different than when using method Lox::SetVerbosity ! The differences are:

  • Using configuration variables it is possible to fetch Log Domains which are residing in different sub-domain trees. For example, setting 'ERRORS', affects any Log Domain with this substring in the path, like
      /ERRORS
      /UI/DIALOGS/ERRORS
      /COMM/ERRORS/SEVERE
    
    This is not possible with using method Lox::SetVerbosity which always modifies exactly one Log Domain and its sub-domains.
  • Using method Lox::SetVerbosity, it is possible to use "relative path addressing", which means, a Log Domain or sub-domain of the evaluated, currently applicable domain resulting from Scope Domains, set can be addressed. Although it is not often recommended to do so, sometimes this possibility might be very helpful. For example to do a quick, temporary setting or for settings that are made explicitly "programatically", meaning those who are depending on state conditions of a software)

In general, as already described, controlling the Verbosity using configuration variables is preferred over using method Lox::SetVerbosity. This is the reason why ALox does not provide an interface method to set "rules", similar to those coming from configuration variables from within the source code.

Note
If you really, urgently want to have the same flexibility of setting the Verbosity for patterns of Log Domain paths from within your code, then your way to go is to save a corresponding configuration variable into the in-memory configuration plug-in found with Priorities::DefaultValues of field Camp::config of the public static singleton alib::ALOX. Of-course,you do this instead of invoking Lox::SetVerbosity.
Source code samples of how this is concretely done in the programming language of your choice can be found in the unit tests of ALox .
Note
To address the internal domain tree (see 11 Internal Logging), provide string "INTERNAL_DOMAINS" for parameter domainpath. This can also be used as a prefix e.g.
  INTERNAL_DOMAINS/REPORT = verbose ;
However, a Logger that is not configured to log on internal Log Domain, can not be enabled for doing so using this configuration variable. A Logger has to be 'added' to the internal Log Domain tree once by the software itself. This behavior is wanted and similiar to the fact that a Logger can not be added to a different Lox instance by just adding the verbosity setting variable on the command line!

13.5 Letting ALox Write the Variable

We had so far ignored the portion [ writeback [ VAR_NAME ] ; ] of configuration variable

ALOX_<LOXNAME>_<LOGGERNAME>_VERBOSITY = [ writeback [ VAR_NAME ] ; ] [*]domainpath[*] = verbosity [ ; … ]

The 'writeback' option tells ALox to write all verbosities back to the configuration as soon as a Logger is removed from a Lox object.

For this, a write-enabled configuration plug-in needs to be registered the configuration object, preferably with Priorities::Standard . The simplest type is found with class config::IniFile . In the sample application provided with this library, this approach is demonstrated. At the beginning of function main(), bootstrapping is haltet after the first phase, an INI-file is created and attached and then bootstrapping is continued:

// Partly initialize ALib/ALox, to have configuration and default resource pool in place
alib::Bootstrap(BootstrapPhases::PrepareConfig );
// Create and attach an INI file to config system...
IniFile iniFile(ALIB_BASE_DIR A_CHAR("/docs/pages/generated/ALoxSample.ini"));
if ( iniFile.FileComments.IsEmpty() )
{
iniFile.FileComments._(
"##################################################################################################\n"
"# ALox Samples INI file (created when running ALox Samples)\n"
"#\n"
"# Copyright 2013-2024 A-Worx GmbH, Germany\n"
"# Published under 'Boost Software License' (a free software license, see LICENSE.txt)\n"
"##################################################################################################\n"
);
}
alib::ALOX.GetConfig().InsertPlugin( &iniFile, Priorities::Standard );
//... and then bootstrap ALib completely
Bootstrap();

At the end of function main(), we remove the INI-File and use utility method Configuration::FetchFromDefault , to fill the INI-file with all defaults. This of-course saves an end-user of your software quite some work.

// annonce the shudown (first shutdown phase) and remove the ini-file
alib::Shutdown( alib::ShutdownPhases::Announce );
alib::ALOX.GetConfig().RemovePlugin( &iniFile );
// Use utility function to copy values from the in-memory-plug-in that carries the default values,
// to the INI-File. Note that only those values that are not existing in the INI-file, yet, are
// copied! This discloses the default values to
// a) the developer gathering all information from his team-members in case of debug-logging
// b) an end-user of an application in case of release-logging
// Both now know what is possible, without consulting a documentation... :-)
alib::ALOX.GetConfig().FetchFromDefault( iniFile );
// write the INI-File
iniFile.WriteFile();
// finalize ALib termination
alib::Shutdown();

After a first run of the application (with no INI-file existing before!), the resulting INI-File looks like this:

##################################################################################################
# ALox Samples INI file (created when running ALox Samples)
#
# Copyright 2013-2024 A-Worx GmbH, Germany
# Published under 'Boost Software License' (a free software license, see LICENSE.txt)
##################################################################################################
# 

[ALIB]
# If true, the process waits for a key stroke on termination. If empty, under Windows
# behavior is detected, under other OSes, defaults to false.
WAIT_FOR_KEY_PRESS= No

# Boolean value that denotes what its name indicates. If empty, under Windows value is 
# detected, under other OSes, defaults to true.
HAS_CONSOLE_WINDOW= No


[ALOX]
# Influences the type of console logger to be created by method
# Lox::CreateConsoleLogger which is also used by Log::AddDebugLogger
# Possible values are: default, plain, ansi, windows, noqtcreator
CONSOLE_TYPE=                      default

# Maximum elapsed time of all runs of Logger 'DEBUG_LOGGER'. To reset elapsed time display
# width, set this to 0 manually. Generated and temporary value.)
DEBUG_LOGGER_MAX_ELAPSED_TIME=     0, limit=59

# Meta info format of text logger "DEBUG_LOGGER", including signatures for verbosity strings and
# an optional string added to the end of each log statement.
#    Format: metaInfoFormat [, Error [, Warning [, Info [, Verbose [, MsgSuffix ]]]]]
DEBUG_LOGGER_FORMAT=               %SF:%SL:%A3%SM %A3[%TC +%TL][%tN][%D]%A1#%#: %V,    \
                                   \ec0,                                               \
                                   \ec3,                                               \
                                   ,                                                   \
                                   \ec8,                                               \
                                   \e[0m

# Meta info date and time format of text logger ")DEBUG_LOGGER".
#    Format: DateFormat [, TimeOfDayFormat [, TimeElapsedDays ]]]
DEBUG_LOGGER_FORMAT_DATE_TIME=     yyyy-MM-dd, HH:mm:ss, " Days "

# Meta info time difference entities of text logger "DEBUG_LOGGER".
#    Format: TimeDiffMinimum [, TimeDiffNone [, TimeDiffNanos [, TimeDiffMicros [, TimeDiffMillis
#            [, TimeDiffSecs [, TimeDiffMins [, TimeDiffHours [,  TimeDiffDays  ]]]]]]]]
DEBUG_LOGGER_FORMAT_TIME_DIFF=     1000, "---   ", " ns", " µs", " ms", " s", " m", " h", " days"

# Multi-line format of text logger "DEBUG_LOGGER".
#    Format: MultiLineMsgMode [, FmtMultiLineMsgHeadline [, FmtMultiLinePrefix [, FmtMultiLineSuffix
#            [, MultiLineDelimiter [, MultiLineDelimiterRepl ]]]]]
DEBUG_LOGGER_FORMAT_MULTILINE=     2, "ALox: Multi line message follows: ", ">> ", 

# The verbosities of logger "DEBUG_LOGGER" in lox "LOG". Use 'writeback [VAR_NAME] ;'
# to enable automatic writing on application exit.
LOG_DEBUG_LOGGER_VERBOSITY=        writeback;    \
                                   /=              Verbose;    \
                                   /TEXTFILE_TEST= Verbose;    \
                                   $/=             Warning;    \
                                   $/DMN=          Verbose;    \
                                   $/LGD=          Warning;    \
                                   $/LGR=          Warning;    \
                                   $/PFX=          Warning;    \
                                   $/REPORT=       Verbose;    \
                                   $/THR=          Warning;    \
                                   $/VAR=          Warning

# Log information about lox "LOG" on exit. Comma separated list of arguments define
# verbosity, domain and content of output. Possible values content arguments are:
#   All, Basic, Version, SPTR, Loggers, Domains, InternalDomains
#   ScopeDomains, DSR, PrefixLogablesOnce, LogData, ThreadMappings, 
#   CompilationFlags. If NONE is given nothing is dumped.
LOG_DUMP_STATE_ON_EXIT=            none, verbosity=info, domain=/ALOX

# Auto size values of last run of Logger 'DEBUG_LOGGER' (generated and temporary values).
DEBUG_LOGGER_AUTO_SIZES=           (T,15,15)(T,35,35)(F,15,15)(F,16,16)(T,85,85)(T,91,91);

# Maximum elapsed time of all runs of Logger 'ANSI_CONSOLE'. To reset elapsed time display
# width, set this to 0 manually. Generated and temporary value.)
ANSI_CONSOLE_MAX_ELAPSED_TIME=     0, limit=59

# Meta info format of text logger "ANSI_CONSOLE", including signatures for verbosity strings and
# an optional string added to the end of each log statement.
#    Format: metaInfoFormat [, Error [, Warning [, Info [, Verbose [, MsgSuffix ]]]]]
ANSI_CONSOLE_FORMAT=               %SF:%SL:%A3%SM %A3[%TC +%TL][%tN][%D]%A1#%#: %V,    \
                                   \ec0,                                               \
                                   \ec3,                                               \
                                   ,                                                   \
                                   \ec8,                                               \
                                   \e[0m

# Meta info date and time format of text logger ")ANSI_CONSOLE".
#    Format: DateFormat [, TimeOfDayFormat [, TimeElapsedDays ]]]
ANSI_CONSOLE_FORMAT_DATE_TIME=     yyyy-MM-dd, HH:mm:ss, " Days "

# Meta info time difference entities of text logger "ANSI_CONSOLE".
#    Format: TimeDiffMinimum [, TimeDiffNone [, TimeDiffNanos [, TimeDiffMicros [, TimeDiffMillis
#            [, TimeDiffSecs [, TimeDiffMins [, TimeDiffHours [,  TimeDiffDays  ]]]]]]]]
ANSI_CONSOLE_FORMAT_TIME_DIFF=     1000, "---   ", " ns", " µs", " ms", " s", " m", " h", " days"

# Multi-line format of text logger "ANSI_CONSOLE".
#    Format: MultiLineMsgMode [, FmtMultiLineMsgHeadline [, FmtMultiLinePrefix [, FmtMultiLineSuffix
#            [, MultiLineDelimiter [, MultiLineDelimiterRepl ]]]]]
ANSI_CONSOLE_FORMAT_MULTILINE=     2, "ALox: Multi line message follows: ", ">> ", 

# The verbosities of logger "ANSI_CONSOLE" in lox "RELEASELOX". Use 'writeback [VAR_NAME] ;'
# to enable automatic writing on application exit.
RELEASELOX_ANSI_CONSOLE_VERBOSITY= writeback;    \
                                   /=    Off;    \
                                   /CON= Verbose

# Log information about lox "RELEASELOX" on exit. Comma separated list of arguments define
# verbosity, domain and content of output. Possible values content arguments are:
#   All, Basic, Version, SPTR, Loggers, Domains, InternalDomains
#   ScopeDomains, DSR, PrefixLogablesOnce, LogData, ThreadMappings, 
#   CompilationFlags. If NONE is given nothing is dumped.
RELEASELOX_DUMP_STATE_ON_EXIT=     none, verbosity=info, domain=/ALOX

# Auto size values of last run of Logger 'ANSI_CONSOLE' (generated and temporary values).
ANSI_CONSOLE_AUTO_SIZES=           (T,15,15)(T,36,33)(F,15,15)(F,4,4)(T,75,74)(T,81,81);

# Maximum elapsed time of all runs of Logger 'MEMORY'. To reset elapsed time display
# width, set this to 0 manually. Generated and temporary value.)
MEMORY_MAX_ELAPSED_TIME=           0, limit=59

# Meta info format of text logger "MEMORY", including signatures for verbosity strings and
# an optional string added to the end of each log statement.
#    Format: metaInfoFormat [, Error [, Warning [, Info [, Verbose [, MsgSuffix ]]]]]
MEMORY_FORMAT=                     "%SF:%SL:%A3%SM %A3[%TC +%TL][%tN]%V[%D]%A1#%#: ",    \
                                   [ERR],                                                \
                                   [WRN],                                                \
                                   "     ",                                              \
                                   [***],                                                \
                                   

# Meta info date and time format of text logger ")MEMORY".
#    Format: DateFormat [, TimeOfDayFormat [, TimeElapsedDays ]]]
MEMORY_FORMAT_DATE_TIME=           yyyy-MM-dd, HH:mm:ss, " Days "

# Meta info time difference entities of text logger "MEMORY".
#    Format: TimeDiffMinimum [, TimeDiffNone [, TimeDiffNanos [, TimeDiffMicros [, TimeDiffMillis
#            [, TimeDiffSecs [, TimeDiffMins [, TimeDiffHours [,  TimeDiffDays  ]]]]]]]]
MEMORY_FORMAT_TIME_DIFF=           1000, "---   ", " ns", " µs", " ms", " s", " m", " h", " days"

# Multi-line format of text logger "MEMORY".
#    Format: MultiLineMsgMode [, FmtMultiLineMsgHeadline [, FmtMultiLinePrefix [, FmtMultiLineSuffix
#            [, MultiLineDelimiter [, MultiLineDelimiterRepl ]]]]]
MEMORY_FORMAT_MULTILINE=           2, "ALox: Multi line message follows: ", ">> ", 

# The verbosities of logger "MEMORY" in lox "LOG". Use 'writeback [VAR_NAME] ;'
# to enable automatic writing on application exit.
LOG_MEMORY_VERBOSITY=              writeback;    \
                                   /=    Off;    \
                                   /CON= Off;    \
                                   /MEM= Verbose

# Auto size values of last run of Logger 'MEMORY' (generated and temporary values).
MEMORY_AUTO_SIZES=                 (T,15,15)(T,36,33)(F,15,15)(F,4,4)(T,80,79)(T,88,88);

# The verbosities of logger "MEMORY" in lox "RELEASELOX". Use 'writeback [VAR_NAME] ;'
# to enable automatic writing on application exit.
RELEASELOX_MEMORY_VERBOSITY=       writeback;    \
                                   /=    Verbose;    \
                                   /CON= Verbose

# Maximum elapsed time of all runs of Logger 'TEXTFILE'. To reset elapsed time display
# width, set this to 0 manually. Generated and temporary value.)
TEXTFILE_MAX_ELAPSED_TIME=         0, limit=59

# Meta info format of text logger "TEXTFILE", including signatures for verbosity strings and
# an optional string added to the end of each log statement.
#    Format: metaInfoFormat [, Error [, Warning [, Info [, Verbose [, MsgSuffix ]]]]]
TEXTFILE_FORMAT=                   "%SF:%SL:%A3%SM %A3[%TC +%TL][%tN]%V[%D]%A1#%#: ",    \
                                   [ERR],                                                \
                                   [WRN],                                                \
                                   "     ",                                              \
                                   [***],                                                \
                                   

# Meta info date and time format of text logger ")TEXTFILE".
#    Format: DateFormat [, TimeOfDayFormat [, TimeElapsedDays ]]]
TEXTFILE_FORMAT_DATE_TIME=         yyyy-MM-dd, HH:mm:ss, " Days "

# Meta info time difference entities of text logger "TEXTFILE".
#    Format: TimeDiffMinimum [, TimeDiffNone [, TimeDiffNanos [, TimeDiffMicros [, TimeDiffMillis
#            [, TimeDiffSecs [, TimeDiffMins [, TimeDiffHours [,  TimeDiffDays  ]]]]]]]]
TEXTFILE_FORMAT_TIME_DIFF=         1000, "---   ", " ns", " µs", " ms", " s", " m", " h", " days"

# Multi-line format of text logger "TEXTFILE".
#    Format: MultiLineMsgMode [, FmtMultiLineMsgHeadline [, FmtMultiLinePrefix [, FmtMultiLineSuffix
#            [, MultiLineDelimiter [, MultiLineDelimiterRepl ]]]]]
TEXTFILE_FORMAT_MULTILINE=         2, "ALox: Multi line message follows: ", ">> ", 

# The verbosities of logger "TEXTFILE" in lox "LOG". Use 'writeback [VAR_NAME] ;'
# to enable automatic writing on application exit.
LOG_TEXTFILE_VERBOSITY=            writeback;    \
                                   /=              Verbose;    \
                                   /TEXTFILE_TEST= Verbose;    \
                                   $/=             Error;      \
                                   $/DMN=          Error;      \
                                   $/LGD=          Error;      \
                                   $/LGR=          Error;      \
                                   $/PFX=          Error;      \
                                   $/REPORT=       Error;      \
                                   $/THR=          Error;      \
                                   $/VAR=          Error

# Auto size values of last run of Logger 'TEXTFILE' (generated and temporary values).
TEXTFILE_AUTO_SIZES=               (T,15,15)(T,30,30)(F,15,15)(F,14,14)(T,83,83)(T,89,89);

Well, this is quite a long INI-file, but this is due to the fact that the sample application creates several Lox and Logger instances. Furthermore, each and every configuration aspect of ALox is found now in this file. An end-user is now enabled to change the logging behaviour to adopt his preferences and needs.

Note
Again: If you don't like this, or don't like it for some subsets of your log statements, a programmer has the full control over allowing such changes or not! Also, utility method Configuration::FetchFromDefault , just fetches everything. You could use your own function to copy only certain variables.

Finally let's talk about the attribute writeback in the config variable. If this is set, then the verbosity setting is written back to its configuration source (here the INI-file) when ALib is shut down. This way, on the next run of your software, the list might be changed: Some new Log Domains might appear others disappear. The reason is that ALox recognizes only those domains that are actually used (independent from their Verbosity setting, so even if no log statement is generated). Subsequent runs may have different execution paths and this way might "touch" other Log Domains.

Note
When manually changing Verbosities it is OK to just use one character for the setting. E.g. change an "Info" to "w". The next time you run the application, ALox will write "Warning" and format the string accurately.

13.6 Writing Substitution Variables

Another look at the format specification of the configuration variable controlling the verbosity:

ALOX_<LOXNAME>_<LOGGERNAME>_VERBOSITY = [ writeback [ VAR_NAME ] ; ] [*]domainpath[*] = verbosity [ ; … ]

tells us: attribute writeback has an optional parameter "VAR_NAME". Its use is best explained when looking at a sample use case.

Let's imagine the developer of the application has some problems with interfacing the XWindow library. Hence, she would focus on the relevant domains and change the Verbosities to this:

    [ALOX]
    LOG_DEBUG_LOGGER_VERBOSITY = writeback;         \
                                 /                   =Warning;     \
                                 /ACTION             =Warning;     \
                                 /AWAX               =Info;        \
                                 /CMDLINE            =Off;         \
                                 /TILE               =Off;         \
                                 /WMI                =Info;        \
                                 /WMI/XLIB           =Verbose;     \
                                 /WMI/XLIB/RANDR     =Warning;     \
                                 /WMI/XLIB/X11       =Verbose;     \

Now, the X11 problems were fixed. However, the developer likes the detailing of the settings and aims to preserve them for the future. All he needs to do, is to add optional parameter VAR_NAME to the write back argument in the INI-file as shown here:

    [ALOX]
    LOG_DEBUG_LOGGER_VERBOSITY = writeback MYSTUFF_X11_DEBUG_VERB; \
                                 /                   =Warning;     \
                                 /ACTION             =Warning;     \
                                 /AWAX               =Info;        \
                                 /CMDLINE            =Off;         \
                                 /TILE               =Off;         \
                                 /WMI                =Info;        \
                                 /WMI/XLIB           =Verbose;     \
                                 /WMI/XLIB/RANDR     =Warning;     \
                                 /WMI/XLIB/X11       =Verbose;     \

After the next run, the INI-file will contain:

    [ALOX]
    LOG_DEBUG_LOGGER_VERBOSITY = writeback MYSTUFF_X11_DEBUG_VERB; \
                                 /                   =Warning;     \
                                 /ACTION             =Warning;     \
                                 /AWAX               =Info;        \
                                 /CMDLINE            =Off;         \
                                 /TILE               =Off;         \
                                 /WMI                =Info;        \
                                 /WMI/XLIB           =Verbose;     \
                                 /WMI/XLIB/RANDR     =Warning;     \
                                 /WMI/XLIB/X11       =Verbose;     \


    [MYSTUFF]

    # Created at runtime through config option 'writeback' in variable "ALOX_LOG_DEBUG_LOGGER_VERBOSITY".
    X11_DEBUG_VERB=              /                   =Warning;     \
                                 /                   =Warning;     \
                                 /ACTION             =Warning;     \
                                 /AWAX               =Info;        \
                                 /CMDLINE            =Off;         \
                                 /TILE               =Off;         \
                                 /WMI                =Info;        \
                                 /WMI/XLIB           =Verbose;     \
                                 /WMI/XLIB/RANDR     =Warning;     \
                                 /WMI/XLIB/X11       =Verbose;     \

As you see, ALox created new INI-file section MYSTUFF and added the variable name that was specified. You can still fine-tune the verbosity setting in LOG_DEBUG_LOGGER_VERBOSITY or chose different execution paths of your software. On each run, ALox will save what is found in variable ALOX_LOG_DEBUG_LOGGER_VERBOSITY to variable MYSTUFF_X11_DEBUG_VERB!

Once the developer thinks that the setting is OK, the writeback feature might be desiabled by changing the logger's Verbosity setting in the INI-file to:

    [ALOX]
    LOG_DEBUG_LOGGER_VERBOSITY = $MYSTUFF_X11_DEBUG_VERB

    [MYSTUFF]
    ...
    ...

Now we are using a feature of the ALib configuration system called Variable Substitution. With the preceding '$' symbol (this is the default and can be changed), class Configuration substitutes what is "$MYSTUFF_X11_DEBUG_VERB" with the corresponding variable.

To conclude, lets take some further notes on what was said above:

  • The procedure of fine-tuning a certain "verbosity preset" and storing this under a different varible name, can of-course be repeated and different sets can be saved in different custom variables. Once this is done, switching the verbosities between different "debug use-cases", is a matter of changing the substitution variable in variable LOXNAME_LOGGERNAME_VERBOSITY.
  • Of-course, the configuration sets stored can be shortened easily to contain only non-redundant information. ALox , when writing the variable, includes all sub-domains in the list of settings, even those that have the same Verbosity as their parent domain. This is intentionally done to support the user and decrease typing effort.

    Once a setting is fixed, all redundant lines can be deleted easily - but again this is not necessary even, it might just increase readability and shorten the configuration file.

  • When execution paths are changing between different runs, ALox will remove Verbosity information provided, if a Log Domain was not used. The advantage of this is, that when Log Domains are renamed in the source code, no "garbage" remains in the config file - at least for freshly written variables. If you want to compare an 'old' variable with what the difference would be using the newest version of your software, variable ALOX_LOG_DEBUG_LOGGER_VERBOSITY can be set as follows:
      [ALOX]
      LOG_DEBUG_LOGGER_VERBOSITY = writeback MY_NEW_VAR ; $MY_OLD_VAR
    
    After running the software MY_NEW_VAR will not contain Log Domains that are not existing any more (or have not been touched by the execution path!) but will contain new domains (or those that previously had not been touched by the execution path).
  • For the variable name optionally provided with argument writeback , if in anonymous category, the same rules on using the underscore character '_' apply as it is described in Variable Substitution.
  • With more complex use cases, there are even new options: For example, when having different configuration files in parallel attached to a Configuration instance, e.g. a system-wide and user specific INI-file (the latter with a higher priority value!), then substitution "preset-variables" might reside in the deployed application's system-wide configuration, while each user locally can address these variables and use them in his local INI-File.
    Likewise, an environment variable, or a command line parameter could use a substitution variable that defines such preset, with the substitute residing in a configuration file...
  • The whole concept can also be used 'in code'. As a sample think of a command line application that provides different "presets" for verbosities". The corresponding CLI parameter is usually specified as <c>-v, &ndash;verbosity \<val\></c>.<br> Now, the software could write a variable of e.g. \b ALOX_REL_CONSOLE_VERBOSITY (a release logger responsible for the software's standard output) with either \ref alib::config::Priorities "Priorities::DefaultValues" or \ref alib::config::Priorities "Priorities::ProtectedValues" (use the latter to disallow the user to hack in). The value of the variable set 'in code' would either contain substituted system-wide external variables, or hard-coded strings (again, depending on the necessary protection level of the software). - If module \ref alib_mod_config "ALib Configuration" is wished to be used in a software, besides the use of \b %ALox , hence for the applications own config parameter, it is possible to separate application-specific configuration variables in different configuration file. When looking at the quite long config file above, the motivation for this is obvious: \b %ALox should not clutter an application's config file with all its information. The way how to do this is described in detail in the Programmer's Manual of \b %ALib in chapter \ref alib_manual_bootstrapping. @subsection alib_mod_external_verbosity_configuration_wrapup 13.6 Wrap-up As a summary, these are the takeaways of this chapter: - \b %ALox has an optional <span>priority</span> with method \b %Lox::SetVerbosity which is fully orthogonal in its use. This means, when omitted \b %ALox works as if this feature was not existing. (aka "...as if ALox was one of the hundreds of other ordinary and boring logging library out there.)
  • The priority of Verbosity settings was invented to make external configuration as transparent and "natural" as possible. It sounds complex, but in the end it is the simplest-stupid approach, when juggling with hard-coded defaults, configuration files, environment variables and command line options...and even with system-wide and local configuration files!
  • The recommended way to set Verbosities - especially when working in a team - is to use external configuration variables.
  • It is possible to overwrite different data sources, e.g. use an environment variable to overwrite what is given in an iniFile. Or to have two INI-files, one local, user dependent and one general, public instance. The user-dependent INI-file may modify (overwrite) settings of the general one.
  • In special situations it is recommended to protect certain Log Domains from being changed at runtime. This is done by explicitly providing Priorities::ProtectedValues to method Lox::SetVerbosity. A similar issue is described for Domain Substitution in an upcoming chapter.
  • External setting of Verbosity has different (partly more, partly less) possibilities in comparison to "programatically" setting. A way to extend the programatically setting to work the same as external configuration, is by using an in-memory configuration file, with higher priority than Priorities::Standard .

14 Trimming Source File Paths and Clickable IDE Output

To collect the path information of a Log Statement, the C++ version of ALox uses preprocessor macro __FILE__.

The source file name and path information received is used in two ways:

  • To determine the Scope of a Log Statement: ALox uses this to implement a variety of nice features like Scope Domains, Prefix Logables, Lox::Once or Log Data.
  • For logging out such information to provide the context of a Log Statement. If an IDE supports it this may make log output 'clickable'.
Note
Let's be honest: While we are roughly on page 50 of this freaking Programmer's Manual, and we are not even very close to its end, and you might wonder: "Wow, what did these people motivate to write and publish all this fancy code and docs?", here comes the answer: The project started in 2013 for one single reason: We wanted to have clickable log output in our IDEs!. We just hated to search the spot of a debug-log message manually in the code! The rest is history...

Source file paths can become quite long and for various different reasons, it might be wanted remove a redundant or irrelevant prefix from the it.

14.1 Source Path Trim Rules

ALox provides a mechanism to trim the path of source files by using a set of rules.

Internally, different lists of such 'Source Path Trim Rules' are maintained. One is a static table, which stores trim information used by all instances of class Lox. In addition, each Lox has an own, private set of Source Path Trim Rules. The advantage of having the static, global table is that a user can set 'hints' for trimming the path only once (at bootstrap) and forget about it. All instances of Lox will use it.

14.1.1 Automatic Detection of Trim Rules

ALox tries to detect trimable portions of a source path automatically. For that, ALox compares the actual path name of the executable of the currently running process with the path of a source file. The prefix both have in common is identified as trimable.

As an example, imagine a process' file name including the path is:

    /home/lisa/bin/myapp(.exe)

When ALox finds a source file residing in:

    /home/lisa/dev/myapp/src/ui/dialog.cpp

The trimable prefix of the path would be "/home/lisa/". Therefore, the trimmed file name would be:

    dev/myapp/src/ui/dialog.cpp

The result is a more narrow log output which is probably even better readable.

Of-course, it would be even nicer to have

    ui/dialog.cpp

or alternatively:

    src/ui/dialog.cpp

Before we learn how we can configure ALox to trim the path like this, let us first better understand when ALox performs the automatic detection described above:

  • ALox tries to detect a rule for trimming only once per Lox:
    The reason for this is simply performance. We rather want ALox to be fast instead of trying to detect trim rules with every Log Statement. However, if different subsets of the source code resides in different paths, the path detected becomes random: It depends on the fact which source logs first! And even worse: if no match was found with that first file, no second try is performed.
  • More precisely, ALox does its one-time detection exactly in the moment a first source file is using a Lox and the path of this file was not processed by an explicitly provided Source Path Trim Rule.
  • If ALox finds a rule, such rule is stored in the list dedicated to the Lox that found it, not in the global list applicable for all Lox instances. This is why the one-time detection is a one-time detection per Lox.

As you see, this auto-detection works nicely on small projects with source files kept local and the executable is residing in a 'related' place.

When things become more complex, it is advisable to add Source Path Trim Rule explicitly.

14.1.2 Programmatical Provision of Trim Rules

A user of ALox may use method Lox::SetSourcePathTrimRule to add Source Path Trim Rules explicitly. When you reconsider the sample above, explicit setting leads to better results even in simple project. The sample used source file:

    /home/lisa/dev/myapp/src/ui/dialog.cpp

Now, for a programmatically setting, Lisa invokes

// hard-coded trim rule
Log_SetSourcePathTrimRule( "*/myapp/src/", lang::Inclusion::Include )

as the very first statement before using ALox . The first parameter tells ALox which string to search. A leading wildcard character '*', indicates that the path is a substring of the file paths identified (no trailing '*' is needed). The second, optional parameter tells ALox to include the given substring in the trimming. In other words, the substring will be excluded from the trimmed path. In again other words it means that the given portion of the path is included in the trimming action!

Now the trimmed source path leads to

    ui/dialog.cpp

which is probably what is wanted. As we see, the huge advantages of explicitly setting Source Path Trim Rules is that they do a better job, are independently applied regardless of 'where' the executable is located and that any random behavior of trimming is eliminated.

Note
If the platform (compiler) specific path separator is '/', then characters '\' found in parameters path and trimReplacement when setting rules are replaced by '\' and vice versa. This allows to specify paths and substrings thereof in a platform independent way. The same is true when rules are defined in external configuration variables, as described later in this chapter.

It is advisable to use set Source Path Trim Rules at the very start of a process. Even prior to attaching the debug Loggers or custom Loggers to any Lox instances. The reason for the latter is that already attaching a Logger lets class Lox evaluate the Scope and potentially create the internal automatic rule. This auto-rule might be conflicting with a user-given rule but be prioritized due to the fact that it was created prior to the users' rule.

Then, in a multi-threaded application, from the moment on parallel access to two different instances of class Lox are performed, it is not allowed to add 'global' rules, because, for performance reasons, the access to the rules is not protected by a mutex.

Note
Alternatively, all existing instances of class Lox have to be locked 'manually' by invoking Acquire() prior to setting a global rule.

If multiple Source Path Trim Rules are set, those are applied in their order of creation. The private rules of a Lox are applied first, the global rules afterwards. Only one rule per source path is executed. In other words, as soon as a rule matches and modifies a source path, no further attempts for trimming are performed.

14.1.3 Trim Rules Provided by External Configuration Variables

Setting Source Path Trim Rules 'from within the source code' is easy and straightforward. However, when working in a team such changes might not be appreciated by other team members. In this and other situations it is advisable to retrieve Source Path Trim Rule from external configuration data, command-line parameters or other sources at runtime.

For Source Path Trim Rules and also other configuration data, ALox leverages the configuration facility provided by sibling module ALib Configuration .

Note
  • For general information on ALox configuration variables consult ALox Configuration Variables.
  • For information on how to pass configuration data from custom sources to ALox , refer to the Programmer's Manual of module ALib Configuration .
  • Furthermore, previous chapter 13 External Verbosity Configuration gives a great introduction in external configuration techniques, including a source code sample that can be quickly copied to your own code.

When an instance of class Lox is constructed, two a configuration variables are tried to be read:

  • ALOX_GLOBAL_SOURCE_PATH_TRIM_RULES.
  • ALOX_LOXNAME_SOURCE_PATH_TRIM_RULES.

The first is for defining global rules. The second variable is specific to a named Lox and the substring 'LOXNAME' needs to be replaced by the name of the Lox in question.

Otherwise, the format of the variables are the same and the following description applies to both.

The variable may contain a list of Source Path Trim Rules separated by semicolons ';'. Each rule consists of up to five values, separated by colons ','. The five values correspond to the first five parameters of method Lox::SetSourcePathTrimRule . The same as parameter 3 (trimOffset ), 4 (sensitivity ) and 5 (trimReplacement are optional with method Lox::SetSourcePathTrimRule, providing them in the configuration variable is optional as well.

As an example, an INI-file of an application might have the following sub-section:

[ALOX]
GLOBAL_SOURCE_PATH_TRIM_RULES= *src/ , Include ; \
/usr/local/lib/ , Exclude, 9, Sensitive, /usr/lib/

14.1.4 Priorities of Trim Rules

Different trim rules might be redundant or even contradict each other. Therefore the rules are equipped with a priority. Rules with higher priority are applied first. Once a rule matches, no further rule is applied. The priority is defined with enumeration config::Priorities and "lent" (for good reasons) from sibling module ALib Configuration . It is are set as follows:

  • In the case that a rule is read from an external configuration variable, the priority value is set to the priority value of the configuration plug-in that provided the variable.
  • In the case that the rule is set from within the source code, the priority is taken from optional parameter priority of method Lox::SetSourcePathTrimRule . The parameter defaults to Priorities::DefaultValues and thus "hard-coded" trim rules in source code have a lower priority than those in external configuration.
  • Rules that are automatically detected have a priority of Priorities::AutoDetected and thus are overruled by all others. To change this behavior, any other priority may be specified in the source code, especially the pre-defined value Priorities::ProtectedValues , which allows a programmer to give the rule set in the source code the highest priority and this way to protect the rule from external manipulation.

14.1.5 Verifying Trim Rules

With most methods invoked on class Lox, internal log messages are provided (See 11 Internal Logging). When setting Source Path Trim Rules, this is not the case. The rational here is, that in most cases no Logger is attached to the Lox attached, yet. If something goes wrong with source path trimming, e.g. when you are not sure if a rule was read from external configuration variables, you can use method Lox::State to dump out the current state of a Lox after a logger was added.
Besides rules set programmatically or using configuration variables, if exists, also the auto-generated rule is displayed.

Note
As an alternative to (temporarily) adding an invocation of Lox::State to your code, ALox provides configuration variable ALOX_LOXNAME_DUMP_STATE_ON_EXIT. This allows to enable an automatic dump of the state using a command line parameter or other external configuration sources.

14.1.6 Removing Trim Rules

In very special situations (e.g. ALox uses it for unit testing) method Lox::ClearSourcePathTrimRules can be used to clear the rules of a Lox instance, optionally including all global rules.

Note
This method can also be used prior to invoking Lox::SetSourcePathTrimRule to suppress the creation of the automatic rule. Of-course, this has to be done for every instance of class Lox in question, because the automatic rule and its creation is local to the each Lox.

14.2 Considerations in respect to Scope Functionality

The benefits of having ALox using reduced length source file paths in consideration to Scopes is in respect to performance. All data associated with language-related Scopes is stored and retrieved in hash-tables with keys containing the source path.
Furthermore, the output of method Lox::State might be better readable with these shorter paths.

It is also important to consider that when using Scope::Path, especially if increased by an amount of addressed parent directories, the source path must not be trimmed more than such use suggests. When the files are sorted in nested directories according to the nested namespace structure implemented in the files, then the directory prior to the tree of namespace directories should be included in the trimmed path.

14.3 Considerations in respect to Logging Meta Information

Note
This section is about textual logging with the use of Loggers derived from class TextLogger , provided with ALox . For other Loggers, other considerations might apply.

Due to the rich meta information that ALox provides, the log output lines quickly become quite wide. Therefore, trimming the path of source files in the log output to a level that still allows to distinguish all files properly, can help a lot to reduce such width.

Class MetaInfo , with its instance found in every ALox TextLogger, provides a format string to structure the meta info. For the source path, two format parameters exists:

  • SP: Provides the full path name
  • Sp: Provides the trimmed path name

Directly from the log-output you can observe how trimming is performed by ALox and whether there is the need to add a trimming rule explicitly.

However, those IDEs which recognize certain patterns and allow to automatically link these patterns with the source file in their editor, sometimes need to see the full path name or a certain part of it. To adjust ALox to your IDE, you might have to play around a little. If your IDE supports clickable output lines, you might want to change the format of the meta info to match the pattern that your IDE requires.

Note
In documentation page Appendix D: IDE Setup for ALox for C++, detailed instructions for selected platforms and IDEs are given.

Certain build systems/compilers might not provide the absolute path information in certain situations. In contrast, they might provide source directories in relative addressing in respect to a project file. If then the IDE does not recognize the right origin of a relative path, this path needs correction to be absolute. ALox provides a feature to do such correction. An optional parameter of method Lox::SetSourcePathTrimRule allows to specify a replacement string for what is cut by a rule. (This replacement string can of-course also be set using the configuration variables described in previous chapter 14.1.3 Trim Rules Provided by External Configuration Variables.) Sometimes it might be needed to "fiddle around" with the options of the trim rules and the format of the TextLogger output, until finally your IDE recognizes the source information correctly. But once succeeded, it proofs being worth the effort!

When you are sure, that your IDE does not support the links to the editor, then you're unfortunate and your way to go is displaying a (best possible) trimmed path, to reduce overall log output width.

Note
Honestly, in this case we would recommend to switch the IDE!

15 Log Domain Substitution

15.1 Introduction

Domain Substitution is a concept of ALox that allows to manipulate the effective domain of Log Statements without changing neither these statements nor the Scope Domains that apply to them. Therefore, Domain Substitution is useful when those changes can not be performed, most likely because the corresponding code using ALox is residing in a library. Sometimes, also a "temporary redirect" of certain domains may be wanted.

As with Scope Domain settings, the concept of Domain Substitution in general should not be 'overused'. Otherwise, things might get complicated.

The rules are set and removed with method Lox::SetDomainSubstitutionRule , which accepts two parameters:

  • domainPath :
    The domain path (substring) that is to be searched and replaced. For the search, a simple 'wildcard' mechanism is applied: It is allowed to use character '*' at the beginning and/or the end of the search string. As a result, four 'types' of rules can be created
    • Exact match (no * given)
    • Prefix match (* at the end of domainPath )
    • Suffix match (* at the start of domainPath )
    • Substring match (* at both, start and the end of domainPath )
  • replacement :
    This is the string that replaces the string in domainPath when the rule matches. If this is empty (or nulled), the rule is removed from the Lox.

If for any reason, all rules should be deleted at once, invoke method Lox::SetDomainSubstitutionRule with providing a nulled or empty string for domainPath . There is no explicit other method for clearing the rules.

15.2 How Class Lox Processes the Rules

Rules for Domain Substitution are applied after any other evaluation of the resulting domain is performed. The rules are applied in the order of their definition. Therefore, rules that are passed from configuration files (or command line, etc.) are applied first. Rules might influence each other, hide each oder or even be circular. When setting the rules, ALox does not check for such (sometimes wanted) dependencies of the rules. It might happen, that a rule that was defined earlier than a different one, would be effective only after the second rule was applied. Therefore, ALox repeats the application of all rules as long as at least one of the rules matched. Because of the possibility of having circular dependencies, a maximum amount of ten loops is performed. After that, ALox stops processing Domain Substitution and writes a Log Statement of Verbosity.Error to the internal domain. The error message is only issued once. However the flag is reset when all rules are cleared (by providing a nulled string value for parameter domainPath of method Lox::SetDomainSubstitutionRule .

This amount of loops should be more than enough for all major use cases. Even a third loop is necessary very seldom.

15.3 Substitution Rules and External Configuration

15.3.1 Using External Configuration

Setting Domain Substitution Rules 'from within the source code' is easy and straightforward. However, when working in a team such changes might not be appreciated by other team members. In this and other situations it is advisable to retrieve Domain Substitution Rules from external configuration data, command-line parameters or other sources at runtime.

For Domain Substitution Rules and also other configuration data, ALox leverages the configuration facility provided by sibling ALib module ALib Configuration

Note
  • For general information on ALox configuration variables consult ALox Configuration Variables.
  • For information on how to pass configuration data from custom sources to ALox , refer to the Programmer's Manual of module ALib Configuration .
  • Furthermore, previous chapter 13 External Verbosity Configuration gives a great introduction in external configuration techniques, including a source code sample that can be quickly copied to your own code.

The constructor of class Lox tries to read a configuration variable named ALOX_LOXNAME_DOMAIN_SUBSTITUTION. When setting the variable, the substring 'LOXNAME' needs to be replaced by the name of the Lox in question. The variable itself may contain a list of Domain Substitution Rules separated by semicolons ';'. Each rule consists of two strings which correspond to the two parameters of method Lox::SetDomainSubstitutionRule . These two strings are to be separated by '->'.

As an example, an INI-file of an application might have the following sub-section:

[ALOX]
MYLOX_DOMAIN_SUBSTITUTION= /A_DOM -> /BETTER_NAME ; \
/UI -> /LIBS/UI

With variable ALOX_MYLOX_DOMAIN_SUBSTITUTION set in the INI-file, a Lox named 'MYLOX' would receive the two rules specified.

15.3.2 Preventing The Use Of External Configuration

Sometimes it might be wanted that Domain Substitution Rules are not manipulable externally. A reason could be that by the nature of these rules, unauthorized users are enabled to change the Log Domains and hence also to change their Verbosity, which otherwise can be prevented for protection (see 13 External Verbosity Configuration).

To keep things simple, ALox does not provide an explicit interface to prevent reading the rules from the configuration, because, the fast way out is to delete all rules after the creation of the lox:

// Note: the name will be converted to upper case for searching configuration settings
Lox myLox( "MyLox" );
// clear rules which eventually just got read from external configuration
myLox.SetDomainSubstitutionRule( null, null );

15.4 Final Remarks

Domain Substitution rules are always applied to all Loggers within a Lox.

As noted already, Domain Substitution should be used with care and in case of doubt, thought well. Different alternatives to setting a Domain Substitution exist. For example, to manipulate the domain of methods executed within a library, a thread-related Scope Domain setting may be set prior to invoking library methods.

Prefix Logables that have been set for a domain that gets substituted, of-course do not apply to the substitution domain. In contrast, a Log Statement whose resulting domain gets substituted works just as it was using the new domain within its invocation and Scope Domain setting. Therefore, Domain Substitution settings - if at all - should be made at the beginning of a processes life-cycle and then should not be altered. In this case, also Prefix Logables will be 'transferred' to the substitution domain, providing that the commands that are setting them use relative domain paths.

Domain Substitution may not be used to substitute Log Domains of the standard domain tree with those of the internal tree or vice versa.

16 Colorful Loggers

16.1 Technical Prerequisites

To explain the use of colorful log output in ALox , it is best to elaborate roughly on some details of the ALox software architecture.

While ALox in principle is designed to process Logables of arbitrary type, the most widely spread use case is logging textual information. For this reason, abstract class detail::Logger is specialized by class detail::textlogger::TextLogger (still abstract). Besides providing basic features useful for textual log output, for example generating human readable meta information of Log Statements, class TextLogger introduces a set of 'escape sequences'. These sequences are gathered in class ESC . The ESC-sequences are quite similar to those known from ANSI terminals. They provide settings of foreground and background colors and font styles. The ESC-sequences may occur in the (string type) Logables and as well in field MetaInfo::Format .

Here is a quick sample:

Log_Info( "The result is: ", ESC::RED, 42 )

Now, if a output 'drain' does not support the modification of colors (e.g. when logging into a text file), these codes need to be removed before the textual log message is written. This task is done by class PlainTextLogger which in turn specializes TextLogger and which also is still abstract. The following diagram visualizes what was said so far:

The result of this design should not need much explanation: Logger types that do not support colors are derived from PlainTextLogger, while those that support colors are derived from TextLogger:

16.2 Colorful Logging with ALox

The takeaways from this short technical spotlight are now fairly obvious:

  • The Logables of Log Statements as well as Prefix Logables may contain ESC-sequences.
  • A user of ALox does not need to care about whether Loggers are used that support colorized output (or similar ESC-sequences) or not. They are automatically removed if not supported.
  • With the introduction of own, ALox -specific escape codes, software that uses ALox becomes independent from the underlying, platform-specific sequences. For example, ALox is not relying on ANSI color codes, which are not supported by colorful Windows consoles. Instead, on each platform, dedicated Loggers will perform the translation of ALox codes to platform-specific ones.
  • A developer of a custom Logger that should not be enabled to support ESC-sequences, simply can derive from class PlainTextLogger which finds and removes ESC-sequences before the messages are passed to the custom type. (For the creation of custom loggers, see also chapter 17 Loggers and Implementing Custom Types).

17 Loggers and Implementing Custom Types

17.1 Class Logger

ALox is designed to support different log streams. A log stream is a destination for the Logables and can be the IDE console, the terminal console, a file, a web service or anything that accepts data streams. Abstract class detail::Logger represents such a log stream. While some specializations of the class are provided with ALox , custom specializations may log information to any 'data drain' in any format you like.

Abstract class detail::Logger has a very lean interface, in fact it is basically just one method, which in specialized classes needs to be implemented to execute a log. The data to log is provided as an instance of class Boxes , which is a vector holding elements of type Box , hence arbitrary objects.

Note
Programmers that seek to implement a custom logger for ALox for C++ should be familiar with ALib module ALib Boxing . You could say that class Box is like "std::any on steroids".

This allows to create Loggers that take any type of (binary) data and use the data for writing a log entry in any custom way.

While all ALox code tries to be short and simple, class Logger is particularly simple! So, let us quickly walk through the class by looking at its members.

17.1.1 The fields of class Logger

There are just a few fields in class detail::Logger .

17.1.1.1 Name and TypeName

class Logger has two members, Logger::Name and Logger::TypeName that can be read using Logger::GetName and Logger::GetTypeName .

Field name is defined by the user and provided with the constructor. Field typeName is 'hard-coded' by each derived classes' constructor. If the user of a Logger does not provide a name in the constructor, then field name defaults to the hard-coded typeName. Both fields are not used internally but only provided to be used by classes managing multiple Loggers (which generally is class Lox ).

If multiple Loggers are attached to a Lox, they need to have unique Names.

17.1.1.2 TimeOfCreation and TimeOfLastLog

These are two time stamps that contain the time of the creation of the detail::Logger (or the time this timestamp was reset to) and the time of the last log. These two members are normally used to calculate the elapsed time (the cumulated time an application is running) and the time difference between two log calls. The latter is especially interesting for log outputs on a debug console, as it allows to get a first quick impression about your software's performance, lock states, bottlenecks, etc.

17.1.1.3 CntLogs

This is a simple counter of the number of logs processed so far. Feel free to reset it as you like, it is not used anywhere internally, other than as an option to output with each log line.

17.1.2 Methods of class Logger

Besides the protected constructor, which just initializes some default values for the Loggers' fields, abstract method Logger::Log is the most important.

Derived classes only need to implement this abstract method with code that is executing the Log Statement. When the method is invoked, class Lox already performed various checks, including that the Verbosity justifies the execution.

17.1.3 Implementing a Custom Logger

As an experienced programmer after reading the previous sections, it is certainly fully obvious to you which steps need to be taken to create your own variation of class detail::Logger that directs your Logables to a certain data drain in a specific format. Nevertheless, let us quickly name these steps explicitly:

  • Create your own class derived from class Logger.
  • Add a constructor that takes a user-defined name and pass this name along with a hard-coded string representing the type of your Logger, to the base constructor.
  • Implement the abstract method Log by converting and streaming the given objects into a custom data 'drain'.
  • In the main code entity add an instance of your custom Logger type to the Lox of your choice.

But before you now go ahead and implement your own Logger type class, you should first continue reading through this chapter.

17.2 Abstract class TextLogger

When you think about debug log output you think about logging textual messages that get displayed on your debug console. We think the use of a logging ecosystem for this quite simple purpose is advisable as soon as a software project bigger than two pages of code!

While ALox wants to be a perfect tool for quick, easy and comfortable debug logging, the goals of ALox go beyond that. This is why ALox logging interface methods are not restricted to string types, but accept any object to be passed to any derived Logger type.

It is very important to understand this. The result is twofold:

  • For textual (e.g. debug) logging, any object that is logged needs to get converted into a textual representation (a human readable format).
  • Custom Logger types are enabled to log data of an application in a very custom way, as these Loggers get the original object passed.

Class detail::textlogger::TextLogger , which is described in this chapter, is exactly about the first thing: Log any object that is passed to it as a textual representation, hence into a character stream. All of the provided ALox Loggers that produce text output, derive from this base class instead of deriving directly from Logger. Among these classes are loggers::ConsoleLogger , loggers::MemoryLogger and loggers::AnsiLogger . Hence, the class diagram above is a little simplified. It rather looks like this:

Note
  • Even this diagram is simplified. Refer to the language specific reference documentation of class detail::textlogger::TextLogger to get the full picture.
  • Of-course you can also derive your own Logger types without using class TextLogger and still do pure textual logging.

17.2.1 Helper classes for TextLogger

Class detail::textlogger::TextLogger contains two helper classes as public fields. The advantage of using helpers is that they can be replaced at runtime by your own, tailored versions of these helpers and this way you can change the default behavior of existing Logger types, without deriving new ones.

The helper classes are described in the following two paragraphs.

17.2.1.1 Class MetaInfo

Class textlogger::MetaInfo is used by class textlogger::TextLogger to assemble the meta information of each log line, which incorporates things like date and time, thread information, Verbosity and Log Domain. MetaInfo provides a public format string that defines the start (prefix) of each log line. A sample of such format string is:

"%SF:%SL:%A3%SM %A3[%TC +%TL][%tN]%V[%D]%A1#%#: "

The format string contains variables, marked by a leading '%' sign. The set of these format variables available are:

Variable
Description
SP
The full path of the source file.
Sp
The trimmed path of the source file.
SF
The callers' source file name.
Sf
The callers' source file name without extension.
SL
The line number in the callers' source file.
SM
The callers' method name.
TD
The date the log call was invoked.
TT
Time of day the log call was invoked.
TC
Time elapsed since the Logger was created or its timer was reset.
TL
Time elapsed since the last log call. Note: These time values do not sum up correctly. This is not only because of rounding errors, but also because the time between the "end" of the last log call and the "beginning" of this log call is measured. This has the advantage that a quite accurate value of "elapsed time since the last log call" is displayed and hence a very good performance indicator is provided.
tN
Thread name
tI
Thread ID.
V
The Verbosity. For the display of the different values, MetaInfo exposes four public fields containing string definitions.
D
The Log Domains' full path.
#
The log call counter (like a line counter, but counting multi-line log output as one).
An
An auto-adjusted tabulator. This grows whenever it needs to grow, but never shrinks. The mandatory number n (a character between 0 and 9) specifies how much extra space is added when tab is adjusted. This is useful to achieve very clean column formatting.
LG
The name of the Logger. This might be useful if multiple loggers write to the same output stream (e.g. the console).
LX
The name of the Lox.
P
The name of the process / application.
Note
The available format variables vary slightly across programming languages and platforms supported by ALox . The details are documented in the corresponding class reference of MetaInfo::Format .
Note
Class TextLogger prepends the meta information to the decoded Logables and therefore, the message itself is not provided as a formatted string variable. This restriction (that the meta information is always prepended) is a result of the otherwise smart and helpful multi-line features of TextLogger (see below).

Changing the format string MetaInfo::Format provides an easy way to change the look of your log output. For example, if you are not interested in thread information, just remove the "[%tN] " part from the original string.

If you want to modify the output of a certain variable or if you want to add your own variables, you can derive your on implementation of MetaInfo and override the virtual method MetaInfo::processVariable . Within the implementation, just fetch your own variables and/or modify existing and call the original method for the rest that you do not want to modify.

Finally, if you really want to customize the logging of meta information in the class TextLogger completely and maybe do not want to even rely on a format string, then feel free to derive your on implementation of MetaInfo and override the virtual method MetaInfo::Write .

17.2.1.2 Class ObjectConverter

Class ObjectConverter is used by class textlogger::TextLogger to convert the Logables that get passed by the user through the Log Statements to the Logger into a string representation. While ObjectConverter is abstract and declares only one simple interface method, the standard implementation used with the built-in loggers is provided with type textlogger::StandardConverter .

This class is still is extremely simple, as it lazily transfers this responsibility to instances of types which are derived from class format::Formatter . This abstract class and corresponding standard implementations are provided with sibling ALib module ALib BaseCamp .

Please consult the ALib documentation of classes

to learn more about how these classes work.

As you see, also here are different "levels" of possibilities of how to change and implement custom functionality in respect to converting the Logables while using class TextLogger:

  • A straight forward approach is to implement interfaces of module ALib Boxing , namely FAppend or FFormat .
  • Own formatters may be implemented and attached to the instance of StandardConverter.
  • Alternatively an own object converter might be implemented.
Note
In C# and Java versions of ALox , the logging of arbitrary complex objects is additionally supported with class LogTools .
This class makes use of the reflection APIs of these languages and creates a nicely formatted output. We are still looking for someone to volunteer and contribute to the C++ version of ALib in respect to using the ABIs of specific compilers to perform similar reflection for debug logging with ALib .

17.2.2 The Multi-Line Features of Class TextLogger

Class textlogger::TextLogger provides a feature to log a message into multiple lines. This is useful for example, if you want to log a string that contains XML text. Instead of one very wide log line, TextLogger is configured by default to separate the text into multiple lines in a very controlled way.

Multi-line output behavior of class TextLogger is configured by the field TextLogger::MultiLineMsgMode .

The following modes are available:

  • 0: Multi-line mode off
    In this mode, the text is not split into multiple lines. However, certain log streams, for example a system console window, will split the msg autonomously if the message contains line end characters (CR, LF or CRLF). Therefore, using this mode, it is recommended to set the fields TextLogger::MultiLineDelimiter and TextLogger::MultiLineDelimiterRepl in a way that your log file does not get cluttered.
  • 1: Multi-line mode with all meta information repeated in each log line
  • 2: Multi-line mode with blanked meta information starting with second log line.
    This is the default, because this setting makes it easy to visually recognize, that a log is continued in the next line and hence it is the best for debug window purposes!
  • 3: Multi-line mode which writes a (configurable) headline message in the first log line.
    All text lines of the multi-line text is logged starting in column zero.
  • 4: Like multi-line mode 3, but without writing the headline.

17.2.3 Locking the Standard Output Streams

Class textlogger::TextLogger optionally uses a facility of ALib to avoid concurrent access to the standard output stream and standard error stream available to most applications.

This feature is described in the reference documentation of singleton object SmartLock::StdOutputStreams found with module ALib Threads . To enable the use of this locker for a derived custom type, all that has to be done is providing value true for parameter usesStdStreams of the protected constructor of class TextLogger.

If an application writes to those streams in parallel to ALox (e.g. using std::cout), then, to avoid mixing such output with ALox output, such direct writes should be performed only after the this lock was 'acquired' likewise. Also, such application has to register once with this singleton. If done so, together with the Logger(s), the critical number of at least two potential "acquirers" are reached and the SmartLock gets activated.

17.2.4 Recursive Log Statements

ALox supports recursive log calls. Recursion occurs when log statements are executed during the evaluation of the logables of an already invoked log statement. A user might think that recursive log calls are seldom and exotic, but we had to learn that in reality, recursive calls unfortunately might occur quite quickly.

To allow and properly treat recursion, each class and method involved in the execution of a log statement has to be prepared for it. The execution must be free of dependencies to member variables or such members need to be created per recursion.

Hence, not only class class Lox needs to support recursion, but also the logger classes themselves.

Class textlogger::TextLogger and its utility class textlogger::StandardConverter are well prepared and as long as custom loggers are built on those, recursion should not be a problem. This is because abstract method TextLogger::logText was "protected" of recursion and therefore will not be invoked recursively.

When implementing own variants of class ObjectConverter or otherwise a "deeper" use of provided classes is done, the possibility of recursion of log calls has to be taken into account. We recommend a look at the built-in classes' source code to have an idea what needs to be done in respect to field of concern.

17.3 Logger implementations provided by ALox

While the abstract classes Logger, TextLogger and PlainTextLogger are located in the namespaces alib::lox::detail, and alib::lox::detail::textlogger, you can checkout which 'ready to use' Logger implementations are available today for your preferred language version of ALox , by referring to the reference documentation of namespace alib::lox::loggers.

For convenience, method Lox::CreateConsoleLogger is provided. This method chooses an appropriate implementation of class Logger suitable for human readable log output. The Logger chosen depends on the platform and configuration settings.

For debug logging, method Log::AddDebugLogger is provided. This may even choose and attach more than one Logger, depending on language, platform and IDE.

A noteworthy, built-in specialization of Logger is found with class MemoryLogger It uses an internal character buffer of type AString and just appends each log entry to this buffer, separated by a new line sequence.

As MemoryLogger does not log to the console or any other slow thing, it is extremely fast. The latest record was over on million log entries per second in a single-thread! (Achieved on Intel(R) Haswell Core(TM) i7 CPU @4.0GHz, using ALox for C++, doing release logging.)

This gives an indication that the ALox ecosystem, in combination with its MemoryLogger is extremely useful in performance critical sections of your code. If you would do 1000 log entries per second, the performance loss for your application would only be around 0.1%.

17.4 Summary and outlook

The following summarizes the takeaways of this chapter:

  • We learned about abstract class Logger and its simple structure and few fields.
  • Class TextLogger was introduced and explained in detail. We learned that TextLogger aims to be the abstract parent class of all Logger types that are supposed to convert Logables into a human readable format.
  • We saw how the output format of class TextLogger can be manipulated without introducing new code and how custom logging of custom types may be achieved, without extending the given loggers themselves.
  • Furthermore, it was explained that custom loggers that share the console of a process, should use SmartLock::StdOutputStreams to protect the console against racing conditions.
  • Then we briefly looked at the currently existing Logger types in the ALox ecosystem.

If you developed an interesting Logger, like one that

  • is supporting a specific IDE with 'clickable' log lines,
  • is sending Emails,
  • is reporting to an analytics/metrics server or
  • is logging into the Linux journal or Windows OS event log,

then please do not hesitate to propose the code to us as an extension of the open source project ALox !

Appendix A: Configuration Variables

ALox defines several configuration variables, which are read from configuration files (e.g. INI-files), environment variables or the command line interface. Their use are documented in various places. A reader might consult:

  • For general information on ALox configuration variables consult ALox Configuration Variables.
  • For information on how to pass configuration data from custom sources to ALox , refer to the Programmer's Manual of module ALib Configuration .
  • Furthermore, previous chapter 13 External Verbosity Configuration gives a great introduction to external configuration techniques, including a source code sample that can be quickly copied to your own code.

The configuration variables (and their documentation) are:

Appendix B: Auto-Configuration and Orthogonality

B.1 Introduction

ALox addresses two fundamental 'use cases', namely debug logging< and release logging. While technically both are similar, from a conceptual perspective the two are very different things.

Looking into the head of a software developer that thinks about debug logging, you will find the following thoughts:

  • "I will quickly write some output routines and if not needed comment them out or just delete them."
  • "Don't bother me, I am not willing to read a user manual for generating simple debug output."
  • "My company will not pay me for writing nice debug log output, so I do not care likewise."
  • "I have a debugger and comment lines in my code, so I do not urgently need a logging library."
  • "I don't care too much if logging misbehaves or even crashes, this happens only in debug-versions of my software".

⇒ We understand that a logging library has to be easy!

Now release logging:

  • "I have to obey legal rules, logging is a key component of my server"
  • "Collecting usage metrics and detecting failures in the field is a key to success of my app"
  • "Using any external library needs deep and thorough understanding of this. I am paid for making sure this works."
  • "More features of a logging library is always better than less."

⇒ We understand that a logging library has to be feature rich and reliable!

This discrepancy easily justifies the use of two different libraries for both use cases. ALox takes a different approach: it tries to match the needs of both worlds. There are two fundamental design rules in ALox that lead to a solution:

1. Orthogonality of ALox and its API.
Orthogonality here means that features that are not used, are not visible when using ALox . This is reached by optional parameters with reasonable default values, many overloaded variants of methods and by taking internal prerequisites when ALox 'detects' that a feature is not used.

2. Auto-configuration.
This means not only that missing configuration is defaulted, but also that ALox is not pedantic but designed rather non-intrusive. For example, logging into a 'new' Log Domain does not need to anyhow 'register' or 'create' such domain. Many things in ALox just happen on the fly.

Most of ALox auto-configuration features apply to debug logging only and are implemented by the dedicated, auto-created Lox instance used for debug logging and also by the dedicated API interface used for debug logging (which enables the automatic pruning of debug-logging from release code).

But it has a little drawback: Hiding features to make an API simple reduces the understanding of the API - and this is what a user of release-logging requires. And this is the goal of this appendix chapter: We try to collect, list and name concepts of ALox that otherwise might be a little hidden for good reasons.

Note
The list might not be too well sorted and certain points may seem to be off-topic. Please excuse. Many of the things that happen 'automatically' or 'on the fly' can be observed by activating the internal logging as described in 11 Internal Logging.

B.2 Initialization, Lox and Logger Instances

B.2.1 ALib/ALox Bootstrapping

Bootstrapping ALox is a matter of bootstrapping the ALib C++ Library , because ALox is just one out of several modules which all needs to be duely initialized and shut down. All about this process is documented with chapter 4. Bootstrapping And Shutting Down ALib of the Programmer's Manual of ALib .

As long as no major configuration changes are planned to be done, the standard bootstrapping and shutdown is just a matter of invoking one function. Thus, the minimum ALox "hello world" sample looks like this:

#include "alib/alox.hpp"
int main( int argc, const char** argv)
{
// bootstrap ALib
alib::ArgC = argc;
alib::ArgVN = argv;
// the main program
Log_Info ( "Hello ALox!" )
// alib termination
alib::Shutdown();
return 0;
}
Note
In previous versions, with debug-logging, ALox could start a missing bootstrapping itself, but this feature was removed.

With this standard bootstrapping, reading ALox variables given with command line options or environment variables is already in place!

In chapter 13.5 Letting ALox Write the Variable, a quick sample of how to slightly customize bootstrapping is given, which allows to write all defaulted ALox variables into a custom INI-file.

B.2.2 The Lox Singleton for Debug-Logging

For debug logging, a pre-configured instance of class Lox is created and used. More information on this is given in chapter 10.1 Debug Logging.

B.2.3 Adding and Removing Loggers to a Lox

While method Lox::RemoveLogger is available for removing a Logger from a Lox instance, there is no method for explicitly adding one. Instead, the method that allows to control the Verbosity of a Log Domain and its sub-domains for a Logger, Lox::SetVerbosity adds a Logger in the moment the one provided is not known to the Lox, yet. Consequently, the very first time a Verbosity of a Logger is set, the overloaded version of Lox::SetVerbosity which takes a reference to the Logger as parameter has to be used. In subsequent invocations, alternatively, the name of the Logger can be provided.

B.2.4 Logger Names

Each Logger attached to a Lox has to have a unique name. Nevertheless, Loggers can be constructed without explicitly providing a name. In this case, a default name which is different with each derived type of Logger is used. This way, many standard use cases do not require the user to think about the name of Loggers. For example if a Logger that logs to the console should be used in parallel to one that logs into a text file, both Loggers will have different names.

However, if for example two different loggers of type loggers::TextFileLogger are to be added to the same Lox, then with their creation, a dedicated, distinguishable name has to be provided.

In addition, when reading Verbosity settings from external configuration (as described in 13 External Verbosity Configuration), it is advisable to explicitly name Loggers, so that external configuration data stays "compatible" in the moment a different Logger type is used for a certain task. On the other hand: this is not necessary for the standard debug Logger created automatically or explicitly by calling Log::AddDebugLogger (see more info on this below), because such Loggers are always named "DEBUG_LOGGER".

B.2.5 Using a Logger with Multiple Lox Instances

If a Logger is set with multiple Lox instances, by internally using ALib class SmartLock , it is automatically protected against concurrent access in multi-threaded applications.

B.2.6 Console Loggers and Debug Logger

Method Lox::CreateConsoleLogger creates an appropriate type of Logger depending on the operating system, IDE used (if in IDE), implementation language, configuration variable, etc. The goal get a maximum logging experience, e.g. colorful logging if the console supports colors.

This method can be used to create a Logger whenever a console logger is needed (also for release-logging of-course).

Furthermore, method Log::AddDebugLogger uses this method to attach a Logger to the Lox singleton used for debug-logging. In addition, if running in an IDE, ALox tries to detect the IDE and optionally (if available) creates a second, IDE specific Logger. See reference documentation of the methods linked above for more details.
For both Loggers, root domain '/' is set to Verbosity.Verbose. In addition, the Verbosity for internal ALox messages is set to Verbosity.Warning. (More information on internal ALox messages is found in 11 Internal Logging.)

Finally, method Lox::AddDebugLogger is automatically invoked internally in the moment that the Lox singleton used for debug-logging is used and no other Logger was attached, yet.

Note
The exclamation to this rule is when a Logger was added to the internal domain tree only. In this case, the normal standard domain tree is still free of loggers and therefore, a debug logger is automatically attached.

B.3 Source Path Trimming

If no Source Path Trim Rules are given a default rule is automatically detected and registered.
If Source Path Trim Rules are given, path separators '/' and '\' are corrected to suit the target platform of compilation.

For more information see chapter 14 Trimming Source File Paths and Clickable IDE Output.

B.4 Log Domains and Verbosity

Log Domains are created on the fly as needed (used). Log Domain, once created, are never deleted and a user does not need to care about their management. New, unknown Log Domains inherit their Verbosity from their parent.

Furthermore, when obtaining Verbosity settings from external configuration, a natural prioritization is applied: First INI_file settings, which are overwritten by environment variable settings, which are overwritten by command-line parameters. All of them are overwriting settings made invocations of Lox::SetVerbosity in the source code - as long as optional parameter priority is not given.
More information on this is found in 13 External Verbosity Configuration.

B.5 Logging Text and Objects

B.5.1 Arbitrary Logables

ALox API transparently hides the fact that it is made for logging arbitrary objects - not just text messages. Features for logging text is just an application build on the concept of logging abstract 'Logables'. This is not only true for the Logables provided in Log Statements, but also for Prefix Logables. More information about logging arbitrary objects is found in 17 Loggers and Implementing Custom Types.

B.5.2 Namespace 'detail::textlogger'

When logging text, classes found in namespace alib::lox::detail::textlogger are doing quite a bit of things in the background that are not obvious to the user. For example:

  • ALox ESC -codes are removed from textual Logables if a Logger does not support them. (For more information see 16 Colorful Loggers.)
  • Classes derived from TextLogger by default recognize multi-line text in Logables (in standard line ending, Windows line ending or mixed mode) and format multi-line text accordingly.
  • With the help of utility class AutoSizes , class TextLogger formats the log output into columns that grow over time. In the moment a write-enabled configuration plug-in is attached (e.g. an INI-file), such sizes are restored when a process is re-launched. Also automatic shrinking happens. This is done when next run of a process detects that the tabulator and field sizes of a previous session it had used, have been higher than needed after all! In the next run, the smaller sizes are restored. Reading and writing of the values from and to the configuration system is performed in method TextLogger::AcknowledgeLox which is invoked when a Logger is added, respectively removed from a Lox.
  • The standard output streams (e.g. in C++ 'std::cout' and 'std::cerr') are locked against concurrent write operations as soon as more than one Logger is active. This avoids clutter that is created by intermixing log statements coming from different threads. It is also possible to synchronize such output with other entities (non-ALox ) that write to these streams. In this case, locking takes place already when only one Logger which uses the streams is established. See global variable SmartLock::StdOutputStreams defined with module ALib Threads , for more information on how to do that.

B.5.3 C++ Strings

In ALox for C++, logging strings leverages the string facilities of underlying ALib, which allows to transparently pass just any sort of string object to Log Statements, including references or pointers to almost any custom string type - without any sort of explicit prior conversion! This is achieved using 'template meta programming'. Information on how to adopt custom string types is found in the Programmer's Manual of module ALib Strings .

Appendix C: Preprocessor Symbols and Macros

C.1 ALox Compiler Symbols

Compiler symbols, including those that control features of ALox , are documented with the reference documentation of ALib C++ Library .

C.2 ALox Macros

ALox for C++ makes extensive use of preprocessor macros. Those mainly achieve:

  • Pruning of log code
    Using the logging macros provided, allows to selectively remove all debug logging or release logging invocations from the code. (see What is Pruning?).
  • Adding scope information
    ALox uses built-in standard preprocessor symbols like "__FILE__" and "__LINE__" to provide information about the position of logging statement within the source. Especially for release logging, one problematic fact is that such logging source information could disclose internals if the release software unit was reverse engineered. Therefore, the use of source information is configurable and disabled for release logging by default.
    Also, hiding the use of the built-in symbols by using dedicated ALox macros, reliefs the source code from a lot of clutter and overhead.

The macros and their documentation are divided into two different groups:

  • ALox Macros For Debug Logging
  • ALox Macros For Release Logging

Documentation for those are found in the corresponding subsections of the reference documentation of ALib C++ Library .

While all macros for debug logging start with the prefix Log_, those for release logging are using the prefix Lox_. Otherwise, their naming and parameters are the same.
The rest of the macro names in most cases corresponds to methods found in class Lox .

Both macro sets include macro Log_Prune, respectively Lox_Prune. All that these two macros do is pruning the code that is passed to them in the moment that debug logging (respectively release logging) is supposed to be pruned. This allows to place code for setting up ALox , for example the creation of custom loggers, which is pruned in the moment the according log-statements are.

Note
The provision of two sets of macros, one for debug- and one for release logging, imposes one of the differences to other ALox language implementations (ALox for C# and ALox for Java), as this allows to prune release logging independent from debug logging.
It might be important to understand the options: When release logging is only disabled, the code for release logging still remains in the binaries, which has an impact on their size and an (probably marginal) impact on their speed. In addition it is possibility to reverse engineer such binaries and learn about internals of the software (should someone do so).
In this respect, ALox for C++ is the most flexible implementation of ALox as two different release builds are possible: such with release logging incorporated and such with release logging pruned.

For an introduction of how to use these macros, work yourself through the ALox - Tutorial.

Appendix D: IDE Setup for ALox for C++

D.1 Introduction

The C++ version of ALox is distributed as an ALib Camp of ALib C++ Library .

Note
This is contrast to the currently discontinued versions of ALox for C# and ALox for Java, which are located in a separated repository.

Documentation for compiling ALib is given with chapter 5. Building The Library of the ALib Programmer's Manual.

D.2. Using ALox with Different IDEs

In the following sections, the use of ALox with different IDEs is discussed. Besides specifics in respect to project setup, an overview about how ALox clickable log output is supported - and achieved! "Achieving" here means most of all to configure the output format in a way that the IDE "understands" the source file and line number information and links them to the editor and source.

To change the format of the log output, field Format of object TextLogger::MetaInfo has to be modified. This can be done either programatically, or, if available in the external configuration of an application. If you are new to ALox , the easiest way to play around with different log output formats, is to compile and run the sample project. This will generate an INI-file (in your personal OS-specific configuration folder) and within this file you will find an entry for configuration variable ALOX_LOGGERNAME_FORMAT. You can edit this, and re-run the sample. The log output will change. From here, you can start your own investigations about how to convince your IDE to accept the log statements.

In the configuration file, there are also other configuration variables which can get useful to adjust the log output to a certain IDE. If your IDE is not listed in the following paragraphs, then still reading these might help you in understanding how such support is possible.

D.2.1. QT Creator

QT Creator can be used in two different "modes", with CMake files and with QMake files. Unfortunately, in respect to clickable log-output there is a tremendous difference in the behavior of QT Creator.

Note
When using ALib classes in your project or you want to debug into or extend ALox , please note the special support of ALox for QT Creator pretty printers. Update: 02/2024: These were completely outdated and have been removed. We completely rely on JetBrains IDEs now.

D.2.1.1 Using QT Creator with QMake

In respect to log output file name recognition, we have made the following observations:

  • When using QTCreator with QMake (!), the IDE recognizes source code information if the line starts with three spaces and the string "Loc:".
  • The source information is then to be provided with square brackets.
  • no other square brackets must occur in the output line
  • Any color information (ANSI color codes) which otherwise are recognized by QT Creator are removed. You can see colors flashing up shortly sometimes and then they disappear. The reason is obviously that QT Creator parses the output window and colorizes all source links it detects in blue - and removing all other color code!
  • The rules how source paths are recognized by QTCreator is not known to us. Often, trimmed source file paths are sufficient (see chapter 14 Trimming Source File Paths and Clickable IDE Output) but sometimes the full path has to be given. Especially if libraries that use ALox are involved, the trimmed path is sometimes not enough. In this case, a source path trim rule might be created that turns relative source file paths (provided by QMake to the C++ compiler) into absolute ones.

From these observations, the following advice can be given:

Using external configuration this means to set three variables. For example, an INI-file might contain the following entries:

CONSOLE_TYPE=                      plain
DEBUG_LOGGER_FORMAT=              "   Loc: [%SP/%SF(%SL)]:%A3%SM %A3{%TC +%TL}{%tN}{%D}%A1#%#: ",    \
                                   {ERR},                                              \
                                   {WRN},                                              \
                                  "     ",                                             \
                                   {***}
DEBUG_LOGGER_REPLACEMENTS=        "[", "{", "]", "}"

D.2.1.2 Using QT Creator with CMake

QT Creator has a built in CMake support which works really well with the CMake files provided with the project (as of the time of this writing).

To use the pre-build CMake files of ALox with QT Creator, open menu File of QT Creator and choose Open File or Project... and select the file:

    ALIB_LIB_PATH/build/cmake/samples/ALox/CMakeLists.txt

Follow the project setup as QT Creator documentation suggests. When the CMake run dialog appears, for debug builds, no arguments need to be passed. For release builds, provide

    -DCMAKE_BUILD_TYPE=Release

as the CMake Arguments

The ALox sample project is now ready to be build in QT Creator

In respect to log output file name recognition, there is some bad news now: When used with CMake projects, QT Creator does not support clickable log lines. At least, we have not found out how! Therefore, we have no recommendation about how to change the log output in respect to make such output clickable.

The output panes of QT Creator support colorful (ANSI) logging and have a light background. Therefore, we recommend to set the following two configuration variables:

CONSOLE_TYPE=            ansi
CONSOLE_LIGHT_COLORS=    background

D.2.2. Using ALox with Visual Studio

When you are new to ALib and you just want to play around with it a bit, the easiest thing is to open the Visual studio solution found in

    ALIB_LIB_PATH/build/ide.vstudio/Solution/ALib.sln

You are set! For example you could walk through the ALox - Tutorial by adding the tutorial code to the main() function of the project ALox-CPP-Samples.

ALox provides a Visual Studio project that compiles ALox into a DLL. The projects is located in:

    ALIB_LIB_PATH/build/ide.vstudio/ALib-DLL/

Add this project to your solution and set a build dependency from your project to the just added ALib-DLL* project. Furthermore, the ALib-DLL project has to be added as a reference to your project. Finally, the ALib include directory has to be added to your project.

Note
When using ALox in a Microsoft Windows software, it may be important to include windows.h prior to any ALox header file. The reason is, that ALib includes windows.h unless it was included already. When the ALib library includes windows.h, it uses some defines like WIN32_LEAN_AND_MEAN or NOGDI to minimize the impact of that huge header file. If windows.h is included prior to including ALox (AWorx library), it is up to the embedding application which level of windows functionality is needed.

Clickable Log Output with Visual Studio:
As far as we know, clickable log output is only possible to have when launching a project in debug mode. In this case, ALox automatically adds a second logger of type VStudioLogger. (This is the default behavior by default, implemented with method Log::AddDebugLogger.)

This logger does everything right to have clickable log lines in Visual Studio. No adjustments need to be done.

D.2.3 Using ALox with JetBrains CLion

CLions' build system relies on CMake. Pre-configured project folders are provides with

ALIB_LIB_PATH/build/ide.clion/samples/ALox
ALIB_LIB_PATH/build/ide.clion/unittests

The CMake files within these directories are just including the corresponding CMake files found in

ALIB_LIB_PATH/build/cmake/samples/ALox
ALIB_LIB_PATH/build/cmake/unittests

The advantage of this approach is that the "generic" folders found in folder cmake are kept free from CLion project files.

Therefore, CLion users should be ready to compile by just opening these project folders.

For debugging ALib and ALox code, you might want to install the Pretty Printers for GNU Debugger.

Clickable Log Output with CLion:

CLion has an intelligent output console that supports ANSI colors and detects links to files automatically. The file name without path is sufficient and this way, the default format string should do fine.

There is one specialty about the ANSI color support: CLion does not support light colors. Therefore, the use of light colors has to be switched off. If this is not done, colors are in most cases not effective.

As a summary, these or similar configuration variable settings are recommended:

[ALOX]
CONSOLE_TYPE=           ansi
CONSOLE_LIGHT_COLORS=   foreground
DEBUG_LOGGER_FORMAT=    %SF:%SL:%A3%SM %A3[%TC +%TL][%tN][%D]%A1#%#: %V,    \
                        \e[31m,                                             \
                        \e[34m,                                             \
                        ,                                                   \
                        \e[33m