I recently saw this post on turning off NSLog for non-debug builds, and found it disappointing and inspirational at the same time. Scattering hundreds of #ifdefs around code is a recipe for disaster. Eventually some non debugging code will end up inside the #ifdef and the debug and release builds will function differently, making debugging more difficult.
I’m not a big fan of debugging from log statements, but they can occasionally be useful.
What follows is details on building a full NSLog() replacement that turns off with a single #ifdef in the header file that contains the function. You can either download the m and h file, or continue on to the extended entry for an explanation of how it works and ways to make it more useful for your particular needs.
Start with a basic Xcode Foundation Tool. This will contain a main.m file that uses NSLog to print “Hello World!”

NSLog(@"Hello, World!");
The output will look like this:
2009-03-23 10:57:13.619 DebugLog[11941:807] Hello, World!
Add a DebugLog.h and DebugLog.m to the file. DebugLog.h should declare a method DebugLog() that will be the replacement for NSLog()
void DebugLog(NSString *format,...);
And DebugLog.m should contain the implementation of that function. A particular item to note is that NSLog only adds a newline to the end of the NSLog format if one is not already there. This function duplicates this feature of NSLog()
void DebugLog(NSString *format,...) {
va_list ap;
va_start (ap, format);
if (![format hasSuffix: @"\n"]) {
format = [format stringByAppendingString: @"\n"];
}
NSString *body = [[NSString alloc] initWithFormat: format arguments: ap];
va_end (ap);
fprintf(stderr,"%s",[body UTF8String]);
[body release];
}
You can download the project at this stage. If you play with the project at this point you will notice the output is different than NSLog().
// With NSLog 2009-03-23 11:01:32.936 DebugLog[12085:807] Hello, World! 2009-03-23 11:01:32.938 DebugLog[12085:807] Hello, World! // With DebugLog Hello, World! Hello, World!
NSLog() includes the time, process name, process id and thread id as a prefix on every message. I find this excessive in most cases, but you can add it in if you wish. More on this later.
In the next version of the project, disable the function except for Debug builds. Do this by editing DebugLog.h to have an #ifdef that turns DebugLog() into a macro, and rename the function from DebugLog to _DebugLog. By using a #else we can disable DebugLog for any non DEBUG builds.
#ifdef DEBUG #define DebugLog(args...) _DebugLog(args); #else #define DebugLog(x...) #endif void _DebugLog(NSString *format,...);
Rename the function in DebugLog.m as well.
void _DebugLog(NSString *format,...) {
Finally, setup the build settings to include a DEBUG flag. For more deatil or for iPhone configurations, see the the post that inspired this one.

Now try building both the Debug and Release version. Run from the command line, only the Debug version will produce the debug lines
explorer:karl/tmp/DebugLog%./build/Debug/DebugLog Hello, World! explorer:karl/tmp/DebugLog%./build/Release/DebugLog explorer:karl/tmp/DebugLog%
Again, you can download the project at this stage.
Now take the function from better to best. By using some preprocessor directives we can embed the name of the file and line number where the DebugLog() is called, or have it print a function or method name.
Change the macro definition and function declaration:
#ifdef DEBUG #define DebugLog(args...) _DebugLog(__FILE__,__LINE__,args); #else #define DebugLog(x...) #endif void _DebugLog(const char *file, int lineNumber, NSString *format,...);
and change the fprintf() in the implementation to print the passed arguments:
fprintf(stderr,"%s:%d %s",file,lineNumber,[body UTF8String]);
The output will now look like this:
/Users/karl/tmp/DebugLog/main.m:15 Hello, World!
The __FILE__ is expanded to the complete path to the file, which is usually more verbose that I need. It can be trimmed down to the actual file name by using an NSString method:
... NSString *fileName=[[NSString stringWithUTF8String:file] lastPathComponent]; fprintf(stderr,"%s:%d %s",[fileName UTF8String],lineNumber,[body UTF8String]); ...
This will produce this output:
main.m 15 Hello, World!
Again, you can download the project at this stage.
Finally, here is a more tricked out version that prints the function or method name instead of the file name, and prints the name of the thread.
explorer:karl/tmp/DebugLog%./build/Debug/DebugLog MainThread/main (main.m:16) Hello, World! MainThread/+[SampleClass debugExample] (SampleClass.m:16) A sample debug message MainThread/-[SampleClass debugExample] (SampleClass.m:21) Another sample debug message
The final project can be downloaded here. Stick the #import into your prefix header, and you can liberally sprinkle DebugLog commands all over your code.
