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 04 - 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 04 - 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 (C++, C#, Java). The Scopes found here are separated in two sets:
ALox adopts the concept of Scopes from each programming language of its implementation (currently C++, C# and Java). A variable declared in one of these languages belongs to a certain scope, e.g. to the class when it is a member variable.
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 class that the method belongs to.
The language-related Scopes that ALox supports are (from outer to inner):
Apart from Scope.Global, to evaluate the actual Scope of an invocation to class Lox, ALox needs to 'automatically' collect information of the calling entity. The techniques are different in each implementation:
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:
The good news is: This is absolutely OK! The rational for claiming this is:
The following sections describe each of the language-related Scopes in detail.
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.
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: A Lox 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.
This Scope has a different name and meaning in ALox for C++/C# and ALox for Java. Let's start with the Java side of things - well, but, even as a C++ or C# programmer, please continue reading here!
The scope information collected by ALox for each Log Statement incorporates the 'package' name of the class and method that is executing it. Therefore, using method Lox.SetDomain (C++, C#, Java) with Scope PACKAGE in Java instructs ALox to use the given Scope Domain for all Log Statements residing in any class of the package. Now, method Lox.SetDomain optionally offers a parameter packageLevel which defaults to 0
. This parameter is ignored with other Scopes. For Scope.PACKAGE, however it allows to refer to an 'outer package'. If this parameter is set to 1
, ALox searches the last '.' in the package name and cuts this and the rest of the name off. This way, Scope.PACKAGE can be considered to not denoting just a single Scope, but rather a set of nested scopes itself! Consequently, when executing a Log Statement within a method of a class residing in a nested package for which a Scope Domain is set, then, if any of the 'parent' packages of this package has also set a Scope Domain, each of them is applied!
The corresponding concept of Java 'packages', in C++ and C# are 'namespaces'. Unfortunately, ALox is not able to automatically gather information about the namespace that a Log Statement resides in. To achieve a similar functionality in these languages, the way out, is using the path in the file system of the source file that embeds a Log Statement. The prerequisite for this is that all source files are sorted properly into sub-directories according to the namespace they they belong to. For example, a directory tree could look like this:
/home/user/myproject/src/main.cpp /home/user/myproject/src/ui/menu/menu.cpp /home/user/myproject/src/ui/dialogs/about.cpp /home/user/myproject/src/io/sockets/http.cpp /home/user/myproject/src/io/database/mysql.cpp
With a directory structure similar to the one given above, Scope.Path available in ALox for C++/C# works exactly the same as Scope.PACKAGE in Java. A Log Statement which resides in file
/home/user/myproject/src/ui/dialogs/about.cpp
would have path
/home/user/myproject/src/ui/dialogs
as associated Scope.Path. Instead of optional paramter \ packageLevel of method Lox.SetDomain, in C++ and and C# the outer directories can simply be reached by adding an integer value specifying the number of directories to cut, to the enumeration element Scope::Path.
If this is used, for values greater than 0
, the corresponding amount of directories are cut from the end of the source files' path to determine the parent directory that a Scope Domain should be set for. As with Java packages, Scope.Path can be considered being not just a single Scope, but rather a set of nested scopes itself. All Scope Domains set for a path of a file as well as those set for parent directories within this path, are applied - from the inner path to the outer path.
42
would simply be overwritten by a subsequent registration with level 43
and the effect would be nearly similar to using Scope.Global.Like Scopes Path/PACKAGE, this Scope.Filename/CLASS has a different name and meaning in ALox for C++/C# and ALox for Java. But here it is quite simple.
In Java, Scope.CLASS simply refers to all statements used within a class.
Again, in C++/C# ALox is unable to automatically identify a class name that a Log Statement resides in. Furthermore, in C++ a Log Statement may not even reside within a class. Therefore, the corresponding Scope.Filename in those languages refer to the name of the source file that a Log Statement resides in. In the usual case when each classes' code resides within a dedicated source file, the effect of both Scopes are the same.
Files with the same name, but residing in different directories are treated as different files.
In C++, if the file name has an extension (like .cpp, .hpp, .h, .hxx, etc.), such extension is ignored. This way, by setting a Scope Domain of Scope.Filename e.g. from within a '.cpp' file, such setting also applies to Log Statements occurring in the corresponding '.hpp' file. Again, this is only true if both files reside in the same directory!
Scope.Method (in Java Scope.METHOD) comprises all Log Statements which are residing in a certain method. 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.
The programming languages C++, C# and Java, allow 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. 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.
When reading earlier chapter 04 - Log Domains and the later chapters (06 - Lox.Once(), 07 - Prefix Logables and 08 - Log Data (Debug Variables)), you might be surprised, that the only way to use a specific Scope is to do this with an invocation of a corresponding method of class Lox 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 class and method, e.g. in the bootstrap section of an application?
The reason is to avoid ambiguities and misconfigurations. If such possibility existed, even if a method class names were given, the same class and method name, might exist in a library, probably using a different namespace. This makes it obvious that the full 'outer' scope has to be provided. Still, adding the namespace in Java would be easily possible, however in C++/C# we are working with the source files' paths and these are 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 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.
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 (in Java Scope.CLASS) 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 package name (Java) or source folder (C++/C#) could be set from different classes belonging to this package, respectively 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 package.
A snapshot of all current settings can be logged using Lox.State (C++, C#, Java) 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 what the code is doing in respect to Scope Domains. See 11 - Internal Logging for more information about how to enable internal log messages.
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.
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:
Remark: [L] and [T] here indicate language-related and thread-related Scopes.
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 static 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.
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 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:
We learned in section 2.2 Scope.Path (In Java Scope.PACKAGE), that this Scope through the use of optional parameter pathLevel of method Lox.SetDomain (C++, C#, Java) 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.
The same method, Lox.SetDomain (C++, C#, Java) 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 (C++, C#, Java) which accepts the domain path to be removed explicitly as a parameter.
It was discussed in 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 (C++, C#, Java) as well as Lox.RemoveThreadDomain (C++, C#, Java) 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.
When things get more complicated, same as with language related scopes, a snapshot of all current settings can be logged using Lox.State (C++, C#, Java) 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.
We want to summarize the takeaways of this chapter:
Finally we want to express an important thought: The three concepts of ALox, namely
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.