Main Page | Class Hierarchy | Class List | File List | Class Members | File Members | Related Pages | Examples

ThreadedLogger Documentation

Author:
Jens Andersson
The RDA defines a logger interface and by default provides a logger implementation which writes messages to stdout.

This document explains how to switch to the ThreadedLogger, which is another logger implementation packaged with the RDA. It is available for Linux, Lynx and Windows (with almost identical code), and has been used for a few years in the OPC gateway.

The ThreadedLogger has three features:

These are more or less independent of each other. It can also optionally write the messages to standard out.

If you are trying to find a bug which is causing your program to crash hard, do not use the ThreadedLogger. Instead, use the normal stdout default logger (and/or a debugger), which will not buffer and probably lose the last few queued messages in case of a program crash, or at least disable the separate thread (and thereby buffering above the file level). The ThreadedLogger is intended for programs stable enough to run unattended, recovering automatically from errors.

If you are very tight on memory and sometimes heavily loaded, it might also be better to use the stdout logger. Otherwise, there is a risk you will queue up a lot of log messages before the log writer thread gets a chance to run and thereby temporarily consume too much memory.

The RDA should first be configured to use this logger implementation. The log messages are passed from the RDA to the ThreadedLogger, which formats them to a string. If the separate thread is enabled, the messages are now placed in a queue which is flushed about once every three seconds by the log writer thread. If the separate thread is disabled, the message is written immediately by the originating thread. If the file is found to be too big, the old history file is removed, the current is renamed to the old, and a new file is started. If the logger is configured to write log events to standard out, this is done. Finally, if a remote logging server has been specified, one UDP packet is sent for each log message.

The implementation is thread safe -- once the setup is finished, logging can be done concurrently by different threads. The only requirement is that the JTC threading package must been initialized using the JTCInitialize object (see below).

It is recommended to initialize the ThreadedLogger from config file properties. This is done by constructing the ThreadedLoggerFactory with an argument true, which will make it read the following properties:

# enable output to a file                               (default: true)
cmw.threadedLogger.enableFileOutput=true
# name of log file
cmw.threadedLogger.logfileName=/tmp/exampleLogFile.txt
# name of the previous log file, copied when the log file starts over
cmw.threadedLogger.oldLogfileName=/tmp/exampleLogFile-old.txt

# the host to send UDP log events to                    (disabled if not set)
cmw.threadedLogger.logServerHostname=abcmw1
# the port of the host to send UDP log events to        (disabled if not set)
cmw.threadedLogger.logServerPort=1500

# an identifier per program type, same for all identical binaries  [A-Za-z]+
cmw.threadedLogger.sourceType=ProgramName
# an identifier per instance, typically the RDA server CORBA name  [A-Za-z]+
cmw.threadedLogger.sourceID=ServerInstanceName

# start a new log file after a certain number of events (default: true)
cmw.threadedLogger.boundedLogSize=true
# write log events using a separate thread              (default: true)
cmw.threadedLogger.useSeparateThread=true
# print log events also on standard out                 (default: false)
cmw.threadedLogger.enableStdoutOutput=false

Always in use is of course the property controlling the general RDA level:

cmw.defaultLogLevel=ALL

To tell the RDA that you want to use the ThreadedLogger, add a call to rdaLogger::init before doing anything that can result in log messages to be written, causing implicit default logging to be initialized. (If you want to change the config file being used, you should do that first with rdaConfig::useConfigFile.)

