Skip to content

Commit 4dc30df

Browse files
Add opt-in screenshot attachment for crash events captured on Windows/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]>
1 parent e26f8d4 commit 4dc30df

8 files changed

+121
-11
lines changed

CHANGELOG.md

+3-5
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
### Features
66

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

910
### Fixes
@@ -12,12 +13,9 @@
1213

1314
### Dependencies
1415

15-
- Bump Cocoa SDK (iOS) from v8.29.0 to v8.29.1 ([#580](https://github.com/getsentry/sentry-unreal/pull/580))
16-
- [changelog](https://github.com/getsentry/sentry-cocoa/blob/main/CHANGELOG.md#8291)
17-
- [diff](https://github.com/getsentry/sentry-cocoa/compare/8.29.0...8.29.1)
18-
- Bump Cocoa SDK (iOS) from v8.29.1 to v8.30.0 ([#585](https://github.com/getsentry/sentry-unreal/pull/585))
16+
- 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))
1917
- [changelog](https://github.com/getsentry/sentry-cocoa/blob/main/CHANGELOG.md#8300)
20-
- [diff](https://github.com/getsentry/sentry-cocoa/compare/8.29.1...8.30.0)
18+
- [diff](https://github.com/getsentry/sentry-cocoa/compare/8.29.0...8.30.0)
2119

2220
## 0.18.0
2321

plugin-dev/Source/Sentry/Private/Desktop/SentrySubsystemDesktop.cpp

+34
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
#include "SentryTransactionContext.h"
2222
#include "SentryUserFeedbackDesktop.h"
2323

24+
#include "Utils/SentryScreenshotUtils.h"
25+
2426
#include "Infrastructure/SentryConvertorsDesktop.h"
2527

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

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

@@ -117,6 +120,7 @@ SentrySubsystemDesktop::SentrySubsystemDesktop()
117120
, crashReporter(MakeShareable(new SentryCrashReporter))
118121
, isEnabled(false)
119122
, isStackTraceEnabled(true)
123+
, isScreenshotAttachmentEnabled(false)
120124
{
121125
}
122126

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

146+
if(settings->AttachScreenshot)
147+
{
148+
isScreenshotAttachmentEnabled = true;
149+
150+
#if PLATFORM_WINDOWS
151+
sentry_options_add_attachmentw(options, *GetScreenshotPath());
152+
#elif PLATFORM_LINUX
153+
sentry_options_add_attachment(options, TCHAR_TO_UTF8(*GetScreenshotPath()));
154+
#endif
155+
}
156+
142157
if(settings->UseProxy)
143158
{
144159
sentry_options_set_http_proxy(options, TCHAR_TO_ANSI(*settings->ProxyUrl));
@@ -460,6 +475,17 @@ USentryBeforeSendHandler* SentrySubsystemDesktop::GetBeforeSendHandler()
460475
return beforeSend;
461476
}
462477

478+
void SentrySubsystemDesktop::TryCaptureScreenshot() const
479+
{
480+
if(!isScreenshotAttachmentEnabled)
481+
{
482+
UE_LOG(LogSentrySdk, Log, TEXT("Screenshot attachment is disabled in plugin settings."));
483+
return;
484+
}
485+
486+
SentryScreenshotUtils::CaptureScreenshot(GetScreenshotPath());
487+
}
488+
463489
TSharedPtr<SentryScopeDesktop> SentrySubsystemDesktop::GetCurrentScope()
464490
{
465491
if(scopeStack.Num() == 0)
@@ -493,4 +519,12 @@ FString SentrySubsystemDesktop::GetDatabasePath() const
493519
return DatabaseFullPath;
494520
}
495521

522+
FString SentrySubsystemDesktop::GetScreenshotPath() const
523+
{
524+
const FString ScreenshotPath = FPaths::Combine(GetDatabasePath(), TEXT("screenshots"), TEXT("crash_screenshot.png"));
525+
const FString ScreenshotFullPath = FPaths::ConvertRelativePathToFull(ScreenshotPath);
526+
527+
return ScreenshotFullPath;
528+
}
529+
496530
#endif

plugin-dev/Source/Sentry/Private/Desktop/SentrySubsystemDesktop.h

+4
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,14 @@ class SentrySubsystemDesktop : public ISentrySubsystem
4646

4747
USentryBeforeSendHandler* GetBeforeSendHandler();
4848

49+
void TryCaptureScreenshot() const;
50+
4951
TSharedPtr<SentryScopeDesktop> GetCurrentScope();
5052

5153
private:
5254
FString GetHandlerPath() const;
5355
FString GetDatabasePath() const;
56+
FString GetScreenshotPath() const;
5457

5558
USentryBeforeSendHandler* beforeSend;
5659

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

6366
bool isStackTraceEnabled;
67+
bool isScreenshotAttachmentEnabled;
6468

6569
FCriticalSection CriticalSection;
6670
};

plugin-dev/Source/Sentry/Private/SentrySubsystem.cpp

+6-6
Original file line numberDiff line numberDiff line change
@@ -217,15 +217,15 @@ USentryId* USentrySubsystem::CaptureMessage(const FString& Message, ESentryLevel
217217

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

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

228-
return SubsystemNativeImpl->CaptureMessageWithScope(Message, OnConfigureScope, Level);
228+
return SubsystemNativeImpl->CaptureMessageWithScope(Message, OnConfigureScope, Level);
229229
}
230230

231231
USentryId* USentrySubsystem::CaptureEvent(USentryEvent* Event)
@@ -238,15 +238,15 @@ USentryId* USentrySubsystem::CaptureEvent(USentryEvent* Event)
238238

239239
USentryId* USentrySubsystem::CaptureEventWithScope(USentryEvent* Event, const FConfigureScopeDelegate& OnConfigureScope)
240240
{
241-
return CaptureEventWithScope(Event, FConfigureScopeNativeDelegate::CreateUFunction(const_cast<UObject*>(OnConfigureScope.GetUObject()), OnConfigureScope.GetFunctionName()));
241+
return CaptureEventWithScope(Event, FConfigureScopeNativeDelegate::CreateUFunction(const_cast<UObject*>(OnConfigureScope.GetUObject()), OnConfigureScope.GetFunctionName()));
242242
}
243243

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

249-
return SubsystemNativeImpl->CaptureEventWithScope(Event, OnConfigureScope);
249+
return SubsystemNativeImpl->CaptureEventWithScope(Event, OnConfigureScope);
250250
}
251251

252252
void USentrySubsystem::CaptureUserFeedback(USentryUserFeedback* UserFeedback)
@@ -286,15 +286,15 @@ void USentrySubsystem::RemoveUser()
286286

287287
void USentrySubsystem::ConfigureScope(const FConfigureScopeDelegate& OnConfigureScope)
288288
{
289-
ConfigureScope(FConfigureScopeNativeDelegate::CreateUFunction(const_cast<UObject*>(OnConfigureScope.GetUObject()), OnConfigureScope.GetFunctionName()));
289+
ConfigureScope(FConfigureScopeNativeDelegate::CreateUFunction(const_cast<UObject*>(OnConfigureScope.GetUObject()), OnConfigureScope.GetFunctionName()));
290290
}
291291

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

297-
SubsystemNativeImpl->ConfigureScope(OnConfigureScope);
297+
SubsystemNativeImpl->ConfigureScope(OnConfigureScope);
298298
}
299299

300300
void USentrySubsystem::SetContext(const FString& Key, const TMap<FString, FString>& Values)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// Copyright (c) 2024 Sentry. All Rights Reserved.
2+
3+
#include "SentryScreenshotUtils.h"
4+
5+
#include "HighResScreenshot.h"
6+
#include "SentryDefines.h"
7+
8+
#include "Engine/Engine.h"
9+
#include "ImageUtils.h"
10+
#include "UnrealClient.h"
11+
#include "Misc/FileHelper.h"
12+
#include "Engine/GameViewportClient.h"
13+
#include "Framework/Application/SlateApplication.h"
14+
15+
bool SentryScreenshotUtils::CaptureScreenshot(const FString& ScreenshotSavePath)
16+
{
17+
if (!GEngine || !GEngine->GameViewport)
18+
{
19+
UE_LOG(LogSentrySdk, Error, TEXT("GameViewport required for screenshot capturing is not valid"));
20+
return false;
21+
}
22+
23+
UGameViewportClient* GameViewportClient = GEngine->GameViewport;
24+
if (!GameViewportClient)
25+
{
26+
UE_LOG(LogSentrySdk, Error, TEXT("Game Viewport Client required for screenshot capturing is not valid"));
27+
return false;
28+
}
29+
30+
FIntVector ViewportSize(GameViewportClient->Viewport->GetSizeXY().X, GameViewportClient->Viewport->GetSizeXY().Y, 0);
31+
32+
TArray<FColor> Bitmap;
33+
34+
if (!FSlateApplication::IsInitialized())
35+
{
36+
UE_LOG(LogSentrySdk, Error, TEXT("Slate application required for screenshot capturing is not initialized"));
37+
return false;
38+
}
39+
40+
TSharedPtr<SWindow> WindowPtr = GameViewportClient->GetWindow();
41+
TSharedRef<SWidget> WindowRef = WindowPtr.ToSharedRef();
42+
43+
bool bScreenshotSuccessful = FSlateApplication::Get().TakeScreenshot(WindowRef, Bitmap, ViewportSize);
44+
if (!bScreenshotSuccessful)
45+
{
46+
UE_LOG(LogSentrySdk, Error, TEXT("Failed to capture screenshot"));
47+
return false;
48+
}
49+
50+
GetHighResScreenshotConfig().MergeMaskIntoAlpha(Bitmap, FIntRect());
51+
52+
TArray64<uint8> CompressedBitmap;
53+
FImageUtils::PNGCompressImageArray(ViewportSize.X, ViewportSize.Y, Bitmap, CompressedBitmap);
54+
FFileHelper::SaveArrayToFile(CompressedBitmap, *ScreenshotSavePath);
55+
56+
UE_LOG(LogSentrySdk, Log, TEXT("Screenshot saved to: %s"), *ScreenshotSavePath);
57+
58+
return true;
59+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// Copyright (c) 2024 Sentry. All Rights Reserved.
2+
3+
#pragma once
4+
5+
#include "CoreMinimal.h"
6+
7+
class SentryScreenshotUtils
8+
{
9+
public:
10+
static bool CaptureScreenshot(const FString& ScreenshotSavePath);
11+
};

scripts/packaging/package-github.snapshot

+2
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,8 @@ Source/Sentry/Private/Tests/SentryUser.spec.cpp
169169
Source/Sentry/Private/Utils/
170170
Source/Sentry/Private/Utils/SentryFileUtils.cpp
171171
Source/Sentry/Private/Utils/SentryFileUtils.h
172+
Source/Sentry/Private/Utils/SentryScreenshotUtils.cpp
173+
Source/Sentry/Private/Utils/SentryScreenshotUtils.h
172174
Source/Sentry/Public/
173175
Source/Sentry/Public/SentryAttachment.h
174176
Source/Sentry/Public/SentryBeforeSendHandler.h

scripts/packaging/package-marketplace.snapshot

+2
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,8 @@ Source/Sentry/Private/Tests/SentryUser.spec.cpp
167167
Source/Sentry/Private/Utils/
168168
Source/Sentry/Private/Utils/SentryFileUtils.cpp
169169
Source/Sentry/Private/Utils/SentryFileUtils.h
170+
Source/Sentry/Private/Utils/SentryScreenshotUtils.cpp
171+
Source/Sentry/Private/Utils/SentryScreenshotUtils.h
170172
Source/Sentry/Public/
171173
Source/Sentry/Public/SentryAttachment.h
172174
Source/Sentry/Public/SentryBeforeSendHandler.h

0 commit comments

Comments
 (0)