Skip to content

Fix invalid assertion issues grouping #537

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

Merged
merged 31 commits into from
Jun 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
470126f
Add callstack capturing on Unreal side of things
tustanivsky Mar 27, 2024
8ce0dee
Test client-side stack unwinding
tustanivsky Mar 29, 2024
92226fd
Revert CI changes
tustanivsky Mar 29, 2024
7060c54
Add callstack convertor
tustanivsky Apr 1, 2024
4c33ddc
Merge branch 'main' into fix/desktop-callstack
tustanivsky Apr 3, 2024
7e58b47
Add error output device
tustanivsky Apr 8, 2024
93d6b75
Add custom assertion capturing for desktop
tustanivsky Apr 8, 2024
3b0e59b
Update snapshot
tustanivsky Apr 9, 2024
0e18e3f
Fix build errors
tustanivsky Apr 10, 2024
9b8ca8b
Add missing implementation stubs
tustanivsky Apr 10, 2024
a112ae9
Extract output devices creation to separate methods
tustanivsky Apr 10, 2024
cb34dfe
Update changelog
tustanivsky Apr 10, 2024
763abc2
Add `ensure` to app termination utility
tustanivsky Apr 19, 2024
a958f93
Fix empty breadcrumbs
tustanivsky Apr 19, 2024
4eac75b
Update assertion reporting condition
tustanivsky Apr 19, 2024
ffc1c3c
Add ensures capturing
tustanivsky Apr 19, 2024
4cb0875
Add missing method stubs
tustanivsky Apr 19, 2024
eabb15b
Merge branch 'main' into fix/desktop-callstack
tustanivsky May 15, 2024
76a00fe
Fix changelog
tustanivsky May 15, 2024
e328c84
Add assertions captruing for Android
tustanivsky May 16, 2024
75051cf
Add assertions/ensures capturing for iOS
tustanivsky May 20, 2024
9d9f341
Fix formatting
tustanivsky May 20, 2024
ff4e72a
Fix formatting
tustanivsky May 20, 2024
2270015
Add ensure capturing for Android
tustanivsky May 21, 2024
7a3eb94
Add ensure capturing to demo
tustanivsky May 22, 2024
e9f2823
Add engine version check to determine how many ensure callstack frame…
tustanivsky May 22, 2024
b243944
Merge branch 'main' into fix/desktop-callstack
tustanivsky May 23, 2024
dd940b3
Merge branch 'main' into fix/desktop-callstack
tustanivsky May 23, 2024
9d0ef3e
Update CHANGELOG.md
tustanivsky Jun 5, 2024
25227cd
Move assertion capturing logic for Android to another method
tustanivsky Jun 6, 2024
9300a53
Rename method
tustanivsky Jun 6, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

### Fixes

