Remote Loggingは、モバイルデバイスからリモートサーバーにログを送信し、一元的な分析と監視を行う仕組みです。デバイスにデータを保存するローカルログとは異なり、リモート収集では全ユーザーデバイスからのエラーや異常をリアルタイムで確認できます。Sentry Resource Libraryによると、remote loggingを利用するアプリケーションはリリース後1時間以内に本番バグの92%を発見できるのに対し、クラッシュレポートのみを使用する場合は15%にとどまります。これはモバイル開発チームにとって必須のツールです。Firebase Crashlytics、Sentry、DatadogはiOSとAndroid向けのSDKを提供しています。
主要ポイント
Remote Loggingは、リモートデバイスからログを収集し、分析のために中央サーバーに送信するプロセスです。モバイル開発の文脈では、remote loggingにはクラッシュレポートだけでなく、カスタムイベント、breadcrumbs、パフォーマンスメトリクス、ユーザーシナリオも含まれます。
Remote loggingとcrash reportingの主な違いはプロアクティブ性です。Crash reportingは既に発生したアプリケーションのクラッシュに関するデータのみを収集します。Remote loggingはクラッシュ前の一連のイベント(ユーザーが開いた画面、実行したリクエスト、入力したデータ)を収集します。これにより、ユーザーとの通信なしでエラーシナリオを再現できます。
Appleは.logarchiveを通じてリモートログ収集の組み込みメカニズムを提供していますが、本番アプリケーションではほとんどの場合、サードパーティのサービスが使用されます。Android SDKにはLogcatが含まれており、ADB経由でリモートアクセスできますが、デバッグモードでないエンドユーザーデバイスにはアクセスできません。
Remote loggingのアーキテクチャは3つのコンポーネントで構成されます。デバイス上のクライアントSDK(ログの収集とバッファリング)、データ送信用のトランスポートプロトコル、保存と可視化のためのサーバーです。
| コンポーネント | 役割 | 例 |
|---|---|---|
| クライアントSDK | 収集、バッファリング、バッチング | Firebase SDK, Sentry Cocoa, Timber |
| トランスポート | HTTPS経由のデータ送信 | REST, gRPC, WebSocket |
| サーバー | 保存、インデックス作成、アラート | Sentry, Crashlytics, Datadog |
クライアントSDKはログをRAMにバッファリングし、定期的にバッチでサーバーにフラッシュします。デバイスがオフラインの場合、ログはローカルファイルに保存され、次のネットワーク接続時に送信されます。バッファサイズと送信間隔は設定可能で、標準値は50イベントまたは30秒です。
HTTPS RESTはremote loggingで最も一般的なプロトコルです。SDKはログをJSONにシリアライズし、POSTリクエストでサーバーエンドポイントに送信します。gRPCはバイナリシリアライゼーション(Protocol Buffers)を使用する代替手段で、JSONより30~40%コンパクトで、不安定な接続のモバイルデバイスで高速です。WebSocketはデバッグ時のリアルタイムロギングに使用されますが、消費電力のため本番環境ではほとんど使用されません。
Firebase Crashlyticsは、クラッシュレポートとカスタムログを収集するGoogleの無料サービスです。Firebase SDKに組み込まれており、別途サーバーは不要です。Crashlyticsはクラッシュ時にスタックトレース、デバイス状態、OSバージョン、開かれていた画面を自動的に収集します。
Crashlyticsのカスタムログはlog()メソッドで追加されます。これらは即座にサーバーに送信されるのではなく、リングバッファに保存され、次のクラッシュレポートに添付されます。各ログが個別のイベントとなるSentryとは異なる点です。Crashlyticsのカスタムログの最大容量はクラッシュあたり64KBです。
// Firebase Crashlytics — Androidでのカスタムログ
import com.google.firebase.crashlytics.FirebaseCrashlytics
class CheckoutViewModel {
fun processPayment(amount: Double) {
FirebaseCrashlytics.getInstance()
.log("Payment started: amount=$amount")
try {
process(amount)
} catch (e: Exception) {
FirebaseCrashlytics.getInstance()
.recordException(e)
}
}
}
Firebase CrashlyticsはsetUserIdentifierをサポートしており、クラッシュを特定のユーザーにリンクできます。これにより、バグが広範囲に及ぶのか、1人のユーザーだけに影響するのかを判断できます。setCustomKeyは各レポートに任意のキー(A/Bテストバージョン、地域、料金プラン)を追加します。
Sentryは、クラッシュレポートだけでなく、すべてのカスタムイベント(breadcrumbs)を独立したレコードとして保存するエラー監視プラットフォームです。Crashlyticsとは異なり、Sentryではエラー前のイベントシーケンスを時系列で表示できます。breadcrumbsはクラッシュログから再構築することなくインターフェースに表示されます。
Sentry SDKは、システムイベントのbreadcrumbsを自動的に収集します。UIViewControllerのライフサイクル変更(viewDidLoad、viewWillAppear)、タッチ、ボタン押下、URLSession経由のHTTPリクエストなどです。これらのイベントはすべてカスタムbreadcrumbsとともにエラータイムラインに表示されます。Androidでも同様に、ActivityとFragmentのライフサイクル、onClickイベント、OkHttp経由のネットワークリクエストが収集されます。
iOSとAndroid用のSentry SDKは、タッチ、ナビゲーション、ライフサイクルなどのUIイベントのbreadcrumbsを自動的に収集します。開発者はaddBreadcrumb()でタイプ、カテゴリ、レベルを指定してカスタムbreadcrumbsを追加できます。Sentryは分散トレーシングをサポートしており、ロガーはクライアントサイドのbreadcrumbsをトレースIDでバックエンドリクエストにリンクします。
import Sentry
func trackCartEvent(action: String, itemId: String) {
let crumb = Breadcrumb()
crumb.level = .info
crumb.category = "cart"
crumb.message = "Cart \(action): \(itemId)"
crumb.data = ["action": action, "item_id": itemId]
SentrySDK.addBreadcrumb(crumb)
}
LogcatはAndroidの標準ロギングシステムで、Android Debug Bridge(ADB)を介してアクセスします。Logcatはすべてのシステムメッセージとアプリケーションメッセージをレベル(V、D、I、W、E、F)とタグで分類して収集します。LogcatへのリモートアクセスはUSBまたはWi-Fi経由のADBで機能しますが、デバッグモードのデバイスに限られます。USB接続がないデバイスの本番アプリケーションにはアクセスできません。
Androidの本番環境でのリモートロギングには代替手段が使用されます。Logcat自体はサーバーにログを送信できません。その役割はローカル診断です。ただし、TimberやLogcatLiveなどのラッパーがあり、使い慣れたLog.d / Log.e APIを維持しながらFirebaseやSentryにメッセージを転送します。Timberはアプリケーションコードを変更せずにハンドラを切り替えられます。デバッグツリーはLogcatに書き込み、リリースツリーはバッチングと圧縮を行ってサーバーに送信します。
バッチング(Batching)は、トラフィックとバッテリーを節約するため、複数のログを1つのHTTPリクエストにグループ化することです。50の個別POSTリクエストの代わりに、SDKは1つのJSON配列を送信します。一般的な戦略は、スケジュール送信(30秒ごと)、数ベース(50イベントごと)、またはイベントベース(クリティカルエラーのみ)です。
何百万人ものユーザーを持つアプリケーションでは、ログの量が1日あたりテラバイトに達することがあります。バッチングはリクエスト数を10~50分の1に減らし、サーバー負荷を軽減します。Sentryはトランスポートレベルでgzip圧縮を使用し、データ量をさらに60~70%削減します。
// Androidでのシンプルなバッチング実装
class LogBatcher {
private val buffer = mutableListOf<LogEvent>()
private val maxSize = 50
private val intervalMs = 30_000L
fun append(event: LogEvent) {
buffer.add(event)
if (buffer.size >= maxSize) flush()
}
suspend fun flush() {
val batch = buffer.toList()
buffer.clear()
sendToServer(batch)
}
}
gzipはログのHTTP送信における標準的な圧縮方法です。SentryとCrashlyticsのSDKは送信前に自動的にリクエストボディを圧縮します。重複排除はクライアント側で重複メッセージを削除します。同じイベントが毎秒100回発生する場合、SDKはcount=100フィールドで1回だけ送信します。
最も一般的な誤りは機密データのロギングです。Remote logging SDKはサーバーにデータを送信するため、開発者が誤ってパスワード、トークン、ユーザーメールをログに記録すると、そのデータはクラウドインフラストラクチャに保存されます。常にSDKレベルでPII(個人を特定できる情報)フィルタリングを使用してください。Sentryには送信前にデータをクリーンアップする組み込みのbeforeSendフックがあります。
2つ目の一般的な問題は過剰なロギングです。指の動きごとにサーバーに送信すると、データ量が指数関数的に増加し、サーバーコストも増大します。ロギングの予算を設定しましょう。本番環境ではユーザー1分あたり1~5イベントまでとし、デバッグログは特定のデバイスでのみ有効になるフラグで送信します。
3つ目の誤りはオフラインシナリオの無視です。SDKがネットワーク不在時にログを失い、再接続時に復元しない場合、不安定な接続のユーザーにとってremote loggingは役に立ちません。すべてのSDK(Firebase、Sentry)は自動的にログをローカルファイルにキャッシュし、ネットワーク利用可能時に送信しますが、この設定を確認する必要があります。
よくある質問
Crash reportingはアプリケーションのクラッシュに関する情報のみを収集します。Remote Loggingはカスタムログ、breadcrumbs、パフォーマンスメトリクス、UIイベントなどすべてのイベントを収集します。Crash reportingはremote loggingのサブセットであり、代替ではありません。
Crashlyticsは無料で基本的なクラッシュレポートに十分です。breadcrumbs、分散トレーシング、カスタムダッシュボード、柔軟なアラートが必要な場合はSentryが適しています。コンプライアンス要件のあるエンタープライズプロジェクトでは、Sentryはセルフホスト版も利用可能です。
ロギングレベルを使用します。debug/infoログはisDebuggableフラグで開発者デバイスからのみ送信します。beforeSendフックで他のレベル(warn、error)をフィルタリングし、PIIを含むフィールドを削除します。セッションあたりの最大ログサイズを定義します。
Logcatはサーバーへのリモート送信をサポートしていません。Androidでのremote loggingにはTimberを使用してFirebaseやSentryに転送し、LogcatはUSBデバッグ用に残しておきます。TimberはAndroid Log APIを置き換え、プランタブルツリーを追加します。
デバイス1分あたり50イベントまでであれば、バッチングを使用すればバッテリー消費に顕著な影響はありません(1件ずつではなくバッチで送信)。毎分200+イベントになるとWi-Fi/モデムが常時アクティブになり、バッテリーの消耗が15~25%速くなります。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。