Skip to content

Commit

Permalink
Add traces sampler function config for Android/Apple (#488)
Browse files Browse the repository at this point in the history
* Update copyright notice

* Add basic traces sampler implementation

* Update package snapshot

* Add traces sampler init for Apple

* Add custom sampling context

* Add transaction context placeholder

* Update package snapshot

* Add minimal transaction context API

* Add transaction context setters

* Add way of initializing transaction context with given params (not only native object)

* Fix build errors in CI

* Add extra API to start transaction

* Update package snapshot

* Add transaction context implementation for desktop

* Add transaction context creation util to library

* Fix logs

* Update demo

* Add custom trace sampler blueprint

* Fix method signature

* Update package snapshot

* Add missing preprocessor directives for conditional compilation

* Add missing native methods implementation

* Update changelog

* Fix traces sampling on Android

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

Co-authored-by: Stefan Jandl <[email protected]>

---------

Co-authored-by: Stefan Jandl <[email protected]>
  • Loading branch information
tustanivsky and bitsandfoxes authored Mar 5, 2024
1 parent 79b7582 commit 407cf19
Show file tree
Hide file tree
Showing 51 changed files with 887 additions and 44 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
### Features

- Add performance monitoring API ([#470](https://github.com/getsentry/sentry-unreal/pull/470))
- Add traces sampler function config for Android/Apple ([#488](https://github.com/getsentry/sentry-unreal/pull/488))
- Add `IsCrashedLastRun` allowing to check whether the app crashed during its last run ([#483](https://github.com/getsentry/sentry-unreal/pull/483))
- Improved crash capture backend handling based on package version (GitHub or Marketplace) ([#479](https://github.com/getsentry/sentry-unreal/pull/479))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@
#include "SentryId.h"
#include "SentryTransaction.h"
#include "SentrySpan.h"
#include "SentryTransactionContext.h"
#include "SentryDefines.h"

#include "Android/SentryScopeAndroid.h"
#include "Android/SentryIdAndroid.h"
#include "Android/SentryTransactionAndroid.h"
#include "Android/SentrySpanAndroid.h"
#include "Android/SentryTransactionContextAndroid.h"

#include "Android/AndroidApplication.h"
#include "Android/AndroidJavaEnv.h"
Expand Down Expand Up @@ -169,6 +171,14 @@ USentrySpan* SentryConvertorsAndroid::SentrySpanToUnreal(jobject span)
return unrealSpan;
}

USentryTransactionContext* SentryConvertorsAndroid::SentryTransactionContextToUnreal(jobject transactionContext)
{
TSharedPtr<SentryTransactionContextAndroid> transactionContextNativeImpl = MakeShareable(new SentryTransactionContextAndroid(transactionContext));
USentryTransactionContext* unrealTransactionContext = NewObject<USentryTransactionContext>();
unrealTransactionContext->InitWithNativeImpl(transactionContextNativeImpl);
return unrealTransactionContext;
}

TMap<FString, FString> SentryConvertorsAndroid::StringMapToUnreal(jobject map)
{
TMap<FString, FString> result;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ class USentryScope;
class USentryId;
class USentryTransaction;
class USentrySpan;
class USentryTransactionContext;
class FSentryJavaObjectWrapper;
class FJsonValue;

Expand All @@ -28,6 +29,7 @@ class SentryConvertorsAndroid
static USentryId* SentryIdToUnreal(jobject id);
static USentryTransaction* SentryTransactionToUnreal(jobject transaction);
static USentrySpan* SentrySpanToUnreal(jobject span);
static USentryTransactionContext* SentryTransactionContextToUnreal(jobject transactionContext);
static TMap<FString, FString> StringMapToUnreal(jobject stringMap);
static TArray<FString> StringListToUnreal(jobject stringList);
static TArray<uint8> ByteArrayToUnreal(jbyteArray byteArray);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,31 @@
#include "SentryJavaClasses.h"

// External Java classes definitions
const FSentryJavaClass SentryJavaClasses::SentryBridgeJava = FSentryJavaClass { "io/sentry/unreal/SentryBridgeJava", ESentryJavaClassType::External };
const FSentryJavaClass SentryJavaClasses::Sentry = FSentryJavaClass { "io/sentry/Sentry", ESentryJavaClassType::External };
const FSentryJavaClass SentryJavaClasses::Attachment = FSentryJavaClass { "io/sentry/Attachment", ESentryJavaClassType::External };
const FSentryJavaClass SentryJavaClasses::Breadcrumb = FSentryJavaClass { "io/sentry/Breadcrumb", ESentryJavaClassType::External };
const FSentryJavaClass SentryJavaClasses::SentryEvent = FSentryJavaClass { "io/sentry/SentryEvent", ESentryJavaClassType::External };
const FSentryJavaClass SentryJavaClasses::SentryId = FSentryJavaClass { "io/sentry/protocol/SentryId", ESentryJavaClassType::External };
const FSentryJavaClass SentryJavaClasses::Scope = FSentryJavaClass { "io/sentry/Scope", ESentryJavaClassType::External };
const FSentryJavaClass SentryJavaClasses::User = FSentryJavaClass { "io/sentry/protocol/User", ESentryJavaClassType::External };
const FSentryJavaClass SentryJavaClasses::UserFeedback = FSentryJavaClass { "io/sentry/UserFeedback", ESentryJavaClassType::External };
const FSentryJavaClass SentryJavaClasses::Message = FSentryJavaClass { "io/sentry/protocol/Message", ESentryJavaClassType::External };
const FSentryJavaClass SentryJavaClasses::SentryLevel = FSentryJavaClass { "io/sentry/SentryLevel", ESentryJavaClassType::External };
const FSentryJavaClass SentryJavaClasses::SentryHint = FSentryJavaClass { "io/sentry/Hint", ESentryJavaClassType::External };
const FSentryJavaClass SentryJavaClasses::Transaction = FSentryJavaClass { "io/sentry/ITransaction", ESentryJavaClassType::External };
const FSentryJavaClass SentryJavaClasses::Span = FSentryJavaClass { "io/sentry/ISpan", ESentryJavaClassType::External };
const FSentryJavaClass SentryJavaClasses::SentryBridgeJava = FSentryJavaClass { "io/sentry/unreal/SentryBridgeJava", ESentryJavaClassType::External };
const FSentryJavaClass SentryJavaClasses::Sentry = FSentryJavaClass { "io/sentry/Sentry", ESentryJavaClassType::External };
const FSentryJavaClass SentryJavaClasses::Attachment = FSentryJavaClass { "io/sentry/Attachment", ESentryJavaClassType::External };
const FSentryJavaClass SentryJavaClasses::Breadcrumb = FSentryJavaClass { "io/sentry/Breadcrumb", ESentryJavaClassType::External };
const FSentryJavaClass SentryJavaClasses::SentryEvent = FSentryJavaClass { "io/sentry/SentryEvent", ESentryJavaClassType::External };
const FSentryJavaClass SentryJavaClasses::SentryId = FSentryJavaClass { "io/sentry/protocol/SentryId", ESentryJavaClassType::External };
const FSentryJavaClass SentryJavaClasses::Scope = FSentryJavaClass { "io/sentry/Scope", ESentryJavaClassType::External };
const FSentryJavaClass SentryJavaClasses::User = FSentryJavaClass { "io/sentry/protocol/User", ESentryJavaClassType::External };
const FSentryJavaClass SentryJavaClasses::UserFeedback = FSentryJavaClass { "io/sentry/UserFeedback", ESentryJavaClassType::External };
const FSentryJavaClass SentryJavaClasses::Message = FSentryJavaClass { "io/sentry/protocol/Message", ESentryJavaClassType::External };
const FSentryJavaClass SentryJavaClasses::SentryLevel = FSentryJavaClass { "io/sentry/SentryLevel", ESentryJavaClassType::External };
const FSentryJavaClass SentryJavaClasses::SentryHint = FSentryJavaClass { "io/sentry/Hint", ESentryJavaClassType::External };
const FSentryJavaClass SentryJavaClasses::Transaction = FSentryJavaClass { "io/sentry/ITransaction", ESentryJavaClassType::External };
const FSentryJavaClass SentryJavaClasses::Span = FSentryJavaClass { "io/sentry/ISpan", ESentryJavaClassType::External };
const FSentryJavaClass SentryJavaClasses::SamplingContext = FSentryJavaClass { "io/sentry/SamplingContext", ESentryJavaClassType::External };
const FSentryJavaClass SentryJavaClasses::CustomSamplingContext = FSentryJavaClass { "io/sentry/CustomSamplingContext", ESentryJavaClassType::External };
const FSentryJavaClass SentryJavaClasses::TransactionContext = FSentryJavaClass { "io/sentry/TransactionContext", ESentryJavaClassType::External };
const FSentryJavaClass SentryJavaClasses::TransactionOptions = FSentryJavaClass { "io/sentry/TransactionOptions", ESentryJavaClassType::External };

// System Java classes definitions
const FSentryJavaClass SentryJavaClasses::ArrayList = FSentryJavaClass { "java/util/ArrayList", ESentryJavaClassType::System };
const FSentryJavaClass SentryJavaClasses::HashMap = FSentryJavaClass { "java/util/HashMap", ESentryJavaClassType::System };
const FSentryJavaClass SentryJavaClasses::Map = FSentryJavaClass { "java/util/Map", ESentryJavaClassType::System };
const FSentryJavaClass SentryJavaClasses::Set = FSentryJavaClass { "java/util/Set", ESentryJavaClassType::System };
const FSentryJavaClass SentryJavaClasses::Iterator = FSentryJavaClass { "java/util/Iterator", ESentryJavaClassType::System };
const FSentryJavaClass SentryJavaClasses::MapEntry = FSentryJavaClass { "java/util/Map$Entry", ESentryJavaClassType::System };
const FSentryJavaClass SentryJavaClasses::List = FSentryJavaClass { "java/util/List", ESentryJavaClassType::System };
const FSentryJavaClass SentryJavaClasses::ArrayList = FSentryJavaClass { "java/util/ArrayList", ESentryJavaClassType::System };
const FSentryJavaClass SentryJavaClasses::HashMap = FSentryJavaClass { "java/util/HashMap", ESentryJavaClassType::System };
const FSentryJavaClass SentryJavaClasses::Map = FSentryJavaClass { "java/util/Map", ESentryJavaClassType::System };
const FSentryJavaClass SentryJavaClasses::Set = FSentryJavaClass { "java/util/Set", ESentryJavaClassType::System };
const FSentryJavaClass SentryJavaClasses::Iterator = FSentryJavaClass { "java/util/Iterator", ESentryJavaClassType::System };
const FSentryJavaClass SentryJavaClasses::MapEntry = FSentryJavaClass { "java/util/Map$Entry", ESentryJavaClassType::System };
const FSentryJavaClass SentryJavaClasses::List = FSentryJavaClass { "java/util/List", ESentryJavaClassType::System };
const FSentryJavaClass SentryJavaClasses::Double = FSentryJavaClass { "java/lang/Double", ESentryJavaClassType::System };
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ struct SentryJavaClasses
const static FSentryJavaClass SentryHint;
const static FSentryJavaClass Transaction;
const static FSentryJavaClass Span;
const static FSentryJavaClass SamplingContext;
const static FSentryJavaClass CustomSamplingContext;
const static FSentryJavaClass TransactionContext;
const static FSentryJavaClass TransactionOptions;

// System Java classes
const static FSentryJavaClass ArrayList;
Expand All @@ -30,4 +34,5 @@ struct SentryJavaClasses
const static FSentryJavaClass Iterator;
const static FSentryJavaClass MapEntry;
const static FSentryJavaClass List;
const static FSentryJavaClass Double;
};
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
public class SentryBridgeJava {
public static native void onConfigureScope(long callbackAddr, IScope scope);
public static native SentryEvent onBeforeSend(long handlerAddr, SentryEvent event, Hint hint);
public static native Double onTracesSampler(long samplerAddr, SamplingContext samplingContext);
public static native float onTracesSampler(long samplerAddr, SamplingContext samplingContext);

public static void init(Activity activity, final String settingsJsonStr, final long beforeSendHandler) {
SentryAndroid.init(activity, new Sentry.OptionsConfiguration<SentryAndroidOptions>() {
Expand Down Expand Up @@ -73,7 +73,12 @@ public SentryEvent execute(SentryEvent event, Hint hint) {
options.setTracesSampler(new SentryOptions.TracesSamplerCallback() {
@Override
public Double sample(SamplingContext samplingContext) {
return onTracesSampler(samplerAddr, samplingContext);
float sampleRate = onTracesSampler(samplerAddr, samplingContext);
if(sampleRate >= 0.0f) {
return (double) sampleRate;
} else {
return null;
}
}
});
}
Expand Down
21 changes: 19 additions & 2 deletions plugin-dev/Source/Sentry/Private/Android/Jni/SentryJniAndroid.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,18 @@

#include "Android/Callbacks/SentryScopeCallbackAndroid.h"
#include "Android/Infrastructure/SentryConvertorsAndroid.h"
#include "Android/Infrastructure/SentryJavaClasses.h"
#include "Android/SentryEventAndroid.h"
#include "Android/SentryHintAndroid.h"
#include "Android/SentrySamplingContextAndroid.h"

#include "Android/AndroidJNI.h"

#include "SentryEvent.h"
#include "SentryHint.h"
#include "SentryBeforeSendHandler.h"
#include "SentryTraceSampler.h"
#include "SentrySamplingContext.h"

JNI_METHOD void Java_io_sentry_unreal_SentryBridgeJava_onConfigureScope(JNIEnv* env, jclass clazz, jlong objAddr, jobject scope)
{
Expand All @@ -33,7 +37,20 @@ JNI_METHOD jobject Java_io_sentry_unreal_SentryBridgeJava_onBeforeSend(JNIEnv* e
return handler->HandleBeforeSend(EventToProcess, HintToProcess) ? event : nullptr;
}

JNI_METHOD jobject Java_io_sentry_unreal_SentryBridgeJava_onTracesSampler(JNIEnv* env, jclass clazz, jlong objAddr, jobject samplingContext)
JNI_METHOD jfloat Java_io_sentry_unreal_SentryBridgeJava_onTracesSampler(JNIEnv* env, jclass clazz, jlong objAddr, jobject samplingContext)
{
return nullptr;
USentryTraceSampler* sampler = reinterpret_cast<USentryTraceSampler*>(objAddr);

USentrySamplingContext* Context = NewObject<USentrySamplingContext>();
Context->InitWithNativeImpl(MakeShareable(new SentrySamplingContextAndroid(samplingContext)));

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

// to avoid instantiating `java.lang.Double` object within this JNI callback a negative value is returned instead
// which should be interpreted as `null` in Java code to fallback to fixed sample rate value
return -1.0f;
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

#include "Infrastructure/SentryJavaObjectWrapper.h"

class SentryMessageAndroid: public FSentryJavaObjectWrapper
class SentryMessageAndroid : public FSentryJavaObjectWrapper
{
public:
SentryMessageAndroid(const FString& message);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Copyright (c) 2024 Sentry. All Rights Reserved.

#include "SentrySamplingContextAndroid.h"

#include "SentryTransactionContext.h"

#include "Infrastructure/SentryConvertorsAndroid.h"
#include "Infrastructure/SentryJavaClasses.h"

SentrySamplingContextAndroid::SentrySamplingContextAndroid(jobject samplingContext)
: FSentryJavaObjectWrapper(SentryJavaClasses::SamplingContext, samplingContext)
{
SetupClassMethods();
}

void SentrySamplingContextAndroid::SetupClassMethods()
{
GetTransactionContextMethod = GetMethod("getTransactionContext", "()Lio/sentry/TransactionContext;");
GetCustomSamplingContextMethod = GetMethod("getCustomSamplingContext", "()Lio/sentry/CustomSamplingContext;");
}

USentryTransactionContext* SentrySamplingContextAndroid::GetTransactionContext() const
{
auto transactionContext = CallObjectMethod<jobject>(GetTransactionContextMethod);
return SentryConvertorsAndroid::SentryTransactionContextToUnreal(*transactionContext);
}

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

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

auto data = NativeCustomSamplingContext.CallObjectMethod<jobject>(GetDataMethod);
return SentryConvertorsAndroid::StringMapToUnreal(*data);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright (c) 2024 Sentry. All Rights Reserved.

#pragma once

#include "Interface/SentrySamplingContextInterface.h"

#include "Infrastructure/SentryJavaObjectWrapper.h"

class SentrySamplingContextAndroid : public ISentrySamplingContext, public FSentryJavaObjectWrapper
{
public:
SentrySamplingContextAndroid(jobject samplingContext);

void SetupClassMethods();

virtual USentryTransactionContext* GetTransactionContext() const override;
virtual TMap<FString, FString> GetCustomSamplingContext() const override;

private:
FSentryJavaMethod GetTransactionContextMethod;
FSentryJavaMethod GetCustomSamplingContextMethod;
};
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,19 @@
#include "SentryBreadcrumbAndroid.h"
#include "SentryUserFeedbackAndroid.h"
#include "SentryUserAndroid.h"
#include "SentryTransactionContextAndroid.h"
#include "SentryTransactionOptionsAndroid.h"

#include "SentryDefines.h"
#include "SentryBeforeSendHandler.h"

#include "SentryTraceSampler.h"
#include "SentryEvent.h"
#include "SentryBreadcrumb.h"
#include "SentryId.h"
#include "SentrySettings.h"
#include "SentryUserFeedback.h"
#include "SentryUser.h"
#include "SentryTransactionContext.h"

#include "Callbacks/SentryScopeCallbackAndroid.h"

Expand All @@ -26,7 +30,7 @@
#include "Dom/JsonObject.h"
#include "Serialization/JsonSerializer.h"

void SentrySubsystemAndroid::InitWithSettings(const USentrySettings* settings, USentryBeforeSendHandler* beforeSendHandler)
void SentrySubsystemAndroid::InitWithSettings(const USentrySettings* settings, USentryBeforeSendHandler* beforeSendHandler, USentryTraceSampler* traceSampler)
{
TSharedPtr<FJsonObject> SettingsJson = MakeShareable(new FJsonObject);
SettingsJson->SetStringField(TEXT("dsn"), settings->Dsn);
Expand All @@ -51,8 +55,7 @@ void SentrySubsystemAndroid::InitWithSettings(const USentrySettings* settings, U
}
if(settings->EnableTracing && settings->SamplingType == ESentryTracesSamplingType::TracesSampler)
{
UE_LOG(LogSentrySdk, Warning, TEXT("Currently sampling functions are not supported"));
SettingsJson->SetNumberField(TEXT("tracesSampler"), (jlong)0);
SettingsJson->SetNumberField(TEXT("tracesSampler"), (jlong)traceSampler);
}

FString SettingsJsonStr;
Expand Down Expand Up @@ -224,3 +227,26 @@ USentryTransaction* SentrySubsystemAndroid::StartTransaction(const FString& name

return SentryConvertorsAndroid::SentryTransactionToUnreal(*transaction);
}

USentryTransaction* SentrySubsystemAndroid::StartTransactionWithContext(USentryTransactionContext* context)
{
TSharedPtr<SentryTransactionContextAndroid> transactionContextAndroid = StaticCastSharedPtr<SentryTransactionContextAndroid>(context->GetNativeImpl());

auto transaction = FSentryJavaObjectWrapper::CallStaticObjectMethod<jobject>(SentryJavaClasses::Sentry, "startTransaction", "(Lio/sentry/TransactionContext;)Lio/sentry/ITransaction;",
transactionContextAndroid->GetJObject());

return SentryConvertorsAndroid::SentryTransactionToUnreal(*transaction);
}

USentryTransaction* SentrySubsystemAndroid::StartTransactionWithContextAndOptions(USentryTransactionContext* context, const TMap<FString, FString>& options)
{
TSharedPtr<SentryTransactionContextAndroid> transactionContextAndroid = StaticCastSharedPtr<SentryTransactionContextAndroid>(context->GetNativeImpl());

TSharedPtr<SentryTransactionOptionsAndroid> transactionOptionsAndroid = MakeShareable(new SentryTransactionOptionsAndroid());
transactionOptionsAndroid->SetCustomSamplingContext(options);

auto transaction = FSentryJavaObjectWrapper::CallStaticObjectMethod<jobject>(SentryJavaClasses::Sentry, "startTransaction", "(Lio/sentry/TransactionContext;Lio/sentry/TransactionOptions;)Lio/sentry/ITransaction;",
transactionContextAndroid->GetJObject(), transactionOptionsAndroid->GetJObject());

return SentryConvertorsAndroid::SentryTransactionToUnreal(*transaction);
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
class SentrySubsystemAndroid : public ISentrySubsystem
{
public:
virtual void InitWithSettings(const USentrySettings* settings, USentryBeforeSendHandler* beforeSendHandler) override;
virtual void InitWithSettings(const USentrySettings* settings, USentryBeforeSendHandler* beforeSendHandler, USentryTraceSampler* traceSampler) override;
virtual void Close() override;
virtual bool IsEnabled() override;
virtual ESentryCrashedLastRun IsCrashedLastRun() override;
Expand All @@ -28,4 +28,6 @@ class SentrySubsystemAndroid : public ISentrySubsystem
virtual void StartSession() override;
virtual void EndSession() override;
virtual USentryTransaction* StartTransaction(const FString& name, const FString& operation) override;
virtual USentryTransaction* StartTransactionWithContext(USentryTransactionContext* context) override;
virtual USentryTransaction* StartTransactionWithContextAndOptions(USentryTransactionContext* context, const TMap<FString, FString>& options) override;
};
Loading

0 comments on commit 407cf19

Please sign in to comment.