Slide 1

Slide 1 text

Mobile Client Crash Logs Ali Rantakari Mobile Fortnightly • 9.8.2013

Slide 2

Slide 2 text

Why • You might have bugs that you don’t know about • Users will not send you “steps to reproduce” — they will leave a zero-star review that says “shit dont work” • If you find out (or the client finds out) that there is a crasher in the released build, you’ll want to fix it ASAP • If you don’t know how to reproduce it, you’ll need the stack trace

Slide 3

Slide 3 text

How Use a service Do it yourself

Slide 4

Slide 4 text

Why use a service • Aggregation — a single bug can generate thousands of crash log reports, but you only want to look at a single instance and ignore the rest • (Semi)automatic symbolication for iOS (in some services, just upload the .dSYM of each build — in others, even this is not required) • Features for searching, browsing, and filtering crashes based on OS/app version, or other device state variables • Project management tool integrations (bug trackers etc.)

Slide 5

Slide 5 text

Services • Mobile OS SDKs for iOS, Android, WP… • Symbolication • Custom report metadata • Looks like they are trying to be more “enterprise- like” • My limited experience: seemed pretty good • 3rd party SDKs for iOS and Android • Symbolication (client-side) • Custom report metadata • Pretty much meant for server-side (Ruby) apps; mobile apps not a “good fit” • My experience: I wouldn’t recommend for mobile apps • Only iOS SDK (Android coming later) • Symbolication • Custom report metadata • I have no experience

Slide 6

Slide 6 text

Services • Mobile OS SDKs for iOS, Android, WP… • Symbolication (paying customers only; server- or client-side) • Custom report metadata • My limited experience: seemed pretty good • Mobile OS SDKs for iOS, Android, WP… • SaaS or self-hosted (QuincyKit) • Custom report metadata: one string (iOS) or none (WP) • Well-known • I have no experience • SDKs for iOS and Android only • Symbolication (client-side) • Custom report metadata • They act like they’re “Invite only” and make you jump through hoops to start using it • Owned by Twitter nowadays (WTF.) • Well-known • Based on some quick testing, seems very polished

Slide 7

Slide 7 text

Which one to try? Go ahead Go ahead (if only iOS + Android) Go ahead Go ahead (Futu has an account?) Forget it. Go ahead (if only iOS) FREE $ $ SELF HOST FREE BASIC FREE BASIC FREE

Slide 8

Slide 8 text

Which one to try? Go ahead Go ahead (if only iOS + Android) Go ahead Go ahead (Futu has an account?) Forget it. Go ahead (if only iOS) FREE $ $ SELF HOST FREE BASIC FREE BASIC FREE Android

Slide 9

Slide 9 text

Which one to try? Go ahead Go ahead (if only iOS + Android) Go ahead Go ahead (Futu has an account?) Forget it. Go ahead (if only iOS) FREE $ $ SELF HOST FREE BASIC FREE BASIC FREE Windows Phone

Slide 10

Slide 10 text

Why do it manually • No existing services fulfill requirements, whatever they may be • Client doesn’t want to use a service for whatever reason • Be a good consultant and help them make a good decision • “We want it in our own datacenter” is not reason enough to do it manually → HockeyApp (QuincyKit) can be self-hosted

Slide 11

Slide 11 text

PLCrashReporter Catch Crashes void myprefix_initCrashReporting() { if (PLCrashReporter.sharedReporter.hasPendingCrashReport) myprefix_handlePendingCrashReport(); NSError *crashLogEnablingError; if (![PLCrashReporter.sharedReporter enableCrashReporterAndReturnError:&crashLogEnablingError]) { // Deal with error } } Manually

Slide 12

Slide 12 text

PLCrashReporter Catch Crashes void myprefix_handlePendingCrashReport() { NSError *crashLoadError; NSData *crashData = [PLCrashReporter.sharedReporter loadPendingCrashReportDataAndReturnError:&crashLoadError]; if (crashData == nil) { // Handle the error [PLCrashReporter.sharedReporter purgePendingCrashReport]; return; } NSError *reportGenerationError; PLCrashReport *crashReport = [[PLCrashReport alloc] initWithData:crashData error:&reportGenerationError]; if (crashReport == nil) { // Handle the error [PLCrashReporter.sharedReporter purgePendingCrashReport]; return; } NSString *formattedCrashReport = [PLCrashReportTextFormatter stringValueForCrashReport:crashReport withTextFormat:PLCrashReportTextFormatiOS]; [PLCrashReporter.sharedReporter purgePendingCrashReport]; // Do something with the crash report } Manually

Slide 13

Slide 13 text

Symbolication This is not very useful: Thread 0: 0 libsystem_kernel.dylib 0x3af45eb4 0x3af45000 + 3764 1 CoreFoundation 0x32d88045 0x32cf1000 + 618565 2 CoreFoundation 0x32d86d5f 0x32cf1000 + 613727 3 CoreFoundation 0x32cf9ebd 0x32cf1000 + 36541 4 CoreFoundation 0x32cf9d49 0x32cf1000 + 36169 5 GraphicsServices 0x368bd2eb 0x368b8000 + 21227 6 UIKit 0x34c0f301 0x34bb8000 + 357121 7 myappname 0x00089ebf 0x7d000 + 52927 Manually

Slide 14

Slide 14 text

Symbolication Save the .xcarchive of each public release! (put them into the VCS) Manually

Slide 15

Slide 15 text

Symbolication Save the .xcarchive of each public release! (put them into the VCS) • This preserves the debug symbols for the binary • Dropping an .xcarchive into the Xcode organizer allows the auto-symbolication features to work Manually

Slide 16

Slide 16 text

Symbolication #!/bin/bash # # Symbolicates a single crash log to stdout. # export DEVELOPER_DIR="`xcode-select -print-path`" "$DEVELOPER_DIR/Platforms/iPhoneOS.platform/Developer/Library/ PrivateFrameworks/DTDeviceKit.framework/Versions/A/Resources/ symbolicatecrash" "$@" Manually

Slide 17

Slide 17 text

Symbolication 11 myappname 0x000ada99 my_reticulateSplines() (MYReticulation.m:18) 11 myappname 0x000ada99 0xab000 + 10905 Instead of this: you’ll see this: $ symbolicatecrash.sh crashlogfile.crash Manually

Slide 18

Slide 18 text

Catch Crashes private void RootFrame_NavigationFailed( object sender, NavigationFailedEventArgs e) { if (System.Diagnostics.Debugger.IsAttached) { System.Diagnostics.Debugger.Break(); } else { // Do something with `e.Exception` // (use .StackTrace) } } private void Application_UnhandledException( object sender, ApplicationUnhandledExceptionEventArgs e) { if (System.Diagnostics.Debugger.IsAttached) { System.Diagnostics.Debugger.Break(); } else { // Do something with `e.ExceptionObject` // (use .StackTrace) } } Manually

Slide 19

Slide 19 text

Symbolication • StackFrame::GetFileName() is marked “security critical” and thus cannot be used ☹ • StackFrame::GetFileLineNumber() either throws an exception or always returns zero ☹ • Exception::StackTrace might contain file names and line numbers (?) but it’s just a string Manually var trace = new StackTrace(exception); StackFrame[] frames = trace.GetFrames();