Customize your Firebase Crashlytics crash reports

ios
android

Firebase Crashlytics can work with very little setup on your part—as soon as you add the SDK, Crashlytics gets to work sending crash reports to the Firebase console.

For more fine-grained control of your crash reports, customize your Crashlytics SDK configuration. For example, you can enable opt-in reporting for privacy-minded users, add logs to track down pesky bugs, and more.

Enable opt-in reporting

By default, Firebase Crashlytics automatically collects crash reports for all your app's users. To give users more control over the data they send, you can enable opt-in reporting instead.

To do that, you have to disable automatic collection and initialize Crashlytics only for opt-in users.

ios
  1. Turn off automatic collection with a new key to your Info.plist file:
    • Key: firebase_crashlytics_collection_enabled
    • Value: false
  2. Enable collection for selected users by initializing Crashlytics at runtime:
    Objective-C
    [Fabric with:@[[Crashlytics class]]];
    Swift
    Fabric.with([Crashlytics.self])
android
  1. Turn off automatic collection with a meta-data tag in your AndroidManifest.xml file:
    <meta-data android:name="firebase_crashlytics_collection_enabled" android:value="false" />
  2. Enable collection for selected users by initializing Crashlytics from one of your app's activities:
    Fabric.with(this, new Crashlytics());
Note: You can't stop Crashlytics reporting once you've initialized it in an app session. To opt-out of reporting after you've initialized Crashlytics, users have to restart your app.

Add custom logs

To give yourself more context for the events leading up to a crash, you can add custom Crashlytics logs to your app. Crashlytics associates the logs with your crash data and makes them visible in the Firebase console.

ios
Objective-C

In Objective-C, use CLS_LOG to help pinpoint issues. It automatically includes information about the Objective-C class, method, and line number associated with the log.

CLS_LOG behavior changes depending on whether it's logging for a debug or a release build:

  • Debug builds: CLS_LOG passes through to NSLog so you can see the output in Xcode and on the device or simulator.
  • Release builds: To improve performance, CLS_LOG silences all other output and does not pass through NSLog.

Use CLS_LOG(format, ...) to log with CLS_LOG. For example:

CLS_LOG(@"Higgs-Boson detected! Bailing out... %@", attributesDict);

Browse the Crashlytics/Crashlytics.h header file for more details on how to use CLS_LOG.

Swift

In Swift, use CLSLogv or CLSNSLogv to help pinpoint issues.

There are two things to keep in mind when logging with CLSLogv and CLSNSLogv:

  • Your format argument must be a compile-time constant string. This is enforced by the compiler in Objective-C, but that protection is currently lost in bridging to Swift.
  • Store log values in an array, and call getVaList on that array to retrieve them.

For example:

func write(string: String) {
    CLSLogv("%@", getVaList([string]))
}
Swift string interpolation will not result in a compile-time constant string. Just like with printf and NSLog, using a non-constant string with CLSLog can result in a crash.

Advanced

For more control, you can directly leverage CLSLog(format, ...) and CLSNSLog(format, ...). The latter passes through to NSLog so you can see the output in Xcode or on the device or simulator. CLSLog(format, ...) and CLSNSLog(format, ...) are thread safe. CLSLog is meant for logging information important to solving crashes. It should not be used to track in-app events.

android

On Android, use Crashlytics.log to help pinpoint issues.

Crashlytics.log can write logs to a crash report and with Log.println(), or just to the next crash report:

  • Crash report and Log.println:
    Crashlytics.log(int priority, String tag, String msg);
  • Crash report only:
    Crashlytics.log(msg);
To avoid slowing down your app, Crashlytics limits logs to 64kB. Crashlytics deletes older log entries if a session's logs go over that limit.

Add custom keys

Custom keys help you get the specific state of your app leading up to a crash. You can associate arbitrary key/value pairs with your crash reports, and see them in the Firebase console.

ios

Start with [CrashlyticsKit setObjectValue:forKey:] or one of the related methods:

- (void)setObjectValue:(id)value forKey:(NSString *)key;

// calls -description on value, perfect for NSStrings!
- (void)setIntValue:(int)value forKey:(NSString *)key;

- (void)setBoolValue:(BOOL)value forKey:(NSString *)key;

- (void)setFloatValue:(float)value forKey:(NSString *)key;

Sometimes you need to change the existing key value. Call the same key, but replace the value, for example:

Objective-C
[CrashlyticsKit setIntValue:3 forKey:@"current_level"];
[CrashlyticsKit setObjectValue:@"logged_in" forKey:@"last_UI_action"];
Swift
Crashlytics.sharedInstance().setIntValue(42, forKey: "MeaningOfLife")
Crashlytics.sharedInstance().setObjectValue("Test value", forKey: "last_UI_action")
android

There are five methods to set keys. Each handles a different data type:

Crashlytics.setString(key, value);

Crashlytics.setBool(String key, boolean value);

Crashlytics.setDouble(String key, double value);

Crashlytics.setFloat(String key, float value);

Crashlytics.setInt(String key, int value);

Re-setting a key updates its value, for example:

