Tuesday, April 14, 2009

Improving on NSLog

While developing I am a big user of logging. Having a well-formated series of reports from my half-developed program telling me that everything has worked up to that point is very comforting to me. It is also really nice when something goes horribly wrong to be able to dial-up the logging on a program or script to help figure out what is going on. But when running a program day-to-day I don't want all the noise clogging up my system logs.
Up until very recently I have been using mixtures of NSLog, printf/fprintf, and syslog to get my debugging logged, but each one of those has their limitations: NSLog does not have any sense of "log level", the printf/fprintf combination gives me two levels of logging, but I then have to manage all of that and figure out where to store things, and syslog mostly matches my thinking but does not integrate completely into a Cocoa program.
Then I read Peter Hosey's series of posts about ASL (Apple System Logger). ASL does have glaring hole for Cocoa programming: it does not understand NSStrings. But that same series of posts had the solution to that: use a #define statement to convert the format and arguments to a single UTF string, which ASL is very happy to take. He even provided a line to do so: 
#define asl_NSLog(client, msg, level, format, ...) asl_log(client, msg, level, "%s", [[NSString stringWithFormat:format, ##__VA_ARGS__] UTF8String])
This was almost enough, but there are still a few things I was not happy with: I only use the default client and message, and there are times I want to have the debugging output to go to stdout/stderr in addition to the system logs. So I have modified Peter's example and come up with the following:
// Inspired by Peter Hosey's series on asl_log
#ifndef ASL_KEY_FACILITY
 #define ASL_KEY_FACILITY "Facility"
#endif

#define LOG_LEVEL_TO_PRINT ASL_LEVEL_WARNING
#define NSLog_level(log_level, format, ...) asl_log(NULL, NULL, log_level, "%s", [[NSString stringWithFormat:format, ##__VA_ARGS__] UTF8String]); if (log_level >= LOG_LEVEL_TO_PRINT) { if (log_level "%s\n", [[NSString stringWithFormat:format, ##__VA_ARGS__] UTF8String]); } else { printf("%s\n", [[NSString stringWithFormat:format, ##__VA_ARGS__] UTF8String]); } }

#define NSLog_emerg(format, ...) NSLog_level(ASL_LEVEL_EMERG, format, ##__VA_ARGS__)
#define NSLog_alert(format, ...) NSLog_level(ASL_LEVEL_ALERT, format, ##__VA_ARGS__)
#define NSLog_crit(format, ...) NSLog_level(ASL_LEVEL_CRIT, format, ##__VA_ARGS__)
#define NSLog_error(format, ...) NSLog_level(ASL_LEVEL_ERR, format, ##__VA_ARGS__)
#define NSLog_warn(format, ...) NSLog_level(ASL_LEVEL_WARNING, format, ##__VA_ARGS__)
#define NSLog_notice(format, ...) NSLog_level(ASL_LEVEL_NOTICE, format, ##__VA_ARGS__)
#define NSLog_info(format, ...) NSLog_level(ASL_LEVEL_INFO, format, ##__VA_ARGS__)
#define NSLog_debug(format, ...) NSLog_level(ASL_LEVEL_DEBUG, format, ##__VA_ARGS__)
Now I have 8 functions that I can use as drop-in replacements for NSLog that will send the output to the system logging facility, and depending on the level I set with LOG_LEVEL_TO_PRINT, also to stdout/stderr. For me this also has the nice benefit of getting rid of all of the timestamps in the XCode console when I am developing which helps me concentrate on my program's output better (while still preserving this information in the system log).

No comments:

Post a Comment