[Objc] Write console log to file… and send for email

I’ve prepared a simple class that write logs on device disk.
This is very useful, because you can send and receive logs from apps, crashlog, backtrace and debug informations.

How it works

It has the same behavior of NSLog method. You can pass as parameters, the format and the optional arguments.

[code autolinks=”false” collapse=”false” firstline=”1″ gutter=”true” htmlscript=”false” light=”false” padlinenumbers=”false” smarttabs=”true” tabsize=”4″ toolbar=”false”]FOUNDATION_EXPORT void NSLog(NSString *format, …) NS_FORMAT_FUNCTION(1,2);

void NSLog (NSString* format, …) {
// […]
}

NSLog(@"[UI:%d] – %@", [NSThread isMainThread], aMessage);[/code]

I’ve made a custom NSLog, called APLog, with the same imprint.

[code autolinks=”false” collapse=”false” firstline=”1″ gutter=”true” htmlscript=”false” light=”false” padlinenumbers=”false” smarttabs=”true” tabsize=”4″ toolbar=”false”]FOUNDATION_EXPORT void APLog(NSString *format, …) NS_FORMAT_FUNCTION(1,2);

void APLog (NSString* format, …) {
// […]
}

APLog(@"[UI:%d] – %@", [NSThread isMainThread], aMessage);</div></td>
[/code]
The difference is that APLog write the logs on the console and in the same time, to a file, in details, append text to a file called console.log in your NSDocumentDirectory, to a max size of XXMb (to prevent big log file).

[code autolinks=”false” collapse=”false” firstline=”1″ gutter=”true” htmlscript=”false” light=”false” padlinenumbers=”false” smarttabs=”true” tabsize=”4″ toolbar=”false”]//[…]
[fileHandle seekToEndOfFile];
[fileHandle writeData:[formattedMessage dataUsingEncoding:NSUTF8StringEncoding]];
//[…][/code]

Here the complete classes and the APLog method and other stuffs:

https://gist.github.com/elpsk/9b72ffaffec947546c6b

Put the include to this file, in your global project config, like Prefix.pch to use in entire application.

Get callStack symbols (main.m)

[code autolinks=”false” collapse=”false” firstline=”1″ gutter=”true” htmlscript=”false” light=”false” padlinenumbers=”false” smarttabs=”true” tabsize=”4″ toolbar=”false”](lldb) backtrace
* thread #1: tid = 0x1c03, 0x00003146 Debug`-[APViewController callMe:andANumber:](self=0x07187e50, _cmd=0x000038b9, str=0x0715aa40, aNum=38) + 230 at APViewController.m:33, stop reason = breakpoint 3.1
frame #0: 0x00003146 Debug`-[APViewController callMe:andANumber:](self=0x07187e50, _cmd=0x000038b9, str=0x0715aa40, aNum=38) + 230 at APViewController.m:33
frame #1: 0x0000304a Debug`-[APViewController viewDidLoad](self=0x07187e50, _cmd=0x005c5a77) + 122 at APViewController.m:16
frame #2: 0x000f41c7 UIKit`-[UIViewController loadViewIfRequired] + 536
frame #3: 0x000f4232 UIKit`-[UIViewController view] + 33
frame #4: 0x000433d5 UIKit`-[UIWindow addRootViewControllerViewIfPossible] + 66
frame #5: 0x0004376f UIKit`-[UIWindow _setHidden:forced:] + 368
frame #6: 0x00043905 UIKit`-[UIWindow _orderFrontWithoutMakingKey] + 49
[…]
frame #10: 0x00010747 UIKit`-[UIApplication _callInitializationDelegatesForURL:payload:suspended:] + 1248
frame #11: 0x0001194b UIKit`-[UIApplication _runWithURL:payload:launchOrientation:statusBarStyle:statusBarHidden:] + 805
frame #12: 0x00022cb5 UIKit`-[UIApplication handleEvent:withNewEvent:] + 1022
frame #13: 0x00023beb UIKit`-[UIApplication sendEvent:] + 85
frame #14: 0x00015698 UIKit`_UIApplicationHandleEvent + 9874
[…]
frame #22: 0x0001117a UIKit`-[UIApplication _run] + 774
frame #23: 0x00012ffc UIKit`UIApplicationMain + 1211
frame #24: 0x00002b22 Debug`main(argc=1, argv=0xbffff3a4) + 130 at main.m:16
frame #25: 0x00002a55 Debug`start + 53
(lldb)[/code]

If you want to collect app crash (backtrace) and redirect to APLog, open your main.mfile and change the original one:

