ALox is designed to support different log streams. A log stream is a destination for the Logables and can be the IDE console, the terminal console, a file, a web service or anything that accepts data streams. Abstract class Logger (C++, C#, Java) represents such a log stream. While some specializations of the class are provided with ALox, custom specializations may log information to any 'data drain' in any format you like.
Abstract class Logger (C++, C#, Java) has a very lean interface, in fact it is basically just one method, which in specialized classes needs to be implemented to execute a log. The data to log is provided as an array of class Object
in C# and Java, respectively in C++ an instance of class Boxes, which is a container of arbitrary objects.
This allows to create Loggers that take any type of (binary) data and use the data for writing a log entry in any custom way.
While all ALox code tries to be short and simple, class Logger is particularly simple! So, let us quickly walk through the class by looking at its members.
There are just a few fields in class Logger (C++, C#, Java):
class Logger has two members, Name (C++, C#, Java) and TypeName (C++, C#, Java) that can be read using. GetName (C++, C#, Java) and GetTypName (C++, C#, Java).
Field name is defined by the user and provided with the constructor. Field typeName is 'hard-coded' by each derived classes constructor. If the user of a Logger does not provide a name in the constructor, then field name defaults to the hard-coded typeName. Both fields are not used internally but only provided to be used by classes managing multiple Loggers (which generally is class Lox (C++, C#, Java)).
If multiple Loggers are attached to a Lox, they need to to have unique Names.
These are two time stamps that contain the time of the creation of the Logger (C++, C#, Java) (or the time this timestamp was reset to) and the time of the last log. These two members are normally used to calculate the elapsed time (the cumulated time an application is running) and the time difference between two log calls. The latter is especially interesting for log outputs on a debug console, as it allows to get for a first quick impression about your software's performance, lock states, bottlenecks, etc.
This is a simple counter of the number of logs processed so far. Feel free to reset it as you like, it is not used anywhere internally, other than as an option to output with each log line.
Besides the protected constructor, which just initializes some default values for the Loggers' fields, abstract method Log (C++, C#, Java) is the most important.
Derived classes only need to implement this abstract method with code that is executing the Log Statement. When the method is invoked, class Lox already performed various checks, including that the Verbosity justifies the execution.
As an experienced programmer after reading the previous sections, it is certainly fully obvious to you which steps need to be taken to create your own variation of class Logger (C++, C#, Java) that directs your Logables to a certain data drain in a specific format. Nevertheless, let us quickly name these steps explicitly:
But before you now go ahead and implement your own Logger type class, you should first continue reading through this chapter.
When you think about debug log output you think about logging textual messages that get displayed on your debug console. We think the use of a logging ecosystem for this quite simple purpose is advisable as soon as a software project bigger than two pages of code!
While ALox wants to be a perfect tool for quick, easy and comfortable debug logging, the goals of ALox go beyond that. This is why ALox logging interface methods are not restricted to string types, but accept any object to be passed to any derived Logger type.
It is very important to understand this. The result is twofold:
Class TextLogger (C++, C#, Java), which is described in this chapter, is exactly about the first thing: Log any object that is passed to it as a textual representation, hence into a character stream. All of the provided ALox Loggers that produce text output, derive from this base class instead of deriving directly from Logger. Among these classes are ConsoleLogger (C++, C#, Java), MemoryLogger (C++, C#, Java) and AnsiLogger (C++, C#, Java). Hence, the class diagram above is a little simplified. It rather looks like this:
Class TextLogger (C++, C#, Java) contains two helper classes as public fields. The advantage of using helpers is that they can be replaced at runtime by your own, tailored versions of these helpers and this way you can change the default behavior of existing Logger types, like without deriving new ones.
The helper classes are described in the following two paragraphs.
Class MetaInfo (C++, C#, Java) is used by class TextLogger (C++, C#, Java) to assemble the meta information of each log line, which incorporates things like date and time, thread information, Verbosity and Log Domain. MetaInfo provides a public format string that defines the start (prefix) of each log line. A sample of such format string (in ALox for C#) is:
"%SF(%SL):%SM()%A3[%DD] [%TD][%TC +%TL][%tN]%V[%D]<%#>: "
The format string contains variables, marked by a leading '%' sign. The set of these format variables available are:
Description | |
---|---|
The full path of the source file (in Java: The callers' package name). | |
The trimmed path of the source file (in Java not available). | |
The callers' source file name. | |
The callers' source file name without extension (in Java not available). | |
The callers' class name (Java only!). | |
The line number in the callers' source file. | |
The callers' method name. | |
The date the log call was invoked. | |
Time of day the log call was invoked. | |
Time elapsed since the Logger was created or its timer was reset. | |
Time elapsed since the last log call. Note: These time values do not sum up correctly. This is not only because of rounding errors, but also because the time between the "end" of the last log call and the "beginning" of this log call is measured. This has the advantage that a quite accurate value of "elapsed time since the last log call" is displayed and hence a very good performance indicator is provided. | |
Thread name | |
Thread ID. | |
The Verbosity. For the display of the different values, MetaInfo exposes four public fields containing string definitions. | |
The Log Domains' full path. | |
The log call counter (like a line counter, but counting multi-line log output as one). | |
An auto-adjusted tabulator. This grows whenever it needs to grow, but never shrinks. The mandatory number n (a character between 0 and 9) specifies how much extra space is added when tab is adjusted. This is useful to achieve very clean column formatting. | |
The name of the Logger. This might be useful if multiple loggers write to the same output stream (e.g. the console). | |
The name of the Lox. | |
The name of the process / application. |
Changing the format string MetaInfo.Format (C++, C#, Java) provides an easy way to change the look of your log output. For example, if you are not interested in thread information, just remove the "[%tN] " part from the original string.
If you want to modify the output of a certain variable or if you want to add your own variables, you can derive your on implementation of MetaInfo and override the virtual method MetaInfo.processVariable (C++, C#, Java) Within the implementation, just fetch your own variables and/or modify existing and call the original method for the rest that you do not want to modify.
Finally, if you really want to customize the logging of meta information in the class TextLogger completely and maybe do not want to even rely on a format string, then feel free to derive your on implementation of MetaInfo and override the virtual method MetaInfo.Write (C++, C#, Java)!
Class ObjectConverter (C++, C#, Java) is used by class TextLogger (C++, C#, Java) to convert the Logables that get passed by the user through the Log Statements to the Logger into a string representation. While ObjectConverter is abstract and declares only one simple interface method, the standard implementation used with the built-in loggers is provided with type StandardConverter (C++, C#, Java).
This class is still is extremely simple, as it transfers this responsibility to objects of type derived from class Formatter! This abstract class and corresponding implementations are provided with ALib, the utility library that ALox builds upon.
Please consult the ALib documentation of classes Formatter (C++, C#, Java), FormatterPythonStyle (C++, C#, Java) and FormatterJAVAStyle (C++, C#, Java) to learn more about how these classes work.
Again, there are different "levels" of possibility on how to change and implement custom functionality in respect to converting the Logables while using class TextLogger:
Class TextLogger (C++, C#, Java) provides a feature to log a message into multiple lines. This is useful for example, if you want to log a string that contains XML text. Instead of one very wide log line, TextLogger is configured by default to separate the text into multiple lines in a very controlled way.
Multi-line output behavior of class TextLogger is configured by the field TextLogger.MultiLineMsgMode (C++, C#, Java).
The following modes are available:
As mentioned above, class TextLogger (C++, C#, Java) is still abstract. While it implements the abstract interface method Logger.Log (C++, C#, Java) it introduces a new abstract interface method in turn! This method is TextLogger.logText (C++, C#, Java). Class TextLogger takes care to build the complete textual representation of the log line, including meta information and the log message itself.
Now, the only thing that a simple derivate of TextLogger needs to do is to override this method and just copy (write) the provided buffer to its final destination.
Those custom Loggers that wish to ignore any special formatting and colorizing codes of class ESC (C++, C#, Java) might rather want to extend abstract class PlainTextLogger (C++, C#, Java) which takes care of the removal of such codes. Class PlainTextLogger implements TextLogger.logText and introduces in turn other abstract methods. As a simple sample of how PlainTextLogger can be derived, see the source code of class MemoryLogger (C++, C#, Java).
On this topic, see also chapter 16 - Colorful Loggers.
Class TextLogger (C++, C#, Java) optionally uses a facility of ALib to avoid concurrent access to the standard output stream and standard error stream available to most applications.
This feature is described in the reference documentation of ALIB.StdOutputStreamsLock (C++, C#, Java).
To enable the use of this locker for a custom types, all that has to be done is providing value true
for parameter usesStdStreams of the protected constructor of class TextLogger.
If an application writes to those streams in parallel to ALox (e.g. using std::cout in C++, Console.WriteLine in C# or System.out.println in Java), then to avoid mixing such output with ALox output, such direct writes should be performed only after the ALIB.StdOutputStreamsLock was 'acquired'. Also, such application has to register once with ALIB.StdOutputStreamsLock. Then, together with the Logger, the critical number of two 'acquirers' are reached and the SmartLock (C++, C#, Java) gets activated.
ALox supports recursive log calls. Recursion occurs when log statements are executed during the evaluation of the logables of an already invoked log statement. A user might think that recursive log calls are seldom and exotic, however, in reality, recursive calls might occur quite quickly.
To allow and properly treat recursion, each class and method involved in the execution of a log statement has to be prepared for it. The execution must be free of dependencies to member variables or such members need to be created per recursion.
Hence, not only class class Lox (C++, C#, Java) needs to support recursion, but also the logger classes themselves.
Class TextLogger (C++, C#, Java) and its utility class StandardConverter (C++, C#, Java) are well prepared and as long as custom loggers are built on those, recursion should not be a problem. This is because abstract method TextLogger.logText will not be invoked recursively.
When implementing own variants of class ObjectConverter or otherwise a "deeper" use of provided classes is done, the possibility of recursion of log calls has to be taken into account.
While the abstract classes Logger, TextLogger and PlainTextLogger are located in the namespaces (packages)
you can checkout which 'ready to use' Logger implementations are available today for your preferred language version of ALox, by referring to the reference documentation of namespace (package)
For convenience, method Lox.CreateConsoleLogger (C++, C#, Java) is provided. This method chooses an appropriate implementation of class Logger suitable for human readable log output. The Logger chosen depends on the platform and configuration settings.
For debug logging, method Log.AddDebugLogger (C++, C#, Java) is provided. This may even choose and attach more than one Logger, depending on language, platform and IDE.
A noteworthy, built-in specialization of Logger is found with class MemoryLogger (C++, C#, Java). It uses an internal character buffer of ALib-type AString (C++, C#, Java) and just appends each log entry to this buffer, separated by a new line sequence.
As MemoryLogger does not log to the console or any other slow thing, is extremely fast. The latest record was over on million log entries per second in a single-thread! (Achieved on Intel(R) Haswell Core(TM) i7 CPU @4.0GHz, using ALox for C++, doing release logging.)
This gives an indication that the ALox ecosystem, in combination with its MemoryLogger is extremely useful in performance critical sections of your code. If you would do 100 log entries per second, the performance loss for your application would be around 0.01%. (Yes, that is why we love Bauhaus coding style.)
The following summarizes the takeaways of this chapter:
If you developed an interesting Logger, like one that
then please do not hesitate to propose the code to us as an extension of the open source project ALox!