Welcome to the ALox for Java tutorial! This tutorial aims to introduce you to ALox as quickly as possible and put you into a position to decide whether ALox is the right logging library for you.
Let us start with the very quick "hello world" sample:
When this method gets invoked, the output should look similar to this
(HelloALox.java:7) sayHello():[00:00.000 +167 µs] [main] [/] (001): Hello ALox!
So, with one line of code we get a Log Statement that incorporates:
[/]
: to be explained later...And: In the release build of your software, this line of code is completely pruned away. Because, as we see later, also in debug versions you can disable log output selectively, with ALox you can keep your debug Log Statements in your code forever!
Let us quickly start a new chapter of this tutorial and go into more details.
The previous chapter was a teaser. We showed the shortest possible sample program which does not 'bootstrap' ALox (aka initialize and configure). This is detected and hence ALox tries to setup itself with reasonable default options.
ALox has some concepts that need a little more time to understand. Before we see more code, we have to take some prerequisites.
ALox is designed to support debug logging (log output that should appear only in the debug version of your software) as well as release logging. In Java such pruning is done using an 'obfuscator' tool and ALox provides ready to use configuration files and a tutorial on how to use one of the best of its kind.
We want you to understand a little of the class design of ALox, while you are reading yourself through this tutorial. Some first general notes:
Most of the methods that we are using here are invoked on class Log instead of class Lox. This class is a 'mirror' class of Lox, a kind of fake class that mimics the interface but internally just calls the original methods on a dedicated Lox singleton which is used for debug logging. Therefore,
Log.info( "Hello ALox" )
means that method info is invoked on the predefined singleton of class Lox which is by default used for debug logging. (Class Log and any reference on it gets pruned with release versions.)
If you want to follow the tutorial samples and play around with them a little, you should either create a tutorial project or right away equip your current project with ALox. For information about how you do that see: IDE Setup for ALox for Java.
To have all packages for this tutorial set up, add the following lines to the start of your source file:
While class Lox is the main interface to ALox logging, abstract class Logger is performing the log. 'Performing' here means that derived instances of this abstract class are responsible for writing the 'Logables' (the message or data that is logged) to a 'drain', for example the console or a text file.
Before we can start logging, we need to create one or more Loggers and attach them to our instance of class Lox.
You need to identify the right place in your application where to do this. A good place is the "main()" method or any other place of your project where some "bootstrapping" of your application takes place. Add the following line:
This tries to identify the best Logger type that fits to your console type. E.g. If the process runs on an ANSI enabled console or a console window on Windows OS, colorful log output is enabled. Sometimes, this method even creates two Loggers: one for the console and one for your IDE output, when ALox is able to detect and support such IDE.
For information about what is currently supported and how (with environment variables, configuration files or command line parameters) this behavior can be changed, see methods Log::addDebugLogger and Lox::createConsoleLogger.
We never interface with a Logger
directly. After it is created and added to the Lox
, we can mostly forget about it. For almost all the rest of this introductory tutorial, all you need to know is that Loggers created with this method, write log messages in textual format e.g. to standard output stream (and/or to the application console). As said above, other Loggers could be added in parallel that use other "drains" to write the output to, e.g. files, a metrics server in the cloud, etc.
Together with the Log Statement of the first introductory sample, your projects code (e.g. in method main
) should look like this:
When you run your project, output similar to this:
(UT_dox_tutorial.java:178) Tut_Hello_ALox():[0.000 +379 µs][UTThread] [/](001): Hello ALox
should appear in your IDE or your console.
If you are using an IDE supported by ALox (e.g. Eclipse), you can double click the log line in the output window. The IDE opens the source and selects the line of code where the log call was invoked. This means, each Log Statement is "clickable" in your IDE and links to its source code line in the editor. This is a tremendous help for developers when debugging their code.
Besides the fact that you can click it, users of ALox are saving even more time. Developers, when using standard "debug print lines", often phrase textual info like:
"attn: dbman.savedata(): oops could not store data....ERROR!"
hence, putting some info about the place in the code, where they have put the debug statements. As you see from the ALox output above, information about the caller of the Log Statement is all included automatically. Advantages are:
Furthermore, as we will see later, the log message type (which seems to be "ERROR!" in the phrase above) is standardized in ALox and does not need to be phrased in your message text.
Besides scope information and message type, you automatically get:
The output format and detail of the log line is configurable in ALox. The line above just shows the default. Details you see can be removed or other details can be added (e.g. the absolute date / time instead of only the relative).
At this point, the tutorial on ALox for C# or ALox for C++ include a section about a "release build" of the current tutorial project. In these languages, such release builds remove all Log Statements from the code. Java does not know release builds. But pruning is still supported and automated in ALox for Java as well (not covered in this tutorial, but documented here: Pruning ALox). The advantages are:
The code above uses the method Lox::info() (embedded in a preprocessor macro) to create the log output. There are three other Versions of that method, together constituting the four 'Verbosities':
Let us see what happens when extending our sample as follows:
If you run your application now (in "Debug" mode), the following output should appear:
(UT_dox_tutorial.java:194) Tut_Verbosity():[0.000 +536 µs][UTThread][ERR][/](001): A severe error happened :-( (UT_dox_tutorial.java:195) Tut_Verbosity():[0.000 +123 µs][UTThread][WRN][/](002): This is a warning :-/ Maybe an error follows? (UT_dox_tutorial.java:196) Tut_Verbosity():[0.000 +139 µs][UTThread] [/](003): Just for your further information! (UT_dox_tutorial.java:197) Tut_Verbosity():[0.000 +123 µs][UTThread][***][/](004): Today, I am in the mood to talk...
The little space in the meta information, right after the thread name [main] fills with text-markers [ERR], [WRN] and [***], giving info about which verbosity a Log Statement defines.
In this sample, we are using just one source file, thread name, etc. When more variety appears, ALox will automatically set tabulators in the meta information of the log output, so that this information is almost always at the same column. This makes it very easy for example to identify those log messages of Verbosity 'ERROR'.
Now, we want to control the Verbosity of the log output. Let's say we just want to see 'Error' and 'Warning' messages, and suppress those of Verbosity 'Info' and 'Verbose'. ALox allows to control this on a 'per Logger' basis using method Lox.setVerbosity. We add the line:
Here, we are explicitly passing the Logger object that ALox creates as a static singleton for debug-logging purposes, with method addDebugLogger(). Instead of passing the reference to the object, we could as well use a Loggers' name. The Logger that method addDebugLogger() creates is named "DEBUG_LOGGER"
. Therefore, the line of code shown above can alternatively stated as:
Our sample now looks like this:
and produces the following output:
(UT_dox_tutorial.java:220) Tut_Verbosity():[0.001 +558 µs][UTThread][ERR][/](001): A severe error happened :-( (UT_dox_tutorial.java:221) Tut_Verbosity():[0.001 +119 µs][UTThread][WRN][/](002): This is a warning :-/ Maybe an error follows?
As you see, only the Log Statements with Verbosity 'ERROR' and 'WARNING' survived.
Obviously ALox uses the attribute Verbosity, defined in enum class Verbosity two times:
Then ALox matches both attributes to decide whether a Log Statement is executed with a Logger or not.
Now, we are able to control the Verbosity of the 'overall' log output of our application. But probably, in bigger projects, we need more 'granularity' for such control. The next section tells how!
Controlling the Verbosity of the log output, including switching it completely off, is of-course a core feature of any logging library. ALox allows to control such Verbosity for different Log Domains. A Log Domain can be seen like a key-word associated with just every Log Statement. This way, Log Domains are sorting each and every Log Statement used in an application into a specific subset.
Until now, we have not used and set any Log Domain in our samples. We have just omitted the parameter in our Log Statements and this way the parameter defaulted to an empty string. Look at the following two Log Statements:
As you see, two parameters are given now. The first denotes the Log Domain. The string provided can be arbitrarily chosen. Let us quickly look at the log output:
(UT_dox_tutorial.java:241) Tut_Domains():[0.000 +446 µs][UTThread][***][/HTTP](001): Connected (UT_dox_tutorial.java:245) Tut_Domains():[0.000 +219 µs][UTThread][***][/UI ](002): Somebody moved the mouse!
In the meta information of the log output, just before the line number, formerly appeared [/]
(what we did not explain, yet). Now it says [/HTTP]
, respectively [/UI]
. Obviously this field of the meta information denotes the Log Domain associated with the Log Statement.
The Log Domains chosen in this sample obviously group the Log Statements of the application into two sets, one for Log Statement concerning information about the 'user interface', the other about HTTP communication. Now, we can control the Verbosity of the two sets independently. We are using another default parameter that we previously omitted. Imagine, the UI of your application works well, but you have some problems with HTTP connections:
Now the output is:
(UT_dox_tutorial.java:263) Tut_Domains():[0.001 +675 µs][UTThread][***][/HTTP](001): Connected
Although both Log Statement share the same Verbosity, only the one that belongs to Log Domain "HTTP" is shown.
The next section tells us more about Log Domains. You might already guess what this is when looking at the meta information of the log output, showing [/HTTP]
, [/UI]
and [/]
for the Log Domains!
ALox organizes Log Domains hierarchically. A first advantage of this is that it becomes easy to switch Verbosity of a whole set of Log Domains by controlling the parent.
Look at the following sample:
and its output:
(UT_dox_tutorial.java:286) Tut_HierDom():[0.000 +688 µs][UTThread] [/UI/MOUSE](001): A mouse click (UT_dox_tutorial.java:288) Tut_HierDom():[0.000 +183 µs][UTThread][***][/UI/MOUSE](002): Somebody moved the mouse! (UT_dox_tutorial.java:291) Tut_HierDom():[0.001 +208 µs][UTThread] [/UI/DLG ](003): About dialog opend (UT_dox_tutorial.java:293) Tut_HierDom():[0.001 +155 µs][UTThread][***][/UI/DLG ](004): About dialog, link to product page pressed.
We can use a slash ( '/'
) to separate Log Domains and organize them in a tree structure, just as we do with directories in a file system. In the sample above, Log Domains "DLG" and "MOUSE" are Sub-Log Domains of Log Domain "UI".
With this information, it is important to understand that method Lox.setVerbosity always sets the given Log Domain and all its sub-domains to the Verbosity value provided. Consequently, the following statement switches Log Domains "UI", "UI/MOUSE" and "UI/DLG" to the same Verbosity.Warning:
If we were interested how mouse events are processed in our application, we might do invoke:
The order of these two statements is important: If they had been written the other way round, then the setting of Log Domain "UI" had overwritten that of Log Domain "UI/MOUSE".
Almost everywhere ALox accepts a Log Domain name as a parameter, a domain path can be given. This even works with the dot and dot-dot syntax that we know from file paths. Also, relative and absolute path names can be given. Here are some samples:
"DOM" // a relative path name "./DOM" // same as "DOM" "/DOM" // an absolute path name "/DOM/XYZ/.." // same as "/DOM" "/DOM/.." // same as "/" "DOM/XYZ/.." // same as "DOM" "DOM/./XYZ" // same as "DOM/XYZ"
Now, we are able control the Verbosity of a (sub-)tree of Log Domains. The next section tells us more about why the hierarchical organization of Log Domains is extremely useful.
As we saw, optional parameter domain of Log Statements allows us to group the log output into different areas of interest and control the Verbosity per group. This is nice, but
To avoid all this (and gain even more) ALox provides a feature called Scope Domains.
The term Scope is known from your programming language. For example, a variable declared within a class method, 'automatically' carries the scope of that method. This means, it is not visible outside of that method. ALox uses a similar approach: All Log Statements within a method might carry the same Log Domain. In other words, we can set a 'default value' for a Log Domain to be used for all Log Statements of a method.
The interface in ALox which provides this feature is found with the set of overloaded versions of Lox.setDomain. Here is a sample:
and its output:
(UT_dox_tutorial.java:44) Extract():[0.000 +650 µs][UTThread] [/ZIP/EXTRACT](001): Extracting myfile.zip (UT_dox_tutorial.java:47) Extract():[0.000 +156 µs][UTThread] [/ZIP/EXTRACT](002): Success
You see, all disadvantages we just listed are blown away using Scope Domains.
In the sample above, we have used Scope.METHOD. Another type is Scope.CLASS which "binds" a given Log Domain path to all Log Statements within a Java class. As with scopes in your programming language, Scopes in ALox are nested and of-course Scope.CLASS is an 'outer Scope' of Scope.METHOD. For a single Log Statement, both Scope Domains might apply at the same time. With having hierarchically organized Log Domains, ALox concatenates all Scope Domains applicable. Look at the following sample:
and its output when both sample methods are executed:
(UT_dox_tutorial.java:64) <init>(): [0.002 +159 µs][UTThread] [/ZIP ] (001): Zipper created (UT_dox_tutorial.java:72) Compress(): [0.002 +169 µs][UTThread] [/ZIP/COMPRESS] (002): Compressing myfile.zip (UT_dox_tutorial.java:75) Compress(): [0.002 +167 µs][UTThread] [/ZIP/COMPRESS] (003): Success (UT_dox_tutorial.java:84) Extract(): [0.002 +174 µs][UTThread] [/ZIP/EXTRACT ] (004): Extracting myfile.zip (UT_dox_tutorial.java:87) Extract(): [0.002 +168 µs][UTThread] [/ZIP/EXTRACT ] (005): Success
Imagine, class Zipper resides in a package, together with other 'utility classes'. Somewhere in these classes, probably at the place where this 'utility library' is initialized (once, when bootstrapping a process), the following statement might be added:
This sets Log Domain "UTIL" for Scope.PACKAGE, which is again an outer scope of Scope.CLASS. Without changing the code in class Zipper, invoking its methods then leads to the output:
(UT_dox_tutorial.java:64) <init>(): [0.004 +174 µs][UTThread] [/UTIL/ZIP ] (001): Zipper created (UT_dox_tutorial.java:72) Compress(): [0.004 +175 µs][UTThread] [/UTIL/ZIP/COMPRESS] (002): Compressing myfile.zip (UT_dox_tutorial.java:75) Compress(): [0.004 +178 µs][UTThread] [/UTIL/ZIP/COMPRESS] (003): Success (UT_dox_tutorial.java:84) Extract(): [0.004 +168 µs][UTThread] [/UTIL/ZIP/EXTRACT ] (004): Extracting myfile.zip (UT_dox_tutorial.java:87) Extract(): [0.005 +169 µs][UTThread] [/UTIL/ZIP/EXTRACT ] (005): Success
What happens when Scope Domains are set and we still use optional parameter domain with a Log Statement? Let us just try out:
(UT_dox_tutorial.java:382) Tut_ScopeDomains():[0.006 +191 µs][UTThread] [/METHOD ] (001): No domain parameter given (UT_dox_tutorial.java:383) Tut_ScopeDomains():[0.006 +188 µs][UTThread] [/METHOD/PARAM] (002): Domain parameter "PARAM" given
As you see, the path provided with parameter domain gets appended to the path of Scope Domains evaluated. You can consider this parameter being a 'local' Scope, or an inner scope of Scope.METHOD!
Finally, imagine you want to 'overwrite' current applicable Scope Domains and direct a certain Log Statement to a different Log Domain. All you need to do is to use an absolute domain path with parameter domain:
(UT_dox_tutorial.java:391) Tut_ScopeDomains():[0.007 +554 µs][UTThread] [/READ ] (001): Reading file (UT_dox_tutorial.java:394) Tut_ScopeDomains():[0.007 +173 µs][UTThread] [/CONFIG ] (002): Path not found.
In this sample, the second Log Statement uses an absolute path. The reason is, that the error dectected in the code, obviously belongs to a different topic. The sample suggests, that it is not related to reading a file, but it is related to a wrong configuration. So, the log output goes directly to the corresponding domain.
This is enough about Scope Domains in the context of this tutorial. All details about Log Domain and Scope Domains are found in the ALox User Manual. Among other things, you will find:
While talking about rather complicated things like Log Domains and Scopes, we must not forget to talk about the log output itself. ALox is designed to be able to pass any type of data to one or more Loggers. In the default case, a textual output of these "Logables" is wanted. We have seen in previous samples already, that if two objects are passed, their textual representation is just concatenated:
(UT_dox_tutorial.java:707) Tut_Format():[0.000 +337 µs][UTThread] [/](001): Value=5
But in fact, this concatenation is just the "fallback" strategy of a quite powerful formatting system coming with ALox. In short, ALox here relies on underlying library ALib, which provides abstract class Formatter allowing to format a set of arguments in accordance with a given format string that contains placeholder symbols.
Now there are two tricks implemented: First, if no format string is recognized, a simple concatenation is performed. This is what we already saw. But secondly, if one formatter does not recognize a format string, a next formatter can be asked.
Two formatters are provided by ALib and ALox (by default) installs both in parallel:
printf
, but of-course much more powerful and also type-safe.With this background information, it is no surprise that the line above can alternatively be written like this:
which is Python compatible syntax, or like this:
which is how Java formatters declare format strings!
If more arguments exists than a format string "consumes", the next remaining argument is treated like the first one: if a format string is detected it is processed, otherwise the textual representation of the argument is just appended to the log output. Because of this, the following statements all produce the same log output:
Of-course it is even allowed to mix Python style and Java style format strings in one log statement:
The output is:
(UT_dox_tutorial.java:730) Tut_Format():[0.001 +004 µs][UTThread] [/](008): Python Style: "PS" - Java Style: "JS"
However, please note that it is not allowed to mix Python and Java styles within one format string!
In general, we are recommending to use Python style syntax with ALox, because it is more powerful and probably also better readable. The full documentation of the formats and how this is adopted within the C++ version of ALib/ALox is found with documentation of classes FormatterPythonStyle and FormatterJavaStyle. Let us conclude this chapter with some more samples:
(UT_dox_tutorial.java:736) Tut_Format():[0.001 +088 µs][UTThread] [/](009): >left < (UT_dox_tutorial.java:737) Tut_Format():[0.002 +003 µs][UTThread] [/](010): > right< (UT_dox_tutorial.java:738) Tut_Format():[0.002 +003 µs][UTThread] [/](011): > center < (UT_dox_tutorial.java:739) Tut_Format():[0.002 +008 µs][UTThread] [/](012): > 12.346< (UT_dox_tutorial.java:741) Tut_Format():[0.002 +002 µs][UTThread] [/](013): Tab: Stop (UT_dox_tutorial.java:743) Tut_Format():[0.002 +001 µs][UTThread] [/](014): Auto Tab: Stop (UT_dox_tutorial.java:744) Tut_Format():[0.003 +001 µs][UTThread] [/](015): Auto Tab XXX: Stop (UT_dox_tutorial.java:745) Tut_Format():[0.003 +001 µs][UTThread] [/](016): Auto Tab: Stop (UT_dox_tutorial.java:747) Tut_Format():[0.003 +002 µs][UTThread] [/](017): A quoted "Placeholder" string (UT_dox_tutorial.java:748) Tut_Format():[0.003 +001 µs][UTThread] [/](018): A quoted "395" number (UT_dox_tutorial.java:750) Tut_Format():[0.003 +002 µs][UTThread] [/](019): Upper "CASE" and lower "case" conversion (UT_dox_tutorial.java:752) Tut_Format():[0.004 +002 µs][UTThread] [/](020): Hex: 0x11ff22ee. With group chars: 11ff'22ee (UT_dox_tutorial.java:753) Tut_Format():[0.004 +002 µs][UTThread] [/](021): Oct: 0o12345670. With group chars: 12'345'670 (UT_dox_tutorial.java:754) Tut_Format():[0.004 +003 µs][UTThread] [/](022): Bin: 0b10010001. With group chars: 1001'0001
Often log lines should only be displayed if a certain condition is met. Here is a sample:
The last two lines can be replaced by one using method Log.Assert() as follows:
Advantages are again less typing and better readability of the code. Furthermore, the Java compiler would probably not prune the if statement if it was a more complex evaluation. As with using Assert() the evaluation of the expression is definitely pruned from your code. (Be sure you do not put side effects into expressions of Assert() invocations).
false
to have ALox perform the log.A similar method to Lox.Assert this is Lox.If. The differences to Lox.Assert are:
true
, the Log Statement is executed.Hence, the very same effect as given in the previous sample can be achieved with:
if
is a reserved keyword in Java.Another useful Log Statement provided by ALox is Lox.once. As the method name suggests, the statement
will lead to a log output only the very first time that it is executed. This seems simple, and well it is - as long as you omit all optional parameters. There are quite a bit, which makes this statement extremely flexible.
All details are given in a dedicated chapter of the ALox User Manual. Let us therefore just summarize some facts:
Imagine, all log statements of a of a certain kind should be very visible and distinguishable from other output. You could do this by
These things can be achieved with ALox using method Lox.setPrefix. With this method, additional Logables are passed to Loggers attached to a Lox. When logging text messages (we have not even talked in this tutorial about logging arbitrary objects) these objects are simply prepended to the log message itself.
Of-course, Prefix Logable can be defined according to ALox Scopes, language-related or thread-related ones. This means, you can do things like this:
Often, it is simpler to assign a Prefix Logable to a Log Domain. Also this is possible with overloaded versions of the method.
The real simple sample looks like this:
(UT_dox_tutorial.java:416) Tut_Prefix():[0.000 +381 µs][UTThread] [/](001): ALOX TUTORIAL: Well, just a sample
For colorizing (depending on the availability of a console that supports colors) the following prefix would be suitable:
We can not easily show colorful sample output in this tutorial. Try this yourself. ALox supports colorized output on ANSI consoles (GNU/Linux, etc.) and Windows OS command windows.
More and more complex use cases are elaborated in the ALox User Manual, for example it is explained how log output can be 'recursively indented' with very simple statements. Recursive indentation is very helpful when logging in recursive algorithms or when structured, composed data objects are logged.
The next chapter shows that ALox has already built-in mechanics for logging out structured data!
ALox also provides some log functions that log more complex stuff. The design decision was made to not put this into the classes Lox / Log but into a separated class LogTools. However, LogTools is used in the same simple fashion as classes Log / Lox itself.
On the Java language platform two methods are currently available which are briefly covered in the following:
The method LogTools.instance() allows you to log just any object recursively in a well formatted way. You can try this out quickly by adding the following code to your sample:
As you can see, we are just passing a quite complex object, the current thread, to the method. The parameter that follows the object which is to be logged (the current thread) determines the level of objects which are recursively logged, starting from the given one (composite objects). You can play around with it, and increase it from 2 to lets say 5. But be careful: this can lead to a quite long log message!
The given parameter "Actual Thread: " provides a headline for the log output. This allows you to save another line of code, which would be logging the headline explicitly.
Here are the first lines of output of the above sample:
>> Actual Thread: {Thread} >> <01> eetop: "<no access>" {String} >> <02> name: "<no access>" {String} >> <03> interrupted: "<no access>" {String} >> <04> contextClassLoader: "<no access>" {String} >> <05> inheritedAccessControlContext: "<no access>" {String} >> <06> threadLocals: "<no access>" {String} >> <07> inheritableThreadLocals: "<no access>" {String} >> <08> scopedValueBindings: "<no access>" {String} >> <09> parkBlocker: "<no access>" {String} >> <10> nioBlocker: "<no access>" {String} >> <11> cont: "<no access>" {String} >> <12> uncaughtExceptionHandler: "<no access>" {String} >> <13> threadLocalRandomSeed: "<no access>" {String} >> <14> threadLocalRandomProbe: "<no access>" {String} >> <15> threadLocalRandomSecondarySeed: "<no access>" {String} >> <16> container: "<no access>" {String} >> <17> headStackableScopes: "<no access>" {String}... ... ...
Just like Instance(), the method LogTools.exception() uses the multi line feature of ALox to log out the contents of a Java Exception well formatted and recursively (following inner exceptions).
Of-course a perfect place in your code to add LogTools.exception() is the first line within a catch() block.
To try it out, add the following code into your project:
Like the method LogTools.instance(), a headline for the Exception log output can be specified. This allows you to save another line of code, which would be logging the headline explicitly.
The following log output will be generated:
>> Logging an exception: >> Type: java.lang.Exception >> Message: TestException Message >> Cause: >> Type: java.lang.Exception >> Message: InnerException Message >> Cause: >> Type: java.lang.Exception >> Message: Inner, inner Exception >> Exception stack trace: >> ut_alox.UT_dox_tutorial.Tut_Exception(UT_dox_tutorial.java:515) >> java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) >> java.base/java.lang.reflect.Method.invoke(Method.java:580) >> org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:45) >> org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15) >> org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:42) >> org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20) >> org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:263) >> org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:68) >> org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:47) >> org.junit.runners.ParentRunner$3.run(ParentRunner.java:231) >> org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:60) >> org.junit.runners.ParentRunner.runChildren(ParentRunner.java:229) >> org.junit.runners.ParentRunner.access$000(ParentRunner.java:50) >> org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:222) >> org.junit.runners.ParentRunner.run(ParentRunner.java:300) >> org.junit.runners.Suite.runChild(Suite.java:128) >> org.junit.runners.Suite.runChild(Suite.java:24) >> org.junit.runners.ParentRunner$3.run(ParentRunner.java:231) >> org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:60) >> org.junit.runners.ParentRunner.runChildren(ParentRunner.java:229) >> org.junit.runners.ParentRunner.access$000(ParentRunner.java:50) >> org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:222) >> org.junit.runners.ParentRunner.run(ParentRunner.java:300) >> org.junit.runner.JUnitCore.run(JUnitCore.java:157) >> com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69) >> com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute(IdeaTestRunner.java:38) >> com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11) >> com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35) >> com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:232) >> com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:55)
In multi-threaded applications, ALox by default logs information about the thread id or name that executed the code containing your log statements. (This can be configured for ALox textual Loggers with class MetaInfo.)
Often threads do not have a name, but just an ID. It would be much nicer to see a name instead. ALox provides a simple mechanism to overwrite thread names with method Log.mapThreadName.
The mechanism is simple, but it might not be simple to identify the right place in the code to put it! Some code can be executed from different threads, and sometimes (for example when using an UI framework) you might not even know exactly which thread will invoke your code. However, the good news is that each thread ID has to be mapped only once during the lifecycle of the program, but may be mapped multiple times. Therefore, the guideline is:
For the sample of UI Frameworks, a good place to invoke Log.mapThreadName is the initial creation callback of your main application UI component. Normally, it is enough to put this statement only in one component (the main one), because all other components will be initialized by the same Framework thread.
But as this is a convenience concept anyhow, it is good practice to not care too much about it at first. Later, when your application grows, you can check your log files periodically for new, unmapped thread IDs. When you see one, scroll up in the log file and try to identify the very first appearance of this new ID. A double click on the log line will open the code that invoked the log. If you have populated your code with a reasonable amount of log entries, you will end up at the right place automatically! It also might be a good idea to restart your app with all domains set to Verbosity.Verbose and then look for the first appearances of unknown threads.
We hope that you are fully aware when reading through this tutorial, that all debug-log statements that are sampled here, are pruned from the release executables. One of the great benefits of ALox is that this removal of the statements is done quite automatically (depending on the language version you are using). Once, you have set-up your projects rightfully, your next release built is silent and does not contain your ALox Log Statements.
The concept of Log Data leverages this feature of ALox to allow to create variables that are existing exclusively in a debug compilation of your software. You can consider this feature as a "low hanging fruit" - not necessarily related with logging - but for ALox easy to implement and for the user easy to use. As the access to Log Data is exclusively done through the ALox API, the data is nicely 'encapsulated' and the probability of creating side effects is reduced in comparison to other ways to introduce temporary variables used for debugging.
The methods to store and retrieve Log Data with ALox are Lox.store and Lox.retrieve.
Here is a sample code which sets a debug variable in ALox providing a version of a file that was read by an application:
Now, whenever it might be interesting, this file version can be accessed:
The output will be:
(UT_dox_tutorial.java:104) Read(): [0.001 +662 µs][UTThread] [/READ] (002): Reading myfile.dat (UT_dox_tutorial.java:116) Read(): [0.002 +250 µs][UTThread] [/READ] (003): Success (UT_dox_tutorial.java:683) Tut_LogData():[0.003 +328 µs][UTThread] [/ ] (004): Working on file version "3.1"
If method "Read()" of the sample above was not executed and therefore no variable was stored, ALox just creates an empty, default object. The output in this case would look like this:
(UT_dox_tutorial.java:674) Tut_LogData():[0.001 +001 ms][UTThread] [/](001): Working on file version "java.lang.Object@56cbfb61"
In complex projects it might get a little confusing to keep track about the Loggers and their Verbosity, Scope Domains, Prefix Logables and Log Data set.
More importantly, when other software components of your project are using ALox as well (defining their own domains) then you might not know exactly which Log Domains get registered and used by those. The same applies when working in a team.
Let us create a sample, do some ALox configuration and then invoke method Lox.state:
Lox.state gathers internal configuration information and logs it out. The output is quite self explanatory:
[UTThread] [/THREAD/PNS/PATH/FN](001): This goes to both loggers [UTThread] [/MEM ](002): This goes only to the memory logger [UTThread] [/THREAD/PNS/PATH/FN](003): Will we see this in the config? [UTThread] [/THREAD/PNS/PATH/FN](004): Will we see this in the config? [TUTORIAL] [/THREAD/PNS/PATH/FN](005): ALox: Multi line message follows: >> TPre: MPre: DomPre: The current configuration of this Lox is: >> Name: "LOG" >> Version: 2402 (Rev. 0) >> Thread-Safe: SAFE >> #Log Calls: 5 >> >> Domain Substitution Rules: >> <no rules set> >> >> Once() Counters: >> Scope.CLASS [ut_alox.UT_dox_tutorial] >> "ONCEKEY" =1 >> >> Scope.Method [ut_alox.UT_dox_tutorial @Tut_LogState()] >> "#583" =1 >> >> Log Data: >> Scope.THREAD_OUTER [Thread="TUTORIAL"]: >> "DataKey" =MyData 4 >> >> Scope.CLASS [ut_alox.UT_dox_tutorial] >> "DataKey" =MyData 3 >> >> Scope.Method [ut_alox.UT_dox_tutorial @Tut_LogState()] >> <global> =MyData 1 >> "DataKey" =MyData 2 >> >> Prefix Logables: >> "TPre: " Scope.THREAD_OUTER [Thread="TUTORIAL"] >> "MPre: " Scope.Method [ut_alox.UT_dox_tutorial @Tut_LogState()] >> "{ESC.RED}" (Excl.) <domain> [/ERRORS] >> "DomPre: " <domain> [/THREAD/PNS/PATH/FN] >> "Mouse: " <domain> [/UI/MOUSE] >> >> Named Threads: >> (1): "TUTORIAL" >> >> Scope Domains: >> THREAD Scope.THREAD_OUTER [Thread="TUTORIAL"] >> PNS Scope.PACKAGE [] >> PATH Scope.PACKAGE [ut_alox] >> FN Scope.CLASS [ut_alox.UT_dox_tutorial] >> >> Loggers: >> DEBUG_LOGGER (ANSI_CONSOLE) >> Lines logged: 4 >> Creation time: 2024-03-02 14:57:28 >> Last log time: 2024-03-02 14:57:28 >> Verbosities: / = VERBOSE (DEFAULT) >> /MEM = OFF (DEFAULT) >> /UI = ERROR (DEFAULT) >> /UI/DLG = INFO (DEFAULT) >> >> MEMORY >> Lines logged: 4 >> Creation time: 2024-03-02 14:57:28 >> Last log time: 2024-03-02 14:57:28 >> Verbosities: / = VERBOSE (DEFAULT) >> /CON = OFF (DEFAULT) >> >> >> Loggers on Internal Domains: >> DEBUG_LOGGER (ANSI_CONSOLE) >> Lines logged: 4 >> Creation time: 2024-03-02 14:57:28 >> Last log time: 2024-03-02 14:57:28 >> Verbosities: $/ = WARNING (DEFAULT) >> >> >> Internal Domains: >> $/ [002] { ([000], WARNING (DEFAULT)) } >> $/DMN [034] { ([000], WARNING (DEFAULT)) } >> $/LGD [004] { ([000], WARNING (DEFAULT)) } >> $/LGR [011] { ([000], WARNING (DEFAULT)) } >> $/PFX [005] { ([000], WARNING (DEFAULT)) } >> $/THR [002] { ([000], WARNING (DEFAULT)) } >> $/VAR [000] { ([000], WARNING (DEFAULT)) } >> >> Domains: >> / [000] { ([000], VERBOSE (DEFAULT)), ([000], VERBOSE (DEFAULT)) } >> /CON [001] { ([001], VERBOSE (DEFAULT)), ([000], OFF (DEFAULT)) } >> /ERRORS [000] { ([000], VERBOSE (DEFAULT)), ([000], VERBOSE (DEFAULT)) } >> /MEM [001] { ([000], OFF (DEFAULT)), ([001], VERBOSE (DEFAULT)) } >> /THREAD [000] { ([000], VERBOSE (DEFAULT)), ([000], VERBOSE (DEFAULT)) } >> /THREAD/PNS [000] { ([000], VERBOSE (DEFAULT)), ([000], VERBOSE (DEFAULT)) } >> /THREAD/PNS/PATH [000] { ([000], VERBOSE (DEFAULT)), ([000], VERBOSE (DEFAULT)) } >> /THREAD/PNS/PATH/FN [003] { ([003], VERBOSE (DEFAULT)), ([003], VERBOSE (DEFAULT)) } >> /UI [000] { ([000], ERROR (DEFAULT)), ([000], VERBOSE (DEFAULT)) } >> /UI/DLG [000] { ([000], INFO (DEFAULT)), ([000], VERBOSE (DEFAULT)) } >> /UI/MOUSE [000] { ([000], ERROR (DEFAULT)), ([000], VERBOSE (DEFAULT)) } >>
Besides this, there is a second option to inspect what happens internally in class Lox. ALox is equipped with an internal Log Domain. This domain is used by ALox to log messages about itself! All we have to do, is setting the Verbosity of the internal domain to verbose for our debug Logger:
The output will be:
[UTThread] [$/LGR](001): Logger "MEMORY": '$/' = Verbosity.VERBOSE (DEFAULT). [UTThread] [$/LGR](002): Logger "DEBUG_LOGGER": '$/' = Verbosity.VERBOSE (DEFAULT). [UTThread] [$/DMN](003): 'PNS' set as default for Scope.PACKAGE(L1). [UTThread] [$/DMN](004): 'PATH' set as default for Scope.PACKAGE(L0). [UTThread] [$/DMN](005): 'FN' set as default for Scope.CLASS. [UTThread] [$/DMN](006): 'THREAD' set as default for Scope.THREAD_OUTER. [UTThread] [$/DMN](007): '/CON' registered. [UTThread][***][$/DMN](008): "DEBUG_LOGGER": /CON = VERBOSE (DEFAULT) [UTThread][***][$/DMN](009): "MEMORY": /CON = VERBOSE (DEFAULT) [UTThread] [$/LGR](010): Logger "MEMORY": '/CON' = Verbosity.OFF (DEFAULT). [UTThread] [$/LGR](011): Logger "DEBUG_LOGGER": '/' = Verbosity.VERBOSE (DEFAULT). [UTThread] [$/DMN](012): '/MEM' registered. [UTThread][***][$/DMN](013): "DEBUG_LOGGER": /MEM = VERBOSE (DEFAULT) [UTThread][***][$/DMN](014): "MEMORY": /MEM = VERBOSE (DEFAULT) [UTThread] [$/LGR](015): Logger "DEBUG_LOGGER": '/MEM' = Verbosity.OFF (DEFAULT). [UTThread] [$/DMN](016): '/UI' registered. [UTThread][***][$/DMN](017): "DEBUG_LOGGER": /UI = VERBOSE (DEFAULT) [UTThread][***][$/DMN](018): "MEMORY": /UI = VERBOSE (DEFAULT) [UTThread] [$/LGR](019): Logger "DEBUG_LOGGER": '/UI' = Verbosity.ERROR (DEFAULT). [UTThread] [$/DMN](020): '/UI/DLG' registered. [UTThread][***][$/DMN](021): "DEBUG_LOGGER": /UI/DLG = ERROR (DEFAULT) [UTThread][***][$/DMN](022): "MEMORY": /UI/DLG = VERBOSE (DEFAULT) [UTThread] [$/LGR](023): Logger "DEBUG_LOGGER": '/UI/DLG' = Verbosity.INFO (DEFAULT). [UTThread] [$/DMN](024): '/THREAD' registered. [UTThread][***][$/DMN](025): "DEBUG_LOGGER": /THREAD = VERBOSE (DEFAULT) [UTThread][***][$/DMN](026): "MEMORY": /THREAD = VERBOSE (DEFAULT) [UTThread] [$/DMN](027): '/THREAD/PNS' registered. [UTThread][***][$/DMN](028): "DEBUG_LOGGER": /THREAD/PNS = VERBOSE (DEFAULT) [UTThread][***][$/DMN](029): "MEMORY": /THREAD/PNS = VERBOSE (DEFAULT) [UTThread] [$/DMN](030): '/THREAD/PNS/PATH' registered. [UTThread][***][$/DMN](031): "DEBUG_LOGGER": /THREAD/PNS/PATH = VERBOSE (DEFAULT) [UTThread][***][$/DMN](032): "MEMORY": /THREAD/PNS/PATH = VERBOSE (DEFAULT) [UTThread] [$/DMN](033): '/THREAD/PNS/PATH/FN' registered. [UTThread][***][$/DMN](034): "DEBUG_LOGGER": /THREAD/PNS/PATH/FN = VERBOSE (DEFAULT) [UTThread][***][$/DMN](035): "MEMORY": /THREAD/PNS/PATH/FN = VERBOSE (DEFAULT) [UTThread] [/THREAD/PNS/PATH/FN](036): Will we see this in the config? [UTThread] [$/ ](037): Once() reached limit of 1 logs. No further logs for Scope.METHOD. [UTThread] [/THREAD/PNS/PATH/FN](038): Will we see this in the config? [UTThread] [$/ ](039): Once() reached limit of 1 logs. No further logs for group "ONCEKEY" in Scope.CLASS. [UTThread] [$/LGD ](040): Stored data in Scope.METHOD. [UTThread] [$/LGD ](041): Stored data with key "ONCEKEY" in Scope.METHOD. [UTThread] [$/LGD ](042): Stored data with key "ONCEKEY" in Scope.CLASS. [UTThread] [$/LGD ](043): Stored data with key "ONCEKEY" in Scope.THREAD_OUTER. [UTThread] [$/PFX ](044): Object "TPre: " set as prefix logable for Scope.THREAD_OUTER. [UTThread] [$/PFX ](045): Object "MPre: " set as prefix logable for Scope.METHOD. [UTThread] [$/PFX ](046): Object "DomPre: " added as prefix logable for domain '/THREAD/PNS/PATH/FN'. [UTThread] [$/DMN ](047): '/UI/MOUSE' registered. [UTThread][***][$/DMN ](048): "DEBUG_LOGGER": /UI/MOUSE = ERROR (DEFAULT) [UTThread][***][$/DMN ](049): "MEMORY": /UI/MOUSE = VERBOSE (DEFAULT) [UTThread] [$/PFX ](050): Object "Mouse: " added as prefix logable for domain '/UI/MOUSE'. [UTThread] [$/DMN ](051): '/ERRORS' registered. [UTThread][***][$/DMN ](052): "DEBUG_LOGGER": /ERRORS = VERBOSE (DEFAULT) [UTThread][***][$/DMN ](053): "MEMORY": /ERRORS = VERBOSE (DEFAULT) [UTThread] [$/PFX ](054): Object "" added as prefix logable for domain '/ERRORS'. [TUTORIAL] [$/THR ](055): Mapped thread ID 1 to "TUTORIAL". Original thread name: "main".
This way, you can exactly observe what is going on inside ALox.
To summarize: We have to ways to look into ALox:
/
, this domain is not affected, because it is just not a part of the general domain system. The advantage is that you can not 'accidentally' switch this domain on or off. In other words, the general domain tree is completely kept free for the users of ALox.This is the end of the tutorial of ALox for C++. You should be able to use the basic features of the ALox logging ecosystem and have nice, formatted, configurable log output in your software projects. ALox puts you in the comfortable position to keep your debug Log Statements in your code, because you can easily configure not only the verbosity as a global parameter, but for different Log Domains separately. When you start editing code that you have not touched for a while, you just can enable the corresponding Log Domain and restrict others to warnings and errors for a while. Because of the efficient ALox implementation, disabled Log Domains do not have a relevant impact on the execution of your debug code. As soon as you release your code, debug logging is pruned completely from your binaries.
In the ALox User Manual you will find all details of the ALox features along with information on use cases and often explanations why a feature is designed as it is. Some important things not covered in the tutorial are:
If however, you just want to start using ALox and get to know more details over time, you should only read IDE Setup for ALox for Java.
Finally, for the daily work, the ALox class reference gives a lot of help!