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:

4. Writing formatted output

Here comes the hard part: actually printing messages to the file. We will be using four functions for this.

First however, comes an enumeration. It represents the colour table written to the file in Init():

//RTF file's colour table
enum COLOURTABLE
{
    BLACK = 0,
    WHITE = 1,
    GREY = 2,
    RED = 3,
    GREEN = 4,
    BLUE = 5,
    CYAN = 6,
    YELLOW = 7,
    MAGENTA = 8,
    DARK_RED = 9,
    DARK_GREEN = 10,
    DARK_BLUE = 11,
    LIGHT_RED = 12,
    LIGHT_GREEN = 13,
    LIGHT_BLUE = 14,
    ORANGE = 15
};

The first of the four functions, WriteString() puts a plaintext string directly into the file. This should be a private function in your logging class. It is listed below:

//Write a string directly to the log file
void WriteString (const string& sText)
{
    DWORD bytesWritten;

    WriteFile (hFile, sText.c_str (), (int) sText.length (), &bytesWritten, NULL);
}

Nothing that really needs to be explained.

The second function, ReplaceTag() simply replaces all occurences of a formatting tag in a string with its RTF command equivalent. It allows us to use formatting tags such as [B] and [/B] rather than writing out the RTF commands. This function should be a private class member

//Replace all instances of a formatting tag with the RTF equivalent
void ReplaceTag (string* sText, const string& sTag, const string& sReplacement)
{
    unsigned int start = 0;
    while (sText->find(sTag, start) != string::npos)
    {
        start = sText->find(sTag, start);
        sText->replace (start, sTag.length (), sReplacement);
        start += sTag.length () + 1;
    }
}

This is basically just a substring-replacement function. Not particularly complicated. If you do not want to use special formatting tags, like [B], you should still include this function as it is later used to replace slashes and braces with the RTF comamdns that allow them to show up in the document.

The third function, DoFormatting(), is where the special formatting tags I've been going on about are actually implemented. It also allows you to easily include slashes and braces in your logging strings. Furthermore, it puts strings to be logged inside a set of braces and clears any previous formatting. This function should be a private class member.

//Replace formatting tags in a string with RTF formatting strings
void DoFormatting (string* sText)
{
    //Fix special symbols {, }, and backslash
    ReplaceTag (sText, "\\", "\\\\");
    ReplaceTag (sText, "{", "\\{");
    ReplaceTag (sText, "}", "\\}");

    //Colours
    ReplaceTag (sText, "[BLACK]", "\\cf0 ");
    ReplaceTag (sText, "[WHITE]", "\\cf1 ");
    ReplaceTag (sText, "[GREY]", "\\cf2 ");
    ReplaceTag (sText, "[RED]", "\\cf3 ");
    ReplaceTag (sText, "[GREEN]", "\\cf4 ");
    ReplaceTag (sText, "[BLUE]", "\\cf5 ");
    ReplaceTag (sText, "[CYAN]", "\\cf6 ");
    ReplaceTag (sText, "[YELLOW]", "\\cf7 ");
    ReplaceTag (sText, "[MAGENTA]", "\\cf8 ");
    ReplaceTag (sText, "[DARK_RED]", "\\cf9 ");
    ReplaceTag (sText, "[DARK_GREEN]", "\\cf10 ");
    ReplaceTag (sText, "[DARK_BLUE]", "\\cf11 ");
    ReplaceTag (sText, "[LIGHT_RED]", "\\cf12 ");
    ReplaceTag (sText, "[LIGHT_GREEN]", "\\cf13 ");
    ReplaceTag (sText, "[LIGHT_BLUE]", "\\cf14 ");
    ReplaceTag (sText, "[ORANGE]", "\\cf15 ");

    //Text style
    ReplaceTag (sText, "[PLAIN]", "\\plain ");
    ReplaceTag (sText, "[B]", "\\b ");
    ReplaceTag (sText, "[/B]", "\\b0 ");
    ReplaceTag (sText, "[I]", "\\i ");
    ReplaceTag (sText, "[/I]", "\\i0 ");
    ReplaceTag (sText, "[U]", "\\ul ");
    ReplaceTag (sText, "[/U]", "\\ul0 ");
    ReplaceTag (sText, "[S]", "\\strike ");
    ReplaceTag (sText, "[/S]", "\\strike0 ");
    ReplaceTag (sText, "[SUB]", "\\sub ");
    ReplaceTag (sText, "[/SUB]", "\\sub0 ");
    ReplaceTag (sText, "[SUPER]", "\\super ");
    ReplaceTag (sText, "[/SUPER]", "\\super0 ");
    ReplaceTag (sText, "[LEFT]", "\\ql ");
    ReplaceTag (sText, "[RIGHT]", "\\qr ");
    ReplaceTag (sText, "[CENTER]", "\\qc ");
    ReplaceTag (sText, "[FULL]", "\\qj ");
    ReplaceTag (sText, "[TITLE]", "\\fs40 ");
    ReplaceTag (sText, "[/TITLE]", "\\fs20 ");
    ReplaceTag (sText, "[H]", "\\fs24 ");
    ReplaceTag (sText, "[/H]", "\\fs20 ");

    //Add brackets and line formatting tags
    sText->insert (0, "{\\pard ");
    sText->insert (sText->length (), "\\par}\n");
}

