OSE - C++ Library User Guide

Graham Dumpleton
Dumpleton Software Consulting Pty Limited
PO BOX 3150
Parramatta, 2124
N.S.W, Australia
email: grahamd@nms.otc.com.au

Table of Contents

Message Log Facility
OSE - C++ Library User GuideMessage Log Facility

Message Log Facility

1 Log Messages

At run time, if your application experiences an error, whether or not the error causes program termination, you will most likely want to know the nature of the error. Knowing the errors which are occurring will allow you to modify your application or the operating environment for the application so as to correctly deal with that error.

The log facility provides you with a mechanism for generating and capturing messages generated by your application. Similar to the UNIX system logger facility, you can log messages at a particular priority. Although similar to the UNIX system logger, it does not actually use the UNIX logging facility. If required, it could be extended to pass messages on to the UNIX syslog facility.

2 The Logger Class

The interface to the logging facility is the class OTC_Logger. To log a message, you should call the static function OTC_Logger::notify(). For example:

  #include <OTC/debug/logger.hh>

OTC_Logger::notify(OTCLIB_LOG_DEBUG,"a debug message");
The first argument to the notify() function is the message priority or level. There are nine different levels at which you can log a message. Each of the levels will be described further below. The second argument to the notify() function is the message which you wish to have logged. The logger facility does not take responsibility for deleting the message. If the message is not a string literal, you will need to make sure you delete it after calling the function.

The notify() function does not accept variable argument lists in the style of printf(). Therefore, if you want to format a message, you will need to do it before calling the notify() function. To format a message you could use the sprintf() function. Alternatively, you could use the streams interface to the logging facility as implemented by the OTC_LogStream class. More information on the streams interface will be provided later in this document.

By default, when you call the notify() function, your message will be displayed on the standard error output. The format of the message displayed will be:

  DEBUG: a debug message
The first half of the message is the message header, and lists the priority level at which the message was logged. The second half is the actual message.

If required, a verbose message header can be generated. The verbose message header will include a time stamp and the process ID of the program which generated the message. To enable the generation of the verbose message header you should set the environment variable OTCLIB_LOGLONGFORMAT before running your program.

3 Message Priority

There are nine different levels at which you can log a message. The lower eight levels are equivalent to the priority levels used by the UNIX syslog() function. The names used in the library and their intended usage are:

----------------------------------------------------------------
Priority              Usage                                       
----------------------------------------------------------------
OTCLIB_LOG_EMERGENCY  A panic condition.                          
OTCLIB_LOG_ALERT      A condition that should be corrected        
                      immediately, such as a corrupted system     
                      database.                                   
OTCLIB_LOG_CRITICAL   Critical conditions, such as hard device    
                      errors.                                     
OTCLIB_LOG_ERROR      Errors.                                     
OTCLIB_LOG_WARNING    Warning messages.                           
OTCLIB_LOG_NOTICE     Conditions that are not error conditions,   
                      but that may require special handling.      
OTCLIB_LOG_INFO       Informational messages.                     
OTCLIB_LOG_DEBUG      Messages that contain information nor       
                      mally of use only when debugging a pro      
                      gram.                                       
OTCLIB_LOG_TRACE      Messages related to tracing program         
                      execution.                                  
----------------------------------------------------------------

4 Disabling/Enabling Output

If OSE has been installed in the standard manner, when you link with either of the `dbg', `prf' or `std' variants of the OSE C++ libraries, messages will by default be displayed. To turn off the display of log messages to the standard error output, you can set the environment variable OTCLIB_NOLOGSTDERR before you run your program.

If you are linking with the `opt' variant of the OSE C++ libraries, messages will by default not be displayed to standard error output. To turn on the display of messages in this case, you can set the environment variable OTCLIB_LOGSTDERR before running your program.

The difference in the default behaviour is that the `opt' variant of the OSE C++ libraries is compiled with the symbol NDEBUG defined. Detection of this symbol when compiling the library is used to change the default behaviour to that of not displaying the log messages.

