How To: Crash Reporting with React-Native, Sentry and Fastlane

In the past I was a big fan of native development. So most of the apps I created were written in Swift for iOS. Getting info about crashes was always very important for me, so I used Crashlytics / Fabric as crash reporting tools. In the beginning, the Fabric Mac App made it easy to upload the dSYM files. With the start of bitcode fastlane was a big help to download the debugging Symbols from iTunes Connect and upload it to the crash reporting tools.

Having a React-Native app, we want to get crash reports for

  • the JavaScript modules
  • and the native Obective-C / Swift Code

We found out that Sentry can do crash symbolication for both layers, iOS (Cocoa) and JavaScript, and even has official support for React-Native.


Lets start with the JavaScript part

The Sentry documentation is quite good. What you basically have to do is:

  1. Bundle the? app to get main.jsbundle as well as main.jsbundle.map files.
  2. Create an App Release on Sentry for every build. Therefore you use the Sentry API.
  3. Upload the .jsbundle file for that release to Sentry.
  4. Upload the jsbundlemap file for that release to Sentry in order to get correct file names and file numbers in your report.

Therefore my fastlane file looks like this:

version_number = get_version_number
build_number = get_build_number
sentry_release =      "#{version_number} #{build_number}"
sentry_organisation = ENV["SENTRY_ORGANIZATION"]
sentry_app_name = ENV["SENTRY_APP_NAME"]
sentry_token = ENV["SENTRY_TOKEN"]
# 1. Bundle app to get main.jsbundle as well as main.jsbundle.map files.
bundle_command = %(cd #{ENV['PWD']} && \
react-native bundle \
--dev false \
--platform ios \
--entry-file index.ios.js \
--bundle-output main.jsbundle \
--sourcemap-output main.jsbundle.map)

sh(bundle_command)
#2. Create an App Release on Sentry for every build.
create_release_command = %(cd #{ENV['PWD']} && \
curl https://sentry.io/api/0/projects/#{sentry_organisation}/#{sentry_app_name}/releases/ \
-X POST -H 'Authorization: Bearer #{sentry_token}' \
-H 'Content-Type: application/json' \
-d '{"version": "#{sentry_release}"}')
sh(create_release_command)
#3. Upload main.jsbundle to Sentry
upload_jsbundle_command = %(cd #{ENV['PWD']} && \
curl https://sentry.io/api/0/projects/#{sentry_organisation}/#{sentry_app_name}/releases/#{sentry_release}/files/ \
-X POST \
-H 'Authorization: Bearer #{sentry_token}' \
-F file=@main.jsbundle \
-F name="/main.jsbundle")
sh(upload_jsbundle_command)
#4. Upload main.jsbundle.map to Sentry

upload_jsbundlemap_command = %(cd #{ENV['PWD']} && \
curl https://sentry.io/api/0/projects/#{sentry_organisation}/#{sentry_app_name}/releases/#{sentry_release}/files/ \
-X POST \
-H 'Authorization: Bearer #{sentry_token}' \
-F file=@main.jsbundle.map \
-F name="/main.jsbundle.map")
sh(upload_jsbundlemap_command)

In our case, fastlane increments the build number on every run. We use this number to create a Sentry release name: “build-#{new_build_number}”

To initialize Sentry, you have to add a few lines of code to your React-Native application. In our case the index.ios.js file looks like that:

import {
AppRegistry,
} from 'react-native';
import DeviceInfo from 'react-native-device-info';
import Config from 'react-native-config';
import App from './app';
const Raven = require('raven-js');
require('raven-js/plugins/react-native')(Raven);
const sentryDsn = Config.SENTRY_DSN_PUBLIC;
const buildNumber = DeviceInfo.getBuildNumber();
const versionNumber = DeviceInfo.getVersionNumber();
const release = `${versionNumber} ${buildNumber}`;
Raven
.config(sentryDsn, {release})
.install();
AppRegistry.registerComponent('SentryTestApp', () => App);

We use react-native-device-info to get the current Build Number of the Xcode Project and set it as release to Raven.config().

Furthermore, we use react-native-config to get the sentryDsn from .env files in order to be able to have different Sentry accounts for staging and production.

Please note that you have to call Raven.config with the “Sentry Public DSN” for React-Native.


Part two: The native Code

The native part is much more straight forward. The Sentry documentation is very good and there is a fastlane plugin for Sentry. It even explains how to configure fastlane for Sentry if you use bitcode or not.

Since we do not use bitcode for our application yet, we don’t have to download bitcode from iTunes connect but can upload it directly after building the app with gym:

gym
#5. Upload dSYM for native crashes
sentry_upload_dsym(
auth_token: sentry_token,
org_slug: sentry_organisation,
project_slug: sentry_app_name,
)

To get native crash reports we have to add the Sentry Cocoa Plugin to our Xcode Project and initlialize the plugin:

#import <ReactNativeConfig/ReactNativeConfig.h>
@import Sentry;
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Sentry
NSString *sentryDsn = [ReactNativeConfig envFor:@"SENTRY_DSN_PRIVATE"];
[SentryClient setShared:[[SentryClient alloc] initWithDsnString:sentryDsn]];
[[SentryClient shared] startCrashHandler];

Again, we use the react-native-config plugin to read the Sentry DSN from an .env file.

Please note, that you have to use the Sentry Private DSN for the Cocoa SDK.


Now we are set up and running with symbolicated crash reports for Swift/Objective-C and JavaScript!

If you want to to get the newest updates about React-Native in a nicely curated weekly Newsletter, check out my React Native for Breakfast newsletter.

I hope you’ve enjoyed reading my article :-)