Runtime Log FilesHow many times have you fired up your game program, anxious to see how your newest additions are working out, only to see a black screen or have the program crash on you? If you said "never," you're either a god or a liar. And since gods have no need to read these articles, that makes you a liar. :) Tracking down logic errors is no fun, so you need anything you can get that will help you locate problems. The Visual C++ debugger is a great tool for this, and log files are another. A log file is simply a text file generated by a program while it's running that tells the results of various function calls, the states of variables at certain points, or anything else you care to throw in there. Here's an example, a little snippet of Terran's log file:
DIRECTDRAW
DirectDraw interface created. Fullscreen cooperation level set. --Message Received: WM_DISPLAYCHANGE Resolution 640x480x16 set. Surfaces created successfully in system memory. Pixel format is 5.6.5 (16-bit). DirectDraw clipper created. Clipper attached to back buffer. This, obviously, is one of the blocks of text generated during initialization of the game. Something like this can help you see exactly which function call is failing, or what setting is coming out not as you'd expect. When I first released a demo of Terran, some people said that the fading wasn't working properly. Thankfully they send back this log file, and I was able to see immediately that it would always fail when the pixel format was 5.5.5, so I knew right where to look for the bug. Another thing you can use log files for is outputting information about the user's computer. If things are running slowly for someone, it's possible that their machine doesn't accelerate some feature of DirectDraw that your machine does. Things like that are easy to spot when you can look at a log file for reference. There are two basic ways to create a log file. One is to just open a file at the beginning of the program, write all your information with sprintf() statements during the game's run, and close the file at the end. The other way is to write a function that does that every time you want to log something. I prefer the latter, because if the program crashes, it's better not to have left the file open. That approach also makes it possible to easily enable or disable logging while the program is running, or add extra features instead of just a straight write. Here's an example of the logging function that I use: BOOL ADXL_LogText(char *lpszText, ...) { // only do this if logging is enabled if (bLogEnabled) { va_list argList; FILE *pFile; // initialize variable argument list va_start(argList, lpszText); // open the log file for append if ((pFile = fopen("log.txt", "a+")) == NULL) return(FALSE); // write the text and a newline vfprintf(pFile, lpszText, argList); putc('\n', pFile); // close the file fclose(pFile); va_end(argList); } // return success return(TRUE); } Chances are you haven't written a function with an arbitrary number of parameters before, so I'll explain briefly. The elipsis (...) in the function header says that after lpszText, any number of additional arguments may follow, of any data type. To handle something like that, you need the va_list data type, and the va_start and va_end macros. The "va" presumably stands for "variable arguments." Anyway, the va_list type is just a pointer to a list of arguments. You need to create one in any function that receives a variable number of arguments. The va_start macro initializes a list of type va_list, and takes the list, and the previous argument as parameters. The va_end macro simply resets the argument list pointer to NULL. Finally, the other bit of syntax to notice in the log function is the use of the vfprintf() function, which is the version of fprintf() that is designed for use with variable argument lists. There are also vsprintf() and vprintf() functions that function similarly if you ever need them. The variable bLogEnabled that you see near the top is set by the other logging function I use, which simply enables the log file. It's a pretty straightforward one: int ADXL_EnableLog() { FILE* pFile; // enable log bLogEnabled = TRUE; // clear the file contents if ((pFile = fopen("log.txt", "wb")) == NULL) return(FALSE); // close it up and return success fclose(pFile); return(TRUE); } The bLogEnabled variable is just a global variable within my ADXL library, where these functions are coming from. The variable initially is set to FALSE, so that nothing gets logged until after you call ADXL_EnableLog(). It's nice to have it set up this way, because then if you want to distribute a version of your program that doesn't generate a huge log file, you can just remove one line, instead of searching through your code to remove all the calls to the logging function. |