2626#include " Convenience/SentryInclude.h"
2727#include " Convenience/SentryMacro.h"
2828
29+ #include " Utils/SentryFileUtils.h"
30+ #include " Utils/SentryLogUtils.h"
31+
2932#include " GenericPlatform/GenericPlatformOutputDevices.h"
3033#include " HAL/FileManager.h"
3134#include " HAL/PlatformSentryAttachment.h"
3437#include " Misc/Paths.h"
3538#include " UObject/GarbageCollection.h"
3639#include " UObject/UObjectThreadContext.h"
37- #include " Utils/SentryLogUtils.h"
3840
3941void FAppleSentrySubsystem::InitWithSettings (const USentrySettings* settings, USentryBeforeSendHandler* beforeSendHandler, USentryBeforeBreadcrumbHandler* beforeBreadcrumbHandler, USentryTraceSampler* traceSampler)
4042{
43+ isScreenshotAttachmentEnabled = settings->AttachScreenshot ;
44+ isGameLogAttachmentEnabled = settings->EnableAutoLogAttachment ;
45+
4146 [SENTRY_APPLE_CLASS (PrivateSentrySDKOnly) setSdkName:@" sentry.cocoa.unreal" ];
4247
4348 dispatch_group_t sentryDispatchGroup = dispatch_group_create ();
@@ -65,24 +70,18 @@ void FAppleSentrySubsystem::InitWithSettings(const USentrySettings* settings, US
6570#if SENTRY_UIKIT_AVAILABLE
6671 options.attachScreenshot = settings->AttachScreenshot ;
6772#endif
68- options.initialScope = ^(SentryScope* scope) {
69- if (settings->EnableAutoLogAttachment ) {
70- const FString logFilePath = IFileManager::Get ().ConvertToAbsolutePathForExternalAppForRead (*FGenericPlatformOutputDevices::GetAbsoluteLogFilename ());
71- SentryAttachment* logAttachment = [[SENTRY_APPLE_CLASS (SentryAttachment) alloc] initWithPath:logFilePath.GetNSString ()];
72- [scope addAttachment:logAttachment];
73- }
74- return scope;
75- };
7673 options.onCrashedLastRun = ^(SentryEvent* event) {
7774 if (settings->AttachScreenshot )
7875 {
7976 // If a screenshot was captured during assertion/crash in the previous app run
8077 // find the most recent one and upload it to Sentry.
81- const FString& screenshotPath = GetLatestScreenshot ();
82- if (!screenshotPath.IsEmpty ())
83- {
84- UploadScreenshotForEvent (MakeShareable (new SentryIdApple (event.eventId )), screenshotPath);
85- }
78+ UploadScreenshotForEvent (MakeShareable (new SentryIdApple (event.eventId )), GetLatestScreenshot ());
79+ }
80+ if (settings->EnableAutoLogAttachment )
81+ {
82+ // Unreal creates game log backups automatically on every app run. If logging is enabled for current configuration, SDK can
83+ // find the most recent one and upload it to Sentry.
84+ UploadGameLogForEvent (MakeShareable (new SentryIdApple (event.eventId )), GetLatestGameLog ());
8685 }
8786 };
8887 options.beforeSend = ^SentryEvent* (SentryEvent* event) {
@@ -206,40 +205,49 @@ void FAppleSentrySubsystem::ClearBreadcrumbs()
206205
207206TSharedPtr<ISentryId> FAppleSentrySubsystem::CaptureMessage (const FString& message, ESentryLevel level)
208207{
209- SentryId* id = [SENTRY_APPLE_CLASS (SentrySDK) captureMessage:message.GetNSString () withScopeBlock:^(SentryScope* scope){
210- [scope setLevel:SentryConvertersApple::SentryLevelToNative (level)];
211- }];
212-
213- return MakeShareable (new SentryIdApple (id));
208+ FSentryScopeDelegate onConfigureScope;
209+ return CaptureMessageWithScope (message, onConfigureScope, level);
214210}
215211
216212TSharedPtr<ISentryId> FAppleSentrySubsystem::CaptureMessageWithScope (const FString& message, const FSentryScopeDelegate& onConfigureScope, ESentryLevel level)
217213{
218- SentryId* id = [SENTRY_APPLE_CLASS (SentrySDK) captureMessage:message.GetNSString () withScopeBlock:^(SentryScope* scope){
214+ SentryId* nativeId = [SENTRY_APPLE_CLASS (SentrySDK) captureMessage:message.GetNSString () withScopeBlock:^(SentryScope* scope){
219215 [scope setLevel:SentryConvertersApple::SentryLevelToNative (level)];
220216 onConfigureScope.ExecuteIfBound (MakeShareable (new SentryScopeApple (scope)));
221217 }];
222218
223- return MakeShareable (new SentryIdApple (id));
219+ TSharedPtr<ISentryId> id = MakeShareable (new SentryIdApple (nativeId));
220+
221+ if (isGameLogAttachmentEnabled)
222+ {
223+ UploadGameLogForEvent (id, GetGameLogPath ());
224+ }
225+
226+ return id;
224227}
225228
226229TSharedPtr<ISentryId> FAppleSentrySubsystem::CaptureEvent (TSharedPtr<ISentryEvent> event)
227230{
228- TSharedPtr<SentryEventApple> eventIOS = StaticCastSharedPtr<SentryEventApple>(event);
229-
230- SentryId* id = [SENTRY_APPLE_CLASS (SentrySDK) captureEvent:eventIOS->GetNativeObject ()];
231- return MakeShareable (new SentryIdApple (id));
231+ FSentryScopeDelegate onConfigureScope;
232+ return CaptureEventWithScope (event, onConfigureScope);
232233}
233234
234235TSharedPtr<ISentryId> FAppleSentrySubsystem::CaptureEventWithScope (TSharedPtr<ISentryEvent> event, const FSentryScopeDelegate& onConfigureScope)
235236{
236- TSharedPtr<SentryEventApple> eventIOS = StaticCastSharedPtr<SentryEventApple>(event);
237+ TSharedPtr<SentryEventApple> eventApple = StaticCastSharedPtr<SentryEventApple>(event);
237238
238- SentryId* id = [SENTRY_APPLE_CLASS (SentrySDK) captureEvent:eventIOS ->GetNativeObject () withScopeBlock:^(SentryScope* scope) {
239+ SentryId* nativeId = [SENTRY_APPLE_CLASS (SentrySDK) captureEvent:eventApple ->GetNativeObject () withScopeBlock:^(SentryScope* scope) {
239240 onConfigureScope.ExecuteIfBound (MakeShareable (new SentryScopeApple (scope)));
240241 }];
241242
242- return MakeShareable (new SentryIdApple (id));
243+ TSharedPtr<ISentryId> id = MakeShareable (new SentryIdApple (nativeId));
244+
245+ if (isGameLogAttachmentEnabled)
246+ {
247+ UploadGameLogForEvent (id, GetGameLogPath ());
248+ }
249+
250+ return id;
243251}
244252
245253TSharedPtr<ISentryId> FAppleSentrySubsystem::CaptureEnsure (const FString& type, const FString& message)
@@ -251,8 +259,16 @@ TSharedPtr<ISentryId> FAppleSentrySubsystem::CaptureEnsure(const FString& type,
251259 SentryEvent *exceptionEvent = [[SENTRY_APPLE_CLASS (SentryEvent) alloc] init];
252260 exceptionEvent.exceptions = nativeExceptionArray;
253261
254- SentryId* id = [SENTRY_APPLE_CLASS (SentrySDK) captureEvent:exceptionEvent];
255- return MakeShareable (new SentryIdApple (id));
262+ SentryId* nativeId = [SENTRY_APPLE_CLASS (SentrySDK) captureEvent:exceptionEvent];
263+
264+ TSharedPtr<ISentryId> id = MakeShareable (new SentryIdApple (nativeId));
265+
266+ if (isGameLogAttachmentEnabled)
267+ {
268+ UploadGameLogForEvent (id, GetGameLogPath ());
269+ }
270+
271+ return id;
256272}
257273
258274
@@ -389,37 +405,60 @@ TSharedPtr<ISentryTransactionContext> FAppleSentrySubsystem::ContinueTrace(const
389405 return MakeShareable (new SentryTransactionContextApple (transactionContext));
390406}
391407
392- void FAppleSentrySubsystem::UploadScreenshotForEvent (TSharedPtr<ISentryId> eventId, const FString& screenshotPath ) const
408+ void FAppleSentrySubsystem::UploadAttachmentForEvent (TSharedPtr<ISentryId> eventId, const FString& filePath, const FString& name, bool deleteAfterUpload ) const
393409{
394410 IFileManager& fileManager = IFileManager::Get ();
395- if (!fileManager.FileExists (*screenshotPath ))
411+ if (!fileManager.FileExists (*filePath ))
396412 {
397- UE_LOG (LogSentrySdk, Error, TEXT (" Failed to upload screenshot - path provided did not exist: %s" ), *screenshotPath );
413+ UE_LOG (LogSentrySdk, Error, TEXT (" Failed to upload attachment - file path provided did not exist: %s" ), *filePath );
398414 return ;
399415 }
400416
401- const FString& screenshotFilePathExt = fileManager.ConvertToAbsolutePathForExternalAppForRead (*screenshotPath );
417+ const FString& filePathExt = fileManager.ConvertToAbsolutePathForExternalAppForRead (*filePath );
402418
403- SentryAttachment* screenshotAttachment = [[SENTRY_APPLE_CLASS (SentryAttachment) alloc] initWithPath:screenshotFilePathExt .GetNSString () filename:@ " screenshot.png " ];
419+ SentryAttachment* attachment = [[SENTRY_APPLE_CLASS (SentryAttachment) alloc] initWithPath:filePathExt .GetNSString () filename:name. GetNSString () ];
404420
405421 SentryOptions* options = [SENTRY_APPLE_CLASS (PrivateSentrySDKOnly) options];
406422 int32 size = options.maxAttachmentSize ;
407423
408- SentryEnvelopeItem* envelopeItem = [[SENTRY_APPLE_CLASS (SentryEnvelopeItem) alloc] initWithAttachment:screenshotAttachment maxAttachmentSize:size];
424+ SentryEnvelopeItem* envelopeItem = [[SENTRY_APPLE_CLASS (SentryEnvelopeItem) alloc] initWithAttachment:attachment maxAttachmentSize:size];
409425
410426 SentryId* id = StaticCastSharedPtr<SentryIdApple>(eventId)->GetNativeObject ();
411427
412428 SentryEnvelope* envelope = [[SENTRY_APPLE_CLASS (SentryEnvelope) alloc] initWithId:id singleItem:envelopeItem];
413429
414430 [SENTRY_APPLE_CLASS (PrivateSentrySDKOnly) captureEnvelope:envelope];
415431
416- // After uploading screenshot it's no longer needed so delete
417- if (!fileManager.Delete (*screenshotPath))
432+ if (deleteAfterUpload)
418433 {
419- UE_LOG (LogSentrySdk, Error, TEXT (" Failed to delete screenshot: %s" ), *screenshotPath);
434+ if (!fileManager.Delete (*filePath))
435+ {
436+ UE_LOG (LogSentrySdk, Error, TEXT (" Failed to delete file attachment after upload: %s" ), *filePath);
437+ }
420438 }
421439}
422440
441+ void FAppleSentrySubsystem::UploadScreenshotForEvent (TSharedPtr<ISentryId> eventId, const FString& screenshotPath) const
442+ {
443+ if (screenshotPath.IsEmpty ())
444+ {
445+ // Screenshot capturing is a best-effort solution so if one wasn't captured (path is empty) skip the upload
446+ return ;
447+ }
448+
449+ UploadAttachmentForEvent (eventId, screenshotPath, TEXT (" screenshot.png" ), true );
450+ }
451+
452+ void FAppleSentrySubsystem::UploadGameLogForEvent (TSharedPtr<ISentryId> eventId, const FString& logFilePath) const
453+ {
454+ #if NO_LOGGING
455+ // If writing logs to a file is disabled (i.e. default behavior for Shipping builds) skip the upload
456+ return ;
457+ #endif
458+
459+ UploadAttachmentForEvent (eventId, logFilePath, SentryFileUtils::GetGameLogName ());
460+ }
461+
423462FString FAppleSentrySubsystem::GetScreenshotPath () const
424463{
425464 return FPaths::Combine (FPaths::ProjectSavedDir (), TEXT (" SentryScreenshots" ), FString::Printf (TEXT (" screenshot-%s.png" ), *FDateTime::Now ().ToString ()));
0 commit comments