[code autolinks=”false” collapse=”false” firstline=”1″ gutter=”true” htmlscript=”false” light=”false” padlinenumbers=”false” smarttabs=”true” tabsize=”4″ toolbar=”false”]int main(int argc, char * argv[])
{
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}[/code]

to this:

[code autolinks=”false” collapse=”false” firstline=”1″ gutter=”true” htmlscript=”false” light=”false” padlinenumbers=”false” smarttabs=”true” tabsize=”4″ toolbar=”false”]int main(int argc, char * argv[])
{
@autoreleasepool {
int retVal;
@try {
retVal = UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
@catch (NSException *exception) {
APLog(@"[APL] !!! -&gt; %@", [exception callStackSymbols]);
}
return retVal;
}
}[/code]

Well, UIApplicationMain is catched and you can write the error to disk.

More crash info on: “Xcode – Power of breakpoints” and 0x8badf00d and others crashes.

ZIP and send file

Using Objective-Zip library, prepare a zip file:

[code autolinks=”false” collapse=”false” firstline=”1″ gutter=”true” htmlscript=”false” light=”false” padlinenumbers=”false” smarttabs=”true” tabsize=”4″ toolbar=”false”]@try {
ZipFile *zipFile = [[ZipFile alloc] initWithFileName:yourZipFilePath mode:ZipFileModeCreate];

NSString *fName = [NSString stringWithFormat:@"%@/console.log", documentsDirectory];
ZipWriteStream *stream = [zipFile writeFileInZipWithName:fName compressionLevel:ZipCompressionLevelBest];

NSData *fData = [NSData dataWithContentsOfFile:fName];
[stream writeData:fData];
[stream finishedWriting];

[zipFile close];
[zipFile release];
}
@catch (ZipException *ze) {
return nil;
}
@catch (id e) {
return nil;
}[/code]

and send it for email, or make an upload to a web service automatically…

[code autolinks=”false” collapse=”false” firstline=”1″ gutter=”true” htmlscript=”false” light=”false” padlinenumbers=”false” smarttabs=”true” tabsize=”4″ toolbar=”false”]// SEND MAIL WITH ZIP ATTACHMENT
if ( [MFMailComposeViewController canSendMail] )
{
MFMailComposeViewController *mail = [[MFMailComposeViewController alloc] init];
mail.mailComposeDelegate = self;
[mail setToRecipients:@[ @"support@yoursite.com" ]];
[mail setSubject:@"[APP-iOS] Support request"];

NSData *zipData = [NSData dataWithContentsOfFile:@"yourZipPath.zip"];
[mail addAttachmentData:zipData mimeType:@"application/zip" fileName:@"console.zip"];

[self presentViewController:mail animated:YES completion:NULL];
}[/code]

Check for crash on start

You want to know if your app is crashed last time?
Well, store a NSUserdefault value on startup and delete on quit..
On startup if value stored is != EMPTY, a crash happened.

When you want, in your code, check it:

[code autolinks=”false” collapse=”false” firstline=”1″ gutter=”true” htmlscript=”false” light=”false” padlinenumbers=”false” smarttabs=”true” tabsize=”4″ toolbar=”false”]- (void) checkForCrash
{
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
if ( appDelegate.hasCrashed )
{
[UIAlertView showAlertViewWithTitle:@"Crash!" message:NSLocalizedString(@"Application has crashed :(. Please send us an email to fix the issue.", nil) cancelButtonTitle:NSLocalizedString(@"Cancel", nil) otherButtonTitles:@[NSLocalizedString(@"Send", nil)] onSelect:^(int buttonIndex)
{
// what you want, load a controller, say Hi! to user, etc…
SegnalazioneViewController *segnalazione = [self.storyboard instantiateViewControllerWithIdentifier:@"segnalazione"];
[self.navigationController pushViewController:segnalazione animated:YES];

} onCancel:^{

}];
}
}[/code]

Note:

You can find showAlertViewWithTitle:

[code autolinks=”false” collapse=”false” firstline=”1″ gutter=”true” htmlscript=”false” light=”false” padlinenumbers=”false” smarttabs=”true” tabsize=”4″ toolbar=”false”]+ (UIAlertView*) showAlertViewWithTitle:(NSString*) title
message:(NSString*) message
cancelButtonTitle:(NSString*) cancelButtonTitle
otherButtonTitles:(NSArray*) otherButtons
onSelect:(SelectBlock) selected
onCancel:(CancelBlock) cancelled;[/code]

on this old article: uialertview-and-blocks

Well, enjoy crashing!

 

Alberto Pasca

Software engineer @ Pirelli & C. S.p.A. with a strong passion for mobile  development, security, and connected things.