New Relic の React Native エージェントは、アプリ内で発生した JavaScript エラーや未処理の Promise Rejection を MobileJSError イベントとして記録します。このイベントを用いてエラーの発生状況を分析することができますが、リリースビルドの JavaScript はバンドルされるため、スタックトレースには index.android.bundle:1:406876 のようなバンドル内の位置しか表示されません。そのため、エラーの発生箇所を正確に把握するには、ビルド時に生成されたソースマップをアップロードしてシンボリケートを行う必要があります。
本記事では、Expo を使わない素の React Native プロジェクトをゼロから作成し、iOS と Android それぞれでソースマップの自動アップロードとシンボリケートの動作を確認していきます。
注意点として、ソースマップのアップロードと MobileJSError イベントの利用は、React Native エージェント 1.9.0 以降となります。
検証環境
今回の検証は以下の環境で行いました。
- React Native 0.87.1(Community CLI で作成、Hermes 有効(デフォルト))
- newrelic-react-native-agent 1.9.1(iOS エージェント 7.7.7 / Android エージェント 7.8.2)
- New Relic Android Gradle プラグイン 7.8.2
- iOS シミュレータ / Android エミュレータ
事前準備
New Relic 側で、以下の 2 つを用意します。どちらも同じアカウントのものを使用してください。
-
各 OS 用のアプリケーショントークン
新規でトークンを発行する場合は、ガイド付きインストールにより、画面の指示に従って進めるとスムーズです。 -
User API キー(
NRAK-から始まるキー)
サンプルアプリの作成
まず、React Native のプロジェクトを作成し、エージェントをインストールします。
npx @react-native-community/cli@latest init NRSourceMapTest --version 0.87.1
cd NRSourceMapTest
npm install newrelic-react-native-agent@1.9.1
エージェントの設定
次に、index.js でエージェントを起動します。New Relic のガイド付きインストールで React Native を選択すると表示されるコードを、テンプレートの index.js に組み込みます。
import NewRelic from 'newrelic-react-native-agent';
import {AppRegistry, Platform} from 'react-native';
import App from './App';
import {name as appName} from './app.json';
let appToken;
if (Platform.OS === 'ios') {
appToken = '<YOUR_IOS_TOKEN>';
} else {
appToken = '<YOUR_ANDROID_TOKEN>';
}
const agentConfiguration = {
// ガイド付きインストールで表示される設定のまま
};
NewRelic.startAgent(appToken, agentConfiguration);
NewRelic.setJSAppVersion('1.0');
AppRegistry.registerComponent(appName, () => App);
エラーを発生させる画面の作成
次に、テンプレートの App.tsx を以下の内容に置き換え、ボタンを押すとエラーを記録する画面を作成します。
シンボリケートが成功していれば、スタックトレースに App.tsx と、エラーを投げた行の行番号が表示されます。
import React from 'react';
import {Button, StyleSheet, View} from 'react-native';
import NewRelic from 'newrelic-react-native-agent';
const onPress = () => {
try {
throw new Error('sourcemap check');
} catch (e) {
NewRelic.recordError(e as Error);
}
};
function App(): React.JSX.Element {
return (
<View style={styles.container}>
<Button title="Send JS error" onPress={onPress} />
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
});
export default App;
ソースマップの自動アップロード設定
ソースマップのアップロード方法は、iOS と Android で異なります。
iOS
iOS では、エージェントに同梱されている upload-react-native-sourcemap スクリプトを Xcode のビルドフェーズから実行し、ソースマップをアップロードします。
まず、エージェントの iOS 向けネイティブモジュールをプロジェクトに組み込むため、pod install を実行します。
cd ios && pod install && cd ..
次に、スクリプト一式を ios フォルダにコピーします。
cp -R node_modules/newrelic-react-native-agent/plugin/dsym-upload-tools ios/
続いて、Xcode で ios/NRSourceMapTest.xcworkspace を開き、アプリのターゲットの Build Phases を表示します。
既存の Bundle React Native code and images フェーズのスクリプト先頭に、以下の 1 行を追加します。
iOS ではデフォルトでソースマップが出力されないため、SOURCEMAP_FILE を明示的に設定する必要があります。
export SOURCEMAP_FILE="$DERIVED_FILE_DIR/main.jsbundle.map"
さらに、New Run Script Phase を追加し、Bundle React Native code and images の後ろに配置します。
1 行目の NEWRELIC_SOURCEMAP_ALLOW_SIMULATOR=true は、シミュレータ向けのビルドでもアップロードを行うための設定です。実機でのみ確認する場合は不要です。
export NEWRELIC_SOURCEMAP_ALLOW_SIMULATOR=true
ARTIFACT_DIR="${BUILD_DIR%Build/*}"
SCRIPT=`/usr/bin/find "${SRCROOT}" "${ARTIFACT_DIR}" -type f -name upload-react-native-sourcemap | head -n 1`
/bin/sh "${SCRIPT}" "<USER_API_KEY>" "<IOS_APP_TOKEN>"
Android
Android では、New Relic Android エージェントの Gradle プラグインが、ビルド時にソースマップをアップロードします。
まず、android/build.gradle の buildscript の dependencies に、Gradle プラグインを追加します。バージョンは、React Native エージェントに含まれる Android エージェントのバージョン(今回は 7.8.2)に合わせます。
buildscript {
dependencies {
// 既存の classpath はそのまま
classpath("com.newrelic.agent.android:agent-gradle-plugin:7.8.2")
}
}
次に、android/app/build.gradle でプラグインを適用します。既存の apply plugin の行の後に追加します。
apply plugin: "newrelic"
最後に、android/app/newrelic.properties を作成し、User API キーと Android 用のアプリケーショントークンを設定します。
com.newrelic.api_key=<USER_API_KEY>
com.newrelic.application_token=<ANDROID_APP_TOKEN>
なお、本手順は検証のため User API キーをプロジェクトファイルに直接記述していますが、実際に運用される際はそのまま Git リポジトリにコミットしないよう注意してください。
ビルド
それでは、ビルドしてみましょう。iOS、Android ともにソースマップがアップロードされるのはリリースビルドのみのため、--mode Release を指定して実行します。
npx react-native run-ios --mode Release
npx react-native run-android --mode Release
New Relic での確認
アプリを起動してボタンを押し、以下の NRQL でイベントが届いているかを確認します。イベントは一定間隔でまとめて送信されるため、反映まで数分かかることがあります。
SELECT * FROM MobileJSError SINCE 30 minutes ago
JavaScript errorsの詳細画面から、スタックトレースがシンボリケートされていることも確認できます。
アップロード済みのソースマップは、API で一覧表示・削除できます。詳しくは「List and delete React Native source maps」のドキュメントを参照してください。
まとめ
本記事では、素の React Native プロジェクトを使って、iOS と Android で JavaScript のソースマップを自動アップロードし、MobileJSError のスタックトレースをシンボリケートする流れを紹介いたしました。
CodePush などの OTA で JavaScript だけを更新する場合は、cURL やスクリプトで手動アップロードする方法も用意されています。詳しくは以下のドキュメントを参照のうえ、ぜひ試してみてください。
本ブログに掲載されている見解は著者に所属するものであり、必ずしも New Relic 株式会社の公式見解であるわけではありません。また、本ブログには、外部サイトにアクセスするリンクが含まれる場合があります。それらリンク先の内容について、New Relic がいかなる保証も提供することはありません。