自定义 Firebase Crashlytics 崩溃报告

本指南介绍如何使用 Firebase Crashlytics SDK 自定义崩溃报告。默认情况下,Crashlytics 会自动为您应用的所有用户收集崩溃报告(您可以关闭自动崩溃报告,并为您的用户启用自选式报告)。Crashlytics 提供了四种日志记录机制:自定义键自定义日志用户标识符捕获的异常

添加自定义键

自定义键可以帮助您获取导致崩溃的应用的特定状态。您可以将任意键值对与您的崩溃报告相关联,然后在 Firebase 控制台中查看这些键值对。

使用 setCustomValue 方法可设置键值对。例如:

Swift

// Set int_key to 100.
Crashlytics.crashlytics().setCustomValue(100, forKey: "int_key")

// Set str_key to "hello".
Crashlytics.crashlytics().setCustomValue("hello", forKey: "str_key")

Objective-C

设置整数、布尔值或浮点数时,请以 @(value) 的形式提供值。

// Set int_key to 100.
[[FIRCrashlytics crashlytics] setCustomValue:@(100) forKey:@"int_key"];

// Set str_key to "hello".
[[FIRCrashlytics crashlytics] setCustomValue:@"hello" forKey:@"str_key"];

您还可以通过调用现有键并将其设置为不同的值来修改现有键的值。例如:

Swift

Crashlytics.crashlytics().setCustomValue(100, forKey: "int_key")

// Set int_key to 50 from 100.
Crashlytics.crashlytics().setCustomValue(50, forKey: "int_key")

Objective-C

[[FIRCrashlytics crashlytics] setCustomValue:@(100) forKey:@"int_key"];

// Set int_key to 50 from 100.
[[FIRCrashlytics crashlytics] setCustomValue:@(50) forKey:@"int_key"];

添加自定义日志消息

记录的消息与您的崩溃数据相关联,并在您查看特定崩溃时显示在 Firebase Crashlytics 信息中心中。

Crashlytics.Log(string message);

为了了解导致崩溃的事件的更多背景信息,您可以向应用添加自定义 Crashlytics 日志。Crashlytics 会将日志与您的崩溃数据相关联,并将其显示在 Firebase 控制台的 Crashlytics 页面的日志标签页下。

Swift

使用 log()log(format:, arguments:) 可帮助查明问题。如果您希望获得包含消息的有用日志输出,那么您传递给 log() 的对象必须符合 CustomStringConvertible 属性。log() 会返回您为该对象定义的说明属性。例如:

Crashlytics.crashlytics().log("Higgs-Boson detected! Bailing out…, \(attributesDict)")

.log(format:, arguments:) 为调用 getVaList() 时返回的值设置格式。例如:

Crashlytics.crashlytics().log(format: "%@, %@", arguments: getVaList(["Higgs-Boson detected! Bailing out…", attributesDict]))

如需详细了解如何使用 log()log(format:, arguments:),请参阅 Crashlytics 函数参考文档

Objective-C

使用 loglogWithFormat 可帮助查明问题。请注意,如果您希望获得包含消息的有用日志输出,那么您传递给这两种方法的对象必须替换 description 实例属性。 例如:

[[FIRCrashlytics crashlytics] log:@"Simple string message"];

[[FIRCrashlytics crashlytics] logWithFormat:@"Higgs-Boson detected! Bailing out... %@", attributesDict];

[[FIRCrashlytics crashlytics] logWithFormat:@"Logging a variable argument list %@" arguments:va_list_arg];

如需详细了解如何使用 loglogWithFormat,请参阅 Crashlytics 函数参考文档

设置用户标识符

为了诊断某个问题,了解哪些用户遇到了特定的崩溃通常很有帮助。Crashlytics 提供了一种在崩溃报告中以匿名方式标识用户的方法。

要将用户 ID 添加到报告中,请以 ID 编号、令牌或哈希值的形式为每个用户分配一个唯一标识符:

