Tuesday, February 1, 2011

Attributed Logging

With an exception in Objective-C, you receive an informative printout like the following:
-[__NSArray0 addObject:]: unrecognized selector sent to instance 0x104660
The reason is great, but often just as useful, is the first part, which tells you where the error is coming from, in this case, a non-mutable array. This got me thinking, what if NSLog displayed that too. It normally displays the time, which is nice, but most of the time it's not that useful at all, and just takes up a lot of space.

I created a Log that does just this. It outputs a message with the format "caller:message". it has two methods: ALog, which logs all the time, and DLog, which logs only on debug builds. It works exactly like NSLog, with formatting of strings. In addition, you can throw any old object in it and it will print the [object description] out.

Here is an example of usage:

int main(int argc, const char * argv[]) {
 NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
 
 ALog(@"Regular log");
 DLog(@"Debug log");
 
 [pool drain];
    return 0;
}

Output:

main: Regular log
main: Debug log            (only would get printed on a debug build)

If the caller is a C-function, it will have output like this, if it is from an Objective-C method the message will have the format "-[MyObject myMethod]: my message".

I'm posting the code here, and an archive is also linked at the bottom: I find that the easiest thing to do to use this, is to simply import the header file into the precompiled header for the whole project, so that I don't have to import into each file individually.

Header file:

#import <Foundation/Foundation.h>

void ALog(id format, ...);
void DLog(id format, ...);

Implementation file:
Note that my code uses DEBUG rather than NDEBUG, because by default, Xcode defines DEBUG.

#import "Log.h"
#import <dlfcn.h>
#import <execinfo.h>

void ALog_PrintMessage(const char *caller, id format, va_list argList) {
 // Collate message.
 NSString *message;
 if ([format isKindOfClass: [NSString class]]) {
  message = [[NSString alloc] initWithFormat: format arguments: argList];
 } else {
  message = [format description];
 }
 
 // Output with format "caller: message".
 printf("%s: %s\n", caller, [message UTF8String]);
}

void ALog(id format, ...) {
 
 // Resolve caller.
 void *callstack[2];
 backtrace(callstack, 2);
 Dl_info info;
 dladdr(callstack[1], &info);
 
 va_list argList;
 va_start(argList, format);
 ALog_PrintMessage(info.dli_sname, format, argList);
 va_end(argList);
}

void DLog(id format, ...) {
#if !defined (DEBUG)
 // Do nothing if in release mode.
#else
 // Resolve caller.
 void *callstack[2];
 backtrace(callstack, 2);
 Dl_info info;
 dladdr(callstack[1], &info);
 
 va_list argList;
 va_start(argList, format);
 ALog_PrintMessage(info.dli_sname, format, argList);
 va_end(argList);
#endif
}

No comments: