My previous post about conditional logging received quite a few responses, with several people pointing out that littering your code with #ifdef DEBUG statements is both ugly and error prone. Karl Kraft, for example, created a new DebugLog class as a drop-in replacement for NSLog.
Since NSLog has been around for quite a while, there have been many solutions to this problem.
CocoaDev suggests this macro:
#if MY_DEBUG_FLAG #define DEBUG_OUTPUT( a ) NSLog( a ) #define DEBUG_OUTPUT1( a, b ) NSLog( a, b ) #else #define DEBUG_OUTPUT( a ) // (a) #define DEBUG_OUTPUT1( a, b ) // (a,b) #endif
Enumerating all the variants with different number of parameters gets a bit tedious, so with Objective-C 2.0 C99 you can use varargs macros:
#define DEBUG_OUTPUT(fmt, ...) NSLog(fmt, ## __VA_ARGS__)
As described by Fraser Hess in Dropping NSLog in release builds you can put this in your <AppName>_Prefix.pch file:
#ifdef DEBUG # define DLog(...) NSLog(__VA_ARGS__) #else # define DLog(...) /* */ #endif #define ALog(...) NSLog(__VA_ARGS__)
Use ALog for cases where you always want log output regardless of the state of the debug flag. I like this preprocessor macro approach because you don’t have to carry around extra classes in all your projects. Just a few lines of code to paste into one file in your project.
But I think we can improve it further.
The built-in macro __PRETTY_FUNCTION__ is pretty nifty in that it outputs the name of the class and the method where it’s running. Mark Damon Huges uses this in his Cocoa logging macro:
#ifdef DEBUG // DLOG takes a format argument (which must begin %s) and 0 or more args: // DLOG(@"%s"); // DLOG(@"%s: %d", x); #define DLOG(fmt, ...) NSLog(fmt, __PRETTY_FUNCTION__, ##__VA_ARGS__) #else #define DLOG(...) #endif
A drawback with this macro is that it requires you to add a “%s” to all your log statements, e.g. DLOG(@”%s foo”); instead of NSLog(@”foo”);. So it’s not a drop-in replacement for NSLog.
Let’s try to fix that.
#define DLog(fmt, ...) NSLog((@"%s " fmt), __PRETTY_FUNCTION__, ##__VA_ARGS__);
Now you can do DLog(@”foo”); as well as pass in parameters like DLog(@”value: %d”, x);
One final touch is to add the line number:
#define DLog(fmt, ...) NSLog((@"%s [Line %d] " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__);
Now if you’re lazy and you want to know that execution has reached a particular line, you can just use DLog();
So here is the complete listing of the macro:
// DLog is almost a drop-in replacement for NSLog // DLog(); // DLog(@"here"); // DLog(@"value: %d", x); // Unfortunately this doesn't work DLog(aStringVariable); you have to do this instead DLog(@"%@", aStringVariable); #ifdef DEBUG # define DLog(fmt, ...) NSLog((@"%s [Line %d] " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__); #else # define DLog(...) #endif // ALog always displays output regardless of the DEBUG setting #define ALog(fmt, ...) NSLog((@"%s [Line %d] " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__);
Set the DEBUG variable in your project as described previously. Update: If your project is 3.0 you may need to set the DEBUG variable this way.
I’m sure there will be further improvements to this. Comments are open as always.
April 4th, 2009 at 04:39
The __VA_ARGS__ identifier is actually part of C99; it’s not an Objective-C extension. As such, you can use it in Objective-C 2, Objective-C 1, and even straight C, as long as you have the compiler set to either C99 or GNU99.
April 6th, 2009 at 09:16
@Peter: You are correct. Post updated. Thanks!
April 14th, 2009 at 03:28
Thanks for this blogpost. I copied the complete listing to the myproject_Prefix.pch file of my iPhone project, but when I try to build I get the following error:
Precompiling myproject_Prefix.pch (1 error)
error: macro names must be identifiers
Any idea what’s wrong?
April 14th, 2009 at 07:22
@Thomas: I have copied and used the text from this blog post several times without problems. (One of the reasons I write this blog is to collect code snippets like this in one place.)
Make sure that the three dots have not turned into an ellipsis, and that the quotes have not turned into “smart quotes”. If that does not help, try removing lines until it compiles.
I recently noted that the macro code has been incorporated into a cool Xcode template project: iphone-static-library-project-template. So another option could be to download the code from there.
April 16th, 2009 at 06:40
thanks for the nice post. i had the same problem as thomas – switching to using “OTHER_CFLAGS $(value) -DDEBUG=1″
seemed to fix!
April 18th, 2009 at 05:48
[...] -[MyAppDelegate myFunction:]:did time intensive stuff Time = 1.2345678 Also check out The Evolution of a Replacement for NSLog for another take on handy macro [...]
April 21st, 2009 at 13:13
Mmm, I tried to test my project in some not yet published SDK and it now fails with the same error as the people above had. I tried all their solutions but none works. Anybody got another bright idea. It would be a shame to have to remove all those nice DLog statements….
April 21st, 2009 at 16:39
If you get “error: macro names must be identifiers”, then make sure you have defined DEBUG properly.
If you are using OTHER_CFLAGS then set the value to -DDEBUG=1, if you are instead using GCC_PREPROCESSOR_DEFINITIONS then the value needs to be just DEBUG=1
May 7th, 2009 at 05:38
If you are using OTHER_CFLAGS then set the value to -DDEBUG=1, if you are instead using GCC_PREPROCESSOR_DEFINITIONS then the value needs to be just DEBUG=1
That was it. Thanks!
May 16th, 2009 at 21:58
Good post, smart technique. And easy to implement. Finally I’ll get rid of all debug text from my releases.
Thanks
June 11th, 2009 at 03:34
Just incase anyone is as stupid as me and does a search and replace to change all existing NSLog calls to DLog – remember that the .pch file should not be included in this search!
The error message: implicit declaration of function DLog had me confused for a while until I realised I had also changed the definitions in the pch file!
Doooh.
June 11th, 2009 at 20:28
[...] The Evolution of a Replacement for NSLog | iPhone Development Blog an intelligent NSLog replacement (tags: iphone debugging logging) [...]
June 19th, 2009 at 08:28
Thanks for the handy logging code!
Just in case anyone has this same problem:
When I tried to add a User Defined GCC_PREPROCESSOR_DEFINITIONS I received the error that this was already defined, although I couldn’t find it anywhere.
Instead I had to go to Show: All Settings and find: Preprocessor Macros under the GCC 4.2 – Preprocessing section and add DEBUG=1 to that item.
It seems Preprocessor Macros and GCC_PREPROCESSOR_DEFINITIONS are the same thing?
I’m using the 3.0 sdk if that matters…
September 11th, 2009 at 13:27
DLog(aStringVariable) isn’t really safe or useful anyway, unless you’re sure the string won’t contain any % sequences. And if you knew what the string was, you wouldn’t really need to log it. If it’s worth doing, it’s worth doing right.
(It’s usually a security issue, too, but I don’t think that matters for a macro that isn’t going to be in release builds.)
January 26th, 2010 at 15:13
Just came across this posting. Thanks for some great ideas here, Nick!
After reading this blog entry, I took these ideas, along with some other typical logging ideas such as logging levels, and created a Logging.h header file. In the spirit of appreciation, I thought I’d contribute the code from that file back to this thread. The code below can be used as-is…just paste it into a central header file somewhere (as mentioned, we created a Logging.h file). There are a series of switches that can be set to customize formats, and turn specific logging levels on and off, or remove logging altogether.
I hope this proves useful to someone.
…Bill Hollings
/*
* There are three levels of logging: debug, info and error, and each can be enabled independently
* via the LOGGING_LEVEL_DEBUG, LOGGING_LEVEL_INFO, and LOGGING_LEVEL_ERROR switches below, respectively.
* In addition, ALL logging can be enabled or disabled via the LOGGING_ENABLED switch below.
*
* To perform logging, use any of the following function calls in your code:
*
* LogDebug(fmt, …) – will print if LOGGING_LEVEL_DEBUG is set on.
* LogInfo(fmt, …) – will print if LOGGING_LEVEL_INFO is set on.
* LogError(fmt, …) – will print if LOGGING_LEVEL_ERROR is set on.
*
* Each logging entry can optionally automatically include class, method and line information by
* enabling the LOGGING_INCLUDE_CODE_LOCATION switch.
*
* Logging functions are implemented here via macros, so disabling logging, either entirely,
* or at a specific level, removes the corresponding log invocations from the compiled code,
* thus completely eliminating both the memory and CPU overhead that the logging calls would add.
*/
// Set this switch to enable or disable ALL logging.
#define LOGGING_ENABLED 1
// Set any or all of these switches to enable or disable logging at specific levels.
#define LOGGING_LEVEL_DEBUG 1
#define LOGGING_LEVEL_INFO 1
#define LOGGING_LEVEL_ERROR 1
// Set this switch to set whether or not to include class, method and line information in the log entries.
#define LOGGING_INCLUDE_CODE_LOCATION 1
// ***************** END OF USER SETTINGS ***************
#if !(defined(LOGGING_ENABLED) && LOGGING_ENABLED)
#undef LOGGING_LEVEL_DEBUG
#undef LOGGING_LEVEL_INFO
#undef LOGGING_LEVEL_ERROR
#endif
// Logging format
#define LOG_FORMAT_NO_LOCATION(fmt, lvl, …) NSLog((@”[%@] ” fmt), lvl, ##__VA_ARGS__)
#define LOG_FORMAT_WITH_LOCATION(fmt, lvl, …) NSLog((@”%s [Line %d] [%@] ” fmt), __PRETTY_FUNCTION__, __LINE__, lvl, ##__VA_ARGS__)
#if defined(LOGGING_INCLUDE_CODE_LOCATION) && LOGGING_INCLUDE_CODE_LOCATION
#define LOG_FORMAT(fmt, lvl, …) LOG_FORMAT_WITH_LOCATION(fmt, lvl, ##__VA_ARGS__)
#else
#define LOG_FORMAT(fmt, lvl, …) LOG_FORMAT_NO_LOCATION(fmt, lvl, ##__VA_ARGS__)
#endif
// Debug level logging
#if defined(LOGGING_LEVEL_DEBUG) && LOGGING_LEVEL_DEBUG
#define LogDebug(fmt, …) LOG_FORMAT(fmt, @”debug”, ##__VA_ARGS__)
#else
#define LogDebug(…)
#endif
// Info level logging
#if defined(LOGGING_LEVEL_INFO) && LOGGING_LEVEL_INFO
#define LogInfo(fmt, …) LOG_FORMAT(fmt, @”info”, ##__VA_ARGS__)
#else
#define LogInfo(…)
#endif
// Error level logging
#if defined(LOGGING_LEVEL_ERROR) && LOGGING_LEVEL_ERROR
#define LogError(fmt, …) LOG_FORMAT(fmt, @”***ERROR***”, ##__VA_ARGS__)
#else
#define LogError(…)
#endif
January 26th, 2010 at 16:08
Thanks Bill!