Logging and debug printing ๐
In the process of developing a C++ paclet it is often useful to print some information from the C++ code, either for debugging purpose or in the form of a log.
The most common way of achieving this in C++ is simple printing to the console (e.g. with std::cout
or std::cerr
) but this approach has noticeable
drawbacks:
requires running Wolfram Language from the command line to be able to see printed messages from the paclet library (and even then may not work on Windows)
print statements must be surrounded with
#ifdef
s, disabled in some other way, or simply removed from the codebase before making it production-ready
One alternative is to log messages to a text file, but this approach has its own problems like choosing the right location for the file, taking care of closing the file, etc.
LLU provides a logging and debug printing mechanism integrated with the Wolfram Language which:
is cross platform
has zero overhead when logging is disabled (so the print statements can stay in the code even in the Release mode)
is flexible and easy to customize
It is achieved by directing all the message data via WSTP to the Kernel and processing/formatting each message there according to the developerโs settings. To be able to use the logging facility from LLU, first:
#include <LLU/ErrorLog/Logger.h>
and then simply use one of the 3 predefined macros:
-
LLU_DEBUG
(...) ๐ Log a message (arbitrary sequence of arguments that can be passed via WSTP) as debug information, the log will consist of the line number, file name, function name and user-provided args.
Formatting can be customized on the Wolfram Language side.
- Note
This macro is only active with LLU_LOG_DEBUG compilation flag.
-
LLU_WARNING
(...) ๐ Log a message (arbitrary sequence of arguments that can be passed via WSTP) as warning, the log will consist of the line number, file name, function name and user-provided args.
Formatting can be customized on the Wolfram Language side.
- Note
This macro is only active with LLU_LOG_DEBUG or LLU_LOG_WARNING compilation flag.
-
LLU_ERROR
(...) ๐ Log a message (arbitrary sequence of arguments that can be passed via WSTP) as error, the log will consist of the line number, file name, function name and user-provided args.
Formatting can be customized on the Wolfram Language side.
- Note
This macro is only active with LLU_LOG_DEBUG, LLU_LOG_WARNING or LLU_LOG_ERROR compilation flag.
Each macro takes an arbitrary number of parameters of any WSTP-supported types (see WSTP support for details). Additionally, each log will be automatically equipped with file name, line number and function name, all related to the place were the log message was issued.
By default the macros are disabled and the compiler will not generate any extra machine code (so the 0 overhead condition is fulfilled).
To enable all macros compile your paclet with -D<log_level>
, where <log_level>
is one of the following:
-
LLU_LOG_DEBUG
๐ Define LLU_LOG_DEBUG to enable all log levels
-
LLU_LOG_WARNING
๐ Define LLU_LOG_WARNING to enable warning and error logs. Debug logs will be ignored.
-
LLU_LOG_ERROR
๐ Define LLU_LOG_ERROR to enable only error logs. Debug and warning logs will be ignored.
For example, consider a simple library function that takes a number of integers and the first of them is the index of the argument that will be returned:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | EXTERN_C DLLEXPORT int LogDemo(WolframLibraryData libData, mint argc, MArgument* args, MArgument res) {
LLU_DEBUG("Library function entered with ", argc, " arguments.");
auto err = LLErrorCode::NoError;
try {
MArgumentManager mngr(libData, argc, args, res);
auto index = mngr.getInteger<mint>(0);
if (index >= argc) {
LLU_WARNING("Index ", index, " is too big for the number of arguments: ", argc, ". Changing to ", argc - 1);
index = argc - 1;
}
auto value = mngr.getInteger<mint>(index);
mngr.setInteger(value);
} catch (const LibraryLinkError& e) {
LLU_ERROR("Caught LLU exception ", e.what(), ": ", e.debug());
err = e.which();
}
return err;
}
|
Compile with -DLLU_LOG_DEBUG
, load in the notebook and try:
A less verbose option is overriding the `LLU`Logger`FormattedLog`
symbol:
Or drop formatting and accumulate logs as Strings in a List:
Other features include:
Easy modification of log styling
Formatting logs as Association or List
Printing logs to Messages window
Filtering messages by file name, function name, log severity
Blocking all logs in top-level (so you donโt have to rebuild your paclet to temporarily disable logging, but the logs will still be sent via WSTP to top-level, only immediately discarded)