Fullstory Integration with Crashlytics Technical Guide - Mobile

Available for the following Plan types:

Fullstory Enterprise*

Fullstory Business*

Fullstory Free

*with the following add-on:

Fullstory for Mobile Apps

Available to the following User roles:

Admin

Architect

Standard

 

Summary

Using Fullstory and Crashlytics together provides unparalleled insights into app issues so you can debug issues and iterate faster. When a crash is reported to Crashlytics, an engineer can use the session replay to see visually what the user did leading up to the moment of the crash.

 

The Goal

This documentation aims to provide common use cases for integrating Fullstory and Crashlytics and technical guidance on how to do so for iOS and Android. Feel free to contact Fullstory Support for more information, or share your success story with us!

 

Use Cases

Add a Fullstory session replay link to a Crashlytics report

With Fullstory for mobile apps, you can retrieve a link to the beginning of the session replay to be attached to the Crashlytics report. When viewing an issue in Crashlytics, navigate to the session replay link and watch the steps to reproduce without ever having to coordinate with end-users to get their feedback about what happened. This enables engineers to quickly and accurately reproduce the crash and fix the issue.

 

Capture user journeys along with app state

In addition to sharing a replay link, you can also share point-in-time data via logging. At any point in your application, you may wish to log certain messages to Crashlytics and Fullstory, so you can easily navigate back and forth between Crashlytics and Fullstory to find valuable information about specific crashes and errors.

By logging the app state as your users journey through your app, you can easily trace back the cause of an exception as it relates to user interaction and also understand the app state when the crash occurred.

 

Identify users who experienced a crash

When diagnosing and working with an issue, it’s often helpful to zoom-in to a certain set of users to understand how they are affected. Both Fullstory and Crashlytics allow you to identify the user by setting a userID. Navigate between Fullstory and Crashlytics for a user and have side by side data for the chosen user.

 

Prioritize non-fatal Errors

Non-fatal errors can still have a negative impact on user experience. While trying to understand your users’ overall experience, it may be valuable to investigate how non-fatal errors are getting in their way. Certain non-fatal errors may be caught and handled but may still stop your users from being able to use key functionalities and thus cause frustration and churn. Use custom events in Fullstory to surface any errors that may have negative impact. All custom events are indexed, which means you can search for non-fatal errors and prioritize them accordingly.

 

Implementation

Add a Fullstory session replay link to a crash report

  1. Set up your Crashlytics SDK and get crash reports via Crashlytics
  2. Set up Fullstory data capture for Android or iOS
  3. Implement your app to respond when Fullstory is ready:
    Android

    Extend your Activity to implement FSOnReadyListener and override the onReady method. onReady is called when Fullstory is initialized and has FSSessionData ready. So you can retrieve the session URL using FS.getCurrentSessionURL():

    @Override
    public void onReady(FSSessionData sessionData) {
        Log.d("TAG", "FSSessionURL " + sessionData.getCurrentSessionURL());
        ...
    }
    iOS

    Using the Fullstory delegate method called fullstoryDidStartSession(_ sessionURL: String), this will provide you with the session URL (no need to override the method).

    func fullstoryDidStartSession(_ sessionUrl: String) {
        print("FSSessionURL: ",sessionUrl)
    ...
    }
    React Native
     
    ES6 Promise


    FullStory.onReady().then((result) => {
      const replayStartUrl = result.replayStartUrl
      print("FSSessionURL: ", replayStartUrl)
    })


    async/await


    const result = await FullStory.onReady()
    const replayStartUrl = result.replayStartUrl
    print("FSSessionURL: ", replayStartUrl)
  4. After retrieving the Fullstory session URL, use the following snippet to attach the sessionURL as a key-value pair to a Crashlytics report:
    Android
    FirebaseCrashlytics instance = FirebaseCrashlytics.getInstance();
    String sessionURL = sessionData.getCurrentSessionURL();
    instance.setCustomKey("FSsessionURL", sessionURL);
    iOS
    Crashlytics.crashlytics()
    .setCustomValue(sessionUrl, forKey: "FSURLSession")
    React Native
    ES6 Promise

    crashlytics().setAttribute(’FSsessionURL’, result.replayStartUrl).then(() ⇒ {})

    async / await

    await crashlytics().setAttribute('FSsessionURL', result.replayStartUrl)
  5. When a crash occurs, you will see an issue under Crashlytics in your Firebase console. Note that when an app crashes, the session will terminate, meaning that the end of this session is where the crash had occurred. In each session under the issue, you can go to the Keys tab, and see the Fullstory session URL:
    Keys.png