#include <rda/ThreadedLogger.h>
int main(int argc, char* argv[]) {
   ThreadedLoggerFactory threadedLoggerFactory(true);
   rdaLogger::init(threadedLoggerFactory);

   //...

Now everything is set up, and you can log from anywhere using the normal rdaLogger interface. Make sure that the logger factory object is not destroyed until the program has called rdaLogger::destroy() (see below, under "Shutting down"). Logging is done like this:

#include <rda/Logger.h>
   rdaLogger::getLogger("some.logger.name")->error("An error message!");

The log messages will always include a millisecond timestamp and a thread identifier, in addition to your log name and message. The log name is often chosen to be the same as the class or subsystem containing the log output. Logger names should not include any special characters (or spaces), but simply be period-separated strings. Log viewers will typically present the logger names in a hierarchy, with the root level at the start of the string.

The threshold for log messages can be controlled programmatically at runtime per logger or set for all. The default level is specified with the property cmw.defaultLogLevel in the config file.

   rdaLogger::setDefaultLevel(rdaLogger::LOG_ALL);                         // set for new
   rdaLogger::setLevels(rdaLogger::LogLevel::LOG_ERROR);                   // set all
   rdaLogger::getLogger("some.logger.name")->setLevel(rdaLogger::LOG_ALL); // set for one

External tools, such as CMWadmin, will allow you to connect to a running server and modify the log levels on it dynamically.

When it is time for your program to exit, call rdaLogger::destroy() as one of the final things you do. After that, all loggers are invalid and cannot be used.

   rdaLogger::destroy();

If you don't call rdaLogger::destroy(), the log writer thread will keep running and your program won't terminate as the JTCInitialize destructor blocks until all created threads have terminated. If you call exit() explicitly, the log writer thread might not have written all queued log messages yet.

You might find it convenient to use the following macros defined in the LogStreamMacros.h file. Combined with strstream, they make it possible to print out complex data structures on a single line, without having to care much about types or format modifiers. Note that these macros are not ThreadedLogger specific: they can be used with any type of rdaLogger.

#define RDALOGGER_LOG_MESSAGE_OUTPUT(LOGMETHODNAME, LOGLEVEL, LOGMESSAGE, LOGGERNAME) \
   if(rdaLogger::getLogger(LOGGERNAME)->isLoggable(LOGLEVEL)) { \
   std::ostrstream theTemporaryStream_unique; \
   theTemporaryStream_unique << LOGMESSAGE << (char) 0; \
   rdaLogger::getLogger(LOGGERNAME)-> LOGMETHODNAME (theTemporaryStream_unique.rdbuf()->str()); \
   theTemporaryStream_unique.rdbuf()->freeze( false ); \
   }


#define LOG_ERROR(LOGGERNAME, LOGMESSAGE) \
   RDALOGGER_LOG_MESSAGE_OUTPUT(error, rdaLogger::LOG_ERROR, LOGMESSAGE, LOGGERNAME)
#define LOG_WARNING(LOGGERNAME, LOGMESSAGE) \
   RDALOGGER_LOG_MESSAGE_OUTPUT(warning, rdaLogger::LOG_WARNING, LOGMESSAGE, LOGGERNAME)
#define LOG_INFO(LOGGERNAME, LOGMESSAGE) \
   RDALOGGER_LOG_MESSAGE_OUTPUT(info, rdaLogger::LOG_INFO, LOGMESSAGE, LOGGERNAME)
#define LOG_TRACE(LOGGERNAME, LOGMESSAGE) \
   RDALOGGER_LOG_MESSAGE_OUTPUT(trace, rdaLogger::LOG_TRACE, LOGMESSAGE, LOGGERNAME)
#define LOG_DEBUG(LOGGERNAME, LOGMESSAGE) \
   RDALOGGER_LOG_MESSAGE_OUTPUT(debug, rdaLogger::LOG_DEBUG, LOGMESSAGE, LOGGERNAME)

The following code snippet illustrates the use of convenience macros.

#include <rda/Logger.h>
#include <rda/LogStreamMacros.h>
  int someValue = 5;
  string someString = "test";
  LOG_ERROR("system_x.subsystem_y",
    "Whops! Something is " << someValue << " and something else is " << someString);

  ObjectWithOperatorLessLess someObject;
  LOG_DEBUG("system_x.subsystem_y",
    "Some object is " << someObject << " right now");

There are a couple of things which might be improved or added in a future version:

See also:
an example of how to use ThreadedLogger

RDA-2.3 documentation - 27 Jun 2007 - N.Trofimov