Component Architecture¶
Slic provides a simple and easy to use logging interface for applications.
The basic component architecture of Slic, depicted in Fig. 34, 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 Log Message Level can have one or more Log Stream instances, which, specify the output destination, format and filtering of messages.
One or more Log Stream instances, bound to a particular logger, that can be shared between log message levels.
The application logs messages at an appropriate Log Message Level, i.e.,
ERROR
, WARNING
, INFO
, or, DEBUG
using the static API.
Internally, the Logger routes the message to the corresponding
Log Stream instances that are bound to the associated
Log Message Level.
The following sections discuss some of these concepts in more detail.
Log Message Level¶
The Log Message Level indicates the severity of a message. Slic provides four levels of messages ranked from highest to lowest as follows:
Message Level |
Usage Description |
---|---|
|
Indicates that the application encountered a critical error or a faulty state. Includes also stacktrace. |
|
Indicates that the application encountered an error, but, the application should proceed. |
|
General information reported by an application. |
|
Information useful to application developers. |
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
slic::setLoggingMsgLevel( slic::message::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.
Warning
All messages will be ignored until the first call to slic::setLoggingMsgLevel()
.
Log Stream¶
The Log Stream class, is an abstract base class that facilitates the following:
Specifying the Log Message Format and output destination of log messages.
Implementing logic for handling and filtering messages.
Defines a pure abstract interface for all Log Stream instances.
Since Log Stream is an abstract base class, it cannot be instantiated and used directly. Slic provides a set of Built-In Log Streams, which provide concrete implementations of the Log Stream 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 Log Stream class and provide a concrete Log Stream instance implementation that implements the abstract interface defined by the Log Stream base class. See the Add a Custom Log Stream section for details.
A concrete Log Stream instance can be attached to one or more
Log Message Level by calling slic::addStreamToMsgLevel()
and
slic::addStreamToAllMsgLevels()
. See the Slic Doxygen API Documentation
for more details.
Log Message Format¶
The Log Message Format 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 |
<LEVEL> |
The Log Message Level, i.e., |
<MESSAGE> |
The supplied message that is being logged. |
<FILE> |
The file from where the message was emitted. |
<LINE> |
The line location where the message was emitted. |
<TAG> |
A string tag associated with a given message, e.g., for filtering during post-processing, etc. |
<RANK> |
The MPI rank(s) that emitted the message. Only applicable when Axom is compiled with MPI enabled and with MPI-aware Log Stream instances, such as, the Synchronized Stream and Lumberjack Stream. |
<RANK_COUNT> |
The number of MPI ranks that emitted the message. Only applicable when Axom is compiled with MPI enabled and with MPI-aware Log Stream instances, such as, the Synchronized Stream and Lumberjack Stream. |
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.
std::string format = "[<LEVEL>]: <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:
std::string format = "[<LEVEL>]: <MESSAGE> \n\t<FILE>:<LINE>";
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.
Default Message Format¶
If the Log Message Format is not specified, the Log Stream base class defines a default format that is set to the following:
std::string DEFAULT_FORMAT = "*****\n[<LEVEL>]\n\n <MESSAGE> \n\n <FILE>\n<LINE>\n****\n"
Built-In Log Streams¶
The Built-In Log Streams provided by Slic are summarized in the following table, followed by a brief description for each.
Log Stream |
Use & Availability |
---|---|
Always available. Used in serial applications, or, for logging on rank zero. |
|
Requires MPI. Used with MPI applications. |
|
Requires MPI. Used with MPI applications. |
Generic Output Stream¶
The Generic Output Stream, is a concrete implementation of the Log Stream base class, that can be constructed by specifying:
A C++
std::ostream
object instance, e.g.,std::cout
,std::cerr
for console output, or to a file by passing a C++std::ofstream
object, and,Optionally, a string that specifies the Log Message Format.
For example, the following code snippet registers a Generic Output Stream
object that is bound to the the std::cout
.
slic::addStreamToAllMsgLevels(
new slic::GenericOutputStream( &std::cout, format ) );
Similarly, the following code snippet, registers a Generic Output Stream object that is bound to a file.
std::ofstream log_file;
log_file.open( "logfile.dat" );
slic::addStreamToAllMsgLevels(
new slic::GenericOutputStream( &log_file, format ) );
Synchronized Stream¶
The Synchronized Stream is intended to be used with parallel MPI applications, primarily for debugging. The Synchronized Stream provides similar functionality to the Generic Output Stream, however, the log messages are synchronized across the MPI ranks of the specified communicator.
Similar to the Generic Output Stream the Synchronized Stream is constructed by specifying:
A C++
std::ostream
object instance, e.g.,std::cout`, ``std::cerr
for console output, or to a file by passing a C++std::ofstream
object.The MPI communicator, and,
Optionally, a string that specifies the Log Message Format.
The following code snippet illustrates how to register a
Synchronized Stream object with Slic to log messages to
std::cout
.
slic::addStreamToAllMsgLevels(
new slic::SynchronizedStream( &std::cout, mpi_comm, format ) );
Note
Since, the Synchronized Stream works across MPI ranks, logging
messages using the Slic Macros Used in Axom 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.
Warning
In the event of an abort in Synchronized Stream, SLIC calls
slic::outputLocalMessages()
, which will output the locally stored
messages to the output destination for log messages (file or console) and
then SLIC calls MPI_Abort()
. The call to slic::outputLocalMessages()
is non-collective, and does not guarantee all locally stored messages
will be outputted by all ranks.
Lumberjack Stream¶
The Lumberjack Stream is intended to be used with parallel MPI applications. In contrast to the Synchronized Stream, which logs messages from all ranks, the Lumberjack Stream uses Lumberjack internally to filter out duplicate messages that are emitted from multiple ranks.
The Lumberjack Stream is constructed by specifying:
A C++
std::ostream
object instance, e.g.,std::cout`, ``std::cerr
for 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 Log Message Format.
The following code snippet illustrates how to register a Lumberjack Stream
object with Slic to log messages to std::cout
.
slic::addStreamToAllMsgLevels(
new slic::LumberjackStream( &std::cout, mpi_comm, 5, format ) );
Note
Since, the Lumberjack Stream works across MPI ranks, logging
messages using the Slic Macros Used in Axom 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.
Warning
In the event of an abort in Lumberjack Stream, SLIC calls
slic::outputLocalMessages()
, which will output the locally stored
messages to the output destination for log messages (file or console) and
then SLIC calls MPI_Abort()
. The call to slic::outputLocalMessages()
is non-collective, and does not guarantee all locally stored messages
will be outputted by all ranks.
Add a Custom Log Stream¶
Slic can be customized by implementing a new subclass of the Log Stream.
This section demonstrates the basic steps required to Add a Custom Log Stream
by walking through the implementation of a new Log Stream instance, which
we will call MyStream
.
Note
MyStream
provides the same functionality as the Generic Output Stream.
The implementation presented herein is primarily intended for demonstrating
the basic process for extending Slic by providing a custom Log Stream.
Create a LogStream Subclass¶
First, we create a new class, MyStream
, that is a subclass of the
Log Stream class, as illustrated in the code snippet below.
1class MyStream : public LogStream
2{
3 public:
4
5 MyStream( ) = delete;
6 MyStream( std::ostream* os, const std::string& format );
7
8 virtual ~MyStream();
9
10 /// \see LogStream::append
11 virtual void append( message::Level msgLevel,
12 const std::string& message,
13 const std::string& tagName,
14 const std::string& fileName,
15 int line,
16 bool filter_duplicates );
17 private:
18 std::ostream* m_stream;
19
20 // disable copy & assignment
21 MyStream( const MyStream & ) = delete;
22 MyStream& operator=(const MyStream&) = delete;
23
24 // disable move & assignment
25 MyStream( const MyStream&& ) = delete;
26 MyStream& operator=(const MyStream&&) = delete;
27};
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 Log Stream base class,
it must implement the append()
method, which is a pure virtual method.
Implement LogStream::append()¶
The MyStream
class implements the LogStream::append()
method of the
Log Stream base class, as demonstrated in the code snippet below.
1 void MyStream::append( message::Level msgLevel,
2 const std::string& message,
3 const std::string& tagName,
4 const std::string& fileName,
5 int line,
6 bool AXOM_UNUSED_PARAM(filtered_duplicates) )
7{
8 assert( m_stream != nullptr );
9
10 (*m_stream) << this->getFormatedMessage( message::getLevelAsString(msgLevel),
11 message,
12 tagName,
13 "",
14 "",
15 fileName,
16 line );
17}
The append()
method takes all the metadata associated with a message through
its argument list:
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 Log Stream base class, which, applies the
Log Message Format according to the specified format string supplied
to the MyStream
class constructor, when the class is instantiated.
Register the new class with Slic¶
The new Log Stream class may be used with Slic in a similar manner to any of the Built-In Log Streams, as demonstrated in the code snippet below:
slic::addStreamToAllMsgLevels( new MyStream( &std::cout, format ) );