Flutter アプリに Firebase Crashlytics を導入し、crash reportも送れてはいるが、Firebase Console で dSYMファイルが見つかりません と表示される場合があります。
この状態では crash report の内容を確認できません。
今回の原因は、firebase.json の uploadDebugSymbols が false になっていたことでした。見落としやすかったので、切り分けの流れごと残します。
今回の確認時点では、flutterfire --version は 1.3.1 でした。
Xcode は 16.3 (16E140) です。
バージョン差分によって、挙動は変わる可能性があります。
結論
flutterfire upload-crashlytics-symbols の Build Phase が存在していても、自動アップロードは必ずしも走りません。
firebase.json の iOS 設定で uploadDebugSymbols が false だと、dSYM の自動アップロードは実行されません。
そのため、symbolicate に必要な dSYM は Firebase 側に登録されず、dSYMファイルが見つかりません と表示されます。
起きていた症状
今回の症状はかなり紛らわしいものでした。
Crashlytics 自体は動いていて、テスト用に送った error や crash は Firebase に届いていました。
一方で、Firebase Console では dSYMファイルが見つかりません と表示され、report の中身を読めない状態でした。
つまり、Crashlytics の導入自体は成功していますが、symbol だけ不足していました。
なお、Firebase の公式ドキュメントでは、Missing dSYM は複数の原因で表示されます。
たとえば、dSYM のアップロード失敗だけでなく、Xcode が dSYM を生成していない場合にも出ます。
最初からアップロード失敗と決め打ちしないほうが切り分けやすいです。
最初に疑ったこと
最初に疑ったのは、iOS の Build Phase に追加される flutterfire upload-crashlytics-symbols が正しく実行されていないことでした。
Xcode の Build Phases には Crashlytics の symbol upload 用 script が入っていおり、build log にも、次のような PhaseScriptExecution が出ていました。
PhaseScriptExecution FlutterFire: "flutterfire upload-crashlytics-symbols" ...
この時点で分かるのは、Build Phase 自体は呼ばれていることだけです。
ただし、これだけでは dSYM が実際にアップロードされたとは言い切れません。
dSYM が本当に存在するか確認した
次に、build log に出ている DWARF_DSYM_FOLDER_PATH と DWARF_DSYM_FILE_NAME を手がかりに、実際に dSYM が生成されているかを確認しました。
Release build の出力先には Runner.app.dSYM が存在していました。
UUID は dwarfdump --uuid で確認できます。
dwarfdump --uuid /path/to/Runner.app.dSYM
手元では、この UUID が Firebase Console の Missing dSYM に表示されていた UUID と一致していました。
この時点で、少なくとも次のことがわかります。
- ローカルでは dSYM が正しく生成されている
- Firebase が探している UUID と、ローカルの dSYM の UUID も一致している
つまり、問題は dSYM が作られていないことではなく、生成済みの dSYM が Firebase に登録されていないことでした。
build phase は動いているのに、なぜアップロードされないのか
ここで初めて、Build Phase は起動していても、その中で実際の upload がスキップされている可能性を考えました。
flutterfire upload-crashlytics-symbols は firebase.json の設定を参照して挙動を変えます。
自分のプロジェクトでは、iOS の設定が次のようになっていました。
{
"flutter": {
"platforms": {
"ios": {
"default": {
"projectId": "...",
"appId": "...",
"uploadDebugSymbols": false,
"fileOutput": "ios/Runner/GoogleService-Info.plist"
}
}
}
}
}
この uploadDebugSymbols が false だと、Build Phase から呼ばれても、実際の dSYM アップロード処理は実行されずに終了します。
一番最初に flutterfire configure を実行した際に uploadDebugSymbols が false となっており、その設定が継承されていたというのが今回の原因でした。
ハマりやすかった理由
この問題が分かりづらいのは、見た目では「設定できていそう」に見えるからです。
Crashlytics 自体は動いています。
Build Phase も実行されています。
ローカルには dSYM もあります。
それでも Firebase には symbol が登録されません。
この状態だと、Build Phase の path や dSYM の生成設定を疑いたくなります。
ただ、実際には firebase.json の 1 行だけで自動 upload が止まっていました。
script は実行されているように見えるのに、内部では upload がスキップされます。そこがこの問題の厄介なところでした。
対処方法
対応はシンプルで、firebase.json の uploadDebugSymbols を true に変更するだけでした。
{
"flutter": {
"platforms": {
"ios": {
"default": {
"projectId": "...",
"appId": "...",
"uploadDebugSymbols": true,
"fileOutput": "ios/Runner/GoogleService-Info.plist"
}
}
}
}
}
この修正後に Release build を実行すると、手元では次のようなログが出るようになりました。
Running upload-symbols in Build Phase mode
Validating build environment for Crashlytics...
Validation succeeded. Symbol uploading will proceed in the background.
このログが出ていれば、少なくとも upload-symbols の呼び出しまで処理が進んでいると判断しやすいです。
その後、少し時間をおいて Firebase Console を確認すると、Missing dSYM の表示は解消されました。
もし自動アップロードが引き続き失敗する場合は、Firebase 公式ドキュメントの手順どおり、upload-symbols を使った手動アップロードも検討するとよいです。
同じ症状でハマったときの確認順
同じ表示で詰まったときは、次の順で確認すると切り分けしやすいです。
まず、iOS の Build Phases に flutterfire upload-crashlytics-symbols が入っているか確認します。
次に、Release build で dSYM が実際に生成されているか確認します。
そのあとで、dwarfdump --uuid の結果と Firebase Console の UUID が一致するか確認します。
ここまで問題なければ、firebase.json の uploadDebugSymbols を確認します。
特に、Build Phase は実行されているのにアップロードされない場合は、この設定を先に疑う価値が高いです。
まとめ
今回の原因は、firebase.json の uploadDebugSymbols が false になっていたことでした。
Build Phase が動いていても、flutterfire upload-crashlytics-symbols は firebase.json を読みます。
その結果、内部で symbol upload をスキップすることがあります。
そのため、Crashlytics 導入時に dSYMファイルが見つかりません と表示された場合は、Build Phase や dSYM の有無を確認するだけでは足りません。
firebase.json も合わせて確認したほうが早いです。
Crash は届いているのに中身が読めない状態でハマったときの、切り分けの一例として参考になればうれしいです。