Swift
Crashlytics.crashlytics().setUserID("123456789")
Objective-C
[[FIRCrashlytics crashlytics] setUserID:@"123456789"];

如果您在设置某个用户标识符后需要将其清除,请将该值重置为空白字符串。清除用户标识符不会移除现有的 Crashlytics 记录。如果您需要删除与用户 ID 关联的记录,请与 Firebase 支持团队联系

报告非严重异常

除了自动报告您的应用中出现的崩溃,Crashlytics 还可让您记录非严重异常,并在应用下次启动时将这些异常记录发送给您。

如需记录非严重异常,您可以使用 recordError 方法记录 NSError 对象。recordError 通过调用 [NSThread callStackReturnAddresses] 来捕获线程的调用堆栈。

Swift

Crashlytics.crashlytics().record(error: error)

Objective-C

[FIRCrashlytics recordError:error];

在使用 recordError 方法时,了解 NSError 的结构以及 Crashlytics 如何使用相关数据对崩溃进行分组是非常重要的。如果 recordError 方法使用不当,可能会导致不可预知的行为,并且可能导致 Crashlytics 限制您的应用中报告所记录错误的功能。

每个 NSError 对象都具有三个参数:

  • domain: String
  • code: Int
  • userInfo: [AnyHashable : Any]? = nil

与根据堆栈轨迹分析结果进行分组的严重崩溃不同,记录的错误是根据 domaincode 来分组的。这是严重崩溃与记录的错误之间的重要区别。例如:

Swift

let userInfo = [
  NSLocalizedDescriptionKey: NSLocalizedString("The request failed.", comment: ""),
  NSLocalizedFailureReasonErrorKey: NSLocalizedString("The response returned a 404.", comment: ""),
  NSLocalizedRecoverySuggestionErrorKey: NSLocalizedString("Does this page exist?", comment: ""),
  "ProductID": "123456",
  "View": "MainView"
]

let error = NSError.init(domain: NSCocoaErrorDomain,
                         code: -1001,
                         userInfo: userInfo)

Objective-C

NSDictionary *userInfo = @{
  NSLocalizedDescriptionKey: NSLocalizedString(@"The request failed.", nil),
  NSLocalizedFailureReasonErrorKey: NSLocalizedString(@"The response returned a 404.", nil),
  NSLocalizedRecoverySuggestionErrorKey: NSLocalizedString(@"Does this page exist?", nil),
  @"ProductID": @"123456",
  @"View": @"MainView",
};

NSError *error = [NSError errorWithDomain:NSCocoaErrorDomain
                                     code:-1001
                                 userInfo:userInfo];

在您记录上述错误时,系统会创建一个根据 NSSomeErrorDomain-1001 分组的新问题。如果记录的其他错误具有与该错误相同的网域和代码值,那么都会划分到这个问题下。userInfo 对象中包含的数据会被转换为键值对,并显示在单个问题内的键/日志部分中。

警告:请避免在网域和代码字段中使用唯一值,例如用户 ID、产品 ID 和时间戳。在这些字段中使用唯一值会导致问题的基数增大,并且可能导致 Crashlytics 需要对您的应用中报告所记录错误的功能进行限制。应该将唯一值添加到 userInfo Dictionary 对象中。

日志和自定义键

与崩溃报告类似,您可以通过嵌入日志和自定义键来向 NSError 中添加背景信息。但是,在具体需要附加的日志方面,崩溃和记录的错误是有区别的。在发生崩溃并且应用重新启动时,Crashlytics 从磁盘中检索到的日志是截至崩溃那一刻所写入的日志。而当您记录一个 NSError 时,应用并不会立即终止。由于 Crashlytics 只在下一次应用启动时才发送记录的错误报告,而且它必须限制为日志分配的磁盘空间量,因此有可能出现以下情况:在记录某个 NSError 之后日志存储空间已被占满,以至于等到 Crashlytics 从设备发送报告时,所有相关日志都已经被轮替掉了。在应用中记录 NSErrors 并使用日志和自定义键时,请务必注意在这方面保持平衡。

