CAD Exchanger SDK
Logging Support

Preface

Design of the logging mechanism follows one of the Apache library log4cxx (http://logging.apache.org/log4cxx/) which seems to be a reputable reference design for many other derived logging systems. However, CAD Exchanger follows some selected concepts of log4cxx and provides a few extensions for greater flexibility.

Overview

During execution of the CAD Exchanger algorithms there could be situations which could be worth drawing user's attention. These may range from specific configurations of geometrical objects in the file being imported to some computational errors.

Internally CAD Exchanger tries to communicate this to a global logger object returned by Base_Logger::GlobalInstance(), which follows the singleton pattern.

Reported messages are assigned a respective severity level (identified by enumeration Base_Logger::Level). The level ranges from Base_Logger::Trace, e.g. simple notifications that some particular algorithm is being entered or exited, to Base_Logger::Fatal, which effectively means the application cannot continue and has to abort.

The logger has a threshold level. The messages sent with the same or greater level are accepted, the ones with a lower level are ignored. If the message is accepted, then it is forwarded to all so called appenders registered in the logger for further processing. (The term 'appender' is derived from the log4cxx library.) Appenders subclass Base_LoggerAppender and implement how the message should be processed (e.g. saved in the trace file, displayed in the GUI widget, and so on). Thus, essentially the appenders and the logger implement the observer pattern.

Message delivery is synchronous, i.e. the control is not returned from the logger until all appenders process the message. To enable asynchronous delivery, the library provides Base_LoggerAsyncAppender.

By default, the logger has no appenders and has the highest threshold level and therefore ignores all the messages.

Usage of the logging mechanism by the SDK user should normally be limited to setting a desired logger's threshold level, creating a prebuilt observer or a custom one, and registering/unregistering it in the logger. The following code snippet demonstrates this:

// Returns either prebuilt or custom appender, with ensured proper life-span
static Base_LoggerAppender& GetAppender()
{
Base_LoggerAppender& anAppender = ...;
return anAppender;
}
static void StartLogging()
{
Base_Logger& aLogger = Base_Logger::GlobalInstance();
aLogger.SetLevel (Base_Logger::Warning);
Base_LoggerAppender& anAppender = GetAppender();
aLogger.Register (anAppender);
}
static void StopLogging()
{
Base_Logger& aLogger = Base_Logger::GlobalInstance();
Base_LoggerAppender& anAppender = GetAppender();
aLogger.Unregister (anAppender);
}

There should be no practical need to send own custom messages via the logger by the SDK user.

Message severity level

Message level is defined by an enumeration Base_Logger::Level. The following levels are supported (given in the order of decreasing severity):

  • fatal errors
  • errors
  • warnings
  • information
  • debug
  • trace

Base_Logger::SetLevel() sets a threshold level for the messages which should be accepted by the logger and dispatched to the appenders. The messages sent with the same or greater level are accepted, the ones with a lower level are ignored.

The debug and trace levels are reserved for internal purposes and should not be used by the SDK users. An attempt to set either of these levels will set an information level.

Note
Currently no messages are sent with the fatal level.

Appenders

Appender implementation should subclass the abstract class Base_LoggerAppender and implement virtual method Base_LoggerAppender::Append() accepting a message string (in Unicode).

CAD Exchanger provides a few appender implementations which can be sufficient for most frequent use cases.

Appenders are registered in the logger with Base_Logger::Register(). The method accepts an optional range of levels that the appender will accept, essentially serving as a filter for the particular appender:

Base_Logger& aLogger = Base_Logger::GlobalInstance();
aLogger.SetLevel (Base_Logger::Info);
Base_LoggerStreamAppender aCErrAppender (std::wcerr); //specific appender for errors
aLogger.Register (aCErrAppender, Base_Logger::Error, Base_Logger::Error);
//run the algorithm...
DoSomething();
aLogger.Unregister (aCErrAppender);

For instance, one appender might only accept errors, another - warnings, and so on. By default, the appender is registered with the full range and thus accepts all the messages which have been dispatched by the logger.

The message is dispatched to the appenders in the order they were registered in the logger.

The appender must be unregistered from the logger (Base_Logger::Unregister()) before own destruction. Otherwise the logger will store a dangling pointer and dispatching the next accepted message will trigger a crash.

To register a temporary appender with a life span of a scope you may use the helper class Base_LoggerAppender::Sentry which implements the RAII pattern:

{
Base_Logger& aLogger = Base_Logger::GlobalInstance();
Base_LoggerAppender anAppender (...); //
Base_LoggerAppender::Sentry aSentry (aLogger, anAppender);
//run an algorithm...
//the Appender will be unregistered from the aLogger in aSentry destructor
}

Prebuilt appenders

CAD Exchanger provides a handful of useful appenders that can be directly used.

File appender

Base_LoggerFileAppender allows to output messages into the file :

...
Base_LoggerFileAppender aLogFileAppender (aLogFileName);
Base_LoggerAppender::Sentry aSentry (aLogger, anAppender);
...

Stream appender

Base_LoggerStreamAppender allows to output messages into the stream. To output messages to standard output the following example can be used:

{
Base_Logger& aLogger = Base_Logger::GlobalInstance();
Base_LoggerStreamAppender anAppender (std::wcout); //note the wcout
...
}

Asynchronous appender

Base_LoggerAsyncAppender allows to process messages asynchronously. The appender forwards the received message to other appenders registered with Base_LoggerAsyncAppender::Register(). Forwarding happens in a separate thread created for each asynchronous appender.

This appender is useful when message processing can be time-consuming and thus would block algorithm execution for too long if happened synchronously.

The following code snippet demonstrates usage of the asynchronous appender:

Base_Logger& aLogger = Base_Logger::GlobalInstance();
Base_LoggerAsyncAppender anAsyncAppender;
{
Base_LoggerAppender::Sentry aSentry (aLogger, anAsyncAppender);
MyVeryHeavyAppender aHeavyAppender = ...;
anAsyncAppender.Register (aHeavyAppender);
DoSomething();
}

Performance considerations

Internal implementation minimizes overhead associated with the use of logging mechanism.

By default, when the logger has no registered appenders and has the highest message level threshold, the decision to ignore a message approximately equals to about one function call and one integer comparison. The message is constructed in a lazy manner, i.e. after the logger responded that it would accept the message.

In the case when a logger accepts a message the performance is essentially determined by the performance of a particular implementation of Base_LoggerAppender::Append() method.

If you have to use heavy-weight appenders consider using asynchronous appender (Base_LoggerAsyncAppender) as a proxy object.

Multi-threading considerations

The logger provides thread-safe management of the list of registered appenders. If the messages are being fed into the logger from multiple threads then the next message will be accepted only after the previous message has been processed by all appenders.

The asynchronous appender (Base_LoggerAsyncAppender) also provides thread-safe management of its own appenders. All appenders process accepted messages in a single thread created by the asynchronous appender.

Thus normally there should be no situations when any appender is accessed for writing from multiple threads, and therefore no special efforts should be applied at user's level to ensure thread-safety. Of course, access to the same resource by different appenders (e.g. global standard output accessed by different stream appenders from different asynchronous appenders) is not thread-safe.