Skip to content

Commit

Permalink
Add reports-only set up API (#548)
Browse files Browse the repository at this point in the history
* Add reports-only install

* Change API and allow repeating reports setup

* Fix format

* Fix pod lint

* Prevent reports store setup after install

* Add docs

* Fix format
  • Loading branch information
bamx23 committed Aug 28, 2024
1 parent 29e574a commit 33feb0a
Show file tree
Hide file tree
Showing 11 changed files with 158 additions and 35 deletions.
16 changes: 16 additions & 0 deletions Samples/Common/Sources/LibraryBridge/InstallBridge.swift
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ public class InstallBridge: ObservableObject {

@Published public var basePath: BasePath = .default
@Published public var installed: Bool = false
@Published public var reportsOnlySetup: Bool = false
@Published public var error: InstallationError?

public init() {
Expand Down Expand Up @@ -97,6 +98,21 @@ public class InstallBridge: ObservableObject {
self.error = .unexpectedError(message)
}
}

public func setupReportsOnly() {
do {
try KSCrash.shared.setupReportStore(withPath: config.installPath)
reportsOnlySetup = true
} catch let error as KSCrashInstallError {
let message = error.localizedDescription
Self.logger.error("Failed to install KSCrash: \(message)")
self.error = .kscrashError(message)
} catch {
let message = error.localizedDescription
Self.logger.error("Unexpected error during KSCrash installation: \(message)")
self.error = .unexpectedError(message)
}
}
}

// An utility method to simplify binding of config fields
Expand Down
9 changes: 3 additions & 6 deletions Samples/Common/Sources/SampleUI/SampleView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,19 +34,16 @@ public struct SampleView: View {

@ObservedObject var installBridge = InstallBridge()

@State private var installSkipped = false

public var body: some View {
NavigationView {
if installBridge.installed || installSkipped {
if installBridge.installed || installBridge.reportsOnlySetup {
MainView(
installSkipped: $installSkipped
reportsOnlySetup: $installBridge.reportsOnlySetup
)
.navigationTitle("KSCrash Sample")
} else {
InstallView(
bridge: installBridge,
installSkipped: $installSkipped
bridge: installBridge
)
.navigationTitle("Install KSCrash")
}
Expand Down
6 changes: 2 additions & 4 deletions Samples/Common/Sources/SampleUI/Screens/InstallView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,6 @@ import LibraryBridge
struct InstallView: View {
@ObservedObject var bridge: InstallBridge

@Binding var installSkipped: Bool

@State private var showingInstallAlert = false

var body: some View {
Expand Down Expand Up @@ -81,8 +79,8 @@ struct InstallView: View {
// TODO: Add deleteBehaviorAfterSendAll
}

Button("Skip install") {
installSkipped = true
Button("Only set up reports") {
bridge.setupReportsOnly()
}
.foregroundStyle(Color.red)
}
Expand Down
8 changes: 5 additions & 3 deletions Samples/Common/Sources/SampleUI/Screens/MainView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,16 @@ import SwiftUI

struct MainView: View {

@Binding var installSkipped: Bool
@Binding var reportsOnlySetup: Bool

var body: some View {
List {
Section {
if installSkipped {
if 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") {
installSkipped = false
reportsOnlySetup = false
}
} else {
Text("KSCrash is installed successfully")
Expand Down
15 changes: 15 additions & 0 deletions Sources/KSCrashRecording/KSCrash.m
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,21 @@ - (BOOL)installWithConfiguration:(KSCrashConfiguration *)configuration error:(NS
return YES;
}

- (BOOL)setupReportStoreWithPath:(NSString *)installPath error:(NSError **)error
{
KSCrashInstallErrorCode result =
kscrash_setupReportsStore(self.bundleName.UTF8String, (installPath ?: getDefaultInstallPath()).UTF8String);

if (result != KSCrashInstallErrorNone) {
if (error != NULL) {
*error = [self errorForInstallErrorCode:result];
}
return NO;
}

return YES;
}

- (void)sendAllReportsWithCompletion:(KSCrashReportFilterCompletion)onCompletion
{
NSArray *reports = [self allReports];
Expand Down
56 changes: 44 additions & 12 deletions Sources/KSCrashRecording/KSCrashC.c
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,21 @@ void handleConfiguration(KSCrashCConfiguration *configuration)
#pragma mark - API -
// ============================================================================

static KSCrashInstallErrorCode setupReportsStore(const char *appName, const char *const installPath)
{
char path[KSFU_MAX_PATH_LENGTH];
if (snprintf(path, sizeof(path), "%s/Reports", installPath) >= (int)sizeof(path)) {
KSLOG_ERROR("Reports path is too long.");
return KSCrashInstallErrorPathTooLong;
}
if (ksfu_makePath(path) == false) {
KSLOG_ERROR("Could not create path: %s", path);
return KSCrashInstallErrorCouldNotCreatePath;
}
kscrs_initialize(appName, path);
return KSCrashInstallErrorNone;
}

KSCrashInstallErrorCode kscrash_install(const char *appName, const char *const installPath,
KSCrashCConfiguration configuration)
{
Expand All @@ -226,19 +241,14 @@ KSCrashInstallErrorCode kscrash_install(const char *appName, const char *const i

handleConfiguration(&configuration);

char path[KSFU_MAX_PATH_LENGTH];
if (snprintf(path, sizeof(path), "%s/Reports", installPath) >= (int)sizeof(path)) {
KSLOG_ERROR("Path too long.");
return KSCrashInstallErrorPathTooLong;
}
if (ksfu_makePath(path) == false) {
KSLOG_ERROR("Could not create path: %s", path);
return KSCrashInstallErrorCouldNotCreatePath;
KSCrashInstallErrorCode result = setupReportsStore(appName, installPath);
if (result != KSCrashInstallErrorNone) {
return result;
}
kscrs_initialize(appName, installPath, path);

char path[KSFU_MAX_PATH_LENGTH];
if (snprintf(path, sizeof(path), "%s/Data", installPath) >= (int)sizeof(path)) {
KSLOG_ERROR("Path too long.");
KSLOG_ERROR("Data path is too long.");
return KSCrashInstallErrorPathTooLong;
}
if (ksfu_makePath(path) == false) {
Expand All @@ -248,14 +258,14 @@ KSCrashInstallErrorCode kscrash_install(const char *appName, const char *const i
ksmemory_initialize(path);

if (snprintf(path, sizeof(path), "%s/Data/CrashState.json", installPath) >= (int)sizeof(path)) {
KSLOG_ERROR("Path too long.");
KSLOG_ERROR("Crash state path is too long.");
return KSCrashInstallErrorPathTooLong;
}
kscrashstate_initialize(path);

if (snprintf(g_consoleLogPath, sizeof(g_consoleLogPath), "%s/Data/ConsoleLog.txt", installPath) >=
(int)sizeof(g_consoleLogPath)) {
KSLOG_ERROR("Console log path too long.");
KSLOG_ERROR("Console log path is too long.");
return KSCrashInstallErrorPathTooLong;
}
if (g_shouldPrintPreviousLog) {
Expand All @@ -279,6 +289,28 @@ KSCrashInstallErrorCode kscrash_install(const char *appName, const char *const i
return KSCrashInstallErrorNone;
}

KSCrashInstallErrorCode kscrash_setupReportsStore(const char *appName, const char *const installPath)
{
KSLOG_DEBUG("Installing reports store.");

if (g_installed) {
KSLOG_DEBUG("Crash reporter is already installed and it's not allowed to set up reports store.");
return KSCrashInstallErrorAlreadyInstalled;
}

if (appName == NULL || installPath == NULL) {
KSLOG_ERROR("Invalid parameters: appName or installPath is NULL.");
return KSCrashInstallErrorInvalidParameter;
}

KSCrashInstallErrorCode result = setupReportsStore(appName, installPath);
if (result != KSCrashInstallErrorNone) {
return result;
}

return KSCrashInstallErrorNone;
}

void kscrash_setUserInfoJSON(const char *const userInfoJSON) { kscrashreport_setUserInfoJSON(userInfoJSON); }

const char *kscrash_getUserInfoJSON(void) { return kscrashreport_getUserInfoJSON(); }
Expand Down
37 changes: 32 additions & 5 deletions Sources/KSCrashRecording/KSCrashReportStore.c
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@

#include "KSCrashReportStore.h"

#include <assert.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
Expand All @@ -45,7 +46,6 @@ static _Atomic(uint32_t) g_nextUniqueIDLow;
static int64_t g_nextUniqueIDHigh;
static const char *g_appName;
static const char *g_reportsPath;
static const char *g_installPath;
static pthread_mutex_t g_mutex = PTHREAD_MUTEX_INITIALIZER;

static int compareInt64(const void *a, const void *b)
Expand All @@ -63,6 +63,7 @@ static inline int64_t getNextUniqueID(void) { return g_nextUniqueIDHigh + g_next

static void getCrashReportPathByID(int64_t id, char *pathBuffer)
{
assert(g_reportsPath != NULL);
snprintf(pathBuffer, KSCRS_MAX_PATH_LENGTH, "%s/%s-report-%016llx.json", g_reportsPath, g_appName, id);
}

Expand All @@ -78,6 +79,11 @@ static int64_t getReportIDFromFilename(const char *filename)

static int getReportCount(void)
{
if (g_reportsPath == NULL) {
KSLOG_ERROR("Reports store is not set up");
return 0;
}

int count = 0;
DIR *dir = opendir(g_reportsPath);
if (dir == NULL) {
Expand All @@ -100,6 +106,11 @@ static int getReportCount(void)

static int getReportIDs(int64_t *reportIDs, int count)
{
if (g_reportsPath == NULL) {
KSLOG_ERROR("Reports store is not set up");
return 0;
}

int index = 0;
DIR *dir = opendir(g_reportsPath);
if (dir == NULL) {
Expand Down Expand Up @@ -157,17 +168,29 @@ static void initializeIDs(void)

// Public API

void kscrs_initialize(const char *appName, const char *installPath, const char *reportsPath)
void kscrs_initialize(const char *appName, const char *reportsPath)
{
const char *previousAppName = NULL;
const char *previousReportsPath = NULL;

pthread_mutex_lock(&g_mutex);
previousAppName = g_appName;
previousReportsPath = g_reportsPath;
g_appName = strdup(appName);
g_installPath = strdup(installPath);
ksfu_makePath(installPath);
g_reportsPath = strdup(reportsPath);
ksfu_makePath(reportsPath);
pruneReports();
initializeIDs();
pthread_mutex_unlock(&g_mutex);

if (previousAppName) {
KSLOG_WARN("Reports app name is changed from '%s' to '%s'", previousAppName, appName);
free((void *)previousAppName);
}
if (previousReportsPath) {
KSLOG_WARN("Reports path is changed from '%s' to '%s'", previousReportsPath, reportsPath);
free((void *)previousReportsPath);
}
}

int64_t kscrs_getNextCrashReport(char *crashReportPathBuffer)
Expand Down Expand Up @@ -249,7 +272,11 @@ int64_t kscrs_addUserReport(const char *report, int reportLength)
void kscrs_deleteAllReports(void)
{
pthread_mutex_lock(&g_mutex);
ksfu_deleteContentsOfPath(g_reportsPath);
if (g_reportsPath != NULL) {
ksfu_deleteContentsOfPath(g_reportsPath);
} else {
KSLOG_WARN("Reports store is not set up");
}
pthread_mutex_unlock(&g_mutex);
}

Expand Down
3 changes: 1 addition & 2 deletions Sources/KSCrashRecording/KSCrashReportStore.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,9 @@ extern "C" {
/** Initialize the report store.
*
* @param appName The application's name.
* @param installPath Full path to directory where the crash system writes files.
* @param reportsPath Full path to directory where the reports are to be stored (path will be created if needed).
*/
void kscrs_initialize(const char *appName, const char *installPath, const char *reportsPath);
void kscrs_initialize(const char *appName, const char *reportsPath);

/** Get the next crash report to be generated.
* Max length for paths is KSCRS_MAX_PATH_LENGTH
Expand Down
27 changes: 25 additions & 2 deletions Sources/KSCrashRecording/include/KSCrash.h
Original file line number Diff line number Diff line change
Expand Up @@ -127,13 +127,28 @@ NS_ASSUME_NONNULL_BEGIN
*/
- (BOOL)installWithConfiguration:(KSCrashConfiguration *)configuration error:(NSError **)error;

/** Sets up the crash repors store.
* A call of this method is required before working with crash reports.
* The `installWithConfiguration:error:` method sets up the crash report store.
* You only need to call this method if you are not using the `installWithConfiguration:error:` method
* or want to read crash reports from a custom location.
*
* @note This method can be called multiple times, but only before `installWithConfiguration:error:` is called.
*
* @param installPath The path to the directory where the crash reports are stored. If `nil`, the default path is used.
* @param error A pointer to an NSError object. If an error occurs, this pointer is set to an actual error object.
* @return YES if the crash report store was successfully set up, NO otherwise.
*/
- (BOOL)setupReportStoreWithPath:(nullable NSString *)installPath error:(NSError **)error;

/** Send all outstanding crash reports to the current sink.
* It will only attempt to send the most recent 5 reports. All others will be
* deleted. Once the reports are successfully sent to the server, they may be
* deleted locally, depending on the property "deleteAfterSendAll".
*
* Note: property "sink" MUST be set or else this method will call onCompletion
* with an error.
* @note A call of `setupReportStoreWithPath:error:` or `installWithConfiguration:error:` is required
* before working with crash reports.
* @note Property "sink" MUST be set or else this method will call `onCompletion` with an error.
*
* @param onCompletion Called when sending is complete (nil = ignore).
*/
Expand All @@ -143,6 +158,9 @@ NS_ASSUME_NONNULL_BEGIN
@property(nonatomic, readonly, strong) NSArray<NSNumber *> *reportIDs;

/** Get report.
*
* @note A call of `setupReportStoreWithPath:error:` or `installWithConfiguration:error:` is required
* before working with crash reports.
*
* @param reportID An ID of report.
*
Expand All @@ -151,10 +169,15 @@ NS_ASSUME_NONNULL_BEGIN
- (nullable KSCrashReportDictionary *)reportForID:(int64_t)reportID NS_SWIFT_NAME(report(for:));

/** Delete all unsent reports.
* @note A call of `setupReportStoreWithPath:error:` or `installWithConfiguration:error:` is required
* before working with crash reports.
*/
- (void)deleteAllReports;

/** Delete report.
*
* @note A call of `setupReportStoreWithPath:error:` or `installWithConfiguration:error:` is required
* before working with crash reports.
*
* @param reportID An ID of report to delete.
*/
Expand Down
14 changes: 14 additions & 0 deletions Sources/KSCrashRecording/include/KSCrashC.h
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,20 @@ extern "C" {
KSCrashInstallErrorCode kscrash_install(const char *appName, const char *const installPath,
KSCrashCConfiguration configuration);

/** Sets up the crash repors store.
* This function is used to initialize the storage for crash reports.
* The `kscrash_install` function sets up the reports store internally.
* You only need to call this function if you are not using the `kscrash_install` function
* or want to read crash reports from a custom location.
*
* @note this function can be called multiple times, but only before `kscrash_install` is called.
*
* @param appName The name of the application. Usually it's bundle name.
* @param installPath The directory where the crash reports and related data will be stored.
* @return KSCrashInstallErrorCode indicating the result of the setup.
*/
KSCrashInstallErrorCode kscrash_setupReportsStore(const char *appName, const char *const installPath);

/** Set the user-supplied data in JSON format.
*
* @param userInfoJSON Pre-baked JSON containing user-supplied information.
Expand Down
Loading

0 comments on commit 33feb0a

Please sign in to comment.