Infinito Nirone 7

白羽の矢を刺すスタイル

在宅勤務で手元にない端末でのアプリのデバッグをするために準備しておくこと

コロナ禍で在宅勤務が一気に広まり、自宅でアプリの開発やリリースをすることが多くなりました。この状況のなかで、アプリの挙動に問題があるなどで動作確認をしようと思うとき、その問題に対処するためのデータを集めたり、問題が発生したときの状況を確認するための手立てを持っておく必要があります。この記事では、手元にない端末で起きる問題のトラブルシューティングをしやすくするために準備しておくとよいことを書き残しておこうと思います。

端末のログを収集する

ある端末でアプリがクラッシュしたときに、そのスタックトレースを記録しておくことはとても重要ですが、そのクラッシュにいたるまでに何があったかをログとして収集しておくことで、クラッシュが起きた状況を把握しやすくなります。

Firebase CrashlyticsDeployGate など、端末からログを収集して Web コンソール上で閲覧できるようにしておくと、なにか問題が起きたときにそれまでのログをいつでもリモートで確認できます。Timber などと組み合わせて、各ログレベルのログを収集できるようにしておくとよいです。

Firebase Crashlytics の場合:

class CrashlyticsTree : Timber.Tree() {
  override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
    when (priority) {
      Log.VERBOSE -> {
        Crashlytics.log(priority, tag, message)
      }
      Log.DEBUG -> {
        Crashlytics.log(priority, tag, message)
      }
      Log.INFO -> {
        Crashlytics.log(priority, tag, message)
      }
      Log.WARN -> {
        Crashlytics.log(priority, tag, message)
      }
      Log.ERROR -> {
        Crashlytics.log(priority, tag, message)
        t?.let {
          Crashlytics.logException(it)
        }
      }
      else -> {
        Crashlytics.log(priority, tag, message)
        t?.let {
          Crashlytics.logException(it)
        }
      }
    }
  }
}

DeployGate の場合:

class DeployGateTree : Timber.Tree() {
  override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
    if (!DeployGate.isInitialized())
      return

    when (priority) {
      Log.VERBOSE -> {
        DeployGate.logVerbose(message)
      }
      Log.DEBUG -> {
        DeployGate.logDebug(message)
      }
      Log.INFO -> {
        DeployGate.logInfo(message)
      }
      Log.WARN -> {
        DeployGate.logWarn("$message $t")
      }
      Log.ERROR -> {
        DeployGate.logError("$message $t")
      }
      else -> {
        DeployGate.logError("wtf: $message $t")
      }
    }
  }
}

カスタムキーの設定

Firebase Crashlytics はロギング以外に Key-Value ペアで表現するカスタムキーを設定できます。これをつかうと、端末のログでは収集しきれないデータを収集できます。主に永続化されている設定値(アプリ内での設定として設けている、ユーザが好きに選べる設定の値)や、バージョン情報以外のビルド時のデータ(BuildConfig に設定している値)をもたせておくと、特定の設定値のユーザのみクラッシュするなど状況を把握するのに役立ちます。

カスタムキーを設定した状態でアプリがクラッシュすると、クラッシュレポートにすべてのカスタムキーが紐付いて保存されます。

Crashlytics.crashlytics().setCustomValue(100, "preference_something_duration_int")

Crashlytics のクラッシュレポートを Slack に通知する

新規クラッシュを検知した場合や、短時間に高頻度でおなじクラッシュが発生した場合など、いくつかの条件で Crashlytics から Slack に通知する設定があります。 リリース後のプロダクションビルドではもちろん、QA 中などリリース前のビルドでも有効にしておくと、テストしている人からのバグレポートの補助的な役割として利用できます。

リモートでログを確認する

リモートで今動いているアプリのログを確認する方法もあります。DeployGate や BUGFENDERShipbook などがこの機能を持っています。この方法であれば、クラッシュ以外にもログを集められます。

リモートで端末を操作する

リモートで端末を操作するための手段もあります。 AWS Device FarmKobiton など幅広いモバイル端末を用意したサービスから、オンプレミスな環境にOpen STFを立ち上げでおくもの、あるいは Samsung Remote Test Lab のようにマニュファクチャラーが用意しているサービスを利用するなどで、ブラウザや専用のアプリケーションからリモートにある端末でアプリを操作できます。

バグ報告を円滑にするためのデバッグ情報を表示する

クラッシュレポートやログ以外にも、端末に関すデータやアプリのビルドに関するデータもデバッグに役立ちます。リリース前のアプリでだれでも簡単にこれらのデータを見られるようデバッグ用の表示を作っておくと、QA やドッグフーディングなど社内でのテスト中に見つかったクラッシュや不具合の報告により多くのデータを含められるようになります。 簡単な例としては JakeWharton/u2020 にあるようなデバッグ用の UI を用意するものですが、Drawer でなくとも、設定画面にデバッグ情報を見る画面を開くメニューを置いておくでもよいです。

このデバッグ情報として入れておくとよいものを次にあげます。

  • git のコミットハッシュ
  • git のブランチ名
  • デバイスのモデル名やマニュファクチャラー名
  • API Level
  • ディスプレイのスペック (解像度や dpi など)

See also

speakerdeck.com