app Center 崩溃 (macOS)

重要

Visual Studio App Center 计划于 2025 年 3 月 31 日停用。 虽然可以继续使用 Visual Studio App Center,直到它完全停用,但你可以考虑迁移到几个建议的替代方法。

详细了解支持时间线和替代方法。

每次应用崩溃时,App Center 崩溃都会自动生成崩溃日志。 日志首先写入设备的存储,当用户再次启动应用时,故障报告将发送到 App Center。 收集崩溃适用于 beta 应用和实时应用,即提交到App Store的应用。 故障日志包含有助于修复崩溃的重要信息。

如果尚未在应用程序中设置 SDK,请按照入门部分进行操作。

此外,macOS 上的崩溃日志需要符号化,检查 App Center 诊断文档,其中介绍了如何为应用提供符号。

注意

4.0.0 App Center 版本中引入了中断性变更。 按照 迁移到 App Center SDK 4.0.0 及更高 版本部分从早期版本迁移 App Center。

扩展中的故障报告

App Center 支持 macOS 扩展中的崩溃报告。 用法与容器应用程序中的用法相同。

生成测试崩溃

App Center 崩溃提供了一个 API,用于生成测试崩溃,以便轻松测试 SDK。 此 API 只能在测试/beta 应用中使用,不会在生产应用中执行任何操作。

[MSACCrashes generateTestCrash];
Crashes.generateTestCrash()

获取有关上一次故障的详细信息

App Center 崩溃有两个 API,可在应用崩溃时为你提供详细信息。

应用在上一个会话中是否收到内存不足警告?

启动 SDK 后,可以随时检查应用是否在上一个会话中收到内存警告:

[MSACCrashes hasReceivedMemoryWarningInLastSession];
Crashes.hasReceivedMemoryWarningInLastSession

注意

此方法必须仅在启动后 Crashes 使用,它将始终返回 NO 或在 false 启动之前使用。

注意

在某些情况下,内存不足的设备无法发送事件。

应用在上一个会话中是否崩溃?

启动 SDK 后,可以随时检查应用是否在上一次启动中崩溃:

[MSACCrashes hasCrashedInLastSession];
Crashes.hasCrashedInLastSession

如果想要在崩溃后调整应用的行为或 UI,这将派上用场。

注意

此方法必须仅在启动后 MSACCrashes 使用,它将始终返回 NO 或在 false 启动之前使用。

有关上次崩溃的详细信息

如果你的应用以前崩溃,你可以获取有关上次崩溃的详细信息。

MSACErrorReport *crashReport = [MSACCrashes lastSessionCrashReport];
var crashReport = Crashes.lastSessionCrashReport

注意

此方法只能在启动后 Crashes 使用,它将始终在启动之前返回 nil

此 API 有许多用例,最常见的用例是调用此 API 并实现其自定义 CrashesDelegate 的人员。

自定义 App Center 崩溃的使用情况

App Center 崩溃为开发人员提供回调,以便在将崩溃日志发送到 App Center 之前和向 App Center 发送时执行其他操作。

若要添加自定义行为,需要采用 CrashesDelegate-protocol,其所有方法都是可选的。

注册为代理人

[MSACCrashes setDelegate:self];
Crashes.delegate = self

注意

必须在调用 AppCenter.start之前设置委托,因为 App Center 在启动后立即开始处理崩溃。

是否应处理崩溃?

crashes:shouldProcessErrorReport:如果要确定是否需要处理特定崩溃,在采用 CrashesDelegate-protocol 的类中实现 -method。 例如,可能存在你想要忽略的系统级崩溃,并且不希望发送到 App Center。

- (BOOL)crashes:(MSACCrashes *)crashes shouldProcessErrorReport:(MSACErrorReport *)errorReport {
  return YES; // return YES if the crash report should be processed, otherwise NO.
}
func crashes(_ crashes: Crashes, shouldProcess errorReport: ErrorReport) -> Bool {
  return true; // return true if the crash report should be processed, otherwise false.
}

处理的错误

App Center 还允许通过 方法使用已处理的异常 trackError 来跟踪错误。 应用可以选择将属性或/和附件附加到已处理的错误报告,以提供进一步的上下文。

