-
-
Notifications
You must be signed in to change notification settings - Fork 49
Fix invalid log file being attached to crash events on Mac/iOS #873
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
Changes from all commits
cb1209c
3040265
015b187
665e974
3e72b60
211e10d
28a8a93
5ace245
888a1e0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -26,6 +26,9 @@ | |
#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 "HAL/PlatformSentryAttachment.h" | ||
|
@@ -34,10 +37,12 @@ | |
#include "Misc/Paths.h" | ||
#include "UObject/GarbageCollection.h" | ||
#include "UObject/UObjectThreadContext.h" | ||
#include "Utils/SentryLogUtils.h" | ||
|
||
void FAppleSentrySubsystem::InitWithSettings(const USentrySettings* settings, USentryBeforeSendHandler* beforeSendHandler, USentryBeforeBreadcrumbHandler* beforeBreadcrumbHandler, USentryTraceSampler* traceSampler) | ||
{ | ||
isScreenshotAttachmentEnabled = settings->AttachScreenshot; | ||
isGameLogAttachmentEnabled = settings->EnableAutoLogAttachment; | ||
Comment on lines
+43
to
+44
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should these go on the actual
This will also make it easier for users to browse and explore and for us to reason through the SDK's behaviour. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These two flags are cached separately here since they don't necessarily have a direct representation in Cocoa SDK options:
Accessing them using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This option (with this name) also exists on Android, .NET, Godot, Unity. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I might be missing the concern here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess I never realized we call it 'settings', where everywhere else we call options in Sentry .. options. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, it's a common naming convention for Unreal - ISettingsModule::RegisterSettings |
||
|
||
[SENTRY_APPLE_CLASS(PrivateSentrySDKOnly) setSdkName:@"sentry.cocoa.unreal"]; | ||
|
||
dispatch_group_t sentryDispatchGroup = dispatch_group_create(); | ||
|
@@ -65,24 +70,18 @@ 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 = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*FGenericPlatformOutputDevices::GetAbsoluteLogFilename()); | ||
SentryAttachment* logAttachment = [[SENTRY_APPLE_CLASS(SentryAttachment) alloc] initWithPath:logFilePath.GetNSString()]; | ||
[scope addAttachment:logAttachment]; | ||
} | ||
return scope; | ||
}; | ||
options.onCrashedLastRun = ^(SentryEvent* event) { | ||
if (settings->AttachScreenshot) | ||
{ | ||
// 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()) | ||
{ | ||
UploadScreenshotForEvent(MakeShareable(new SentryIdApple(event.eventId)), screenshotPath); | ||
} | ||
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 | ||
// find the most recent one and upload it to Sentry. | ||
UploadGameLogForEvent(MakeShareable(new SentryIdApple(event.eventId)), GetLatestGameLog()); | ||
} | ||
}; | ||
options.beforeSend = ^SentryEvent* (SentryEvent* event) { | ||
|
@@ -206,40 +205,49 @@ void FAppleSentrySubsystem::ClearBreadcrumbs() | |
|
||
TSharedPtr<ISentryId> 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<ISentryId> 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)]; | ||
onConfigureScope.ExecuteIfBound(MakeShareable(new SentryScopeApple(scope))); | ||
}]; | ||
|
||
return MakeShareable(new SentryIdApple(id)); | ||
TSharedPtr<ISentryId> id = MakeShareable(new SentryIdApple(nativeId)); | ||
|
||
if (isGameLogAttachmentEnabled) | ||
{ | ||
UploadGameLogForEvent(id, GetGameLogPath()); | ||
} | ||
|
||
return id; | ||
} | ||
|
||
TSharedPtr<ISentryId> FAppleSentrySubsystem::CaptureEvent(TSharedPtr<ISentryEvent> event) | ||
{ | ||
TSharedPtr<SentryEventApple> eventIOS = StaticCastSharedPtr<SentryEventApple>(event); | ||
|
||
SentryId* id = [SENTRY_APPLE_CLASS(SentrySDK) captureEvent:eventIOS->GetNativeObject()]; | ||
return MakeShareable(new SentryIdApple(id)); | ||
FSentryScopeDelegate onConfigureScope; | ||
return CaptureEventWithScope(event, onConfigureScope); | ||
} | ||
|
||
TSharedPtr<ISentryId> FAppleSentrySubsystem::CaptureEventWithScope(TSharedPtr<ISentryEvent> event, const FSentryScopeDelegate& onConfigureScope) | ||
{ | ||
TSharedPtr<SentryEventApple> eventIOS = StaticCastSharedPtr<SentryEventApple>(event); | ||
TSharedPtr<SentryEventApple> eventApple = StaticCastSharedPtr<SentryEventApple>(event); | ||
|
||
SentryId* id = [SENTRY_APPLE_CLASS(SentrySDK) captureEvent:eventIOS->GetNativeObject() withScopeBlock:^(SentryScope* scope) { | ||
SentryId* nativeId = [SENTRY_APPLE_CLASS(SentrySDK) captureEvent:eventApple->GetNativeObject() withScopeBlock:^(SentryScope* scope) { | ||
onConfigureScope.ExecuteIfBound(MakeShareable(new SentryScopeApple(scope))); | ||
}]; | ||
|
||
return MakeShareable(new SentryIdApple(id)); | ||
TSharedPtr<ISentryId> id = MakeShareable(new SentryIdApple(nativeId)); | ||
|
||
if (isGameLogAttachmentEnabled) | ||
{ | ||
UploadGameLogForEvent(id, GetGameLogPath()); | ||
} | ||
|
||
return id; | ||
} | ||
|
||
TSharedPtr<ISentryId> FAppleSentrySubsystem::CaptureEnsure(const FString& type, const FString& message) | ||
|
@@ -251,8 +259,16 @@ TSharedPtr<ISentryId> FAppleSentrySubsystem::CaptureEnsure(const FString& type, | |
SentryEvent *exceptionEvent = [[SENTRY_APPLE_CLASS(SentryEvent) alloc] init]; | ||
exceptionEvent.exceptions = nativeExceptionArray; | ||
|
||
SentryId* id = [SENTRY_APPLE_CLASS(SentrySDK) captureEvent:exceptionEvent]; | ||
return MakeShareable(new SentryIdApple(id)); | ||
SentryId* nativeId = [SENTRY_APPLE_CLASS(SentrySDK) captureEvent:exceptionEvent]; | ||
|
||
TSharedPtr<ISentryId> id = MakeShareable(new SentryIdApple(nativeId)); | ||
|
||
if (isGameLogAttachmentEnabled) | ||
{ | ||
UploadGameLogForEvent(id, GetGameLogPath()); | ||
} | ||
|
||
return id; | ||
} | ||
|
||
|
||
|
@@ -389,37 +405,60 @@ TSharedPtr<ISentryTransactionContext> FAppleSentrySubsystem::ContinueTrace(const | |
return MakeShareable(new SentryTransactionContextApple(transactionContext)); | ||
} | ||
|
||
void FAppleSentrySubsystem::UploadScreenshotForEvent(TSharedPtr<ISentryId> eventId, const FString& screenshotPath) const | ||
void FAppleSentrySubsystem::UploadAttachmentForEvent(TSharedPtr<ISentryId> 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* 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<SentryIdApple>(eventId)->GetNativeObject(); | ||
|
||
SentryEnvelope* envelope = [[SENTRY_APPLE_CLASS(SentryEnvelope) alloc] initWithId:id singleItem:envelopeItem]; | ||
|
||
[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); | ||
} | ||
} | ||
} | ||
|
||
void FAppleSentrySubsystem::UploadScreenshotForEvent(TSharedPtr<ISentryId> 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<ISentryId> 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())); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The section on the changelog is already "Fixes" so we could (going forward, from the next release perhaps) avoid starting the message with Fix.
Personally I'd be more interested in which platform the fix relates to. So this is a iOS/Mac, could be:
Or something like that.