From a5ab1c1e41fa0005a55e35e7ce44d58d2d0d17bb Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Tue, 5 Nov 2024 13:59:15 +0000 Subject: [PATCH 01/10] release: 8.40.0 --- .github/last-release-runid | 2 +- CHANGELOG.md | 19 ++++++++++++++++++- Package.swift | 8 ++++---- Samples/iOS-Swift/iOS-Swift/Sample.xcconfig | 2 +- Sentry.podspec | 2 +- SentryPrivate.podspec | 2 +- SentrySwiftUI.podspec | 4 ++-- Sources/Configuration/SDK.xcconfig | 2 +- Sources/Configuration/SentrySwiftUI.xcconfig | 2 +- Sources/Sentry/SentryMeta.m | 2 +- Tests/HybridSDKTest/HybridPod.podspec | 2 +- 11 files changed, 32 insertions(+), 15 deletions(-) diff --git a/.github/last-release-runid b/.github/last-release-runid index 532e0c4bd9..df0826299c 100644 --- a/.github/last-release-runid +++ b/.github/last-release-runid @@ -1 +1 @@ -11404022886 +11685387200 diff --git a/CHANGELOG.md b/CHANGELOG.md index f984d85e9d..d36ee6a375 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,23 @@ # Changelog -## Unreleased +## 8.40.0 + +### Various fixes & improvements + +- chore(deps): bump cocoapods from 1.15.2 to 1.16.2 (#4501) by @dependabot +- fix: Masking text with transparent text color (#4499) by @brustolin +- internal(hybrid): Add Replay Mask options to dict init (#4495) by @krystofwoldrich +- internal(hybrid): Add Replay Mask options to dict init (#4492) by @krystofwoldrich +- fix: Too many navigation breadcrumbs for Session Replay (#4480) by @brustolin +- Fix: Time-of-check time-of-use filesystem race condition (#4473) by @brustolin +- Expose `SentrySessionReplayIntegration-Hybrid.h` as `private` (#4486) by @denrase +- fix: Capture all touches with session replay (#4477) by @brustolin +- feat: Option for uncaught NSExceptions on macOS (#4471) by @philipphofmann +- chore(deps): bump rexml from 3.3.8 to 3.3.9 (#4478) by @dependabot +- ref: Improve frames tracker performance (#4469) by @brustolin +- fix: Build visionOS project with static Sentry SDK (#4462) by @brustolin +- ref(transport): Log a warning when dropping envelopes due to rate-limiting (#4463) by @rwachtler +- chore(deps): bump fastlane from 2.224.0 to 2.225.0 (#4464) by @dependabot ## Feature diff --git a/Package.swift b/Package.swift index dbc43ec32c..21fd4d10db 100644 --- a/Package.swift +++ b/Package.swift @@ -12,13 +12,13 @@ let package = Package( targets: [ .binaryTarget( name: "Sentry", - url: "https://github.com/getsentry/sentry-cocoa/releases/download/8.39.0-beta.1/Sentry.xcframework.zip", - checksum: "0e82bbb2f5714b52249da238cefd5c2ded20e69b52d2c812cc3ac804f02fa0f9" //Sentry-Static + url: "https://github.com/getsentry/sentry-cocoa/releases/download/8.40.0/Sentry.xcframework.zip", + checksum: "aa02c15ed98f2560436ccbcc0d25c848f2e8250c28c6c4a01ff3ac4231eb008b" //Sentry-Static ), .binaryTarget( name: "Sentry-Dynamic", - url: "https://github.com/getsentry/sentry-cocoa/releases/download/8.39.0-beta.1/Sentry-Dynamic.xcframework.zip", - checksum: "77822c632e846f5a19f3042ad89713885dcdc3d953ab49f4f1d8631170459598" //Sentry-Dynamic + url: "https://github.com/getsentry/sentry-cocoa/releases/download/8.40.0/Sentry-Dynamic.xcframework.zip", + checksum: "a24eeb1737a531d56c8b4ce6f63b2d1fb1a2a0fd05c4eb85a27ece8fe3e442b5" //Sentry-Dynamic ), .target ( name: "SentrySwiftUI", dependencies: ["Sentry", "SentryInternal"], diff --git a/Samples/iOS-Swift/iOS-Swift/Sample.xcconfig b/Samples/iOS-Swift/iOS-Swift/Sample.xcconfig index 01fb0720cf..f94e2a2b39 100644 --- a/Samples/iOS-Swift/iOS-Swift/Sample.xcconfig +++ b/Samples/iOS-Swift/iOS-Swift/Sample.xcconfig @@ -1 +1 @@ -MARKETING_VERSION = 8.39.0 +MARKETING_VERSION = 8.40.0 diff --git a/Sentry.podspec b/Sentry.podspec index 5294f13d8a..524067ed65 100644 --- a/Sentry.podspec +++ b/Sentry.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "Sentry" - s.version = "8.39.0-beta.1" + s.version = "8.40.0" s.summary = "Sentry client for cocoa" s.homepage = "https://github.com/getsentry/sentry-cocoa" s.license = "mit" diff --git a/SentryPrivate.podspec b/SentryPrivate.podspec index 1e254602a1..3cb757c6dd 100644 --- a/SentryPrivate.podspec +++ b/SentryPrivate.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "SentryPrivate" - s.version = "8.39.0-beta.1" + s.version = "8.40.0" s.summary = "Sentry Private Library." s.homepage = "https://github.com/getsentry/sentry-cocoa" s.license = "mit" diff --git a/SentrySwiftUI.podspec b/SentrySwiftUI.podspec index 78b5ba8601..ad7b689536 100644 --- a/SentrySwiftUI.podspec +++ b/SentrySwiftUI.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "SentrySwiftUI" - s.version = "8.39.0-beta.1" + s.version = "8.40.0" s.summary = "Sentry client for SwiftUI" s.homepage = "https://github.com/getsentry/sentry-cocoa" s.license = "mit" @@ -19,5 +19,5 @@ Pod::Spec.new do |s| s.watchos.framework = 'WatchKit' s.source_files = "Sources/SentrySwiftUI/**/*.{swift,h,m}" - s.dependency 'Sentry', "8.39.0-beta.1" + s.dependency 'Sentry', "8.40.0" end diff --git a/Sources/Configuration/SDK.xcconfig b/Sources/Configuration/SDK.xcconfig index 4270c685e3..ce6fe3ff3f 100644 --- a/Sources/Configuration/SDK.xcconfig +++ b/Sources/Configuration/SDK.xcconfig @@ -10,7 +10,7 @@ DYLIB_INSTALL_NAME_BASE = @rpath MACH_O_TYPE = mh_dylib FRAMEWORK_VERSION = A -CURRENT_PROJECT_VERSION = 8.39.0 +CURRENT_PROJECT_VERSION = 8.40.0 ALWAYS_SEARCH_USER_PATHS = NO CLANG_ENABLE_OBJC_ARC = YES diff --git a/Sources/Configuration/SentrySwiftUI.xcconfig b/Sources/Configuration/SentrySwiftUI.xcconfig index d2dfef0646..5bcd170836 100644 --- a/Sources/Configuration/SentrySwiftUI.xcconfig +++ b/Sources/Configuration/SentrySwiftUI.xcconfig @@ -1,5 +1,5 @@ PRODUCT_NAME = SentrySwiftUI -CURRENT_PROJECT_VERSION = 8.39.0 +CURRENT_PROJECT_VERSION = 8.40.0 MACOSX_DEPLOYMENT_TARGET = 10.15 IPHONEOS_DEPLOYMENT_TARGET = 13.0 diff --git a/Sources/Sentry/SentryMeta.m b/Sources/Sentry/SentryMeta.m index abcfc6bc56..e443f01891 100644 --- a/Sources/Sentry/SentryMeta.m +++ b/Sources/Sentry/SentryMeta.m @@ -5,7 +5,7 @@ @implementation SentryMeta // Don't remove the static keyword. If you do the compiler adds the constant name to the global // symbol table and it might clash with other constants. When keeping the static keyword the // compiler replaces all occurrences with the value. -static NSString *versionString = @"8.39.0-beta.1"; +static NSString *versionString = @"8.40.0"; static NSString *sdkName = @"sentry.cocoa"; + (NSString *)versionString diff --git a/Tests/HybridSDKTest/HybridPod.podspec b/Tests/HybridSDKTest/HybridPod.podspec index 1bfb8eed50..01ddd5c244 100644 --- a/Tests/HybridSDKTest/HybridPod.podspec +++ b/Tests/HybridSDKTest/HybridPod.podspec @@ -13,6 +13,6 @@ Pod::Spec.new do |s| s.requires_arc = true s.frameworks = 'Foundation' s.swift_versions = "5.5" - s.dependency "Sentry/HybridSDK", "8.39.0-beta.1" + s.dependency "Sentry/HybridSDK", "8.40.0" s.source_files = "HybridTest.swift" end From c64d4a85fd8a48a49b89788684826820a68d0863 Mon Sep 17 00:00:00 2001 From: Krystof Woldrich <31292499+krystofwoldrich@users.noreply.github.com> Date: Wed, 6 Nov 2024 09:33:28 +0100 Subject: [PATCH 02/10] Update CHANGELOG.md --- CHANGELOG.md | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d36ee6a375..0bc572aae9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,23 +2,6 @@ ## 8.40.0 -### Various fixes & improvements - -- chore(deps): bump cocoapods from 1.15.2 to 1.16.2 (#4501) by @dependabot -- fix: Masking text with transparent text color (#4499) by @brustolin -- internal(hybrid): Add Replay Mask options to dict init (#4495) by @krystofwoldrich -- internal(hybrid): Add Replay Mask options to dict init (#4492) by @krystofwoldrich -- fix: Too many navigation breadcrumbs for Session Replay (#4480) by @brustolin -- Fix: Time-of-check time-of-use filesystem race condition (#4473) by @brustolin -- Expose `SentrySessionReplayIntegration-Hybrid.h` as `private` (#4486) by @denrase -- fix: Capture all touches with session replay (#4477) by @brustolin -- feat: Option for uncaught NSExceptions on macOS (#4471) by @philipphofmann -- chore(deps): bump rexml from 3.3.8 to 3.3.9 (#4478) by @dependabot -- ref: Improve frames tracker performance (#4469) by @brustolin -- fix: Build visionOS project with static Sentry SDK (#4462) by @brustolin -- ref(transport): Log a warning when dropping envelopes due to rate-limiting (#4463) by @rwachtler -- chore(deps): bump fastlane from 2.224.0 to 2.225.0 (#4464) by @dependabot - ## Feature - Add option to report uncaught NSExceptions on macOS (#4471) From c3f6814afe669c7b8f712f325529f3af5bd0e4f3 Mon Sep 17 00:00:00 2001 From: Philipp Hofmann Date: Wed, 6 Nov 2024 11:39:35 +0100 Subject: [PATCH 03/10] ref: Remove unused crc32ofString (#4507) Remove unused sentry_crc32ofString including tests. --- Sentry.xcodeproj/project.pbxproj | 4 ---- Sources/Sentry/SentryNSDataUtils.m | 7 ------- Sources/Sentry/include/SentryNSDataUtils.h | 6 ------ .../Categories/SentryNSDataUtilsTests.swift | 18 ------------------ 4 files changed, 35 deletions(-) delete mode 100644 Tests/SentryTests/Categories/SentryNSDataUtilsTests.swift diff --git a/Sentry.xcodeproj/project.pbxproj b/Sentry.xcodeproj/project.pbxproj index ae4646f023..bf4a5fcb6f 100644 --- a/Sentry.xcodeproj/project.pbxproj +++ b/Sentry.xcodeproj/project.pbxproj @@ -83,7 +83,6 @@ 621D9F2F2B9B0320003D94DE /* SentryCurrentDateProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 621D9F2E2B9B0320003D94DE /* SentryCurrentDateProvider.swift */; }; 621F61F12BEA073A005E654F /* SentryEnabledFeaturesBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 621F61F02BEA073A005E654F /* SentryEnabledFeaturesBuilder.swift */; }; 6221BBCA2CAA932100C627CA /* SentryANRType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6221BBC92CAA932100C627CA /* SentryANRType.swift */; }; - 6229416A2BB2F123004765D1 /* SentryNSDataUtilsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 622941692BB2F123004765D1 /* SentryNSDataUtilsTests.swift */; }; 622C08D829E546F4002571D4 /* SentryTraceOrigins.h in Headers */ = {isa = PBXBuildFile; fileRef = 622C08D729E546F4002571D4 /* SentryTraceOrigins.h */; }; 622C08DB29E554B9002571D4 /* SentrySpanContext+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 622C08D929E554B9002571D4 /* SentrySpanContext+Private.h */; }; 62375FB92B47F9F000CC55F1 /* SentryDependencyContainerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62375FB82B47F9F000CC55F1 /* SentryDependencyContainerTests.swift */; }; @@ -1083,7 +1082,6 @@ 621D9F2E2B9B0320003D94DE /* SentryCurrentDateProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryCurrentDateProvider.swift; sourceTree = ""; }; 621F61F02BEA073A005E654F /* SentryEnabledFeaturesBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryEnabledFeaturesBuilder.swift; sourceTree = ""; }; 6221BBC92CAA932100C627CA /* SentryANRType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryANRType.swift; sourceTree = ""; }; - 622941692BB2F123004765D1 /* SentryNSDataUtilsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryNSDataUtilsTests.swift; sourceTree = ""; }; 622C08D729E546F4002571D4 /* SentryTraceOrigins.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryTraceOrigins.h; path = include/SentryTraceOrigins.h; sourceTree = ""; }; 622C08D929E554B9002571D4 /* SentrySpanContext+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "SentrySpanContext+Private.h"; path = "include/SentrySpanContext+Private.h"; sourceTree = ""; }; 62375FB82B47F9F000CC55F1 /* SentryDependencyContainerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryDependencyContainerTests.swift; sourceTree = ""; }; @@ -2928,7 +2926,6 @@ children = ( 7B6438A626A70DDB000D0F65 /* UIViewControllerSentryTests.swift */, 7B0DC73328869BF40039995F /* NSMutableDictionarySentryTests.swift */, - 622941692BB2F123004765D1 /* SentryNSDataUtilsTests.swift */, ); path = Categories; sourceTree = ""; @@ -4931,7 +4928,6 @@ 7B2A70DF27D60904008B0D15 /* SentryTestThreadWrapper.swift in Sources */, 62F4DDA12C04CB9700588890 /* SentryBaggageSerializationTests.swift in Sources */, 7BE912AF272166DD00E49E62 /* SentryNoOpSpanTests.swift in Sources */, - 6229416A2BB2F123004765D1 /* SentryNSDataUtilsTests.swift in Sources */, 7B56D73524616E5600B842DA /* SentryConcurrentRateLimitsDictionaryTests.swift in Sources */, 7B7D8730248648AD00D2ECFF /* SentryStacktraceBuilderTests.swift in Sources */, 62E081AB29ED4322000F69FC /* SentryBreadcrumbTestDelegate.swift in Sources */, diff --git a/Sources/Sentry/SentryNSDataUtils.m b/Sources/Sentry/SentryNSDataUtils.m index 7df513c47f..15ba40b4f9 100644 --- a/Sources/Sentry/SentryNSDataUtils.m +++ b/Sources/Sentry/SentryNSDataUtils.m @@ -63,11 +63,4 @@ return mutable; } -NSUInteger -sentry_crc32ofString(NSString *value) -{ - NSData *data = [value dataUsingEncoding:NSUTF8StringEncoding]; - return crc32(0, data.bytes, (uInt)[data length]); -} - NS_ASSUME_NONNULL_END diff --git a/Sources/Sentry/include/SentryNSDataUtils.h b/Sources/Sentry/include/SentryNSDataUtils.h index aa2d5d5a6f..19692a4b95 100644 --- a/Sources/Sentry/include/SentryNSDataUtils.h +++ b/Sources/Sentry/include/SentryNSDataUtils.h @@ -11,10 +11,4 @@ NSData *_Nullable sentry_gzippedWithCompressionLevel( */ NSData *_Nullable sentry_nullTerminated(NSData *_Nullable data); -/** - * Calculates an CRC32 (Cyclic Redundancy Check 32) checksum for the string by first encoding it to - * UTF8Encoded data. - */ -NSUInteger sentry_crc32ofString(NSString *value); - NS_ASSUME_NONNULL_END diff --git a/Tests/SentryTests/Categories/SentryNSDataUtilsTests.swift b/Tests/SentryTests/Categories/SentryNSDataUtilsTests.swift deleted file mode 100644 index 5159e88820..0000000000 --- a/Tests/SentryTests/Categories/SentryNSDataUtilsTests.swift +++ /dev/null @@ -1,18 +0,0 @@ -import Sentry -import XCTest - -final class SentryNSDataUtilsTests: XCTestCase { - - func testCRC32OfString_SameString_ReturnsSameResult() throws { - let result1 = sentry_crc32ofString("test-string") - let result2 = sentry_crc32ofString("test-string") - XCTAssertEqual(result1, result2) - } - - func testCRC32OfString_DifferentString_ReturnsDifferentResult() throws { - let result1 = sentry_crc32ofString("test-string") - let result2 = sentry_crc32ofString("test-string1") - XCTAssertNotEqual(result1, result2) - } - -} From a8e28a356e92d3cba3b4c6c67d24f75cf04a3679 Mon Sep 17 00:00:00 2001 From: Philipp Hofmann Date: Wed, 6 Nov 2024 11:40:03 +0100 Subject: [PATCH 04/10] ref: Remove unused getAllThreads method (#4506) Remove unused sentrycrashccd_getAllThreads. --- Sources/SentryCrash/Recording/SentryCrashCachedData.c | 9 --------- Sources/SentryCrash/Recording/SentryCrashCachedData.h | 2 -- 2 files changed, 11 deletions(-) diff --git a/Sources/SentryCrash/Recording/SentryCrashCachedData.c b/Sources/SentryCrash/Recording/SentryCrashCachedData.c index b08133480a..3d0a0a0ba4 100644 --- a/Sources/SentryCrash/Recording/SentryCrashCachedData.c +++ b/Sources/SentryCrash/Recording/SentryCrashCachedData.c @@ -202,15 +202,6 @@ sentrycrashccd_unfreeze(void) } } -SentryCrashThread * -sentrycrashccd_getAllThreads(int *threadCount) -{ - if (threadCount != NULL) { - *threadCount = g_allThreadsCount; - } - return g_allMachThreads; -} - const char * sentrycrashccd_getThreadName(SentryCrashThread thread) SENTRY_DISABLE_THREAD_SANITIZER("Known data race to fix") diff --git a/Sources/SentryCrash/Recording/SentryCrashCachedData.h b/Sources/SentryCrash/Recording/SentryCrashCachedData.h index f6a89f95d8..c8e7f31d95 100644 --- a/Sources/SentryCrash/Recording/SentryCrashCachedData.h +++ b/Sources/SentryCrash/Recording/SentryCrashCachedData.h @@ -35,8 +35,6 @@ bool sentrycrashccd_hasThreadStarted(void); void sentrycrashccd_freeze(void); void sentrycrashccd_unfreeze(void); -SentryCrashThread *sentrycrashccd_getAllThreads(int *threadCount); - const char *sentrycrashccd_getThreadName(SentryCrashThread thread); const char *sentrycrashccd_getQueueName(SentryCrashThread thread); From 06d30401c6c5ed6d828b7de1aeaf6fc6d5cf28e5 Mon Sep 17 00:00:00 2001 From: Philipp Hofmann Date: Wed, 6 Nov 2024 15:09:00 +0100 Subject: [PATCH 05/10] ref: Remove redundant log prefix in swizzling (#4505) The log messages contain the class name no need to prefix all the log messages with it. --- .../Sentry/SentryUIViewControllerSwizzling.m | 69 +++++++++---------- 1 file changed, 31 insertions(+), 38 deletions(-) diff --git a/Sources/Sentry/SentryUIViewControllerSwizzling.m b/Sources/Sentry/SentryUIViewControllerSwizzling.m index 456dd933e1..2a2ec7943e 100644 --- a/Sources/Sentry/SentryUIViewControllerSwizzling.m +++ b/Sources/Sentry/SentryUIViewControllerSwizzling.m @@ -99,9 +99,8 @@ - (void)start // twice. We could also use objc_getClassList to lookup sub classes of UIViewController, but // the lookup can take around 60ms, which is not acceptable. if (![self swizzleRootViewControllerFromUIApplication:app]) { - SENTRY_LOG_DEBUG(@"UIViewControllerSwizzling: Failed to find root UIViewController " - @"from UIApplicationDelegate. Trying to use " - @"UISceneWillConnectNotification notification."); + SENTRY_LOG_DEBUG(@"Failed to find root UIViewController from UIApplicationDelegate. " + @"Trying to use UISceneWillConnectNotification notification."); if (@available(iOS 13.0, tvOS 13.0, *)) { [NSNotificationCenter.defaultCenter @@ -110,9 +109,9 @@ - (void)start name:UISceneWillConnectNotification object:nil]; } else { - SENTRY_LOG_DEBUG(@"UIViewControllerSwizzling: iOS version older then 13. There is " - @"no UISceneWillConnectNotification notification. Could not find " - @"a rootViewController"); + SENTRY_LOG_DEBUG( + @"iOS version older then 13. There is no UISceneWillConnectNotification " + @"notification. Could not find a rootViewController"); } } } @@ -124,15 +123,14 @@ - (void)start - (id)findApp { if (![UIApplication respondsToSelector:@selector(sharedApplication)]) { - SENTRY_LOG_DEBUG( - @"UIViewControllerSwizzling: UIApplication doesn't respond to sharedApplication."); + SENTRY_LOG_DEBUG(@"UIApplication doesn't respond to sharedApplication."); return nil; } UIApplication *app = [UIApplication performSelector:@selector(sharedApplication)]; if (app == nil) { - SENTRY_LOG_DEBUG(@"UIViewControllerSwizzling: UIApplication.sharedApplication is nil."); + SENTRY_LOG_DEBUG(@"UIApplication.sharedApplication is nil."); return nil; } @@ -142,18 +140,17 @@ - (void)start - (void)swizzleUIViewControllersOfClassesInImageOf:(Class)class { if (class == NULL) { - SENTRY_LOG_DEBUG(@"UIViewControllerSwizzling: class is NULL. Skipping swizzling of classes " - @"in same image."); + SENTRY_LOG_DEBUG(@"Class is NULL. Skipping swizzling of classes in same image."); return; } - SENTRY_LOG_DEBUG(@"UIViewControllerSwizzling: Class to get the image name: %@", class); + SENTRY_LOG_DEBUG(@"Class to get the image name: %@", class); const char *imageNameAsCharArray = [self.objcRuntimeWrapper class_getImageName:class]; if (imageNameAsCharArray == NULL) { - SENTRY_LOG_DEBUG(@"UIViewControllerSwizzling: Wasn't able to get image name of the class: " - @"%@. Skipping swizzling of classes in same image.", + SENTRY_LOG_DEBUG(@"Wasn't able to get image name of the class: %@. Skipping swizzling of " + @"classes in same image.", class); return; } @@ -162,9 +159,8 @@ - (void)swizzleUIViewControllersOfClassesInImageOf:(Class)class encoding:NSUTF8StringEncoding]; if (imageName == nil || imageName.length == 0) { - SENTRY_LOG_DEBUG( - @"UIViewControllerSwizzling: Wasn't able to get the app image name of the app " - @"delegate class: %@. Skipping swizzling of classes in same image.", + SENTRY_LOG_DEBUG(@"Wasn't able to get the app image name of the app delegate class: %@. " + @"Skipping swizzling of classes in same image.", class); return; } @@ -175,14 +171,12 @@ - (void)swizzleUIViewControllersOfClassesInImageOf:(Class)class - (void)swizzleUIViewControllersOfImage:(NSString *)imageName { if ([imageName containsString:@"UIKitCore"]) { - SENTRY_LOG_DEBUG(@"UIViewControllerSwizzling: Skipping UIKitCore."); + SENTRY_LOG_DEBUG(@"Skipping UIKitCore."); return; } if ([self.imagesActedOnSubclassesOfUIViewControllers containsObject:imageName]) { - SENTRY_LOG_DEBUG( - @"UIViewControllerSwizzling: Already swizzled UIViewControllers in image: %@.", - imageName); + SENTRY_LOG_DEBUG(@"Already swizzled UIViewControllers in image: %@.", imageName); return; } @@ -225,15 +219,15 @@ - (void)swizzleRootViewControllerFromSceneDelegateNotification:(NSNotification * // The object of a UISceneWillConnectNotification should be a NSWindowScene if (![notification.object respondsToSelector:@selector(windows)]) { SENTRY_LOG_DEBUG( - @"UIViewControllerSwizzling: Failed to find root UIViewController from " - @"UISceneWillConnectNotification. Notification object has no windows property"); + @"Failed to find root UIViewController from UISceneWillConnectNotification. " + @"Notification object has no windows property"); return; } id windows = [notification.object performSelector:@selector(windows)]; if (![windows isKindOfClass:[NSArray class]]) { - SENTRY_LOG_DEBUG(@"UIViewControllerSwizzling: Failed to find root UIViewController " - @"from UISceneWillConnectNotification. Windows is not an array"); + SENTRY_LOG_DEBUG(@"Failed to find root UIViewController from " + @"UISceneWillConnectNotification. Windows is not an array"); return; } @@ -244,9 +238,9 @@ - (void)swizzleRootViewControllerFromSceneDelegateNotification:(NSNotification * [self swizzleRootViewControllerAndDescendant:((UIWindow *)window).rootViewController]; } else { - SENTRY_LOG_DEBUG(@"UIViewControllerSwizzling: Failed to find root " - @"UIViewController from UISceneWillConnectNotification. Window is " - @"not a UIWindow class or the rootViewController is nil"); + SENTRY_LOG_DEBUG( + @"Failed to find root UIViewController from UISceneWillConnectNotification. " + @"Window is not a UIWindow class or the rootViewController is nil"); } } } @@ -255,29 +249,28 @@ - (void)swizzleRootViewControllerFromSceneDelegateNotification:(NSNotification * - (BOOL)swizzleRootViewControllerFromUIApplication:(id)app { if (app.delegate == nil) { - SENTRY_LOG_DEBUG(@"UIViewControllerSwizzling: App delegate is nil. Skipping " - @"swizzleRootViewControllerFromAppDelegate."); + SENTRY_LOG_DEBUG( + @"App delegate is nil. Skipping swizzleRootViewControllerFromAppDelegate."); return NO; } // Check if delegate responds to window, which it doesn't have to. if (![app.delegate respondsToSelector:@selector(window)]) { - SENTRY_LOG_DEBUG(@"UIViewControllerSwizzling: UIApplicationDelegate.window is nil. " - @"Skipping swizzleRootViewControllerFromAppDelegate."); + SENTRY_LOG_DEBUG(@"UIApplicationDelegate.window is nil. Skipping " + @"swizzleRootViewControllerFromAppDelegate."); return NO; } if (app.delegate.window == nil) { - SENTRY_LOG_DEBUG(@"UIViewControllerSwizzling: UIApplicationDelegate.window is nil. " - @"Skipping swizzleRootViewControllerFromAppDelegate."); + SENTRY_LOG_DEBUG(@"UIApplicationDelegate.window is nil. Skipping " + @"swizzleRootViewControllerFromAppDelegate."); return NO; } UIViewController *rootViewController = app.delegate.window.rootViewController; if (rootViewController == nil) { - SENTRY_LOG_DEBUG( - @"UIViewControllerSwizzling: UIApplicationDelegate.window.rootViewController is nil. " - @"Skipping swizzleRootViewControllerFromAppDelegate."); + SENTRY_LOG_DEBUG(@"UIApplicationDelegate.window.rootViewController is nil. Skipping " + @"swizzleRootViewControllerFromAppDelegate."); return NO; } @@ -294,7 +287,7 @@ - (void)swizzleRootViewControllerAndDescendant:(UIViewController *)rootViewContr for (UIViewController *viewController in allViewControllers) { Class viewControllerClass = [viewController class]; if (viewControllerClass != nil) { - SENTRY_LOG_DEBUG(@"UIViewControllerSwizzling Calling swizzleRootViewController."); + SENTRY_LOG_DEBUG(@"Calling swizzleRootViewController."); [self swizzleViewControllerSubClass:viewControllerClass]; // We can't get the image name with the app delegate class for some apps. Therefore, we From f31a3eff586235b76a9d9dfb0ec82d7bc326afee Mon Sep 17 00:00:00 2001 From: Philipp Hofmann Date: Wed, 6 Nov 2024 15:49:46 +0100 Subject: [PATCH 06/10] impr: Add extra logs for UIViewControllerSwizzling (#4511) Add more log messages for potential troubleshooting for swizzling UIViewControllers. --- CHANGELOG.md | 6 ++++++ Sources/Sentry/SentrySubClassFinder.m | 10 +++++----- Sources/Sentry/SentryUIViewControllerSwizzling.m | 13 +++++++++++-- .../Performance/SwizzleClassNameExclude.swift | 1 + 4 files changed, 23 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0bc572aae9..4163298a63 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +### Improvements + +- Add extra logs for UIViewControllerSwizzling (#4511) + ## 8.40.0 ## Feature diff --git a/Sources/Sentry/SentrySubClassFinder.m b/Sources/Sentry/SentrySubClassFinder.m index b730f01f68..84bea71325 100644 --- a/Sources/Sentry/SentrySubClassFinder.m +++ b/Sources/Sentry/SentrySubClassFinder.m @@ -36,6 +36,8 @@ - (instancetype)initWithDispatchQueue:(SentryDispatchQueueWrapper *)dispatchQueu - (void)actOnSubclassesOfViewControllerInImage:(NSString *)imageName block:(void (^)(Class))block; { [self.dispatchQueue dispatchAsyncWithBlock:^{ + SENTRY_LOG_DEBUG(@"ActOnSubclassesOfViewControllerInImage: %@", imageName); + Class viewControllerClass = [UIViewController class]; if (viewControllerClass == nil) { SENTRY_LOG_DEBUG(@"UIViewController class not found."); @@ -85,11 +87,9 @@ - (void)actOnSubclassesOfViewControllerInImage:(NSString *)imageName block:(void block(NSClassFromString(className)); } - [SentryLog - logWithMessage:[NSString stringWithFormat:@"The following UIViewControllers will " - @"generate automatic transactions: %@", - [classesToSwizzle componentsJoinedByString:@", "]] - andLevel:kSentryLevelDebug]; + SENTRY_LOG_DEBUG( + @"The following UIViewControllers will generate automatic transactions: %@", + [classesToSwizzle componentsJoinedByString:@", "]); }]; }]; } diff --git a/Sources/Sentry/SentryUIViewControllerSwizzling.m b/Sources/Sentry/SentryUIViewControllerSwizzling.m index 2a2ec7943e..6b27feaa4b 100644 --- a/Sources/Sentry/SentryUIViewControllerSwizzling.m +++ b/Sources/Sentry/SentryUIViewControllerSwizzling.m @@ -284,16 +284,23 @@ - (void)swizzleRootViewControllerAndDescendant:(UIViewController *)rootViewContr NSArray *allViewControllers = [SentryViewController descendantsOfViewController:rootViewController]; + SENTRY_LOG_DEBUG(@"Found %lu descendants for RootViewController %@", allViewControllers.count, + rootViewController.description); + for (UIViewController *viewController in allViewControllers) { Class viewControllerClass = [viewController class]; if (viewControllerClass != nil) { - SENTRY_LOG_DEBUG(@"Calling swizzleRootViewController."); + SENTRY_LOG_DEBUG( + @"Calling swizzleRootViewController for %@", viewController.description); [self swizzleViewControllerSubClass:viewControllerClass]; // We can't get the image name with the app delegate class for some apps. Therefore, we // use the rootViewController and its subclasses as a fallback. The following method // ensures we don't swizzle ViewControllers of UIKit. [self swizzleUIViewControllersOfClassesInImageOf:viewControllerClass]; + } else { + SENTRY_LOG_WARN(@"ViewControllerClass was nil for UIViewController: %@", + viewController.description); } } } @@ -318,8 +325,10 @@ - (void)swizzleUIViewController - (void)swizzleViewControllerSubClass:(Class)class { - if (![self shouldSwizzleViewController:class]) + if (![self shouldSwizzleViewController:class]) { + SENTRY_LOG_DEBUG(@"Skipping swizzling of class: %@", class); return; + } // This are the five main functions related to UI creation in a view controller. // We are swizzling it to track anything that happens inside one of this functions. diff --git a/Sources/Swift/Integrations/Performance/SwizzleClassNameExclude.swift b/Sources/Swift/Integrations/Performance/SwizzleClassNameExclude.swift index 9b60ae87ff..ca9ef1eb4d 100644 --- a/Sources/Swift/Integrations/Performance/SwizzleClassNameExclude.swift +++ b/Sources/Swift/Integrations/Performance/SwizzleClassNameExclude.swift @@ -5,6 +5,7 @@ class SentrySwizzleClassNameExclude: NSObject { static func shouldExcludeClass(className: String, swizzleClassNameExcludes: Set) -> Bool { for exclude in swizzleClassNameExcludes { if className.contains(exclude) { + SentryLog.debug("Excluding class \(className) from swizzling cause it matches the exclude pattern: \(exclude).") return true } } From 8fb1a4a609469b971e985b66b561409ce4426881 Mon Sep 17 00:00:00 2001 From: Dhiogo Brustolin Date: Thu, 7 Nov 2024 11:00:23 +0100 Subject: [PATCH 07/10] ref: Stops session replay if rate limiting is activated (#4496) Stop session replay if the app is being rate limited. --- CHANGELOG.md | 1 + Sources/Sentry/SentryClient.m | 8 +- Sources/Sentry/SentryDependencyContainer.m | 24 ++++ .../Sentry/SentrySessionReplayIntegration.m | 22 ++++ Sources/Sentry/SentryTransportFactory.m | 13 +-- .../HybridPublic/SentryDependencyContainer.h | 2 + .../Sentry/include/SentryTransportFactory.h | 3 +- .../SentrySessionReplayIntegrationTests.swift | 104 ++++++++++++++++++ .../SentryTransportFactoryTests.swift | 16 ++- .../SentryTransportInitializerTests.swift | 2 +- 10 files changed, 173 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4163298a63..8cd929c553 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ - Improve frames tracker performance (#4469) - Log a warning when dropping envelopes due to rate-limiting (#4463) - Expose `SentrySessionReplayIntegration-Hybrid.h` as `private` (#4486) +- Stops session replay if rate limiting is activated (#4496) - Add `maskedViewClasses` and `unmaskedViewClasses` to SentryReplayOptions init via dict (#4492) - Add `quality` to SentryReplayOptions init via dict (#4495) diff --git a/Sources/Sentry/SentryClient.m b/Sources/Sentry/SentryClient.m index a31ac3abff..b3d95d32ac 100644 --- a/Sources/Sentry/SentryClient.m +++ b/Sources/Sentry/SentryClient.m @@ -99,10 +99,10 @@ - (instancetype)initWithOptions:(SentryOptions *)options fileManager:(SentryFileManager *)fileManager deleteOldEnvelopeItems:(BOOL)deleteOldEnvelopeItems { - NSArray> *transports = [SentryTransportFactory - initTransports:options - sentryFileManager:fileManager - currentDateProvider:SentryDependencyContainer.sharedInstance.dateProvider]; + NSArray> *transports = + [SentryTransportFactory initTransports:options + sentryFileManager:fileManager + rateLimits:SentryDependencyContainer.sharedInstance.rateLimits]; SentryTransportAdapter *transportAdapter = [[SentryTransportAdapter alloc] initWithTransports:transports options:options]; diff --git a/Sources/Sentry/SentryDependencyContainer.m b/Sources/Sentry/SentryDependencyContainer.m index c53086900d..710470a6ad 100644 --- a/Sources/Sentry/SentryDependencyContainer.m +++ b/Sources/Sentry/SentryDependencyContainer.m @@ -22,8 +22,12 @@ #import #import #import +#import #import +#import #import +#import +#import #import #import #import @@ -215,6 +219,26 @@ - (SentryNSNotificationCenterWrapper *)notificationCenterWrapper } } +- (id)rateLimits +{ + @synchronized(sentryDependencyContainerLock) { + if (_rateLimits == nil) { + SentryRetryAfterHeaderParser *retryAfterHeaderParser = + [[SentryRetryAfterHeaderParser alloc] + initWithHttpDateParser:[[SentryHttpDateParser alloc] init] + currentDateProvider:self.dateProvider]; + SentryRateLimitParser *rateLimitParser = + [[SentryRateLimitParser alloc] initWithCurrentDateProvider:self.dateProvider]; + + _rateLimits = [[SentryDefaultRateLimits alloc] + initWithRetryAfterHeaderParser:retryAfterHeaderParser + andRateLimitParser:rateLimitParser + currentDateProvider:self.dateProvider]; + } + return _rateLimits; + } +} + #if SENTRY_HAS_UIKIT - (SentryUIDeviceWrapper *)uiDeviceWrapper SENTRY_DISABLE_THREAD_SANITIZER( "double-checked lock produce false alarms") diff --git a/Sources/Sentry/SentrySessionReplayIntegration.m b/Sources/Sentry/SentrySessionReplayIntegration.m index f24a3abf27..3c5245dac7 100644 --- a/Sources/Sentry/SentrySessionReplayIntegration.m +++ b/Sources/Sentry/SentrySessionReplayIntegration.m @@ -14,6 +14,7 @@ # import "SentryNSNotificationCenterWrapper.h" # import "SentryOptions.h" # import "SentryRandom.h" +# import "SentryRateLimits.h" # import "SentryReachability.h" # import "SentrySDK+Private.h" # import "SentryScope+Private.h" @@ -46,6 +47,10 @@ @implementation SentrySessionReplayIntegration { SentryReplayOptions *_replayOptions; SentryNSNotificationCenterWrapper *_notificationCenter; SentryOnDemandReplay *_resumeReplayMaker; + id _rateLimits; + // We need to use this variable to identify whether rate limiting was ever activated for session replay in this session, instead of always looking for the rate status in `SentryRateLimits` + // This is the easiest way to ensure segment 0 will always reach the server, because session replay absolutely needs segment 0 to make replay work. + BOOL _rateLimited; } - (instancetype)init @@ -78,6 +83,7 @@ - (void)setupWith:(SentryReplayOptions *)replayOptions enableTouchTracker:(BOOL) { _replayOptions = replayOptions; _viewPhotographer = [[SentryViewPhotographer alloc] initWithRedactOptions:replayOptions]; + _rateLimits = SentryDependencyContainer.sharedInstance.rateLimits; if (touchTracker) { _touchTracker = [[SentryTouchTracker alloc] @@ -416,6 +422,12 @@ - (void)resume - (void)start { + if (_rateLimited) { + SENTRY_LOG_WARN( + @"This session was rate limited. Not starting session replay until next app session"); + return; + } + if (self.sessionReplay != nil) { if (self.sessionReplay.isFullSession == NO) { [self.sessionReplay captureReplay]; @@ -447,6 +459,7 @@ - (void)sentrySessionEnded:(SentrySession *)session - (void)sentrySessionStarted:(SentrySession *)session { + _rateLimited = NO; [self startSession]; } @@ -553,6 +566,15 @@ - (void)sessionReplayNewSegmentWithReplayEvent:(SentryReplayEvent *)replayEvent replayRecording:(SentryReplayRecording *)replayRecording videoUrl:(NSURL *)videoUrl { + if ([_rateLimits isRateLimitActive:kSentryDataCategoryReplay] || + [_rateLimits isRateLimitActive:kSentryDataCategoryAll]) { + SENTRY_LOG_DEBUG( + @"Rate limiting is active for replays. Stopping session replay until next session."); + _rateLimited = YES; + [self stop]; + return; + } + [SentrySDK.currentHub captureReplayEvent:replayEvent replayRecording:replayRecording video:videoUrl]; diff --git a/Sources/Sentry/SentryTransportFactory.m b/Sources/Sentry/SentryTransportFactory.m index cbdd10e599..e689706917 100644 --- a/Sources/Sentry/SentryTransportFactory.m +++ b/Sources/Sentry/SentryTransportFactory.m @@ -25,7 +25,7 @@ @implementation SentryTransportFactory + (NSArray> *)initTransports:(SentryOptions *)options sentryFileManager:(SentryFileManager *)sentryFileManager - currentDateProvider:(id)currentDateProvider + rateLimits:(id)rateLimits { NSURLSession *session; @@ -42,17 +42,6 @@ @implementation SentryTransportFactory id requestManager = [[SentryQueueableRequestManager alloc] initWithSession:session]; - SentryHttpDateParser *httpDateParser = [[SentryHttpDateParser alloc] init]; - SentryRetryAfterHeaderParser *retryAfterHeaderParser = - [[SentryRetryAfterHeaderParser alloc] initWithHttpDateParser:httpDateParser - currentDateProvider:currentDateProvider]; - SentryRateLimitParser *rateLimitParser = - [[SentryRateLimitParser alloc] initWithCurrentDateProvider:currentDateProvider]; - id rateLimits = - [[SentryDefaultRateLimits alloc] initWithRetryAfterHeaderParser:retryAfterHeaderParser - andRateLimitParser:rateLimitParser - currentDateProvider:currentDateProvider]; - SentryEnvelopeRateLimit *envelopeRateLimit = [[SentryEnvelopeRateLimit alloc] initWithRateLimits:rateLimits]; diff --git a/Sources/Sentry/include/HybridPublic/SentryDependencyContainer.h b/Sources/Sentry/include/HybridPublic/SentryDependencyContainer.h index a506cc707a..a0b6791773 100644 --- a/Sources/Sentry/include/HybridPublic/SentryDependencyContainer.h +++ b/Sources/Sentry/include/HybridPublic/SentryDependencyContainer.h @@ -20,6 +20,7 @@ @class SentryThreadInspector; @protocol SentryRandom; @protocol SentryCurrentDateProvider; +@protocol SentryRateLimits; #if SENTRY_HAS_METRIC_KIT @class SentryMXManager; @@ -71,6 +72,7 @@ SENTRY_NO_INIT @property (nonatomic, strong) SentryExtraContextProvider *extraContextProvider; @property (nonatomic, strong) SentrySysctl *sysctlWrapper; @property (nonatomic, strong) SentryThreadInspector *threadInspector; +@property (nonatomic, strong) id rateLimits; #if SENTRY_UIKIT_AVAILABLE @property (nonatomic, strong) SentryFramesTracker *framesTracker; diff --git a/Sources/Sentry/include/SentryTransportFactory.h b/Sources/Sentry/include/SentryTransportFactory.h index da079af079..7568630803 100644 --- a/Sources/Sentry/include/SentryTransportFactory.h +++ b/Sources/Sentry/include/SentryTransportFactory.h @@ -4,6 +4,7 @@ @class SentryOptions, SentryFileManager; @protocol SentryCurrentDateProvider; +@protocol SentryRateLimits; NS_ASSUME_NONNULL_BEGIN @@ -12,7 +13,7 @@ NS_SWIFT_NAME(TransportInitializer) + (NSArray> *)initTransports:(SentryOptions *)options sentryFileManager:(SentryFileManager *)sentryFileManager - currentDateProvider:(id)currentDateProvider; + rateLimits:(id)rateLimits; @end diff --git a/Tests/SentryTests/Integrations/SessionReplay/SentrySessionReplayIntegrationTests.swift b/Tests/SentryTests/Integrations/SessionReplay/SentrySessionReplayIntegrationTests.swift index 10f4b740fa..ed9a6010c5 100644 --- a/Tests/SentryTests/Integrations/SessionReplay/SentrySessionReplayIntegrationTests.swift +++ b/Tests/SentryTests/Integrations/SessionReplay/SentrySessionReplayIntegrationTests.swift @@ -348,6 +348,110 @@ class SentrySessionReplayIntegrationTests: XCTestCase { XCTAssertEqual(sessionReplay.sessionReplayId, replayId) } + func testStopBecauseOfReplayRateLimit() throws { + let rateLimiter = TestRateLimits() + SentryDependencyContainer.sharedInstance().rateLimits = rateLimiter + rateLimiter.rateLimits.append(.replay) + + startSDK(sessionSampleRate: 1, errorSampleRate: 1) + let sut = try getSut() + let sessionReplay = sut.sessionReplay + + XCTAssertTrue(sessionReplay?.isRunning ?? false) + + let videoUrl = URL(fileURLWithPath: "video.mp4") + let videoInfo = SentryVideoInfo(path: videoUrl, height: 1_024, width: 480, duration: 5, frameCount: 5, frameRate: 1, start: Date(), end: Date(), fileSize: 10, screens: []) + let replayEvent = SentryReplayEvent(eventId: SentryId(), replayStartTimestamp: Date(), replayType: .session, segmentId: 0) + + (sut as SentrySessionReplayDelegate).sessionReplayNewSegment(replayEvent: replayEvent, + replayRecording: SentryReplayRecording(segmentId: 0, video: videoInfo, extraEvents: []), + videoUrl: videoUrl) + + XCTAssertFalse(sessionReplay?.isRunning ?? true) + XCTAssertNil(sut.sessionReplay) + } + + func testStopBecauseOfAllRateLimit() throws { + let rateLimiter = TestRateLimits() + SentryDependencyContainer.sharedInstance().rateLimits = rateLimiter + rateLimiter.rateLimits.append(.all) + + startSDK(sessionSampleRate: 1, errorSampleRate: 1) + let sut = try getSut() + let sessionReplay = sut.sessionReplay + + XCTAssertTrue(sessionReplay?.isRunning ?? false) + + let videoUrl = URL(fileURLWithPath: "video.mp4") + let videoInfo = SentryVideoInfo(path: videoUrl, height: 1_024, width: 480, duration: 5, frameCount: 5, frameRate: 1, start: Date(), end: Date(), fileSize: 10, screens: []) + let replayEvent = SentryReplayEvent(eventId: SentryId(), replayStartTimestamp: Date(), replayType: .session, segmentId: 0) + + (sut as SentrySessionReplayDelegate).sessionReplayNewSegment(replayEvent: replayEvent, + replayRecording: SentryReplayRecording(segmentId: 0, video: videoInfo, extraEvents: []), + videoUrl: videoUrl) + + XCTAssertFalse(sessionReplay?.isRunning ?? true) + XCTAssertNil(sut.sessionReplay) + } + + func testDontRestartAfterRateLimit() throws { + let rateLimiter = TestRateLimits() + SentryDependencyContainer.sharedInstance().rateLimits = rateLimiter + rateLimiter.rateLimits.append(.all) + + startSDK(sessionSampleRate: 1, errorSampleRate: 1) + let sut = try getSut() + let sessionReplay = sut.sessionReplay + + XCTAssertTrue(sessionReplay?.isRunning ?? false) + + let videoUrl = URL(fileURLWithPath: "video.mp4") + let videoInfo = SentryVideoInfo(path: videoUrl, height: 1_024, width: 480, duration: 5, frameCount: 5, frameRate: 1, start: Date(), end: Date(), fileSize: 10, screens: []) + let replayEvent = SentryReplayEvent(eventId: SentryId(), replayStartTimestamp: Date(), replayType: .session, segmentId: 0) + + (sut as SentrySessionReplayDelegate).sessionReplayNewSegment(replayEvent: replayEvent, + replayRecording: SentryReplayRecording(segmentId: 0, video: videoInfo, extraEvents: []), + videoUrl: videoUrl) + + XCTAssertFalse(sessionReplay?.isRunning ?? true) + XCTAssertNil(sut.sessionReplay) + + sut.start() + + XCTAssertFalse(sessionReplay?.isRunning ?? true) + XCTAssertNil(sut.sessionReplay) + } + + func testAlowStartForNewSessionAfterRateLimit() throws { + let rateLimiter = TestRateLimits() + SentryDependencyContainer.sharedInstance().rateLimits = rateLimiter + rateLimiter.rateLimits.append(.all) + + startSDK(sessionSampleRate: 0, errorSampleRate: 1) + let sut = try getSut() + let sessionReplay = sut.sessionReplay + sut.start() + + XCTAssertTrue(sessionReplay?.isRunning ?? false) + + let videoUrl = URL(fileURLWithPath: "video.mp4") + let videoInfo = SentryVideoInfo(path: videoUrl, height: 1_024, width: 480, duration: 5, frameCount: 5, frameRate: 1, start: Date(), end: Date(), fileSize: 10, screens: []) + let replayEvent = SentryReplayEvent(eventId: SentryId(), replayStartTimestamp: Date(), replayType: .session, segmentId: 0) + + (sut as SentrySessionReplayDelegate).sessionReplayNewSegment(replayEvent: replayEvent, + replayRecording: SentryReplayRecording(segmentId: 0, video: videoInfo, extraEvents: []), + videoUrl: videoUrl) + XCTAssertNil(sut.sessionReplay) + + sut.start() + XCTAssertNil(sut.sessionReplay) + + (sut as SentrySessionListener).sentrySessionStarted(SentrySession(releaseName: "", distinctId: "")) + + sut.start() + XCTAssertTrue(sut.sessionReplay?.isRunning ?? false) + } + func testStartWithBufferSessionReplay() throws { startSDK(sessionSampleRate: 0, errorSampleRate: 1) let sut = try getSut() diff --git a/Tests/SentryTests/Networking/SentryTransportFactoryTests.swift b/Tests/SentryTests/Networking/SentryTransportFactoryTests.swift index e98560a01c..96659a7ac6 100644 --- a/Tests/SentryTests/Networking/SentryTransportFactoryTests.swift +++ b/Tests/SentryTests/Networking/SentryTransportFactoryTests.swift @@ -5,7 +5,7 @@ import XCTest class SentryTransportFactoryTests: XCTestCase { private static let dsnAsString = TestConstants.dsnAsString(username: "SentryTransportFactoryTests") - + func testIntegration_UrlSessionDelegate_PassedToRequestManager() throws { let urlSessionDelegateSpy = UrlSessionDelegateSpy() @@ -19,7 +19,7 @@ class SentryTransportFactoryTests: XCTestCase { options.urlSessionDelegate = urlSessionDelegateSpy let fileManager = try! SentryFileManager(options: options, dispatchQueueWrapper: TestSentryDispatchQueueWrapper()) - let transports = TransportInitializer.initTransports(options, sentryFileManager: fileManager, currentDateProvider: TestCurrentDateProvider()) + let transports = TransportInitializer.initTransports(options, sentryFileManager: fileManager, rateLimits: rateLimiting()) let httpTransport = transports.first let requestManager = try XCTUnwrap(Dynamic(httpTransport).requestManager.asObject as? SentryQueueableRequestManager) @@ -44,7 +44,7 @@ class SentryTransportFactoryTests: XCTestCase { options.urlSession = sessionConfiguration let fileManager = try! SentryFileManager(options: options, dispatchQueueWrapper: TestSentryDispatchQueueWrapper()) - let transports = TransportInitializer.initTransports(options, sentryFileManager: fileManager, currentDateProvider: TestCurrentDateProvider()) + let transports = TransportInitializer.initTransports(options, sentryFileManager: fileManager, rateLimits: rateLimiting()) let httpTransport = transports.first let requestManager = try XCTUnwrap(Dynamic(httpTransport).requestManager.asObject as? SentryQueueableRequestManager) @@ -60,7 +60,7 @@ class SentryTransportFactoryTests: XCTestCase { func testShouldReturnTwoTransports_WhenSpotlightEnabled() throws { let options = Options() options.enableSpotlight = true - let transports = TransportInitializer.initTransports(options, sentryFileManager: try SentryFileManager(options: options), currentDateProvider: TestCurrentDateProvider()) + let transports = TransportInitializer.initTransports(options, sentryFileManager: try SentryFileManager(options: options), rateLimits: rateLimiting()) XCTAssert(transports.contains { $0.isKind(of: SentrySpotlightTransport.self) @@ -70,5 +70,13 @@ class SentryTransportFactoryTests: XCTestCase { $0.isKind(of: SentryHttpTransport.self) }) } + + func rateLimiting() -> RateLimits { + let dateProvider = TestCurrentDateProvider() + let retryAfterHeaderParser = RetryAfterHeaderParser(httpDateParser: HttpDateParser(), currentDateProvider: dateProvider) + let rateLimitParser = RateLimitParser(currentDateProvider: dateProvider) + + return DefaultRateLimits(retryAfterHeaderParser: retryAfterHeaderParser, andRateLimitParser: rateLimitParser, currentDateProvider: dateProvider) + } } diff --git a/Tests/SentryTests/Networking/SentryTransportInitializerTests.swift b/Tests/SentryTests/Networking/SentryTransportInitializerTests.swift index bd629b80be..6175b39d90 100644 --- a/Tests/SentryTests/Networking/SentryTransportInitializerTests.swift +++ b/Tests/SentryTests/Networking/SentryTransportInitializerTests.swift @@ -18,7 +18,7 @@ class SentryTransportInitializerTests: XCTestCase { func testDefault() throws { let options = try Options(dict: ["dsn": SentryTransportInitializerTests.dsnAsString]) - let result = TransportInitializer.initTransports(options, sentryFileManager: fileManager, currentDateProvider: TestCurrentDateProvider()) + let result = TransportInitializer.initTransports(options, sentryFileManager: fileManager, rateLimits: SentryDependencyContainer.sharedInstance().rateLimits) XCTAssertEqual(result.count, 1) let firstTransport = result.first From 7708b0b8abee75801abe0a32070e0674ea1d8315 Mon Sep 17 00:00:00 2001 From: Dhiogo Brustolin Date: Thu, 7 Nov 2024 14:19:31 +0100 Subject: [PATCH 08/10] fix: Session replay masking not working inside scroll view (#4498) Fixed calculation of scroll view children position for masking --- CHANGELOG.md | 4 +++ .../ViewControllers/TableViewController.swift | 1 + Sources/Swift/Tools/UIRedactBuilder.swift | 25 ++++++++++--------- .../SentryViewPhotographerTests.swift | 15 +++++++++++ 4 files changed, 33 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8cd929c553..7627467d8a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,10 @@ - Masking text with transparent text color (#4499) +### Fixes + +- Session replay masking not working inside scroll view (#4498) + ## 8.39.0 ### Removal of Experimental API diff --git a/Samples/iOS-Swift/iOS-Swift/ViewControllers/TableViewController.swift b/Samples/iOS-Swift/iOS-Swift/ViewControllers/TableViewController.swift index ae085943e7..2df83233c4 100644 --- a/Samples/iOS-Swift/iOS-Swift/ViewControllers/TableViewController.swift +++ b/Samples/iOS-Swift/iOS-Swift/ViewControllers/TableViewController.swift @@ -36,6 +36,7 @@ class TableViewController: UITableViewController { let w = 1.0 - (Double(indexPath.row) / 99) cell.backgroundColor = UIColor(white: CGFloat(w), alpha: 1) + cell.textLabel?.text = "Row #\(indexPath.row)" return cell } diff --git a/Sources/Swift/Tools/UIRedactBuilder.swift b/Sources/Swift/Tools/UIRedactBuilder.swift index 5e1bd3f985..45635df3a9 100644 --- a/Sources/Swift/Tools/UIRedactBuilder.swift +++ b/Sources/Swift/Tools/UIRedactBuilder.swift @@ -158,9 +158,9 @@ class UIRedactBuilder { var redactingRegions = [RedactRegion]() self.mapRedactRegion(fromView: view, + relativeTo: nil, redacting: &redactingRegions, - rootFrame: view.frame, - transform: CGAffineTransform.identity) + rootFrame: view.frame) var swiftUIRedact = [RedactRegion]() var otherRegions = [RedactRegion]() @@ -198,12 +198,12 @@ class UIRedactBuilder { return image.imageAsset?.value(forKey: "_containingBundle") == nil } - private func mapRedactRegion(fromView view: UIView, redacting: inout [RedactRegion], rootFrame: CGRect, transform: CGAffineTransform, forceRedact: Bool = false) { + private func mapRedactRegion(fromView view: UIView, relativeTo parentLayer: CALayer?, redacting: inout [RedactRegion], rootFrame: CGRect, forceRedact: Bool = false) { guard !redactClassesIdentifiers.isEmpty && !view.isHidden && view.alpha != 0 else { return } let layer = view.layer.presentation() ?? view.layer - let newTransform = concatenateTranform(transform, with: layer) + let newTransform = getTranform(from: layer, withParent: parentLayer) let ignore = !forceRedact && shouldIgnore(view: view) let swiftUI = SentryRedactViewHelper.shouldRedactSwiftUI(view) @@ -233,23 +233,24 @@ class UIRedactBuilder { redacting.append(RedactRegion(size: layer.bounds.size, transform: newTransform, type: .clipEnd)) } for subview in view.subviews.sorted(by: { $0.layer.zPosition < $1.layer.zPosition }) { - mapRedactRegion(fromView: subview, redacting: &redacting, rootFrame: rootFrame, transform: newTransform, forceRedact: enforceRedact) + mapRedactRegion(fromView: subview, relativeTo: layer, redacting: &redacting, rootFrame: rootFrame, forceRedact: enforceRedact) } if view.clipsToBounds { redacting.append(RedactRegion(size: layer.bounds.size, transform: newTransform, type: .clipBegin)) } } - + /** - Apply the layer transformation and position to given transformation. + Gets a transform that represents the layer global position. */ - private func concatenateTranform(_ transform: CGAffineTransform, with layer: CALayer) -> CGAffineTransform { + private func getTranform(from layer: CALayer, withParent parentLayer: CALayer?) -> CGAffineTransform { let size = layer.bounds.size - let layerMiddle = CGPoint(x: size.width * layer.anchorPoint.x, y: size.height * layer.anchorPoint.y) - - var newTransform = transform.translatedBy(x: layer.position.x, y: layer.position.y) + let anchorPoint = CGPoint(x: size.width * layer.anchorPoint.x, y: size.height * layer.anchorPoint.y) + let position = parentLayer?.convert(layer.position, to: nil) ?? layer.position + + var newTransform = CGAffineTransform(translationX: position.x, y: position.y) newTransform = CATransform3DGetAffineTransform(layer.transform).concatenating(newTransform) - return newTransform.translatedBy(x: -layerMiddle.x, y: -layerMiddle.y) + return newTransform.translatedBy(x: -anchorPoint.x, y: -anchorPoint.y) } /** diff --git a/Tests/SentryTests/SentryViewPhotographerTests.swift b/Tests/SentryTests/SentryViewPhotographerTests.swift index 69643f483d..fd85fe559f 100644 --- a/Tests/SentryTests/SentryViewPhotographerTests.swift +++ b/Tests/SentryTests/SentryViewPhotographerTests.swift @@ -195,6 +195,21 @@ class SentryViewPhotographerTests: XCTestCase { assertColor(pixel1, .green) } + func testLabelInsideScrollView() throws { + let label1 = UILabel(frame: CGRect(x: 0, y: 25, width: 50, height: 25)) + label1.text = "Test" + label1.textColor = .green + + let scrollView = UIScrollView(frame: CGRect(x: 0, y: 0, width: 50, height: 50)) + scrollView.addSubview(label1) + scrollView.contentOffset = CGPoint(x: 0, y: 25) + + let image = try XCTUnwrap(prepare(views: [scrollView])) + let pixel1 = color(at: CGPoint(x: 10, y: 10), in: image) + + assertColor(pixel1, .green) + } + private func assertColor(_ color1: UIColor, _ color2: UIColor) { let sRGBColor1 = color1.cgColor.converted(to: CGColorSpace(name: CGColorSpace.sRGB)!, intent: .defaultIntent, options: nil) let sRGBColor2 = color2.cgColor.converted(to: CGColorSpace(name: CGColorSpace.sRGB)!, intent: .defaultIntent, options: nil) From 84640bd31b5ebc4e6dc6119d2fc1237f52b7def2 Mon Sep 17 00:00:00 2001 From: Philipp Hofmann Date: Thu, 7 Nov 2024 14:21:49 +0100 Subject: [PATCH 09/10] Fix CHANGELOG.md (#4514) --- CHANGELOG.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7627467d8a..c7cbb80a7d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Fixes + +- Session replay masking not working inside scroll view (#4498) + ### Improvements - Add extra logs for UIViewControllerSwizzling (#4511) @@ -29,10 +33,6 @@ - Masking text with transparent text color (#4499) -### Fixes - -- Session replay masking not working inside scroll view (#4498) - ## 8.39.0 ### Removal of Experimental API From 66d05830543c17e66ed5fd7bfef9bea4a18a57cb Mon Sep 17 00:00:00 2001 From: Philipp Hofmann Date: Thu, 7 Nov 2024 15:40:29 +0100 Subject: [PATCH 10/10] chore: Format SRIntegration (#4515) --- Sources/Sentry/SentrySessionReplayIntegration.m | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Sources/Sentry/SentrySessionReplayIntegration.m b/Sources/Sentry/SentrySessionReplayIntegration.m index 3c5245dac7..85c1dd746a 100644 --- a/Sources/Sentry/SentrySessionReplayIntegration.m +++ b/Sources/Sentry/SentrySessionReplayIntegration.m @@ -48,8 +48,10 @@ @implementation SentrySessionReplayIntegration { SentryNSNotificationCenterWrapper *_notificationCenter; SentryOnDemandReplay *_resumeReplayMaker; id _rateLimits; - // We need to use this variable to identify whether rate limiting was ever activated for session replay in this session, instead of always looking for the rate status in `SentryRateLimits` - // This is the easiest way to ensure segment 0 will always reach the server, because session replay absolutely needs segment 0 to make replay work. + // We need to use this variable to identify whether rate limiting was ever activated for session + // replay in this session, instead of always looking for the rate status in `SentryRateLimits` + // This is the easiest way to ensure segment 0 will always reach the server, because session + // replay absolutely needs segment 0 to make replay work. BOOL _rateLimited; }