Get better log in your extensions thanks to the CamiTK Log System

How to use the powerful CamiTK log system.

Starting from CamiTK 4.1, the log system was entirely redesigned. The new log system is more powerful, very flexible and can trace anything on the console as well as in a log file.

Forget about all this std::cout and other way of “debugging” that clutters and slowdown your code, and let start with the new log system.

The aims of the log system in CamiTK are:

  • to simplify and give a flexible way to log message in any application
  • to be minimally invasive in the source code
  • to help bug tracking and report
  • to help new development by providing automatically generated debugging information
  • to suppress any need to use qDebug macro or std::cout / std::cerr printf

Basics

Using the CamiTK log system is very easy, all you need to do is include the Log.h header and use one of the CAMITK_...(QString) macro.

Code example

Examples in an Action or Component based class:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <...>
...
// recommended: add #include<Log.h> last
#include <Log.h>

    ...

    // anywhere in the code, log a trace message
    CAMITK_TRACE("A little message")

    ...

    // anywhere in the code, log a warning and error messages
    double x, y; // input, x should be lesser than y
    double z; // z should always be between 0 and 100

    if (fabs(y)<1e-10) {
        z = 100.0;
        CAMITK_WARNING("y very small: z set to 100.")
    }
    else {
        if (x>y) {
            percent = 0.0;
            CAMITK_ERROR("x greater than y: invalid input data. z set to 0.")
        }    
        else {
            // all good
            z = 100.0 * x / y;
            CAMITK_TRACE("z=" + QString::number(z)) // the second part converts the double to a QString, many formatting option are available, see the QString method
        }
    }

    ...

    // during a debugging session, you can also use the
    // conditional macro to only log a portion of the value or flow
    for(unsigned int i=...) {
        // this will only log a message if i lesser than 5
        CAMITK_INFO_IF((i<5), "Value: " + QString::number(i))
    }

As you can see in the above example, there is four types of log messages, explained in the table below.

log message description
ERROR error messages should be used only when a problem occurs which cannot be taken care of by the source code (e.g. input data are invalid and preventive action is taken, execution might result in unfinished process / undefined behaviour, or a crash…)
WARNING warning messages should be used when something went wrong but is not critical to the application state but might require some corrective or alternative action by the user
INFO information messages should be used to show normal processing, stage or intermediate steps or display some useful data values
TRACE trace messages should be used to trace anything useful for debugging or log post-processing

These log macros will generate a compilation error in some context (i.e., in static methods and in classes that do not derived from QObject). In this case you need to use the ALT version of the macros instead, see the corresponding section below.

Note about the IF versions All the CamiTK log macro also have an IF version. In this case the message is only logged if the first parameter of the macro is evaluated to true during execution. This can be useful when debugging code inside a loop or display a log message in specific cases.

Log level

This is where log messages get flexible: when running a CamiTK application (such as camitk-imp), log messages will be displayed depending on the current log level.

There is four types of current log level as described in this table:

log level description
TRACE all types of messages are logged
INFO INFO, WARNING and ERROR messages are logged
WARNING only WARNING and ERROR messages are logged
ERROR only error messages are logged
NONE in this case, no message are logged at all. Logger is completely silent

The current log level can be changed interactively (see the log console section) as well as programmatically (see the corresponding good practice section). To change the log level programmatically, at any stage, use Log::getLogger()->setLogLevel(...).

A message box level is also available: any message equals or above the message box level will be shown in a modal QMessageBox dialog. This is very handy to define the level of interaction required in an application:

  • if the application requires a user, it is a good practice to show all message above warning into a QMessageBox modal dialog
  • if the application should run without any user interaction (e.g., a test application) or manages the warning and error differently, then the message box level should be set to NONE

Format of a log message

A log message has always the same format (at least in CamiTK default logger):

yyyy-MM-dd HH:mm:ss.zzz [LOG_LEVEL] [DEBUG_INFO] message

^----- part #1 -------^ ^--- #2 --^ ^--- #3 ---^ ^--- #4 ---^

There are four parts:

  1. The first part is the time stamp of the message (at what date time exactly it is being printed). Note that you can choose to hide the time stamp if you wish.
  2. The message level: it is either ERROR, WARNING, INFO or TRACE
  3. The debug information: depending on your OS, compiler, and context, it generally contains the name of the class, method, and line number of the log message emitter. Note that you can choose to hide the debug information if you wish
  4. The message itself: this is the QString parameter given to the log macro.

