A Better Approach to Logging

Following on from my post last week about one-star reviews associated with the choice of artwork resolution for the retina iPad, there’s no surer way to get one-star reviews than have an application that crashes. At best it’s frustrating, at worst it results in data loss or corruption in applications that store data.

For live applications, iTunes Connect provides crash report feedback and crash reports can also be extracted direct from devices in a corporate environment by plugging into a Mac running Xcode. For a crash log to be useful though, it’s extremely helpful to have some sort of traceback through the application that enables you as a developer to work out the conditions that led to the crash.

Limitations of NSLog

The basic logging mechanism in Objective-C is the NSLog function call. This is a C function, so it has the parentheses rather than square brackets for on Objective-C message. It uses a format string and takes a variable number of arguments. Here’s a typical example, in this case on a fetch request using a fetched results controller with Core Data:

NSError *error = nil;
if (![self.fetchedResultsController performFetch:&error]) {
    NSLog(@"Fetch failed: %@", [error localizedDescription]);
}

That approach is good for trapping errors but crashes are unintentional and unexpected. In those cases it’s useful to have logging messages that show how the program ended up at the crash point. NSLog can be used for this but it can be overdone. Device logs don’t grow indefinitely, they are truncated, and too many logging messages can result in the root cause being lost. Especially as core iOS logging also consumes space in the log and in some cases, notably iCloud synchronization, can be quite verbose.

Create a logging framework with macros

A much better approach is to follow the tried and tested approach of logging frameworks used in other programming environments and have logging levels that can be raised or lowered to suit the needs of production code and debugging. In your deployed application, use a minimum of logging. During debugging, make the logs much more verbose (the Xcode console doesn’t truncate a verbose stream of output from your application).

A common and simple approach to achieving this is to take advantage of C preprocessor macros:

First of all define the log levels (I’ve just used three levels but you could have as many as you like):

#define LF_DEBUG 0
#define LF_INFO 1
#define LF_ERROR 2

// Set the default log level if not already defined
#ifndef LF_LOG_LEVEL
    #define LF_LOG_LEVEL LF_INFO
#endif

Now create macros that translate to NSLog statements if the log level is equal or less than the set logging level. This would be repeated for each logging level defined above:

#if LF_LOG_LEVEL <= LF_DEBUG
    #define LFDebug(fmt, ...) NSLog(fmt, ##__VA_ARGS__)
#else
    #define LFDebug(fmt, ...)
#endif

The beauty of using the C preprocessor to do this is that minimal overhead is added if the logging statement is disabled–the macros just expand to an empty statement. Here is a macro used at the debug level to trace the flow of an application:

LFDebug(@"Creating new customer %@", customer.name);

The logging definitions are easily placed into their own header file and included in all source files by adding an import to the project’s .pch file. The desired logging level is then easily set globally:

#define LF_LOG_LEVEL LF_DEBUG
#import LFLogging.h

For a little more information, there are a couple of useful macros that can be added to the NSLog macro expansions. These display the method or function containing the logging statement and the source line number respectively (you’d do this on a single line with no wrapping):

#define LFDebug(fmt, ...) NSLog("%s %d: " fmt,
    __PRETTY_FUNCTION__,
    __LINE__,
    ##__VA_ARGS__
)

So, overall a simple but very effective approach to logging in iOS applications that can help greatly with tracking down those one-star-inducing crashes from apps on the App Store!

You can learn more about error handling in iOS applications, including generating your own NSError objects, when we cover SQLite and Core Data on Learning Tree’s Building iPhone® and iPad® Applications: Extended Features course.

Richard Senior

Type to search blog.learningtree.com

Do you mean "" ?

Sorry, no results were found for your query.

Please check your spelling and try your search again.