Event monitoring is a key feature in debugging applications and should be implemented from the start in your C++ programs. It will later help you discovering the origin of errors when deploying your software to a large variety of computers. In this post, I will cover an easy way to implement event monitoring in your own applications using a program that I developed for the [∞] OpenRAMAN SpectrumAnalyzer Suite.
The method I will be using here is based on a program called EVEMON.EXE that you will find on the OpenRAMAN software page starting from revision r123. The program source code is given under a CERN OHL-W v2 license. Long story short but EVEMON obviously stands for “EVEnt MONitoring” with an extra reference to the hacker world where Eve is the common name of eavesdroppers – the application doing just that with compatible softwares.
Briefly explained, compatible software that you develop will use a mechanism to send messages to the EVEMON.EXE program which will then show them in a list format that you may then export for later analysis. When someone is experiencing troubles running your software, just ask them to create a report using the EVEMON.EXE program so that you can track what went wrong. A screenshot of the program running is given in Figure 1 where you can see it monitoring the SpectrumAnalyzer.exe program of OpenRAMAN.
The program will discriminate between several types of messages including debug messages, warnings, errors, critical errors and exceptions. The debug message shall be used to give an overview of the major steps the application is performing like when it starts up, save a file, connect to a camera… The warnings, errors and critical errors message will inform of potential troubles happening in the program. Finally, the exceptions type will show you all exceptions that were thrown in your program.
To trigger these messages in your program, just use the macros defined in evemon.h:
_error("Unable to connect to camera!");
As simple as that!
There is one special case which are the exceptions. To send exceptions messages, you can use the throwException() function instead of throw and derive all your exceptions from the base IException class:
throwException(InvalidFilenameException, pszFilename);
The good news is that the previous code will automatically display all the exceptions, even if they were silently caught by a catch(…){} statement that will do nothing.
The mechanism behind these is actually fairly simple:
// macro to notify events
#define _event(type, file, line, szfmt, ...) { \
auto p = EventMonitor::getInstance(); \
\
if (p != nullptr) \
p->notify(type, file, line, format(szfmt, __VA_ARGS__)); \
}
#define _debug(szfmt, ...) _event(EventType::DBG_MESSAGE, __FILE__, __LINE__, szfmt, __VA_ARGS__);
#define _warning(szfmt, ...) _event(EventType::DBG_WARNING, __FILE__, __LINE__, szfmt, __VA_ARGS__);
#define _error(szfmt, ...) _event(EventType::DBG_ERROR, __FILE__, __LINE__, szfmt, __VA_ARGS__);
#define _critical(szfmt, ...) _event(EventType::DBG_CRITICAL, __FILE__, __LINE__, szfmt, __VA_ARGS__);
// generic exception class
class IException
{
public:
virtual std::string toString(void) const = 0;
};
// send message to event monitor
void hookException(const IException& rException, const char* pszFilename, unsigned int uiLineNumber)
{
auto p = EventMonitor::getInstance();
if (p != nullptr)
p->notify(EventType::DBG_EXCEPTION, pszFilename, uiLineNumber, rException.toString());
}
#define throwException(ExceptionType, ...) { ExceptionType e{__VA_ARGS__}; hookException(e, __FILE__, __LINE__); throw e; }
Everything therefore boils down behind the EventMonitor singleton class and its notify function.
Before we dig into the boilerplate code, I would like to briefly explain how I used to implement logger previously and why I recently switched to the current implementation.
Previously, all my loggers used a fairly simple mechanism for their notify function which was to write the data to a log.txt or log.html file. This works fine but has a limitation. When you have several program (or DLLs!) running in the same folder, each should have its own log file to avoid overwriting other programs log data. This makes debugging of programs that are dependant on each others more difficult. A typical example is an .exe file calling functions in a .dll file. Unless you implement a complex mechanism to pass the pointer of the .exe singleton object to the .dll file (which I did use for several years!), you have to work with two different files. And this trick is limited to an .exe/.dll and will not work between two .exe files in a client/server relation. Also, you need a mechanism to tell the application when to debug and when not to. Debugging adds extra overhead to your application, especially when you have to write data to files, so you want to be sure to activate it when you need it only.
All these problems are solved with the approach I used in EVEMON.EXE.
When your software creates an instance of the EventMonitor singleton class, the first thing that it will do is trying to locate the program EVEMON.EXE. If the program is running, we save a pointer to its HWND handle. Otherelse, the event monitoring is disabled with minimal overhead to your application (just a if/return statement). When the notify function is called, and if the HWND handle is valid, an object containing data representing the event will be created including a timestamp, event type (warning, error, exception…), name of the program and message content. The data will then be sent to EVEMON.EXE using the WM_COPYDATA Windows message. And that is all! No multi-access to take care of, not even to worry about concurrency because the WM_COPYDATA is thread safe! What to ask for more? Simple and effective!
When EVEMON.EXE receives the COPYDATA message, it will simply add it to its list and display information on screen. You may have a look at how this is exactly done in the source code of the program but it’s nothing more complicated than that.
The simplicity and advantages that this approach gives made it my standard for debugging application and I will certainly use it in all my other projects from now on.
Before I conclude this post, there is one more thing that I have to address which is the potential recursive behaviour of the notify function. Imagine you call the _warning(…) function and that it generates an exception inside because something went wrong (no more memory etc.). The throwException will catch this and send a new notification message. If the problem is recurrent (like no more memory) the function will throw an exception again and so on. This is why it is important that the notify function should abort immediately if it detects a recursive call (call of notify() inside a notify() call). To do that I create the RLOCK class (recursive lock) which will tell you if we are in a recursive call or not. It is relatively simple and function with a flag that is turned on as we enter the function and turned off as we exit it. To handle concurrency, each thread will have its own flag mapped by the GetCurrentThreadID function of windows. Et voilà!
I would like to give a big thanks to James, Daniel and Mikhail who have supported this post through Patreon. I also take the occasion to invite you to [∞] donate through Patreon, even as little as $1. I cannot stress it more, you can really help me to post more content and make more experiments!
[⇈] Top of PageYou may also like:
[»] Instancing Objects from Strings in C++
[»] A concurrent job system in C++ with dependencies
[»] Spectrum Analyzer Software for OpenRAMAN