Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Yet Another Logging Library

0.00/5 (No votes)
9 Jul 2013 1  
Message logging library

Introduction

Good grief. Does the world really need another library for printing trace messages? Probably not, but this one addresses some of the problems that tend to afflict the squillions of other libraries out there:

  • This library lets you use a stream-style syntax i.e. you can write your message like this: LOG_MSG( "The answer is " << iFoo ) ;
  • You can control exactly which messages get printed and which don't. A lot of libraries have the concept of low, medium and high priority messages but the days of my wading through pages and pages of low priority messages searching for the one that I actually wanted are long gone.
  • Uses an ostream-derived class. Why this is cool probably deserves its own section - read on...

Good logging is an essential part of any developer's toolkit. You don't always have the luxury of being able to run your app from an IDE (e.g., NT services or CGI processes) and if you don't know exactly what your program is doing, well, you're in trouble! Furthermore, while this library gives you the option of disabling compilation of all logging code, I'm a big fan of leaving it in for release builds. Then, when (not if!) your customers start to have problems, you can just turn logging on via some hidden switches and then have at least a clue as to what's going on.

In summary, these are the features offered by this library:

  • Log messages can be enabled/disabled at runtime.
  • Output can be sent to any ostream-derived destination.
  • Optional inclusion of message number, date/time stamps and source file location.
  • Automatic indentation of messages (useful for nested or recursive calls).
  • Compile-time switch to enable/disable compilation of logging code.

Why an ostream-based solution is cool

First, a quick primer for those of you who are new to streams. An ostream (or output stream) is simply somewhere where you can send data. That's it! Well, not quite, but I'm not going to go into the differences between a stream and a streambuf here. Look it up. The point is that the code sending the data doesn't have to know the mechanics of how the data gets to where it's going, or even where it's going to. It just gives the ostream a pile of data and says "deal with it!".

So, if you wrote a function like this:

void foo( ostream& os )
{
    os << "Hello world!" << endl  ;
}

this accepts an ostream and sends the message "Hello world!" to it.

cout is a special ostream that sends its output to stdout, so writing:

foo( cout ) ;

would print "Hello world!" to the console.

Similarly, ofstream is an ostream-derived class that sends its output to a file, so this would send the message to the specified file:

ofstream outputFile( "greeting.txt" ) ; 
foo( outputFile ) ;

So, how does all of this relate to this article? CMessageLog is my class that manages log messages by forwarding them on to an ostream object that you specify. By passing cout to the CMessageLog constructor, you can print your trace messages to the console, but by installing an ofstream object, you can send your trace messages to a file. But wait, there's more! I have in the past written an ostream-derived class that sends its data over a socket, so with a single line of code, you could plug one of those babies into this library and have instant remote logging. Cool! Or you could install a stringstream to keep your log messages in memory. Or one that records log messages as rows in a database. One of the guys I work with wants to write a ostream wrapper for OutputDebugString() so that we can send log messages to the debugger (hi Pete - is it ready yet?). I've even written a library to generate PDF's that had a stream-based interface and tried plugging that into this library. Sending trace messages to a PDF: totally useless but a neat validation of the power of streams :-)

Examples

Time for some examples.

This is how to use the library in its simplest form:

#define _LOG // need this defined somewhere to enable logging to be compiled
#include "log/log.hpp"

// create and configure a message log 
CMessageLog myLog( cout ) ;
myLog.enableTimeStamps( true ) ; 
myLog.enableDateStamps( true ) ; 

// log a message 
myLog << "Hello world!" << endl ;

This produces the following output:

01jan02 12:48:19 | Hello world!

Most applications will typically only need the one log and so a global instance is provided for you as a convenience. This object can be accessed via the global function theMessageLog(). Some macros have been defined as well to send messages to this global object:

// let's send our output to a file this time
ofstream logFile( "log.txt" ) ; 
theMessageLog().setOutputStream( logFile ) ; 

// log the message (to the file)
LOG_MSG( "Hello world!" ) ;

Now we'll create some message groups, that is, groups of messages that can be enabled or disabled individually at runtime.

// create our message groups
CMessageGroup gMsgGroup1 ;
CMessageGroup gMsgGroup2 ;
CMessageGroup gMsgGroup3 ;

// enable/disable our message groups
gMsgGroup1.enableMsgGroup( true ) ; 
gMsgGroup2.enableMsgGroup( true ) ; 
gMsgGroup3.enableMsgGroup( false ) ; 

// output some messages 
LOG_GMSG( gMsgGroup1 , "This is a message from group 1." ) ;
LOG_GMSG( gMsgGroup2 , "This is a message from group 2." ) ;
LOG_GMSG( gMsgGroup3 , "This is a message from group 3." ) ;

In this example, only the first two messages would appear. The third would not because its group has been disabled. Note that I used the LOG_GMSG() macro instead of LOG_MSG(). The former will check to see if the message group is enabled before outputting the message, the latter doesn't check anything and unconditionally logs the message.

Full example

Now for a real-life example. Let's say I'm writing a server application that accepts requests on a socket, does some processing and sends back a response. I might want to set up three message groups, one to log incoming requests, one for the processing, and one to log the responses being sent back. Using the helper macros, I might define them like this:

DEFINE_MSG_GROUP( gReqMsgGroup , "req" , "Log incoming requests." ) 
DEFINE_MSG_GROUP( gProcMsgGroup , "proc" , "Log request processing." ) 
DEFINE_MSG_GROUP( gRespMsgGroup , "resp" , "Log outgoing responses." )

Note that I gave each group a name which can be used identify each group in addition to their automatically-assigned numeric ID's. Each one also has a brief description which will be printed out if you call CMessageGroup::dumpMsgGroups(). Take a look at the demo to see how this works.

I also usually define some helper macros of my own to log messages:

#define LOG_REQ_MSG( msg )   LOG_GMSG( gReqMsgGroup , msg )
#define LOG_PROC_MSG( msg )  LOG_GMSG( gProcMsgGroup , msg )
#define LOG_RESP_MSG( msg )  LOG_GMSG( gRespMsgGroup , msg )

Now, I could write my server to be something like this:

void main( int argc , char* argv[] )
{
    // enable any message groups specified in the command line
    CMessageGroup::disableAllMsgGroups( true ) ; 
    if ( argc > 1 )
        CMessageGroup::enableMsgGroups( argv[1] , true ) ;

    // main loop
    for ( ; ; )
    {
        // wait for the next request (let's assume it's just a string)
        string req = acceptRequest() ; 
        LOG_REQ_MSG( "Received a request: " << req ) ; 

        // process the request
        string resp = processRequest( req ) ; 

        // return the response 
        LOG_RESP_MSG( "Sending response: " << resp ) ; 
    }
}
    
string processRequest( const string& req )
{
    // process the request 
    LOG_PROC_MSG( "Processing request: " << req ) ; 

    // return the response (just the same string as the request)
    return req ;
}

Now, when I start my server app, I can specifiy which message groups I want enabled:

    server.exe req,resp   <== log requests & responses only, no processing

I would also add command line switches to turn on date/time stamping, etc.

You could also, of course, add a UI to dynamically enable or disable message groups by calling enableMsgGroup() for the appropriate CMessageGroup objects. Or perhaps periodically reload the settings from an INI file.

Summary

I've been lurking around CodeProject for a long time and figured it was about time I got off my butt and put something back in. This is one of the hardest-working libraries in my toolkit and while the implementation is a bit clunky - it was written way back in '97, pretty early on in my C++ days - I hope you guys find it useful. Cheers :-)

Revision History

  • 4 Nov 2002 - Initial editing.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here