- The SDK now correctly captures and groups Assertions ([#537](https://github.com/getsentry/sentry-unreal/pull/537))
- Add path strings escaping for debug symbol upload script ([#561](https://github.com/getsentry/sentry-unreal/pull/561))

### Dependencies
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,16 @@
package io.sentry.unreal;

import android.app.Activity;
import android.util.Log;

import androidx.annotation.NonNull;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import io.sentry.Breadcrumb;
Expand All @@ -26,7 +27,10 @@
import io.sentry.SentryOptions;
import io.sentry.android.core.SentryAndroid;
import io.sentry.android.core.SentryAndroidOptions;
import io.sentry.protocol.SentryException;
import io.sentry.protocol.SentryId;
import io.sentry.protocol.SentryStackFrame;
import io.sentry.protocol.SentryStackTrace;

public class SentryBridgeJava {
public static native void onConfigureScope(long callbackAddr, IScope scope);
Expand All @@ -53,6 +57,7 @@ public void configure(SentryAndroidOptions options) {
options.setBeforeSend(new SentryOptions.BeforeSendCallback() {
@Override
public SentryEvent execute(SentryEvent event, Hint hint) {
preProcessEvent(event);
return onBeforeSend(beforeSendHandler, event, hint);
}
});
Expand All @@ -73,12 +78,12 @@ public SentryEvent execute(SentryEvent event, Hint hint) {
options.setTracesSampler(new SentryOptions.TracesSamplerCallback() {
@Override
public Double sample(SamplingContext samplingContext) {
float sampleRate = onTracesSampler(samplerAddr, samplingContext);
if(sampleRate >= 0.0f) {
return (double) sampleRate;
} else {
return null;
}
float sampleRate = onTracesSampler(samplerAddr, samplingContext);
if(sampleRate >= 0.0f) {
return (double) sampleRate;
} else {
return null;
}
}
});
}
Expand All @@ -89,6 +94,24 @@ public Double sample(SamplingContext samplingContext) {
});
}

private static void preProcessEvent(SentryEvent event) {
if (event.getTags().containsKey("sentry_unreal_exception")) {
SentryException exception = event.getUnhandledException();
if (exception != null) {
exception.setType(event.getTag("sentry_unreal_exception_type"));
exception.setValue(event.getTag("sentry_unreal_exception_message"));
SentryStackTrace trace = exception.getStacktrace();
int numFramesToSkip = Integer.parseInt(event.getTag("sentry_unreal_exception_skip_frames"));
List<SentryStackFrame> frames = trace.getFrames();
trace.setFrames(frames.subList(0, frames.size() - numFramesToSkip));
}
event.removeTag("sentry_unreal_exception_type");
event.removeTag("sentry_unreal_exception_message");
event.removeTag("sentry_unreal_exception_skip_frames");
event.removeTag("sentry_unreal_exception");
Comment on lines +108 to +111
Copy link
Contributor

Choose a reason for hiding this comment

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

We really have to reevaluate the use of tags in the SDK. As a general rule, the SDK should not set any tags and add that information in contexts instead. Relay then handles those and elevates specific ones to tags.

}
}

public static void addBreadcrumb(final String message, final String category, final String type, final HashMap<String, String> data, final SentryLevel level) {
Breadcrumb breadcrumb = new Breadcrumb();
breadcrumb.setMessage(message);
Expand All @@ -106,8 +129,8 @@ public static SentryId captureMessageWithScope(final String message, final Sentr
SentryId messageId = Sentry.captureMessage(message, new ScopeCallback() {
@Override
public void run(@NonNull IScope scope) {
scope.setLevel(level);
onConfigureScope(callback, scope);
scope.setLevel(level);
onConfigureScope(callback, scope);
}
});
return messageId;
Expand All @@ -117,12 +140,22 @@ public static SentryId captureEventWithScope(final SentryEvent event, final long
SentryId eventId = Sentry.captureEvent(event, new ScopeCallback() {
@Override
public void run(@NonNull IScope scope) {
onConfigureScope(callback, scope);
onConfigureScope(callback, scope);
}
});
return eventId;
}

public static SentryId captureException(final String type, final String value) {
SentryException exception = new SentryException();
exception.setType(type);
exception.setValue(value);
SentryEvent event = new SentryEvent();
event.setExceptions(Collections.singletonList(exception));
SentryId eventId = Sentry.captureEvent(event);
return eventId;
}

public static void configureScope(final long callback) {
Sentry.configureScope(new ScopeCallback() {
@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,34 @@ USentryId* SentrySubsystemAndroid::CaptureEventWithScope(USentryEvent* event, co
return SentryConvertorsAndroid::SentryIdToUnreal(*id);
}

USentryId* SentrySubsystemAndroid::CaptureException(const FString& type, const FString& message, int32 framesToSkip)
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't follow this. CaptureException does not actually capture an exception. Instead it adds a whole bunch of markers that are later getting picked up by the bridge?

{
return nullptr;
}

USentryId* SentrySubsystemAndroid::CaptureAssertion(const FString& type, const FString& message)
{
const int32 framesToSkip = 8;

// add marker tags specific for Unreal assertions
SetTag(TEXT("sentry_unreal_exception"), TEXT("assert"));
SetTag(TEXT("sentry_unreal_exception_skip_frames"), FString::Printf(TEXT("%d"), framesToSkip));
SetTag(TEXT("sentry_unreal_exception_type"), type);
SetTag(TEXT("sentry_unreal_exception_message"), message);
Comment on lines +182 to +185
Copy link
Contributor

Choose a reason for hiding this comment

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

Can these be put in contexts instead?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The problem with using context values here is that these are ignored by sentry-native when it captures a native crash on Android and thus there will be no way to identify whether the event is an assertion or not upon the next application launch.


PLATFORM_BREAK();

return nullptr;
}

USentryId* SentrySubsystemAndroid::CaptureEnsure(const FString& type, const FString& message)
{
auto id = FSentryJavaObjectWrapper::CallStaticObjectMethod<jobject>(SentryJavaClasses::SentryBridgeJava, "captureException", "(Ljava/lang/String;Ljava/lang/String;)Lio/sentry/protocol/SentryId;",
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't follow. Why can we call CaptureException from in CaptureAssertion but we have to go straight to the bridge?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

That's a good point. I've moved the assertion-specific logic from CaptureException to CaptureAssertion. Also, in this case we can leave the CaptureException interface stub empty since on Android assert/ensure handling flow is different from other platforms.

*FSentryJavaObjectWrapper::GetJString(type), *FSentryJavaObjectWrapper::GetJString(message));

return SentryConvertorsAndroid::SentryIdToUnreal(*id);
}

void SentrySubsystemAndroid::CaptureUserFeedback(USentryUserFeedback* userFeedback)
{
TSharedPtr<SentryUserFeedbackAndroid> userFeedbackAndroid = StaticCastSharedPtr<SentryUserFeedbackAndroid>(userFeedback->GetNativeImpl());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ class SentrySubsystemAndroid : public ISentrySubsystem
virtual USentryId* CaptureMessageWithScope(const FString& message, const FConfigureScopeNativeDelegate& onConfigureScope, ESentryLevel level) override;
virtual USentryId* CaptureEvent(USentryEvent* event) override;
virtual USentryId* CaptureEventWithScope(USentryEvent* event, const FConfigureScopeNativeDelegate& onConfigureScope) override;
virtual USentryId* CaptureException(const FString& type, const FString& message, int32 framesToSkip) override;
virtual USentryId* CaptureAssertion(const FString& type, const FString& message) override;
virtual USentryId* CaptureEnsure(const FString& type, const FString& message) override;
virtual void CaptureUserFeedback(USentryUserFeedback* userFeedback) override;
virtual void SetUser(USentryUser* user) override;
virtual void RemoveUser() override;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
#include "Apple/SentryTransactionApple.h"
#include "Apple/SentrySpanApple.h"

#include "Convenience/SentryMacro.h"

SentryLevel SentryConvertorsApple::SentryLevelToNative(ESentryLevel level)
{
SentryLevel nativeLevel = kSentryLevelDebug;
Expand Down Expand Up @@ -72,6 +74,24 @@ NSData* SentryConvertorsApple::ByteDataToNative(const TArray<uint8>& array)
return [NSData dataWithBytes:array.GetData() length:array.Num()];
}

SentryStacktrace* SentryConvertorsApple::CallstackToNative(const TArray<FProgramCounterSymbolInfo>& callstack)
{
int32 framesCount = callstack.Num();

NSMutableArray *arr = [NSMutableArray arrayWithCapacity:framesCount];

for (int i = 0; i < framesCount; ++i)
{
SentryFrame *frame = [[SENTRY_APPLE_CLASS(SentryFrame) alloc] init];
frame.instructionAddress = FString::Printf(TEXT("0x%llx"), callstack[framesCount - i - 1].ProgramCounter).GetNSString();
[arr addObject:frame];
}

SentryStacktrace *trace = [[SENTRY_APPLE_CLASS(SentryStacktrace) alloc] initWithFrames:arr registers:@{}];

return trace;
}

ESentryLevel SentryConvertorsApple::SentryLevelToUnreal(SentryLevel level)
{
ESentryLevel unrealLevel = ESentryLevel::Debug;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

#include "Convenience/SentryInclude.h"

#include "GenericPlatform/GenericPlatformStackWalk.h"

class USentryTransactionContext;
class USentryScope;
class USentryId;
Expand All @@ -20,6 +22,7 @@ class SentryConvertorsApple
static NSDictionary* StringMapToNative(const TMap<FString, FString>& map);
static NSArray* StringArrayToNative(const TArray<FString>& array);
static NSData* ByteDataToNative(const TArray<uint8>& array);
static SentryStacktrace* CallstackToNative(const TArray<FProgramCounterSymbolInfo>& callstack);

/** Conversions from native iOS types */
static ESentryLevel SentryLevelToUnreal(SentryLevel level);
Expand Down
31 changes: 31 additions & 0 deletions plugin-dev/Source/Sentry/Private/Apple/SentrySubsystemApple.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,37 @@ USentryId* SentrySubsystemApple::CaptureEventWithScope(USentryEvent* event, cons
return SentryConvertorsApple::SentryIdToUnreal(id);
}

USentryId* SentrySubsystemApple::CaptureException(const FString& type, const FString& message, int32 framesToSkip)
{
auto StackFrames = FGenericPlatformStackWalk::GetStack(framesToSkip);

SentryException *nativeException = [[SENTRY_APPLE_CLASS(SentryException) alloc] initWithValue:message.GetNSString() type:type.GetNSString()];
NSMutableArray *nativeExceptionArray = [NSMutableArray arrayWithCapacity:1];
[nativeExceptionArray addObject:nativeException];

SentryEvent *exceptionEvent = [[SENTRY_APPLE_CLASS(SentryEvent) alloc] init];
exceptionEvent.exceptions = nativeExceptionArray;
exceptionEvent.stacktrace = SentryConvertorsApple::CallstackToNative(StackFrames);

SentryId* id = [SENTRY_APPLE_CLASS(SentrySDK) captureEvent:exceptionEvent];
return SentryConvertorsApple::SentryIdToUnreal(id);
}

USentryId* SentrySubsystemApple::CaptureAssertion(const FString& type, const FString& message)
{
#if PLATFORM_MAC
int32 framesToSkip = 6;
#elif PLATFORM_IOS
int32 framesToSkip = 5;
#endif
return CaptureException(type, message, framesToSkip);
}

USentryId* SentrySubsystemApple::CaptureEnsure(const FString& type, const FString& message)
{
return CaptureException(type, message, 6);
}

void SentrySubsystemApple::CaptureUserFeedback(USentryUserFeedback* userFeedback)
{
TSharedPtr<SentryUserFeedbackApple> userFeedbackIOS = StaticCastSharedPtr<SentryUserFeedbackApple>(userFeedback->GetNativeImpl());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ class SentrySubsystemApple : public ISentrySubsystem
virtual USentryId* CaptureMessageWithScope(const FString& message, const FConfigureScopeNativeDelegate& onConfigureScope, ESentryLevel level) override;
virtual USentryId* CaptureEvent(USentryEvent* event) override;
virtual USentryId* CaptureEventWithScope(USentryEvent* event, const FConfigureScopeNativeDelegate& onConfigureScope) override;
virtual USentryId* CaptureException(const FString& type, const FString& message, int32 framesToSkip) override;
virtual USentryId* CaptureAssertion(const FString& type, const FString& message) override;
virtual USentryId* CaptureEnsure(const FString& type, const FString& message) override;
virtual void CaptureUserFeedback(USentryUserFeedback* userFeedback) override;
virtual void SetUser(USentryUser* user) override;
virtual void RemoveUser() override;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,36 @@ sentry_value_t SentryConvertorsDesktop::StringArrayToNative(const TArray<FString
return sentryArray;
}

sentry_value_t SentryConvertorsDesktop::AddressToNative(uint64 address)
{
char buffer[32];
size_t written = (size_t)snprintf(buffer, sizeof(buffer), "0x%llx", (unsigned long long)address);
if (written >= sizeof(buffer))
{
return sentry_value_new_null();
}
buffer[written] = '\0';
return sentry_value_new_string(buffer);
}

sentry_value_t SentryConvertorsDesktop::CallstackToNative(const TArray<FProgramCounterSymbolInfo>& callstack)
{
int32 framesCount = callstack.Num();

sentry_value_t frames = sentry_value_new_list();
for (int i = 0; i < framesCount; ++i)
{
sentry_value_t frame = sentry_value_new_object();
sentry_value_set_by_key(frame, "instruction_addr", AddressToNative(callstack[framesCount - i - 1].ProgramCounter));
sentry_value_append(frames, frame);
}

sentry_value_t stacktrace = sentry_value_new_object();
sentry_value_set_by_key(stacktrace, "frames", frames);

return stacktrace;
}

ESentryLevel SentryConvertorsDesktop::SentryLevelToUnreal(sentry_value_t level)
{
FString levelStr = FString(sentry_value_as_string(level));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

#include "Convenience/SentryInclude.h"

#include "GenericPlatform/GenericPlatformStackWalk.h"

#if USE_SENTRY_NATIVE

class USentryId;
Expand All @@ -18,7 +20,9 @@ class SentryConvertorsDesktop
/** Conversions to native desktop (Windows/Mac) types */
static sentry_level_e SentryLevelToNative(ESentryLevel level);
static sentry_value_t StringMapToNative(const TMap<FString, FString>& map);
static sentry_value_t StringArrayToNative(const TArray<FString>& array );
static sentry_value_t StringArrayToNative(const TArray<FString>& array);
static sentry_value_t AddressToNative(uint64 address);
static sentry_value_t CallstackToNative(const TArray<FProgramCounterSymbolInfo>& callstack);

/** Conversions from native desktop (Windows/Mac) types */
static ESentryLevel SentryLevelToUnreal(sentry_value_t level);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ void SentrySubsystemDesktop::InitWithSettings(const USentrySettings* settings, U
sentry_options_set_max_breadcrumbs(options, settings->MaxBreadcrumbs);
sentry_options_set_before_send(options, HandleBeforeSend, this);
sentry_options_set_on_crash(options, HandleBeforeCrash, this);
sentry_options_set_shutdown_timeout(options, 3000);
Copy link
Contributor

Choose a reason for hiding this comment

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

Where is this coming from?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Previously, UE caused the app to crash whenever an assertion failed and the Crashpad captured these crashes for us. We've changed how assertions are handled so now when it fails a timeout is used to ensure a corresponding event is sent to Sentry within the same session before shutting down the app manually.


#if PLATFORM_LINUX
sentry_options_set_transport(options, FSentryTransport::Create());
Expand Down Expand Up @@ -300,6 +301,35 @@ USentryId* SentrySubsystemDesktop::CaptureEventWithScope(USentryEvent* event, co
return Id;
}

USentryId* SentrySubsystemDesktop::CaptureException(const FString& type, const FString& message, int32 framesToSkip)
{
sentry_value_t exceptionEvent = sentry_value_new_event();

auto StackFrames = FGenericPlatformStackWalk::GetStack(framesToSkip);
sentry_value_set_by_key(exceptionEvent, "stacktrace", SentryConvertorsDesktop::CallstackToNative(StackFrames));

sentry_value_t nativeException = sentry_value_new_exception(TCHAR_TO_ANSI(*type), TCHAR_TO_ANSI(*message));
sentry_event_add_exception(exceptionEvent, nativeException);

sentry_uuid_t id = sentry_capture_event(exceptionEvent);
return SentryConvertorsDesktop::SentryIdToUnreal(id);
}

USentryId* SentrySubsystemDesktop::CaptureAssertion(const FString& type, const FString& message)
{
return CaptureException(type, message, 7);
}

USentryId* SentrySubsystemDesktop::CaptureEnsure(const FString& type, const FString& message)
{
#if PLATFORM_WINDOWS && ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 3
int32 framesToSkip = 8;
#else
int32 framesToSkip = 7;
#endif
return CaptureException(type, message, framesToSkip);
}

void SentrySubsystemDesktop::CaptureUserFeedback(USentryUserFeedback* userFeedback)
{
TSharedPtr<SentryUserFeedbackDesktop> userFeedbackDesktop = StaticCastSharedPtr<SentryUserFeedbackDesktop>(userFeedback->GetNativeImpl());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ class SentrySubsystemDesktop : public ISentrySubsystem
virtual USentryId* CaptureMessageWithScope(const FString& message, const FConfigureScopeNativeDelegate& onScopeConfigure, ESentryLevel level) override;
virtual USentryId* CaptureEvent(USentryEvent* event) override;
virtual USentryId* CaptureEventWithScope(USentryEvent* event, const FConfigureScopeNativeDelegate& onScopeConfigure) override;
virtual USentryId* CaptureException(const FString& type, const FString& message, int32 framesToSkip) override;
virtual USentryId* CaptureAssertion(const FString& type, const FString& message) override;
virtual USentryId* CaptureEnsure(const FString& type, const FString& message) override;
virtual void CaptureUserFeedback(USentryUserFeedback* userFeedback) override;
virtual void SetUser(USentryUser* user) override;
virtual void RemoveUser() override;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ class ISentrySubsystem
virtual USentryId* CaptureMessageWithScope(const FString& message, const FConfigureScopeNativeDelegate& onConfigureScope, ESentryLevel level) = 0;
virtual USentryId* CaptureEvent(USentryEvent* event) = 0;
virtual USentryId* CaptureEventWithScope(USentryEvent* event, const FConfigureScopeNativeDelegate& onConfigureScope) = 0;
virtual USentryId* CaptureException(const FString& type, const FString& message, int32 framesToSkip = 0) = 0;
virtual USentryId* CaptureAssertion(const FString& type, const FString& message) = 0;
virtual USentryId* CaptureEnsure(const FString& type, const FString& message) = 0;
virtual void CaptureUserFeedback(USentryUserFeedback* userFeedback) = 0;
virtual void SetUser(USentryUser* user) = 0;
virtual void RemoveUser() = 0;
Expand Down
Loading
Loading