If it looks a bit too unwieldy for your liking, you can remove a few of the formatting tags. I personally don't think speed is a big issue in logging. File output buffers make sure the log files dont slow down your program too much, and the cost of a few substring replacements is minimal. Anyways, you would probably want to disable logging in release builds (the next section details methods of making all of the logging function calls compile right out of the program).

The final function needed to get a fully-functioning RTF log file up and running is LogString(). This is the function you can call from anywhere to log messages to the file. It should be a public member of your logging class.

//Format and log a string
int LogString (string sText)
{
    //Make sure log file is already opened
    if (!bLoaded)
        return FALSE;
    //Format string
    DoFormatting (&sText);

    //Write string to file
    WriteString (sText);

    //Success
    return TRUE;
}

That does it. The log file should be running properly at this point. You should be able to open a file with Init(), write to it with LogString() and close it with Close().

5. Logging macros

In this section, a series of macros will be presented that allow access to the log file. If you use only these macros to interact with the logging functions, it is quite easy to have the preprocessor remove all logging code from a release build of your program.

Before we get to the macros, I would like to introduce a function that allows you to easily write messages that look like the following:

<Class::FunctionName> Some information
<Class::FunctionName> A notification of success
<Class::FunctionName> A warning
<Class::FunctionName> A fatal error

Here's a short snip from one of my log files that shows how this looks in practice:

<CMusicManager::Init> Attempting to load songs in directory: Music\
<CMusicManager::Init> Loaded song: (286394ms) Coheed and Cambria - Delirium Trigger (album).ogg
<CMusicManager::Init> Successfully loaded all songs in given directory
<CGraphics::Init> Initializing Direct3D...
<CGraphics::Init> Direct3D 8 object created
<CFake::Error> Nothing ever goes wrong with my code so I had to make a fake error
<CGraphics::CheckCaps> Examining adapter capabilities...
<CGraphics::CheckCaps> Adapter being examined: 3D Blaster GeForce2 MX
<CGraphics::CheckCaps> Video card supports textures as big as 1024x1024
<CGraphics::CheckCaps> Video card supports non-square textures
<CGraphics::CheckCaps> Video card does not support non-power-of-2 textures
<CGraphics::CheckCaps> Video card supports alpha channel in textures
<CGraphics::CheckCaps> Video card supports modulated texture blending
<CGraphics::CheckCaps> Video card supports source alpha blending
<CGraphics::CheckCaps> Video card supports destination alpha blending
<CGraphics::CheckCaps> Video card has required capabilities

As you can see, it is very easy to pick out warnings and errors from the rest of the messages.

Here is an enumeration the function depends on:

//Log message types
enum MESSAGETYPE
{
    MSG_SUCCESS = 1,
    MSG_INFO = 2,
    MSG_WARN = 3,
    MSG_ERROR = 4,
};

Here is the function:

//Write a specially formatted message to the log file
int CLogFile::LogMessage (const string &sSource,
           const string& sMessage, MESSAGETYPE messageType)
{
    string sString;
    switch (messageType)
    {
    case MSG_SUCCESS:
        sString = "[BLUE]";
        break;
    case MSG_INFO:
        sString = "[GREY]";
        break;
    case MSG_WARN:
        sString = "[ORANGE]";
        break;
    case MSG_ERROR:
        sString = "[RED]";
        break;
    }

    sString += "[B]<";
    sString += sSource;
    sString += ">[/B] ";
    sString += sMessage;

    //Format the string and write it to the log file
    return LogString (sString);
}

All it does is add a couple colour tags and some formatting, and passes it off to the LogString() function.

Now onto the macros. Here they are:

//Constant for disabling logging
const int LOGGING_DISABLED = 0x00330099;

//Logging definitions
#ifdef ENABLE_LOGGING
    #define openLog(fileName) Init(fileName)
    #define closeLog Close()
    #define writeLog(s) LogString(s)
    #define msgLog(message, type) LogMessage(__FUNCTION__, message, type)
    #define infoLog(message) LogMessage(__FUNCTION__, message, CLogFile::MSG_INFO)
    #define successLog(message) LogMessage(__FUNCTION__, message, CLogFile::MSG_SUCCESS)
    #define warnLog(message) LogMessage(__FUNCTION__, message, CLogFile::MSG_WARN)
    #define errorLog(message) LogMessage(__FUNCTION__, message, CLogFile::MSG_ERROR)
#else
    #define openLog(fileName) LOGGING_DISABLED
    #define closeLog LOGGING_DISABLED
    #define writeLog(s)
    #define msgLog(message, type)
    #define infoLog(message)
    #define successLog(message)
    #define warnLog(message)
    #define errorLog(message)
#endif

As should be apparent, logging will only compile into your program if ENABLE_LOGGING is defined. Otherwise, attempts to use openLog and closeLog will "return" LOGGING_DISABLED.

You can write to the file with the LogString() function via the writeLog macro. You can also write to the file with the LogMessage() function using the remaining 5 macros, unless you use Visual C++ 6.0. If you do, you don't have the luxury of the __FUNCTION__ macro. For Visual C++ 6 users (and for users of other compilers that do not support __FUNCTION__) I recommend rewriting the macros in this fashion:

#define msgLog(source, message, type) LogMessage(source, message, type)

This concludes my article. All of the functions in this article, have been compiled into the source files available here.

Please send any bugs, suggestions, or feedback to Eamonn@gdnMail.net




Contents
  Page 1
  Page 2

  Source code
  Printable version
  Discuss this article