自定义 Firebase Crashlytics 崩溃报告

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

添加自定义键

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

  • Crashlytics 信息中心内,您可以搜索与自定义键匹配的问题。
  • 当您在控制台中查看某个特定问题时,可以查看每个事件关联的自定义键(在“键”子标签中),甚至可以按自定义键过滤事件(使用页面顶部的“过滤条件”菜单)。

使用 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"];

使用 setCustomKeysAndValues 方法(NSDictionary 作为唯一的参数)批量添加键值对:

Swift

let keysAndValues = [
                 "string key" : "string value",
                 "string key 2" : "string value 2",
                 "boolean key" : true,
                 "boolean key 2" : false,
                 "float key" : 1.01,
                 "float key 2" : 2.02
                ] as [String : Any]

Crashlytics.crashlytics().setCustomKeysAndValues(keysAndValues)

Objective-C

NSDictionary *keysAndValues =
    @{@"string key" : @"string value",
      @"string key 2" : @"string value 2",
      @"boolean key" : @(YES),
      @"boolean key 2" : @(NO),
      @"float key" : @(1.01),
      @"float key 2" : @(2.02)};

[[FIRCrashlytics crashlytics] setCustomKeysAndValues: keysAndValues];

添加自定义日志消息

为了了解导致崩溃的事件的更多背景信息,您可以向应用添加自定义 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 crashlytics] 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 对象中包含的数据会被转换为键值对,并显示在具体问题内的键/日志部分中。

日志和自定义键

与崩溃报告类似,您可以通过嵌入日志和自定义键来向 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(name:"FooException", reason:"There was a foo.")
ex.stackTrace = [
  StackFrame(symbol:"makeError", file:"handler.js", line:495),
  StackFrame(symbol:"then", file:"routes.js", line:102),
  StackFrame(symbol:"main", file:"app.js", line:12),
]

crashlytics.record(exceptionModel:ex)

Objective-C

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

[[FIRCrashlytics crashlytics] recordExceptionModel:model];

自定义栈帧也可以仅使用地址进行初始化:

Swift

var  ex = ExceptionModel.init(name:"FooException", reason:"There was a foo.")
ex.stackTrace = [
  StackFrame(address:0xfa12123),
  StackFrame(address:12412412),
  StackFrame(address:194129124),
]

crashlytics.record(exceptionModel:ex)

Objective-C

FIRExceptionModel *model =
    [FIRExceptionModel exceptionModelWithName:@"FooException" reason:@"There was a foo."];
model.stackTrace = @[
  [FIRStackFrame stackFrameWithAddress:0xfa12123],
  [FIRStackFrame stackFrameWithAddress:12412412],
  [FIRStackFrame stackFrameWithAddress:194129124],
];

[[FIRCrashlytics crashlytics] recordExceptionModel:model];

启用自选式报告

默认情况下,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 问题列表顶部。