Upcoming Events
Unite 2010
11/10 - 11/12 @ Montréal, Canada

GDC China
12/5 - 12/7 @ Shanghai, China

Asia Game Show 2010
12/24 - 12/27  

GDC 2011
2/28 - 3/4 @ San Francisco, CA

More events...
Quick Stats
112 people currently visiting GDNet.
2406 articles in the reference section.

Help us fight cancer!
Join SETI Team GDNet!
Link to us Events 4 Gamers
Intel sponsors gamedev.net search:

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 files
  2. The RTF file format
  3. Preparing an RTF file for output
  4. Writing formatted output
  5. Logging macros

1. The case for RTF log files

First 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:

  • Adding a bit of colour and formatting to text makes it a lot more readable

  • You don't have to worry about forgetting to close a tag like you do in HTML. You can have all formatting be reset after every outputted string (and I strongly recommend you do this). The last thing you want to be doing is debugging your debug output strings!

  • It's not particularly hard to write, and anyways, I've done it for you.

  • It can actually help save programming time. Looking through a large, plaintext log file for some impotant output can be a pain in the ass. If the log file is large enough, it can consume a fair amount of time if you have to do it often. With an RTF file, you can have all run-of-the-mill output be a dull grey colour, while the important stuff is a bright blue or red, making it much easier to pick out.

  • It just plain looks cooler

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 format

After 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:

Command   Purpose
\plainTurn off all text formatting
\pardEnd paragraph / turn off all text formatting
\ulUnderline text
\bBold text
\iItalicize text
\subMake text a subscript
\superMake text a superscript
\f#Change font to given # in the font table
\fs#Change font size to # half-points
\cf#Change foreground font colour to # in the colour table
\cb#Change background font colour to # in the colour table
\strikeStrikethrough text

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 output

Here 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.





Page 2

Contents
  Page 1
  Page 2

  Source code
  Printable version
  Discuss this article