diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a6f464cc85..4c92251da6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Features + +- Add option to choose Crash Report and offline Event directory (#1954) + ### Fixes - Properly sanitize the event context and SDK information (#1943) diff --git a/Sources/Sentry/Public/SentryOptions.h b/Sources/Sentry/Public/SentryOptions.h index 8094513eaad..1e7c0340836 100644 --- a/Sources/Sentry/Public/SentryOptions.h +++ b/Sources/Sentry/Public/SentryOptions.h @@ -335,6 +335,13 @@ NS_SWIFT_NAME(Options) */ @property (nonatomic, assign) NSTimeInterval appHangTimeoutInterval; +/** + * The cache directory path for caching offline events. + * If this property is nil the SDK uses the default user cache directory in the OS. + * If this path does not exists the SDK tries to create it with intermediate directories. + */ +@property (nonatomic, strong) NSString *cacheDirectoryPath; + @end NS_ASSUME_NONNULL_END diff --git a/Sources/Sentry/SentryCrashIntegration.m b/Sources/Sentry/SentryCrashIntegration.m index 3edbc72087f..7d284e63741 100644 --- a/Sources/Sentry/SentryCrashIntegration.m +++ b/Sources/Sentry/SentryCrashIntegration.m @@ -100,7 +100,13 @@ - (void)startCrashHandler canSendReports = YES; } - [installation install]; + NSString *reportPath = self.options.cacheDirectoryPath; + if (reportPath == nil) { + reportPath = [NSSearchPathForDirectoriesInDomains( + NSCachesDirectory, NSUserDomainMask, YES) firstObject]; + } + + [installation installWithReportPath:reportPath]; // We need to send the crashed event together with the crashed session in the same envelope // to have proper statistics in release health. To achieve this we need both synchronously diff --git a/Sources/Sentry/SentryFileManager.m b/Sources/Sentry/SentryFileManager.m index 44dddd8dcef..6116f4280e0 100644 --- a/Sources/Sentry/SentryFileManager.m +++ b/Sources/Sentry/SentryFileManager.m @@ -40,13 +40,17 @@ - (nullable instancetype)initWithOptions:(SentryOptions *)options self = [super init]; if (self) { self.currentDateProvider = currentDateProvider; - NSFileManager *fileManager = [NSFileManager defaultManager]; - NSString *cachePath - = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) - .firstObject; - self.sentryPath = [cachePath stringByAppendingPathComponent:@"io.sentry"]; + if (options.cacheDirectoryPath) { + self.sentryPath = options.cacheDirectoryPath; + } else { + self.sentryPath + = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) + .firstObject; + } + + self.sentryPath = [self.sentryPath stringByAppendingPathComponent:@"io.sentry"]; self.sentryPath = [self.sentryPath stringByAppendingPathComponent:[options.parsedDsn getHash]]; diff --git a/Sources/Sentry/SentryOptions.m b/Sources/Sentry/SentryOptions.m index 104a3b9afad..0c254b0c2b2 100644 --- a/Sources/Sentry/SentryOptions.m +++ b/Sources/Sentry/SentryOptions.m @@ -304,6 +304,10 @@ - (BOOL)validateOptions:(NSDictionary *)options _sdkInfo = [[SentrySdkInfo alloc] initWithDict:options orDefaults:_sdkInfo]; } + if ([options[@"cacheDirectoryPath"] isKindOfClass:[NSString class]]) { + self.cacheDirectoryPath = options[@"cacheDirectoryPath"]; + } + if (nil != error && nil != *error) { return NO; } else { diff --git a/Sources/SentryCrash/Installations/SentryCrashInstallation.h b/Sources/SentryCrash/Installations/SentryCrashInstallation.h index 98f786b9165..3ce6746d21b 100644 --- a/Sources/SentryCrash/Installations/SentryCrashInstallation.h +++ b/Sources/SentryCrash/Installations/SentryCrashInstallation.h @@ -48,7 +48,7 @@ /** Install this installation. Call this instead of -[SentryCrash install] to * install with everything needed for your particular backend. */ -- (void)install; +- (void)installWithReportPath:(NSString *)reportPath; /** Convenience method to call -[SentryCrash sendAllReportsWithCompletion:]. * This method will set the SentryCrash sink and then send all outstanding diff --git a/Sources/SentryCrash/Installations/SentryCrashInstallation.m b/Sources/SentryCrash/Installations/SentryCrashInstallation.m index 6bc479eeb20..979890bc49d 100644 --- a/Sources/SentryCrash/Installations/SentryCrashInstallation.m +++ b/Sources/SentryCrash/Installations/SentryCrashInstallation.m @@ -288,13 +288,13 @@ - (void)setOnCrash:(SentryCrashReportWriteCallback)onCrash } } -- (void)install +- (void)installWithReportPath:(NSString *)reportPath { SentryCrash *handler = [SentryCrash sharedInstance]; @synchronized(handler) { g_crashHandlerData = self.crashHandlerData; handler.onCrash = crashCallback; - [handler install]; + [handler installWithReportPath:reportPath]; } } diff --git a/Sources/SentryCrash/Recording/SentryCrash.h b/Sources/SentryCrash/Recording/SentryCrash.h index 95748d93d34..50237e6f98e 100644 --- a/Sources/SentryCrash/Recording/SentryCrash.h +++ b/Sources/SentryCrash/Recording/SentryCrash.h @@ -55,9 +55,6 @@ static NSString *const SENTRYCRASH_REPORT_SCREENSHOT_ITEM = @"screenshots"; #pragma mark - Configuration - -/** Init SentryCrash instance with custom base path. */ -- (id)initWithBasePath:(NSString *)basePath; - /** A dictionary containing any info you'd like to appear in crash reports. Must * contain only JSON-safe data: NSString for keys, and NSDictionary, NSArray, * NSString, NSDate, and NSNumber for values. @@ -209,6 +206,16 @@ static NSString *const SENTRYCRASH_REPORT_SCREENSHOT_ITEM = @"screenshots"; */ - (BOOL)install; +/** Install the crash reporter. + * The reporter will record crashes, but will not send any crash reports unless + * sink is set. + * + *@param reportPath A path to store crash reports. + * + * @return YES if the reporter successfully installed. + */ +- (BOOL)installWithReportPath:(NSString *)reportPath; + /** 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 diff --git a/Sources/SentryCrash/Recording/SentryCrash.m b/Sources/SentryCrash/Recording/SentryCrash.m index 61f2d503629..26ee74c5955 100644 --- a/Sources/SentryCrash/Recording/SentryCrash.m +++ b/Sources/SentryCrash/Recording/SentryCrash.m @@ -54,7 +54,6 @@ SentryCrash () @property (nonatomic, readwrite, retain) NSString *bundleName; -@property (nonatomic, readwrite, retain) NSString *basePath; @end @@ -82,8 +81,7 @@ SentryCrashLOG_ERROR(@"Could not locate cache directory path."); return nil; } - NSString *pathEnd = [@"SentryCrash" stringByAppendingPathComponent:getBundleName()]; - return [cachePath stringByAppendingPathComponent:pathEnd]; + return cachePath; } @implementation SentryCrash @@ -98,7 +96,6 @@ @implementation SentryCrash @synthesize monitoring = _monitoring; @synthesize onCrash = _onCrash; @synthesize bundleName = _bundleName; -@synthesize basePath = _basePath; @synthesize introspectMemory = _introspectMemory; @synthesize catchZombies = _catchZombies; @synthesize doNotIntrospectClasses = _doNotIntrospectClasses; @@ -124,20 +121,9 @@ + (instancetype)sharedInstance } - (id)init -{ - return [self initWithBasePath:getBasePath()]; -} - -- (id)initWithBasePath:(NSString *)basePath { if ((self = [super init])) { self.bundleName = getBundleName(); - self.basePath = basePath; - if (self.basePath == nil) { - SentryCrashLOG_ERROR(@"Failed to initialize crash handler. Crash " - @"reporting disabled."); - return nil; - } self.deleteBehaviorAfterSendAll = SentryCrashCDeleteAlways; self.introspectMemory = YES; self.catchZombies = NO; @@ -269,7 +255,22 @@ - (NSDictionary *)systemInfo - (BOOL)install { - _monitoring = sentrycrash_install(self.bundleName.UTF8String, self.basePath.UTF8String); + return [self installWithReportPath:getBasePath()]; +} + +- (BOOL)installWithReportPath:(NSString *)reportPath +{ + if (reportPath == nil) { + SentryCrashLOG_ERROR(@"Failed to initialize crash handler. Crash " + @"reporting disabled."); + return false; + } + + reportPath = [reportPath + stringByAppendingPathComponent:[@"SentryCrash" + stringByAppendingPathComponent:getBundleName()]]; + + _monitoring = sentrycrash_install(self.bundleName.UTF8String, reportPath.UTF8String); if (self.monitoring == 0) { return false; } diff --git a/Tests/SentryTests/Helper/SentryFileManagerTests.swift b/Tests/SentryTests/Helper/SentryFileManagerTests.swift index 06806be9bfc..236ed91ab1b 100644 --- a/Tests/SentryTests/Helper/SentryFileManagerTests.swift +++ b/Tests/SentryTests/Helper/SentryFileManagerTests.swift @@ -505,6 +505,16 @@ class SentryFileManagerTests: XCTestCase { try "garbage".write(to: URL(fileURLWithPath: sut.timezoneOffsetFilePath), atomically: true, encoding: .utf8) XCTAssertNil(sut.readTimezoneOffset()) } + + func testPathFromOptions() throws { + var altCache = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true).first ?? "" + altCache += "/altReportPath" + fixture.options.cacheDirectoryPath = altCache + + let sut = try fixture.getSut() + + XCTAssertTrue(sut.sentryPath.hasPrefix(altCache)) + } private func givenMaximumEnvelopes() { fixture.eventIds.forEach { id in diff --git a/Tests/SentryTests/SentryCrash/SentryCrashInstallationReporterTests.swift b/Tests/SentryTests/SentryCrash/SentryCrashInstallationReporterTests.swift index 03b75702514..f88a358dedc 100644 --- a/Tests/SentryTests/SentryCrash/SentryCrashInstallationReporterTests.swift +++ b/Tests/SentryTests/SentryCrash/SentryCrashInstallationReporterTests.swift @@ -12,7 +12,10 @@ class SentryCrashInstallationReporterTests: XCTestCase { override func setUp() { super.setUp() sut = SentryCrashInstallationReporter(inAppLogic: SentryInAppLogic(inAppIncludes: [], inAppExcludes: [])) - sut.install() + + let cacheDir = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true).first + + sut.install(withReportPath: cacheDir) // Works only if SentryCrash is installed sentrycrash_deleteAllReports() } diff --git a/Tests/SentryTests/SentryCrash/SentryCrashTests.m b/Tests/SentryTests/SentryCrash/SentryCrashTests.m index 2266a31bcb5..671c5daba2e 100644 --- a/Tests/SentryTests/SentryCrash/SentryCrashTests.m +++ b/Tests/SentryTests/SentryCrash/SentryCrashTests.m @@ -23,8 +23,7 @@ - (void)test_getScreenshots_CheckName { [self initReport:12 withScreenshots:1]; - SentryCrash *sentryCrash = [[SentryCrash alloc] - initWithBasePath:[self.tempPath stringByAppendingPathComponent:@"Reports"]]; + SentryCrash *sentryCrash = [[SentryCrash alloc] init]; NSArray *files = [sentryCrash getScreenshotPaths:12]; XCTAssertEqual(files.count, 1); @@ -37,8 +36,7 @@ - (void)test_getScreenshots_TwoFiles { [self initReport:12 withScreenshots:2]; - SentryCrash *sentryCrash = [[SentryCrash alloc] - initWithBasePath:[self.tempPath stringByAppendingPathComponent:@"Reports"]]; + SentryCrash *sentryCrash = [[SentryCrash alloc] init]; NSArray *files = [sentryCrash getScreenshotPaths:12]; XCTAssertEqual(files.count, 2); } @@ -47,16 +45,14 @@ - (void)test_getScreenshots_NoFiles { [self initReport:12 withScreenshots:0]; - SentryCrash *sentryCrash = [[SentryCrash alloc] - initWithBasePath:[self.tempPath stringByAppendingPathComponent:@"Reports"]]; + SentryCrash *sentryCrash = [[SentryCrash alloc] init]; NSArray *files = [sentryCrash getScreenshotPaths:12]; XCTAssertEqual(files.count, 0); } - (void)test_getScreenshots_NoDirectory { - SentryCrash *sentryCrash = [[SentryCrash alloc] - initWithBasePath:[self.tempPath stringByAppendingPathComponent:@"ReportsFake"]]; + SentryCrash *sentryCrash = [[SentryCrash alloc] init]; NSArray *files = [sentryCrash getScreenshotPaths:12]; XCTAssertEqual(files.count, 0); } diff --git a/Tests/SentryTests/SentryOptionsTest.m b/Tests/SentryTests/SentryOptionsTest.m index f50a9e0896d..c3dd13fe3f9 100644 --- a/Tests/SentryTests/SentryOptionsTest.m +++ b/Tests/SentryTests/SentryOptionsTest.m @@ -93,6 +93,15 @@ - (void)testDist XCTAssertEqualObjects(options.dist, @"hhh"); } +- (void)testCacheDirectoryPath +{ + SentryOptions *options = [self getValidOptions:@{}]; + XCTAssertNil(options.cacheDirectoryPath); + + options = [self getValidOptions:@{ @"cacheDirectoryPath" : @"/somePath/" }]; + XCTAssertEqualObjects(options.cacheDirectoryPath, @"/somePath/"); +} + - (void)testValidDebug { [self testDebugWith:@YES expected:YES]; @@ -488,7 +497,9 @@ - (void)testNSNull_SetsDefaultValue @"experimentalEnableTraceSampling" : [NSNull null], @"enableSwizzling" : [NSNull null], @"enableIOTracking" : [NSNull null], - @"sdk" : [NSNull null] + @"sdk" : [NSNull null], + @"cacheDirectoryPath" : [NSNull null] + } didFailWithError:nil]; @@ -540,6 +551,7 @@ - (void)assertDefaultValues:(SentryOptions *)options #endif XCTAssertEqual(SentryMeta.sdkName, options.sdkInfo.name); XCTAssertEqual(SentryMeta.versionString, options.sdkInfo.version); + XCTAssertNil(options.cacheDirectoryPath); } - (void)testSetValidDsn