Note that if the log macro is used inside a CamiTK inherited class (e.g., Action, Component,…), then the message is prepend with a specific text (for example, if a log macro is called from a camitk::Action or any class inheriting from camitk::Action, then "Action 'TheActionName' - " is automatically added at the beginning of the log message string)

Class inheriting from Action, Component, Viewer, Application, MainWindow, ComponentExtension, ActionExtension and ViewerExtension are currently supported.

Log console

The log console is automatically available in camitk-imp. It can be shown/displayed using the Help → Toggle Log Console menu or by clicking on the console icon at the left of the progress bar. It displays all the log messages above the current log level: CamiTK console

Moreover it allows you to modify the current log level and to show or hide the time stamp or debug information:

  • click on to set the current log level to NONE
  • click on to set the current log level to ERROR
  • click on to set the current log level to WARNING
  • click on to set the current log level to INFO
  • click on to set the current log level to TRACE
  • click on to hide/show the debug information
  • click on to hide/show the time stamp

There are other ways to change the current log level in camitk-imp:

  • You can set the log parameters in the application settings dialog
  • You can test or play with the log parameters using the “Logger Parameters” action

Good practice, tips and tricks

Debugging with CAMITK_TRACE

CAMITK_TRACE is a great tool to be used for temporarily debugging in replacement of std::cout or std::cerr. As debug information can be turned on, you will get the exact location of the log message, and

You can also use CAMITK_TRACE_IF((test),message) for instance to display a message for a given value of a loop iterator or if a given parameter or attribute has a specific value you are currently testing.

It can also be used for information about normal process current step/stage.

No more QMessageBox or modal dialogs

CAMITK_WARNING or CAMITK_ERROR are to be used instead of QMessageBox. The main advantage is that in environment where there is no user available to close the message dialog (such as a test environment) or where specific actions have to be taken when a warning or an error arise (such a clinical test environment) then the application is not blocked and can take the right course of actions. This increase dramatically your code portability.

QMessageBox should be reserved of asking the user some question. When the user interaction is really required, you may therefore use a QMessageBox, but please mark your need with specific comment above the QMessageBox declaration, such as:

// CCC Exception: Use a QMessageBox::question as the user interaction is required

or

// CCC Exception: Use a QMessageBox::warning instead of CAMITK_WARNING as the user interaction is required

(CCC stands for CamiTK Coding Convention)

Using QMessageBox is not recommended as it interrupt the user flow and might reduce the extensibility of your extension. There are a lot of other ways to get some information from the user (parameters in action or properties in component for instance, check the property system of CamiTK).

Think twice before you add a QMessageBox or a modal dialog to your application (e.g., sometimes a default value or a CamiTK property may be better).

Changing the current log level programmatically

To change the log level at any stage in your code (for instance to display the TRACE message only for a portion of the code), use the setLogLevel(..) method:


    ...
    CAMITK_TRACE("This message might not be seen. If current log level is higher than TRACE, then this message is not printed.")

    // store the current log level
    InterfaceLogger::LogLevel appCurrentLogLevel = Log::getLogger()->getLogLevel();
    // change the current log level temporarily, after this line, any TRACE message is shown
    Log::getLogger()->setLogLevel(TRACE);

    ...

    CAMITK_TRACE("This message is guaranteed to be visible")

    ...

    // Restore previous state
    Log::getLogger()->setLogLevel(appCurrentLogLevel);

Other things can be changed programmatically (click on the arrow for more details):

Show/hide the time stamp To show or hide the time stamp, just use the
Log::getLogger()->setTimeStampInformation(bool)
Show/hide the debug information To show or hide the debug information, just use the
Log::getLogger()->setDebugInformation(bool)
Log to a file Every log messages can be written in a log file. The filename is determined automatically using a time stamp, but you can modify the directory of the log file.

Using CamiTK log system in static methods and in classes that do not derived from QObject

You need to use the specific ALT (alternative) log macros in the following cases:

  • inside a static method
  • inside a class that does not derived from QObject
  • inside a function (such as a main(..))

If you use the normal macros and get a compilation error such as:

  • error: invalid use of ‘this’ in non-member function
  • error: no matching function for call to ‘camitk::InterfaceLogger::log(QString, camitk::InterfaceLogger::LogLevel, const char [93], const char [84], int, NameOfANonQObjectDerivedClass*)’

