diff --git a/CHANGELOG.md b/CHANGELOG.md index a117ea24b..39f53583a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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` 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 diff --git a/plugin-dev/Source/Sentry/Private/Android/AndroidSentrySamplingContext.cpp b/plugin-dev/Source/Sentry/Private/Android/AndroidSentrySamplingContext.cpp index 7a0935522..2f70fe9db 100644 --- a/plugin-dev/Source/Sentry/Private/Android/AndroidSentrySamplingContext.cpp +++ b/plugin-dev/Source/Sentry/Private/Android/AndroidSentrySamplingContext.cpp @@ -24,15 +24,15 @@ TSharedPtr FAndroidSentrySamplingContext::GetTransact return MakeShareable(new FAndroidSentryTransactionContext(*transactionContext)); } -TMap FAndroidSentrySamplingContext::GetCustomSamplingContext() const +TMap FAndroidSentrySamplingContext::GetCustomSamplingContext() const { auto customSamplingContext = CallObjectMethod(GetCustomSamplingContextMethod); if (!customSamplingContext) - return TMap(); + return TMap(); FSentryJavaObjectWrapper NativeCustomSamplingContext(SentryJavaClasses::CustomSamplingContext, *customSamplingContext); FSentryJavaMethod GetDataMethod = NativeCustomSamplingContext.GetMethod("getData", "()Ljava/util/Map;"); auto data = NativeCustomSamplingContext.CallObjectMethod(GetDataMethod); - return FAndroidSentryConverters::StringMapToUnreal(*data); + return FAndroidSentryConverters::VariantMapToUnreal(*data); } diff --git a/plugin-dev/Source/Sentry/Private/Android/AndroidSentrySamplingContext.h b/plugin-dev/Source/Sentry/Private/Android/AndroidSentrySamplingContext.h index 74146e21f..cf586157f 100644 --- a/plugin-dev/Source/Sentry/Private/Android/AndroidSentrySamplingContext.h +++ b/plugin-dev/Source/Sentry/Private/Android/AndroidSentrySamplingContext.h @@ -14,7 +14,7 @@ class FAndroidSentrySamplingContext : public ISentrySamplingContext, public FSen void SetupClassMethods(); virtual TSharedPtr GetTransactionContext() const override; - virtual TMap GetCustomSamplingContext() const override; + virtual TMap GetCustomSamplingContext() const override; private: FSentryJavaMethod GetTransactionContextMethod; diff --git a/plugin-dev/Source/Sentry/Private/Android/AndroidSentrySubsystem.cpp b/plugin-dev/Source/Sentry/Private/Android/AndroidSentrySubsystem.cpp index 24c758188..9f8a6b032 100644 --- a/plugin-dev/Source/Sentry/Private/Android/AndroidSentrySubsystem.cpp +++ b/plugin-dev/Source/Sentry/Private/Android/AndroidSentrySubsystem.cpp @@ -297,12 +297,12 @@ TSharedPtr FAndroidSentrySubsystem::StartTransactionWithCont return StartTransactionWithContext(context); } -TSharedPtr FAndroidSentrySubsystem::StartTransactionWithContextAndOptions(TSharedPtr context, const TMap& options) +TSharedPtr FAndroidSentrySubsystem::StartTransactionWithContextAndOptions(TSharedPtr context, const FSentryTransactionOptions& options) { TSharedPtr transactionContextAndroid = StaticCastSharedPtr(context); TSharedPtr transactionOptionsAndroid = MakeShareable(new FAndroidSentryTransactionOptions()); - transactionOptionsAndroid->SetCustomSamplingContext(options); + transactionOptionsAndroid->SetCustomSamplingContext(options.CustomSamplingContext); auto transaction = FSentryJavaObjectWrapper::CallStaticObjectMethod(SentryJavaClasses::Sentry, "startTransaction", "(Lio/sentry/TransactionContext;Lio/sentry/TransactionOptions;)Lio/sentry/ITransaction;", transactionContextAndroid->GetJObject(), transactionOptionsAndroid->GetJObject()); diff --git a/plugin-dev/Source/Sentry/Private/Android/AndroidSentrySubsystem.h b/plugin-dev/Source/Sentry/Private/Android/AndroidSentrySubsystem.h index b985906c5..651d2bc8f 100644 --- a/plugin-dev/Source/Sentry/Private/Android/AndroidSentrySubsystem.h +++ b/plugin-dev/Source/Sentry/Private/Android/AndroidSentrySubsystem.h @@ -37,7 +37,7 @@ class FAndroidSentrySubsystem : public ISentrySubsystem virtual TSharedPtr StartTransaction(const FString& name, const FString& operation) override; virtual TSharedPtr StartTransactionWithContext(TSharedPtr context) override; virtual TSharedPtr StartTransactionWithContextAndTimestamp(TSharedPtr context, int64 timestamp) override; - virtual TSharedPtr StartTransactionWithContextAndOptions(TSharedPtr context, const TMap& options) override; + virtual TSharedPtr StartTransactionWithContextAndOptions(TSharedPtr context, const FSentryTransactionOptions& options) override; virtual TSharedPtr ContinueTrace(const FString& sentryTrace, const TArray& baggageHeaders) override; virtual void HandleAssert() override; diff --git a/plugin-dev/Source/Sentry/Private/Android/AndroidSentryTransactionOptions.cpp b/plugin-dev/Source/Sentry/Private/Android/AndroidSentryTransactionOptions.cpp index 9d26eabbc..47a2d8c9a 100644 --- a/plugin-dev/Source/Sentry/Private/Android/AndroidSentryTransactionOptions.cpp +++ b/plugin-dev/Source/Sentry/Private/Android/AndroidSentryTransactionOptions.cpp @@ -16,14 +16,14 @@ void FAndroidSentryTransactionOptions::SetupClassMethods() SetCustomSamplingContextMethod = GetMethod("setCustomSamplingContext", "(Lio/sentry/CustomSamplingContext;)V"); } -void FAndroidSentryTransactionOptions::SetCustomSamplingContext(const TMap& data) +void FAndroidSentryTransactionOptions::SetCustomSamplingContext(const TMap& 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(SetMethod, *GetJString(dataItem.Key), *GetJString(dataItem.Value)); + NativeCustomSamplingContext.CallMethod(SetMethod, *GetJString(dataItem.Key), FAndroidSentryConverters::VariantToNative(dataItem.Value)->GetJObject()); } CallMethod(SetCustomSamplingContextMethod, NativeCustomSamplingContext.GetJObject()); diff --git a/plugin-dev/Source/Sentry/Private/Android/AndroidSentryTransactionOptions.h b/plugin-dev/Source/Sentry/Private/Android/AndroidSentryTransactionOptions.h index 8523211f5..0a15ccc68 100644 --- a/plugin-dev/Source/Sentry/Private/Android/AndroidSentryTransactionOptions.h +++ b/plugin-dev/Source/Sentry/Private/Android/AndroidSentryTransactionOptions.h @@ -2,6 +2,8 @@ #pragma once +#include "SentryVariant.h" + #include "Infrastructure/AndroidSentryJavaObjectWrapper.h" class FAndroidSentryTransactionOptions : public FSentryJavaObjectWrapper @@ -11,7 +13,7 @@ class FAndroidSentryTransactionOptions : public FSentryJavaObjectWrapper void SetupClassMethods(); - void SetCustomSamplingContext(const TMap& data); + void SetCustomSamplingContext(const TMap& data); private: FSentryJavaMethod SetCustomSamplingContextMethod; diff --git a/plugin-dev/Source/Sentry/Private/Apple/AppleSentrySamplingContext.cpp b/plugin-dev/Source/Sentry/Private/Apple/AppleSentrySamplingContext.cpp index daa9c75fc..fa88b0b5e 100644 --- a/plugin-dev/Source/Sentry/Private/Apple/AppleSentrySamplingContext.cpp +++ b/plugin-dev/Source/Sentry/Private/Apple/AppleSentrySamplingContext.cpp @@ -22,9 +22,9 @@ TSharedPtr FAppleSentrySamplingContext::GetTransactio return MakeShareable(new FAppleSentryTransactionContext(SamplingContext.transactionContext)); } -TMap FAppleSentrySamplingContext::GetCustomSamplingContext() const +TMap FAppleSentrySamplingContext::GetCustomSamplingContext() const { - return FAppleSentryConverters::StringMapToUnreal(SamplingContext.customSamplingContext); + return FAppleSentryConverters::VariantMapToUnreal(SamplingContext.customSamplingContext); } SentrySamplingContext* FAppleSentrySamplingContext::GetNativeObject() diff --git a/plugin-dev/Source/Sentry/Private/Apple/AppleSentrySamplingContext.h b/plugin-dev/Source/Sentry/Private/Apple/AppleSentrySamplingContext.h index 0ab103ff2..65f9803cb 100644 --- a/plugin-dev/Source/Sentry/Private/Apple/AppleSentrySamplingContext.h +++ b/plugin-dev/Source/Sentry/Private/Apple/AppleSentrySamplingContext.h @@ -13,7 +13,7 @@ class FAppleSentrySamplingContext : public ISentrySamplingContext virtual ~FAppleSentrySamplingContext() override; virtual TSharedPtr GetTransactionContext() const override; - virtual TMap GetCustomSamplingContext() const override; + virtual TMap GetCustomSamplingContext() const override; SentrySamplingContext* GetNativeObject(); diff --git a/plugin-dev/Source/Sentry/Private/Apple/AppleSentrySubsystem.cpp b/plugin-dev/Source/Sentry/Private/Apple/AppleSentrySubsystem.cpp index 6ea15a657..fabef73ca 100644 --- a/plugin-dev/Source/Sentry/Private/Apple/AppleSentrySubsystem.cpp +++ b/plugin-dev/Source/Sentry/Private/Apple/AppleSentrySubsystem.cpp @@ -387,12 +387,12 @@ TSharedPtr FAppleSentrySubsystem::StartTransactionWithContex return StartTransactionWithContext(context); } -TSharedPtr FAppleSentrySubsystem::StartTransactionWithContextAndOptions(TSharedPtr context, const TMap& options) +TSharedPtr FAppleSentrySubsystem::StartTransactionWithContextAndOptions(TSharedPtr context, const FSentryTransactionOptions& options) { TSharedPtr transactionContextIOS = StaticCastSharedPtr(context); id transaction = [SENTRY_APPLE_CLASS(SentrySDK) startTransactionWithContext:transactionContextIOS->GetNativeObject() - customSamplingContext:FAppleSentryConverters::StringMapToNative(options)]; + customSamplingContext:FAppleSentryConverters::VariantMapToNative(options.CustomSamplingContext)]; return MakeShareable(new FAppleSentryTransaction(transaction)); } diff --git a/plugin-dev/Source/Sentry/Private/Apple/AppleSentrySubsystem.h b/plugin-dev/Source/Sentry/Private/Apple/AppleSentrySubsystem.h index d26233c44..72861cd5d 100644 --- a/plugin-dev/Source/Sentry/Private/Apple/AppleSentrySubsystem.h +++ b/plugin-dev/Source/Sentry/Private/Apple/AppleSentrySubsystem.h @@ -37,7 +37,7 @@ class FAppleSentrySubsystem : public ISentrySubsystem virtual TSharedPtr StartTransaction(const FString& name, const FString& operation) override; virtual TSharedPtr StartTransactionWithContext(TSharedPtr context) override; virtual TSharedPtr StartTransactionWithContextAndTimestamp(TSharedPtr context, int64 timestamp) override; - virtual TSharedPtr StartTransactionWithContextAndOptions(TSharedPtr context, const TMap& options) override; + virtual TSharedPtr StartTransactionWithContextAndOptions(TSharedPtr context, const FSentryTransactionOptions& options) override; virtual TSharedPtr ContinueTrace(const FString& sentryTrace, const TArray& baggageHeaders) override; virtual void HandleAssert() override {} diff --git a/plugin-dev/Source/Sentry/Private/GenericPlatform/GenericPlatformSentrySamplingContext.cpp b/plugin-dev/Source/Sentry/Private/GenericPlatform/GenericPlatformSentrySamplingContext.cpp new file mode 100644 index 000000000..d4703f4c5 --- /dev/null +++ b/plugin-dev/Source/Sentry/Private/GenericPlatform/GenericPlatformSentrySamplingContext.cpp @@ -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 FGenericPlatformSentrySamplingContext::GetTransactionContext() const +{ + return MakeShareable(new FGenericPlatformSentryTransactionContext(TransactionContext)); +} + +TMap FGenericPlatformSentrySamplingContext::GetCustomSamplingContext() const +{ + return FGenericPlatformSentryConverters::VariantToUnreal(CustomSamplingContext); +} + +#endif diff --git a/plugin-dev/Source/Sentry/Private/GenericPlatform/GenericPlatformSentrySamplingContext.h b/plugin-dev/Source/Sentry/Private/GenericPlatform/GenericPlatformSentrySamplingContext.h new file mode 100644 index 000000000..b3ea66086 --- /dev/null +++ b/plugin-dev/Source/Sentry/Private/GenericPlatform/GenericPlatformSentrySamplingContext.h @@ -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 GetTransactionContext() const override; + virtual TMap GetCustomSamplingContext() const override; + +private: + sentry_transaction_context_t* TransactionContext; + sentry_value_t CustomSamplingContext; +}; + +typedef FGenericPlatformSentrySamplingContext FPlatformSentrySamplingContext; + +#endif diff --git a/plugin-dev/Source/Sentry/Private/GenericPlatform/GenericPlatformSentrySubsystem.cpp b/plugin-dev/Source/Sentry/Private/GenericPlatform/GenericPlatformSentrySubsystem.cpp index 3821b723a..030480830 100644 --- a/plugin-dev/Source/Sentry/Private/GenericPlatform/GenericPlatformSentrySubsystem.cpp +++ b/plugin-dev/Source/Sentry/Private/GenericPlatform/GenericPlatformSentrySubsystem.cpp @@ -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" @@ -17,8 +18,9 @@ #include "SentryDefines.h" #include "SentryEvent.h" #include "SentryModule.h" +#include "SentrySamplingContext.h" #include "SentrySettings.h" - +#include "SentrySubsystem.h" #include "SentryTraceSampler.h" #include "Utils/SentryFileUtils.h" @@ -30,6 +32,7 @@ #include "GenericPlatform/CrashReporter/GenericPlatformSentryCrashContext.h" #include "GenericPlatform/CrashReporter/GenericPlatformSentryCrashReporter.h" +#include "Engine/Engine.h" #include "GenericPlatform/GenericPlatformOutputDevices.h" #include "HAL/ExceptionHandling.h" #include "HAL/FileManager.h" @@ -96,6 +99,24 @@ 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) +{ + if (GEngine) + { + USentrySubsystem* SentrySubsystem = GEngine->GetEngineSubsystem(); + if (SentrySubsystem && SentrySubsystem->IsEnabled()) + { + TSharedPtr NativeSubsystem = StaticCastSharedPtr(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) @@ -187,6 +208,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(transaction_ctx), custom_sampling_ctx))); + + 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); @@ -233,6 +290,7 @@ void FGenericPlatformSentrySubsystem::AddByteAttachment(TSharedPtrEnableTracing && 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); } ConfigureHandlerPath(options); @@ -672,10 +731,17 @@ TSharedPtr FGenericPlatformSentrySubsystem::StartTransaction return nullptr; } -TSharedPtr FGenericPlatformSentrySubsystem::StartTransactionWithContextAndOptions(TSharedPtr context, const TMap& options) +TSharedPtr FGenericPlatformSentrySubsystem::StartTransactionWithContextAndOptions(TSharedPtr 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 platformTransactionContext = StaticCastSharedPtr(context)) + { + if (sentry_transaction_t* nativeTransaction = sentry_transaction_start(platformTransactionContext->GetNativeObject(), FGenericPlatformSentryConverters::VariantMapToNative(options.CustomSamplingContext))) + { + return MakeShareable(new FGenericPlatformSentryTransaction(nativeTransaction)); + } + } + + return nullptr; } TSharedPtr FGenericPlatformSentrySubsystem::ContinueTrace(const FString& sentryTrace, const TArray& baggageHeaders) @@ -699,6 +765,11 @@ USentryBeforeBreadcrumbHandler* FGenericPlatformSentrySubsystem::GetBeforeBreadc return beforeBreadcrumb; } +USentryTraceSampler* FGenericPlatformSentrySubsystem::GetTraceSampler() +{ + return sampler; +} + void FGenericPlatformSentrySubsystem::TryCaptureScreenshot() { const FString& ScreenshotPath = GetScreenshotPath(); diff --git a/plugin-dev/Source/Sentry/Private/GenericPlatform/GenericPlatformSentrySubsystem.h b/plugin-dev/Source/Sentry/Private/GenericPlatform/GenericPlatformSentrySubsystem.h index c395a01e5..d20edd302 100644 --- a/plugin-dev/Source/Sentry/Private/GenericPlatform/GenericPlatformSentrySubsystem.h +++ b/plugin-dev/Source/Sentry/Private/GenericPlatform/GenericPlatformSentrySubsystem.h @@ -49,13 +49,14 @@ class FGenericPlatformSentrySubsystem : public ISentrySubsystem virtual TSharedPtr StartTransaction(const FString& name, const FString& operation) override; virtual TSharedPtr StartTransactionWithContext(TSharedPtr context) override; virtual TSharedPtr StartTransactionWithContextAndTimestamp(TSharedPtr context, int64 timestamp) override; - virtual TSharedPtr StartTransactionWithContextAndOptions(TSharedPtr context, const TMap& options) override; + virtual TSharedPtr StartTransactionWithContextAndOptions(TSharedPtr context, const FSentryTransactionOptions& options) override; virtual TSharedPtr ContinueTrace(const FString& sentryTrace, const TArray& baggageHeaders) override; virtual void HandleAssert() override {} USentryBeforeSendHandler* GetBeforeSendHandler(); USentryBeforeBreadcrumbHandler* GetBeforeBreadcrumbHandler(); + USentryTraceSampler* GetTraceSampler(); void TryCaptureScreenshot(); void TryCaptureGpuDump(); @@ -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); @@ -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 crashReporter; diff --git a/plugin-dev/Source/Sentry/Private/GenericPlatform/GenericPlatformSentryTransactionContext.cpp b/plugin-dev/Source/Sentry/Private/GenericPlatform/GenericPlatformSentryTransactionContext.cpp index c38cf7c84..15ebe027e 100644 --- a/plugin-dev/Source/Sentry/Private/GenericPlatform/GenericPlatformSentryTransactionContext.cpp +++ b/plugin-dev/Source/Sentry/Private/GenericPlatform/GenericPlatformSentryTransactionContext.cpp @@ -19,16 +19,12 @@ FGenericPlatformSentryTransactionContext::FGenericPlatformSentryTransactionConte FString FGenericPlatformSentryTransactionContext::GetName() const { - // no corresponding implementation in sentry-native - UE_LOG(LogSentrySdk, Warning, TEXT("The native SDK doesn't currently support transaction's context GetName function")); - return FString(); + return FString(sentry_transaction_context_get_name(TransactionContext)); } FString FGenericPlatformSentryTransactionContext::GetOperation() const { - // no corresponding implementation in sentry-native - UE_LOG(LogSentrySdk, Warning, TEXT("The native SDK doesn't currently support transaction's context GetOperation function")); - return FString(); + return FString(sentry_transaction_context_get_operation(TransactionContext)); } sentry_transaction_context_t* FGenericPlatformSentryTransactionContext::GetNativeObject() diff --git a/plugin-dev/Source/Sentry/Private/HAL/PlatformSentrySamplingContext.h b/plugin-dev/Source/Sentry/Private/HAL/PlatformSentrySamplingContext.h index 9ef13fea4..28c59a963 100644 --- a/plugin-dev/Source/Sentry/Private/HAL/PlatformSentrySamplingContext.h +++ b/plugin-dev/Source/Sentry/Private/HAL/PlatformSentrySamplingContext.h @@ -6,6 +6,8 @@ #include "Android/AndroidSentrySamplingContext.h" #elif PLATFORM_APPLE #include "Apple/AppleSentrySamplingContext.h" +#elif USE_SENTRY_NATIVE +#include "GenericPlatform/GenericPlatformSentrySamplingContext.h" #else #include "Null/NullSentrySamplingContext.h" #endif diff --git a/plugin-dev/Source/Sentry/Private/Interface/SentrySamplingContextInterface.h b/plugin-dev/Source/Sentry/Private/Interface/SentrySamplingContextInterface.h index e454be0e9..9ee535b35 100644 --- a/plugin-dev/Source/Sentry/Private/Interface/SentrySamplingContextInterface.h +++ b/plugin-dev/Source/Sentry/Private/Interface/SentrySamplingContextInterface.h @@ -4,6 +4,8 @@ #include "CoreMinimal.h" +#include "SentryVariant.h" + class ISentryTransactionContext; class ISentrySamplingContext @@ -12,5 +14,5 @@ class ISentrySamplingContext virtual ~ISentrySamplingContext() = default; virtual TSharedPtr GetTransactionContext() const = 0; - virtual TMap GetCustomSamplingContext() const = 0; + virtual TMap GetCustomSamplingContext() const = 0; }; \ No newline at end of file diff --git a/plugin-dev/Source/Sentry/Private/Interface/SentrySubsystemInterface.h b/plugin-dev/Source/Sentry/Private/Interface/SentrySubsystemInterface.h index be72668bd..631b86c61 100644 --- a/plugin-dev/Source/Sentry/Private/Interface/SentrySubsystemInterface.h +++ b/plugin-dev/Source/Sentry/Private/Interface/SentrySubsystemInterface.h @@ -5,6 +5,7 @@ #include "CoreMinimal.h" #include "SentryDataTypes.h" +#include "SentryTransactionOptions.h" #include "SentryVariant.h" class ISentryAttachment; @@ -60,7 +61,7 @@ class ISentrySubsystem virtual TSharedPtr StartTransaction(const FString& name, const FString& operation) = 0; virtual TSharedPtr StartTransactionWithContext(TSharedPtr context) = 0; virtual TSharedPtr StartTransactionWithContextAndTimestamp(TSharedPtr context, int64 timestamp) = 0; - virtual TSharedPtr StartTransactionWithContextAndOptions(TSharedPtr context, const TMap& options) = 0; + virtual TSharedPtr StartTransactionWithContextAndOptions(TSharedPtr context, const FSentryTransactionOptions& options) = 0; virtual TSharedPtr ContinueTrace(const FString& sentryTrace, const TArray& baggageHeaders) = 0; /** Unreal-specific methods that are not part of the platform's Sentry SDK API */ diff --git a/plugin-dev/Source/Sentry/Private/Null/NullSentrySamplingContext.h b/plugin-dev/Source/Sentry/Private/Null/NullSentrySamplingContext.h index 390e1f8ed..7a92f6c79 100644 --- a/plugin-dev/Source/Sentry/Private/Null/NullSentrySamplingContext.h +++ b/plugin-dev/Source/Sentry/Private/Null/NullSentrySamplingContext.h @@ -10,7 +10,7 @@ class FNullSentrySamplingContext final : public ISentrySamplingContext virtual ~FNullSentrySamplingContext() override = default; virtual TSharedPtr GetTransactionContext() const override { return nullptr; } - virtual TMap GetCustomSamplingContext() const override { return {}; } + virtual TMap GetCustomSamplingContext() const override { return {}; } }; typedef FNullSentrySamplingContext FPlatformSentrySamplingContext; diff --git a/plugin-dev/Source/Sentry/Private/Null/NullSentrySubsystem.h b/plugin-dev/Source/Sentry/Private/Null/NullSentrySubsystem.h index 23c28fc47..d9f827283 100644 --- a/plugin-dev/Source/Sentry/Private/Null/NullSentrySubsystem.h +++ b/plugin-dev/Source/Sentry/Private/Null/NullSentrySubsystem.h @@ -39,7 +39,7 @@ class FNullSentrySubsystem : public ISentrySubsystem virtual TSharedPtr StartTransaction(const FString& name, const FString& operation) override { return nullptr; } virtual TSharedPtr StartTransactionWithContext(TSharedPtr context) override { return nullptr; } virtual TSharedPtr StartTransactionWithContextAndTimestamp(TSharedPtr context, int64 timestamp) override { return nullptr; } - virtual TSharedPtr StartTransactionWithContextAndOptions(TSharedPtr context, const TMap& options) override { return nullptr; } + virtual TSharedPtr StartTransactionWithContextAndOptions(TSharedPtr context, const FSentryTransactionOptions& options) override { return nullptr; } virtual TSharedPtr ContinueTrace(const FString& sentryTrace, const TArray& baggageHeaders) override { return nullptr; } virtual void HandleAssert() override {} diff --git a/plugin-dev/Source/Sentry/Private/SentrySamplingContext.cpp b/plugin-dev/Source/Sentry/Private/SentrySamplingContext.cpp index d83cafcc8..b452a60c2 100644 --- a/plugin-dev/Source/Sentry/Private/SentrySamplingContext.cpp +++ b/plugin-dev/Source/Sentry/Private/SentrySamplingContext.cpp @@ -15,10 +15,10 @@ USentryTransactionContext* USentrySamplingContext::GetTransactionContext() const return USentryTransactionContext::Create(transactionContextNativeImpl); } -TMap USentrySamplingContext::GetCustomSamplingContext() const +TMap USentrySamplingContext::GetCustomSamplingContext() const { if (!NativeImpl) - return TMap(); + return TMap(); return NativeImpl->GetCustomSamplingContext(); } diff --git a/plugin-dev/Source/Sentry/Private/SentrySubsystem.cpp b/plugin-dev/Source/Sentry/Private/SentrySubsystem.cpp index 582988dbd..78fdbdd1c 100644 --- a/plugin-dev/Source/Sentry/Private/SentrySubsystem.cpp +++ b/plugin-dev/Source/Sentry/Private/SentrySubsystem.cpp @@ -553,7 +553,7 @@ USentryTransaction* USentrySubsystem::StartTransactionWithContextAndTimestamp(US return USentryTransaction::Create(SentryTransaction); } -USentryTransaction* USentrySubsystem::StartTransactionWithContextAndOptions(USentryTransactionContext* Context, const TMap& Options) +USentryTransaction* USentrySubsystem::StartTransactionWithContextAndOptions(USentryTransactionContext* Context, const FSentryTransactionOptions& Options) { check(SubsystemNativeImpl); check(Context); @@ -599,6 +599,11 @@ bool USentrySubsystem::IsSupportedForCurrentSettings() const return true; } +TSharedPtr USentrySubsystem::GetNativeObject() const +{ + return SubsystemNativeImpl; +} + void USentrySubsystem::AddDefaultContext() { check(SubsystemNativeImpl); diff --git a/plugin-dev/Source/Sentry/Private/Tests/SentryTraceSampling.spec.cpp b/plugin-dev/Source/Sentry/Private/Tests/SentryTraceSampling.spec.cpp new file mode 100644 index 000000000..4c6adcf18 --- /dev/null +++ b/plugin-dev/Source/Sentry/Private/Tests/SentryTraceSampling.spec.cpp @@ -0,0 +1,149 @@ +// Copyright (c) 2025 Sentry. All Rights Reserved. + +#include "SentrySettings.h" +#include "SentrySubsystem.h" +#include "SentryTests.h" +#include "SentryTraceSamplingHandler.h" +#include "SentryTransaction.h" +#include "SentryTransactionContext.h" + +#include "HAL/PlatformSentryTransactionContext.h" + +#include "Engine/Engine.h" +#include "Misc/AutomationTest.h" + +TDelegate UTraceSamplingTestHandler::OnTraceSamplingTestHandler; + +#if WITH_AUTOMATION_TESTS + +BEGIN_DEFINE_SPEC(SentryTraceSamplingSpec, "Sentry.SentryTraceSampling", EAutomationTestFlags::ProductFilter | SentryApplicationContextMask) + USentrySubsystem* SentrySubsystem; + USentryTransactionContext* TransactionContext; + FSentryTransactionOptions TransactionOptions; +END_DEFINE_SPEC(SentryTraceSamplingSpec) + +void SentryTraceSamplingSpec::Define() +{ + BeforeEach([this]() + { + SentrySubsystem = GEngine->GetEngineSubsystem(); + + SentrySubsystem->InitializeWithSettings(FConfigureSettingsNativeDelegate::CreateLambda([=](USentrySettings* Settings) + { + Settings->SamplingType = ESentryTracesSamplingType::TracesSampler; + Settings->TracesSampler = UTraceSamplingTestHandler::StaticClass(); + })); + + TransactionContext = USentryTransactionContext::Create( + MakeShareable(new FPlatformSentryTransactionContext(TEXT("Test transaction"), TEXT("Test operation")))); + + TransactionOptions = FSentryTransactionOptions(); + }); + + Describe("Trace sampler", [this]() + { + It("should be called and provide sampling context", [this]() + { + bool CallbackExecuted = false; + USentrySamplingContext* ReceivedContext = nullptr; + + UTraceSamplingTestHandler::OnTraceSamplingTestHandler.BindLambda([&](USentrySamplingContext* SamplingContext) + { + CallbackExecuted = true; + ReceivedContext = SamplingContext; + }); + + USentryTransaction* Transaction = SentrySubsystem->StartTransactionWithContextAndOptions(TransactionContext, TransactionOptions); + + TestTrue("Sampling callback should be executed", CallbackExecuted); + TestNotNull("Sampling context should not be null", ReceivedContext); + + if (Transaction) + { + Transaction->Finish(); + } + + UTraceSamplingTestHandler::OnTraceSamplingTestHandler.Unbind(); + + SentrySubsystem->Close(); + }); + + It("should provide transaction context in sampling context", [this]() + { + USentryTransactionContext* CapturedTransactionContext = nullptr; + + UTraceSamplingTestHandler::OnTraceSamplingTestHandler.BindLambda([&](USentrySamplingContext* SamplingContext) + { + if (SamplingContext) + { + CapturedTransactionContext = SamplingContext->GetTransactionContext(); + } + }); + + USentryTransaction* Transaction = SentrySubsystem->StartTransactionWithContextAndOptions(TransactionContext, TransactionOptions); + + TestNotNull("Transaction context should be available in sampling context", CapturedTransactionContext); + if (CapturedTransactionContext) + { + TestEqual("Transaction name should match", CapturedTransactionContext->GetName(), TEXT("Test transaction")); + TestEqual("Transaction operation should match", CapturedTransactionContext->GetOperation(), TEXT("Test operation")); + } + + if (Transaction) + { + Transaction->Finish(); + } + + UTraceSamplingTestHandler::OnTraceSamplingTestHandler.Unbind(); + + SentrySubsystem->Close(); + }); + + It("should provide custom sampling context data in sampling context", [this]() + { + TMap CapturedCustomData; + + UTraceSamplingTestHandler::OnTraceSamplingTestHandler.BindLambda([&](USentrySamplingContext* SamplingContext) + { + if (SamplingContext) + { + CapturedCustomData = SamplingContext->GetCustomSamplingContext(); + } + }); + + TransactionOptions.CustomSamplingContext.Add(TEXT("test_key"), FSentryVariant(TEXT("test_value"))); + TransactionOptions.CustomSamplingContext.Add(TEXT("numeric_key"), FSentryVariant(42)); + + USentryTransaction* Transaction = SentrySubsystem->StartTransactionWithContextAndOptions(TransactionContext, TransactionOptions); + + TestTrue("Custom sampling context should contain test_key", CapturedCustomData.Contains(TEXT("test_key"))); + TestTrue("Custom sampling context should contain numeric_key", CapturedCustomData.Contains(TEXT("numeric_key"))); + + if (CapturedCustomData.Contains(TEXT("test_key"))) + { + TestEqual("test_key should have correct value", CapturedCustomData[TEXT("test_key")].GetValue(), TEXT("test_value")); + } + + if (CapturedCustomData.Contains(TEXT("numeric_key"))) + { + TestEqual("numeric_key should have correct value", CapturedCustomData[TEXT("numeric_key")].GetValue(), 42); + } + + if (Transaction) + { + Transaction->Finish(); + } + + UTraceSamplingTestHandler::OnTraceSamplingTestHandler.Unbind(); + + SentrySubsystem->Close(); + }); + }); + + AfterEach([this] + { + SentrySubsystem->Close(); + }); +} + +#endif \ No newline at end of file diff --git a/plugin-dev/Source/Sentry/Private/Tests/SentryTraceSamplingHandler.h b/plugin-dev/Source/Sentry/Private/Tests/SentryTraceSamplingHandler.h new file mode 100644 index 000000000..69de6d877 --- /dev/null +++ b/plugin-dev/Source/Sentry/Private/Tests/SentryTraceSamplingHandler.h @@ -0,0 +1,22 @@ +// Copyright (c) 2025 Sentry. All Rights Reserved. + +#pragma once + +#include "SentrySamplingContext.h" +#include "SentryTraceSampler.h" + +#include "SentryTraceSamplingHandler.generated.h" + +UCLASS() +class UTraceSamplingTestHandler : public USentryTraceSampler +{ + GENERATED_BODY() +public: + virtual bool Sample_Implementation(USentrySamplingContext* samplingContext, float& samplingValue) override + { + OnTraceSamplingTestHandler.ExecuteIfBound(samplingContext); + return Super::Sample_Implementation(samplingContext, samplingValue); + } + + static TDelegate OnTraceSamplingTestHandler; +}; diff --git a/plugin-dev/Source/Sentry/Public/SentrySamplingContext.h b/plugin-dev/Source/Sentry/Public/SentrySamplingContext.h index 44e1fb670..53ca4406a 100644 --- a/plugin-dev/Source/Sentry/Public/SentrySamplingContext.h +++ b/plugin-dev/Source/Sentry/Public/SentrySamplingContext.h @@ -5,6 +5,7 @@ #include "CoreMinimal.h" #include "SentryImplWrapper.h" +#include "SentryVariant.h" #include "SentrySamplingContext.generated.h" @@ -29,5 +30,5 @@ class SENTRY_API USentrySamplingContext : public UObject, public TSentryImplWrap /** Gets custom data used for sampling. */ UFUNCTION(BlueprintPure, Category = "Sentry") - TMap GetCustomSamplingContext() const; + TMap GetCustomSamplingContext() const; }; diff --git a/plugin-dev/Source/Sentry/Public/SentrySettings.h b/plugin-dev/Source/Sentry/Public/SentrySettings.h index cbb98e835..b2318d11e 100644 --- a/plugin-dev/Source/Sentry/Public/SentrySettings.h +++ b/plugin-dev/Source/Sentry/Public/SentrySettings.h @@ -311,7 +311,7 @@ class SENTRY_API USentrySettings : public UObject float TracesSampleRate; UPROPERTY(Config, EditAnywhere, Category = "General|Performance Monitoring", - Meta = (DisplayName = "Traces sampler (for Android/Apple only)", ToolTip = "Custom handler for determining traces sample rate based on the sampling context.", + Meta = (DisplayName = "Traces sampler", ToolTip = "Custom handler for determining traces sample rate based on the sampling context.", EditCondition = "EnableTracing && SamplingType == ESentryTracesSamplingType::TracesSampler", EditConditionHides)) TSubclassOf TracesSampler; diff --git a/plugin-dev/Source/Sentry/Public/SentrySubsystem.h b/plugin-dev/Source/Sentry/Public/SentrySubsystem.h index b731ef6a2..6ee65f6aa 100644 --- a/plugin-dev/Source/Sentry/Public/SentrySubsystem.h +++ b/plugin-dev/Source/Sentry/Public/SentrySubsystem.h @@ -7,6 +7,7 @@ #include "SentryDataTypes.h" #include "SentryScope.h" +#include "SentryTransactionOptions.h" #include "SentryVariant.h" #include "SentrySubsystem.generated.h" @@ -307,7 +308,7 @@ class SENTRY_API USentrySubsystem : public UEngineSubsystem * @param Options Transaction options. */ UFUNCTION(BlueprintCallable, Category = "Sentry") - USentryTransaction* StartTransactionWithContextAndOptions(USentryTransactionContext* Context, const TMap& Options); + USentryTransaction* StartTransactionWithContextAndOptions(USentryTransactionContext* Context, const FSentryTransactionOptions& Options); /** * Creates a transaction context to propagate distributed tracing metadata from upstream @@ -323,6 +324,9 @@ class SENTRY_API USentrySubsystem : public UEngineSubsystem UFUNCTION(BlueprintCallable, Category = "Sentry") bool IsSupportedForCurrentSettings() const; + /** Retrieves the underlying native implementation. */ + TSharedPtr GetNativeObject() const; + private: /** Adds default context data for all events captured by Sentry SDK. */ void AddDefaultContext(); diff --git a/plugin-dev/Source/Sentry/Public/SentryTransactionOptions.h b/plugin-dev/Source/Sentry/Public/SentryTransactionOptions.h new file mode 100644 index 000000000..cec674577 --- /dev/null +++ b/plugin-dev/Source/Sentry/Public/SentryTransactionOptions.h @@ -0,0 +1,21 @@ +// Copyright (c) 2025 Sentry. All Rights Reserved. + +#pragma once + +#include "SentryVariant.h" + +#include "SentryTransactionOptions.generated.h" + +/** + * Additional data used to create transactions. + */ +USTRUCT(BlueprintType) +struct SENTRY_API FSentryTransactionOptions +{ + GENERATED_BODY() + + UPROPERTY(BlueprintReadWrite, Category = "Sentry") + TMap CustomSamplingContext; + + // TODO: Add other options such as `BindToScope` flag +}; \ No newline at end of file diff --git a/sample/Content/Misc/BP_TraceSampler.uasset b/sample/Content/Misc/BP_TraceSampler.uasset index 9a0878f24..82c9a4929 100644 Binary files a/sample/Content/Misc/BP_TraceSampler.uasset and b/sample/Content/Misc/BP_TraceSampler.uasset differ diff --git a/sample/Content/UI/W_SentryDemo.uasset b/sample/Content/UI/W_SentryDemo.uasset index 9ebff3763..6dc34ee33 100644 Binary files a/sample/Content/UI/W_SentryDemo.uasset and b/sample/Content/UI/W_SentryDemo.uasset differ diff --git a/scripts/packaging/package.snapshot b/scripts/packaging/package.snapshot index b50804f0c..5fe2807e1 100644 --- a/scripts/packaging/package.snapshot +++ b/scripts/packaging/package.snapshot @@ -91,6 +91,8 @@ Source/Sentry/Private/GenericPlatform/GenericPlatformSentryFeedback.cpp Source/Sentry/Private/GenericPlatform/GenericPlatformSentryFeedback.h Source/Sentry/Private/GenericPlatform/GenericPlatformSentryId.cpp Source/Sentry/Private/GenericPlatform/GenericPlatformSentryId.h +Source/Sentry/Private/GenericPlatform/GenericPlatformSentrySamplingContext.cpp +Source/Sentry/Private/GenericPlatform/GenericPlatformSentrySamplingContext.h Source/Sentry/Private/GenericPlatform/GenericPlatformSentryScope.cpp Source/Sentry/Private/GenericPlatform/GenericPlatformSentryScope.h Source/Sentry/Private/GenericPlatform/GenericPlatformSentrySpan.cpp @@ -184,6 +186,8 @@ Source/Sentry/Private/Tests/SentryScope.spec.cpp Source/Sentry/Private/Tests/SentryScopeBeforeSendHandler.h Source/Sentry/Private/Tests/SentrySubsystem.spec.cpp Source/Sentry/Private/Tests/SentryTests.h +Source/Sentry/Private/Tests/SentryTraceSampling.spec.cpp +Source/Sentry/Private/Tests/SentryTraceSamplingHandler.h Source/Sentry/Private/Tests/SentryUser.spec.cpp Source/Sentry/Private/Tests/SentryVariant.spec.cpp Source/Sentry/Private/Utils/SentryFileUtils.cpp @@ -217,6 +221,7 @@ Source/Sentry/Public/SentrySubsystem.h Source/Sentry/Public/SentryTraceSampler.h Source/Sentry/Public/SentryTransaction.h Source/Sentry/Public/SentryTransactionContext.h +Source/Sentry/Public/SentryTransactionOptions.h Source/Sentry/Public/SentryUser.h Source/Sentry/Public/SentryVariant.h Source/Sentry/Sentry_Android_UPL.xml