ALox  V. 2402 R. 0
Home ALox for C++ ALox for C# ALox for Java Download
06 - Lox.Once()

1. Introduction

For a better understanding of what is explained in this chapter, it might be advisable to have read chapter 05 - 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 (C++, C#, Java).

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 04 - 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 (C++, C#, Java), 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.

Note
The samples in this chapter showcases ALox for C#. With the other language implementations the code looks very similar. With Java, due to a lack of the language in respect to resolving (quite easily resolvable!) overloading ambiguities, there might be less options to omit parameters and some variants have a slightly different order in the parameters.

The concept of method Log.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", "Directory given in config.ini not found. Using default." );

    By the way: this sample output above indicates a next use case besides the one asking for "was a piece of code reached once". The sample logs a fact that will not change in the future (The configuration file "config.ini" is how it is, its just wrong). The sample indicates, that a default directory was chosen. This will be done all the time the method is used but it is logged out only once.

  • To avoid the definition of even a bigger set of overloaded methods in class Lox (e.g. they could have been named Log.ErrorOnce or Log.WarningOnce), the Verbosity is provided as a parameter and defaults to Verbosity.Info. If a different Verbosity is to be chosen, use:

    Log.Once( "IO", Verbosity.Warning, "I warn you... and I don't repeat myself!" );

    At this point in time, it might be worth to mention, that the counter is increased with each Log.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 );
  • 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 );

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", "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:

public void ReadData()
{
//...
// directory not found
Log.Once( "IO", Verbosity.Warning,
"Directory given in config.ini not found. Using default." );
//...
}
public 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:

public void ReadData()
{
//...
// directory not found
Log.Once( "IO", Verbosity.Warning,
"Directory given in config.ini not found. Using default.",
"INI_DIR_ERROR" );
//...
}
public void WriteData()
{
//...
// directory not found
Log.Once( "IO", Verbosity.Warning,
"Directory given in config.ini not found. Using default.",
"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 Log.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 Log.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 Log.Once statements which are located in an 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.

3. Log.Once and Scopes

Instead of 'grouping' a set of Log.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 (In Java class, in C++/C# source file), the very same that was achieved using Groups, can be achieved using Scopes:

class MyIOManager
{
// static constructor
static MyIOManager()
{
// bind all log statements of this file to domain path 'IO'
Log.SetDomain( "IO", Scope.Filename );
}
public void ReadData()
{
//...
// directory not found
Log.Once( Verbosity.Warning,
"Directory given in config.ini not found. Using default.",
Scope.Filename );
//...
}
public 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 05 - 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 Log.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 and this is when using Scope.ThreadOuter or Scope.ThreadInner. This attaches the Log.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, both variants have their proper use case and both have advantages and disadvantages. Why not combining them?

4. Combining Groups and Scopes

The parameter list of Lox.Once (C++, C#, Java), 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 Log.Once is used.
When parameter scope is omitted with Log.Once, it simply defaults to Scope.Global, which is, as explained in 05 - 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 (In Java Scope.CLASS) 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 (in Java a class)
    • a directory of source files (in Java a package)
    • a parent directory of source files and all sources recursively (in Java an outer package)
  • Log only the first n statements of a group of statements executed by a specific thread.
  • Have an arbitrary number of different Groups of Log.Once statements per Thread.

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

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 object. 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 )
}

In C# and Java versions, a simple Object[] can be created on the fly. For C# this looks as follows:

Log.Once( new Object[] {"One - {} - {}!", "two", 3} );

and the very same in JAVA:

Log.once( new Object[] {"One - {} - {}!", "two", 3} );

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 Log.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 II - ALox 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.

Next chapter: 07 - Prefix Logables
Back to index
Verbosity
Verbosity
Log
lox::Log Log
Box
boxing::Box Box
Log_Once
#define Log_Once(...)
Scope
Scope
Log_Prune
#define Log_Prune(...)
Boxes
boxing::Boxes Boxes