My Growing iOS Developer Toolbox — Logging With Levels and Categories

I really love working with iOS and Objective-C, but I still feel like I am missing some items from my developer’s toolbox. (I am using the term ‘tool’ generically to cover anything from a fairly simple development pattern to a full fledged application.) Those missing items fall into several categories:

  1. Existing features within Xcode or other Apple tools that I have not yet found;
  2. Tools that are not needed within this new environment that I only think I need;
  3. Tools that have no direct one-to-one replacement;
  4. Tools that have some third party support that I have not yet found;
  5. Useful tools that really do not exist yet in the Objective-C world.

I usually learn a new language itself much faster than I get comfortable with the new environment, development style, and available tools. So, when I feel like something is missing, I know it might just be part of the learning curve. When I move to a new platform, I want to be willing to think differently and adapt to it rather than fight it — the difficulty is often deciding which category that missing piece falls in.

A Tool Missing From My Objective-C and iOS Development Toolbox

One of the the simple tools I use heavily during development in Java is Apache Commons Logging and Log4J. For those unfamiliar with the Java world, they are simple packages that allow you to easily manage development and production logging. They support multiple categories and multiple levels within each category, and are easily configured at runtime via a simple text file. For the majority of applications, you can leave the logging in place and just disable it in the configuration file with a negligible impact on performance. It is great to be able to come back to existing code to make changes and simply enable detail level logging for individual categories (usually based on class name) to more easily monitor what is happening.

When I first started heavily into iOS and Objective-C last winter, I went searching for replacement tools in several areas, including this one. I did not find a one-to-one replacement, mostly because of the difference in the development environment, but I still had a desire for something close. My searching found several people doing limited forms of this via custom macros to wrap NSLog statements. I like to credit those on whose worked I build, but I did not track the source of most of what I learned and it has been too long for me to remember. So feel free to use what you find here however you wish, knowing that much of it did not originate with me.

Requirements for Objective-C Logging

I have no desire to implement an entire framework to support logging, nor do I want to duplicate everything from the java packages, even though there are lots of useful things I am ignoring.

My simple requirements are:

  1. Control logging horizontally via debug levels;
  2. Control logging vertically via categories;
  3. Enable or disable logging via very simple changes;

Debug levels are important to me because usually I want only higher level log messages displayed to avoid unnecessary detail in the console log across all of my classes.

Categories are important to me because when I am focused on a particular subsection of code, or an individual class, I want to be able quickly disable logging at any level for all other categories. This allows me to more quickly find the pertinent messages and finish my task, whether it is debugging or implementing a new feature.

It is also important to me to not add configuration complexity to my project (Xcode has enough of that), so I wanted to have only one or two places where I could tweak the levels and categories.

Macros for Objective-C Logging

I reuse IGDebug.h across projects, so it does not contain any project specific definitions. For convenience, I include this file in my precompiled header.

I enable debug logging for a particular target by setting the value of Preprocessor Macro to IGDEBUG_MODE=1 in its configuration for the debug Distribution. By setting this only for your Debug distributions, you can ensure none of the debug logging is included in your release distributions.

My project specific categories are defined in a separate, project specific header file. I recommend placing the category defines in a single file, it makes it easy to find them, but the IGDebug macros do not care where you define your category.

IGDebug.h

/*
 *  Created by Douglas Sjoquist.
 *  Copyright 2010.
 *  Free to use or modify for any purpose.
 */


// Define IG_DEBUG_MODE for Debug distributions in project build config files

// if IG_DEBUG_MODE is set, then these macros are in effect, otherwise they do nothing
//   IGDStatement includes enclosed text as a statement, only simple statements will work
//      (intent is to used when a temporary object needs to be created to display in subsequent log message)
//
//   IGDLog takes 3 or more parameters
//      l   --  debug level, an integer value that is compared against the value of
//              IGDBG_MAX_LOG_LEVEL to determine how detailed of log messages should be displayed
//      c   --  additional condition that must be true to display log message
//              the intent is to allow specific classes to be turned on and off via individual
//				#defines, but it can be used for any purpose whatsoever
//      s   --  NSLog format string to use
//      ...     Var args that match the format string
//
// IGALog displays similar log messages, but is unconditional (no debug level, conditions, and ignores IG_DEBUG_MODE)
//
// IGDSLog and IGASLog behave the same, except they include the self pointer as well
// IGDFLog and IGAFLog behave the same, except they include the filename/linenumber as well
// IGDSFLog and IGASFLog behave the same, except they include the self pointer and filename/linenumber as well

#define IGDBG_TRACE 3
#define IGDBG_DEBUG 2
#define IGDBG_INFO 1
#define IGDBG_WARN 0

#define IGDBG_MAX_LOG_LEVEL IGDBG_INFO

