Remote Logging 是一种将日志从移动设备发送到远程服务器以进行集中分析和监控的机制。与将数据存储在设备上的本地日志记录不同,远程收集可以实时查看来自所有用户设备的错误和异常。根据 Sentry Resource Library 的数据,使用远程日志记录的应用程序在发布后第一小时内能发现 92% 的生产错误,而仅使用崩溃报告时只能发现 15%。这是每个移动开发团队的必备工具:Firebase Crashlytics、Sentry 和 Datadog 为 iOS 和 Android 提供现成的 SDK。
要点
Remote Logging 是从远程设备收集日志并将其发送到中央服务器进行分析的过程。在移动开发环境中,远程日志记录不仅包括崩溃报告(crash reporting),还包括自定义事件、breadcrumbs、性能指标和用户场景。
远程日志记录与崩溃报告的主要区别在于主动性。崩溃报告只收集已经发生的应用程序崩溃数据。远程日志收集崩溃前的事件序列:用户打开了哪些屏幕、发送了哪些请求、输入了哪些数据。这使得无需与用户沟通即可重现错误场景。
Apple 通过 .logarchive 提供了内置的远程日志收集机制,但对于生产应用程序几乎总是使用第三方服务。Android SDK 包含 Logcat,可通过 ADB 远程访问,但不能用于没有调试模式的最终用户设备。
远程日志记录的架构由三个组件组成:设备上的客户端 SDK,负责收集和缓冲日志;传输 协议,用于发送数据;以及用于存储和可视化的服务器。
| 组件 | 角色 | 示例 |
|---|---|---|
| 客户端 SDK | 收集、缓冲、批处理 | Firebase SDK, Sentry Cocoa, Timber |
| 传输 | 通过 HTTPS 传输数据 | REST, gRPC, WebSocket |
| 服务器 | 存储、索引、告警 | Sentry, Crashlytics, Datadog |
客户端 SDK 在 RAM 中缓冲日志,并定期将它们以批处理(batches)形式发送到服务器。如果设备离线,日志将保存在本地文件中,并在下次网络连接时发送。缓冲区大小和发送间隔可配置:典型值为 50 个事件或 30 秒。
HTTPS REST — 最常用的远程日志记录协议。SDK 将日志序列化为 JSON 并通过 POST 请求发送到服务器端点。gRPC — 一种使用二进制序列化(Protocol Buffers)的替代方案,比 JSON 紧凑 30–40%,在连接不稳定的移动设备上速度更快。WebSocket 用于调试时的实时日志记录,但由于功耗问题在生产中很少使用。
Firebase Crashlytics — Google 的免费服务,用于收集崩溃报告和自定义日志。它内置于 Firebase SDK 中,不需要单独的服务器。Crashlytics 自动收集崩溃时的堆栈跟踪、设备状态、操作系统版本和打开的屏幕。
Crashlytics 中的自定义日志通过 log() 方法添加 — 它们不会立即发送到服务器,而是存储在环形缓冲区中,并附加到下一个崩溃报告中。这是与 Sentry 的关键区别,在 Sentry 中每个日志都是一个单独的事件。Crashlytics 中自定义日志的最大容量为每次崩溃 64 KB。
// 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 将崩溃与特定用户关联。这有助于确定错误是全局性的还是只影响单个用户。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 支持分布式追踪:日志器通过 trace ID 将客户端的 breadcrumbs 与后端请求关联起来。
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)和标签分类。通过 ADB 的远程访问可通过 USB 或 Wi-Fi 进行,但仅适用于调试模式下的设备——没有 USB 连接的设备上的生产应用程序不可访问。
在 Android 生产环境中进行远程日志记录时,会使用替代方案:Logcat 本身无法将日志发送到服务器。它的作用是本地诊断。但存在包装器(Timber、LogcatLive)可将消息转发到 Firebase 或 Sentry,同时保留熟悉的 Log.d / Log.e API。Timber 允许在不更改应用程序代码的情况下切换处理程序——调试树写入 Logcat,发布树通过批处理和压缩发送到服务器。
批处理 — 将多个日志分组到一个 HTTP 请求中以节省流量和电量。SDK 不是发送 50 个单独的 POST 请求,而是发送一个 JSON 数组。典型策略:按计划发送(每 30 秒)、按数量发送(每 50 个事件)或按事件发送(仅在严重错误时)。
对于拥有数百万用户的应用,日志量每天可达 TB 级别。批处理将请求数量减少 10–50 倍,并降低服务器负载。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。
最常见的错误——记录敏感数据。远程日志记录 SDK 将数据传输到服务器,如果开发者意外记录了用户的密码、令牌或电子邮件,这些数据就会进入云基础设施。始终在 SDK 级别使用 PII(个人身份信息)过滤:Sentry 有一个内置的 beforeSend 钩子,用于在发送前清理数据。
第二个常见问题——过度记录。如果每个手指移动都发送到服务器,数据量呈指数级增长,服务器成本也随之增加。为日志设定预算:生产中每个用户每分钟不超过 1-5 个事件。调试日志仅使用为特定设备启用的标志发送。
第三个错误——忽略离线场景。如果 SDK 在网络不可用时丢失日志,并且在重新连接时没有恢复它们,那么远程日志记录对于连接不稳定的用户毫无用处。所有 SDK(Firebase、Sentry)会自动将日志缓存到本地文件,并在网络可用时发送,但需要检查此设置。
常见问题
崩溃报告只收集应用程序崩溃的信息。远程日志记录收集所有事件:自定义日志、breadcrumbs、性能指标、UI 事件。崩溃报告是远程日志记录的子集,而不是其替代品。
Crashlytics 是免费的,足以满足基本的崩溃报告需求。如果需要 breadcrumbs、分布式追踪、自定义仪表板和灵活的告警,Sentry 更好。对于有合规要求的企业项目,Sentry 提供自托管版本。
使用日志级别:调试/信息日志仅通过 isDebuggable 标志从开发者设备发送。其他级别(警告、错误)通过 beforeSend 钩子过滤,删除包含 PII 的字段。为每个会话设置最大日志大小。
Logcat 不支持远程发送到服务器。对于 Android 上的远程日志记录,使用 Timber 转发到 Firebase 或 Sentry,Logcat 保留用于通过 USB 调试。Timber 替代 Android Log API 并添加可种植的树。
如果使用批处理(批量发送,而不是单独发送),每个设备每分钟最多 50 个事件不会对电池消耗产生显著影响。每分钟 200+ 事件时,Wi-Fi/调制解调器将持续处于活动状态——电池放电速度加快 15–25%。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。