5,550,131 members and growing! (19,183 online)
Email Password   helpLost your password?
Development Lifecycle » Debug Tips » Trace     Intermediate

Yet Another Logging Library

By Taka Muraoka

Message logging library
VC6, VC7, C++Windows, NT4, Win2K, WinXPVS6, Visual Studio, Dev, QA

Posted: 2 Nov 2002
Updated: 2 Nov 2002
Views: 50,502
Bookmarked: 30 times
Announcements
Want a new Job?



Search    
Advanced Search
Sitemap
16 votes for this Article.
Popularity: 4.89 Rating: 4.06 out of 5
1 vote, 8.3%
1
2 votes, 16.7%
2
1 vote, 8.3%
3
2 votes, 16.7%
4
6 votes, 50.0%
5

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

About the Author

Taka Muraoka



Occupation: Other
Location: Australia Australia

Other popular Debug Tips articles:

Article Top
Sign Up to vote for this article
You must Sign In to use this message board.
FAQ FAQ Noise ToleranceSearch Search Messages 
 Layout  Per page   
 Msgs 1 to 10 of 10 (Total in Forum: 10) (Refresh)FirstPrevNext
Subject  Author Date 
GeneralUsing in a DLLmemberPeterDavid9:15 13 Nov '06  
GeneralRe: Using in a DLLmemberTaka Muraoka6:48 14 Nov '06  
GeneralRe: Using in a DLLmemberPeterDavid9:42 14 Nov '06  
GeneralRe: Using in a DLLmemberTaka Muraoka14:09 14 Nov '06  
GeneralIncorporating into Existing ProjectmemberSteve Johns12:04 16 Jun '06  
GeneralRe: Incorporating into Existing ProjectmemberTaka Muraoka15:24 16 Jun '06  
Generalnot ANSI C++sussBonio Lopez1:03 14 May '03  
GeneralRe: not ANSI C++memberTaka Muraoka5:12 14 May '03  
GeneralBut for advanced users ...sussAnonymous5:27 4 Nov '02  
GeneralPerhaps you meant: For advanced, non-commercial users?memberMichael Curry6:22 13 Nov '03  

General General    News News    Question Question    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

PermaLink | Privacy | Terms of Use
Last Updated: 2 Nov 2002
Editor: Brian Delahunty
Copyright 2002 by Taka Muraoka
Everything else Copyright © CodeProject, 1999-2008
Web12 | Advertise on the Code Project