@try {
  // Throw error.
} @catch (NSError *error) {

  // Init attachments.
  NSArray<MSACErrorAttachmentLog *> attachments = @[ MSACErrorAttachmentLog attachmentWithText:@"Hello world!" filename:@"hello.txt"] ]

  // Init properties.
  NSDictionary *properties = @{ "Category" : "Music", "Wifi" : "On" };

  // Track errors.
  [MSACCrashes trackError:error withProperties:properties attachments:attachments];
  [MSACCrashes trackError:error withProperties:properties attachments:nil];
  [MSACCrashes trackError:error withProperties:nil attachments:attachments];
  [MSACCrashes trackError:error withProperties:nil attachments:nil];
}
do {
  // Throw error.
} catch {

  // Init attachments.
  let attachments = [ErrorAttachmentLog.attachment(withText: "Hello world!", filename: "hello.txt")]

  // Init properties.
  let properties:Dictionary<String, String> = ["Category" : "Music", "Wifi" : "On"]

  // Track errors.
  Crashes.trackError(error, properties: properties, attachments: attachments)
  Crashes.trackError(error, properties: properties, attachments: nil)
  Crashes.trackError(error, properties: nil, attachments: attachments)
  Crashes.trackError(error, properties: nil, attachments: nil)
}

对于跟踪异常,可以使用 trackException 方法:

@try {
  // Throw exceptions.
} @catch (NSException *exception) {

  // Init exceptions.
  MSACExceptionModel *customException1 = [MSACExceptionModel initWithType:@"Custom exception" exceptionMessage:"Track custom exception.", stackTrace:exception.callStackSymbols];
  MSACExceptionModel *customException2 = [MSACExceptionModel initWithException:exception];

  // Track exceptions.
  [MSACCrashes trackException:customException1 withProperties:properties attachments:nil];
  [MSACCrashes trackException:customException2 withProperties:properties attachments:nil];
}
do {
  // Throw exception.
} catch {

  // Init exception.
  let exception = ExceptionModel(withType: "Custom exception", exceptionMessage: "Track custom exception.", stackTrace: Thread.callStackSymbols)

  // Track exception.
  Crashes.trackException(exception, properties: properties, attachments: nil)
}

如果用户隐私对你很重要,你可能希望在向 App Center 发送故障报告之前获得用户确认。 SDK 公开一个回调,告知 App Center 崩溃在发送任何崩溃报告之前等待用户确认。

如果选择这样做,则需负责获取用户的确认,例如通过对话框提示,其中包含以下选项之一: 始终发送发送不发送。 根据输入,你将告知 App Center 崩溃要执行的操作,然后会相应地处理崩溃。

注意

SDK 不为此显示对话框,应用必须提供自己的 UI 才能请求用户同意。

以下方法演示如何设置用户确认处理程序:

[MSACCrashes setUserConfirmationHandler:(^(NSArray<MSACErrorReport *> *errorReports) {

  // Your code to present your UI to the user, e.g. an NSAlert.
  NSAlert *alert = [[NSAlert alloc] init];
  [alert setMessageText:@"Sorry about that!"];
  [alert setInformativeText:@"Do you want to send an anonymous crash report so we can fix the issue?"];
  [alert addButtonWithTitle:@"Always send"];
  [alert addButtonWithTitle:@"Send"];
  [alert addButtonWithTitle:@"Don't send"];
  [alert setAlertStyle:NSWarningAlertStyle];

  switch ([alert runModal]) {
  case NSAlertFirstButtonReturn:
    [MSACCrashes notifyWithUserConfirmation:MSACUserConfirmationAlways];
    break;
  case NSAlertSecondButtonReturn:
    [MSACCrashes notifyWithUserConfirmation:MSACUserConfirmationSend];
    break;
  case NSAlertThirdButtonReturn:
    [MSACCrashes notifyWithUserConfirmation:MSACUserConfirmationDontSend];
    break;
  default:
    break;
  }

  return YES; // Return YES if the SDK should await user confirmation, otherwise NO.
})];
Crashes.setUserConfirmationHandler({ (errorReports: [ErrorReport]) in

  // Your code to present your UI to the user, e.g. an NSAlert.
  let alert: NSAlert = NSAlert()
  alert.messageText = "Sorry about that!"
  alert.informativeText = "Do you want to send an anonymous crash report so we can fix the issue?"
  alert.addButton(withTitle: "Always send")
  alert.addButton(withTitle: "Send")
  alert.addButton(withTitle: "Don't send")
  alert.alertStyle = NSWarningAlertStyle

  switch (alert.runModal()) {
  case NSAlertFirstButtonReturn:
    Crashes.notify(with: .always)
    break;
  case NSAlertSecondButtonReturn:
    Crashes.notify(with: .send)
    break;
  case NSAlertThirdButtonReturn:
    Crashes.notify(with: .dontSend)
    break;
  default:
    break;
  }

  return true // Return true if the SDK should await user confirmation, otherwise return false.
})

