Skip to content
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

Add opt-in screenshot attachment for crash events captured on Windows/Linux #582

Merged
merged 11 commits into from
Jul 1, 2024
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

### Features

- Add opt-in screenshot attachment for crash events captured on Windows/Linux ([#582](https://github.com/getsentry/sentry-unreal/pull/582))
tustanivsky marked this conversation as resolved.
Show resolved Hide resolved

### Dependencies

- Bump Cocoa SDK (iOS) from v8.29.0 to v8.29.1 ([#580](https://github.com/getsentry/sentry-unreal/pull/580))
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 @@ -77,6 +79,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 @@ -107,6 +110,7 @@ SentrySubsystemDesktop::SentrySubsystemDesktop()
, crashReporter(MakeShareable(new SentryCrashReporter))
, isEnabled(false)
, isStackTraceEnabled(true)
, isScreenshotAttachmentEnabled(false)
{
}

Expand All @@ -129,6 +133,19 @@ void SentrySubsystemDesktop::InitWithSettings(const USentrySettings* settings, U
#endif
}

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

const FString ScreenshotPath = FGenericPlatformOutputDevices::GetAbsoluteLogFilename();
tustanivsky marked this conversation as resolved.
Show resolved Hide resolved

#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 @@ -450,6 +467,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 @@ -483,4 +511,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
Copy link
Contributor

Choose a reason for hiding this comment

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

Changes like this reeeally make me think we should put a linter in the repo.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

We'll probably cover this in scope of #575

Original file line number Diff line number Diff line change
Expand Up @@ -206,15 +206,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 @@ -227,15 +227,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 @@ -275,15 +275,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
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
Loading