Skip to content

Commit

Permalink
Extract report store API (#549)
Browse files Browse the repository at this point in the history
* Rename C API report store

* Move reporting to a separate class

* Fix unit tests

* Fix format

* Fix linter issues

* Update C API for report store

* Fix tests

* Fix linter issues

* Fix wrong "c"

* Update docs

* Introduce store configuration

* Fix config memory leak

* Fix sample

* Fix default report store installation

* Add default reports subfolder to API

* Fix linter

* Fix delete behavior of installation

* Add docs

* Fix order of `installation.addConditionalAlert` in README

* Address code review
  • Loading branch information
bamx23 committed Sep 3, 2024
1 parent 9629343 commit e680dd9
Show file tree
Hide file tree
Showing 29 changed files with 916 additions and 630 deletions.
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,11 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
let installation = CrashInstallationStandard.shared
installation.url = URL(string: "http://put.your.url.here")!
// Install the crash reporting system
let config = KSCrashConfiguration()
config.monitors = [.machException, .signal]
installation.install(with: config) // set `nil` for default config
// Optional: Add an alert confirmation (recommended for email installation)
installation.addConditionalAlert(
withTitle: "Crash Detected",
Expand All @@ -139,11 +144,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
noAnswer: "No thanks"
)
// Install the crash reporting system
let config = KSCrashConfiguration()
config.monitors = [.machException, .signal]
installation.install(with: config) // set `nil` for default config
return true
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,14 @@ public struct ReportConfig: Codable {
extension ReportConfig {
func report() {
let url = URL(fileURLWithPath: directoryPath)
KSCrash.shared.sink = CrashReportFilterPipeline(filtersArray: [
guard let store = KSCrash.shared.reportStore else {
return
}
store.sink = CrashReportFilterPipeline(filtersArray: [
CrashReportFilterAppleFmt(),
DirectorySink(url),
])
KSCrash.shared.sendAllReports()
store.sendAllReports()
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// ReportingSample.swift
// CrashReportStore+Bridge.swift
//
// Created by Nikolay Volosatov on 2024-06-23.
//
Expand Down Expand Up @@ -31,12 +31,25 @@ import KSCrashSinks
import KSCrashDemangleFilter
import Logging

public class ReportingSample {
public extension CrashReportStore {
private static let logger = Logger(label: "ReportingSample")

public static func logToConsole() {
KSCrash.shared.sink = CrashReportSinkConsole.filter().defaultCrashReportFilterSet()
KSCrash.shared.sendAllReports { reports, isSuccess, error in
func logAll() async throws {
let (reports, isSuccess) = try await sendAllReports()
guard isSuccess else {
return
}
for report in reports {
guard let report = report as? CrashReportDictionary else {
continue
}
Self.logger.info("\(report.value)")
}
}

func logToConsole() {
sink = CrashReportSinkConsole.filter().defaultCrashReportFilterSet()
sendAllReports { reports, isSuccess, error in
if isSuccess, let reports {
Self.logger.info("Logged \(reports.count) reports")
for (idx, report) in reports.enumerated() {
Expand All @@ -57,13 +70,13 @@ public class ReportingSample {
}
}

public static func sampleLogToConsole() {
KSCrash.shared.sink = CrashReportFilterPipeline(filtersArray: [
func sampleLogToConsole() {
sink = CrashReportFilterPipeline(filtersArray: [
CrashReportFilterDemangle(),
SampleFilter(),
SampleSink(),
])
KSCrash.shared.sendAllReports()
sendAllReports()
}
}

Expand Down
7 changes: 6 additions & 1 deletion Samples/Common/Sources/LibraryBridge/InstallBridge.swift
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ public class InstallBridge: ObservableObject {
@Published public var reportsOnlySetup: Bool = false
@Published public var error: InstallationError?

@Published public var reportStore: CrashReportStore?

public init() {
config = .init()

Expand All @@ -87,6 +89,7 @@ public class InstallBridge: ObservableObject {

do {
try KSCrash.shared.install(with: config)
reportStore = KSCrash.shared.reportStore
installed = true
} catch let error as KSCrashInstallError {
let message = error.localizedDescription
Expand All @@ -101,7 +104,9 @@ public class InstallBridge: ObservableObject {

public func setupReportsOnly() {
do {
try KSCrash.shared.setupReportStore(withPath: config.installPath)
let config = CrashReportStoreConfiguration()
config.reportsPath = self.config.installPath.map { $0 + "/" + CrashReportStore.defaultInstallSubfolder }
reportStore = try CrashReportStore(configuration: config)
reportsOnlySetup = true
} catch let error as KSCrashInstallError {
let message = error.localizedDescription
Expand Down
2 changes: 1 addition & 1 deletion Samples/Common/Sources/SampleUI/SampleView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public struct SampleView: View {
NavigationView {
if installBridge.installed || installBridge.reportsOnlySetup {
MainView(
reportsOnlySetup: $installBridge.reportsOnlySetup
bridge: installBridge
)
.navigationTitle("KSCrash Sample")
} else {
Expand Down
1 change: 0 additions & 1 deletion Samples/Common/Sources/SampleUI/Screens/InstallView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,6 @@ struct InstallView: View {
Toggle(isOn: bridge.configBinding(for: \.enableSwapCxaThrow)) {
Text("Swap __cxa_throw")
}
// TODO: Add deleteBehaviorAfterSendAll
}

Button("Only set up reports") {
Expand Down
13 changes: 9 additions & 4 deletions Samples/Common/Sources/SampleUI/Screens/MainView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,20 @@

import Foundation
import SwiftUI
import LibraryBridge

struct MainView: View {

@Binding var reportsOnlySetup: Bool
@ObservedObject var bridge: InstallBridge

var body: some View {
List {
Section {
if reportsOnlySetup {
if bridge.reportsOnlySetup {
Text("It's only reporting that was set up. Crashes won't be caught. You can go back to the install screen.")
.foregroundStyle(Color.secondary)
Button("Back to Install") {
reportsOnlySetup = false
bridge.reportsOnlySetup = false
}
} else {
Text("KSCrash is installed successfully")
Expand All @@ -47,7 +48,11 @@ struct MainView: View {
}

NavigationLink("Crash", destination: CrashView())
NavigationLink("Report", destination: ReportingView())
if let store = bridge.reportStore {
NavigationLink("Report", destination: ReportingView(store: store))
} else {
Text("Reporting is not available")
}
}
}
}
7 changes: 5 additions & 2 deletions Samples/Common/Sources/SampleUI/Screens/ReportingView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,18 @@

import SwiftUI
import LibraryBridge
import KSCrashRecording

struct ReportingView: View {
let store: CrashReportStore

var body: some View {
List {
Button("Log To Console") {
ReportingSample.logToConsole()
store.logToConsole()
}
Button("Sample Custom Log To Console") {
ReportingSample.sampleLogToConsole()
store.sampleLogToConsole()
}
}
.navigationTitle("Report")
Expand Down
2 changes: 2 additions & 0 deletions Sources/KSCrashDemangleFilter/KSDemangle_CPP.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ extern "C" {
#endif

/** Demangle a C++ symbol.
*
* @warning MEMORY MANAGEMENT WARNING: User is responsible for calling free() on the returned value.
*
* @param mangledSymbol The mangled symbol.
*
Expand Down
3 changes: 2 additions & 1 deletion Sources/KSCrashDemangleFilter/KSDemangle_Swift.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,12 @@ extern "C" {
#endif

/** Demangle a Swift symbol.
*
* @warning MEMORY MANAGEMENT WARNING: User is responsible for calling free() on the returned value.
*
* @param mangledSymbol The mangled symbol.
*
* @return A demangled symbol, or NULL if demangling failed.
* MEMORY MANAGEMENT WARNING: User is responsible for calling free() on the returned value.
*/
char *ksdm_demangleSwift(const char *mangledSymbol);

Expand Down
31 changes: 20 additions & 11 deletions Sources/KSCrashInstallations/KSCrashInstallation.m
Original file line number Diff line number Diff line change
Expand Up @@ -322,9 +322,19 @@ - (void)sendAllReportsWithCompletion:(KSCrashReportFilterCompletion)onCompletion
}
sink = [KSCrashReportFilterPipeline filterWithFiltersArray:sinkFilters];

KSCrash *handler = [KSCrash sharedInstance];
handler.sink = sink;
[handler sendAllReportsWithCompletion:onCompletion];
KSCrashReportStore *store = [KSCrash sharedInstance].reportStore;
if (store == nil) {
onCompletion(
nil, NO,
[KSNSErrorHelper
errorWithDomain:[[self class] description]
code:0
description:@"Reporting is not allowed before the call of `installWithConfiguration:error:`"]);
return;
}

store.sink = sink;
[store sendAllReportsWithCompletion:onCompletion];
}

- (void)addPreFilter:(id<KSCrashReportFilter>)filter
Expand All @@ -346,14 +356,13 @@ - (void)addConditionalAlertWithTitle:(NSString *)title
message:message
yesAnswer:yesAnswer
noAnswer:noAnswer]];
// FIXME: Accessing config
// KSCrash* handler = [KSCrash sharedInstance];
// if(handler.deleteBehaviorAfterSendAll == KSCDeleteOnSucess)
// {
// // Better to delete always, or else the user will keep getting nagged
// // until he presses "yes"!
// handler.deleteBehaviorAfterSendAll = KSCDeleteAlways;
// }

KSCrashReportStore *store = [KSCrash sharedInstance].reportStore;
if (store.reportCleanupPolicy == KSCrashReportCleanupPolicyOnSuccess) {
// Better to delete always, or else the user will keep getting nagged
// until he presses "yes"!
store.reportCleanupPolicy = KSCrashReportCleanupPolicyAlways;
}
}

- (void)addUnconditionalAlertWithTitle:(NSString *)title
Expand Down
18 changes: 18 additions & 0 deletions Sources/KSCrashRecording/KSCrash+Private.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,30 @@
#define KSCrash_Private_h

#import "KSCrash.h"
#import "KSCrashError.h"

NS_ASSUME_NONNULL_BEGIN

#ifdef __cplusplus
extern "C" {
#endif

NSString *kscrash_getBundleName(void);
NSString *kscrash_getDefaultInstallPath(void);

#ifdef __cplusplus
}
#endif

@interface KSCrash ()

@property(nonatomic, readwrite, assign) NSUncaughtExceptionHandler *uncaughtExceptionHandler;
@property(nonatomic, readwrite, assign) NSUncaughtExceptionHandler *currentSnapshotUserReportedExceptionHandler;

+ (NSError *)errorForInstallErrorCode:(KSCrashInstallErrorCode)errorCode;

@end

NS_ASSUME_NONNULL_END

#endif /* KSCrash_Private_h */
Loading

0 comments on commit e680dd9

Please sign in to comment.