如果你在上面的处理程序块中返回 YES/true ,你的应用应获取用户权限,并使用以下 API 向 SDK 发送结果消息。 如果为此使用警报,正如我们在上面的示例中所做的那样,可以根据调用结果 (ModalResponse) runModal 调用它。

// Depending on the user's choice, call notifyWithUserConfirmation: with the right value.
[MSACCrashes notifyWithUserConfirmation:MSACUserConfirmationDontSend];
[MSACCrashes notifyWithUserConfirmation:MSACUserConfirmationSend];
[MSACCrashes notifyWithUserConfirmation:MSACUserConfirmationAlways];
// Depending on the user's choice, call notify(with:) with the right value.
Crashes.notify(with: .dontSend)
Crashes.notify(with: .send)
Crashes.notify(with: .always)

启用捕获在main线程上引发的未捕获异常

AppKit 捕获main线程上引发的异常,防止应用程序在 macOS 上崩溃,因此 SDK 无法捕获这些故障。 若要模拟 iOS 行为,请在 SDK 初始化之前设置 NSApplicationCrashOnExceptions 标志,此标志允许应用程序在未捕获的异常时崩溃,并且 SDK 可以报告这些异常。

[[NSUserDefaults standardUserDefaults] registerDefaults:@{ @"NSApplicationCrashOnExceptions" : @YES }];
UserDefaults.standard.register(defaults: ["NSApplicationCrashOnExceptions": true])

注意

App Center SDK 在 1.10.0 及更低版本中自动设置标志。 从版本 1.11.0 开始,不再自动设置此标志。

禁止将应用程序main类的方法调用转发到 App Center 崩溃

App Center 崩溃 SDK 使用重排来改进其集成,方法是将某些应用程序转发main类的方法调用。 方法重排是在运行时更改方法实现的一种方法。 如果出于任何原因不想使用重排 ((例如,由于特定的策略) ),则应自行替代应用程序的 reportException:sendEvent: 方法,以便崩溃正确报告main线程上引发的异常。

  1. 创建 ReportExceptionApplication.m 文件并添加以下实现:

    @import Cocoa;
    @import AppCenterCrashes;
    
    @interface ReportExceptionApplication : NSApplication
    @end
    
    @implementation ReportExceptionApplication
    
    - (void)reportException:(NSException *)exception {
      [MSACCrashes applicationDidReportException:exception];
      [super reportException:exception];
    }
    
    - (void)sendEvent:(NSEvent *)theEvent {
      @try {
        [super sendEvent:theEvent];
      } @catch (NSException *exception) {
        [self reportException:exception];
      }
    }
    
    @end
    

    注意

    Swift 的 try/catch 不适用于 。NSException 这些异常只能在 Objective-C 中处理。

  2. 打开 Info.plist 文件,将 Principal 类字段中的 NSApplication 替换为本示例中的应用程序类名称 ReportExceptionApplication

  3. 若要在 App Center SDK 中禁用重排,请将 AppCenterApplicationForwarderEnabled 密钥添加到 Info.plist 文件,并将值设置为 0

获取有关故障日志的发送状态的信息

有时,你想知道应用崩溃的状态。 一个常见用例是,你可能想要显示 UI,告知用户你的应用正在提交崩溃报告,或者,如果你的应用在启动后快速崩溃,你希望调整应用的行为以确保可以提交崩溃日志。 CrashesDelegate-protocol 定义了三个不同的回调,你可以在应用中使用这些回调来接收所发生情况的通知:

在 SDK 发送崩溃日志之前,将调用以下回调

- (void)crashes:(MSACCrashes *)crashes willSendErrorReport:(MSACErrorReport *)errorReport {
  // Your code, e.g. to present a custom UI.
}
func crashes(_ crashes: Crashes, willSend errorReport: ErrorReport) {
  // Your code, e.g. to present a custom UI.
}

