Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,14 @@
- `CaptureUserFeedback` function in `SentrySubsystem` replaced with `CaptureFeedback`
- `CreateSentryUserFeedback` function in `SentryLibrary` replaced with `CreateSentryFeedback`
- On Windows and Linux, `ToString` function of `SentryId` class now returns the ID without dashes
- `StartTransactionWithContextAndOptions` function in `SentrySubsystem` now accepts `FSentryTransactionOptions` struct instead of string map
- `GetCustomSamplingContext` function in `SentrySamplingContext` now returns `TMap<FString, FSentryVariant>` instead of string map

### Features

- Add functionality to give/revoke user consent for crash uploads ([#1053](https://github.com/getsentry/sentry-unreal/pull/1053))
- Add new API for capturing user feedback ([#1051](https://github.com/getsentry/sentry-unreal/pull/1051))
- Add Traces sampling function support for Windows and Linux ([#1057](https://github.com/getsentry/sentry-unreal/pull/1057))
- Read `DSN`, `Environment` and `Release` options from environment variables ([#1054](https://github.com/getsentry/sentry-unreal/pull/1054))

### Dependencies
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,15 @@ TSharedPtr<ISentryTransactionContext> FAndroidSentrySamplingContext::GetTransact
return MakeShareable(new FAndroidSentryTransactionContext(*transactionContext));
}

TMap<FString, FString> FAndroidSentrySamplingContext::GetCustomSamplingContext() const
TMap<FString, FSentryVariant> FAndroidSentrySamplingContext::GetCustomSamplingContext() const
{
auto customSamplingContext = CallObjectMethod<jobject>(GetCustomSamplingContextMethod);
if (!customSamplingContext)
return TMap<FString, FString>();
return TMap<FString, FSentryVariant>();

FSentryJavaObjectWrapper NativeCustomSamplingContext(SentryJavaClasses::CustomSamplingContext, *customSamplingContext);
FSentryJavaMethod GetDataMethod = NativeCustomSamplingContext.GetMethod("getData", "()Ljava/util/Map;");

auto data = NativeCustomSamplingContext.CallObjectMethod<jobject>(GetDataMethod);
return FAndroidSentryConverters::StringMapToUnreal(*data);
return FAndroidSentryConverters::VariantMapToUnreal(*data);
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class FAndroidSentrySamplingContext : public ISentrySamplingContext, public FSen
void SetupClassMethods();

virtual TSharedPtr<ISentryTransactionContext> GetTransactionContext() const override;
virtual TMap<FString, FString> GetCustomSamplingContext() const override;
virtual TMap<FString, FSentryVariant> GetCustomSamplingContext() const override;

private:
FSentryJavaMethod GetTransactionContextMethod;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -297,12 +297,12 @@ TSharedPtr<ISentryTransaction> FAndroidSentrySubsystem::StartTransactionWithCont
return StartTransactionWithContext(context);
}

TSharedPtr<ISentryTransaction> FAndroidSentrySubsystem::StartTransactionWithContextAndOptions(TSharedPtr<ISentryTransactionContext> context, const TMap<FString, FString>& options)
TSharedPtr<ISentryTransaction> FAndroidSentrySubsystem::StartTransactionWithContextAndOptions(TSharedPtr<ISentryTransactionContext> context, const FSentryTransactionOptions& options)
{
TSharedPtr<FAndroidSentryTransactionContext> transactionContextAndroid = StaticCastSharedPtr<FAndroidSentryTransactionContext>(context);

TSharedPtr<FAndroidSentryTransactionOptions> transactionOptionsAndroid = MakeShareable(new FAndroidSentryTransactionOptions());
transactionOptionsAndroid->SetCustomSamplingContext(options);
transactionOptionsAndroid->SetCustomSamplingContext(options.CustomSamplingContext);

auto transaction = FSentryJavaObjectWrapper::CallStaticObjectMethod<jobject>(SentryJavaClasses::Sentry, "startTransaction", "(Lio/sentry/TransactionContext;Lio/sentry/TransactionOptions;)Lio/sentry/ITransaction;",
transactionContextAndroid->GetJObject(), transactionOptionsAndroid->GetJObject());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class FAndroidSentrySubsystem : public ISentrySubsystem
virtual TSharedPtr<ISentryTransaction> StartTransaction(const FString& name, const FString& operation) override;
virtual TSharedPtr<ISentryTransaction> StartTransactionWithContext(TSharedPtr<ISentryTransactionContext> context) override;
virtual TSharedPtr<ISentryTransaction> StartTransactionWithContextAndTimestamp(TSharedPtr<ISentryTransactionContext> context, int64 timestamp) override;
virtual TSharedPtr<ISentryTransaction> StartTransactionWithContextAndOptions(TSharedPtr<ISentryTransactionContext> context, const TMap<FString, FString>& options) override;
virtual TSharedPtr<ISentryTransaction> StartTransactionWithContextAndOptions(TSharedPtr<ISentryTransactionContext> context, const FSentryTransactionOptions& options) override;
virtual TSharedPtr<ISentryTransactionContext> ContinueTrace(const FString& sentryTrace, const TArray<FString>& baggageHeaders) override;

virtual void HandleAssert() override;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@ void FAndroidSentryTransactionOptions::SetupClassMethods()
SetCustomSamplingContextMethod = GetMethod("setCustomSamplingContext", "(Lio/sentry/CustomSamplingContext;)V");
}

void FAndroidSentryTransactionOptions::SetCustomSamplingContext(const TMap<FString, FString>& data)
void FAndroidSentryTransactionOptions::SetCustomSamplingContext(const TMap<FString, FSentryVariant>& data)
{
FSentryJavaObjectWrapper NativeCustomSamplingContext(SentryJavaClasses::CustomSamplingContext, "()V");
FSentryJavaMethod SetMethod = NativeCustomSamplingContext.GetMethod("set", "(Ljava/lang/String;Ljava/lang/Object;)V");

for (const auto& dataItem : data)
{
NativeCustomSamplingContext.CallMethod<void>(SetMethod, *GetJString(dataItem.Key), *GetJString(dataItem.Value));
NativeCustomSamplingContext.CallMethod<void>(SetMethod, *GetJString(dataItem.Key), FAndroidSentryConverters::VariantToNative(dataItem.Value)->GetJObject());
}

CallMethod<void>(SetCustomSamplingContextMethod, NativeCustomSamplingContext.GetJObject());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

#pragma once

#include "SentryVariant.h"

#include "Infrastructure/AndroidSentryJavaObjectWrapper.h"

class FAndroidSentryTransactionOptions : public FSentryJavaObjectWrapper
Expand All @@ -11,7 +13,7 @@ class FAndroidSentryTransactionOptions : public FSentryJavaObjectWrapper

void SetupClassMethods();

void SetCustomSamplingContext(const TMap<FString, FString>& data);
void SetCustomSamplingContext(const TMap<FString, FSentryVariant>& data);

private:
FSentryJavaMethod SetCustomSamplingContextMethod;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ TSharedPtr<ISentryTransactionContext> FAppleSentrySamplingContext::GetTransactio
return MakeShareable(new FAppleSentryTransactionContext(SamplingContext.transactionContext));
}

TMap<FString, FString> FAppleSentrySamplingContext::GetCustomSamplingContext() const
TMap<FString, FSentryVariant> FAppleSentrySamplingContext::GetCustomSamplingContext() const
{
return FAppleSentryConverters::StringMapToUnreal(SamplingContext.customSamplingContext);
return FAppleSentryConverters::VariantMapToUnreal(SamplingContext.customSamplingContext);
}

SentrySamplingContext* FAppleSentrySamplingContext::GetNativeObject()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class FAppleSentrySamplingContext : public ISentrySamplingContext
virtual ~FAppleSentrySamplingContext() override;

virtual TSharedPtr<ISentryTransactionContext> GetTransactionContext() const override;
virtual TMap<FString, FString> GetCustomSamplingContext() const override;
virtual TMap<FString, FSentryVariant> GetCustomSamplingContext() const override;

SentrySamplingContext* GetNativeObject();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -387,12 +387,12 @@ TSharedPtr<ISentryTransaction> FAppleSentrySubsystem::StartTransactionWithContex
return StartTransactionWithContext(context);
}

TSharedPtr<ISentryTransaction> FAppleSentrySubsystem::StartTransactionWithContextAndOptions(TSharedPtr<ISentryTransactionContext> context, const TMap<FString, FString>& options)
TSharedPtr<ISentryTransaction> FAppleSentrySubsystem::StartTransactionWithContextAndOptions(TSharedPtr<ISentryTransactionContext> context, const FSentryTransactionOptions& options)
{
TSharedPtr<FAppleSentryTransactionContext> transactionContextIOS = StaticCastSharedPtr<FAppleSentryTransactionContext>(context);

id<SentrySpan> transaction = [SENTRY_APPLE_CLASS(SentrySDK) startTransactionWithContext:transactionContextIOS->GetNativeObject()
customSamplingContext:FAppleSentryConverters::StringMapToNative(options)];
customSamplingContext:FAppleSentryConverters::VariantMapToNative(options.CustomSamplingContext)];

return MakeShareable(new FAppleSentryTransaction(transaction));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class FAppleSentrySubsystem : public ISentrySubsystem
virtual TSharedPtr<ISentryTransaction> StartTransaction(const FString& name, const FString& operation) override;
virtual TSharedPtr<ISentryTransaction> StartTransactionWithContext(TSharedPtr<ISentryTransactionContext> context) override;
virtual TSharedPtr<ISentryTransaction> StartTransactionWithContextAndTimestamp(TSharedPtr<ISentryTransactionContext> context, int64 timestamp) override;
virtual TSharedPtr<ISentryTransaction> StartTransactionWithContextAndOptions(TSharedPtr<ISentryTransactionContext> context, const TMap<FString, FString>& options) override;
virtual TSharedPtr<ISentryTransaction> StartTransactionWithContextAndOptions(TSharedPtr<ISentryTransactionContext> context, const FSentryTransactionOptions& options) override;
virtual TSharedPtr<ISentryTransactionContext> ContinueTrace(const FString& sentryTrace, const TArray<FString>& baggageHeaders) override;

virtual void HandleAssert() override {}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright (c) 2025 Sentry. All Rights Reserved.

#include "GenericPlatformSentrySamplingContext.h"
#include "GenericPlatformSentryTransactionContext.h"

#include "Infrastructure/GenericPlatformSentryConverters.h"

#include "Convenience/GenericPlatformSentryInclude.h"

#if USE_SENTRY_NATIVE

FGenericPlatformSentrySamplingContext::FGenericPlatformSentrySamplingContext(sentry_transaction_context_t* transactionContext, sentry_value_t customSamplingContext)
{
TransactionContext = transactionContext;
CustomSamplingContext = customSamplingContext;
}

FGenericPlatformSentrySamplingContext::~FGenericPlatformSentrySamplingContext()
{
// Put custom destructor logic here if needed
}

TSharedPtr<ISentryTransactionContext> FGenericPlatformSentrySamplingContext::GetTransactionContext() const
{
return MakeShareable(new FGenericPlatformSentryTransactionContext(TransactionContext));
}

TMap<FString, FSentryVariant> FGenericPlatformSentrySamplingContext::GetCustomSamplingContext() const
{
return FGenericPlatformSentryConverters::VariantToUnreal(CustomSamplingContext);
}

#endif
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright (c) 2025 Sentry. All Rights Reserved.

#pragma once

#include "Convenience/GenericPlatformSentryInclude.h"

#include "Interface/SentrySamplingContextInterface.h"

#if USE_SENTRY_NATIVE

class FGenericPlatformSentrySamplingContext : public ISentrySamplingContext
{
public:
FGenericPlatformSentrySamplingContext(sentry_transaction_context_t* transactionContext, sentry_value_t customSamplingContext);
virtual ~FGenericPlatformSentrySamplingContext() override;

virtual TSharedPtr<ISentryTransactionContext> GetTransactionContext() const override;
virtual TMap<FString, FSentryVariant> GetCustomSamplingContext() const override;

private:
sentry_transaction_context_t* TransactionContext;
sentry_value_t CustomSamplingContext;
};

typedef FGenericPlatformSentrySamplingContext FPlatformSentrySamplingContext;

#endif
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include "GenericPlatformSentryEvent.h"
#include "GenericPlatformSentryFeedback.h"
#include "GenericPlatformSentryId.h"
#include "GenericPlatformSentrySamplingContext.h"
#include "GenericPlatformSentryScope.h"
#include "GenericPlatformSentryTransaction.h"
#include "GenericPlatformSentryTransactionContext.h"
Expand All @@ -17,8 +18,11 @@
#include "SentryDefines.h"
#include "SentryEvent.h"
#include "SentryModule.h"
#include "SentrySamplingContext.h"
#include "SentrySettings.h"
#include "SentrySubsystem.h"

#include "Engine/Engine.h"
#include "SentryTraceSampler.h"

#include "Utils/SentryFileUtils.h"
Expand Down Expand Up @@ -96,6 +100,21 @@ void PrintVerboseLog(sentry_level_t level, const char* message, va_list args, vo
}
}

/* static */ double FGenericPlatformSentrySubsystem::HandleTraceSampling(const sentry_transaction_context_t* transaction_ctx, sentry_value_t custom_sampling_ctx, const int* parent_sampled)
{
USentrySubsystem* SentrySubsystem = GEngine->GetEngineSubsystem<USentrySubsystem>();
if (SentrySubsystem)
{
TSharedPtr<FGenericPlatformSentrySubsystem> NativeSubsystem = StaticCastSharedPtr<FGenericPlatformSentrySubsystem>(SentrySubsystem->GetNativeObject());
if (NativeSubsystem)
{
return NativeSubsystem->OnTraceSampling(transaction_ctx, custom_sampling_ctx, parent_sampled);
}
}

return parent_sampled != nullptr ? *parent_sampled : 0.0;
}

sentry_value_t FGenericPlatformSentrySubsystem::OnBeforeSend(sentry_value_t event, void* hint, void* closure, bool isCrash)
{
if (!closure || this != closure)
Expand Down Expand Up @@ -187,6 +206,42 @@ sentry_value_t FGenericPlatformSentrySubsystem::OnCrash(const sentry_ucontext_t*
return OnBeforeSend(event, nullptr, closure, true);
}

double FGenericPlatformSentrySubsystem::OnTraceSampling(const sentry_transaction_context_t* transaction_ctx, sentry_value_t custom_sampling_ctx, const int* parent_sampled)
{
USentryTraceSampler* Sampler = GetTraceSampler();
if (!Sampler)
{
// If custom sampler isn't set skip further processing
return parent_sampled != nullptr ? *parent_sampled : 0.0;
}

if (FUObjectThreadContext::Get().IsRoutingPostLoad)
{
UE_LOG(LogSentrySdk, Log, TEXT("Executing traces sampler is not allowed during object post-loading."));
return parent_sampled != nullptr ? *parent_sampled : 0.0;
}

if (IsGarbageCollecting())
{
// If traces sampling happens during garbage collection we can't instantiate UObjects safely or obtain a GC lock
// since it will cause a deadlock (see https://github.com/getsentry/sentry-unreal/issues/850).
// In this case event will be reported without calling a `beforeSend` handler.
UE_LOG(LogSentrySdk, Log, TEXT("Executing traces sampler is not allowed during garbage collection."));
return parent_sampled != nullptr ? *parent_sampled : 0.0;
}

USentrySamplingContext* Context = USentrySamplingContext::Create(
MakeShareable(new FGenericPlatformSentrySamplingContext(const_cast<sentry_transaction_context_t*>(transaction_ctx), custom_sampling_ctx)));
Copy link

Choose a reason for hiding this comment

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

Bug: const_cast Violates Native SDK Contract

In OnTraceSampling, const_cast is used on the transaction_ctx parameter, which is received as const sentry_transaction_context_t*. This removes the const qualifier, violating the native SDK's contract and potentially leading to undefined behavior if the transaction context is modified or expected to be immutable.

Fix in Cursor Fix in Web

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

FGenericPlatformSentryTransactionContext that holds sentry_transaction_context_t* doesn't expose API allowing to mutate underlying native object.


float samplingValue;
if (Sampler->Sample(Context, samplingValue))
{
return samplingValue;
}

return parent_sampled != nullptr ? *parent_sampled : 0.0;
}

void FGenericPlatformSentrySubsystem::InitCrashReporter(const FString& release, const FString& environment)
{
crashReporter = MakeShareable(new FGenericPlatformSentryCrashReporter);
Expand Down Expand Up @@ -233,6 +288,7 @@ void FGenericPlatformSentrySubsystem::AddByteAttachment(TSharedPtr<ISentryAttach
FGenericPlatformSentrySubsystem::FGenericPlatformSentrySubsystem()
: beforeSend(nullptr)
, beforeBreadcrumb(nullptr)
, sampler(nullptr)
, crashReporter(nullptr)
, isEnabled(false)
, isStackTraceEnabled(true)
Expand All @@ -246,6 +302,7 @@ void FGenericPlatformSentrySubsystem::InitWithSettings(const USentrySettings* se
{
beforeSend = beforeSendHandler;
beforeBreadcrumb = beforeBreadcrumbHandler;
sampler = traceSampler;

sentry_options_t* options = sentry_options_new();

Expand Down Expand Up @@ -290,7 +347,7 @@ void FGenericPlatformSentrySubsystem::InitWithSettings(const USentrySettings* se
}
if (settings->EnableTracing && settings->SamplingType == ESentryTracesSamplingType::TracesSampler)
{
UE_LOG(LogSentrySdk, Warning, TEXT("The Native SDK doesn't currently support sampling functions"));
sentry_options_set_traces_sampler(options, HandleTraceSampling);
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we make HandleTraceSampling non-static and forward "this" here. We could get rid of the static pointer to the class then?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Unlike other Native SDK callbacks sentry_options_set_traces_sampler doesn’t provide a user_data parameter which normally acts as a closure allowing access to the subsystem implementation within the handler.

Copy link
Collaborator

Choose a reason for hiding this comment

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

}

ConfigureHandlerPath(options);
Expand Down Expand Up @@ -672,10 +729,17 @@ TSharedPtr<ISentryTransaction> FGenericPlatformSentrySubsystem::StartTransaction
return nullptr;
}

TSharedPtr<ISentryTransaction> FGenericPlatformSentrySubsystem::StartTransactionWithContextAndOptions(TSharedPtr<ISentryTransactionContext> context, const TMap<FString, FString>& options)
TSharedPtr<ISentryTransaction> FGenericPlatformSentrySubsystem::StartTransactionWithContextAndOptions(TSharedPtr<ISentryTransactionContext> context, const FSentryTransactionOptions& options)
{
UE_LOG(LogSentrySdk, Log, TEXT("Transaction options currently not supported (and therefore ignored) on generic platform."));
return StartTransactionWithContext(context);
if (TSharedPtr<FGenericPlatformSentryTransactionContext> platformTransactionContext = StaticCastSharedPtr<FGenericPlatformSentryTransactionContext>(context))
{
if (sentry_transaction_t* nativeTransaction = sentry_transaction_start(platformTransactionContext->GetNativeObject(), FGenericPlatformSentryConverters::VariantMapToNative(options.CustomSamplingContext)))
{
return MakeShareable(new FGenericPlatformSentryTransaction(nativeTransaction));
}
}

return nullptr;
}

TSharedPtr<ISentryTransactionContext> FGenericPlatformSentrySubsystem::ContinueTrace(const FString& sentryTrace, const TArray<FString>& baggageHeaders)
Expand All @@ -699,6 +763,11 @@ USentryBeforeBreadcrumbHandler* FGenericPlatformSentrySubsystem::GetBeforeBreadc
return beforeBreadcrumb;
}

USentryTraceSampler* FGenericPlatformSentrySubsystem::GetTraceSampler()
{
return sampler;
}

void FGenericPlatformSentrySubsystem::TryCaptureScreenshot()
{
const FString& ScreenshotPath = GetScreenshotPath();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,14 @@ class FGenericPlatformSentrySubsystem : public ISentrySubsystem
virtual TSharedPtr<ISentryTransaction> StartTransaction(const FString& name, const FString& operation) override;
virtual TSharedPtr<ISentryTransaction> StartTransactionWithContext(TSharedPtr<ISentryTransactionContext> context) override;
virtual TSharedPtr<ISentryTransaction> StartTransactionWithContextAndTimestamp(TSharedPtr<ISentryTransactionContext> context, int64 timestamp) override;
virtual TSharedPtr<ISentryTransaction> StartTransactionWithContextAndOptions(TSharedPtr<ISentryTransactionContext> context, const TMap<FString, FString>& options) override;
virtual TSharedPtr<ISentryTransaction> StartTransactionWithContextAndOptions(TSharedPtr<ISentryTransactionContext> context, const FSentryTransactionOptions& options) override;
virtual TSharedPtr<ISentryTransactionContext> ContinueTrace(const FString& sentryTrace, const TArray<FString>& baggageHeaders) override;

virtual void HandleAssert() override {}

USentryBeforeSendHandler* GetBeforeSendHandler();
USentryBeforeBreadcrumbHandler* GetBeforeBreadcrumbHandler();
USentryTraceSampler* GetTraceSampler();

void TryCaptureScreenshot();
void TryCaptureGpuDump();
Expand All @@ -75,6 +76,7 @@ class FGenericPlatformSentrySubsystem : public ISentrySubsystem
virtual sentry_value_t OnBeforeSend(sentry_value_t event, void* hint, void* closure, bool isCrash);
virtual sentry_value_t OnBeforeBreadcrumb(sentry_value_t breadcrumb, void* hint, void* closure);
virtual sentry_value_t OnCrash(const sentry_ucontext_t* uctx, sentry_value_t event, void* closure);
virtual double OnTraceSampling(const sentry_transaction_context_t* transaction_ctx, sentry_value_t custom_sampling_ctx, const int* parent_sampled);

void InitCrashReporter(const FString& release, const FString& environment);

Expand All @@ -90,9 +92,11 @@ class FGenericPlatformSentrySubsystem : public ISentrySubsystem
static sentry_value_t HandleBeforeSend(sentry_value_t event, void* hint, void* closure);
static sentry_value_t HandleBeforeBreadcrumb(sentry_value_t breadcrumb, void* hint, void* closure);
static sentry_value_t HandleOnCrash(const sentry_ucontext_t* uctx, sentry_value_t event, void* closure);
static double HandleTraceSampling(const sentry_transaction_context_t* transaction_ctx, sentry_value_t custom_sampling_ctx, const int* parent_sampled);

USentryBeforeSendHandler* beforeSend;
USentryBeforeBreadcrumbHandler* beforeBreadcrumb;
USentryTraceSampler* sampler;

TSharedPtr<FGenericPlatformSentryCrashReporter> crashReporter;

Expand Down
Loading