Slic provides a simple and easy to use logging interface for applications.
The basic component architecture of Slic, depicted in figs/slic_architecture
, consists of three main components:
- A static logger API. This serves as the primary interface to the application.
- One or more Logger object instances. In addition to the root logger, which is created when Slic is initialized, an application may create additional loggers. However, at any given instance, there is only a single active logger to which all messages are logged.
- A Logger consists of four log message levels:
ERROR
,WARNING
,INFO
andDEBUG
. - Each
logMessageLevel
can have one or morelogStream
instances, which, specify the output destination, format and filtering of messages.
- A Logger consists of four log message levels:
- One or more
logStream
instances, bound to a particular logger, that can be shared between log message levels.
The application logs messages at an appropriate logMessageLevel
, i.e., ERROR
, WARNING
, INFO
, or, DEBUG
using the static API. Internally, the Logger routes the message to the corresponding logStream
instances that are bound to the associated logMessageLevel
.
The following sections discuss some of these concepts in more detail.
The logMessageLevel
indicates the severity of a message. Slic provides four levels of messages ranked from highest to lowest as follows:
Message Level | Usage Description |
---|---|
ERROR |
|
WARNING |
|
INFO |
|
DEBUG |
|
Note
ERROR
messages by default will cause the application to abort. This behavior may be toggled by calling slic::enableAbortOnError()
and slic::disableAbortOnError()
. See the Slic Doxygen API Documentation for more details.
An application may adjust at runtime the severity level of messages to capture by calling slic::setLoggingMsgLevel()
. For example, the following code snippet, sets the severity level to WARNING
This indicates that all messages with a level of severity of WARNING
and higher will be captured, namely WARNING
and ERROR
messages. Thereby, enable the application to filter out messages with lower severity.
The logStream
class, is an abstract base class that facilitates the following:
- Specifying the
logMessageFormat
and output destination of log messages. - Implementing logic for handling and filtering messages.
- Defines a pure abstract interface for all
logStream
instances.
Since logStream
is an abstract base class, it cannot be instantiated and used directly. Slic provides a set of BuiltInLogStreams
, which provide concrete implementations of the logStream
base class that support common use cases for logging, e.g., logging to a file or output to the console.
Applications requiring custom functionality, may extend the logStream
class and provide a concrete logStream
instance implementation that implements the abstract interface defined by the logStream
base class. See the addACustomLogStream
section for details.
A concrete logStream
instance can be attached to one or more logMessageLevel
by calling slic::addStreamToMsgLevel()
and slic::addStreamToAllMsgLevels()
. See the Slic Doxygen API Documentation for more details.
The logMessageFormat
is specified as a string consisting of keywords that are encapsulated in <...>
, which, Slic knows to interpret when assembling the log message.
The list of keywords is summarized in the table below.
keyword | Replaced With |
---|---|
<TIMESTAMP> | A textual representation of the time a message is logged, as returned by std::asctime() . |
<LEVEL> | The logMessageLevel , i.e., ERROR , WARNING , INFO , or DEBUG . |
<MESSAGE> | The supplied message that is being logged. |
<FILE> | The file from where the message was emmitted. |
<LINE> | The line location where the message was emmitted. |
<TAG> | A string tag associated with a given message, e.g., for filtering during post-processing, etc. |
<RANK> | The MPI rank that emmitted the message. Only applicable when the Axom Toolkit is compiled with MPI enabled and with MPI-aware logStream instances, such as, the SynchronizedOutputStream and LumberjackStream . |
These keywords can be combined in a string to specify a template for a log message.
For example, the following code snippet, specifies that all reported log messages consist of the level, enclosed in brackets followed by the user-supplied log message.
To get the file and line location within the file where the message was emitted, the format string above could be amended with the following:
This indicates that the in addition to the level and user-supplied, the resulting log messages will have an additional line consisting of the file and line where the message was emitted.
If the logMessageFormat
is not specified, the logStream
base class defines a default format that is set to the following:
The BuiltInLogStreams
provided by Slic are summarized in the following table, followed by a brief description for each.
Log Stream | Use & Availability |
---|---|
GenericOutputStream |
Always available. Used in serial applications, or, for logging on rank zero. |
SynchronizedOutputStream |
Requires MPI. Used with MPI applications. |
LumberjackStream |
Requires MPI. Used with MPI applications. |
The GenericOutputStream
, is a concrete implementation of the logStream
base class, that can be constructed by specifying:
- A C++
std::ostream
object instance, e.g.,std::cout`,
std::cerrfor console output, or to a file by passing a C++
std::ofstream`` object, and, - Optionally, a string that specifies the
logMessageFormat
.
For example, the following code snippet registers a GenericOutputStream
object that is bound to the the std::cout
.
Similarly, the following code snippet, registers a GenericOutputStream
object that is bound to a file.
The SynchronizedOutputStream
is intended to be used with parallel MPI applications, primarily for debugging. The SynchronizedOutputStream
provides similar functionality to the GenericOutputStream
, however, the log messages are synchronized across the MPI ranks of the specified communicator.
Similar to the GenericOutputStream
the SynchronizedOutputStream
is constructed by specifying:
- A C++
std::ostream
object instance, e.g.,std::cout`,
std::cerrfor console output, or to a file by passing a C++
std::ofstream`` object. - The MPI communicator, and,
- Optionally, a string that specifies the
logMessageFormat
.
The following code snippet illustrates how to register a SynchronizedOutputStream
object with Slic to log messages to std::cout
.
Note
Since, the SynchronizedOutputStream
works across MPI ranks, logging messages using the SlicMacros
or the static API directly only logs the messages locally. To send the messages to the output destination the application must call slic::flushStreams()
explicitly, which, in this context is a collective call.
The LumberjackStream
, is intended to be used with parallel MPI applications. In contrast to the SynchronizedOutputStream
, which logs messages from all ranks, the LumberjackStream
uses Lumberjack internally to filter out duplicate messages that are emitted from multiple ranks.
The LumberjackStream
is constructed by specifying:
- A C++
std::ostream
object instance, e.g.,std::cout`,
std::cerrfor console output, or to a file by passing a C++
std::ofstream`` object. - The MPI communicator,
- An integer that sets a limit on the number of duplicate messsages reported per rank, and,
- Optionally, a string that specifies the
logMessageFormat
.
The following code snippet illustrates how to register a LumberjackStream
object with Slic to log messages to std::cout
.
Note
Since, the LumberjackStream
works across MPI ranks, logging messages using the SlicMacros
or the static API directly only logs the messages locally. To send the messages to the output destination the application must call slic::flushStreams()
explicitly, which, in this context is a collective call.
Slic can be customized by implementing a new subclass of the logStream
. This section demonstrates the basic steps required to addACustomLogStream
by walking through the implementation of a new logStream
instance, which we will call MyStream
.
Note
MyStream
provides the same functionality as the GenericOutputStream
. The implementation presented herein is primarily intended for demonstrating the basic process for extending Slic by providing a custom logStream
.
First, we create a new class, MyStream
, that is a subclass of the logStream
class, as illustrated in the code snippet below.
class MyStream : public LogStream
{
public:
MyStream( ) = delete;
MyStream( std::ostream* os, const std::string& format );
virtual ~MyStream();
/// \see LogStream::append
virtual void append( message::Level msgLevel,
const std::string& message,
const std::string& tagName,
const std::string& fileName,
int line,
bool filter_duplicates );
private:
std::ostream* m_stream;
// disable copy & assignment
MyStream( const MyStream & ) = delete;
MyStream& operator=(const MyStream&) = delete;
// disable move & assignment
MyStream( const MyStream&& ) = delete;
MyStream& operator=(const MyStream&&) = delete;
};
The class has a pointer to a C++ std::ostream
object as a private class member. The std::ostream
object holds a reference to the output destination for log messages, which can be any std::ostream
instance, e.g., std::cout
, std::cerr
, or a file std::ofstream
, etc.
The reference to the std::ostream
is specified in the class constructor and is supplied by the application when a MyStream
object is instantiated.
Since MyStream
is a concrete instance of the logStream
base class, it must implement the append()
method, which is a pure virtual method.
The MyStream
class implements the LogStream::append()
method of the logStream
base class, as demonstrated in the code snippet below.
- {
assert( m_stream != nillptr );
- (*m_stream) << this->getFormatedMessage( message::getLevelAsString(msgLevel),
message, tagName, "", fileName, line );
}
The append()
method takes all the metadata associated with a message through its argument list:
- The
logMessageLevel
- The user-specified message
- A tag associated with the message, may be set
MSG_IGNORE_TAG
- The file where the message was emitted
- The line location within the file where the message was emitted
The append()
method calls LogStream::getFormatedMessage()
, a method implemented in the logStream
base class, which, applies the logMessageFormat
according to the specified format string supplied to the MyStream
class constructor, when the class is instantiated.
The new logStream
class may be used with Slic in a similar manner to any of the BuiltInLogStreams
, as demonstrated in the code snippet below: