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 for Java/C# are of type class
Object, in ALox for C++ of type aworx::Box. An implementation of abstract class Logger, receives an arbitrary amount of objects of arbitrary types. This corresponds to the fact that the Log Statements also accept arbitrary amounts of arbitrary objects to be logged out. However, the lists may differ: a Logger might receive more objects than those that have been provided with a Log Statement! Those additional objects are 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.
For the first way of setting and removing Prefix Logables method Lox.SetPrefix (C++, C#, Java) is used. The method and its overloaded versions is very similar to method Lox.SetDomain (C++, C#, Java) 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 04 - 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 null
(C++: 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 (C++, C#, Java), which provides a little extra flexibility of maintaining Scope Domains in contrast to maintaining Prefix Logables.
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 Lox.SetPrefix (C++, C#, Java).
The method accepts a Log Domain path which may be absolute (starting with "/"
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.
While the Log Statements accept arbitrary amount of objects (in C++ "Boxes"), the methods to set Prefix Logables have only one parameter. Nevertheless, more than one object can be set! In Java and C# this is done by passing an an array of type Object
[] with the "prefixes" to the corresponding method. In C++ the objects have to be wrapped in an object of class aworx::Boxes, which 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.
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:
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.
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.
When logging textual messages (more precisely: when using Loggers derived from abstract class TextLogger (C++, C#, Java), 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 (C++, C#, Java) which in turn (in its default implementation) passes them to field StandardConverter::FormatterPS (C++, C#, Java). This formatter, has object StandardConverter::FormatterJS (C++, C#, Java) 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.
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:
The output will look similar to this:
UT_dox_manual.cs(347):Log_SetPrefix() [/]: Data File: Opened. UT_dox_manual.cs(350):Log_SetPrefix() [/]: Data File: Read. UT_dox_manual.cs(353):Log_SetPrefix() [/]: Data File: Closed.
A next use case is recursively increasing 'indentation' of the log messages, as demonstrated here:
Note that this sample is using Scope.ThreadOuter. If 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 of a nested namespace (in Java 'package'), 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 ESC (C++, C#, Java). Prefixing those instead of normal strings or spaces, leads to nicely colorized, bold and 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).
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.
The situation with Loggers designed to log arbitrary objects is different. (How to create such custom, application specific Loggers is described in A - 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.
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 (as Java and C# do) 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 Logables 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 it. 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:
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.
This is what this chapter has covered in respect to Prefix Logables:
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 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.