性能考虑因素

请注意,记录 NSError 日志的开销可能相当高。在您进行调用时,Crashlytics 会通过一种称为 Stack Unwinding(堆栈展开)的过程来捕获当前线程的调用堆栈。这个过程可能需要占用大量的 CPU 和 I/O 资源,尤其是在支持 DWARF 展开的架构(arm64 和 x86)上。展开过程完成后,信息将同步写入到磁盘中。 这可以防止在下一行代码发生崩溃时出现数据丢失现象。

尽管在后台线程中调用此 API 是安全的,但请注意,如果将此调用分发到另一个队列中,当前堆栈轨迹的背景信息将会丢失。

那 NSException 呢?

Crashlytics 不提供直接记录 NSException 实例以及为其记录日志的功能。一般来说,Cocoa API 和 Cocoa Touch API 并不具有异常安全性。这意味着使用 @catch 可能会在您的进程中引发非常严重的意外副作用,即使在极其谨慎的情况下使用它也是如此。切勿在代码中使用 @catch 语句。请参阅有关该主题的 Apple 文档

自定义堆栈轨迹

如果您的应用在非原生环境(如 C++ 或 Unity)中运行,您可以使用 Exception Model API 以应用的原生异常格式报告崩溃元数据。已报告的异常会被标为非严重异常。

Swift

var  ex = ExceptionModel.init(name:"FooException", reason:"There was a foo.")
ex.stackTrace = [
  StackFrame.init(symbol:"makeError" fileName:"handler.js" lineNumber:495),
  StackFrame.init(symbol:"then" fileName:"routes.js" lineNumber:102),
  StackFrame.init(symbol:"main" fileName:"app.js" lineNumber:12),
]

crashlytics.record(exceptionModel:ex)

Objective-C

FIRExceptionModel *model =
    [FIRExceptionModel exceptionModelWithName:@"FooException" reason:@"There was a foo."];
model.stackTrace = @[
  [FIRStackFrame stackFrameWithSymbol:@"makeError" fileName:@"handler.js" lineNumber:495],
  [FIRStackFrame stackFrameWithSymbol:@"then" fileName:@"routes.js" lineNumber:102],
  [FIRStackFrame stackFrameWithSymbol:@"main" fileName:@"app.js" lineNumber:12],
];

启用自选式报告

默认情况下,Crashlytics 会自动为您的应用的所有用户收集崩溃报告。为了让用户对其发送的数据有更多的控制权,您可以为用户启用自选式报告功能,为此,请停用自动收集功能,并且仅为选定用户初始化 Crashlytics:

  1. 如需停用自动收集功能,请在 Info.plist 文件中添加新键:

    • 键:FirebaseCrashlyticsCollectionEnabled
    • 值:false
  2. 在运行时调用 Crashlytics 数据收集替换,从而为选定用户启用收集功能。在您的应用的多次启动之间,替换值会保持不变,这样,在该应用实例将来启动时,Crashlytics 就可以自动收集相关报告。如需停用自动崩溃报告功能,请将 false 作为替换值传递。将此设置为 false 时,新值要等到应用下次运行时才会生效。

    Swift
    Crashlytics.crashlytics().setCrashlyticsCollectionEnabled(true)
    Objective-C
    [[FIRCrashlytics crashlytics] setCrashlyticsCollectionEnabled:YES];

管理 Crash Insights 数据

Crash Insights 会比较您的匿名堆栈轨迹和来自其他 Firebase 应用的跟踪数据,并让您知道您的问题是否属于个例,从而帮助您解决问题。对于许多问题,Crash Insights 甚至会提供资源来帮助您调试崩溃。

Crash Insights 使用汇总的崩溃数据来识别常见的稳定性趋势。 如果您不想分享应用的数据,则可以在 Crash Insights 菜单中选择停用 Crash Insights。此菜单位于 Firebase 控制台的 Crashlytics 问题列表顶部。