This article is intended for anyone who is looking for a new debugging tool to add to their arsenal. This article is divded into the following sections:
1. The case for RTF log filesFirst of all, I'd like to let you know that if you don't have the time or inclination to read through all of this, theres a link at the end of this article to the source being discussed. Second, all code accompanying this article will be written as stand-alone functions. I personally use a C++ singleton class to handle logging, but I'm leaving it up to you to decide how you want to implement this technique in your own program. I'm sure I don't have to convince you of the value of using a log file, but why go through all of the trouble to make an RTF log file when you could just use a regular text file, or even an HTML log file? Here are a couple of points to consider:
If you're still not convinced, I ask you to just give it a shot. What have you got to lose besides 5 minutes of coding time? 2. The RTF file formatAfter I made the decision to write an RTF log file, I went ahead and downloaded the RTF file spec documents. When I opened the RTF 1.7 specification file in Word, I nearly gave up right then and there. The file spec was 225 pages long and covered everything from "document variables" to foreign language support. All I wanted to do was make some text blue. Fortunately, a bit of searching turned up a great site with a basic overview of the RTF file format : The RTF Cookbook RTF files are organized into blocks denoted by { braces } and use formatting commands that begin with a back-slash and are delimited by a space, line-break, semi-colon, or the beginning of another command (the delimiter following a command does not show up in the actual text of the document). Braces and backslashes that are part the actual text have a backslash prepended to them and a space or line-break after them (i.e \\ , \{ . and \} ). RTF files have a header, and it's fairly simple to construct. Well, it doesn't have to be simple (foreign language support, etc, etc), but the one I created is. First, you need a line or two that lets a word processor know this is an RTF file. It looks something like this: {\rtf1 \ansi \deff0 \fonttbl{\f0 Courier New;} \fs20} The rtf1, ansi, and deff0 commands are file format tags and nothing to worry about. The fonttbl command defines a table of all the fonts used in the file. For this tutorial, we will only be using one font: Courier New. The fs20 command sets the font size to 10. The syntax of this command is \fs<double the desired font size>. The command is formatted like this to add easy support for font sizes like "10.5" (which would be \fs21). Next in the file comes the colour table. Any font colour you want to use in your files has to be in this table. Here's an example with three colours (we'll be using more the actual implementation of the logging class): {\colortbl; \red255\green255\blue255; \red128\green128\blue128; \red255\green0\blue0; } The above colour table makes the colours black, grey, and red available for use in the rest of the document. Now we get into the actual text of the document, and the formatting tags that go with it. Just like commands, text can be organized into blocks using braces. Any formatting attributes set inside a block will not affect text outside of the block. Here's where RTF files begin to look a little sexier than HTML files. If we surround every outputted line with braces, each line begins with a nice, plain black font. Also, there's a command we'll be using on each line as sort of a safeguard, "\pard", that removes all special formatting. Here's an example of a line of text: {\pard \cf5 \b <CGraphics::Init>\b0 Direct3D initialized successfully\par} If the 6th entry (index of 5) in the colour table is blue, This will look like: <CGraphics::Init> Direct3D initialized successfully Here's a list of some common formatting commands, and what they do:
Note that many of these formatting flags can be turned off by writing them again with an appended zero (e.g. you can turn off italics by writing \i0) We've covered the header of the file and the meat of the file. All that's left is the footer. Here's where things get really complicated. Below is an example of an RTF file footer: } I suggest just copy-and-pasting that instead of trying to be some kind of programming hero and writing your own. 3. Preparing an RTF file for outputHere are the variables the logger will be using: //External variables referenced by logger -- typically private class members static BOOL bLoaded; //File loaded flag static HANDLE hFile; //Handle to the file static string sFilename; //Filename Here is the initialization function for the logger: //Open log file int Init (const string& sFile) { //Make sure log file is not already open if (bLoaded) return FALSE; //Open file hFile = CreateFile (sFile.c_str(), GENERIC_WRITE, 0, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); if (hFile == INVALID_HANDLE_VALUE) return FALSE; //Set file loaded flag bLoaded = TRUE; //Set filename sFilename = sFile; //Write RTF header WriteString ("{\\rtf1\\ansi\\deff0{\\fonttbl{\\f0 Courier New;}}\\fs20\n"); //Write colour table WriteString ("{\\colortbl;\n"); //Black WriteString ("\\red255\\green255\\blue255;\n"); //White WriteString ("\\red128\\green128\\blue128;\n"); //Grey WriteString ("\\red255\\green0\\blue0;\n"); //Red WriteString ("\\red0\\green255\\blue0;\n"); //Green WriteString ("\\red0\\green0\\blue255;\n"); //Blue WriteString ("\\red0\\green255\\blue255;\n"); //Cyan WriteString ("\\red255\\green255\\blue0;\n"); //Yellow WriteString ("\\red255\\green0\\blue255;\n"); //Magenta WriteString ("\\red128\\green0\\blue0;\n"); //Dark Red WriteString ("\\red0\\green128\\blue0;\n"); //Dark Green WriteString ("\\red0\\green0\\blue128;\n"); //Dark Blue WriteString ("\\red255\\green128\\blue128;\n"); //Light Red WriteString ("\\red128\\green255\\blue128;\n"); //Light Green WriteString ("\\red128\\green128\\blue255;\n"); //Light Blue WriteString ("\\red255\\green128\\blue0;\n"); //Orange WriteString ("}\n"); //Write log header LogString ("*** Logging Started"); LogString (""); //Success return TRUE; } Nothing overly complicated here. The function opens a file, drops in an RTF file header and colour table, and writes a string that says "*** Logging Started" into the file. The function LogString() used to write text in the file is shown later on in the article. Here's a function that will close the file properly //Close log file int Close () { //Make sure log file is already opened if (!bLoaded) return FALSE; //Write closing line LogString (""); LogString ("*** Logging Ended"); //Write closing brace (RTF file footer) WriteString ("}"); //Close file CloseHandle (hFile); //Clear file loaded flag bLoaded = FALSE; //Success return TRUE; } If you use the logging file in a DLL, checking the file for the "*** Logging Ended" message can be a good way of assuring yourself the DLL is cleaning itself up properly. |
|