It means you need to use the ALT macros instead. But don’t worry, it just means you have to suffix the normal CamiTK log macros with _ALT, as in the following example:

// in header
class MyClassA : public QWidget {
    Q_OBJECT // this class inherits from QObject through QWidget

    void methodA();
    static void methodB();
}

...

class MyClassB {
    // this class does not inherits from QObject
    void methodC();
}

...

// in implementation
void MyClassA::methodA() {
    CAMITK_TRACE("`this` pointer is available, detailed information can be fetched by introspection.")
}

void MyClassA::methodB() {
    CAMITK_TRACE_ALT("In static method, introspection is not available. Use the ALT macros instead.")
}

...

void MyClassB::methodC() {
    CAMITK_TRACE_ALT("In non-Qt inherited class, introspection cannot be based on Qt mechanism. Use the ALT macros instead.")
}

In static method, the autoreference pointer this is not available, therefore helper methods cannot easily determine the class name and introspection is not guaranteed.

Both Action and Component CamiTK class derived from Qt’s QObject. The log system can therefore takes advantage of Qt introspection mechanism to fetch more debugging information. This is not available for classes that do not derived from QObject.

Log message in your Action

In a CamiTK Action, it is recommended to issue a CAMITK_WARNING in case the the return status is ABORTED (unless the user voluntarily did cancel/abort the action)

New application

If you create your own application, you can change and override the default log settings. You can also create a new logger completely if the default CamiTK logger does not suit your needs.

Translation of the GUI visible messages

It is a good practice to wrap your messages into a tr(..), so that they can be easily translated to another language. In order to collect all the strings properly and allow translators to re-arrange the sentence structures with respect to the grammar of the targeted language, use tr() in conjunction with arg()

CAMITK_TRACE(tr("File %1 saved").arg(filename))
// and if there are more than one parameter use the tr("%1 %2 %3...").arg(..,..,..), e.g.,
CAMITK_TRACE(tr("File %1 saved in directory %2").arg(filename,dirName))

Note that for non-Qt derived class, you can add Q_DECLARE_TR_FUNCTIONS to the class declaration

...
#include <QCoreApplication>
...

class MyClass {
Q_DECLARE_TR_FUNCTIONS(MyClass)  // add at the beginning before any public/private/protected section
...
}

Then it can be used directly in the class:

...
CAMITK_TRACE_ALT(tr("Parsing %1 files...").arg(QString::number(fileCount)))
...

Troubleshooting compilation error on MSVC

A (legacy) MS Windows header defines ERROR as a preprocessor macro. This header is unfortunately sometimes included in low-level library code. This will clash with the CAMITK_ERROR macro.

If you use the CAMITK_ERROR or CAMITK_ERROR_ALT macro in your code and get these compilation errors:

error C2589: 'constant': illegal token on right side of '::'

or

error C2059: syntax error: '::'

Move your #include <Log.h> line at the end of your #include lines.

Therefore #include <Log.h> should be the last #include directive.

No semi-column at the end of a macro line

It is not recommended to add a semi-column sign (;) at the end of a log macro instruction. As the the macro command will be replaced by the proper C++ code, including an ending semi-column, the extra semi-column sign will add an extra NOP.

Moreover if the log macro are completely disabled using the CAMITK_DISABLE_LOG definition flag at compile time, the the semi-column will still be present (and transformed into a useless NOP).

Stylishly speaking, omitting the semi-column also remind the developer that the log messages are created by a corresponding preprocessor macro.

Pretty print pointer value

Sometimes it is quite handy to print the address where a pointer myPointer is pointing to. In order to do that you, you can use the CamiTK helper macro CAMITK_PRINT_POINTER.

For instance, to print the pointer myPointer declared as AnyClass*, you can write:

CAMITK_INFO(tr("checking ") + CAMITK_PRINT_POINTER(myPointer))

This will print:

checking myPointer=0x00005632e5fd5af0

For your information, the macro is defined like this:

#define CAMITK_PRINT_POINTER(PTR)    (QString(#PTR) + QString("=0x%1").arg((quintptr)PTR, QT_POINTER_SIZE * 2, 16, QChar('0')))

To go further

A lot of other things are possible with the CamiTK log system:

  • log all messages to a file
  • show or hide the time stamp or debugging information programmatically

→ Make sure to read the Log and CamiTKLogger class API documentation to go further and improve your productivity.