The intention is that a release version of an application should have the display of log messages off by default. If however, a customer was having a problem, you could again enable the display of the messages by having the customer set the OTCLIB_LOGSTDERR environment variable before they ran the program. This means you do not have to compile out of your application code which generates log messages. Linking with the `opt' variant of the OSE C++ libraries will automatically change the display of messages to being off by default.

As the logger facility could be used before the `main()' function is executed, environment variables are the only reliable way of enabling and disabling output of messages to the standard error output. If your application doesn't rely on initialisation of static objects, you can also rely on static member functions of the OTC_Logger class for enabling and disabling output of messages to the standard error output. You would call the functions provided, at the start of the `main()' function to set the state of the log system as required. The member functions of the OTC_Logger class are:

  OTC_Boolean OTC_Logger::stderrOutputEnabled();
// Returns <OTCLIB_TRUE> if output of
// log messages to <stderr> is enabled.

void OTC_Logger::enableStderrOutput();
// Enables output of log messages to
// <stderr>.

void OTC_Logger::disableStderrOutput();
// Disables output of log messages to
// <stderr>.

5 Saving Messages to a File

If you need to capture the log messages into a file, you can set the environment variable OTCLIB_LOGFILE to the name of the file before you run your program. If the file cannot be opened, the request to send messages to a log file will be ignored. If the file can be opened, it will be truncated before being used. If you do not want the file to be truncated, you should set the environment variable OTCLIB_APPENDLOGFILE.

If the environment variable OTCLIB_LOGFILE is set and the file could be opened, messages will always be sent to the file regardless of which variant of the OSE C++ libraries your program was linked with. The message headers will be, by default, displayed in their abbreviated form. To distinguish between successive runs of the same program, you should set the environment variable OTCLIB_LOGLONGFORMAT to enable the display of the process ID and time stamp in the message header. A single log file should not be used for two concurrently executing programs, as no controls exist to ensure that a process doesn't corrupt information generated by another process.

To embed information about the name of the machine the process is executing on, and the process ID, two special tags may be included in the name defined in the environment variable OTCLIB_LOGFILE. These tags will be expanded as appropriate when the log file is opened. To include the name of the machine in the log file name, you should use `%h'. To include the process ID, you should use `%p'. For example, `/usr/tmp/app.%h.%p'.

When you define the name of the log file, you should always use an absolute pathname. This is necessary, as the log file is opened at each point that a message needs to be written to it. If a relative pathname was used, and the working directory of the process changed, the log file would be written relative to the new working directory. As the log file is opened each time a message needs to be written, if the tag `%p' is used, a separate log file will automatically be used when a process forks.

As the logger facility could be used before the `main()' function is executed, an environment variable is the only reliable way of specifying a log file into which all log messages will be captured. If your application does not rely on initialisation of static objects to perform work, the name of the log file may be set from the `main()' function using static member functions of the OTC_Logger class. The functions provided are:

  char const* OTC_Logger::logFile();
// If a log file is currently being used,
// the name of the file is returned.
// Returns <0> if no log file specified.

void OTC_Logger::setLogFile(char const* theFile);
// Sets the name of the log file. All
// log messages will now go to this
// file until the process terminates
// or the file is changed.
The tags `%h' and `%p' can also be used in the string passed to the setLogFile() function.

6 Daemon Processes

When the logger displays messages onto the standard error output, it does so by writing directly to file descriptor `2'. If your application is a daemon process, which must close all file descriptors, you should ensure that display of messages on the standard error output is always disabled. To do this your application should call the function OTC_Logger::disableStderrOutput() before any use of the logger has occurred. Because log output could occur as the result of work undertaken in the initialisation of static objects, daemon type processes should avoid the use of static objects to automatically initialise the resources of an application.

7 Redirecting Messages

As noted in the previous section, the logger displays messages on the standard error output by writing error messages directly to file descriptor `2'. If you need to change the file descriptor the program uses to display the messages, you can set the environment variable OTCLIB_LOGFD before you run your program. Alternatively, you may set this environment variable from within your program before the logger has been used for the first time.

