Advanced Setup

Build Servers

We support Jenkins and Xcode Bots right out of the box. Onboard your app using Fabric, push to your server and we’ll receive your dSYMs automatically.

Pro tip: Make sure that Xcode.app is closed and the Fabric OS X app isn’t installed on the server.

Move the Framework

If you want to move Fabric.Framework to another directory, you can; just be sure to update the Run Script Build Phase to point to the new directory.

Example: The default Crashlytics run script build phase is:

./Fabric.framework/run <your_api_key_here>

If you move the Crashlytics framework one level down, to a directory called “Vendor”, you’ll need to modify the run script build phase to be:

./Vendor/Fabric.framework/run <your_api_key_here>

You can use our Xcode Build Phase Cheat Sheet to help.

Conditional dSYM Upload

The default set up process ensures that we get your app’s dSYMs on every build (assuming a code change). This is the preferred configuration, so that if a crash comes while you develop and do internal testing, we can show you the exact line of code that caused the crash.

However, if you would like to run Crashlytics only on certain builds, you can add logic to the run script build phase to only run when desired. Try something similar to:

releaseConfig="Release"

if [ "$releaseConfig" = "${CONFIGURATION}" ]; then
    echo "Running Crashlytics"
    ./Fabric.framework/run <your_api_key_here>
fi

Bitcode Enabled Apps

Note

We’ve updated our documentation related to missing dSYMs and Bitcode enabled apps. Visit our Missing dSYMs page for more information.

Multiple Targets

To run Crashlytics with multiple targets, add a Crashlytics Run Script to each target’s Build Phase. The default Crashlytics run script build phase is:

./Fabric.framework/run <your_api_key_here>

Apps with Multiple Environments

Let’s say you have an app called MyAwesomeApp and you want to keep the bundle id the same, even though you have a staging, dev and prod version of your app.

The easiest thing to do is create a separate organization for each version, onboard each version of MyAwesomeApp to the corresponding org (just make sure you grab the new API key for each org) and you’re all set.

Why do it this way?

  • Keeps your crashes separated by org
  • Add different team members to each org to control who has access to prod vs staging, etc.

Multiple exception handlers

Using multiple uncaught exception handlers is not supported by Crashlytics.

Static or Dynamic Frameworks

If you link in other static or dynamic frameworks, you could see crash reports with missing line numbers or file information. This information comes from your dSYM files, so ensure that your dSYM files for the frameworks are placed in the same directory as the app’s dSYM and that they are built before your .app.

The recommended setup for Xcode is the following for static libraries:

"Strip Debug Symbols During Copy" -> NO
"Strip Linked Product" -> NO
"Use Separate Strip" -> NO

This will not impact your app’s size as long as you strip the symbols from your .app.

Identifiers Used

Crashlytics uses a variety of identifiers, such as an RFC-4122 UUID which is used to de-duplicate crashes, to provide our services. The IDFA will only be collected if your app links against AdSupport.framework.

Control Submission Behavior

There are many reasons why a developer may want to control submission behavior. One example is to request permission to submit crash reports out of privacy concerns. You also may want to build a preference into your app instead of asking on every request.Additionally, you may want to add post-crash data.

First, you must set the Crashlytics delegate. When using the delegate, you must initialize Crashlytics in this order:

// correct usage
CrashlyticsKit.delegate = self;
[Fabric with:@[[Crashlytics class]]];

// incorrect usage
[Fabric with:@[Crashlytics class]]];
CrashlyticsKit.delegate = self; // Important delegate calls may have already been missed

Note

Implementing this delegate method disables synchronous report submission, which could be important, if you experience a crash on launch. If your app crashes on launch, on the subsequent launch we will try to send a crash report synchronously blocking the main thread for a short period of time. If you call this delegate, we will not block the main thread. This could result in another crash preventing the original crash from being submitted successfully.

Ensure that the Crashlytics’ delegate is set to your object and then implement the following method:

- (void)crashlyticsDidDetectReportForLastExecution:(CLSReport *)report completionHandler:(void (^)(BOOL submit))completionHandler;

Your delegate will be called back synchronously on the main thread informing you of the previous report, if any existed. You can then use the CLSReport object to inspect the previous report and append information. However, you must call the completion handler, otherwise this report will not be submitted.

Note

The completion handler must be called or the report will not be sent.

It is safe, but not required, to call the completion handler from a background thread. Here’s the complete implementation including the most important part which is calling the completion handler.

- (void)crashlyticsDidDetectReportForLastExecution:(CLSReport *)report completionHandler:(void (^)(BOOL))completionHandler {
    // Use this opportunity to take synchronous action on a crash. See Crashlytics.h for
    // details and implications.

    // Maybe consult NSUserDefaults or show a UI prompt.

    // But, make ABSOLUTELY SURE you invoke completionHandler, as the SDK
    // will not submit the report until you do. You can do this from any
    // thread, but that's optional. If you want, you can just call the
    // completionHandler and return.
    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
        completionHandler(YES);
    }];
}

By default the SDK uses background sessions. On iOS 6 or lower, we will wait up to 3 seconds to send a crash report before unblocking the main thread. On iOS 7 and above, the main thread is blocked for an insignificant amount of time.

If you don’t want Crashlytics to use background sessions, set this value to false. Note: background submissions cannot be used for extensions.

- (BOOL)crashlyticsCanUseBackgroundSessions:(Crashlytics *)crashlytics;

Directories Used

Crashlytics writes files required to provide our services to a few locations in your application’s sandbox. In order to allow Crashlytics to properly function, do not remove files that your application did not create from the following directories:

NSTemporaryDirectory() // <app-sandbox>/tmp
NSCachesDirectory // <app-sandbox>/Library/Caches

Note

Other system services, including the networking stack, use NSTemporaryDirectory() too. Deleting files owned by system services can cause unexpected behavior in your application and is not recommended.

Crashlytics Frames Appearing in Crashes

In order to show stack traces for your threads, Crashlytics needs to run some code post-crash. Because this code is executing on one of your app’s threads, Crashlytics always captures information about its own execution as part of this process. You will always see a thread executing the “CLSProcessRecordAllThreads” function. In fact, you’ll see it more than once, due to a compiler optimization called inlining.

../_images/thread-recording.png

Exceptions add an extra bit of complexity. When a Objective-C or C++ exception goes uncaught, Crashlytics records some information about it before the app is allowed to terminate. When this happens, the CLSProcessRecordAllThreads function must be run on the thread that threw the exception. This means that in the case of an exception, the “crashing” thread will always look like it is running Crashlytics code. This is normal, and is just an artifact of how we capture and present the stack traces at the time of exception.