Skip to content

Commit

Permalink
Add opt-in screenshot attachment for crash events captured on Windows…
Browse files Browse the repository at this point in the history
…/Linux (#582)

* Add screenshot attachment for desktop crash events

* Fix includes

* Fix more includes

* Fix indents

* Add ability to capture screenshot including game UI

* Update snapshot

* Add missing include

* Update changelog

* Update CHANGELOG.md

Co-authored-by: Stefan Jandl <[email protected]>

* Remove redundant variable

---------

Co-authored-by: Stefan Jandl <[email protected]>
  • Loading branch information
tustanivsky and bitsandfoxes authored Jul 1, 2024
1 parent e26f8d4 commit 4dc30df
Show file tree
Hide file tree
Showing 8 changed files with 121 additions and 11 deletions.
8 changes: 3 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

### Features

- On Windows/Linux the SDK can now automatically attach a screenshot to crash events([#582](https://github.com/getsentry/sentry-unreal/pull/582))
- Add API allowing to check if captured event is ANR error ([#581](https://github.com/getsentry/sentry-unreal/pull/581))

### Fixes
Expand All @@ -12,12 +13,9 @@

### Dependencies

- Bump Cocoa SDK (iOS) from v8.29.0 to v8.29.1 ([#580](https://github.com/getsentry/sentry-unreal/pull/580))
- [changelog](https://github.com/getsentry/sentry-cocoa/blob/main/CHANGELOG.md#8291)
- [diff](https://github.com/getsentry/sentry-cocoa/compare/8.29.0...8.29.1)
- Bump Cocoa SDK (iOS) from v8.29.1 to v8.30.0 ([#585](https://github.com/getsentry/sentry-unreal/pull/585))
- Bump Cocoa SDK (iOS) from v8.29.0 to v8.30.0 ([#580](https://github.com/getsentry/sentry-unreal/pull/580), [#585](https://github.com/getsentry/sentry-unreal/pull/585))
- [changelog](https://github.com/getsentry/sentry-cocoa/blob/main/CHANGELOG.md#8300)
- [diff](https://github.com/getsentry/sentry-cocoa/compare/8.29.1...8.30.0)
- [diff](https://github.com/getsentry/sentry-cocoa/compare/8.29.0...8.30.0)

## 0.18.0

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
#include "SentryTransactionContext.h"
#include "SentryUserFeedbackDesktop.h"

#include "Utils/SentryScreenshotUtils.h"

#include "Infrastructure/SentryConvertorsDesktop.h"

#include "CrashReporter/SentryCrashReporter.h"
Expand Down Expand Up @@ -87,6 +89,7 @@ sentry_value_t HandleBeforeSend(sentry_value_t event, void *hint, void *closure)
sentry_value_t HandleBeforeCrash(const sentry_ucontext_t *uctx, sentry_value_t event, void *closure)
{
SentrySubsystemDesktop* SentrySubsystem = static_cast<SentrySubsystemDesktop*>(closure);
SentrySubsystem->TryCaptureScreenshot();

FSentryCrashContext::Get()->Apply(SentrySubsystem->GetCurrentScope());

Expand Down Expand Up @@ -117,6 +120,7 @@ SentrySubsystemDesktop::SentrySubsystemDesktop()
, crashReporter(MakeShareable(new SentryCrashReporter))
, isEnabled(false)
, isStackTraceEnabled(true)
, isScreenshotAttachmentEnabled(false)
{
}

Expand All @@ -139,6 +143,17 @@ void SentrySubsystemDesktop::InitWithSettings(const USentrySettings* settings, U
#endif
}

if(settings->AttachScreenshot)
{
isScreenshotAttachmentEnabled = true;

#if PLATFORM_WINDOWS
sentry_options_add_attachmentw(options, *GetScreenshotPath());
#elif PLATFORM_LINUX
sentry_options_add_attachment(options, TCHAR_TO_UTF8(*GetScreenshotPath()));
#endif
}

if(settings->UseProxy)
{
sentry_options_set_http_proxy(options, TCHAR_TO_ANSI(*settings->ProxyUrl));
Expand Down Expand Up @@ -460,6 +475,17 @@ USentryBeforeSendHandler* SentrySubsystemDesktop::GetBeforeSendHandler()
return beforeSend;
}

void SentrySubsystemDesktop::TryCaptureScreenshot() const
{
if(!isScreenshotAttachmentEnabled)
{
UE_LOG(LogSentrySdk, Log, TEXT("Screenshot attachment is disabled in plugin settings."));
return;
}

SentryScreenshotUtils::CaptureScreenshot(GetScreenshotPath());
}

TSharedPtr<SentryScopeDesktop> SentrySubsystemDesktop::GetCurrentScope()
{
if(scopeStack.Num() == 0)
Expand Down Expand Up @@ -493,4 +519,12 @@ FString SentrySubsystemDesktop::GetDatabasePath() const
return DatabaseFullPath;
}

FString SentrySubsystemDesktop::GetScreenshotPath() const
{
const FString ScreenshotPath = FPaths::Combine(GetDatabasePath(), TEXT("screenshots"), TEXT("crash_screenshot.png"));
const FString ScreenshotFullPath = FPaths::ConvertRelativePathToFull(ScreenshotPath);

return ScreenshotFullPath;
}

#endif
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,14 @@ class SentrySubsystemDesktop : public ISentrySubsystem

USentryBeforeSendHandler* GetBeforeSendHandler();

void TryCaptureScreenshot() const;

TSharedPtr<SentryScopeDesktop> GetCurrentScope();

private:
FString GetHandlerPath() const;
FString GetDatabasePath() const;
FString GetScreenshotPath() const;

USentryBeforeSendHandler* beforeSend;

Expand All @@ -61,6 +64,7 @@ class SentrySubsystemDesktop : public ISentrySubsystem
bool isEnabled;

bool isStackTraceEnabled;
bool isScreenshotAttachmentEnabled;

FCriticalSection CriticalSection;
};
Expand Down
12 changes: 6 additions & 6 deletions plugin-dev/Source/Sentry/Private/SentrySubsystem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -217,15 +217,15 @@ USentryId* USentrySubsystem::CaptureMessage(const FString& Message, ESentryLevel

USentryId* USentrySubsystem::CaptureMessageWithScope(const FString& Message, const FConfigureScopeDelegate& OnConfigureScope, ESentryLevel Level)
{
return CaptureMessageWithScope(Message, FConfigureScopeNativeDelegate::CreateUFunction(const_cast<UObject*>(OnConfigureScope.GetUObject()), OnConfigureScope.GetFunctionName()), Level);
return CaptureMessageWithScope(Message, FConfigureScopeNativeDelegate::CreateUFunction(const_cast<UObject*>(OnConfigureScope.GetUObject()), OnConfigureScope.GetFunctionName()), Level);
}

USentryId* USentrySubsystem::CaptureMessageWithScope(const FString& Message, const FConfigureScopeNativeDelegate& OnConfigureScope, ESentryLevel Level)
{
if (!SubsystemNativeImpl || !SubsystemNativeImpl->IsEnabled())
return nullptr;

return SubsystemNativeImpl->CaptureMessageWithScope(Message, OnConfigureScope, Level);
return SubsystemNativeImpl->CaptureMessageWithScope(Message, OnConfigureScope, Level);
}

USentryId* USentrySubsystem::CaptureEvent(USentryEvent* Event)
Expand All @@ -238,15 +238,15 @@ USentryId* USentrySubsystem::CaptureEvent(USentryEvent* Event)

USentryId* USentrySubsystem::CaptureEventWithScope(USentryEvent* Event, const FConfigureScopeDelegate& OnConfigureScope)
{
return CaptureEventWithScope(Event, FConfigureScopeNativeDelegate::CreateUFunction(const_cast<UObject*>(OnConfigureScope.GetUObject()), OnConfigureScope.GetFunctionName()));
return CaptureEventWithScope(Event, FConfigureScopeNativeDelegate::CreateUFunction(const_cast<UObject*>(OnConfigureScope.GetUObject()), OnConfigureScope.GetFunctionName()));
}

USentryId* USentrySubsystem::CaptureEventWithScope(USentryEvent* Event, const FConfigureScopeNativeDelegate& OnConfigureScope)
{
if (!SubsystemNativeImpl || !SubsystemNativeImpl->IsEnabled())
return nullptr;

return SubsystemNativeImpl->CaptureEventWithScope(Event, OnConfigureScope);
return SubsystemNativeImpl->CaptureEventWithScope(Event, OnConfigureScope);
}

void USentrySubsystem::CaptureUserFeedback(USentryUserFeedback* UserFeedback)
Expand Down Expand Up @@ -286,15 +286,15 @@ void USentrySubsystem::RemoveUser()

void USentrySubsystem::ConfigureScope(const FConfigureScopeDelegate& OnConfigureScope)
{
ConfigureScope(FConfigureScopeNativeDelegate::CreateUFunction(const_cast<UObject*>(OnConfigureScope.GetUObject()), OnConfigureScope.GetFunctionName()));
ConfigureScope(FConfigureScopeNativeDelegate::CreateUFunction(const_cast<UObject*>(OnConfigureScope.GetUObject()), OnConfigureScope.GetFunctionName()));
}

void USentrySubsystem::ConfigureScope(const FConfigureScopeNativeDelegate& OnConfigureScope)
{
if (!SubsystemNativeImpl || !SubsystemNativeImpl->IsEnabled())
return;

SubsystemNativeImpl->ConfigureScope(OnConfigureScope);
SubsystemNativeImpl->ConfigureScope(OnConfigureScope);
}

void USentrySubsystem::SetContext(const FString& Key, const TMap<FString, FString>& Values)
Expand Down
59 changes: 59 additions & 0 deletions plugin-dev/Source/Sentry/Private/Utils/SentryScreenshotUtils.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Copyright (c) 2024 Sentry. All Rights Reserved.

#include "SentryScreenshotUtils.h"

#include "HighResScreenshot.h"
#include "SentryDefines.h"

#include "Engine/Engine.h"
#include "ImageUtils.h"
#include "UnrealClient.h"
#include "Misc/FileHelper.h"
#include "Engine/GameViewportClient.h"
#include "Framework/Application/SlateApplication.h"

bool SentryScreenshotUtils::CaptureScreenshot(const FString& ScreenshotSavePath)
{
if (!GEngine || !GEngine->GameViewport)
{
UE_LOG(LogSentrySdk, Error, TEXT("GameViewport required for screenshot capturing is not valid"));
return false;
}

UGameViewportClient* GameViewportClient = GEngine->GameViewport;
if (!GameViewportClient)
{
UE_LOG(LogSentrySdk, Error, TEXT("Game Viewport Client required for screenshot capturing is not valid"));
return false;
}

FIntVector ViewportSize(GameViewportClient->Viewport->GetSizeXY().X, GameViewportClient->Viewport->GetSizeXY().Y, 0);

TArray<FColor> Bitmap;

if (!FSlateApplication::IsInitialized())
{
UE_LOG(LogSentrySdk, Error, TEXT("Slate application required for screenshot capturing is not initialized"));
return false;
}

TSharedPtr<SWindow> WindowPtr = GameViewportClient->GetWindow();
TSharedRef<SWidget> WindowRef = WindowPtr.ToSharedRef();

bool bScreenshotSuccessful = FSlateApplication::Get().TakeScreenshot(WindowRef, Bitmap, ViewportSize);
if (!bScreenshotSuccessful)
{
UE_LOG(LogSentrySdk, Error, TEXT("Failed to capture screenshot"));
return false;
}

GetHighResScreenshotConfig().MergeMaskIntoAlpha(Bitmap, FIntRect());

TArray64<uint8> CompressedBitmap;
FImageUtils::PNGCompressImageArray(ViewportSize.X, ViewportSize.Y, Bitmap, CompressedBitmap);
FFileHelper::SaveArrayToFile(CompressedBitmap, *ScreenshotSavePath);

UE_LOG(LogSentrySdk, Log, TEXT("Screenshot saved to: %s"), *ScreenshotSavePath);

return true;
}
11 changes: 11 additions & 0 deletions plugin-dev/Source/Sentry/Private/Utils/SentryScreenshotUtils.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Copyright (c) 2024 Sentry. All Rights Reserved.

#pragma once

#include "CoreMinimal.h"

class SentryScreenshotUtils
{
public:
static bool CaptureScreenshot(const FString& ScreenshotSavePath);
};
2 changes: 2 additions & 0 deletions scripts/packaging/package-github.snapshot
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,8 @@ Source/Sentry/Private/Tests/SentryUser.spec.cpp
Source/Sentry/Private/Utils/
Source/Sentry/Private/Utils/SentryFileUtils.cpp
Source/Sentry/Private/Utils/SentryFileUtils.h
Source/Sentry/Private/Utils/SentryScreenshotUtils.cpp
Source/Sentry/Private/Utils/SentryScreenshotUtils.h
Source/Sentry/Public/
Source/Sentry/Public/SentryAttachment.h
Source/Sentry/Public/SentryBeforeSendHandler.h
Expand Down
2 changes: 2 additions & 0 deletions scripts/packaging/package-marketplace.snapshot
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,8 @@ Source/Sentry/Private/Tests/SentryUser.spec.cpp
Source/Sentry/Private/Utils/
Source/Sentry/Private/Utils/SentryFileUtils.cpp
Source/Sentry/Private/Utils/SentryFileUtils.h
Source/Sentry/Private/Utils/SentryScreenshotUtils.cpp
Source/Sentry/Private/Utils/SentryScreenshotUtils.h
Source/Sentry/Public/
Source/Sentry/Public/SentryAttachment.h
Source/Sentry/Public/SentryBeforeSendHandler.h
Expand Down

0 comments on commit 4dc30df

Please sign in to comment.