From c444e55ca643244467d01f96d7eb92038057123e Mon Sep 17 00:00:00 2001 From: Philip Niedertscheider Date: Fri, 11 Apr 2025 12:59:00 +0200 Subject: [PATCH 1/4] feat(session-replay): refactor session replay types and logging --- CHANGELOG.md | 4 ++ .../iOS-Swift/SentrySDKWrapper.swift | 7 +- Sentry.xcodeproj/project.pbxproj | 8 +++ .../Sentry/SentrySessionReplayIntegration.m | 28 ++++---- .../SessionReplay/SentryOnDemandReplay.swift | 64 ++++++++++--------- .../SentryOnDemandReplayError.swift | 6 ++ .../SessionReplay/SentryReplayFrame.swift | 7 ++ .../SessionReplay/SentrySessionReplay.swift | 16 +++-- 8 files changed, 91 insertions(+), 49 deletions(-) create mode 100644 Sources/Swift/Integrations/SessionReplay/SentryOnDemandReplayError.swift create mode 100644 Sources/Swift/Integrations/SessionReplay/SentryReplayFrame.swift diff --git a/CHANGELOG.md b/CHANGELOG.md index 67eeebb846a..30ad5ec7a3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ - Crash in setMeasurement when name is nil (#5064) - Make setMeasurement thread safe (#5067, #5078) +### Improvements + +- Refactor session replay types and logging + ## 8.49.0 ### Features diff --git a/Samples/iOS-Swift/iOS-Swift/SentrySDKWrapper.swift b/Samples/iOS-Swift/iOS-Swift/SentrySDKWrapper.swift index 1b7590fbef3..cf1119685d0 100644 --- a/Samples/iOS-Swift/iOS-Swift/SentrySDKWrapper.swift +++ b/Samples/iOS-Swift/iOS-Swift/SentrySDKWrapper.swift @@ -19,7 +19,12 @@ struct SentrySDKWrapper { options.debug = true if #available(iOS 16.0, *), enableSessionReplay { - options.sessionReplay = SentryReplayOptions(sessionSampleRate: 0, onErrorSampleRate: 1, maskAllText: true, maskAllImages: true) + options.sessionReplay = SentryReplayOptions( + sessionSampleRate: 0, + onErrorSampleRate: 1, + maskAllText: true, + maskAllImages: true + ) options.sessionReplay.quality = .high } diff --git a/Sentry.xcodeproj/project.pbxproj b/Sentry.xcodeproj/project.pbxproj index ef42675d0e1..e1cddf21f07 100644 --- a/Sentry.xcodeproj/project.pbxproj +++ b/Sentry.xcodeproj/project.pbxproj @@ -821,6 +821,8 @@ D43B26D62D70964C007747FD /* SentrySpanOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = D43B26D52D709648007747FD /* SentrySpanOperation.m */; }; D43B26D82D70A550007747FD /* SentryTraceOrigin.m in Sources */ = {isa = PBXBuildFile; fileRef = D43B26D72D70A54A007747FD /* SentryTraceOrigin.m */; }; D43B26DA2D70A612007747FD /* SentrySpanDataKey.m in Sources */ = {isa = PBXBuildFile; fileRef = D43B26D92D70A60E007747FD /* SentrySpanDataKey.m */; }; + D451ED5D2D92ECD200C9BEA8 /* SentryOnDemandReplayError.swift in Sources */ = {isa = PBXBuildFile; fileRef = D451ED5C2D92ECD200C9BEA8 /* SentryOnDemandReplayError.swift */; }; + D451ED5F2D92ECDE00C9BEA8 /* SentryReplayFrame.swift in Sources */ = {isa = PBXBuildFile; fileRef = D451ED5E2D92ECDE00C9BEA8 /* SentryReplayFrame.swift */; }; D456B4322D706BDF007068CB /* SentrySpanOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = D456B4312D706BDD007068CB /* SentrySpanOperation.h */; }; D456B4362D706BF2007068CB /* SentryTraceOrigin.h in Headers */ = {isa = PBXBuildFile; fileRef = D456B4352D706BEE007068CB /* SentryTraceOrigin.h */; }; D456B4382D706BFE007068CB /* SentrySpanDataKey.h in Headers */ = {isa = PBXBuildFile; fileRef = D456B4372D706BFB007068CB /* SentrySpanDataKey.h */; }; @@ -1981,6 +1983,8 @@ D43B26D52D709648007747FD /* SentrySpanOperation.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentrySpanOperation.m; sourceTree = ""; }; D43B26D72D70A54A007747FD /* SentryTraceOrigin.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryTraceOrigin.m; sourceTree = ""; }; D43B26D92D70A60E007747FD /* SentrySpanDataKey.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentrySpanDataKey.m; sourceTree = ""; }; + D451ED5C2D92ECD200C9BEA8 /* SentryOnDemandReplayError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryOnDemandReplayError.swift; sourceTree = ""; }; + D451ED5E2D92ECDE00C9BEA8 /* SentryReplayFrame.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryReplayFrame.swift; sourceTree = ""; }; D456B4312D706BDD007068CB /* SentrySpanOperation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentrySpanOperation.h; path = include/SentrySpanOperation.h; sourceTree = ""; }; D456B4352D706BEE007068CB /* SentryTraceOrigin.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryTraceOrigin.h; path = include/SentryTraceOrigin.h; sourceTree = ""; }; D456B4372D706BFB007068CB /* SentrySpanDataKey.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentrySpanDataKey.h; path = include/SentrySpanDataKey.h; sourceTree = ""; }; @@ -4265,6 +4269,8 @@ D8CAC02A2BA0663E00E38F34 /* SentryReplayOptions.swift */, D8CAC02B2BA0663E00E38F34 /* SentryVideoInfo.swift */, D802994D2BA836EF000F0081 /* SentryOnDemandReplay.swift */, + D451ED5E2D92ECDE00C9BEA8 /* SentryReplayFrame.swift */, + D451ED5C2D92ECD200C9BEA8 /* SentryOnDemandReplayError.swift */, D802994F2BA83A88000F0081 /* SentryPixelBuffer.swift */, D8AFC03C2BDA79BF00118BE1 /* SentryReplayVideoMaker.swift */, D8F67B1A2BE9728600C9197B /* SentrySRDefaultBreadcrumbConverter.swift */, @@ -5032,6 +5038,7 @@ 7B30B67E26527894006B2752 /* SentryDisplayLinkWrapper.m in Sources */, 63FE711D20DA4C1000CDBAE8 /* SentryCrashCPU_arm64.c in Sources */, 844EDC77294144DB00C86F34 /* SentrySystemWrapper.mm in Sources */, + D451ED5F2D92ECDE00C9BEA8 /* SentryReplayFrame.swift in Sources */, D8739CF92BECFFB5007D2F66 /* SentryTransactionNameSource.swift in Sources */, 630435FF1EBCA9D900C4D3FA /* SentryNSURLRequest.m in Sources */, 6281C5722D3E4F12009D0978 /* DecodeArbitraryData.swift in Sources */, @@ -5124,6 +5131,7 @@ D83D079C2B7F9D1C00CC9674 /* SentryMsgPackSerializer.m in Sources */, 7B6D1261265F784000C9BE4B /* PrivateSentrySDKOnly.mm in Sources */, 63BE85711ECEC6DE00DC44F5 /* SentryDateUtils.m in Sources */, + D451ED5D2D92ECD200C9BEA8 /* SentryOnDemandReplayError.swift in Sources */, D4E829D82D75E57900D375AD /* SentryMaskRenderer.swift in Sources */, 7BD4BD4927EB2A5D0071F4FF /* SentryDiscardedEvent.m in Sources */, 628308612D50ADAC00EAEF77 /* SentryRequestCodable.swift in Sources */, diff --git a/Sources/Sentry/SentrySessionReplayIntegration.m b/Sources/Sentry/SentrySessionReplayIntegration.m index 0eb4de59798..ae6be609878 100644 --- a/Sources/Sentry/SentrySessionReplayIntegration.m +++ b/Sources/Sentry/SentrySessionReplayIntegration.m @@ -57,6 +57,7 @@ @implementation SentrySessionReplayIntegration { // 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; + id _dateProvider; } - (instancetype)init @@ -120,6 +121,9 @@ - (void)setupWith:(SentryReplayOptions *)replayOptions } _notificationCenter = SentryDependencyContainer.sharedInstance.notificationCenterWrapper; + _dateProvider = SentryDependencyContainer.sharedInstance.dateProvider; + + // The asset worker queue is used to work on video and frames data. [self moveCurrentReplay]; [self cleanUp]; @@ -196,23 +200,23 @@ - (void)resumePreviousSessionReplay:(SentryEvent *)event NSDate *beginning = hasCrashInfo ? [NSDate dateWithTimeIntervalSinceReferenceDate:crashInfo.lastSegmentEnd] : [resumeReplayMaker oldestFrameDate]; - if (beginning == nil) { return; // no frames to send } - - SentryReplayType _type = type; - int _segmentId = segmentId; + NSDate *end = [beginning dateByAddingTimeInterval:duration]; NSError *error; - NSArray *videos = - [resumeReplayMaker createVideoWithBeginning:beginning - end:[beginning dateByAddingTimeInterval:duration] - error:&error]; + NSArray *videos = [resumeReplayMaker createVideoWithBeginning:beginning + end:end + error:&error]; if (videos == nil) { - SENTRY_LOG_ERROR(@"Could not create replay video: %@", error); + SENTRY_LOG_ERROR(@"Could not create replay video, reason: no videos available"); return; } + + // For each segment we need to create a new event with the video. + int _segmentId = segmentId; + SentryReplayType _type = type; for (SentryVideoInfo *video in videos) { [self captureVideo:video replayId:replayId segmentId:_segmentId++ type:_type]; // type buffer is only for the first segment @@ -224,8 +228,10 @@ - (void)resumePreviousSessionReplay:(SentryEvent *)event [NSDictionary dictionaryWithObjectsAndKeys:replayId.sentryIdString, @"replay_id", nil]; event.context = eventContext; - if ([NSFileManager.defaultManager removeItemAtURL:lastReplayURL error:&error] == NO) { - SENTRY_LOG_ERROR(@"Can`t delete '%@': %@", SENTRY_LAST_REPLAY, error); + NSError *_Nullable removeError; + BOOL result = [NSFileManager.defaultManager removeItemAtURL:lastReplayURL error:&removeError]; + if (result == NO) { + SENTRY_LOG_ERROR(@"Can`t delete '%@': %@", SENTRY_LAST_REPLAY, removeError); } } diff --git a/Sources/Swift/Integrations/SessionReplay/SentryOnDemandReplay.swift b/Sources/Swift/Integrations/SessionReplay/SentryOnDemandReplay.swift index 6017a98a5f9..34e0c5860e9 100644 --- a/Sources/Swift/Integrations/SessionReplay/SentryOnDemandReplay.swift +++ b/Sources/Swift/Integrations/SessionReplay/SentryOnDemandReplay.swift @@ -7,18 +7,6 @@ import CoreGraphics import Foundation import UIKit -struct SentryReplayFrame { - let imagePath: String - let time: Date - let screenName: String? -} - -enum SentryOnDemandReplayError: Error { - case cantReadVideoSize - case cantCreatePixelBuffer - case errorRenderingVideo -} - @objcMembers class SentryOnDemandReplay: NSObject, SentryReplayVideoMaker { @@ -27,7 +15,7 @@ class SentryOnDemandReplay: NSObject, SentryReplayVideoMaker { private let dateProvider: SentryCurrentDateProvider private let workingQueue: SentryDispatchQueueWrapper private var _frames = [SentryReplayFrame]() - + #if SENTRY_TEST || SENTRY_TEST_CI || DEBUG //This is exposed only for tests, no need to make it thread safe. var frames: [SentryReplayFrame] { @@ -48,20 +36,28 @@ class SentryOnDemandReplay: NSObject, SentryReplayVideoMaker { convenience init(withContentFrom outputPath: String, workingQueue: SentryDispatchQueueWrapper, dateProvider: SentryCurrentDateProvider) { self.init(outputPath: outputPath, workingQueue: workingQueue, dateProvider: dateProvider) - + loadFrames(fromPath: outputPath) + } + + /// Loads the frames from the given path. + /// + /// - Parameter path: The path to the directory containing the frames. + private func loadFrames(fromPath path: String) { + SentryLog.debug("[Session Replay] Loading frames from path: \(path)") do { - let content = try FileManager.default.contentsOfDirectory(atPath: outputPath) - _frames = content.compactMap { - guard $0.hasSuffix(".png") else { return SentryReplayFrame?.none } - guard let time = Double($0.dropLast(4)) else { return nil } - return SentryReplayFrame(imagePath: "\(outputPath)/\($0)", time: Date(timeIntervalSinceReferenceDate: time), screenName: nil) + let content = try FileManager.default.contentsOfDirectory(atPath: path) + _frames = content.compactMap { frameFilePath -> SentryReplayFrame? in + guard frameFilePath.hasSuffix(".png") else { return nil } + guard let time = Double(frameFilePath.dropLast(4)) else { return nil } + let timestamp = Date(timeIntervalSinceReferenceDate: time) + return SentryReplayFrame(imagePath: "\(path)/\(frameFilePath)", time: timestamp, screenName: nil) }.sorted { $0.time < $1.time } + SentryLog.debug("[Session Replay] Loaded \(content.count) files into \(_frames.count) frames from path: \(path)") } catch { - SentryLog.debug("Could not list frames from replay: \(error.localizedDescription)") - return + SentryLog.error("[Session Replay] Could not list frames from replay: \(error.localizedDescription)") } } - + convenience init(outputPath: String) { self.init(outputPath: outputPath, workingQueue: SentryDispatchQueueWrapper(name: "io.sentry.onDemandReplay", attributes: nil), @@ -73,7 +69,7 @@ class SentryOnDemandReplay: NSObject, SentryReplayVideoMaker { workingQueue: SentryDispatchQueueWrapper(name: "io.sentry.onDemandReplay", attributes: nil), dateProvider: SentryDefaultCurrentDateProvider()) } - + func addFrameAsync(image: UIImage, forScreen: String?) { workingQueue.dispatchAsync({ self.addFrame(image: image, forScreen: forScreen) @@ -88,11 +84,12 @@ class SentryOnDemandReplay: NSObject, SentryReplayVideoMaker { do { try data.write(to: URL(fileURLWithPath: imagePath)) } catch { - SentryLog.debug("Could not save replay frame. Error: \(error)") + SentryLog.error("[Session Replay] Could not save replay frame. Error: \(error)") return } _frames.append(SentryReplayFrame(imagePath: imagePath, time: date, screenName: forScreen)) - + + // Remove the oldest frames if the cache size exceeds the maximum size. while _frames.count > cacheMaxSize { let first = _frames.removeFirst() try? FileManager.default.removeItem(at: URL(fileURLWithPath: first.imagePath)) @@ -111,12 +108,17 @@ class SentryOnDemandReplay: NSObject, SentryReplayVideoMaker { } func releaseFramesUntil(_ date: Date) { - workingQueue.dispatchAsync ({ - while let first = self._frames.first, first.time < date { - self._frames.removeFirst() - try? FileManager.default.removeItem(at: URL(fileURLWithPath: first.imagePath)) + SentryLog.debug("[Session Replay] Releasing frames until date: \(date)") + while let first = self._frames.first, first.time < date { + self._frames.removeFirst() + let fileUrl = URL(fileURLWithPath: first.imagePath) + do { + try FileManager.default.removeItem(at: fileUrl) + SentryLog.debug("[Session Replay] Removed frame at url: \(fileUrl.path)") + } catch { + SentryLog.error("[Session Replay] Failed to remove frame at: \(fileUrl.path), reason: \(error.localizedDescription), ignoring error") } - }) + } } var oldestFrameDate: Date? { @@ -239,7 +241,7 @@ class SentryOnDemandReplay: NSObject, SentryReplayVideoMaker { }) return frames } - + private func createVideoSettings(width: CGFloat, height: CGFloat) -> [String: Any] { return [ AVVideoCodecKey: AVVideoCodecType.h264, diff --git a/Sources/Swift/Integrations/SessionReplay/SentryOnDemandReplayError.swift b/Sources/Swift/Integrations/SessionReplay/SentryOnDemandReplayError.swift new file mode 100644 index 00000000000..7b060ad97d1 --- /dev/null +++ b/Sources/Swift/Integrations/SessionReplay/SentryOnDemandReplayError.swift @@ -0,0 +1,6 @@ +enum SentryOnDemandReplayError: Error { + case cantReadVideoSize + case cantCreatePixelBuffer + case errorRenderingVideo + case cantReadVideoStartTime +} diff --git a/Sources/Swift/Integrations/SessionReplay/SentryReplayFrame.swift b/Sources/Swift/Integrations/SessionReplay/SentryReplayFrame.swift new file mode 100644 index 00000000000..0497b784f61 --- /dev/null +++ b/Sources/Swift/Integrations/SessionReplay/SentryReplayFrame.swift @@ -0,0 +1,7 @@ +import Foundation + +struct SentryReplayFrame { + let imagePath: String + let time: Date + let screenName: String? +} diff --git a/Sources/Swift/Integrations/SessionReplay/SentrySessionReplay.swift b/Sources/Swift/Integrations/SessionReplay/SentrySessionReplay.swift index 49e856871ab..648973f630f 100644 --- a/Sources/Swift/Integrations/SessionReplay/SentrySessionReplay.swift +++ b/Sources/Swift/Integrations/SessionReplay/SentrySessionReplay.swift @@ -131,7 +131,7 @@ class SentrySessionReplay: NSObject { videoSegmentStart = nil displayLink.link(withTarget: self, selector: #selector(newFrame(_:))) } - + func captureReplayFor(event: Event) { guard isRunning else { return } @@ -140,8 +140,9 @@ class SentrySessionReplay: NSObject { return } - guard (event.error != nil || event.exceptions?.isEmpty == false) - && captureReplay() else { return } + guard (event.error != nil || event.exceptions?.isEmpty == false) && captureReplay() else { + return + } setEventContext(event: event) } @@ -223,6 +224,7 @@ class SentrySessionReplay: NSObject { } private func createAndCapture(startedAt: Date, replayType: SentryReplayType) { + SentryLog.debug("[Session Replay] Creating replay video started at date: \(startedAt), replayType: \(replayType)") //Creating a video is heavy and blocks the thread //Since this function is always called in the main thread //we dispatch it to a background thread. @@ -232,6 +234,7 @@ class SentrySessionReplay: NSObject { for video in videos { self.newSegmentAvailable(videoInfo: video, replayType: replayType) } + SentryLog.debug("[Session Replay] Finished replay video creation with \(videos.count) segments") } catch { SentryLog.debug("Could not create replay video - \(error.localizedDescription)") } @@ -239,6 +242,7 @@ class SentrySessionReplay: NSObject { } private func newSegmentAvailable(videoInfo: SentryVideoInfo, replayType: SentryReplayType) { + SentryLog.debug("[Session Replay] New segment available for replayType: \(replayType), videoInfo: \(videoInfo)") guard let sessionReplayId = sessionReplayId else { return } captureSegment(segment: currentSegmentId, video: videoInfo, replayId: sessionReplayId, replayType: replayType) replayMaker.releaseFramesUntil(videoInfo.end) @@ -270,7 +274,7 @@ class SentrySessionReplay: NSObject { } let recording = SentryReplayRecording(segmentId: segment, video: video, extraEvents: events) - + delegate?.sessionReplayNewSegment(replayEvent: replayEvent, replayRecording: recording, videoUrl: video.path) do { @@ -302,7 +306,7 @@ class SentrySessionReplay: NSObject { private func takeScreenshot() { guard let rootView = rootView, !processingScreenshot else { return } - + lock.lock() guard !processingScreenshot else { lock.unlock() @@ -310,7 +314,7 @@ class SentrySessionReplay: NSObject { } processingScreenshot = true lock.unlock() - + let screenName = delegate?.currentScreenNameForSessionReplay() screenshotProvider.image(view: rootView) { [weak self] screenshot in From 403a55da57b306f6dd27b97676494fbdb42bf879 Mon Sep 17 00:00:00 2001 From: Philip Niedertscheider Date: Fri, 11 Apr 2025 13:02:24 +0200 Subject: [PATCH 2/4] WIP --- CHANGELOG.md | 4 ---- Sources/Sentry/SentrySessionReplayIntegration.m | 14 +++++++------- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 30ad5ec7a3c..67eeebb846a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,10 +7,6 @@ - Crash in setMeasurement when name is nil (#5064) - Make setMeasurement thread safe (#5067, #5078) -### Improvements - -- Refactor session replay types and logging - ## 8.49.0 ### Features diff --git a/Sources/Sentry/SentrySessionReplayIntegration.m b/Sources/Sentry/SentrySessionReplayIntegration.m index ae6be609878..7b6d2189a3c 100644 --- a/Sources/Sentry/SentrySessionReplayIntegration.m +++ b/Sources/Sentry/SentrySessionReplayIntegration.m @@ -98,6 +98,8 @@ - (void)setupWith:(SentryReplayOptions *)replayOptions { _replayOptions = replayOptions; _rateLimits = SentryDependencyContainer.sharedInstance.rateLimits; + _dateProvider = SentryDependencyContainer.sharedInstance.dateProvider; + id viewRenderer; if (enableExperimentalRenderer) { viewRenderer = [[SentryExperimentalViewRenderer alloc] @@ -114,14 +116,11 @@ - (void)setupWith:(SentryReplayOptions *)replayOptions enableExperimentalMaskRenderer:enableExperimentalRenderer]; if (touchTracker) { - _touchTracker = [[SentryTouchTracker alloc] - initWithDateProvider:SentryDependencyContainer.sharedInstance.dateProvider - scale:replayOptions.sizeScale]; + _touchTracker = [[SentryTouchTracker alloc] initWithDateProvider:_dateProvider + scale:replayOptions.sizeScale]; [self swizzleApplicationTouch]; } - _notificationCenter = SentryDependencyContainer.sharedInstance.notificationCenterWrapper; - _dateProvider = SentryDependencyContainer.sharedInstance.dateProvider; // The asset worker queue is used to work on video and frames data. @@ -210,7 +209,7 @@ - (void)resumePreviousSessionReplay:(SentryEvent *)event end:end error:&error]; if (videos == nil) { - SENTRY_LOG_ERROR(@"Could not create replay video, reason: no videos available"); + SENTRY_LOG_ERROR(@"Could not create replay video, reason: %@", error); return; } @@ -231,7 +230,8 @@ - (void)resumePreviousSessionReplay:(SentryEvent *)event NSError *_Nullable removeError; BOOL result = [NSFileManager.defaultManager removeItemAtURL:lastReplayURL error:&removeError]; if (result == NO) { - SENTRY_LOG_ERROR(@"Can`t delete '%@': %@", SENTRY_LAST_REPLAY, removeError); + SENTRY_LOG_ERROR( + @"Can`t delete file item at url: '%@', reason: %@", lastReplayURL, removeError); } } From 2347529f77cf81d99e223dddd794e6d99dea8871 Mon Sep 17 00:00:00 2001 From: Philip Niedertscheider Date: Fri, 11 Apr 2025 13:06:05 +0200 Subject: [PATCH 3/4] smaller changes --- Sources/Sentry/SentrySessionReplayIntegration.m | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Sources/Sentry/SentrySessionReplayIntegration.m b/Sources/Sentry/SentrySessionReplayIntegration.m index 7b6d2189a3c..100f431f4d2 100644 --- a/Sources/Sentry/SentrySessionReplayIntegration.m +++ b/Sources/Sentry/SentrySessionReplayIntegration.m @@ -120,6 +120,7 @@ - (void)setupWith:(SentryReplayOptions *)replayOptions scale:replayOptions.sizeScale]; [self swizzleApplicationTouch]; } + _notificationCenter = SentryDependencyContainer.sharedInstance.notificationCenterWrapper; // The asset worker queue is used to work on video and frames data. @@ -230,8 +231,8 @@ - (void)resumePreviousSessionReplay:(SentryEvent *)event NSError *_Nullable removeError; BOOL result = [NSFileManager.defaultManager removeItemAtURL:lastReplayURL error:&removeError]; if (result == NO) { - SENTRY_LOG_ERROR( - @"Can`t delete file item at url: '%@', reason: %@", lastReplayURL, removeError); + SENTRY_LOG_ERROR(@"Can't delete '%@' with file item at url: '%@', reason: %@", + SENTRY_LAST_REPLAY, lastReplayURL, removeError); } } From 4f73f9a6bf6b0d4d12607075e13bb56ca15f329c Mon Sep 17 00:00:00 2001 From: Philip Niedertscheider Date: Fri, 11 Apr 2025 13:10:06 +0200 Subject: [PATCH 4/4] WIP --- .../SessionReplay/SentryOnDemandReplay.swift | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/Sources/Swift/Integrations/SessionReplay/SentryOnDemandReplay.swift b/Sources/Swift/Integrations/SessionReplay/SentryOnDemandReplay.swift index 34e0c5860e9..d6b7cf7e35c 100644 --- a/Sources/Swift/Integrations/SessionReplay/SentryOnDemandReplay.swift +++ b/Sources/Swift/Integrations/SessionReplay/SentryOnDemandReplay.swift @@ -109,16 +109,18 @@ class SentryOnDemandReplay: NSObject, SentryReplayVideoMaker { func releaseFramesUntil(_ date: Date) { SentryLog.debug("[Session Replay] Releasing frames until date: \(date)") - while let first = self._frames.first, first.time < date { - self._frames.removeFirst() - let fileUrl = URL(fileURLWithPath: first.imagePath) - do { - try FileManager.default.removeItem(at: fileUrl) - SentryLog.debug("[Session Replay] Removed frame at url: \(fileUrl.path)") - } catch { - SentryLog.error("[Session Replay] Failed to remove frame at: \(fileUrl.path), reason: \(error.localizedDescription), ignoring error") + workingQueue.dispatchAsync ({ + while let first = self._frames.first, first.time < date { + self._frames.removeFirst() + let fileUrl = URL(fileURLWithPath: first.imagePath) + do { + try FileManager.default.removeItem(at: fileUrl) + SentryLog.debug("[Session Replay] Removed frame at url: \(fileUrl.path)") + } catch { + SentryLog.error("[Session Replay] Failed to remove frame at: \(fileUrl.path), reason: \(error.localizedDescription), ignoring error") + } } - } + }) } var oldestFrameDate: Date? {