如果我们在终结点上遇到网络问题或中断,并且你重新启动应用, willSendErrorReport 则会在进程重启后再次触发。

SDK 成功发送崩溃日志后,将调用以下回调

- (void)crashes:(MSACCrashes *)crashes didSucceedSendingErrorReport:(MSACErrorReport *)errorReport {
  // Your code, e.g. to hide the custom UI.
}
func crashes(_ crashes: Crashes, didSucceedSending errorReport: ErrorReport) {
  // Your code goes here.
}

如果 SDK 无法发送故障日志,将调用以下回调

- (void)crashes:(MSACCrashes *)crashes didFailSendingErrorReport:(MSACErrorReport *)errorReport withError:(NSError *)error {
  // Your code goes here.
}
func crashes(_ crashes: Crashes, didFailSending errorReport: ErrorReport, withError error: Error) {
  // Your code goes here.
}

接收 didFailSendingErrorReport 意味着发生了不可恢复的错误,例如 4xx 代码。 例如, 401 表示 appSecret 错误。

如果这是网络问题,则不会触发此回调。 在这种情况下,SDK 会继续重试 (,并在网络连接关闭) 时暂停重试。

向崩溃报告添加附件

可以将二进制附件和文本附件添加到崩溃报告。 SDK 会连同崩溃一起发送它们,以便你可以在 App Center 门户中看到它们。 在从以前的应用程序启动时发送存储的故障之前,将立即调用以下回调。 发生崩溃时,不会调用它。 下面是如何将文本和图像附加到崩溃的示例:

- (NSArray<MSACErrorAttachmentLog *> *)attachmentsWithCrashes:(MSACCrashes *)crashes
                                             forErrorReport:(MSACErrorReport *)errorReport {
  MSACErrorAttachmentLog *attachment1 = [MSACErrorAttachmentLog attachmentWithText:@"Hello world!" filename:@"hello.txt"];
  MSACErrorAttachmentLog *attachment2 = [MSACErrorAttachmentLog attachmentWithBinary:[@"Fake image" dataUsingEncoding:NSUTF8StringEncoding] filename:@"fake_image.jpeg" contentType:@"image/jpeg"];
  return @[ attachment1, attachment2 ];
}
func attachments(with crashes: Crashes, for errorReport: ErrorReport) -> [ErrorAttachmentLog]? {
  let attachment1 = ErrorAttachmentLog.attachment(withText: "Hello world!", filename: "hello.txt")
  let attachment2 = ErrorAttachmentLog.attachment(withBinary: "Fake image".data(using: String.Encoding.utf8), filename: nil, contentType: "image/jpeg")
  return [attachment1!, attachment2!]
}

注意

大小限制当前为 7 MB。 尝试发送较大的附件将触发错误。

在运行时启用或禁用 App Center 崩溃

可以在运行时启用和禁用 App Center 崩溃。 如果禁用它,SDK 不会为应用执行任何崩溃报告。

[MSACCrashes setEnabled:NO];
Crashes.enabled = false

若要再次启用 App Center 崩溃,请使用同一 API,但作为参数传递 YES/true

[MSACCrashes setEnabled:YES];
Crashes.enabled = true

状态在应用程序启动时保留在设备的存储中。

注意

只有在启动后 Crashes 才能使用此方法。

检查是否启用了 App Center 崩溃

还可以检查是否启用 App Center 崩溃:

BOOL enabled = [MSACCrashes isEnabled];
var enabled = Crashes.enabled

注意

此方法只能在启动后 Crashes 使用,它始终在启动前返回 false

禁用 Mach 异常处理

默认情况下,App Center 崩溃使用 Mach 异常处理程序通过 Mach 异常服务器捕获致命信号,例如堆栈溢出。

disableMachExceptionHandler-方法提供了一个选项,用于禁用通过 Mach 异常服务器捕获致命信号。 如果要禁用 Mach 异常处理程序,则应在启动 SDK 之前 调用此方法。 典型的设置代码如下所示:

[MSACCrashes disableMachExceptionHandler];
[MSACAppCenter start:@"{Your App Secret}" withServices:@[[MSACAnalytics class], [MSACCrashes class]]];
Crashes.disableMachExceptionHandler()
AppCenter.start(withAppSecret: "{Your App Secret}", services: [Analytics.self, Crashes.self])