Firebase Crashlytics MacOS - AppKit 下的所有崩溃

Firebase Crashlytics MacOS - all crashes under AppKit

我有一个 iOS 应用程序移植到 MacOS。该应用程序使用 Firebase for Crashlytics。到目前为止,我通过为该目标创建一个单独的 Mac 目标和单独的 Firebase 项目,成功地配置了一切。问题是我在 MacOS 项目的控制台中看到的崩溃都在“AppKit”下。示例:

AppKit | -[NSApplication _crashOnException:] + 106

信息量不大,是吗...现在,如果我检查崩溃然后转到 'Keys':

,我仍然可以得到崩溃异常
crash_info_entry_0 | Crashing on exception: *** -[__NSCFCalendar rangeOfUnit:startDate:interval:forDate:]: date cannot be nil

但是所有不同的崩溃都被归为 AppKit 崩溃的一部分,所以它不是很有帮助。

我意识到这个问题是由于 AppKit 的默认行为默认捕获 MacOS 上的所有异常。是否有更好的方法来为 MacOS 设置 Crashlytics,以便获得更详细的报告,例如在 iOS 和其他平台上?

经过大量研究,我发现没有完美的解决方案。我尝试覆盖 NSApplication 并将其设置为 NSPrincipalClass,甚至改为实施 Sentry - 没有成功。但是我找到了一种使用 method swizzling 和 FIRExceptionModel 绕过 AppKit 的方法。

注意:首先,要使 Firebase Crashlytics 在 MacOS 上运行,您需要在 AppDelegate 的 didFinishLaunchingWithOptions 中添加以下内容:

NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
[userDefaults registerDefaults:@{@"NSApplicationCrashOnExceptions" : @"YES"}];

然后你需要创建一个 NSApplication 的类别并 swizzle 方法 _crashOnException:

#import <objc/runtime.h>
#import "NSApplication+CrashReport.h"
#import <FIRCrashlytics.h>

@implementation NSApplication (CrashReport)

+(void)load {
    static dispatch_once_t once_token;
    dispatch_once(&once_token,  ^{
        SEL crashOnExceptionSelector = @selector(_crashOnException:); // Ignore 'Undeclared selector' warning.
        SEL crashOnExceptionReporterSelector = @selector(reported__crashOnException:);
        Method originalMethod = class_getInstanceMethod(self, crashOnExceptionSelector);
        Method extendedMethod = class_getInstanceMethod(self, crashOnExceptionReporterSelector);
        method_exchangeImplementations(originalMethod, extendedMethod);
    });
}

- (void)reported__crashOnException:(NSException*)exception {
    NSArray<NSString*> *stacktrace = [exception callStackSymbols];
    [[FIRCrashlytics crashlytics]setCustomValue:stacktrace forKey:@"mac_os_stacktrace"];
    FIRExceptionModel *errorModel = [FIRExceptionModel exceptionModelWithName:exception.name reason:exception.reason];
    // The below stacktrace is hardcoded as an example, in an actual solution you should parse the stacktrace array entries.
    errorModel.stackTrace = @[
        [FIRStackFrame stackFrameWithSymbol:@"This stacktrace is fabricated as a proof of concept" file:@"Hello from Serge" line:2021],
        [FIRStackFrame stackFrameWithSymbol:@"__exceptionPreprocess" file:@"CoreFoundation" line:250],
        [FIRStackFrame stackFrameWithSymbol:@"objc_exception_throw" file:@"libobjc.A.dylib" line:48],
        [FIRStackFrame stackFrameWithSymbol:@"-[__NSCFCalendar rangeOfUnit:startDate:interval:forDate:]" file:@"CoreFoundation" line:453]
    ];
    // Note: ExceptionModel will always be reported as a non-fatal.
    [[FIRCrashlytics crashlytics] recordExceptionModel:errorModel];
    [self reported__crashOnException:exception];
}

@end

此代码作为要点:https://gist.github.com/sc941737/c0c4542401ce203142c93ddc9b05eb1f

但这意味着异常不会被报告为崩溃,而是非致命的。所以我建议设置一个额外的自定义键来更容易地过滤非致命崩溃。有关详细信息,请参阅 Firebase 文档:https://firebase.google.com/docs/crashlytics/customize-crash-reports?platform=ios