Simple, more flexible Obj C Logging

NSLog is a simple way of logging text and values out to Console in your code.

When I first started programming Alfred, I very quickly realised that NSLog didn’t provide me with enough granularity to be particularly useful. I wanted to separate out log messages such that in normal use, I only see significant messages, however I still have the option to see ALL logging if I needed.

Enter APLog, a utility class offering multiple levels of configurable logging to NSLog.

Logging Levels and Simple Usage

APLog supports 5 levels of logging. Tiny, Fine, Info, Warning and Error, These methods output depending on a ‘logging’ value set in NSUserDefaults as follows:

0: Only Error is output
1: Error, Warning and Info is output
2: Error, Warning, Info and Fine is output
3: Everything is output

You can decide how these different levels suit you, I generally log things I want to see often to info and scope down from there with tiny messages being very low level things you may never want to see unless something is acting up.

// error logging
[APLog error:@"Unable to connect to network"];

// warning logging
[APLog warning:@"Clipboard cache contains unexpected info, cleaning up"];

// standard logging
[APLog info:@"Application Started"];

// fine logging
[APLog fine:@"Loaded %d rows from the database", 30]; 

// tiny logging
[APLog tiny:@"Currently processing item %d of %d", 4, 20000];

The beauty of the logging granularity is that when the logging value isn’t set in NSUserDefaults, the end user doesn’t get their console log clogged up with your logging. Subsequently, by default I have my logging level set to 1 which means I only see info, warning and fine messages during development while still logging everything which may be of use later.

Creating class methods

We want this logging to be as easy to use as possible, without the need for instantiating a class, however having a more modern Obj C interface. This can easily be accomplished using class methods.

By using a + before a method, this is making that method available without instantiating the class. In the header file, we add the info method as follows:

// in APLog.h
// info log
+(void) info:(NSString *)format,...;

The ,…; allows for open ended arguments like you see in NSLog where you can pass additional parameters in which are formatted into the log string.

// in APLog.m
// info log
+(void) info:(NSString *)format,... {
  if ([[NSUserDefaults standardUserDefaults] integerForKey:@"logging"] >= 1) {
    va_list args;
    va_start(args, format);
    NSString *str = [[NSString alloc] initWithFormat:format arguments:args];
    [AlfredLog doLog:str level:@"INFO"];
    [str release];
    va_end(args);
  }
}

First, we check NSUserDefaults to see if this level of logging is required, if so, we split out the argument levels and pass them to a method which actually does the logging. Note that we alloc the string and release it, this means that if APLog is used in a large loop, we won’t need to wait for the autorelease pool to release the memory for logging, it happens here and now.

The other levels of logging for error, warning, fine and tiny are created in the same way as above but with a different method signature.

Using a separate method for Logging

By separating the actual method for logging, it allows us to quickly change the format we want for all of the logging output. In my case, I like

[level] log message

… as this makes things easy to scan for INFO, FINE, TINY – especially considering they are all 4 fix width characters in size. This also makes ERROR and WARNING stand out when reading your log.

// do the logging
+(void) doLog:(NSString *)log level:(NSString *)level {
  NSLog(@"[%@] %@", level, log);
}

The doLog method is a categorised private method, something I will cover in a different blog post.

Easy use by prefixing in your headers

One final step is to add APLog.h to your Prefix.pch file which means you won’t need to import this in every class you want to Log in. Your Prefix.pch will look something like this:

#ifdef __OBJC__
  #import <Cocoa/Cocoa.h>
  #import "APLog.h"
#endif

Download the source

Download APLog to use in all of your projects, commercial or otherwise. This code is released under the MIT License.

Note: Don’t forget to set the ‘logging’ value in NSUserDefaults (Alfred has the index of a NSPopupMenu bound to this value in the prefs), or call [[NSUserDefaults standardUserDefaults] setInteger:3 forKey:@”logging”]; near the beginning of your code to log all levels.

I hope this is as useful to you as it is for me. Please leave me any comments or improvements below.

Cheers,
Andrew

11 thoughts on “Simple, more flexible Obj C Logging

  1. This is a very interesting article. I have considered making a basic logging system in projects before, but never had them big enough for it to be worth it. I can see how something like this would be invaluable in a complex piece of software where just having breakpoints with actions might be too cumbersome. I also think it’s great how simple it would be to code.

    1. For things that you need to enable/disable often, adding breakpoints in Xcode that print out things, or only occur on particular conditions is really useful. You can just toggle them on and off in the breakpoints sidebar.

  2. I feel strangely guilty for saying this, but I never touch the debugger. It scares me, and I’ve never found a bug that I couldn’t resolve with a well placed printf() or two. But one day, I’d like to learn to use it properly.

    1. Learning how to use a debugger (and I’m by no means an expert) was probably the single thing that improved my programming skills the most.

      Just being able to set a breakpoint and step through line by line to see the variables change is invaluable and I can’t imagine debugging without it. I would highly recommend learning the basics of GDB in Xcode.

  3. Thanks again for this, Andrew. I incorporated APLog into my current project and found it really useful.

    It inspired me to think more deeply about my logging needs and I ended up modifying it for my own purposes. I added a per-file logging option. When debugging a specific class it enables detailed logging for that class only.

    I also reimplemented it as a function with companion macros, rather than a class. That’s just my own perverse preference; the basic idea is the same.

    Here it is (with a detailed README): https://github.com/invariant/NMLog

  4. Thanks Andrew and others for sharing this!

    I was wondering how you guys convey error messages from deep model classes up to the user interface… do you use Exceptions? or a modelClass.lastError property?

    It is nice to log to console, but some errors need to be presented to the user and i have not found a good way to cleanly bring these error messages up to the UI

    1. This is kind of separate to the simple lightweight logging in this post – I’ve added a note to do a blog post about this :)

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s