Crashlytics.setInt("current_level", 3);
Crashlytics.setString("last_UI_action", "logged_in");
Crashlytics supports a maximum of 64 key/value pairs. Once you reach this threshold, additional values are not saved. Each key/value pair can be up to 1 kB in size.

Set user IDs

To diagnose an issue, it’s often helpful to know which of your users experienced a given crash. Crashlytics includes a way to anonymously identify users in your crash reports.

ios

To add user IDs to your reports, assign each user a unique identifier in the form of an ID number, token, or hashed value:

Objective-C
[CrashlyticsKit setUserIdentifier:@"123456789"];
Swift
Crashlytics.sharedInstance().setUserIdentifier("123456789")

If you ever need to clear a user identifier after you set it, reset the value to a blank string.

android

To add user IDs to your reports, assign each user a unique identifier in the form of an ID number, token, or hashed value:

void Crashlytics.setUserIdentifier(String identifier);

If you need to clear a user identifier after you set it, reset the value to a blank string.

Log non-fatal exceptions

In addition to automatically reporting your app’s crashes, Crashlytics lets you log non-fatal exceptions.

ios

On iOS, you do that by recording NSError objects, which Crashlytics reports and groups much like crashes:

Objective-C
[CrashlyticsKit recordError:error];
Swift
Crashlytics.sharedInstance().recordError(error)

When using the recordError method, it's important to understand the NSError structure and how Crashlytics uses the data to group crashes. Incorrect usage of the recordError method can cause unpredicatable behavior and may require Crashlytics to limit reporting of logged errors for your app.

An NSError object has three arguments: domain: String, code: Int, and userInfo: [AnyHashable : Any]? = nil

Unlike fatal crashes, which are grouped via stack trace analysis, logged errors are grouped by the NSError domain and code. This is an important distinction between fatal crashes and logged errors. For example, logging an error such as:

NSDictionary *userInfo = @{
    NSLocalizedDescriptionKey: NSLocalizedString(@"The request failed.", nil),
    NSLocalizedFailureReasonErrorKey: NSLocalizedString(@"The response returned a 404.", nil),
    NSLocalizedRecoverySuggestionErrorKey: NSLocalizedString(@"Does this page exist?", nil),
    ProductID: @"123456";
    UserID: @"Jane Smith"
};

NSError *error = [NSError domain:NSSomeErrorDomain
                          code:-1001
                          userInfo:userInfo];

Creates a new issue that is grouped by NSSomeErrorDomain and -1001. Additional logged errors that use the same domain and code values will be grouped under this issue.

Avoid using unique values, such as user ID, product ID, and timestamps in the domain and code fields. Using unique values in these fields causes a high cardinality of issues and may result in Crashlytics needing to limit the reporting of logged errors in your app. Unique values should instead be added to the userInfo Dictionary object.

Data contained within the userInfo object are converted to key-value pairs and displayed in the keys/logs section within an individual issue.

Crashlytics only stores the most recent 8 exceptions in a given app session. If your app throws more than 8 exceptions in a session, older exceptions are lost.

Logs and custom keys

Just like crash reports, you can embed logs and custom keys to add context to the NSError. However, there is a difference in what logs are attached to crashes versus logged errors. When a crash occurs and the app is relaunched, the logs Crashlytics retrieves from disk are those that were written right up to the time of the crash. When you log an NSError, the app does not immediately terminate. Because Crashlytics only sends the logged error report on the next app launch, and because Crashlytics must limit the amount of space allocated for logs on disk, it is possible to log enough after an NSError is recorded so that all relevant logs are rotated out by the time Crashlytics sends the report from the device. Keep this balance in mind when logging NSErrors and using CLSLog and custom keys in your app.

Performance considerations

Keep in mind that logging an NSError can be fairly expensive. At the time you make the call, Crashlytics captures the current thread’s call stack using a process called stack unwinding. This process can be CPU and I/O intensive, particularly on architectures that support DWARF unwinding (arm64 and x86). After the unwind is complete, the information is written to disk synchronously. This prevents data loss if the next line were to crash.

While it is safe to call this API on a background thread, remember that dispatching this call to another queue will lose the context of the current stack trace.

What about NSExceptions?

Crashlytics doesn’t offer a facility for logging/recording NSException instances directly. Generally speaking, the Cocoa and Cocoa Touch APIs are not exception-safe. That means the use of @catch can have very serious unintended side-effects in your process, even when used with extreme care. You should never use @catch statements in your code. Please refer to Apple’s documentation on the topic.

android

On Android, that means you can log caught exceptions in your app’s catch blocks:

try {
    methodThatThrows();
} catch (Exception e) {
    Crashlytics.logException(e);
    // handle your exception here
}

All logged exceptions appear as non-fatal issues in the Firebase console. The issue summary contains all the state information you normally get from crashes, along with breakdowns by Android version and hardware device.

Crashlytics processes exceptions on a dedicated background thread, so the performance impact to your app is minimal. To reduce your users’ network traffic, Crashlytics batches logged exceptions together and sends them the next time the app launches.

Crashlytics only stores the most recent 8 exceptions in a given app session. If your app throws more than 8 exceptions in a session, older exceptions are lost.

Send feedback about...

Need help? Visit our support page.