Getting Started with Slic

This section illustrates some of the key concepts and capabilities of Slic by presenting a short walk-through of a C++ application. The complete Slic Application Code Example is included in the Appendix section and is also available within the Slic source code, under src/axom/slic/examples/basic/logging.cpp.

This example illustrates the following concepts:

Step 1: Add Header Includes

First, the Slic header must be included to make all the Slic functions and classes accessible to an application:

1// Slic includes
2#include "axom/slic.hpp"

Note

All the classes and functions in Slic are encapsulated within the axom::slic namespace.

Step 2: Initialize Slic

Prior to logging any messages, the Slic Logging Environment is initialized by the following:

1  slic::initialize();

This creates the root logger instance. However, in order to log messages, an application must first specify an output destination and optionally, prescribe the format of the log messages. These steps are demonstrated in the following sections.

Warning

If you do not initialize Slic, Slic will call slic::initialize(), setup a minimal configuration (perform Steps 2 through 5), and issue a warning message. It is recommended that you call slic::initialize() to get rid of the warning and perform your own configuration.

Step 3: Set the Message Format

The Log Message Format is specified as a string consisting of keywords, enclosed in < ... >, that Slic knows how to interpret when assembling the log message.

1  std::string format = std::string("<TIMESTAMP>\n") +
2    std::string("[ <LEVEL> <TAG>]: <MESSAGE> \n") +
3    std::string("FILE=<FILE>\n") + std::string("LINE=<LINE>\n\n");

For example, the format string in the code snippet above indicates that the resulting log messages will have the following format:

  • A line with the message time stamp

  • A line consisting of the Log Message Level, enclosed in brackets [ ], followed by the user-supplied message,

  • A third line with the name of the file where the message was emitted and

  • The corresponding line number location within the file, in the fourth line.

The format string is used in Step 5: Register a Log Stream. Specifically, it is passed as an argument to the Generic Output Stream object constructor to prescribe the format of the messages.

See the Log Message Format section for the complete list of keyword options available that may be used to customize the format of the messsages.

Note

This step is optional. If the format is not specified, a Default Message Format will be used to assemble the message.

Step 4: Set Severity Level

The severity of log messages to be captured may also be adjusted at runtime to the desired Log Message Level by calling slic::setLoggingMsgLevel(). This provides another knob that the application can use to filter the type and level of messages to be captured.

All log messages with the specified severity level or higher are captured. For example, the following code snippet sets the severity level to debug.

1  slic::setLoggingMsgLevel(slic::message::Debug);

This indicates that all log messages that are debug or higher are captured otherwise, the messages are ignored. Since debug is the lowest severity level, all messages will be captured in this case.

Warning

All messages will be ignored until the first call to slic::setLoggingMsgLevel().

Step 5: Register a Log Stream

Log messages can have one or more output destination. The output destination is specified by registering a corresponding Log Stream object to each Log Message Level.

The following code snippet uses the Generic Output Stream object, one of the Built-In Log Streams provided by Slic, to specify std::cout as the output destination for messages at each Log Message Level.

1  slic::addStreamToAllMsgLevels(new slic::GenericOutputStream(&std::cout, format));

Note

Instead of calling slic::addStreamToAllMsgLevels() an application may use slic::addStreamToMsgLevel() that allows more fine grain control of how to bind Log Stream objects to each Log Message Level. Consult the Slic Doxygen API Documentation for more information.

The Generic Output Stream, takes two arguments in its constructor:

  • A C++ std::ostream object that specifies the destination of messages. Consequently, output of messages can be directed to the console, by passing std::cout or std::cerr, or to a file by passing a C++ std::ofstream object. In this case, std::cout is specified as the output destination.

  • A string corresponding to the Log Message Format, discussed in Step 3: Set the Message Format.

Note

Slic maintains ownership of all registered Log Stream instances and will deallocate them when slic::finalize() is called.

Step 5.1: Tagged Log Streams

Slic has limited support for tags, where users can bind streams to user-defined tags. The bound streams only output messages with the given tag, disregarding the message’s Log Message Level. The tagged Log Stream can be created by calling slic::addStreamToTag().

The following code snippet uses the Generic Output Stream object to specify std::cout as the output destination for messages tagged with the custom tag myTag.

1  slic::addStreamToTag(new slic::GenericOutputStream(&std::cout, format),
2                       "myTag");

Step 6: Log Messages

Once the output destination of messages is specified, messages can be logged using the Slic Macros Used in Axom, as demonstrated in the code snippet below.

1  SLIC_DEBUG("Here is a debug message!");
2  SLIC_INFO("Here is an info mesage!");
3  SLIC_WARNING("Here is a warning!");
4  SLIC_ERROR("Here is an error message!");
5  SLIC_INFO_TAGGED("Here is a message for tagged streams with tag 'myTag'!",
6                   "myTag");

Note

By default, SLIC_ERROR() will print the specified message and a stacktrace to the corresponding output destination and call axom::utilities::processAbort() to gracefully abort the application. This behavior can be toggled by calling slic::disableAbortOnError(). Additionally, a custom abort function can be registered with slic::setAbortFunction(). See the Slic Doxygen API Documentation for more details.

Note

A subset of SLIC macros are collective operations when used with MPI-aware Log Stream instances such as Synchronized Stream or Lumberjack Stream. Consult Collective Slic Macros for a list of collective Axom macros.

Step 7: Finalize Slic

Before the application terminates, the Slic Logging Environment must be finalized, as follows:

1  slic::finalize();

Calling slic::finalize() will properly deallocate the registered Log Stream objects and terminate the Slic Logging Environment.

Note

slic::finalize() is a collective operation when used with MPI-aware Log Stream instances such as Synchronized Stream or Lumberjack Stream. See the Slic Doxygen API Documentation for more details.

Step 8: Run the Example

After building the Axom Toolkit the Slic Application Code Example may be run from within the build space directory as follows:

> ./example/slic_logging_ex

The resulting output should look similar to the following:

Fri Apr 26 14:29:53 2019
[DEBUG]: Here is a debug message!
FILE=/Users/zagaris2/dev/AXOM/source/axom/src/axom/slic/examples/basic/logging.cpp
LINE=44

Fri Apr 26 14:29:53 2019
[INFO]: Here is an info mesage!
FILE=/Users/zagaris2/dev/AXOM/source/axom/src/axom/slic/examples/basic/logging.cpp
LINE=45

Fri Apr 26 14:29:53 2019
[WARNING]: Here is a warning!
FILE=/Users/zagaris2/dev/AXOM/source/axom/src/axom/slic/examples/basic/logging.cpp
LINE=46

Fri Apr 26 14:29:53 2019
[ERROR]: Here is an error message!
** StackTrace of 3 frames **
Frame 1: axom::slic::logErrorMessage(std::__1::basic_string<char, std::__1::char_traits<char>,
std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>,
std::__1::allocator<char> > const&, int)
Frame 2: main
Frame 3: start
=====


FILE=/Users/zagaris2/dev/AXOM/source/axom/src/axom/slic/examples/basic/logging.cpp
LINE=47

Abort trap: 6