Logging with CocoaLumberjack and TestFlight
Consider the following situation that happens far too often in mobile app development: You’ve just released an app that works perfectly for you, and you’ve tested it extensively. You’re proud of your accomplishments and submit the app to the world, only to have several emails sent to you from users who have nothing but difficulties in running the app. You send a bug fix release to the App Store, but since you’re still unable to reproduce the problem you’re at the whim of luck and end-user feedback. You can hope your users know how to send you a crash report, but what if the app isn’t actually crashing? Wouldn’t it be great to be able to access your app’s log information from that client to be able to troubleshoot the problems?
In working on Docset Viewer I’ve run up against this very same problem. I’ve solved most of the bugs reported to me, but I still have gotten a few troubling reports that users have gotten into a stuck state where they had to uninstall/reinstall the app to get it to work consistently. Obviously some bad data got stored somewhere that was breaking the app, but until I’m able to find out what it was that was making the app feel ill, I can’t solve the root cause.
With the recent release of TestFlight’s “Live” service, they allow for automatic crash-log reporting and log-file upload support to their cloud-based service. But since the vast majority of my users don’t have any problems using my app, I don’t want to wade through tons of log information. Additionally, during the development of the app I’ll use console logging for my own internal diagnostics, but the high level of verbosity would be overkill when used in a production app. Instead of simply commenting out or deleting log lines when I’m done developing a feature, it would be useful to have a flexible logging solution that would let me develop my code with ease, while still allowing to have certain high-priority log messages make it through to me in production.
I’ve managed to come to a happy medium between ease of use and cloud-based log reporting by combining the excellent CocoaLumberjack library with the TestFlight Live SDK.
What’s TestFlight Live
TestFlight has been around for a while as a solution for simplifying the beta-tester management process. It includes an SDK that lets you send crash reports and usage information to the web-based service to see which beta testers have installed the app, what issues they’ve encountered, and even provides a method to send feedback to you from within the app. Recently however they released an update to their service that extends it to become TestFlight Live, which provides crash log reporting and usage analysis for live apps in the App Store.
What’s CocoaLumberjack?
In it’s own words, CocoaLumberjack is “…similar in concept to other popular logging frameworks such as log4j, yet is designed specifically for Objective-C, and takes advantage of features such as multi-threading, grand central dispatch (if available), lockless atomic operations, and the dynamic nature of the Objective-C runtime.” It is faster than plain NSLog, is more flexible through the use of different log levels, and is pluggable for providing additional modes of logging above and beyond simple console logging.
I was introduced to this wonderful framework through the CocoaHTTPServer library which I use in Docset Viewer as well as another upcoming app of mine. In my apps so far however, I took many of Lumberjack’s more advanced features for granted, only opting to use its different log-levels and per-configuration log targets to customize my app’s behaviour. For example, on debug builds my app logs at a higher verbosity than on release builds.
This all changed when I released my recent v1.3 update to Docset Viewer. Between v1.1 and v1.3 I fixed a number of bugs reported by my users, and the app is far more stable as a result. But there are still some hard-to-reproduce issues that are occurring and I need more information to be able to correct them properly.
To get more insights into what’s happening, I used Lumberjack’s custom logger capability to add my own class for reporting log messages to TestFlight.
#import <UIKit/UIKit.h>
#import "DDLog.h"
@interface TestflightLogger : DDAbstractLogger <DDLogger>
@end
#import "TestflightLogger.h"
#import "TestFlight.h"
@implementation TestflightLogger
- (void)logMessage:(DDLogMessage *)logMessage {
NSString *logMsg = logMessage->logMsg;
if (logMsg)
TFLog(logMsg);
}
@end
Using this in your application is easy. You simply need to add the following lines to the beginning of your application:didFinishLaunchingWithOptions:
method:
[TestFlight takeOff:kTestFlightTeamID];
[DDLog addLogger:[DDASLLogger sharedInstance]];
#ifndef DEBUG
TestflightLogger *logger = [[[TestflightLogger alloc] init] autorelease];
[DDLog addLogger:logger];
#endif
This assumes you have a “DEBUG” definition declared for your debug or adhoc builds which helps prevent log messages filtering into TestFlight unnecessarily. With this addition, not only will your TestFlight account give you timely information about crashes encountered by your users, but you’ll have access to the log messages that will provide valuable insights into why those crashes occurred.
This is just the tip of the iceberg, and I’m sure there’s more areas where TestFlight and Lumberjack can be used to enhance your ability to quickly fix bugs.