您可以将 Crashlytics 数据导出到 BigQuery 以便进一步分析。借助 BigQuery,您可以使用 BigQuery SQL 来分析数据,将数据导出至其他云提供商,并搭配 Google 数据洞察将数据用于实现可视化和自定义信息中心。
启用 BigQuery Export
- 进入 Firebase 控制台的集成页面。
- 在 BigQuery 卡片中,点击关联。
- 按照屏幕上的说明启用 BigQuery。
当您将项目关联到 BigQuery 时:
- Firebase 会安排每日将您的数据从 Firebase 项目同步到 BigQuery。
- 默认情况下,项目中的所有应用都会关联到 BigQuery,并且您此后向项目中添加的所有应用都会自动关联到 BigQuery。您能控制哪些应用可发送数据。
- Firebase 会将您现有数据的副本导出到 BigQuery。对于每个关联的应用,这包括一个批处理表,其中含有每日同步的数据。
- 如果启用了 Crashlytics BigQuery 流式导出,所有关联的应用都将具有一个包含持续更新数据的实时表。
如需停用 BigQuery Export,请在 Firebase 控制台中取消关联您的项目。
哪些数据会导出至 BigQuery?
Firebase Crashlytics 数据会导出到名为 firebase_crashlytics
的 BigQuery 数据集。默认情况下,系统将在 Crashlytics 数据集内为项目中的每个应用分别创建表。Firebase 会根据应用的软件包标识符为这些表命名:将英文句点转换为下划线,并在末尾处附加平台名称。
例如,ID 是 com.google.test
的应用的数据会位于名为 com_google_test_ANDROID
的表中。此批处理表每天更新一次。如果您启用了 Crashlytics BigQuery 流式导出,Firebase Crashlytics 数据也将实时流式传输到 com_google_test_ANDROID_REALTIME
。
表中的每一行均表示应用中发生的事件,包括崩溃、非严重错误和 ANR。
启用 Crashlytics BigQuery 流式导出
您可以使用 BigQueryStreaming 实时流式传输您的 Crashlytics 数据。 您可以将其用于任何需要实时数据的用途,例如在实时信息中心内展示信息、实时观察发布情况,或者监控触发提醒和自定义工作流的应用问题。
Crashlytics BigQuery 流式导出不适用于 BigQuery 沙盒。
启用 Crashlytics BigQuery 流式导出后,除了批处理表之外,您还会获得一个实时表。您应该了解,这两种表之间有如下区别:
批处理表 | 实时表 |
---|---|
|
|
批处理表非常适合用于长期分析以及识别随时间推移而变化的趋势,因为我们在写入事件之前会先将数据持久存储,而且最多可在表中回填 30 天前的事件数据。在将数据写入实时表时,我们会立即将其写入 BigQuery,因此这种表非常适合用于实时信息中心和自定义提醒。您可以使用拼接查询将这两种表结合使用,从而同时获享两者的优势。请参阅下面的查询示例 9。
默认情况下,实时表的分区过期时间为 30 天。如需了解如何修改此设置,请参阅更新分区过期时间。
启用 Crashlytics BigQuery 流式传输
如需启用流式传输,请前往 BigQuery 集成页面的 Crashlytics 部分,然后选中包含流式传输复选框。
数据洞察模板
如需在数据洞察模板中启用实时数据,请按照使用数据洞察直观呈现导出的 Crashlytics 数据中的说明操作。
视图
您可以使用 BigQuery 界面将如下示例查询转换为视图。如需查看详细说明,请参阅创建视图。
您可以对导出的数据执行哪些操作?
BigQuery 导出的数据包含原始崩溃数据,其中包括设备类型、操作系统、异常(Android 应用)或错误(Apple 应用)和 Crashlytics 日志,以及其他数据。
处理 BigQuery 中的 Firebase Crashlytics 数据
以下示例演示了您可以对 Crashlytics 数据运行的查询。 Crashlytics 信息中心不提供这些查询生成的报告。
Crashlytics 查询示例
以下示例说明了如何生成报告,从而将崩溃事件数据汇总为更易理解的摘要。
示例 1:每天的崩溃次数
在努力解决尽可能多的 bug 之后,开发负责人认为其团队终于做好了准备,可以推出他们全新的照片共享应用。在推出之前,他们想检查一下过去一个月内每天的崩溃次数,以确保这次大规模的纠错让应用的运行更加稳定了:
SELECT COUNT(DISTINCT event_id) AS number_of_crashes, FORMAT_TIMESTAMP("%F", event_timestamp) AS date_of_crashes FROM `projectId.firebase_crashlytics.package_name_ANDROID` GROUP BY date_of_crashes ORDER BY date_of_crashes DESC LIMIT 30;
示例 2:查找最普遍的崩溃
为了适当排定生产计划的优先级,项目经理想要找出其产品中 10 个最普遍的崩溃。为此,他们生成了一个查询来提供相关的数据点:
SELECT DISTINCT issue_id, COUNT(DISTINCT event_id) AS number_of_crashes, COUNT(DISTINCT installation_uuid) AS number_of_impacted_user, blame_frame.file, blame_frame.line FROM `projectId.firebase_crashlytics.package_name_ANDROID` WHERE event_timestamp >= TIMESTAMP_SUB(CURRENT_TIMESTAMP(),INTERVAL 168 HOUR) AND event_timestamp < CURRENT_TIMESTAMP() GROUP BY issue_id, blame_frame.file, blame_frame.line ORDER BY number_of_crashes DESC LIMIT 10;
示例 3:10 大易崩溃设备
秋季是新手机的发布季!而开发者们知道,这也意味着又到了解决新设备特有的问题的季节。为了针对很可能出现的兼容性问题而未雨绸缪,他们编写了一个查询,可以识别过去一周里出现最多崩溃的 10 款设备:
SELECT device.model, COUNT(DISTINCT event_id) AS number_of_crashes FROM `projectId.firebase_crashlytics.package_name_ANDROID` WHERE event_timestamp >= TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 168 HOUR) AND event_timestamp < CURRENT_TIMESTAMP() GROUP BY device.model ORDER BY number_of_crashes DESC LIMIT 10;
示例 4:按自定义键过滤
游戏开发者想知道游戏中崩溃最频繁的是哪个关卡。为了跟踪该统计信息,他们设置了自定义 Crashlytics 键 current_level
,并在用户每次进入新的关卡时更新该键。
Objective-C
CrashlyticsKit setIntValue:3 forKey:@"current_level";
Swift
Crashlytics.sharedInstance().setIntValue(3, forKey: "current_level");
Java
Crashlytics.setInt("current_level", 3);
现在 BigQuery 导出数据中包含该键,他们编写了一个查询来报告与各个崩溃事件相关联的 current_level
值的分布情况:
SELECT COUNT(DISTINCT event_id) AS num_of_crashes, value FROM `projectId.firebase_crashlytics.package_name_ANDROID` UNNEST(custom_keys) WHERE key = "current_level" GROUP BY key, value ORDER BY num_of_crashes DESC
示例 5:用户 ID 提取
开发者有一个处于抢先体验阶段的应用。该应用的大多数用户都很喜欢它,但有三位用户遇到了异常数量的崩溃。为了确定该问题的根源,他们编写了一个查询,按照用户 ID 拉取这些用户的所有崩溃事件:
SELECT * FROM `projectId.firebase_crashlytics.package_name_ANDROID` WHERE user.id IN ("userid1", "userid2", "userid3") ORDER BY user.id
示例 6:查找面临特定崩溃问题的所有用户
开发者向一组 Beta 版测试人员发布的版本中包含一个严重 bug。该团队可以使用上面示例 2 中的查询来找出这一特定崩溃问题的 ID。现在,开发者想要运行查询来提取受此崩溃影响的应用用户列表:
SELECT user.id as user_id FROM `projectId.firebase_crashlytics.package_name_ANDROID` WHERE issue_id = "YOUR_ISSUE_ID" AND application.display_version = "" AND user.id != "" ORDER BY user.id;
示例 7:受崩溃问题影响的用户数量,按国家/地区细分
开发团队在发布新版本期间发现了一个严重 bug。他们可以使用上面示例 2 中的查询来找出这一特定崩溃问题的 ID。该团队现在想要确认该崩溃是否已蔓延至全球不同国家/地区的用户。
要编写此查询,团队将需要执行以下操作:
为 Google Analytics 启用 BigQuery Export。请参阅将项目数据导出到 BigQuery。
更新应用以将用户 ID 传递到 Google Analytics SDK 和 Crashlytics SDK。
Objective-C
CrashlyticsKit setUserIdentifier:@"123456789"; FIRAnalytics setUserID:@"12345678 9";
Swift
Crashlytics.sharedInstance().setUserIdentifier("123456789"); Analytics.setUserID("123456789");
Java
Crashlytics.setUserIdentifier("123456789"); mFirebaseAnalytics.setUserId("123456789");
编写一个查询,根据用户 ID 字段将 Google Analytics BigQuery 数据集中的事件与 Crashlytics BigQuery 数据集中的崩溃联接到一起:
SELECT DISTINCT c.issue_id, a.geo.country, COUNT(DISTINCT c.user.id) as num_users_impacted FROM `projectId.firebase_crashlytics.package_name_ANDROID` c INNER JOIN `projectId.analytics_YOUR_TABLE.events_*` a on c.user.id = a.user_id WHERE c.issue_id = "YOUR_ISSUE_ID" AND a._TABLE_SUFFIX BETWEEN '20190101' AND '20200101' GROUP BY c.issue_id, a.geo.country, c.user.id
示例 8:今天到现在为止的 5 大问题
需要启用 Crashlytics BigQuery 流式导出
SELECT issue_id, COUNT(DISTINCT event_id) AS events FROM `your_project.firebase_crashlytics.package_name_ANDROID_REALTIME` WHERE DATE(event_timestamp) = CURRENT_DATE() GROUP BY issue_id ORDER BY events DESC LIMIT 5;
示例 9:自 DATE 起至今(包括今天)的 5 大问题
需要启用 Crashlytics BigQuery 流式导出。
在此示例中,我们结合使用了批处理表和实时表,以便将实时信息添加到可靠的批量数据中。由于 event_id
是主键,因此我们可以使用 DISTINCT event_id
对两个表中的任何常见事件进行重复数据删除。
SELECT issue_id, COUNT(DISTINCT event_id) AS events FROM ( SELECT issue_id, event_id, event_timestamp FROM `your_project.firebase_crashlytics.package_name_ANDROID_REALTIME` UNION ALL SELECT issue_id, event_id, event_timestamp FROM `your_project.firebase_crashlytics.package_name_ANDROID`) WHERE event_timestamp >= "2020-01-13" GROUP BY issue_id ORDER BY events DESC LIMIT 5;
了解 BigQuery 中的 Firebase Crashlytics 架构
将 Crashlytics 与 BigQuery 关联后,Firebase 会导出最近的事件(崩溃、非严重错误和 ANR),包括执行关联操作之前几天(最多两天)的事件,并提供回填选项(最多可回填 30 天前的数据)。
从关联起直至您停用该关联,Firebase 每天都会导出 Crashlytics 事件。每次导出后,可能需要几分钟时间才能在 BigQuery 中找到这些数据。
数据集
Firebase Crashlytics 会为 Crashlytics 数据在 BigQuery 中创建一个新的数据集。该数据集会涵盖您的整个项目(即使它有多个应用)。
表
除非您选择停止导出应用数据,否则 Firebase Crashlytics 会在数据集中为项目的每个应用创建一个表。Firebase 会根据应用的软件包标识符为这些表命名:将英文句点转换为下划线,并在末尾处附加平台名称。
例如,ID 为 com.google.test
的 Android 应用的数据会存储在名为 com_google_test_ANDROID
的表中,而实时数据(如果已启用)会存储在名为 com_google_test_ANDROID_REALTIME
的表中
除了开发者自定义的任何 Crashlytics 键之外,表还包含一组标准的 Crashlytics 数据。
行
表中的每一行都表示应用遇到的一个错误。
列
对于崩溃、非严重错误和 ANR,表中的列完全相同。如果启用了 Crashlytics BigQuery 流式导出,则实时表中的列与批处理表中的列相同。下面列出了导出结果中的各个列。
无堆栈轨迹
表示无堆栈轨迹事件的行中存在的列。
字段名称 | 数据类型 | 说明 |
---|---|---|
platform | STRING | Apple 或 Android 应用 |
bundle_identifier | STRING | 软件包 ID,例如 com.google.gmail |
event_id | STRING | 事件的唯一 ID |
is_fatal | BOOLEAN | 应用是否崩溃 |
error_type | STRING | 事件的错误类型(FATAL、NON_FATAL、ANR) |
issue_id | STRING | 与事件相关的问题 |
variant_id | STRING | 与此事件相关的问题变体 请注意,并非所有事件都有关联的问题变体。 |
event_timestamp | TIMESTAMP | 事件发生的时间 |
device | RECORD | 发生事件的设备 |
device.manufacturer | STRING | 设备制造商 |
device.model | STRING | 设备型号 |
device.architecture | STRING | X86_32、X86_64、ARMV7、ARM64、ARMV7S 或 ARMV7K |
memory | RECORD | 设备的内存状态 |
memory.used | INT64 | 使用的内存字节数 |
memory.free | INT64 | 剩余的内存字节数 |
storage | RECORD | 设备的永久性存储空间 |
storage.used | INT64 | 使用的存储空间字节数 |
storage.free | INT64 | 剩余的存储空间字节数 |
operating_system | RECORD | 设备上的操作系统的详细信息 |
operating_system.display_version | STRING | 设备上的操作系统的版本 |
operating_system.name | STRING | 设备上的操作系统的名称 |
operating_system.modification_state | STRING | 设备是否已经过修改,例如已越狱/已启用 root 权限 (MODIFIED 或 UNMODIFIED) |
operating_system.type | STRING | 在设备上运行的操作系统的类型(例如 IOS、MACOS);仅适用于 Apple 平台应用 |
operating_system.device_type | STRING | 设备的类型(例如 MOBILE、TABLET、TV 等);也称为“设备类别” |
应用 | RECORD | 引发事件的应用 |
application.build_version | STRING | 应用的版本号 |
application.display_version | STRING | |
user | RECORD | 可选:收集的应用用户信息 |
user.name | STRING | 可选:用户的姓名 |
user.email | STRING | 可选:用户的电子邮件地址 |
user.id | STRING | 可选:与用户关联的特定应用的 ID |
custom_keys | REPEATED RECORD | 开发者定义的键值对 |
custom_keys.key | STRING | 开发者定义的密钥 |
custom_keys.value | STRING | 开发者定义的值 |
installation_uuid | STRING | 标识唯一应用和设备安装的 ID |
crashlytics_sdk_versions | STRING | 生成事件的 Crashlytics SDK 版本 |
app_orientation | STRING | PORTRAIT、LANDSCAPE、FACE_UP 或 FACE_DOWN |
device_orientation | STRING | PORTRAIT、LANDSCAPE、FACE_UP 或 FACE_DOWN |
process_state | STRING | BACKGROUND 或 FOREGROUND |
logs | REPEATED RECORD | 由 Crashlytics 日志记录器生成的带时间戳的日志消息(如果已启用) |
logs.timestamp | TIMESTAMP | 日志创建时间 |
logs.message | STRING | 记录的消息 |
breadcrumbs | REPEATED RECORD | 带有时间戳的 Google Analytics 路径(如果已启用) |
breadcrumbs.timestamp | TIMESTAMP | 与路径关联的时间戳 |
breadcrumbs.name | STRING | 与路径关联的名称 |
breadcrumbs.params | REPEATED RECORD | 与路径关联的参数 |
breadcrumbs.params.key | STRING | 与路径关联的参数键 |
breadcrumbs.params.value | STRING | 与路径关联的参数值 |
blame_frame | RECORD | 被确定为崩溃或错误根本原因的帧 |
blame_frame.line | INT64 | 帧文件的行号 |
blame_frame.file | STRING | 帧文件的名称 |
blame_frame.symbol | STRING | 水化合 (hydration) 符号,或原始符号(如果无法水化合) |
blame_frame.offset | INT64 | 包含代码的二进制图片中的字节偏移量,Java 异常未设置 |
blame_frame.address | INT64 | 包含代码的二进制图片中的地址,Java 帧未设置 |
blame_frame.library | STRING | 包含帧的库的显示名 |
blame_frame.owner | STRING | DEVELOPER、VENDOR、RUNTIME、PLATFORM 或 SYSTEM |
blame_frame.blamed | BOOLEAN | Crashlytics 的分析是否已确定此帧是导致崩溃或错误的原因 |
exceptions | REPEATED RECORD | 仅适用于 Android:此事件期间发生的异常。嵌套的异常以时间倒序呈现(读取:最后一条记录是抛出的第一个异常) |
exceptions.type | STRING | 异常类型,例如 java.lang.IllegalStateException |
exceptions.exception_message | STRING | 与异常关联的消息 |
exceptions.nested | BOOLEAN | 对于除最后抛出的异常(即第一条记录)之外的所有异常都为 true |
exceptions.title | STRING | 线程的标题 |
exceptions.subtitle | STRING | 线程的副标题 |
exceptions.blamed | BOOLEAN | 如果 Crashlytics 确定此异常是导致错误或崩溃的原因,则为 true |
exceptions.frames | REPEATED RECORD | 与异常关联的帧 |
exceptions.frames.line | INT64 | 帧文件的行号 |
exceptions.frames.file | STRING | 帧文件的名称 |
exceptions.frames.symbol | STRING | 水化合符号,或原始符号(如果无法水化合) |
exceptions.frames.offset | INT64 | 包含代码的二进制图片中的字节偏移量,Java 异常未设置 |
exceptions.frames.address | INT64 | 包含代码的二进制图片中的地址,Java 帧未设置 |
exceptions.frames.library | STRING | 包含帧的库的显示名 |
exceptions.frames.owner | STRING | DEVELOPER、VENDOR、RUNTIME、PLATFORM 或 SYSTEM |
exceptions.frames.blamed | BOOLEAN | Crashlytics 的分析是否已确定此帧是导致崩溃或错误的原因 |
error | REPEATED RECORD | 仅限 Apple 应用:非严重错误 |
error.queue_name | STRING | 线程正在运行的队列 |
error.code | INT64 | 与应用自定义记录的 NSError 关联的错误代码 |
error.title | STRING | 线程的标题 |
error.subtitle | STRING | 线程的副标题 |
error.blamed | BOOLEAN | Crashlytics 的分析是否已确定此帧是导致错误的原因 |
error.frames | REPEATED RECORD | 堆栈轨迹的帧 |
error.frames.line | INT64 | 帧文件的行号 |
error.frames.file | STRING | 帧文件的名称 |
error.frames.symbol | STRING | 水化合符号,或原始符号(如果无法水化合) |
error.frames.offset | INT64 | 包含代码的二进制图片中的字节偏移量 |
error.frames.address | INT64 | 包含代码的二进制图片中的地址 |
error.frames.library | STRING | 包含帧的库的显示名 |
error.frames.owner | STRING | DEVELOPER、VENDOR、RUNTIME、PLATFORM 或 SYSTEM |
error.frames.blamed | BOOLEAN | Crashlytics 的分析是否已确定此帧是导致错误的原因 |
threads | REPEATED RECORD | 事件发生时显示的线程 |
threads.crashed | BOOLEAN | 线程是否崩溃 |
threads.thread_name | STRING | 线程的名称 |
threads.queue_name | STRING | 仅限 Apple 应用:运行线程的队列 |
threads.signal_name | STRING | 导致应用崩溃的信号的名称,仅出现在崩溃的原生线程上 |
threads.signal_code | STRING | 导致应用崩溃的信号的代码;仅出现在崩溃的原生线程上 |
threads.crash_address | INT64 | 导致应用崩溃的信号的地址;仅出现在崩溃的原生线程上 |
threads.code | INT64 | 仅限 Apple 应用:应用的自定义记录 NSError 的错误代码 |
threads.title | STRING | 线程的标题 |
threads.subtitle | STRING | 线程的副标题 |
threads.blamed | BOOLEAN | Crashlytics 的分析是否已确定此帧是导致崩溃或错误的原因 |
threads.frames | REPEATED RECORD | 线程帧 |
threads.frames.line | INT64 | 帧文件的行号 |
threads.frames.file | STRING | 帧文件的名称 |
threads.frames.symbol | STRING | 水化合符号,或原始符号(如果无法水化合) |
threads.frames.offset | INT64 | 包含代码的二进制图片中的字节偏移量 |
threads.frames.address | INT64 | 包含代码的二进制图片中的地址 |
threads.frames.library | STRING | 包含帧的库的显示名 |
threads.frames.owner | STRING | DEVELOPER、VENDOR、RUNTIME、PLATFORM 或 SYSTEM |
threads.frames.blamed | BOOLEAN | Crashlytics 的分析是否已确定此帧是导致错误的原因 |
unity_metadata.unity_version | STRING | 此设备上运行的 Unity 版本 |
unity_metadata.debug_build | BOOLEAN | 指示该 build 是否为调试 build |
unity_metadata.processor_type | STRING | 处理器类型 |
unity_metadata.processor_count | INT64 | 处理器(内核)数量 |
unity_metadata.processor_frequency_mhz | INT64 | 处理器的频率(以 MHz 为单位) |
unity_metadata.system_memory_size_mb | INT64 | 系统内存的大小(以 Mb 为单位) |
unity_metadata.graphics_memory_size_mb | INT64 | 图形内存(以 MB 为单位) |
unity_metadata.graphics_device_id | INT64 | 图形设备的标识符 |
unity_metadata.graphics_device_vendor_id | INT64 | 图形处理器供应商的标识符 |
unity_metadata.graphics_device_name | STRING | 图形设备的名称 |
unity_metadata.graphics_device_vendor | STRING | 图形设备的供应商 |
unity_metadata.graphics_device_version | STRING | 图形设备的版本 |
unity_metadata.graphics_device_type | STRING | 图形设备的类型 |
unity_metadata.graphics_shader_level | INT64 | 图形着色器级别 |
unity_metadata.graphics_render_target_count | INT64 | 图形渲染目标的数量 |
unity_metadata.graphics_copy_texture_support | STRING | 支持以 Unity API 中定义的方式复制图形纹理 |
unity_metadata.graphics_max_texture_size | INT64 | 纹理渲染专用大小上限 |
unity_metadata.screen_size_px | STRING | 屏幕大小(以像素为单位),格式为“宽 x 高” |
unity_metadata.screen_resolution_dpi | STRING | 屏幕的 DPI(以浮点数表示) |
unity_metadata.screen_refresh_rate_hz | INT64 | 屏幕的刷新率(以 Hz 为单位) |
使用数据洞察直观呈现导出的 Crashlytics 数据
Google 数据洞察可以将您在 BigQuery 中的 Crashlytics 数据集转换成方便阅读和共享、可完全自定义的报告。
如需详细了解如何使用数据洞察,请阅读数据洞察快速入门指南:欢迎使用数据洞察。
使用 Crashlytics 报告模板
数据洞察提供了一个 Crashlytics 的示例报告,其中包含一组来自已导出的 Crashlytics BigQuery 架构的全面维度和指标。如果您已启用 Crashlytics BigQuery 流式导出,则可以在数据洞察模板的实时趋势页面上查看这些数据。您可以将该示例作为模板,根据自己应用的原始崩溃数据快速创建新报告并直观呈现数据:
- 打开 Crashlytics 数据洞察信息中心模板。
- 点击右上角的使用模板。
- 在新数据源下拉列表中,选择创建新数据源。
- 点击 BigQuery 卡片上的选择。
- 选择一个包含导出的 Crashlytics 数据的表,方法是依次选择我的项目 > [your-project-name] > firebase_crashlytics > [your-table-name]。 您的批处理表格始终可供选择;如果已启用 Crashlytics BigQuery 流式导出,那么还可以改为选择实时表。
- 在配置下,将 Crashlytics 模板级设置为默认。
- 点击连接,创建新的数据源。
- 点击添加到报告,以返回 Crashlytics 模板。
- 最后,点击创建报告,创建 Crashlytics 数据洞察信息中心模板的副本。