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

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

2. Setting Prefix Logables

2.1 Setting Prefix Logables According to the Scope

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.

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

Note
Alternatively, Prefix Logables bound to a Log Domain can set using configuration variable ALOX_LOXNAME_PREFIXES. Of-course, this allows only string-type-Prefix Logables to be set.
For general information on ALox configuration variables consult I - Configuration Variables.
For information on how to pass configuration data from custom sources to ALox, refer to namespace documentation aworx::lib::config (C++, C#, Java).

2.3 Setting More than one Prefix Logable at a Time

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.

Note
This technique of passing a whole list of objects trough one parameter is found several times with ALox. For example, the various overloaded versions of method Lox.Once (C++, C#, Java) is using the same technique. The following code snippets sample how such object array are created right in the parameter list of the invocation.
C++:
// 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 )
}
C#:
Log.Once( new Object[] {"One - {} - {}!", "two", 3} );
Java:
Log.once( new Object[] {"One - {} - {}!", "two", 3} );

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 (PACKAGE)
  4. Prefix Logable of Scope.Filename (CLASS)
  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.

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.

4.1 Textual Logging

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.

Note
As already explained in this manual and in the documentation of class 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 A - 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_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:

public RecursiveDataType Search( String name )
{
Log.SetPrefix( " ", Scope.ThreadOuter ); // add indent
Log.Info( "Inspecting object: " + Name );
if ( Name.Equals ( name ) )
{
Log.SetPrefix( null, Scope.ThreadOuter ); // remove indent
return this;
}
// recursion
RecursiveDataType returnValue= null;
foreach( RecursiveDataType child in Children )
if( (returnValue= child.Search( name )) != null )
break;
Log.SetPrefix( null, Scope.ThreadOuter ); // remove indent
return returnValue;
}

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.

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

5. ALox for C++ specifics

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:

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

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 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 for 3rd-party libraries:

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


Next chapter: 08 - Log Data (Debug Variables)
Back to index
String
strings::TString< character > String
Log
lox::Log Log
Box
boxing::Box Box
Log_Once
#define Log_Once(...)
Scope
Scope
Log_Prune
#define Log_Prune(...)
Boxes
boxing::Boxes Boxes