Wednesday, September 7, 2011

How to Do Dynamic Debug Logging in Released Application

Remote debugging can be really difficult, especially for released applications. You can't send ad hoc releases to everyone! Would love that, with service like TestFlight, but normal Apple iOS developer account can register only max 100 devices.

So what can you do?

If you log debug data all the time, it will slow down your application - not to mention Apple might have something to say about your app "spamming" shared console log. You could create your own log file, add code and UI to let user to email you etc. but that's a lot of code to create and maintain. How would you make sure log file doesn't just keep growing and finally fill up the whole device? How would you make sure you get the relevant log data?

Creating separate releases per debug case is really not possible. All releases go public via AppStore and again Apple might have something to say about you releasing "similar" apps. This would create so many problems, better to forget the whole thing!

One solution is to include the complete debug log system inside your released application - and turn it off! Allow users to activate it temporarily, do their problem case, disable debugging again and email you the log file.

Here is one way, modification based on current Stack Overflow answer for question "NSLog tips and tricks" with DLog and ALog. Please note that your debug messages go to system console log, as always when using NSLog. User needs to get your log data from device console e.g. using iPhone Configuration Utility.

Put this code into a separate header (easier to reuse in next project), which you include in YourApp_Prefix.pch file:
// Define global variable
extern BOOL myDebugEnabled;
#define DLog(fmt, ...) if (myDebugEnabled) NSLog((@"%s [Line %d] " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__);
#define ALog(fmt, ...) NSLog((@"%s [Line %d] " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__);
Choose how user can enable and disable logging. Here I use UITableViewController, where row selection normally doesn't do anything. This could be e.g. your Settings or About view.
// Create global variable
BOOL myDebugEnabled = FALSE;

- (NSIndexPath *)tableView:(UITableView *)tableView
  willSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
  NSString *kMyDebugEnabled = @"myDebugEnabled";
  NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
  FSDebugEnabled = [prefs boolForKey:kMyDebugEnabled];
  [prefs setObject:[NSString stringWithFormat:@"%i", !myDebugEnabled] forKey:kMyDebugEnabled];

  UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Debug Log"
    message:myDebugEnabled ? @"ON" : @"OFF"
    delegate:nil
    cancelButtonTitle:@"OK"
    otherButtonTitles:nil];
  [alert show];
  [alert release];

  return nil; // row select disabled
}
Yes, it's a global variable. Yes, it's really bad. No, you shouldn't use it.

However when comparing the required development effort, code complexity, debugging of debug system and maintainability of using one global variable vs. anything else, global variable wins without any questions.

Well, as long as your main goal is to write applications instead of debugging systems.

2 comments:

  1. ... or you may want to use my NSLogger https://github.com/fpillet/NSLogger and use some of the example macros highlighted in the wiki.

    ReplyDelete
  2. So you did write a debugging system :) Thanx for the effort, have to check it!

    ReplyDelete