|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
IntroductionGood 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:
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:
Why an ostream-based solution is cool
First, a quick primer for those of you who are new to streams. An 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.
foo( cout ) ;
would print "Hello world!" to the console.
Similarly,
ofstream outputFile( "greeting.txt" ) ;
foo( outputFile ) ;
So, how does all of this relate to this article? ExamplesTime 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
// 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 Full exampleNow 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 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 SummaryI'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 History4 Nov 2002 - Initial Editing
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||