Skip to content

Commit 63992d9

Browse files
authored
Add callback to pre-process breadcrumbs before adding (#814)
* Add beforeBreadcrumb callback stubs * Add hook implementation for Apple * Fix threading issues on Android * Fix potential recursive onBeforeBreadcrumb handler invocation when printing to logs * Update changelog * Update package snapshot * Remove unimplemented api * Replace game thread check with static init check * Updated GC checks * Update package snapshots * Fix beforeBreadcrumb for native * Fix minor error/warnings * Remove static init check * Update readme * Add beforeBreadcrumbHandler to sample * Fix build error on Mac
1 parent 952e75c commit 63992d9

23 files changed

+1062
-862
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
- Allow Sentry CLI to authenticate via environment variables during debug symbols upload ([#836](https://github.com/getsentry/sentry-unreal/pull/836))
1212
- Added the ability to specify a separate DSN for crashes while in editor vs cooked title ([#853](https://github.com/getsentry/sentry-unreal/pull/853))
13+
- Add callback to pre-process breadcrumbs before adding ([#814](https://github.com/getsentry/sentry-unreal/pull/814))
1314

1415
### Fixes
1516

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ Blog posts:
5353

5454
- Only crash events captured on Android contain the full callstack. Events that were captured manually won't have the native C++ part there.
5555

56-
- If an event was captured during the garbage collection, the `BeforeSendHandler` will not be invoked.
56+
- `BeforeSendHandler` and `BeforeBreadcrumbHandler` hooks will not be invoked during garbage collection.
5757

5858
- It may be required to upgrade the C++ standard library (`libstdc++`) on older Linux distributions (such as Ubuntu 18.04 and 20.04) to ensure crashpad handler proper functionality within the deployment environment. This can be achieved with something like this:
5959
```

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
#include "Dom/JsonObject.h"
2929
#include "Serialization/JsonSerializer.h"
3030

31-
void FAndroidSentrySubsystem::InitWithSettings(const USentrySettings* settings, USentryBeforeSendHandler* beforeSendHandler, USentryTraceSampler* traceSampler)
31+
void FAndroidSentrySubsystem::InitWithSettings(const USentrySettings* settings, USentryBeforeSendHandler* beforeSendHandler, USentryBeforeBreadcrumbHandler* beforeBreadcrumbHandler, USentryTraceSampler* traceSampler)
3232
{
3333
TSharedPtr<FJsonObject> SettingsJson = MakeShareable(new FJsonObject);
3434
SettingsJson->SetStringField(TEXT("dsn"), settings->Dsn);
@@ -55,6 +55,10 @@ void FAndroidSentrySubsystem::InitWithSettings(const USentrySettings* settings,
5555
{
5656
SettingsJson->SetNumberField(TEXT("tracesSampler"), (jlong)traceSampler);
5757
}
58+
if(beforeBreadcrumbHandler != nullptr)
59+
{
60+
SettingsJson->SetNumberField(TEXT("beforeBreadcrumb"), (jlong)beforeBreadcrumbHandler);
61+
}
5862

5963
FString SettingsJsonStr;
6064
TSharedRef<TJsonWriter<>> JsonWriter = TJsonWriterFactory<>::Create(&SettingsJsonStr);

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
class FAndroidSentrySubsystem : public ISentrySubsystem
88
{
99
public:
10-
virtual void InitWithSettings(const USentrySettings* settings, USentryBeforeSendHandler* beforeSendHandler, USentryTraceSampler* traceSampler) override;
10+
virtual void InitWithSettings(const USentrySettings* settings, USentryBeforeSendHandler* beforeSendHandler, USentryBeforeBreadcrumbHandler* beforeBreadcrumbHandler, USentryTraceSampler* traceSampler) override;
1111
virtual void Close() override;
1212
virtual bool IsEnabled() override;
1313
virtual ESentryCrashedLastRun IsCrashedLastRun() override;

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
public class SentryBridgeJava {
3535
public static native void onConfigureScope(long callbackAddr, IScope scope);
3636
public static native SentryEvent onBeforeSend(long handlerAddr, SentryEvent event, Hint hint);
37+
public static native Breadcrumb onBeforeBreadcrumb(long handlerAddr, Breadcrumb breadcrumb, Hint hint);
3738
public static native float onTracesSampler(long samplerAddr, SamplingContext samplingContext);
3839

3940
public static void init(Activity activity, final String settingsJsonStr, final long beforeSendHandler) {
@@ -85,6 +86,15 @@ public Double sample(SamplingContext samplingContext) {
8586
}
8687
});
8788
}
89+
if(settingJson.has("beforeBreadcrumb")) {
90+
final long beforeBreadcrumbAddr = settingJson.getLong("beforeBreadcrumb");
91+
options.setBeforeBreadcrumb(new SentryOptions.BeforeBreadcrumbCallback() {
92+
@Override
93+
public Breadcrumb execute(Breadcrumb breadcrumb, Hint hint) {
94+
return onBeforeBreadcrumb(beforeBreadcrumbAddr, breadcrumb, hint);
95+
}
96+
});
97+
}
8898
} catch (JSONException e) {
8999
throw new RuntimeException(e);
90100
}

plugin-dev/Source/Sentry/Private/Android/Jni/SentryJniAndroid.cpp

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,19 @@
55
#include "Android/Infrastructure/SentryJavaClasses.h"
66
#include "Android/AndroidSentrySubsystem.h"
77
#include "Android/SentryScopeAndroid.h"
8+
#include "Android/SentryBreadcrumbAndroid.h"
89
#include "Android/SentryEventAndroid.h"
910
#include "Android/SentryHintAndroid.h"
1011
#include "Android/SentrySamplingContextAndroid.h"
1112

1213
#include "Android/AndroidJNI.h"
1314

1415
#include "SentryDefines.h"
16+
#include "SentryBreadcrumb.h"
1517
#include "SentryEvent.h"
1618
#include "SentryHint.h"
1719
#include "SentryBeforeSendHandler.h"
20+
#include "SentryBeforeBreadcrumbHandler.h"
1821
#include "SentryTraceSampler.h"
1922
#include "SentrySamplingContext.h"
2023
#include "UObject/GarbageCollection.h"
@@ -66,6 +69,32 @@ JNI_METHOD jobject Java_io_sentry_unreal_SentryBridgeJava_onBeforeSend(JNIEnv* e
6669
return ProcessedEvent ? event : nullptr;
6770
}
6871

72+
JNI_METHOD jobject Java_io_sentry_unreal_SentryBridgeJava_onBeforeBreadcrumb(JNIEnv* env, jclass clazz, jlong objAddr, jobject breadcrumb, jobject hint)
73+
{
74+
if (FUObjectThreadContext::Get().IsRoutingPostLoad)
75+
{
76+
// Don't print to logs within `onBeforeBreadcrumb` handler as this can lead to creating new breadcrumb
77+
return breadcrumb;
78+
}
79+
80+
if (IsGarbageCollecting())
81+
{
82+
// If breadcrumb is added during garbage collection we can't instantiate UObjects safely or obtain a GC lock
83+
// since there is no guarantee it will be ever freed.
84+
// In this case breadcrumb will be added without calling a `beforeBreadcrumb` handler.
85+
return breadcrumb;
86+
}
87+
88+
USentryBeforeBreadcrumbHandler* handler = reinterpret_cast<USentryBeforeBreadcrumbHandler*>(objAddr);
89+
90+
USentryBreadcrumb* BreadcrumbToProcess = USentryBreadcrumb::Create(MakeShareable(new SentryBreadcrumbAndroid(breadcrumb)));
91+
USentryHint* HintToProcess = USentryHint::Create(MakeShareable(new SentryHintAndroid(hint)));
92+
93+
USentryBreadcrumb* ProcessedBreadcrumb = handler->HandleBeforeBreadcrumb(BreadcrumbToProcess, HintToProcess);
94+
95+
return ProcessedBreadcrumb ? breadcrumb : nullptr;
96+
}
97+
6998
JNI_METHOD jfloat Java_io_sentry_unreal_SentryBridgeJava_onTracesSampler(JNIEnv* env, jclass clazz, jlong objAddr, jobject samplingContext)
7099
{
71100
FGCScopeGuard GCScopeGuard;

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

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@
1212
#include "SentrySamplingContextApple.h"
1313
#include "SentryIdApple.h"
1414

15+
#include "SentryBreadcrumb.h"
1516
#include "SentryEvent.h"
1617
#include "SentrySettings.h"
1718
#include "SentryBeforeSendHandler.h"
19+
#include "SentryBeforeBreadcrumbHandler.h"
1820
#include "SentryDefines.h"
1921
#include "SentrySamplingContext.h"
2022
#include "SentryTraceSampler.h"
@@ -30,7 +32,7 @@
3032
#include "UObject/UObjectThreadContext.h"
3133
#include "Utils/SentryLogUtils.h"
3234

33-
void FAppleSentrySubsystem::InitWithSettings(const USentrySettings* settings, USentryBeforeSendHandler* beforeSendHandler, USentryTraceSampler* traceSampler)
35+
void FAppleSentrySubsystem::InitWithSettings(const USentrySettings* settings, USentryBeforeSendHandler* beforeSendHandler, USentryBeforeBreadcrumbHandler* beforeBreadcrumbHandler, USentryTraceSampler* traceSampler)
3436
{
3537
[SENTRY_APPLE_CLASS(PrivateSentrySDKOnly) setSdkName:@"sentry.cocoa.unreal"];
3638

@@ -110,6 +112,30 @@ void FAppleSentrySubsystem::InitWithSettings(const USentrySettings* settings, US
110112
return traceSampler->Sample(Context, samplingValue) ? [NSNumber numberWithFloat:samplingValue] : nil;
111113
};
112114
}
115+
if (beforeBreadcrumbHandler != nullptr)
116+
{
117+
options.beforeBreadcrumb = ^SentryBreadcrumb* (SentryBreadcrumb* breadcrumb) {
118+
if (FUObjectThreadContext::Get().IsRoutingPostLoad)
119+
{
120+
// Don't print to logs within `onBeforeBreadcrumb` handler as this can lead to creating new breadcrumb
121+
return breadcrumb;
122+
}
123+
124+
if (IsGarbageCollecting())
125+
{
126+
// If breadcrumb is added during garbage collection we can't instantiate UObjects safely or obtain a GC lock
127+
// since there is no guarantee it will be ever freed.
128+
// In this case breadcrumb will be added without calling a `beforeBreadcrumb` handler.
129+
return breadcrumb;
130+
}
131+
132+
USentryBreadcrumb* BreadcrumbToProcess = USentryBreadcrumb::Create(MakeShareable(new SentryBreadcrumbApple(breadcrumb)));
133+
134+
USentryBreadcrumb* ProcessedBreadcrumb = beforeBreadcrumbHandler->HandleBeforeBreadcrumb(BreadcrumbToProcess, nullptr);
135+
136+
return ProcessedBreadcrumb ? breadcrumb : nullptr;
137+
};
138+
}
113139
}];
114140

115141
dispatch_group_leave(sentryDispatchGroup);

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
class FAppleSentrySubsystem : public ISentrySubsystem
88
{
99
public:
10-
virtual void InitWithSettings(const USentrySettings* settings, USentryBeforeSendHandler* beforeSendHandler, USentryTraceSampler* traceSampler) override;
10+
virtual void InitWithSettings(const USentrySettings* settings, USentryBeforeSendHandler* beforeSendHandler, USentryBeforeBreadcrumbHandler* beforeBreadcrumbHandler, USentryTraceSampler* traceSampler) override;
1111
virtual void Close() override;
1212
virtual bool IsEnabled() override;
1313
virtual ESentryCrashedLastRun IsCrashedLastRun() override;

plugin-dev/Source/Sentry/Private/GenericPlatform/GenericPlatformSentrySubsystem.cpp

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
#include "SentryEvent.h"
1616
#include "SentryModule.h"
1717
#include "SentryBeforeSendHandler.h"
18+
#include "SentryBeforeBreadcrumbHandler.h"
19+
#include "SentryBreadcrumb.h"
1820

1921
#include "SentryTraceSampler.h"
2022

@@ -69,6 +71,18 @@ void PrintVerboseLog(sentry_level_t level, const char* message, va_list args, vo
6971
}
7072
}
7173

74+
/* static */ sentry_value_t FGenericPlatformSentrySubsystem::HandleBeforeBreadcrumb(sentry_value_t breadcrumb, void* hint, void* closure)
75+
{
76+
if (closure)
77+
{
78+
return StaticCast<FGenericPlatformSentrySubsystem*>(closure)->OnBeforeBreadcrumb(breadcrumb, hint, closure);
79+
}
80+
else
81+
{
82+
return breadcrumb;
83+
}
84+
}
85+
7286
/* static */ sentry_value_t FGenericPlatformSentrySubsystem::HandleOnCrash(const sentry_ucontext_t* uctx, sentry_value_t event, void* closure)
7387
{
7488
if (closure)
@@ -114,6 +128,39 @@ sentry_value_t FGenericPlatformSentrySubsystem::OnBeforeSend(sentry_value_t even
114128
return ProcessedEvent ? event : sentry_value_new_null();
115129
}
116130

131+
// Currently this handler is not set anywhere since the Unreal SDK doesn't use `sentry_add_breadcrumb` directly and relies on
132+
// custom scope implementation to store breadcrumbs instead.
133+
// The support for it will be enabled with https://github.com/getsentry/sentry-native/pull/1166
134+
sentry_value_t FGenericPlatformSentrySubsystem::OnBeforeBreadcrumb(sentry_value_t breadcrumb, void* hint, void* closure)
135+
{
136+
if (!closure || this != closure)
137+
{
138+
return breadcrumb;
139+
}
140+
141+
if (FUObjectThreadContext::Get().IsRoutingPostLoad)
142+
{
143+
// Don't print to logs within `onBeforeBreadcrumb` handler as this can lead to creating new breadcrumb
144+
return breadcrumb;
145+
}
146+
147+
if (IsGarbageCollecting())
148+
{
149+
// If breadcrumb is added during garbage collection we can't instantiate UObjects safely or obtain a GC lock
150+
// since there is no guarantee it will be ever freed.
151+
// In this case breadcrumb will be added without calling a `beforeBreadcrumb` handler.
152+
return breadcrumb;
153+
}
154+
155+
TSharedPtr<FGenericPlatformSentryBreadcrumb> Breadcrumb = MakeShareable(new FGenericPlatformSentryBreadcrumb(breadcrumb));
156+
157+
USentryBreadcrumb* BreadcrumbToProcess = USentryBreadcrumb::Create(Breadcrumb);
158+
159+
USentryBreadcrumb* ProcessedBreadcrumb = GetBeforeBreadcrumbHandler()->HandleBeforeBreadcrumb(BreadcrumbToProcess, nullptr);
160+
161+
return ProcessedBreadcrumb ? breadcrumb : sentry_value_new_null();
162+
}
163+
117164
sentry_value_t FGenericPlatformSentrySubsystem::OnCrash(const sentry_ucontext_t* uctx, sentry_value_t event, void* closure)
118165
{
119166
if (!closure || this != closure)
@@ -159,16 +206,19 @@ sentry_value_t FGenericPlatformSentrySubsystem::OnCrash(const sentry_ucontext_t*
159206

160207
FGenericPlatformSentrySubsystem::FGenericPlatformSentrySubsystem()
161208
: beforeSend(nullptr)
209+
, beforeBreadcrumb(nullptr)
162210
, crashReporter(MakeShareable(new FGenericPlatformSentryCrashReporter))
163211
, isEnabled(false)
164212
, isStackTraceEnabled(true)
213+
, isPiiAttachmentEnabled(false)
165214
, isScreenshotAttachmentEnabled(false)
166215
{
167216
}
168217

169-
void FGenericPlatformSentrySubsystem::InitWithSettings(const USentrySettings* settings, USentryBeforeSendHandler* beforeSendHandler, USentryTraceSampler* traceSampler)
218+
void FGenericPlatformSentrySubsystem::InitWithSettings(const USentrySettings* settings, USentryBeforeSendHandler* beforeSendHandler, USentryBeforeBreadcrumbHandler* beforeBreadcrumbHandler, USentryTraceSampler* traceSampler)
170219
{
171220
beforeSend = beforeSendHandler;
221+
beforeBreadcrumb = beforeBreadcrumbHandler;
172222

173223
scopeStack.Push(MakeShareable(new FGenericPlatformSentryScope()));
174224

@@ -298,6 +348,15 @@ ESentryCrashedLastRun FGenericPlatformSentrySubsystem::IsCrashedLastRun()
298348

299349
void FGenericPlatformSentrySubsystem::AddBreadcrumb(TSharedPtr<ISentryBreadcrumb> breadcrumb)
300350
{
351+
if (beforeBreadcrumb != nullptr)
352+
{
353+
sentry_value_t processdBreadcrumb = HandleBeforeBreadcrumb(StaticCastSharedPtr<FGenericPlatformSentryBreadcrumb>(breadcrumb)->GetNativeObject(), nullptr, this);
354+
if (sentry_value_is_null(processdBreadcrumb))
355+
{
356+
return;
357+
}
358+
}
359+
301360
GetCurrentScope()->AddBreadcrumb(breadcrumb);
302361
}
303362

@@ -310,6 +369,15 @@ void FGenericPlatformSentrySubsystem::AddBreadcrumbWithParams(const FString& Mes
310369
Breadcrumb->SetData(Data);
311370
Breadcrumb->SetLevel(Level);
312371

372+
if (beforeBreadcrumb != nullptr)
373+
{
374+
sentry_value_t processdBreadcrumb = HandleBeforeBreadcrumb(Breadcrumb->GetNativeObject(), nullptr, this);
375+
if (sentry_value_is_null(processdBreadcrumb))
376+
{
377+
return;
378+
}
379+
}
380+
313381
GetCurrentScope()->AddBreadcrumb(Breadcrumb);
314382
}
315383

@@ -514,6 +582,11 @@ USentryBeforeSendHandler* FGenericPlatformSentrySubsystem::GetBeforeSendHandler(
514582
return beforeSend;
515583
}
516584

585+
USentryBeforeBreadcrumbHandler* FGenericPlatformSentrySubsystem::GetBeforeBreadcrumbHandler()
586+
{
587+
return beforeBreadcrumb;
588+
}
589+
517590
void FGenericPlatformSentrySubsystem::TryCaptureScreenshot() const
518591
{
519592
if (!isScreenshotAttachmentEnabled)

plugin-dev/Source/Sentry/Private/GenericPlatform/GenericPlatformSentrySubsystem.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ class FGenericPlatformSentrySubsystem : public ISentrySubsystem
1818
public:
1919
FGenericPlatformSentrySubsystem();
2020

21-
virtual void InitWithSettings(const USentrySettings* settings, USentryBeforeSendHandler* beforeSendHandler, USentryTraceSampler* traceSampler) override;
21+
virtual void InitWithSettings(const USentrySettings* settings, USentryBeforeSendHandler* beforeSendHandler, USentryBeforeBreadcrumbHandler* beforeBreadcrumbHandler, USentryTraceSampler* traceSampler) override;
2222
virtual void Close() override;
2323
virtual bool IsEnabled() override;
2424
virtual ESentryCrashedLastRun IsCrashedLastRun() override;
@@ -47,6 +47,7 @@ class FGenericPlatformSentrySubsystem : public ISentrySubsystem
4747
virtual TSharedPtr<ISentryTransactionContext> ContinueTrace(const FString& sentryTrace, const TArray<FString>& baggageHeaders) override;
4848

4949
USentryBeforeSendHandler* GetBeforeSendHandler();
50+
USentryBeforeBreadcrumbHandler* GetBeforeBreadcrumbHandler();
5051

5152
void TryCaptureScreenshot() const;
5253

@@ -67,16 +68,19 @@ class FGenericPlatformSentrySubsystem : public ISentrySubsystem
6768
virtual FString GetHandlerExecutableName() const { return TEXT("invalid"); }
6869

6970
virtual sentry_value_t OnBeforeSend(sentry_value_t event, void* hint, void* closure);
71+
virtual sentry_value_t OnBeforeBreadcrumb(sentry_value_t breadcrumb, void* hint, void* closure);
7072
virtual sentry_value_t OnCrash(const sentry_ucontext_t* uctx, sentry_value_t event, void* closure);
7173

7274
private:
7375
/**
7476
* Static wrappers that are passed to the Sentry library.
7577
*/
7678
static sentry_value_t HandleBeforeSend(sentry_value_t event, void* hint, void* closure);
79+
static sentry_value_t HandleBeforeBreadcrumb(sentry_value_t breadcrumb, void* hint, void* closure);
7780
static sentry_value_t HandleOnCrash(const sentry_ucontext_t* uctx, sentry_value_t event, void* closure);
7881

7982
USentryBeforeSendHandler* beforeSend;
83+
USentryBeforeBreadcrumbHandler* beforeBreadcrumb;
8084

8185
TSharedPtr<FGenericPlatformSentryCrashReporter> crashReporter;
8286

0 commit comments

Comments
 (0)