From cb1209ce432affdb77733030d094ef8591395ad6 Mon Sep 17 00:00:00 2001 From: Ivan Tustanivskyi Date: Wed, 15 Jan 2025 14:48:42 +0200 Subject: [PATCH 1/7] Replace log file path getter with utility method --- .../Source/Sentry/Private/Apple/AppleSentrySubsystem.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/plugin-dev/Source/Sentry/Private/Apple/AppleSentrySubsystem.cpp b/plugin-dev/Source/Sentry/Private/Apple/AppleSentrySubsystem.cpp index ef66084a7..4edcfd056 100644 --- a/plugin-dev/Source/Sentry/Private/Apple/AppleSentrySubsystem.cpp +++ b/plugin-dev/Source/Sentry/Private/Apple/AppleSentrySubsystem.cpp @@ -24,11 +24,13 @@ #include "Convenience/SentryInclude.h" #include "Convenience/SentryMacro.h" +#include "Utils/SentryFileUtils.h" +#include "Utils/SentryLogUtils.h" + #include "GenericPlatform/GenericPlatformOutputDevices.h" #include "HAL/FileManager.h" #include "UObject/GarbageCollection.h" #include "UObject/UObjectThreadContext.h" -#include "Utils/SentryLogUtils.h" void FAppleSentrySubsystem::InitWithSettings(const USentrySettings* settings, USentryBeforeSendHandler* beforeSendHandler, USentryTraceSampler* traceSampler) { @@ -60,7 +62,7 @@ void FAppleSentrySubsystem::InitWithSettings(const USentrySettings* settings, US #endif options.initialScope = ^(SentryScope* scope) { if(settings->EnableAutoLogAttachment) { - const FString logFilePath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*FGenericPlatformOutputDevices::GetAbsoluteLogFilename()); + const FString logFilePath = SentryFileUtils::GetGameLogPath(); SentryAttachment* logAttachment = [[SENTRY_APPLE_CLASS(SentryAttachment) alloc] initWithPath:logFilePath.GetNSString()]; [scope addAttachment:logAttachment]; } From 015b187280508ef9e2ffc8e5643e1500bf275897 Mon Sep 17 00:00:00 2001 From: Ivan Tustanivskyi Date: Thu, 17 Apr 2025 14:10:15 +0300 Subject: [PATCH 2/7] Update log file attachment for crashes --- .../Private/Apple/AppleSentrySubsystem.cpp | 46 ++++++++++++------- .../Private/Apple/AppleSentrySubsystem.h | 2 +- .../Sentry/Private/Mac/MacSentrySubsystem.cpp | 2 +- 3 files changed, 31 insertions(+), 19 deletions(-) diff --git a/plugin-dev/Source/Sentry/Private/Apple/AppleSentrySubsystem.cpp b/plugin-dev/Source/Sentry/Private/Apple/AppleSentrySubsystem.cpp index 6718aebcd..e2cb3a196 100644 --- a/plugin-dev/Source/Sentry/Private/Apple/AppleSentrySubsystem.cpp +++ b/plugin-dev/Source/Sentry/Private/Apple/AppleSentrySubsystem.cpp @@ -67,14 +67,6 @@ void FAppleSentrySubsystem::InitWithSettings(const USentrySettings* settings, US #if SENTRY_UIKIT_AVAILABLE options.attachScreenshot = settings->AttachScreenshot; #endif - options.initialScope = ^(SentryScope* scope) { - if(settings->EnableAutoLogAttachment) { - const FString logFilePath = SentryFileUtils::GetGameLogPath(); - SentryAttachment* logAttachment = [[SENTRY_APPLE_CLASS(SentryAttachment) alloc] initWithPath:logFilePath.GetNSString()]; - [scope addAttachment:logAttachment]; - } - return scope; - }; options.onCrashedLastRun = ^(SentryEvent* event) { if (settings->AttachScreenshot) { @@ -83,11 +75,29 @@ void FAppleSentrySubsystem::InitWithSettings(const USentrySettings* settings, US const FString& screenshotPath = GetLatestScreenshot(); if (!screenshotPath.IsEmpty()) { - UploadScreenshotForEvent(MakeShareable(new SentryIdApple(event.eventId)), screenshotPath); + UploadAttachmentForEvent(MakeShareable(new SentryIdApple(event.eventId)), screenshotPath, TEXT("screenshot.png"), true); + } + } + if(settings->EnableAutoLogAttachment) { + // Unreal creates game log backups automatically on every app run. If logging is enabled for current configuration SDK can + // find the most recent one and upload it to Sentry. + const FString logFilePath = SentryFileUtils::GetGameLogBackupPath(); + if (!logFilePath.IsEmpty()) + { + UploadAttachmentForEvent(MakeShareable(new SentryIdApple(event.eventId)), logFilePath, FPaths::GetCleanFilename(logFilePath)); } } }; options.beforeSend = ^SentryEvent* (SentryEvent* event) { + // TODO: check if crash + if(settings->EnableAutoLogAttachment) { + const FString logFilePath = SentryFileUtils::GetGameLogPath(); + if (!logFilePath.IsEmpty()) + { + UploadAttachmentForEvent(MakeShareable(new SentryIdApple(event.eventId)), logFilePath, FPaths::GetCleanFilename(logFilePath)); + } + } + if (FUObjectThreadContext::Get().IsRoutingPostLoad) { UE_LOG(LogSentrySdk, Log, TEXT("Executing `beforeSend` handler is not allowed when post-loading.")); @@ -391,18 +401,18 @@ TSharedPtr FAppleSentrySubsystem::ContinueTrace(const return MakeShareable(new SentryTransactionContextApple(transactionContext)); } -void FAppleSentrySubsystem::UploadScreenshotForEvent(TSharedPtr eventId, const FString& screenshotPath) const +void FAppleSentrySubsystem::UploadAttachmentForEvent(TSharedPtr eventId, const FString& filePath, const FString& name, bool deleteAfterUpload) const { IFileManager& fileManager = IFileManager::Get(); - if (!fileManager.FileExists(*screenshotPath)) + if (!fileManager.FileExists(*filePath)) { - UE_LOG(LogSentrySdk, Error, TEXT("Failed to upload screenshot - path provided did not exist: %s"), *screenshotPath); + UE_LOG(LogSentrySdk, Error, TEXT("Failed to upload attachment - file path provided did not exist: %s"), *filePath); return; } - const FString& screenshotFilePathExt = fileManager.ConvertToAbsolutePathForExternalAppForRead(*screenshotPath); + const FString& filePathExt = fileManager.ConvertToAbsolutePathForExternalAppForRead(*filePath); - SentryAttachment* screenshotAttachment = [[SENTRY_APPLE_CLASS(SentryAttachment) alloc] initWithPath:screenshotFilePathExt.GetNSString() filename:@"screenshot.png"]; + SentryAttachment* screenshotAttachment = [[SENTRY_APPLE_CLASS(SentryAttachment) alloc] initWithPath:filePathExt.GetNSString() filename:name.GetNSString()]; SentryOptions* options = [SENTRY_APPLE_CLASS(PrivateSentrySDKOnly) options]; int32 size = options.maxAttachmentSize; @@ -415,10 +425,12 @@ void FAppleSentrySubsystem::UploadScreenshotForEvent(TSharedPtr event [SENTRY_APPLE_CLASS(PrivateSentrySDKOnly) captureEnvelope:envelope]; - // After uploading screenshot it's no longer needed so delete - if (!fileManager.Delete(*screenshotPath)) + if (deleteAfterUpload) { - UE_LOG(LogSentrySdk, Error, TEXT("Failed to delete screenshot: %s"), *screenshotPath); + if (!fileManager.Delete(*filePath)) + { + UE_LOG(LogSentrySdk, Error, TEXT("Failed to delete file attachment after upload: %s"), *filePath); + } } } diff --git a/plugin-dev/Source/Sentry/Private/Apple/AppleSentrySubsystem.h b/plugin-dev/Source/Sentry/Private/Apple/AppleSentrySubsystem.h index 8ed556d6e..cf1c6f7c8 100644 --- a/plugin-dev/Source/Sentry/Private/Apple/AppleSentrySubsystem.h +++ b/plugin-dev/Source/Sentry/Private/Apple/AppleSentrySubsystem.h @@ -38,7 +38,7 @@ class FAppleSentrySubsystem : public ISentrySubsystem virtual FString TryCaptureScreenshot() const { return FString(); }; protected: - void UploadScreenshotForEvent(TSharedPtr eventId, const FString& screenshotPath) const; + void UploadAttachmentForEvent(TSharedPtr eventId, const FString& filePath, const FString& name, bool deleteAfterUpload = false) const; virtual FString GetScreenshotPath() const; virtual FString GetLatestScreenshot() const; diff --git a/plugin-dev/Source/Sentry/Private/Mac/MacSentrySubsystem.cpp b/plugin-dev/Source/Sentry/Private/Mac/MacSentrySubsystem.cpp index d780af6b0..3370079f0 100644 --- a/plugin-dev/Source/Sentry/Private/Mac/MacSentrySubsystem.cpp +++ b/plugin-dev/Source/Sentry/Private/Mac/MacSentrySubsystem.cpp @@ -35,7 +35,7 @@ TSharedPtr FMacSentrySubsystem::CaptureEnsure(const FString& type, co const FString& screenshotPath = TryCaptureScreenshot(); if (!screenshotPath.IsEmpty()) { - UploadScreenshotForEvent(id, screenshotPath); + UploadAttachmentForEvent(id, screenshotPath, TEXT("screenshot.png"), true); } } From 665e97483f236b5ff212e01857758199312830c2 Mon Sep 17 00:00:00 2001 From: Ivan Tustanivskyi Date: Thu, 17 Apr 2025 20:52:18 +0300 Subject: [PATCH 3/7] Fix redundant log file attached to crash events --- .../Private/Apple/AppleSentrySubsystem.cpp | 46 +++++++++++-------- .../Private/Apple/AppleSentrySubsystem.h | 4 ++ .../Sentry/Private/Mac/MacSentrySubsystem.cpp | 4 +- .../Sentry/Private/Mac/MacSentrySubsystem.h | 3 -- .../Sentry/Private/Utils/SentryFileUtils.cpp | 5 ++ .../Sentry/Private/Utils/SentryFileUtils.h | 1 + 6 files changed, 37 insertions(+), 26 deletions(-) diff --git a/plugin-dev/Source/Sentry/Private/Apple/AppleSentrySubsystem.cpp b/plugin-dev/Source/Sentry/Private/Apple/AppleSentrySubsystem.cpp index e2cb3a196..b751c91be 100644 --- a/plugin-dev/Source/Sentry/Private/Apple/AppleSentrySubsystem.cpp +++ b/plugin-dev/Source/Sentry/Private/Apple/AppleSentrySubsystem.cpp @@ -40,6 +40,9 @@ void FAppleSentrySubsystem::InitWithSettings(const USentrySettings* settings, USentryBeforeSendHandler* beforeSendHandler, USentryBeforeBreadcrumbHandler* beforeBreadcrumbHandler, USentryTraceSampler* traceSampler) { + isScreenshotAttachmentEnabled = settings->AttachScreenshot; + isGameLogAttachmentEnabled = settings->EnableAutoLogAttachment; + [SENTRY_APPLE_CLASS(PrivateSentrySDKOnly) setSdkName:@"sentry.cocoa.unreal"]; dispatch_group_t sentryDispatchGroup = dispatch_group_create(); @@ -84,20 +87,11 @@ void FAppleSentrySubsystem::InitWithSettings(const USentrySettings* settings, US const FString logFilePath = SentryFileUtils::GetGameLogBackupPath(); if (!logFilePath.IsEmpty()) { - UploadAttachmentForEvent(MakeShareable(new SentryIdApple(event.eventId)), logFilePath, FPaths::GetCleanFilename(logFilePath)); + UploadAttachmentForEvent(MakeShareable(new SentryIdApple(event.eventId)), logFilePath, SentryFileUtils::GetGameLogName()); } } }; options.beforeSend = ^SentryEvent* (SentryEvent* event) { - // TODO: check if crash - if(settings->EnableAutoLogAttachment) { - const FString logFilePath = SentryFileUtils::GetGameLogPath(); - if (!logFilePath.IsEmpty()) - { - UploadAttachmentForEvent(MakeShareable(new SentryIdApple(event.eventId)), logFilePath, FPaths::GetCleanFilename(logFilePath)); - } - } - if (FUObjectThreadContext::Get().IsRoutingPostLoad) { UE_LOG(LogSentrySdk, Log, TEXT("Executing `beforeSend` handler is not allowed when post-loading.")); @@ -218,17 +212,19 @@ void FAppleSentrySubsystem::ClearBreadcrumbs() TSharedPtr FAppleSentrySubsystem::CaptureMessage(const FString& message, ESentryLevel level) { - SentryId* id = [SENTRY_APPLE_CLASS(SentrySDK) captureMessage:message.GetNSString() withScopeBlock:^(SentryScope* scope){ - [scope setLevel:SentryConvertersApple::SentryLevelToNative(level)]; - }]; - - return MakeShareable(new SentryIdApple(id)); + FSentryScopeDelegate onConfigureScope; + return CaptureMessageWithScope(message, onConfigureScope, level); } TSharedPtr FAppleSentrySubsystem::CaptureMessageWithScope(const FString& message, const FSentryScopeDelegate& onConfigureScope, ESentryLevel level) { SentryId* id = [SENTRY_APPLE_CLASS(SentrySDK) captureMessage:message.GetNSString() withScopeBlock:^(SentryScope* scope){ [scope setLevel:SentryConvertersApple::SentryLevelToNative(level)]; + if(isGameLogAttachmentEnabled) { + const FString logFilePath = SentryFileUtils::GetGameLogPath(); + SentryAttachment* logAttachment = [[SENTRY_APPLE_CLASS(SentryAttachment) alloc] initWithPath:logFilePath.GetNSString()]; + [scope addAttachment:logAttachment]; + } onConfigureScope.ExecuteIfBound(MakeShareable(new SentryScopeApple(scope))); }]; @@ -237,10 +233,8 @@ TSharedPtr FAppleSentrySubsystem::CaptureMessageWithScope(const FStri TSharedPtr FAppleSentrySubsystem::CaptureEvent(TSharedPtr event) { - TSharedPtr eventIOS = StaticCastSharedPtr(event); - - SentryId* id = [SENTRY_APPLE_CLASS(SentrySDK) captureEvent:eventIOS->GetNativeObject()]; - return MakeShareable(new SentryIdApple(id)); + FSentryScopeDelegate onConfigureScope; + return CaptureEventWithScope(event, onConfigureScope); } TSharedPtr FAppleSentrySubsystem::CaptureEventWithScope(TSharedPtr event, const FSentryScopeDelegate& onConfigureScope) @@ -248,6 +242,11 @@ TSharedPtr FAppleSentrySubsystem::CaptureEventWithScope(TSharedPtr eventIOS = StaticCastSharedPtr(event); SentryId* id = [SENTRY_APPLE_CLASS(SentrySDK) captureEvent:eventIOS->GetNativeObject() withScopeBlock:^(SentryScope* scope) { + if(isGameLogAttachmentEnabled) { + const FString logFilePath = SentryFileUtils::GetGameLogPath(); + SentryAttachment* logAttachment = [[SENTRY_APPLE_CLASS(SentryAttachment) alloc] initWithPath:logFilePath.GetNSString()]; + [scope addAttachment:logAttachment]; + } onConfigureScope.ExecuteIfBound(MakeShareable(new SentryScopeApple(scope))); }]; @@ -263,7 +262,14 @@ TSharedPtr FAppleSentrySubsystem::CaptureEnsure(const FString& type, SentryEvent *exceptionEvent = [[SENTRY_APPLE_CLASS(SentryEvent) alloc] init]; exceptionEvent.exceptions = nativeExceptionArray; - SentryId* id = [SENTRY_APPLE_CLASS(SentrySDK) captureEvent:exceptionEvent]; + SentryId* id = [SENTRY_APPLE_CLASS(SentrySDK) captureEvent:exceptionEvent withScopeBlock:^(SentryScope* scope) { + if(isGameLogAttachmentEnabled) { + const FString logFilePath = SentryFileUtils::GetGameLogPath(); + SentryAttachment* logAttachment = [[SENTRY_APPLE_CLASS(SentryAttachment) alloc] initWithPath:logFilePath.GetNSString()]; + [scope addAttachment:logAttachment]; + } + }]; + return MakeShareable(new SentryIdApple(id)); } diff --git a/plugin-dev/Source/Sentry/Private/Apple/AppleSentrySubsystem.h b/plugin-dev/Source/Sentry/Private/Apple/AppleSentrySubsystem.h index cf1c6f7c8..7c4cb88f1 100644 --- a/plugin-dev/Source/Sentry/Private/Apple/AppleSentrySubsystem.h +++ b/plugin-dev/Source/Sentry/Private/Apple/AppleSentrySubsystem.h @@ -42,4 +42,8 @@ class FAppleSentrySubsystem : public ISentrySubsystem virtual FString GetScreenshotPath() const; virtual FString GetLatestScreenshot() const; + +protected: + bool isScreenshotAttachmentEnabled = false; + bool isGameLogAttachmentEnabled = false; }; diff --git a/plugin-dev/Source/Sentry/Private/Mac/MacSentrySubsystem.cpp b/plugin-dev/Source/Sentry/Private/Mac/MacSentrySubsystem.cpp index 3370079f0..d610fe4fa 100644 --- a/plugin-dev/Source/Sentry/Private/Mac/MacSentrySubsystem.cpp +++ b/plugin-dev/Source/Sentry/Private/Mac/MacSentrySubsystem.cpp @@ -15,9 +15,7 @@ void FMacSentrySubsystem::InitWithSettings(const USentrySettings* Settings, USen { FAppleSentrySubsystem::InitWithSettings(Settings, BeforeSendHandler, BeforeBreadcrumbHandler, TraceSampler); - isScreenshotAttachmentEnabled = Settings->AttachScreenshot; - - if (IsEnabled() && Settings->AttachScreenshot) + if (IsEnabled() && isScreenshotAttachmentEnabled) { FCoreDelegates::OnHandleSystemError.AddLambda([this]() { diff --git a/plugin-dev/Source/Sentry/Private/Mac/MacSentrySubsystem.h b/plugin-dev/Source/Sentry/Private/Mac/MacSentrySubsystem.h index 58ae2607f..dca8eaf65 100644 --- a/plugin-dev/Source/Sentry/Private/Mac/MacSentrySubsystem.h +++ b/plugin-dev/Source/Sentry/Private/Mac/MacSentrySubsystem.h @@ -15,9 +15,6 @@ class FMacSentrySubsystem : public FAppleSentrySubsystem virtual TSharedPtr CaptureEnsure(const FString& type, const FString& message) override; virtual FString TryCaptureScreenshot() const override; - -private: - bool isScreenshotAttachmentEnabled = false; }; typedef FMacSentrySubsystem FPlatformSentrySubsystem; diff --git a/plugin-dev/Source/Sentry/Private/Utils/SentryFileUtils.cpp b/plugin-dev/Source/Sentry/Private/Utils/SentryFileUtils.cpp index 5bebd4cec..0507da369 100644 --- a/plugin-dev/Source/Sentry/Private/Utils/SentryFileUtils.cpp +++ b/plugin-dev/Source/Sentry/Private/Utils/SentryFileUtils.cpp @@ -17,6 +17,11 @@ struct FSentrySortFileByDatePredicate } }; +FString SentryFileUtils::GetGameLogName() +{ + return FPaths::GetCleanFilename(FGenericPlatformOutputDevices::GetAbsoluteLogFilename()); +} + FString SentryFileUtils::GetGameLogPath() { return IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*FGenericPlatformOutputDevices::GetAbsoluteLogFilename()); diff --git a/plugin-dev/Source/Sentry/Private/Utils/SentryFileUtils.h b/plugin-dev/Source/Sentry/Private/Utils/SentryFileUtils.h index 142e911a1..1a25ef741 100644 --- a/plugin-dev/Source/Sentry/Private/Utils/SentryFileUtils.h +++ b/plugin-dev/Source/Sentry/Private/Utils/SentryFileUtils.h @@ -7,6 +7,7 @@ class SentryFileUtils { public: + static FString GetGameLogName(); static FString GetGameLogPath(); static FString GetGameLogBackupPath(); static FString GetGpuDumpPath(); From 3e72b602f868c43a8e6273977ab322dd2b7a1a0c Mon Sep 17 00:00:00 2001 From: Ivan Tustanivskyi Date: Thu, 17 Apr 2025 21:20:48 +0300 Subject: [PATCH 4/7] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5960fff44..dbf9cd3fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ ### Fixes - Fix warnings caused by deprecated Cocoa SDK API usages ([#868](https://github.com/getsentry/sentry-unreal/pull/868)) +- Fix invalid log file being attached to crash events on Mac/iOS ([#873](https://github.com/getsentry/sentry-unreal/pull/873)) ### Dependencies From 211e10daff671faa3caca595d96d8c06265a364a Mon Sep 17 00:00:00 2001 From: Ivan Tustanivskyi Date: Tue, 22 Apr 2025 16:43:33 +0300 Subject: [PATCH 5/7] Add workaround for issue with invalid log file path on iOS --- .../Private/Apple/AppleSentrySubsystem.cpp | 8 ++-- .../Private/Apple/AppleSentrySubsystem.h | 2 + .../Sentry/Private/IOS/IOSSentrySubsystem.cpp | 33 +++++++++++++- .../Sentry/Private/IOS/IOSSentrySubsystem.h | 43 +++++++++++-------- .../Sentry/Private/Mac/MacSentrySubsystem.cpp | 12 ++++++ .../Sentry/Private/Mac/MacSentrySubsystem.h | 4 ++ 6 files changed, 79 insertions(+), 23 deletions(-) diff --git a/plugin-dev/Source/Sentry/Private/Apple/AppleSentrySubsystem.cpp b/plugin-dev/Source/Sentry/Private/Apple/AppleSentrySubsystem.cpp index b751c91be..54a22fec2 100644 --- a/plugin-dev/Source/Sentry/Private/Apple/AppleSentrySubsystem.cpp +++ b/plugin-dev/Source/Sentry/Private/Apple/AppleSentrySubsystem.cpp @@ -84,7 +84,7 @@ void FAppleSentrySubsystem::InitWithSettings(const USentrySettings* settings, US if(settings->EnableAutoLogAttachment) { // Unreal creates game log backups automatically on every app run. If logging is enabled for current configuration SDK can // find the most recent one and upload it to Sentry. - const FString logFilePath = SentryFileUtils::GetGameLogBackupPath(); + const FString logFilePath = GetLatestGameLog(); if (!logFilePath.IsEmpty()) { UploadAttachmentForEvent(MakeShareable(new SentryIdApple(event.eventId)), logFilePath, SentryFileUtils::GetGameLogName()); @@ -221,7 +221,7 @@ TSharedPtr FAppleSentrySubsystem::CaptureMessageWithScope(const FStri SentryId* id = [SENTRY_APPLE_CLASS(SentrySDK) captureMessage:message.GetNSString() withScopeBlock:^(SentryScope* scope){ [scope setLevel:SentryConvertersApple::SentryLevelToNative(level)]; if(isGameLogAttachmentEnabled) { - const FString logFilePath = SentryFileUtils::GetGameLogPath(); + const FString logFilePath = GetGameLogPath(); SentryAttachment* logAttachment = [[SENTRY_APPLE_CLASS(SentryAttachment) alloc] initWithPath:logFilePath.GetNSString()]; [scope addAttachment:logAttachment]; } @@ -243,7 +243,7 @@ TSharedPtr FAppleSentrySubsystem::CaptureEventWithScope(TSharedPtrGetNativeObject() withScopeBlock:^(SentryScope* scope) { if(isGameLogAttachmentEnabled) { - const FString logFilePath = SentryFileUtils::GetGameLogPath(); + const FString logFilePath = GetGameLogPath(); SentryAttachment* logAttachment = [[SENTRY_APPLE_CLASS(SentryAttachment) alloc] initWithPath:logFilePath.GetNSString()]; [scope addAttachment:logAttachment]; } @@ -264,7 +264,7 @@ TSharedPtr FAppleSentrySubsystem::CaptureEnsure(const FString& type, SentryId* id = [SENTRY_APPLE_CLASS(SentrySDK) captureEvent:exceptionEvent withScopeBlock:^(SentryScope* scope) { if(isGameLogAttachmentEnabled) { - const FString logFilePath = SentryFileUtils::GetGameLogPath(); + const FString logFilePath = GetGameLogPath(); SentryAttachment* logAttachment = [[SENTRY_APPLE_CLASS(SentryAttachment) alloc] initWithPath:logFilePath.GetNSString()]; [scope addAttachment:logAttachment]; } diff --git a/plugin-dev/Source/Sentry/Private/Apple/AppleSentrySubsystem.h b/plugin-dev/Source/Sentry/Private/Apple/AppleSentrySubsystem.h index 7c4cb88f1..58c39345f 100644 --- a/plugin-dev/Source/Sentry/Private/Apple/AppleSentrySubsystem.h +++ b/plugin-dev/Source/Sentry/Private/Apple/AppleSentrySubsystem.h @@ -42,6 +42,8 @@ class FAppleSentrySubsystem : public ISentrySubsystem virtual FString GetScreenshotPath() const; virtual FString GetLatestScreenshot() const; + virtual FString GetGameLogPath() const { return FString(); }; + virtual FString GetLatestGameLog() const { return FString(); }; protected: bool isScreenshotAttachmentEnabled = false; diff --git a/plugin-dev/Source/Sentry/Private/IOS/IOSSentrySubsystem.cpp b/plugin-dev/Source/Sentry/Private/IOS/IOSSentrySubsystem.cpp index d61f44486..a4f07ca64 100644 --- a/plugin-dev/Source/Sentry/Private/IOS/IOSSentrySubsystem.cpp +++ b/plugin-dev/Source/Sentry/Private/IOS/IOSSentrySubsystem.cpp @@ -5,10 +5,13 @@ #include "SentryDefines.h" #include "SentrySettings.h" +#include "Utils/SentryScreenshotUtils.h" +#include "Utils/SentryFileUtils.h" + +#include "HAL/FileManager.h" #include "Misc/CoreDelegates.h" #include "Misc/FileHelper.h" #include "Misc/Paths.h" -#include "Utils/SentryScreenshotUtils.h" static FIOSSentrySubsystem* GIOSSentrySubsystem = nullptr; @@ -103,3 +106,31 @@ FString FIOSSentrySubsystem::TryCaptureScreenshot() const return ScreenshotPath; } + +FString FIOSSentrySubsystem::GetGameLogPath() const +{ + const FString& logFilePath = SentryFileUtils::GetGameLogPath(); + return IFileManager::Get().FileExists(*logFilePath) ? logFilePath : NormalizeToPublicIOSPath(logFilePath); +} + +FString FIOSSentrySubsystem::GetLatestGameLog() const +{ + const FString logFilePath = SentryFileUtils::GetGameLogBackupPath(); + return IFileManager::Get().FileExists(*logFilePath) ? logFilePath : NormalizeToPublicIOSPath(logFilePath); +} + +FString FIOSSentrySubsystem::NormalizeToPublicIOSPath(const FString& logFilePath) const +{ + // This is a workaround for iOS log file path not being accessible via the path returned by engine's API. + // See https://github.com/getsentry/sentry-unreal/pull/732 + + static FString PublicWritePathBase = FString([NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]); + static FString PrivateWritePathBase = FString([NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) objectAtIndex:0]); + + if (logFilePath.StartsWith(PrivateWritePathBase)) + { + return logFilePath.Replace(PrivateWritePathBase, PublicWritePathBase); + } + + return logFilePath; +} diff --git a/plugin-dev/Source/Sentry/Private/IOS/IOSSentrySubsystem.h b/plugin-dev/Source/Sentry/Private/IOS/IOSSentrySubsystem.h index 4070d999a..84a30448c 100644 --- a/plugin-dev/Source/Sentry/Private/IOS/IOSSentrySubsystem.h +++ b/plugin-dev/Source/Sentry/Private/IOS/IOSSentrySubsystem.h @@ -1,18 +1,25 @@ -#pragma once - -#include "Apple/AppleSentrySubsystem.h" - -class FIOSSentrySubsystem : public FAppleSentrySubsystem -{ -public: - virtual void InitWithSettings( - const USentrySettings* Settings, - USentryBeforeSendHandler* BeforeSendHandler, - USentryBeforeBreadcrumbHandler* BeforeBreadcrumbHandler, - USentryTraceSampler* TraceSampler - ) override; - - virtual FString TryCaptureScreenshot() const override; -}; - -typedef FIOSSentrySubsystem FPlatformSentrySubsystem; +#pragma once + +#include "Apple/AppleSentrySubsystem.h" + +class FIOSSentrySubsystem : public FAppleSentrySubsystem +{ +public: + virtual void InitWithSettings( + const USentrySettings* Settings, + USentryBeforeSendHandler* BeforeSendHandler, + USentryBeforeBreadcrumbHandler* BeforeBreadcrumbHandler, + USentryTraceSampler* TraceSampler + ) override; + + virtual FString TryCaptureScreenshot() const override; + +protected: + virtual FString GetGameLogPath() const override; + virtual FString GetLatestGameLog() const override; + +private: + FString NormalizeToPublicIOSPath(const FString& logFilePath) const; +}; + +typedef FIOSSentrySubsystem FPlatformSentrySubsystem; diff --git a/plugin-dev/Source/Sentry/Private/Mac/MacSentrySubsystem.cpp b/plugin-dev/Source/Sentry/Private/Mac/MacSentrySubsystem.cpp index d610fe4fa..963f662ba 100644 --- a/plugin-dev/Source/Sentry/Private/Mac/MacSentrySubsystem.cpp +++ b/plugin-dev/Source/Sentry/Private/Mac/MacSentrySubsystem.cpp @@ -6,6 +6,8 @@ #include "SentryModule.h" #include "SentrySettings.h" +#include "Utils/SentryFileUtils.h" + #include "Misc/CoreDelegates.h" #include "Misc/FileHelper.h" #include "Misc/Paths.h" @@ -79,3 +81,13 @@ FString FMacSentrySubsystem::TryCaptureScreenshot() const return ScreenshotPath; } + +FString FMacSentrySubsystem::GetGameLogPath() const +{ + return SentryFileUtils::GetGameLogPath(); +} + +FString FMacSentrySubsystem::GetLatestGameLog() const +{ + return SentryFileUtils::GetGameLogBackupPath(); +} diff --git a/plugin-dev/Source/Sentry/Private/Mac/MacSentrySubsystem.h b/plugin-dev/Source/Sentry/Private/Mac/MacSentrySubsystem.h index dca8eaf65..9646b0036 100644 --- a/plugin-dev/Source/Sentry/Private/Mac/MacSentrySubsystem.h +++ b/plugin-dev/Source/Sentry/Private/Mac/MacSentrySubsystem.h @@ -15,6 +15,10 @@ class FMacSentrySubsystem : public FAppleSentrySubsystem virtual TSharedPtr CaptureEnsure(const FString& type, const FString& message) override; virtual FString TryCaptureScreenshot() const override; + +protected: + virtual FString GetGameLogPath() const override; + virtual FString GetLatestGameLog() const override; }; typedef FMacSentrySubsystem FPlatformSentrySubsystem; From 28a8a935aace5e2845a96865cc87ae8ddeb0a8b3 Mon Sep 17 00:00:00 2001 From: Ivan Tustanivskyi Date: Wed, 23 Apr 2025 09:10:49 +0300 Subject: [PATCH 6/7] Fix compilation errors on iOS --- plugin-dev/Source/Sentry/Private/IOS/IOSSentrySubsystem.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugin-dev/Source/Sentry/Private/IOS/IOSSentrySubsystem.cpp b/plugin-dev/Source/Sentry/Private/IOS/IOSSentrySubsystem.cpp index a4f07ca64..51d2d24c5 100644 --- a/plugin-dev/Source/Sentry/Private/IOS/IOSSentrySubsystem.cpp +++ b/plugin-dev/Source/Sentry/Private/IOS/IOSSentrySubsystem.cpp @@ -121,7 +121,7 @@ FString FIOSSentrySubsystem::GetLatestGameLog() const FString FIOSSentrySubsystem::NormalizeToPublicIOSPath(const FString& logFilePath) const { - // This is a workaround for iOS log file path not being accessible via the path returned by engine's API. + // This is a workaround for iOS log file not being accessible via the path returned by engine's API. // See https://github.com/getsentry/sentry-unreal/pull/732 static FString PublicWritePathBase = FString([NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]); @@ -129,7 +129,7 @@ FString FIOSSentrySubsystem::NormalizeToPublicIOSPath(const FString& logFilePath if (logFilePath.StartsWith(PrivateWritePathBase)) { - return logFilePath.Replace(PrivateWritePathBase, PublicWritePathBase); + return logFilePath.Replace(*PrivateWritePathBase, *PublicWritePathBase); } return logFilePath; From 888a1e000c466a13e1a6b15ec68907c2797d2530 Mon Sep 17 00:00:00 2001 From: Ivan Tustanivskyi Date: Mon, 28 Apr 2025 13:44:19 +0300 Subject: [PATCH 7/7] Refactor attaching log to events --- .../Private/Apple/AppleSentrySubsystem.cpp | 93 +++++++++++-------- .../Private/Apple/AppleSentrySubsystem.h | 3 + .../Sentry/Private/Mac/MacSentrySubsystem.cpp | 5 +- 3 files changed, 60 insertions(+), 41 deletions(-) diff --git a/plugin-dev/Source/Sentry/Private/Apple/AppleSentrySubsystem.cpp b/plugin-dev/Source/Sentry/Private/Apple/AppleSentrySubsystem.cpp index 54a22fec2..65ceb4af7 100644 --- a/plugin-dev/Source/Sentry/Private/Apple/AppleSentrySubsystem.cpp +++ b/plugin-dev/Source/Sentry/Private/Apple/AppleSentrySubsystem.cpp @@ -75,20 +75,13 @@ void FAppleSentrySubsystem::InitWithSettings(const USentrySettings* settings, US { // If a screenshot was captured during assertion/crash in the previous app run // find the most recent one and upload it to Sentry. - const FString& screenshotPath = GetLatestScreenshot(); - if (!screenshotPath.IsEmpty()) - { - UploadAttachmentForEvent(MakeShareable(new SentryIdApple(event.eventId)), screenshotPath, TEXT("screenshot.png"), true); - } + UploadScreenshotForEvent(MakeShareable(new SentryIdApple(event.eventId)), GetLatestScreenshot()); } - if(settings->EnableAutoLogAttachment) { - // Unreal creates game log backups automatically on every app run. If logging is enabled for current configuration SDK can + if(settings->EnableAutoLogAttachment) + { + // Unreal creates game log backups automatically on every app run. If logging is enabled for current configuration, SDK can // find the most recent one and upload it to Sentry. - const FString logFilePath = GetLatestGameLog(); - if (!logFilePath.IsEmpty()) - { - UploadAttachmentForEvent(MakeShareable(new SentryIdApple(event.eventId)), logFilePath, SentryFileUtils::GetGameLogName()); - } + UploadGameLogForEvent(MakeShareable(new SentryIdApple(event.eventId)), GetLatestGameLog()); } }; options.beforeSend = ^SentryEvent* (SentryEvent* event) { @@ -218,17 +211,19 @@ TSharedPtr FAppleSentrySubsystem::CaptureMessage(const FString& messa TSharedPtr FAppleSentrySubsystem::CaptureMessageWithScope(const FString& message, const FSentryScopeDelegate& onConfigureScope, ESentryLevel level) { - SentryId* id = [SENTRY_APPLE_CLASS(SentrySDK) captureMessage:message.GetNSString() withScopeBlock:^(SentryScope* scope){ + SentryId* nativeId = [SENTRY_APPLE_CLASS(SentrySDK) captureMessage:message.GetNSString() withScopeBlock:^(SentryScope* scope){ [scope setLevel:SentryConvertersApple::SentryLevelToNative(level)]; - if(isGameLogAttachmentEnabled) { - const FString logFilePath = GetGameLogPath(); - SentryAttachment* logAttachment = [[SENTRY_APPLE_CLASS(SentryAttachment) alloc] initWithPath:logFilePath.GetNSString()]; - [scope addAttachment:logAttachment]; - } onConfigureScope.ExecuteIfBound(MakeShareable(new SentryScopeApple(scope))); }]; - return MakeShareable(new SentryIdApple(id)); + TSharedPtr id = MakeShareable(new SentryIdApple(nativeId)); + + if (isGameLogAttachmentEnabled) + { + UploadGameLogForEvent(id, GetGameLogPath()); + } + + return id; } TSharedPtr FAppleSentrySubsystem::CaptureEvent(TSharedPtr event) @@ -239,18 +234,20 @@ TSharedPtr FAppleSentrySubsystem::CaptureEvent(TSharedPtr FAppleSentrySubsystem::CaptureEventWithScope(TSharedPtr event, const FSentryScopeDelegate& onConfigureScope) { - TSharedPtr eventIOS = StaticCastSharedPtr(event); + TSharedPtr eventApple = StaticCastSharedPtr(event); - SentryId* id = [SENTRY_APPLE_CLASS(SentrySDK) captureEvent:eventIOS->GetNativeObject() withScopeBlock:^(SentryScope* scope) { - if(isGameLogAttachmentEnabled) { - const FString logFilePath = GetGameLogPath(); - SentryAttachment* logAttachment = [[SENTRY_APPLE_CLASS(SentryAttachment) alloc] initWithPath:logFilePath.GetNSString()]; - [scope addAttachment:logAttachment]; - } + SentryId* nativeId = [SENTRY_APPLE_CLASS(SentrySDK) captureEvent:eventApple->GetNativeObject() withScopeBlock:^(SentryScope* scope) { onConfigureScope.ExecuteIfBound(MakeShareable(new SentryScopeApple(scope))); }]; - return MakeShareable(new SentryIdApple(id)); + TSharedPtr id = MakeShareable(new SentryIdApple(nativeId)); + + if (isGameLogAttachmentEnabled) + { + UploadGameLogForEvent(id, GetGameLogPath()); + } + + return id; } TSharedPtr FAppleSentrySubsystem::CaptureEnsure(const FString& type, const FString& message) @@ -262,15 +259,16 @@ TSharedPtr FAppleSentrySubsystem::CaptureEnsure(const FString& type, SentryEvent *exceptionEvent = [[SENTRY_APPLE_CLASS(SentryEvent) alloc] init]; exceptionEvent.exceptions = nativeExceptionArray; - SentryId* id = [SENTRY_APPLE_CLASS(SentrySDK) captureEvent:exceptionEvent withScopeBlock:^(SentryScope* scope) { - if(isGameLogAttachmentEnabled) { - const FString logFilePath = GetGameLogPath(); - SentryAttachment* logAttachment = [[SENTRY_APPLE_CLASS(SentryAttachment) alloc] initWithPath:logFilePath.GetNSString()]; - [scope addAttachment:logAttachment]; - } - }]; + SentryId* nativeId = [SENTRY_APPLE_CLASS(SentrySDK) captureEvent:exceptionEvent]; - return MakeShareable(new SentryIdApple(id)); + TSharedPtr id = MakeShareable(new SentryIdApple(nativeId)); + + if (isGameLogAttachmentEnabled) + { + UploadGameLogForEvent(id, GetGameLogPath()); + } + + return id; } @@ -418,12 +416,12 @@ void FAppleSentrySubsystem::UploadAttachmentForEvent(TSharedPtr event const FString& filePathExt = fileManager.ConvertToAbsolutePathForExternalAppForRead(*filePath); - SentryAttachment* screenshotAttachment = [[SENTRY_APPLE_CLASS(SentryAttachment) alloc] initWithPath:filePathExt.GetNSString() filename:name.GetNSString()]; + SentryAttachment* attachment = [[SENTRY_APPLE_CLASS(SentryAttachment) alloc] initWithPath:filePathExt.GetNSString() filename:name.GetNSString()]; SentryOptions* options = [SENTRY_APPLE_CLASS(PrivateSentrySDKOnly) options]; int32 size = options.maxAttachmentSize; - SentryEnvelopeItem* envelopeItem = [[SENTRY_APPLE_CLASS(SentryEnvelopeItem) alloc] initWithAttachment:screenshotAttachment maxAttachmentSize:size]; + SentryEnvelopeItem* envelopeItem = [[SENTRY_APPLE_CLASS(SentryEnvelopeItem) alloc] initWithAttachment:attachment maxAttachmentSize:size]; SentryId* id = StaticCastSharedPtr(eventId)->GetNativeObject(); @@ -440,6 +438,27 @@ void FAppleSentrySubsystem::UploadAttachmentForEvent(TSharedPtr event } } +void FAppleSentrySubsystem::UploadScreenshotForEvent(TSharedPtr eventId, const FString& screenshotPath) const +{ + if (screenshotPath.IsEmpty()) + { + // Screenshot capturing is a best-effort solution so if one wasn't captured (path is empty) skip the upload + return; + } + + UploadAttachmentForEvent(eventId, screenshotPath, TEXT("screenshot.png"), true); +} + +void FAppleSentrySubsystem::UploadGameLogForEvent(TSharedPtr eventId, const FString& logFilePath) const +{ +#if NO_LOGGING + // If writing logs to a file is disabled (i.e. default behavior for Shipping builds) skip the upload + return; +#endif + + UploadAttachmentForEvent(eventId, logFilePath, SentryFileUtils::GetGameLogName()); +} + FString FAppleSentrySubsystem::GetScreenshotPath() const { return FPaths::Combine(FPaths::ProjectSavedDir(), TEXT("SentryScreenshots"), FString::Printf(TEXT("screenshot-%s.png"), *FDateTime::Now().ToString())); diff --git a/plugin-dev/Source/Sentry/Private/Apple/AppleSentrySubsystem.h b/plugin-dev/Source/Sentry/Private/Apple/AppleSentrySubsystem.h index 58c39345f..99d010793 100644 --- a/plugin-dev/Source/Sentry/Private/Apple/AppleSentrySubsystem.h +++ b/plugin-dev/Source/Sentry/Private/Apple/AppleSentrySubsystem.h @@ -40,6 +40,9 @@ class FAppleSentrySubsystem : public ISentrySubsystem protected: void UploadAttachmentForEvent(TSharedPtr eventId, const FString& filePath, const FString& name, bool deleteAfterUpload = false) const; + void UploadScreenshotForEvent(TSharedPtr eventId, const FString& screenshotPath) const; + void UploadGameLogForEvent(TSharedPtr eventId, const FString& logFilePath) const; + virtual FString GetScreenshotPath() const; virtual FString GetLatestScreenshot() const; virtual FString GetGameLogPath() const { return FString(); }; diff --git a/plugin-dev/Source/Sentry/Private/Mac/MacSentrySubsystem.cpp b/plugin-dev/Source/Sentry/Private/Mac/MacSentrySubsystem.cpp index 963f662ba..682649d5e 100644 --- a/plugin-dev/Source/Sentry/Private/Mac/MacSentrySubsystem.cpp +++ b/plugin-dev/Source/Sentry/Private/Mac/MacSentrySubsystem.cpp @@ -33,10 +33,7 @@ TSharedPtr FMacSentrySubsystem::CaptureEnsure(const FString& type, co if (isScreenshotAttachmentEnabled) { const FString& screenshotPath = TryCaptureScreenshot(); - if (!screenshotPath.IsEmpty()) - { - UploadAttachmentForEvent(id, screenshotPath, TEXT("screenshot.png"), true); - } + UploadScreenshotForEvent(id, screenshotPath); } return id;