Upcoming and OnDemand Webinars View full list

A Quieter Log

Mark Dalrymple

Montana log cabinI’m a fan of Caveman Debugging, where you use print statements to trace program flow and display specific program information. I was kind of surprised when reading Coders at Work how many industry luminaries do the same thing. It’s just another tool in the debugging arsenal, along with unit tests, debuggers, Instruments, and the plethora of other software investigation toys.

When Caveman Debugging, I usually just use NSLog. It’s handy and easy to type. But sometimes I get annoyed by its verbosity. Like when I print out this array:

NSArray *array = [NSArray arrayWithObjects: @"Greeble", @"Bork", @"Fnord", nil];
NSLog (@"The array is %@", array);

I get this in the Xcode debugger pane, or out to a terminal window:

2012-06-24 21:05:19.731 log[45714:303] The array is (
    Greeble,
    Bork,
    Fnord
)

Knowing the year, time, program, pid, and thread ID definitely have their uses, especially when I have log statements in a book – I can tell exactly how many years ago it was I ran that particular piece of code.

But there are times where all that stuff isn’t germane to the problem I’m tracking down, especially if I’m running a program in a narrowish terminal. That extra info pushes the useful content off to the right, sometimes wrapping the line around. Also, I can’t just skim down the left-side of the window. Most of array being logged is flush-left, but the first line starts halfway across the screen.

Additionally, NSLog sends its text to the system console. Sometimes I might be logging something that shouldn’t be enshrined in the system log, such as passwords or security tokens, or the contents of my Patsy Cline / Rammstein playlist.

So I can’t use NSLog if I want less chatty output. What options do I have now? The next level down in the technology stack is the C standard library, which has the printf family of functions. Flexible, powerful, and well-known, I can pretty much use the exact same log strings with printf that I can with NSLog. One problem, though. The standard library is below Cocoa and Objective-C, so it has no idea how to handle the %@ format specifier, which just sends description to the corresponding object in the argument list. Not being able to use %@ is a serious limitation when caveman debugging Cocoa [Touch] applications.

In that case, I dust off my QuietLog function and use it (you can find it at this gist)

void QuietLog (NSString *format, ...) {
    va_list argList;
    va_start (argList, format);

    NSString *message = [[NSString alloc] initWithFormat: format
                                          arguments: argList];
    va_end (argList);

    fprintf (stderr, "%sn", [message UTF8String]);

} // QuietLog

So, how does this work? It’s a pretty standard varargs function. See the stdarg manpage if you’re curious about the full details. This function just turns around and calls an NSString method that does the heavy lifting for me.

Here’s the function line-by-line:

void QuietLog (NSString *format, ...) {

This declares a function named QuietLog that returns nothing. It takes an NSString for the format string. The three dots say that the function can be passed an arbitrary number of arguments. What happens to those arguments is entirely up to the function. In this case, the consumption of arguments is driven by the format string. You’ve used differing varargs semantics before, such as terminating the list of objects with nil with NSArray’s arrayWithObjects:, and passing pairs of objects for NSDictionary’s dictionaryWithObjectsAndKeys:, and.

Varargs functions have to take at least one parameter. That parameter is how the argument processing machinery knows where to start looking for the extra arguments, which you pass to va_start:

    va_list argList;
    va_start (argList, format);

argList is a local variable of type va_list. va_list is an opaque type which acts like a pointer into the call stack. Calling va_start, in essence, makes va_list point to the next function argument after format. If you were processing the arguments yourself, you’d call va_arg with an expected type, and you’d get the value back from the next argument.

Luckily, QuietLog doesn’t have to do that much work. NSString has a method, initWithFormat:arguments: that will take the format string and one of these variable list pointers and make a string out of it.

    NSString *message = [[NSString alloc] initWithFormat: format
                                          arguments: argList];

If you’re not using ARC, you’ll want to autorelease this.

initWithFormat:arguments: looks at the format string (which QuietLog just turns around and passes on unmodified) and processes the argument list, including the hyper-useful %@ format specifier. The end result is an NSString with the format string with its format specifiers replaced with the appropriate data values.

Any function or method that takes a va_list can be used like this, as the workhorse for some higher-level purpose. Cocoa has a couple of them, such as one for raising exceptions, and methods to create new strings and predicates.

    va_end (argList);

Clean up any state that might be used to point into the call stack.

So now there’s an NSString with the contents we want to log. Time to print it out!

    fprintf (stderr, "%sn", [message UTF8String]);

UTF8String converts the string (if necessary) into a zero-terminated stream of bytes, a form suitable for passing down into the C standard library. This code prints out the string, along with a newline, to the standard-error stream.

Now the logging is much quieter:

NSArray *array = [NSArray arrayWithObjects: @"Greeble", @"Bork", @"Fnord", nil];
QuietLog (@"The array is %@", array);

which prints out

The array is (
    Greeble,
    Bork,
    Fnord
)

(and, of course, I have to plug Advanced Mac OS X Programming, the Big Nerd Ranch Guide , which covers varargs processing)

Not Happy with Your Current App, or Digital Product?

Submit your event

Let's Discuss Your Project

Let's Discuss Your Project