Capture user journeys along with app state

  1. You can choose to send the session URL along with custom log messages to Crashlytics log, and also mark them in Fullstory playback using FS.event or FS.log:
    Android
    String fsCustomEventTag = "CrashlyticLog";
    String sessionURL = sessionData.getCurrentSessionURL();
    String logMsg = "Higgs-Boson detected! Bailing out";
    Map<String, String> eventVars = new HashMap<>();
    eventVars.put("logMessage", logMsg);

    // send FS event or log to be shown in the playback
    FS.event(fsCustomEventTag,eventVars);
    FS.log(FS.LogLevel.INFO, logMsg);

    // Crashlytics:
    instance.log(logMsg);
    instance.log("Current FSSessionURL "+ sessionURL);
    instance.log("Look for FS event in playback with tag: " + fsCustomEventTag);
    iOS
    let fsSessionURL = FS.currentSessionURL
    let fsCustomEventTag: String = "CrashlyticLog"
    let logMsg = "Higgs-Boson detected! Bailing out"
    var eventVars = [String: String]()
    eventVars["logMessage"] = logMsg

    // send FS event or log to be shown in the playback
    FS.event(fsCustomEventTag,properties: eventVars)
    FS.log(with: FSLOG_INFO, message: logMsg)

    // Crashlytics:
    Crashlytics.crashlytics().log(logMsg)
    Crashlytics.crashlytics()
       .log("Current FSSessionURL \(String(describing: fsSessionURL))");
    Crashlytics.crashlytics()
       .log("Look for FS event in playback with tag: \(fsCustomEventTag)");
    React Native
    ES6 Promise

    FullStory.getCurrentSessionURL().then((fsSessionURL ⇒ {
    let fsCustomEventTag = "CrashlyticLog"
    let logMsg = "Higgs-Boson detected! Bailing out"
    var eventVars = {}
    eventVars["logMessage"] = logMsg

    // send FS even or log to be shown in the playback
    FullStory.event(fsCustomEventTag, eventVars)
    FullStory.log(FullStory.LogLevel.Log, logMsg)

    // Crashlytics:
    crashlytics().log(logMsg).then(() ⇒ {})
    crashlytics().log(`Current FSSessionURL ${fsSessionURL}`).then(() ⇒ {})
    crashlytics().log(
    `Look for FS event in playback with tag: ${fsCustomEventTag}`,
    ).then(() ⇒ {})
    })

    async / await

    let fsSessionURL = await FullStory.getCurrentSessionURL()
    let fsCustomEventTag = "CrashlyticLog"
    let logMsg = "Higgs-Boson detected! Bailing out"
    var eventVars = {}
    eventVars["logMessage"] = logMsg
    // send FS even or log to be shown in the playback
    FullStory.event(fsCustomEventTag, eventVars)
    FullStory.log(FullStory.LogLevel.Log, logMsg)

    // Crashlytics:
    await crashlytics().log(logMsg)
    await crashlytics().log(`Current FSSessionURL ${fsSessionURL}`)
    await crashlytics().log(
    `Look for FS event in playback with tag: ${fsCustomEventTag}`,
    )
  2. View your custom logs for an issue under the Logs tab:
    Logs.png

  3. Then you can navigate to the Fullstory session URL. You will see custom events on the right hand pane during replay, and custom logging will show up under dev tools to allow you to identify the point of time when the logs were sent to Crashlytics. These app state breadcrumbs help you better understand the exception.
  4. See the logs and event in Fullstory playback like below:
    Screen Shot 2022-07-08 at 9.23.26 AM.png

    Notice the blocked-out areas of the app visible during replay. These are our default privacy controls in action. To learn more about how we’ve designed our privacy controls for mobile apps, check out this article on our engineering blog: https://bionic.fullstory.com/private-by-default-mobile-analytics/

Set user identifiers across Fullstory and Crashlytics

  1. When you are able to identify the user, you can send the user identity to Crashlytics
  2. At the same time, send the same user ID to Fullstory using FS.identify, with optional userVars, to help you identify users that experience a crash.

    Android
    // when user is identified(logged in), create a Map for the userVars
    Map<String, String> userVars = new HashMap<>();
    userVars.put("userID", "testuser3");
    userVars.put("displayName","crashlytics user3");
    userVars.put("email","testeamil@gmail.com");

    // send user identity and userVars to FS
    FS.identify(userVar.get("userID"),userVar);

    // send user identity to Crashlytics
    instance.setUserId(userVar.get("userID"));
    iOS
    // when user is identified(logged in), create a Map for the userVars
    var userVars = [String: String]()
    userVars["userID"] = "testuser3"
    userVars["displayName"] = "crashlytics user3"
    userVars["email"] = "testeamil@gmail.com"

    // send user identity and userVars to FS
    FS.identify("testuser3", userVars: userVars);

    // send user identity to Crashlytics
    Crashlytics.crashlytics().setUserID("testuser3")
    React Native ES6 Promise
    // when user is identified(logged in), create a Map for the userVars
    var userVars = {}
    userVars["userID"] = "testuser3"
    userVars["displayName"] = "crashlytics user3"
    userVars["email"] = "testemail@gmail.com"

    // send user identity and userVars to FS
    FS.identify("testuser3", userVars);
    // send user identity to Crashlytics
    crashlytics().setUserID("testuser3").then(() => {})

    async / await
    // when user is identified(logged in), create a Map for the userVars
    var userVars = {}
    userVars["userID"] = "testuser3"
    userVars["displayName"] = "crashlytics user3"
    userVars["email"] = "testemail@gmail.co"

    // send user identity and userVars to FS
    FS.identify("testuser3", userVars);
    // send user identity to Crashlytics
    await crashlytics().setUserID("testuser3")
  3. By doing this you are now able to view and identify issues in Crashlytics by how many users are affected:
    Crashusers.png
  4. User ID is also shown in the Data tab under an issue:
    DataTab.png
  5. Search for a certain user to investigate any user that is affected by crashes (Note that Fullstory does not capture crash events for iOS as of right now, but you can still see all the sessions from a certain user without adding the crashed event filter):
    Screen Shot 2022-06-30 at 2.43.28 PM.png
  6. Optionally, build your own deep link into Fullstory to be passed into Crashlytics, or vice versa:
    <FS_USER_LINK> = https://app.fullstory.com/ui/<ORG_ID>/segments/everyone/people:search:(:((UserAppKey:==:<USER_ID>)):():():():)/0


    Android only:

    <FS_USER_CRASH_LINK> = https://app.fullstory.com/ui/<ORG_ID>/segments/everyone/people:search:(:((UserAppKey:==:<USER_ID>)):():(((EventType:==:%22crashed%22))):():)/0


    Get a list of crashes for specific users in Crashlytics by searching for a user ID, getting the link from your browser, something like this:

    <CRASHLYTICS_LINK> = https://console.firebase.google.com/u/0/project/<PROJECT_NAME>/crashlytics/app/<PLATFORM:APP_ID>/search?time=last-seven-days&type=crash&q=<USER_ID>

     

  7. Pass the deep links into Fullstory or Crashlytics. Note that a different keys should be used for each deep link, if the same key is used, the latest value being passed will override the previous ones:

    Android
    // optionally attach the user specific links between platforms 
    // Hardcoding the query link, use with caution

    Map<String, String> userVars = new HashMap<>();
    // call setUserVars or you can set these vars during identify
    userVars.put("crashlyticsURLAndroidDemoapp",<CRASHLYTICS_LINK>);
    FS.setUserVars(userVar);

    // add FS links to the crash report as custom key
    instance.setCustomKey("FSUserSearchURL",<FS_USER_LINK>);
    instance.setCustomKey("FSUserCrashedSearchURL",<FS_USER_CRASH_LINK>);
    iOS
    var userVars = [String: String]()
    // call setUserVars or you can set these vars during identify
    userVars["crashlyticsURLiOSDemoApp"] = <CRASHLYTICS_LINK>
    FS.setUserVars(userVars);

    // add FS links to the crash report as custom key
    Crashlytics.crashlytics()
       .setCustomValue(<FS_USER_LINK>, forKey: "FSUserSearchURL")
    React Native ES6 Promise
    // call setUserVars or you can set these vars during identify 
    userVars["crashlyticsURLRNDemoApp"] = <CRASHLYTICS_LINK>
    FullStory.setUserVars(userVars)

    // add FS links to the crash report as custom key
    crashlytics()
    .setAttribute('FSUserSearchURL', <FS_USER_LINK>).then(() => {})

    async / await
    // call setUserVars or you can set these vars during identify
    userVars["crashlyticsURLRNDemoApp"] = <CRASHLYTICS_LINK>
    FullStory.setUserVars(userVars)

    // add FS links to the crash report as custom key
    await crashlytics()
    .setAttribute('FSUserSearchURL', <FS_USER_LINK>)
  8. Now you are able to go back and forth when investigating a certain crash like so:
    1. Identify a user in Crashlytics:
      DataTab.png
    2. Get the link to the Fullstory session replay so you can see all the crashes from all users or just the current user. Feel free to build on top of these prebuilt links and create your own search or segment.
      • For Android, retrieve all crashes:
        keys_crash.png
    3. Get the most recent URL to Crashlytic's user search page, so you can go directly to see all the crashes for this user:

Send Fullstory events during non-fatal exception handling

  1. Crashlytics automatically reports fatal crashes, but you can optionally send non-fatal crashes when you catch one
  2. You can also send a log or an event to Fullstory to help you pinpoint the error when it happens during session replay
    Android
    try {
        // your code that throws an Exception here
    }catch (Exception e){
       // record the Exception to Crashlytics
       FirebaseCrashlytics.getInstance().recordException(e);
       // Send the error to FS
       Map<String, String> eventVars = new HashMap<>();
       eventVars.put("errorMessage", e.getMessage());
       FS.event("nonFatalException",eventVars);
       FS.log(FS.LogLevel.ERROR,e.getMessage());
    }
    iOS
    do {
        // your code that throws an Exception here such as:
        throw NSError(domain: "TestDomain", code: 404, userInfo: nil)
    }catch {
    // code to handle your error

    // record the Exception to Crashlytics
       Crashlytics.crashlytics().record(error: error)

    // Send the error to FS via event or log
        var eventVars = [String: String]()
        eventVars["errorMessage"] = error.localizedDescription
        FS.event("nonFatalException",properties: eventVars)
        FS.log(with: FSLOG_ERROR, message: error.localizedDescription)
    }
    React Native ES6 Promise
    new Promise((resolve, reject) => { 
    // your code that throws an Exception here
    }).catch(e => {
    // code to handle your error
    // record the Exception to Crashlytics
    crashlytics()
    .record(e)
    .then(() => {})
    // Send the error to FS via event or log
    var eventVars = {}
    eventVars.errorMessage = e.message
    FullStory.event('nonFatalException', eventVars)
    FullStory.log(FullStory.LogLevel.Error, e.message)
    })

    async / await
    try { 
    // your code that throws an Exception here such as:
    throw new Error('Error message')
    } catch (e) {
    // code to handle your error
    // record the Exception to Crashlytics
    await crashlytics().record(e)
    // Send the error to FS via event or log
    var eventVars = {}
    eventVars.errorMessage = e.message
    FullStory.event('nonFatalException', eventVars)
    FullStory.log(FullStory.LogLevel.Error, e.message)
    }

This will help you pinpoint the non-fatal error that occurred during a user session:

  1. Pinpoint the error occurrence in Fullstory playback
  2. In this case even though the exception is non-fatal and did not crash the app, it prevents your user from clicking on “purchase” button
  3. You can easily build searches and filters to identify the most crucial exceptions in your workflow:
    Screen Shot 2022-06-30 at 2.45.54 PM.png

  4. Once you’ve pinpointed the exception that you would like to prioritize, you can then view the non-fatal exceptions in Crashlytics
    non_fatals_end.png

Was this article helpful?

Got Questions?

Get in touch with a Fullstory rep, ask the community or check out our developer documentation.