Skip to content

Commit 51bea9b

Browse files
authored
Fix invalid assertion issues grouping (#537)
1 parent 1fbef40 commit 51bea9b

23 files changed

+331
-23
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
### Fixes
1111

12+
- The SDK now correctly captures and groups Assertions ([#537](https://github.com/getsentry/sentry-unreal/pull/537))
1213
- Add path strings escaping for debug symbol upload script ([#561](https://github.com/getsentry/sentry-unreal/pull/561))
1314
- Fix crashes not being reported during garbage collection ([#566](https://github.com/getsentry/sentry-unreal/pull/566))
1415
- The SDK now uploads debug symbols properly with the `Android File Server` plugin enabled in UE 5.0 and newer ([#568](https://github.com/getsentry/sentry-unreal/pull/568))

plugin-dev/Source/Sentry/Private/Android/Java/SentryBridgeJava.java

+43-10
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,16 @@
33
package io.sentry.unreal;
44

55
import android.app.Activity;
6-
import android.util.Log;
76

87
import androidx.annotation.NonNull;
98

109
import org.json.JSONArray;
1110
import org.json.JSONException;
1211
import org.json.JSONObject;
1312

13+
import java.util.Collections;
1414
import java.util.HashMap;
15+
import java.util.List;
1516
import java.util.Map;
1617

1718
import io.sentry.Breadcrumb;
@@ -26,7 +27,10 @@
2627
import io.sentry.SentryOptions;
2728
import io.sentry.android.core.SentryAndroid;
2829
import io.sentry.android.core.SentryAndroidOptions;
30+
import io.sentry.protocol.SentryException;
2931
import io.sentry.protocol.SentryId;
32+
import io.sentry.protocol.SentryStackFrame;
33+
import io.sentry.protocol.SentryStackTrace;
3034

3135
public class SentryBridgeJava {
3236
public static native void onConfigureScope(long callbackAddr, IScope scope);
@@ -53,6 +57,7 @@ public void configure(SentryAndroidOptions options) {
5357
options.setBeforeSend(new SentryOptions.BeforeSendCallback() {
5458
@Override
5559
public SentryEvent execute(SentryEvent event, Hint hint) {
60+
preProcessEvent(event);
5661
return onBeforeSend(beforeSendHandler, event, hint);
5762
}
5863
});
@@ -73,12 +78,12 @@ public SentryEvent execute(SentryEvent event, Hint hint) {
7378
options.setTracesSampler(new SentryOptions.TracesSamplerCallback() {
7479
@Override
7580
public Double sample(SamplingContext samplingContext) {
76-
float sampleRate = onTracesSampler(samplerAddr, samplingContext);
77-
if(sampleRate >= 0.0f) {
78-
return (double) sampleRate;
79-
} else {
80-
return null;
81-
}
81+
float sampleRate = onTracesSampler(samplerAddr, samplingContext);
82+
if(sampleRate >= 0.0f) {
83+
return (double) sampleRate;
84+
} else {
85+
return null;
86+
}
8287
}
8388
});
8489
}
@@ -89,6 +94,24 @@ public Double sample(SamplingContext samplingContext) {
8994
});
9095
}
9196

97+
private static void preProcessEvent(SentryEvent event) {
98+
if (event.getTags().containsKey("sentry_unreal_exception")) {
99+
SentryException exception = event.getUnhandledException();
100+
if (exception != null) {
101+
exception.setType(event.getTag("sentry_unreal_exception_type"));
102+
exception.setValue(event.getTag("sentry_unreal_exception_message"));
103+
SentryStackTrace trace = exception.getStacktrace();
104+
int numFramesToSkip = Integer.parseInt(event.getTag("sentry_unreal_exception_skip_frames"));
105+
List<SentryStackFrame> frames = trace.getFrames();
106+
trace.setFrames(frames.subList(0, frames.size() - numFramesToSkip));
107+
}
108+
event.removeTag("sentry_unreal_exception_type");
109+
event.removeTag("sentry_unreal_exception_message");
110+
event.removeTag("sentry_unreal_exception_skip_frames");
111+
event.removeTag("sentry_unreal_exception");
112+
}
113+
}
114+
92115
public static void addBreadcrumb(final String message, final String category, final String type, final HashMap<String, String> data, final SentryLevel level) {
93116
Breadcrumb breadcrumb = new Breadcrumb();
94117
breadcrumb.setMessage(message);
@@ -106,8 +129,8 @@ public static SentryId captureMessageWithScope(final String message, final Sentr
106129
SentryId messageId = Sentry.captureMessage(message, new ScopeCallback() {
107130
@Override
108131
public void run(@NonNull IScope scope) {
109-
scope.setLevel(level);
110-
onConfigureScope(callback, scope);
132+
scope.setLevel(level);
133+
onConfigureScope(callback, scope);
111134
}
112135
});
113136
return messageId;
@@ -117,12 +140,22 @@ public static SentryId captureEventWithScope(final SentryEvent event, final long
117140
SentryId eventId = Sentry.captureEvent(event, new ScopeCallback() {
118141
@Override
119142
public void run(@NonNull IScope scope) {
120-
onConfigureScope(callback, scope);
143+
onConfigureScope(callback, scope);
121144
}
122145
});
123146
return eventId;
124147
}
125148

149+
public static SentryId captureException(final String type, final String value) {
150+
SentryException exception = new SentryException();
151+
exception.setType(type);
152+
exception.setValue(value);
153+
SentryEvent event = new SentryEvent();
154+
event.setExceptions(Collections.singletonList(exception));
155+
SentryId eventId = Sentry.captureEvent(event);
156+
return eventId;
157+
}
158+
126159
public static void configureScope(final long callback) {
127160
Sentry.configureScope(new ScopeCallback() {
128161
@Override

plugin-dev/Source/Sentry/Private/Android/SentrySubsystemAndroid.cpp

+28
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,34 @@ USentryId* SentrySubsystemAndroid::CaptureEventWithScope(USentryEvent* event, co
169169
return SentryConvertorsAndroid::SentryIdToUnreal(*id);
170170
}
171171

172+
USentryId* SentrySubsystemAndroid::CaptureException(const FString& type, const FString& message, int32 framesToSkip)
173+
{
174+
return nullptr;
175+
}
176+
177+
USentryId* SentrySubsystemAndroid::CaptureAssertion(const FString& type, const FString& message)
178+
{
179+
const int32 framesToSkip = 8;
180+
181+
// add marker tags specific for Unreal assertions
182+
SetTag(TEXT("sentry_unreal_exception"), TEXT("assert"));
183+
SetTag(TEXT("sentry_unreal_exception_skip_frames"), FString::Printf(TEXT("%d"), framesToSkip));
184+
SetTag(TEXT("sentry_unreal_exception_type"), type);
185+
SetTag(TEXT("sentry_unreal_exception_message"), message);
186+
187+
PLATFORM_BREAK();
188+
189+
return nullptr;
190+
}
191+
192+
USentryId* SentrySubsystemAndroid::CaptureEnsure(const FString& type, const FString& message)
193+
{
194+
auto id = FSentryJavaObjectWrapper::CallStaticObjectMethod<jobject>(SentryJavaClasses::SentryBridgeJava, "captureException", "(Ljava/lang/String;Ljava/lang/String;)Lio/sentry/protocol/SentryId;",
195+
*FSentryJavaObjectWrapper::GetJString(type), *FSentryJavaObjectWrapper::GetJString(message));
196+
197+
return SentryConvertorsAndroid::SentryIdToUnreal(*id);
198+
}
199+
172200
void SentrySubsystemAndroid::CaptureUserFeedback(USentryUserFeedback* userFeedback)
173201
{
174202
TSharedPtr<SentryUserFeedbackAndroid> userFeedbackAndroid = StaticCastSharedPtr<SentryUserFeedbackAndroid>(userFeedback->GetNativeImpl());

plugin-dev/Source/Sentry/Private/Android/SentrySubsystemAndroid.h

+3
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ class SentrySubsystemAndroid : public ISentrySubsystem
1818
virtual USentryId* CaptureMessageWithScope(const FString& message, const FConfigureScopeNativeDelegate& onConfigureScope, ESentryLevel level) override;
1919
virtual USentryId* CaptureEvent(USentryEvent* event) override;
2020
virtual USentryId* CaptureEventWithScope(USentryEvent* event, const FConfigureScopeNativeDelegate& onConfigureScope) override;
21+
virtual USentryId* CaptureException(const FString& type, const FString& message, int32 framesToSkip) override;
22+
virtual USentryId* CaptureAssertion(const FString& type, const FString& message) override;
23+
virtual USentryId* CaptureEnsure(const FString& type, const FString& message) override;
2124
virtual void CaptureUserFeedback(USentryUserFeedback* userFeedback) override;
2225
virtual void SetUser(USentryUser* user) override;
2326
virtual void RemoveUser() override;

plugin-dev/Source/Sentry/Private/Apple/Infrastructure/SentryConvertorsApple.cpp

+20
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
#include "Apple/SentryTransactionApple.h"
1616
#include "Apple/SentrySpanApple.h"
1717

18+
#include "Convenience/SentryMacro.h"
19+
1820
SentryLevel SentryConvertorsApple::SentryLevelToNative(ESentryLevel level)
1921
{
2022
SentryLevel nativeLevel = kSentryLevelDebug;
@@ -72,6 +74,24 @@ NSData* SentryConvertorsApple::ByteDataToNative(const TArray<uint8>& array)
7274
return [NSData dataWithBytes:array.GetData() length:array.Num()];
7375
}
7476

77+
SentryStacktrace* SentryConvertorsApple::CallstackToNative(const TArray<FProgramCounterSymbolInfo>& callstack)
78+
{
79+
int32 framesCount = callstack.Num();
80+
81+
NSMutableArray *arr = [NSMutableArray arrayWithCapacity:framesCount];
82+
83+
for (int i = 0; i < framesCount; ++i)
84+
{
85+
SentryFrame *frame = [[SENTRY_APPLE_CLASS(SentryFrame) alloc] init];
86+
frame.instructionAddress = FString::Printf(TEXT("0x%llx"), callstack[framesCount - i - 1].ProgramCounter).GetNSString();
87+
[arr addObject:frame];
88+
}
89+
90+
SentryStacktrace *trace = [[SENTRY_APPLE_CLASS(SentryStacktrace) alloc] initWithFrames:arr registers:@{}];
91+
92+
return trace;
93+
}
94+
7595
ESentryLevel SentryConvertorsApple::SentryLevelToUnreal(SentryLevel level)
7696
{
7797
ESentryLevel unrealLevel = ESentryLevel::Debug;

plugin-dev/Source/Sentry/Private/Apple/Infrastructure/SentryConvertorsApple.h

+3
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
#include "Convenience/SentryInclude.h"
88

9+
#include "GenericPlatform/GenericPlatformStackWalk.h"
10+
911
class USentryTransactionContext;
1012
class USentryScope;
1113
class USentryId;
@@ -20,6 +22,7 @@ class SentryConvertorsApple
2022
static NSDictionary* StringMapToNative(const TMap<FString, FString>& map);
2123
static NSArray* StringArrayToNative(const TArray<FString>& array);
2224
static NSData* ByteDataToNative(const TArray<uint8>& array);
25+
static SentryStacktrace* CallstackToNative(const TArray<FProgramCounterSymbolInfo>& callstack);
2326

2427
/** Conversions from native iOS types */
2528
static ESentryLevel SentryLevelToUnreal(SentryLevel level);

plugin-dev/Source/Sentry/Private/Apple/SentrySubsystemApple.cpp

+31
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,37 @@ USentryId* SentrySubsystemApple::CaptureEventWithScope(USentryEvent* event, cons
172172
return SentryConvertorsApple::SentryIdToUnreal(id);
173173
}
174174

175+
USentryId* SentrySubsystemApple::CaptureException(const FString& type, const FString& message, int32 framesToSkip)
176+
{
177+
auto StackFrames = FGenericPlatformStackWalk::GetStack(framesToSkip);
178+
179+
SentryException *nativeException = [[SENTRY_APPLE_CLASS(SentryException) alloc] initWithValue:message.GetNSString() type:type.GetNSString()];
180+
NSMutableArray *nativeExceptionArray = [NSMutableArray arrayWithCapacity:1];
181+
[nativeExceptionArray addObject:nativeException];
182+
183+
SentryEvent *exceptionEvent = [[SENTRY_APPLE_CLASS(SentryEvent) alloc] init];
184+
exceptionEvent.exceptions = nativeExceptionArray;
185+
exceptionEvent.stacktrace = SentryConvertorsApple::CallstackToNative(StackFrames);
186+
187+
SentryId* id = [SENTRY_APPLE_CLASS(SentrySDK) captureEvent:exceptionEvent];
188+
return SentryConvertorsApple::SentryIdToUnreal(id);
189+
}
190+
191+
USentryId* SentrySubsystemApple::CaptureAssertion(const FString& type, const FString& message)
192+
{
193+
#if PLATFORM_MAC
194+
int32 framesToSkip = 6;
195+
#elif PLATFORM_IOS
196+
int32 framesToSkip = 5;
197+
#endif
198+
return CaptureException(type, message, framesToSkip);
199+
}
200+
201+
USentryId* SentrySubsystemApple::CaptureEnsure(const FString& type, const FString& message)
202+
{
203+
return CaptureException(type, message, 6);
204+
}
205+
175206
void SentrySubsystemApple::CaptureUserFeedback(USentryUserFeedback* userFeedback)
176207
{
177208
TSharedPtr<SentryUserFeedbackApple> userFeedbackIOS = StaticCastSharedPtr<SentryUserFeedbackApple>(userFeedback->GetNativeImpl());

plugin-dev/Source/Sentry/Private/Apple/SentrySubsystemApple.h

+3
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ class SentrySubsystemApple : public ISentrySubsystem
1818
virtual USentryId* CaptureMessageWithScope(const FString& message, const FConfigureScopeNativeDelegate& onConfigureScope, ESentryLevel level) override;
1919
virtual USentryId* CaptureEvent(USentryEvent* event) override;
2020
virtual USentryId* CaptureEventWithScope(USentryEvent* event, const FConfigureScopeNativeDelegate& onConfigureScope) override;
21+
virtual USentryId* CaptureException(const FString& type, const FString& message, int32 framesToSkip) override;
22+
virtual USentryId* CaptureAssertion(const FString& type, const FString& message) override;
23+
virtual USentryId* CaptureEnsure(const FString& type, const FString& message) override;
2124
virtual void CaptureUserFeedback(USentryUserFeedback* userFeedback) override;
2225
virtual void SetUser(USentryUser* user) override;
2326
virtual void RemoveUser() override;

plugin-dev/Source/Sentry/Private/Desktop/Infrastructure/SentryConvertorsDesktop.cpp

+30
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,36 @@ sentry_value_t SentryConvertorsDesktop::StringArrayToNative(const TArray<FString
7171
return sentryArray;
7272
}
7373

74+
sentry_value_t SentryConvertorsDesktop::AddressToNative(uint64 address)
75+
{
76+
char buffer[32];
77+
size_t written = (size_t)snprintf(buffer, sizeof(buffer), "0x%llx", (unsigned long long)address);
78+
if (written >= sizeof(buffer))
79+
{
80+
return sentry_value_new_null();
81+
}
82+
buffer[written] = '\0';
83+
return sentry_value_new_string(buffer);
84+
}
85+
86+
sentry_value_t SentryConvertorsDesktop::CallstackToNative(const TArray<FProgramCounterSymbolInfo>& callstack)
87+
{
88+
int32 framesCount = callstack.Num();
89+
90+
sentry_value_t frames = sentry_value_new_list();
91+
for (int i = 0; i < framesCount; ++i)
92+
{
93+
sentry_value_t frame = sentry_value_new_object();
94+
sentry_value_set_by_key(frame, "instruction_addr", AddressToNative(callstack[framesCount - i - 1].ProgramCounter));
95+
sentry_value_append(frames, frame);
96+
}
97+
98+
sentry_value_t stacktrace = sentry_value_new_object();
99+
sentry_value_set_by_key(stacktrace, "frames", frames);
100+
101+
return stacktrace;
102+
}
103+
74104
ESentryLevel SentryConvertorsDesktop::SentryLevelToUnreal(sentry_value_t level)
75105
{
76106
FString levelStr = FString(sentry_value_as_string(level));

plugin-dev/Source/Sentry/Private/Desktop/Infrastructure/SentryConvertorsDesktop.h

+5-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
#include "Convenience/SentryInclude.h"
88

9+
#include "GenericPlatform/GenericPlatformStackWalk.h"
10+
911
#if USE_SENTRY_NATIVE
1012

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

2327
/** Conversions from native desktop (Windows/Mac) types */
2428
static ESentryLevel SentryLevelToUnreal(sentry_value_t level);

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

+30
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,7 @@ void SentrySubsystemDesktop::InitWithSettings(const USentrySettings* settings, U
179179
sentry_options_set_max_breadcrumbs(options, settings->MaxBreadcrumbs);
180180
sentry_options_set_before_send(options, HandleBeforeSend, this);
181181
sentry_options_set_on_crash(options, HandleBeforeCrash, this);
182+
sentry_options_set_shutdown_timeout(options, 3000);
182183

183184
#if PLATFORM_LINUX
184185
sentry_options_set_transport(options, FSentryTransport::Create());
@@ -326,6 +327,35 @@ USentryId* SentrySubsystemDesktop::CaptureEventWithScope(USentryEvent* event, co
326327
return Id;
327328
}
328329

330+
USentryId* SentrySubsystemDesktop::CaptureException(const FString& type, const FString& message, int32 framesToSkip)
331+
{
332+
sentry_value_t exceptionEvent = sentry_value_new_event();
333+
334+
auto StackFrames = FGenericPlatformStackWalk::GetStack(framesToSkip);
335+
sentry_value_set_by_key(exceptionEvent, "stacktrace", SentryConvertorsDesktop::CallstackToNative(StackFrames));
336+
337+
sentry_value_t nativeException = sentry_value_new_exception(TCHAR_TO_ANSI(*type), TCHAR_TO_ANSI(*message));
338+
sentry_event_add_exception(exceptionEvent, nativeException);
339+
340+
sentry_uuid_t id = sentry_capture_event(exceptionEvent);
341+
return SentryConvertorsDesktop::SentryIdToUnreal(id);
342+
}
343+
344+
USentryId* SentrySubsystemDesktop::CaptureAssertion(const FString& type, const FString& message)
345+
{
346+
return CaptureException(type, message, 7);
347+
}
348+
349+
USentryId* SentrySubsystemDesktop::CaptureEnsure(const FString& type, const FString& message)
350+
{
351+
#if PLATFORM_WINDOWS && ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 3
352+
int32 framesToSkip = 8;
353+
#else
354+
int32 framesToSkip = 7;
355+
#endif
356+
return CaptureException(type, message, framesToSkip);
357+
}
358+
329359
void SentrySubsystemDesktop::CaptureUserFeedback(USentryUserFeedback* userFeedback)
330360
{
331361
TSharedPtr<SentryUserFeedbackDesktop> userFeedbackDesktop = StaticCastSharedPtr<SentryUserFeedbackDesktop>(userFeedback->GetNativeImpl());

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

+3
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ class SentrySubsystemDesktop : public ISentrySubsystem
2727
virtual USentryId* CaptureMessageWithScope(const FString& message, const FConfigureScopeNativeDelegate& onScopeConfigure, ESentryLevel level) override;
2828
virtual USentryId* CaptureEvent(USentryEvent* event) override;
2929
virtual USentryId* CaptureEventWithScope(USentryEvent* event, const FConfigureScopeNativeDelegate& onScopeConfigure) override;
30+
virtual USentryId* CaptureException(const FString& type, const FString& message, int32 framesToSkip) override;
31+
virtual USentryId* CaptureAssertion(const FString& type, const FString& message) override;
32+
virtual USentryId* CaptureEnsure(const FString& type, const FString& message) override;
3033
virtual void CaptureUserFeedback(USentryUserFeedback* userFeedback) override;
3134
virtual void SetUser(USentryUser* user) override;
3235
virtual void RemoveUser() override;

plugin-dev/Source/Sentry/Private/Interface/SentrySubsystemInterface.h

+3
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ class ISentrySubsystem
3434
virtual USentryId* CaptureMessageWithScope(const FString& message, const FConfigureScopeNativeDelegate& onConfigureScope, ESentryLevel level) = 0;
3535
virtual USentryId* CaptureEvent(USentryEvent* event) = 0;
3636
virtual USentryId* CaptureEventWithScope(USentryEvent* event, const FConfigureScopeNativeDelegate& onConfigureScope) = 0;
37+
virtual USentryId* CaptureException(const FString& type, const FString& message, int32 framesToSkip = 0) = 0;
38+
virtual USentryId* CaptureAssertion(const FString& type, const FString& message) = 0;
39+
virtual USentryId* CaptureEnsure(const FString& type, const FString& message) = 0;
3740
virtual void CaptureUserFeedback(USentryUserFeedback* userFeedback) = 0;
3841
virtual void SetUser(USentryUser* user) = 0;
3942
virtual void RemoveUser() = 0;

0 commit comments

Comments
 (0)