As an example, you might set the OTCLIB_LOGFD environment variable so that you can send log messages through a filter program, which sends you mail when a high priority message occurs. This could be done using the shell `rc' as follows:

  OTCLIB_LOGFD=3 program |[3] filter
Note that the OTCLIB_NOLOGSTDERR environment variable, and corresponding static member function of the OTC_Logger class, do not affect log output when an alternate file descriptor has been defined using the OTCLIB_LOGFD environment variable.

8 Macro Interface

While you are still developing an application, it is often preferable that a program does not stop when an error occurs, but recovers as much as possible and keeps running. Although, the program is kept running, you would like to be notified that a problem has occurred and where. The logger can be used to generate these messages, but having to insert the name of the file and line number in the file where the problem occurred is laborious.

For this situation the OTCLIB_WARNING() macro is supplied. This macro takes a single argument which is the message to be displayed. When the message is sent to the logger it will be sent with priority OTCLIB_LOG_WARNING. In addition to the message, the name of the file, and line in the file where the OTCLIB_WARNING() macro was used, will also be displayed. For example:

  OTCLIB_WARNING("error has occurred");
would produce the messages:

  WARNING: error has occurred
WARNING: Location: "file.cc", line 42

9 Streams Interface

If you need to send formatted messages to the logger, you need to compose the message before sending it. One method of formatting the message is to use the C library function sprintf(). Alternatively you can use the streams interface to the logger system. The streams interface to the logger system is implemented by the class OTC_LogStream.

To use the OTC_LogStream class, you need to create an instance of the class on the stack, supplying to it, a memory buffer into which it can format messages. You also need to provide the size of the memory so that it will not overwrite the end of the memory buffer. Once created, you can use the OTC_LogStream class like a normal stream class. Each time you flush the stream however, the formatted message will be passed to the logger and the memory buffer cleared ready for the next message. Flushing of the stream will occur automatically when you use the `endl' or `flush' manipulators, if you explicitly call the flush() member function, or if the memory buffer is filled. The priority at which the message will be displayed will be OTCLIB_LOG_DEBUG by default. This can be changed by using the setLevel() member function.

An example of using the OTC_LogStream class is:

  #include <OTC/debug/logstrm.hh>

char buf[1024];
OTC_LogStream log(buf,1024);

log.setLevel(OTCLIB_LOG_WARNING);

log << "error has occurred" << flush;
log << "Location: \"" << __FILE__;
log << "\", line " << __LINE__ << flush;
The size of the message you can send will be limited by the size of the memory buffer you use for formatting. Preferably, you should create the memory buffer on the stack as this will mean that no memory will be allocated from the free store at any time. If you want to generate a message to indicate that memory has been exhausted, this would be quite important.

Instead of creating an instance of the OTC_LogStream class each time you wish to log a message, you can also use the function OTCLIB_LOGGER() to access a central log stream. This central stream is allocated in static memory so it is safe to use it if memory has been exhausted. The maximum length message which you can format using the central log stream is 2048 characters. When you call the function to access the stream you must supply the level at which you want messages to be logged. For example:

  OTCLIB_LOGGER(OTCLIB_LOG_WARNING)
<< "error has occurred" << flush;

OTCLIB_LOGGER(OTCLIB_LOG_WARNING)
<< "Location: \"" << __FILE__
<< "\", line " << __LINE__ << flush;
You can only format one message at a time when using the central log stream. The central log stream is not safe to use in a multi-threaded application.

10 Adding your own Logger

The standard logging system only allows you to display messages on a specific file descriptor, standard error output by default; or capture the messages into a file. If you want to do something else with the log messages, you can introduce your own logger class into the system by deriving a new class from the OTC_Logger class and defining the log() member function. When you create an instance of your class it will be linked to any other loggers which exist. Each time a message is logged, as well as being displayed in the normal manner, the message will be sent to the instance of your logger class.

An example of a logger which passes messages onto the UNIX system logger is illustrated below. Note that this example presumes that the UNIX system log had already been initialised using the openlog() function.

  #include <OTC/debug/logger.hh>

class EX_Syslog : public OTC_Logger
{
protected:

void log(
OTC_LogLevel theLevel,
char const* theMessage
);
};

void EX_Syslog::log( OTC_LogLevel theLevel,
char const* theMessage
)
{
if (theLevel != OTCLIB_LOG_TRACE)
syslog(theLevel,theMessage);
}
If you define your own logger, you should not allocate memory in the log() function as the log message may be to report that memory has been exhausted. You should not take responsibility for deleting the log message as this is the responsibility of the user who logged the message. Also, you should not use the form() function to format messages, as the original message may have been created using form() and using it again will destroy the original message.