#ifdef IG_DEBUG_MODE
#define IGDStatement( s ) s
#define IGDLog( l, c, s, ... ) if (((l) <= IGDBG_MAX_LOG_LEVEL) && (c)) NSLog( @"%@", [NSString stringWithFormat:(s), ##__VA_ARGS__] )
#define IGDSLog( l, c, s, ... ) if (((l) <= IGDBG_MAX_LOG_LEVEL) && (c)) NSLog( @"<%p> %@", self, [NSString stringWithFormat:(s), ##__VA_ARGS__] )
#define IGDFLog( l, c, s, ... ) if (((l) <= IGDBG_MAX_LOG_LEVEL) && (c)) NSLog( @"%@:(%d) %@", [[NSString stringWithUTF8String:__FILE__] lastPathComponent], __LINE__, [NSString stringWithFormat:(s), ##__VA_ARGS__] )
#define IGDSFLog( l, c, s, ... ) if (((l) <= IGDBG_MAX_LOG_LEVEL) && (c)) NSLog( @"<%p %@:(%d)> %@", self, [[NSString stringWithUTF8String:__FILE__] lastPathComponent], __LINE__, [NSString stringWithFormat:(s), ##__VA_ARGS__] )
#else
#define IGDStatement( s )
#define IGDLog( l, c, s, ... )
#define IGDSLog( l, c, s, ... )
#define IGDFLog( l, c, s, ... )
#define IGDSFLog( l, c, s, ... )
#endif

#define IGALog( s, ... ) NSLog( @"%@", [NSString stringWithFormat:(s), ##__VA_ARGS__] )
#define IGASLog( s, ... ) NSLog( @"<%p> %@", self, [NSString stringWithFormat:(s), ##__VA_ARGS__] )
#define IGAFLog( s, ... ) NSLog( @"%@:(%d) %@", [[NSString stringWithUTF8String:__FILE__] lastPathComponent], __LINE__, [NSString stringWithFormat:(s), ##__VA_ARGS__] )
#define IGASFLog( s, ... ) NSLog( @"<%p %@:(%d)> %@", self, [[NSString stringWithUTF8String:__FILE__] lastPathComponent], __LINE__, [NSString stringWithFormat:(s), ##__VA_ARGS__] )

Using IGDebug.h

The examples shown below include IGConstants.h, a project specific file I use to configure all my logging categories in a single place. There is no magic to it, it is just a list of #define statements to define the adhoc logging categories I use.

There are two types of macros defined in IGDebug.h: logging macros intended for use in debug distributions only, and macros that should produce log output in all distributions. The debug versions are prefixed with “IGD” and the always log versions are prefixed with “IGA”. There are four different macros in each set:

  1. IG?Log — functions as a simple wrapper for NSLog, adds no extra output
  2. IG?SLog — adds the self pointer to the log output using <%p>
  3. IG?FLog — adds the file name and line number to the log output
  4. IG?SFLog — adds the self pointer, the file name, and line number to the log output

The debug versions of these macros also include two additional parameters to control the logging level and assign it to a category. The logging level parameter should be an integer, and will be compared to the IGDBG_MAX_LOG_LEVEL definition defined in IGDebug.h. The category can be any value that evaluates to true or false — in my exmaples I use simple #define values of 1 or 0 to enable or disable log output for that category.

Example usage

IGConstants.h

#define GESTURE 1
#define SUBGESTURE 1
#define TOUCHINFO 1

IGTouchInfo.m

- (IGTimeAndPosition *) addCurrentTimeAndPositionInView:(UIView *) view {
    IGTimeAndPosition *timeAndPosition = [[IGTimeAndPosition alloc] initWithTime:[NSDate timeIntervalSinceReferenceDate] position:[touch locationInView:view]];
    IGDSFLog(IGDBG_DEBUG, TOUCHINFO, @"createCTAP: %@(rc=%u)", timeAndPosition, [timeAndPosition retainCount]);
    [timeAndPositionArray addObject:timeAndPosition];
    [timeAndPosition release];
    return timeAndPosition;
}

Caveats

Because these are macros, you may find cases where values you want to display confuse the preprocessor. As a workaround, I added a IGDStatement macro that simply wraps an existing single Objective-C statement. The intent is to be able to calculate a value to use in a logging statement that is only included when IG_DEBUG_MODE is defined.

Example usage of IGDStatement

...
    CGFloat movementRange = [touchInfo movementRange];
    NSTimeInterval timeDiff = [touchInfo timeDifference];
    IGDStatement(CGPoint min = [touchInfo minimumPosition];)
    IGDStatement(CGPoint max = [touchInfo maximumPosition];)
    IGDFLog(IGDBG_DEBUG, SUBGESTURE, @"min=%f,%f max=%f,%f range=%f, time=%f", min.x, min.y, max.x, max.y, movementRange, timeDiff);
 ...

Conclusion

Feel free to use IGDebug.h as-is or however you wish in your projects. If you have improvements, I’d love to hear about them in the comments or on twitter.

I have been using a form of this in my projects since spring, and it is finally becoming a habit for me. Whatever tools you use, getting to the “I don’t have to think about this” level is important for your productivity, so choose a few and get in the habit of using them, whatever they are.


As an indie developer, one of the best things you can do is to find like-minded developers that will provide encouragement and motivation while pursuing a commitment. A great collection of indie iOS developers have helped me stay on track, most of them are either developers associated with iDevBlogADay, or those I have met through the 360iDev conferences. I am writing this from the lobby during the pre-conference day of 360iDev in Austin, and I am really look forward to the conference.

Also, here is a little more information about me, Doug Sjoquist, and how I came to my current place in life. You should follow me on twitter and subscribe to my blog. Have a great day!