Apple’s Cocoa SDK framework for the iOS provides basic logging capabilities through the
NSLog function. This allows the developer to log simple text messages to the development console.
However, the use of
NSLog suffers from two drawbacks:
- There is only one level of logging with
NSLog. So during development, you get a record of all of the
NSLogcalls that you’ve inserted, some of which might be quite detailed and frequent, and it’s easy to be overwhelmed by the clutter. There is no way of selectively turning off some of the output.
NSLogfunction is called every time it is encountered, including within executing production release code. To avoid this, developers must typically remove all calls to
NSLogbefore compiling for a production release. This is a headache fraught with possible mistakes and inhibits debugging future releases.
To remedy this, we’ve developed a logging framework that solves both of these issues. This library consists of a single
Logging.h header file which adds, to iOS, flexible, non-intrusive logging capabilities that are efficiently enabled or disabled via compile switches.
To use this enhanced logging functionality, simply
Logging.h header file into any code file to which you want to add enhanced logging, and set the compiler switch
LOGGING_ENABLED=1, either in a
#define statement or in the
Xcode build setting
To address the first issue described above, that of a single level of logging, this library defines four levels of logging:
Debug, and each is enabled independently via a distinct compiler switch. Invoking any level is as simple as calling the appropriate variadic (variable number of arguments) function as shown in the following examples:
LogTrace(@"Message with %i argument…oops I mean %.1f arguments", 1, 2.0f);
LogInfo(@"Do you object to this %@ date object?", [NSDate new]);
LogDebug(@"I’ll take this out if ever I solve this bug");
LogError(@"Don’t do that: %@", [NSException exceptionWithName: @"CRYING" reason: @"Spilled milk" userInfo:nil]);
In the examples above, you can see the four logging levels in action, along with the use of variable arguments that match against templates in the message string, in exactly the same manner as the
printf functions. The first argument is an
NSString that optionally includes embedded format specifiers, and subsequent optional arguments indicate data to be formatted and inserted into that string. As with
printf, there must be an equal number of embedded format specifiers and optional arguments. For more information on string formatting, see the documentation for
NSLog, String Format Specifiers and
The output of the above code is:
2010-07-23 18:27:23.398 Logging Sample[35700:20b] [trace] Simple message
2010-07-23 18:27:23.399 Logging Sample[35700:20b] [trace] Message with 1 argument…oops I mean 2.0 arguments
2010-07-23 18:27:23.402 Logging Sample[35700:20b] [info] Do you object to this 2010-07-23 18:27:23 -0400 date object?
2010-07-23 18:27:23.402 Logging Sample[35700:20b] [DEBUG] I’ll take this out if ever I solve this bug
2010-07-23 18:27:23.403 Logging Sample[35700:20b] [***ERROR***] Don’t do that: Spilled milk
In this output listing,
Logging Sample is just the name of the
Xcode project. As you can see, each log message identifies the level of logging. Trace logging is recommended for detailed tracing of program flow, such as inside a loop, or on each frame render in a graphic engine. Info logging is recommended for general, infrequent, information messages such as initialization or shutdown activities, file loads, or user settings changes. Error logging, as the name suggests, is recommended for use only when there is an error to be logged. And finally, debug logging is recommended for temporary use during debugging. When trying to track down a coding error, you might temporarily sprinkle even your most detailed code with debug log messages. But once the problem is resolved, you should remove them, or possibly convert the most useful of them into info or trace logs.
Each of the logging levels is independently turned on or off by setting a boolean compiler switch to either
0, respectively, as follows:
LOGGING_LEVEL_TRACEenables or disables the
LOGGING_LEVEL_INFOenables or disables the
LOGGING_LEVEL_ERRORenables or disables the
LOGGING_LEVEL_DEBUGenables or disables the
By selectively turning on or off each of these switches, you could choose to output only error log messages so that logging will only occur if something goes wrong, or perhaps include info messages, so that you can keep track of high-level events such as file loads, or initialization and shutdown code. And when attempting to debug the details of a module, where you want more copious and detailed logging, you might enable either or both of the trace or debug logging levels.
LOGGING_ENABLED compiler switch controls overall logging. This is normally turned on in development code to make use of the logging functionality. But in production code, you can disable all logging in one fell swoop by turning off the
LOGGING_ENABLED compiler switch. This has the effect of overriding all the individual level switches, and disables all logging. None of the levels will produce log messages with the
LOGGING_ENABLED compiler switch turned off.
To assist with tracking down bugs, you can opt to have each logged entry automatically include class, method and line information by turning on the
LOGGING_INCLUDE_CODE_LOCATION compiler switch. Doing so will modify the log output above to appear as:
2010-07-23 18:28:17.238 Logging Sample[35732:20b] -[Logging_SampleAppDelegate applicationDidFinishLaunching:][Line 20] [trace] Simple message
2010-07-23 18:28:17.239 Logging Sample[35732:20b] -[Logging_SampleAppDelegate applicationDidFinishLaunching:][Line 21] [trace] Message with 1 argument…oops I mean 2.0 arguments
2010-07-23 18:28:17.247 Logging Sample[35732:20b] -[Logging_SampleAppDelegate applicationDidFinishLaunching:][Line 22] [info] Do you object to this 2010-07-23 18:28:17 -0400 date object?
2010-07-23 18:28:17.248 Logging Sample[35732:20b] -[Logging_SampleAppDelegate applicationDidFinishLaunching:][Line 23] [DEBUG] I’ll take this out if ever I solve this bug
2010-07-23 18:28:17.249 Logging Sample[35732:20b] -[Logging_SampleAppDelegate applicationDidFinishLaunching:][Line 24] [***ERROR***] Don’t do that: Spilled milk
Like most compiler switches, each of the switches discussed in this article is turned on by setting its value to
1 and turned off by setting its value to
0. This can be done via
#define statements in your code (or in
Logging.h), or via the compiler build setting
GCC_PREPROCESSOR_DEFINITIONS in your
Xcode build configuration. Using the
Xcode setting is the preferred choice, so that logging can be configured differently for different
Xcode targets or build configurations, and particularly to ensure that logging is not accidentally left enabled in production release builds.
To address the second issue described above, namely the memory and processing overhead of the logging function calls themselves, the logging functions are implemented here via macros. Disabling logging, either entirely, or at a specific level, completely removes the corresponding log invocations from the compiled code, thereby eliminating both the memory and CPU overhead that the logging calls would add. So, there is absolutely no runtime penalty to be paid by leaving copious quantities of
LogTrace calls spread thickly throughout your code, as long as you simply disable either trace logging or all logging by turning off the
LOGGING_ENABLED compiler switch, respectively, at build time.
Fantastic! Where Can I Get Me Some of That?
You can download this logging framework (a single
Logging.h file) in the download area at the top-right of this article. It is distributed under an MIT license, which makes it free for you to use in your projects. However, if you find this code is useful, please remember to make a donation above to help us fund the ongoing development and support of frameworks such as this.
Credit Where Credit is Due
Thanks to Nick Dalton for outlining the underlying ideas for using variadic macros as well as for outputting the code location as part of the log entry.