Skip to content

Add screenshot capturing for Mac/iOS #849

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 20 commits into from
Apr 16, 2025
Merged

Add screenshot capturing for Mac/iOS #849

merged 20 commits into from
Apr 16, 2025

Conversation

tustanivsky
Copy link
Collaborator

@tustanivsky tustanivsky commented Mar 28, 2025

This PR adds automatic screenshot capturing for crash events on Mac and iOS.

Due to current Cocoa SDK limitations the underlying native library can capture screenshots only for certain event types depending on the platforms:

  • On Mac, screenshot capturing is supported for ensures/assertions/crashes using custom implementation (the approach similar to one previously taken for Windows and Linux in Add opt-in screenshot attachment for crash events captured on Windows/Linux #582).
  • On iOS, screenshot capturing is supported for ensures using build-int sentry-cocoa functionality and for assertions/crashes using custom implementation.

The actual screenshot attachment is sent to Sentry in a separate envelope which's constructed upon the next app launch after it crashed. This may change in future when at least one of the following Cocoa SDK tickets will be resolved:

Related to #567

Copy link
Contributor

github-actions bot commented Mar 28, 2025

Messages
📖 Do not forget to update Sentry-docs with your feature once the pull request gets approved.

Generated by 🚫 dangerJS against 6d1a1c7

@tustanivsky tustanivsky marked this pull request as ready for review April 11, 2025 07:39
Copy link
Member

@bruno-garcia bruno-garcia left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found a few areas that could be improved before we go ahead

FString backupFilePath = FString::Printf(TEXT("%s%s%s.%s"), *name, TEXT("-backup-"), *originalTime.ToString(), *extension);
if (!fileManager.Move(*backupFilePath, *screenshotFilePath, true))
{
UE_LOG(LogSentrySdk, Error, TEXT("Failed to backup screenshot."));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need to make a backup of the file?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No more backups - now we delete screenshot right after it was sent to Sentry.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're certain those screenshots were taken by us, right? No file deletion of other types of attachment where we could be deleting someone's files?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Screenshots are saved separately in Saved/SentryScreenshots dir so deleting them after sending to Sentry should be safe.


FString FIOSSentrySubsystem::GetScreenshotPath() const
{
return FPaths::Combine(FPaths::ProjectSavedDir(), TEXT("screenshot.png"));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like we'll create conflicting file names.
Should we use something pseudo random here on the file name? We can return the file name around (I mean we are, here doing that already)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair point - I've updated it so that we now append the event ID to the file name when one is already available (e.g. in the case of ensures).

For asserts/crashes we still fall back to using just screenshot.png so that the file can be easily found on the next app launch and attached to the corresponding event.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could consider any file that startWith("screenshot_") for example, and use a timestamp after _.
This way we are sure to never override anything accidently.


if (isScreenshotAttachmentEnabled)
{
TryCaptureScreenshot();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should Try.. return bool to indicate whether it was successful? We can skip the next method call altogether if we know it failed

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Depending on the platform TryCaptureScreenshot may run asynchronously (i.e. on iOS screenshot capturing happens on the main thread) so returning a bool immediately won't always work as expected.
While we could wait for the operation to complete that's probably not ideal during crash handling. In that context, it's better to make a best-effort attempt to capture the screen without blocking.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But we call UploadScreenshotForEvent without synchronizing at all.
is it likely the screenshot will be ready by the time we call UploadScreenshotForEvent ?
We just check for the file path existing after but I imagine it's likely not as the code will run much faster than a screenshot can be taken + file I/O

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright, now I see where the confusion is coming from - TryCaptureScreenshot() is a virtual void method defined in parent AppleSentrySubsystem which behaves synchronously on Mac and asynchronously on iOS.

Probably makes it worth considering separating the two implementations given their different nature.


FString FIOSSentrySubsystem::GetScreenshotPath() const
{
return FPaths::Combine(FPaths::ProjectSavedDir(), TEXT("screenshot.png"));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could consider any file that startWith("screenshot_") for example, and use a timestamp after _.
This way we are sure to never override anything accidently.


if (isScreenshotAttachmentEnabled)
{
TryCaptureScreenshot();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But we call UploadScreenshotForEvent without synchronizing at all.
is it likely the screenshot will be ready by the time we call UploadScreenshotForEvent ?
We just check for the file path existing after but I imagine it's likely not as the code will run much faster than a screenshot can be taken + file I/O

@tustanivsky tustanivsky merged commit c8c2ac0 into main Apr 16, 2025
30 checks passed
@tustanivsky tustanivsky deleted the feat/ios-screenshot branch April 16, 2025 11:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants