From be328234ce9e6e8aaa05a964043179d9953f0782 Mon Sep 17 00:00:00 2001 From: Ivan Tustanivskyi Date: Wed, 9 Oct 2024 12:56:01 +0300 Subject: [PATCH] Add trace propagation (#631) --- CHANGELOG.md | 1 + .../Infrastructure/SentryJavaClasses.cpp | 1 + .../Infrastructure/SentryJavaClasses.h | 1 + .../Private/Android/SentrySpanAndroid.cpp | 10 ++++++ .../Private/Android/SentrySpanAndroid.h | 3 +- .../Android/SentrySubsystemAndroid.cpp | 8 +++++ .../Private/Android/SentrySubsystemAndroid.h | 1 + .../Android/SentryTransactionAndroid.cpp | 10 ++++++ .../Android/SentryTransactionAndroid.h | 2 ++ .../Sentry/Private/Apple/SentryIdApple.cpp | 5 +++ .../Sentry/Private/Apple/SentrySpanApple.cpp | 10 ++++++ .../Sentry/Private/Apple/SentrySpanApple.h | 2 +- .../Private/Apple/SentrySubsystemApple.cpp | 36 ++++++++++++++++++- .../Private/Apple/SentrySubsystemApple.h | 1 + .../Private/Apple/SentryTransactionApple.cpp | 8 +++++ .../Private/Apple/SentryTransactionApple.h | 1 + .../Private/Desktop/SentrySpanDesktop.cpp | 18 ++++++++++ .../Private/Desktop/SentrySpanDesktop.h | 1 + .../Desktop/SentrySubsystemDesktop.cpp | 15 ++++++++ .../Private/Desktop/SentrySubsystemDesktop.h | 1 + .../Desktop/SentryTransactionDesktop.cpp | 18 ++++++++++ .../Desktop/SentryTransactionDesktop.h | 1 + .../Private/Interface/SentrySpanInterface.h | 1 + .../Interface/SentrySubsystemInterface.h | 1 + .../Interface/SentryTransactionInterface.h | 1 + .../Source/Sentry/Private/SentrySpan.cpp | 8 +++++ .../Source/Sentry/Private/SentrySubsystem.cpp | 8 +++++ .../Sentry/Private/SentryTransaction.cpp | 8 +++++ .../Private/Tests/SentrySubsystem.spec.cpp | 25 +++++++++++++ plugin-dev/Source/Sentry/Public/SentrySpan.h | 4 +++ .../Source/Sentry/Public/SentrySubsystem.h | 10 ++++++ .../Source/Sentry/Public/SentryTransaction.h | 4 +++ 32 files changed, 221 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7cac0fd5..86740515 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Add setting that allows switching between the project and user directories for the internal Sentry database location on Windows/Linux ([#616](https://github.com/getsentry/sentry-unreal/pull/616)) - Add non-ASCII characters support for user messages ([#624](https://github.com/getsentry/sentry-unreal/pull/624)) +- Add API to allow users to trace their distributed system and connect in-game with backend errors ([#631](https://github.com/getsentry/sentry-unreal/pull/631)) - Allow overriding `UploadSymbolsAutomatically` via environment variable `SENTRY_UPLOAD_SYMBOLS_AUTOMATICALLY` ([#636](https://github.com/getsentry/sentry-unreal/pull/636)) ### Fixes diff --git a/plugin-dev/Source/Sentry/Private/Android/Infrastructure/SentryJavaClasses.cpp b/plugin-dev/Source/Sentry/Private/Android/Infrastructure/SentryJavaClasses.cpp index fe78bfac..036b8706 100644 --- a/plugin-dev/Source/Sentry/Private/Android/Infrastructure/SentryJavaClasses.cpp +++ b/plugin-dev/Source/Sentry/Private/Android/Infrastructure/SentryJavaClasses.cpp @@ -21,6 +21,7 @@ const FSentryJavaClass SentryJavaClasses::SamplingContext = FSentryJavaClass { 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 }; +const FSentryJavaClass SentryJavaClasses::SentryTraceHeader = FSentryJavaClass { "io/sentry/SentryTraceHeader", ESentryJavaClassType::External }; // System Java classes definitions const FSentryJavaClass SentryJavaClasses::ArrayList = FSentryJavaClass { "java/util/ArrayList", ESentryJavaClassType::System }; diff --git a/plugin-dev/Source/Sentry/Private/Android/Infrastructure/SentryJavaClasses.h b/plugin-dev/Source/Sentry/Private/Android/Infrastructure/SentryJavaClasses.h index ddf92504..4891bef9 100644 --- a/plugin-dev/Source/Sentry/Private/Android/Infrastructure/SentryJavaClasses.h +++ b/plugin-dev/Source/Sentry/Private/Android/Infrastructure/SentryJavaClasses.h @@ -25,6 +25,7 @@ struct SentryJavaClasses const static FSentryJavaClass CustomSamplingContext; const static FSentryJavaClass TransactionContext; const static FSentryJavaClass TransactionOptions; + const static FSentryJavaClass SentryTraceHeader; // System Java classes const static FSentryJavaClass ArrayList; diff --git a/plugin-dev/Source/Sentry/Private/Android/SentrySpanAndroid.cpp b/plugin-dev/Source/Sentry/Private/Android/SentrySpanAndroid.cpp index 5c401a7e..5ef2abb2 100644 --- a/plugin-dev/Source/Sentry/Private/Android/SentrySpanAndroid.cpp +++ b/plugin-dev/Source/Sentry/Private/Android/SentrySpanAndroid.cpp @@ -17,6 +17,7 @@ void SentrySpanAndroid::SetupClassMethods() IsFinishedMethod = GetMethod("isFinished", "()Z"); SetTagMethod = GetMethod("setTag", "(Ljava/lang/String;Ljava/lang/String;)V"); SetDataMethod = GetMethod("setData", "(Ljava/lang/String;Ljava/lang/Object;)V"); + ToSentryTraceMethod = GetMethod("toSentryTrace", "()Lio/sentry/SentryTraceHeader;"); } void SentrySpanAndroid::Finish() @@ -48,3 +49,12 @@ void SentrySpanAndroid::RemoveData(const FString& key) { SetData(key, TMap()); } + +void SentrySpanAndroid::GetTrace(FString& name, FString& value) +{ + FSentryJavaObjectWrapper NativeTraceHeader(SentryJavaClasses::SentryTraceHeader, *CallObjectMethod(ToSentryTraceMethod)); + FSentryJavaMethod GetValueMethod = NativeTraceHeader.GetMethod("getValue", "()Ljava/lang/String;"); + + name = TEXT("sentry-trace"); + value = NativeTraceHeader.CallMethod(GetValueMethod); +} diff --git a/plugin-dev/Source/Sentry/Private/Android/SentrySpanAndroid.h b/plugin-dev/Source/Sentry/Private/Android/SentrySpanAndroid.h index 2ee130e2..b1049d8c 100644 --- a/plugin-dev/Source/Sentry/Private/Android/SentrySpanAndroid.h +++ b/plugin-dev/Source/Sentry/Private/Android/SentrySpanAndroid.h @@ -19,11 +19,12 @@ class SentrySpanAndroid : public ISentrySpan, public FSentryJavaObjectWrapper virtual void RemoveTag(const FString& key) override; virtual void SetData(const FString& key, const TMap& values) override; virtual void RemoveData(const FString& key) override; - + virtual void GetTrace(FString& name, FString& value) override; private: FSentryJavaMethod FinishMethod; FSentryJavaMethod IsFinishedMethod; FSentryJavaMethod SetTagMethod; FSentryJavaMethod SetDataMethod; + FSentryJavaMethod ToSentryTraceMethod; }; diff --git a/plugin-dev/Source/Sentry/Private/Android/SentrySubsystemAndroid.cpp b/plugin-dev/Source/Sentry/Private/Android/SentrySubsystemAndroid.cpp index 8fad4b64..21a0cbc5 100644 --- a/plugin-dev/Source/Sentry/Private/Android/SentrySubsystemAndroid.cpp +++ b/plugin-dev/Source/Sentry/Private/Android/SentrySubsystemAndroid.cpp @@ -292,3 +292,11 @@ USentryTransaction* SentrySubsystemAndroid::StartTransactionWithContextAndOption return SentryConvertorsAndroid::SentryTransactionToUnreal(*transaction); } + +USentryTransactionContext* SentrySubsystemAndroid::ContinueTrace(const FString& sentryTrace, const TArray& baggageHeaders) +{ + auto transactionContext = FSentryJavaObjectWrapper::CallStaticObjectMethod(SentryJavaClasses::Sentry, "continueTrace", "(Ljava/lang/String;Ljava/util/List;)Lio/sentry/TransactionContext;", + *FSentryJavaObjectWrapper::GetJString(sentryTrace), SentryConvertorsAndroid::StringArrayToNative(baggageHeaders)->GetJObject()); + + return SentryConvertorsAndroid::SentryTransactionContextToUnreal(*transactionContext); +} diff --git a/plugin-dev/Source/Sentry/Private/Android/SentrySubsystemAndroid.h b/plugin-dev/Source/Sentry/Private/Android/SentrySubsystemAndroid.h index eb3bbe56..3c3f1d51 100644 --- a/plugin-dev/Source/Sentry/Private/Android/SentrySubsystemAndroid.h +++ b/plugin-dev/Source/Sentry/Private/Android/SentrySubsystemAndroid.h @@ -34,4 +34,5 @@ class SentrySubsystemAndroid : public ISentrySubsystem virtual USentryTransaction* StartTransaction(const FString& name, const FString& operation) override; virtual USentryTransaction* StartTransactionWithContext(USentryTransactionContext* context) override; virtual USentryTransaction* StartTransactionWithContextAndOptions(USentryTransactionContext* context, const TMap& options) override; + virtual USentryTransactionContext* ContinueTrace(const FString& sentryTrace, const TArray& baggageHeaders) override; }; diff --git a/plugin-dev/Source/Sentry/Private/Android/SentryTransactionAndroid.cpp b/plugin-dev/Source/Sentry/Private/Android/SentryTransactionAndroid.cpp index f67bffba..ca82510a 100644 --- a/plugin-dev/Source/Sentry/Private/Android/SentryTransactionAndroid.cpp +++ b/plugin-dev/Source/Sentry/Private/Android/SentryTransactionAndroid.cpp @@ -19,6 +19,7 @@ void SentryTransactionAndroid::SetupClassMethods() SetNameMethod = GetMethod("setName", "(Ljava/lang/String;)V"); SetTagMethod = GetMethod("setTag", "(Ljava/lang/String;Ljava/lang/String;)V"); SetDataMethod = GetMethod("setData", "(Ljava/lang/String;Ljava/lang/Object;)V"); + ToSentryTraceMethod = GetMethod("toSentryTrace", "()Lio/sentry/SentryTraceHeader;"); } USentrySpan* SentryTransactionAndroid::StartChild(const FString& operation, const FString& desctiption) @@ -61,3 +62,12 @@ void SentryTransactionAndroid::RemoveData(const FString& key) { SetData(key, TMap()); } + +void SentryTransactionAndroid::GetTrace(FString& name, FString& value) +{ + FSentryJavaObjectWrapper NativeTraceHeader(SentryJavaClasses::SentryTraceHeader, *CallObjectMethod(ToSentryTraceMethod)); + FSentryJavaMethod GetValueMethod = NativeTraceHeader.GetMethod("getValue", "()Ljava/lang/String;"); + + name = TEXT("sentry-trace"); + value = NativeTraceHeader.CallMethod(GetValueMethod); +} diff --git a/plugin-dev/Source/Sentry/Private/Android/SentryTransactionAndroid.h b/plugin-dev/Source/Sentry/Private/Android/SentryTransactionAndroid.h index aa51c851..9f114e9f 100644 --- a/plugin-dev/Source/Sentry/Private/Android/SentryTransactionAndroid.h +++ b/plugin-dev/Source/Sentry/Private/Android/SentryTransactionAndroid.h @@ -21,6 +21,7 @@ class SentryTransactionAndroid : public ISentryTransaction, public FSentryJavaOb virtual void RemoveTag(const FString& key) override; virtual void SetData(const FString& key, const TMap& values) override; virtual void RemoveData(const FString& key) override; + virtual void GetTrace(FString& name, FString& value) override; private: FSentryJavaMethod StartChildMethod; @@ -29,4 +30,5 @@ class SentryTransactionAndroid : public ISentryTransaction, public FSentryJavaOb FSentryJavaMethod SetNameMethod; FSentryJavaMethod SetTagMethod; FSentryJavaMethod SetDataMethod; + FSentryJavaMethod ToSentryTraceMethod; }; diff --git a/plugin-dev/Source/Sentry/Private/Apple/SentryIdApple.cpp b/plugin-dev/Source/Sentry/Private/Apple/SentryIdApple.cpp index 29136a36..ed0e0ba9 100644 --- a/plugin-dev/Source/Sentry/Private/Apple/SentryIdApple.cpp +++ b/plugin-dev/Source/Sentry/Private/Apple/SentryIdApple.cpp @@ -7,7 +7,12 @@ SentryIdApple::SentryIdApple() { + // `SentryId` definition was moved to Swift so its name that can be recognized by UE should be taken from "Sentry-Swift.h" to successfully load class on Mac +#if PLATFORM_MAC + IdApple = [[SENTRY_APPLE_CLASS(_TtC6Sentry8SentryId) alloc] init]; +#elif PLATFORM_IOS IdApple = [[SENTRY_APPLE_CLASS(SentryId) alloc] init]; +#endif } SentryIdApple::SentryIdApple(SentryId* id) diff --git a/plugin-dev/Source/Sentry/Private/Apple/SentrySpanApple.cpp b/plugin-dev/Source/Sentry/Private/Apple/SentrySpanApple.cpp index 2caed47a..24d086e6 100644 --- a/plugin-dev/Source/Sentry/Private/Apple/SentrySpanApple.cpp +++ b/plugin-dev/Source/Sentry/Private/Apple/SentrySpanApple.cpp @@ -2,6 +2,8 @@ #include "SentrySpanApple.h" +#include "SentryDefines.h" + #include "Infrastructure/SentryConvertorsApple.h" #include "Convenience/SentryInclude.h" @@ -51,3 +53,11 @@ void SentrySpanApple::RemoveData(const FString& key) { [SpanApple removeDataForKey:key.GetNSString()]; } + +void SentrySpanApple::GetTrace(FString& name, FString& value) +{ + SentryTraceHeader* traceHeader = [SpanApple toTraceHeader]; + + name = TEXT("sentry-trace"); + value = FString([traceHeader value]); +} diff --git a/plugin-dev/Source/Sentry/Private/Apple/SentrySpanApple.h b/plugin-dev/Source/Sentry/Private/Apple/SentrySpanApple.h index d508e651..514cc722 100644 --- a/plugin-dev/Source/Sentry/Private/Apple/SentrySpanApple.h +++ b/plugin-dev/Source/Sentry/Private/Apple/SentrySpanApple.h @@ -20,7 +20,7 @@ class SentrySpanApple : public ISentrySpan virtual void RemoveTag(const FString& key) override; virtual void SetData(const FString& key, const TMap& values) override; virtual void RemoveData(const FString& key) override; - + virtual void GetTrace(FString& name, FString& value) override; private: id SpanApple; diff --git a/plugin-dev/Source/Sentry/Private/Apple/SentrySubsystemApple.cpp b/plugin-dev/Source/Sentry/Private/Apple/SentrySubsystemApple.cpp index 5c585f6f..44aac2f6 100644 --- a/plugin-dev/Source/Sentry/Private/Apple/SentrySubsystemApple.cpp +++ b/plugin-dev/Source/Sentry/Private/Apple/SentrySubsystemApple.cpp @@ -79,7 +79,6 @@ void SentrySubsystemApple::InitWithSettings(const USentrySettings* settings, USe [options addInAppExclude:it->GetNSString()]; } options.enableAppHangTracking = settings->EnableAppNotRespondingTracking; - options.enableTracing = settings->EnableTracing; if(settings->EnableTracing && settings->SamplingType == ESentryTracesSamplingType::UniformSampleRate) { options.tracesSampleRate = [NSNumber numberWithFloat:settings->TracesSampleRate]; @@ -303,3 +302,38 @@ USentryTransaction* SentrySubsystemApple::StartTransactionWithContextAndOptions( return SentryConvertorsApple::SentryTransactionToUnreal(transaction); } + +USentryTransactionContext* SentrySubsystemApple::ContinueTrace(const FString& sentryTrace, const TArray& baggageHeaders) +{ + TArray traceParts; + sentryTrace.ParseIntoArray(traceParts, TEXT("-")); + + if (traceParts.Num() < 2) + { + return nullptr; + } + + SentrySampleDecision sampleDecision = kSentrySampleDecisionUndecided; + if (traceParts.Num() == 3) + { + sampleDecision = traceParts[2].Equals(TEXT("1")) ? kSentrySampleDecisionYes : kSentrySampleDecisionNo; + } + + // `SentryId` definition was moved to Swift so its name that can be recognized by UE should be taken from "Sentry-Swift.h" to successfully load class on Mac + +#if PLATFORM_MAC + SentryId* traceId = [[SENTRY_APPLE_CLASS(_TtC6Sentry8SentryId) alloc] initWithUUIDString:traceParts[0].GetNSString()]; +#elif PLATFORM_IOS + SentryId* traceId = [[SENTRY_APPLE_CLASS(SentryId) alloc] initWithUUIDString:traceParts[0].GetNSString()]; +#endif + + SentryTransactionContext* transactionContext = [[SENTRY_APPLE_CLASS(SentryTransactionContext) alloc] initWithName:@"" operation:@"default" + traceId:traceId + spanId:[[SENTRY_APPLE_CLASS(SentrySpanId) alloc] init] + parentSpanId:[[SENTRY_APPLE_CLASS(SentrySpanId) alloc] initWithValue:traceParts[1].GetNSString()] + parentSampled:sampleDecision]; + + // currently `sentry-cocoa` doesn't have API for `SentryTransactionContext` to set `baggageHeaders` + + return SentryConvertorsApple::SentryTransactionContextToUnreal(transactionContext); +} diff --git a/plugin-dev/Source/Sentry/Private/Apple/SentrySubsystemApple.h b/plugin-dev/Source/Sentry/Private/Apple/SentrySubsystemApple.h index 14c880f4..c858c277 100644 --- a/plugin-dev/Source/Sentry/Private/Apple/SentrySubsystemApple.h +++ b/plugin-dev/Source/Sentry/Private/Apple/SentrySubsystemApple.h @@ -34,4 +34,5 @@ class SentrySubsystemApple : public ISentrySubsystem virtual USentryTransaction* StartTransaction(const FString& name, const FString& operation) override; virtual USentryTransaction* StartTransactionWithContext(USentryTransactionContext* context) override; virtual USentryTransaction* StartTransactionWithContextAndOptions(USentryTransactionContext* context, const TMap& options) override; + virtual USentryTransactionContext* ContinueTrace(const FString& sentryTrace, const TArray& baggageHeaders) override; }; diff --git a/plugin-dev/Source/Sentry/Private/Apple/SentryTransactionApple.cpp b/plugin-dev/Source/Sentry/Private/Apple/SentryTransactionApple.cpp index 78891754..9245ce7b 100644 --- a/plugin-dev/Source/Sentry/Private/Apple/SentryTransactionApple.cpp +++ b/plugin-dev/Source/Sentry/Private/Apple/SentryTransactionApple.cpp @@ -65,3 +65,11 @@ void SentryTransactionApple::RemoveData(const FString& key) { [TransactionApple removeDataForKey:key.GetNSString()]; } + +void SentryTransactionApple::GetTrace(FString& name, FString& value) +{ + SentryTraceHeader* traceHeader = [TransactionApple toTraceHeader]; + + name = TEXT("sentry-trace"); + value = FString([traceHeader value]); +} diff --git a/plugin-dev/Source/Sentry/Private/Apple/SentryTransactionApple.h b/plugin-dev/Source/Sentry/Private/Apple/SentryTransactionApple.h index f6b08db7..844f5292 100644 --- a/plugin-dev/Source/Sentry/Private/Apple/SentryTransactionApple.h +++ b/plugin-dev/Source/Sentry/Private/Apple/SentryTransactionApple.h @@ -22,6 +22,7 @@ class SentryTransactionApple : public ISentryTransaction virtual void RemoveTag(const FString& key) override; virtual void SetData(const FString& key, const TMap& values) override; virtual void RemoveData(const FString& key) override; + virtual void GetTrace(FString& name, FString& value) override; private: id TransactionApple; diff --git a/plugin-dev/Source/Sentry/Private/Desktop/SentrySpanDesktop.cpp b/plugin-dev/Source/Sentry/Private/Desktop/SentrySpanDesktop.cpp index d3f65cbe..1a302b4e 100644 --- a/plugin-dev/Source/Sentry/Private/Desktop/SentrySpanDesktop.cpp +++ b/plugin-dev/Source/Sentry/Private/Desktop/SentrySpanDesktop.cpp @@ -6,6 +6,12 @@ #if USE_SENTRY_NATIVE +void CopySpanTracingHeader(const char *key, const char *value, void *userdata) +{ + sentry_value_t *header = static_cast(userdata); + sentry_value_set_by_key(*header, key, sentry_value_new_string(value)); +} + SentrySpanDesktop::SentrySpanDesktop(sentry_span_t* span) : SpanDesktop(span) , isFinished(false) @@ -62,4 +68,16 @@ void SentrySpanDesktop::RemoveData(const FString& key) sentry_span_remove_data(SpanDesktop, TCHAR_TO_ANSI(*key)); } +void SentrySpanDesktop::GetTrace(FString& name, FString& value) +{ + sentry_value_t tracingHeader = sentry_value_new_object(); + + sentry_span_iter_headers(SpanDesktop, CopySpanTracingHeader, &tracingHeader); + + name = TEXT("sentry-trace"); + value = FString(sentry_value_as_string(sentry_value_get_by_key(tracingHeader, "sentry-trace"))); + + sentry_value_decref(tracingHeader); +} + #endif diff --git a/plugin-dev/Source/Sentry/Private/Desktop/SentrySpanDesktop.h b/plugin-dev/Source/Sentry/Private/Desktop/SentrySpanDesktop.h index f8c00552..cf7862fc 100644 --- a/plugin-dev/Source/Sentry/Private/Desktop/SentrySpanDesktop.h +++ b/plugin-dev/Source/Sentry/Private/Desktop/SentrySpanDesktop.h @@ -24,6 +24,7 @@ class SentrySpanDesktop : public ISentrySpan virtual void RemoveTag(const FString& key) override; virtual void SetData(const FString& key, const TMap& values) override; virtual void RemoveData(const FString& key) override; + virtual void GetTrace(FString& name, FString& value) override; private: sentry_span_t* SpanDesktop; diff --git a/plugin-dev/Source/Sentry/Private/Desktop/SentrySubsystemDesktop.cpp b/plugin-dev/Source/Sentry/Private/Desktop/SentrySubsystemDesktop.cpp index 24f921be..659ac6cf 100644 --- a/plugin-dev/Source/Sentry/Private/Desktop/SentrySubsystemDesktop.cpp +++ b/plugin-dev/Source/Sentry/Private/Desktop/SentrySubsystemDesktop.cpp @@ -497,6 +497,21 @@ USentryTransaction* SentrySubsystemDesktop::StartTransactionWithContextAndOption return StartTransactionWithContext(context); } +USentryTransactionContext* SentrySubsystemDesktop::ContinueTrace(const FString& sentryTrace, const TArray& baggageHeaders) +{ + sentry_transaction_context_t* nativeTransactionContext = sentry_transaction_context_new("", "default"); + sentry_transaction_context_update_from_header(nativeTransactionContext, "sentry-trace", TCHAR_TO_ANSI(*sentryTrace)); + + // currently `sentry-native` doesn't have API for `sentry_transaction_context_t` to set `baggageHeaders` + + TSharedPtr transactionContextDesktop = MakeShareable(new SentryTransactionContextDesktop(nativeTransactionContext)); + + USentryTransactionContext* TransactionContext = NewObject(); + TransactionContext->InitWithNativeImpl(transactionContextDesktop); + + return TransactionContext; +} + USentryBeforeSendHandler* SentrySubsystemDesktop::GetBeforeSendHandler() { return beforeSend; diff --git a/plugin-dev/Source/Sentry/Private/Desktop/SentrySubsystemDesktop.h b/plugin-dev/Source/Sentry/Private/Desktop/SentrySubsystemDesktop.h index e05ba2cb..51c6bbd4 100644 --- a/plugin-dev/Source/Sentry/Private/Desktop/SentrySubsystemDesktop.h +++ b/plugin-dev/Source/Sentry/Private/Desktop/SentrySubsystemDesktop.h @@ -43,6 +43,7 @@ class SentrySubsystemDesktop : public ISentrySubsystem virtual USentryTransaction* StartTransaction(const FString& name, const FString& operation) override; virtual USentryTransaction* StartTransactionWithContext(USentryTransactionContext* context) override; virtual USentryTransaction* StartTransactionWithContextAndOptions(USentryTransactionContext* context, const TMap& options) override; + virtual USentryTransactionContext* ContinueTrace(const FString& sentryTrace, const TArray& baggageHeaders) override; USentryBeforeSendHandler* GetBeforeSendHandler(); diff --git a/plugin-dev/Source/Sentry/Private/Desktop/SentryTransactionDesktop.cpp b/plugin-dev/Source/Sentry/Private/Desktop/SentryTransactionDesktop.cpp index aceef7ab..4168864a 100644 --- a/plugin-dev/Source/Sentry/Private/Desktop/SentryTransactionDesktop.cpp +++ b/plugin-dev/Source/Sentry/Private/Desktop/SentryTransactionDesktop.cpp @@ -9,6 +9,12 @@ #if USE_SENTRY_NATIVE +void CopyTransactionTracingHeader(const char *key, const char *value, void *userdata) +{ + sentry_value_t *header = static_cast(userdata); + sentry_value_set_by_key(*header, key, sentry_value_new_string(value)); +} + SentryTransactionDesktop::SentryTransactionDesktop(sentry_transaction_t* transaction) : TransactionDesktop(transaction) , isFinished(false) @@ -77,4 +83,16 @@ void SentryTransactionDesktop::RemoveData(const FString& key) sentry_transaction_remove_data(TransactionDesktop, TCHAR_TO_ANSI(*key)); } +void SentryTransactionDesktop::GetTrace(FString& name, FString& value) +{ + sentry_value_t tracingHeader = sentry_value_new_object(); + + sentry_transaction_iter_headers(TransactionDesktop, CopyTransactionTracingHeader, &tracingHeader); + + name = TEXT("sentry-trace"); + value = FString(sentry_value_as_string(sentry_value_get_by_key(tracingHeader, "sentry-trace"))); + + sentry_value_decref(tracingHeader); +} + #endif diff --git a/plugin-dev/Source/Sentry/Private/Desktop/SentryTransactionDesktop.h b/plugin-dev/Source/Sentry/Private/Desktop/SentryTransactionDesktop.h index 5a6f59ea..a1bafef9 100644 --- a/plugin-dev/Source/Sentry/Private/Desktop/SentryTransactionDesktop.h +++ b/plugin-dev/Source/Sentry/Private/Desktop/SentryTransactionDesktop.h @@ -26,6 +26,7 @@ class SentryTransactionDesktop : public ISentryTransaction virtual void RemoveTag(const FString& key) override; virtual void SetData(const FString& key, const TMap& values) override; virtual void RemoveData(const FString& key) override; + virtual void GetTrace(FString& name, FString& value) override; private: sentry_transaction_t* TransactionDesktop; diff --git a/plugin-dev/Source/Sentry/Private/Interface/SentrySpanInterface.h b/plugin-dev/Source/Sentry/Private/Interface/SentrySpanInterface.h index 08ec7a1c..a233d905 100644 --- a/plugin-dev/Source/Sentry/Private/Interface/SentrySpanInterface.h +++ b/plugin-dev/Source/Sentry/Private/Interface/SentrySpanInterface.h @@ -15,4 +15,5 @@ class ISentrySpan virtual void RemoveTag(const FString& key) = 0; virtual void SetData(const FString& key, const TMap& values) = 0; virtual void RemoveData(const FString& key) = 0; + virtual void GetTrace(FString& name, FString& value) = 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 89b1574a..a64641f5 100644 --- a/plugin-dev/Source/Sentry/Private/Interface/SentrySubsystemInterface.h +++ b/plugin-dev/Source/Sentry/Private/Interface/SentrySubsystemInterface.h @@ -50,4 +50,5 @@ class ISentrySubsystem virtual USentryTransaction* StartTransaction(const FString& name, const FString& operation) = 0; virtual USentryTransaction* StartTransactionWithContext(USentryTransactionContext* context) = 0; virtual USentryTransaction* StartTransactionWithContextAndOptions(USentryTransactionContext* context, const TMap& options) = 0; + virtual USentryTransactionContext* ContinueTrace(const FString& sentryTrace, const TArray& baggageHeaders) = 0; }; \ No newline at end of file diff --git a/plugin-dev/Source/Sentry/Private/Interface/SentryTransactionInterface.h b/plugin-dev/Source/Sentry/Private/Interface/SentryTransactionInterface.h index 1cdbff55..fd57e194 100644 --- a/plugin-dev/Source/Sentry/Private/Interface/SentryTransactionInterface.h +++ b/plugin-dev/Source/Sentry/Private/Interface/SentryTransactionInterface.h @@ -19,4 +19,5 @@ class ISentryTransaction virtual void RemoveTag(const FString& key) = 0; virtual void SetData(const FString& key, const TMap& values) = 0; virtual void RemoveData(const FString& key) = 0; + virtual void GetTrace(FString& name, FString& value) = 0; }; \ No newline at end of file diff --git a/plugin-dev/Source/Sentry/Private/SentrySpan.cpp b/plugin-dev/Source/Sentry/Private/SentrySpan.cpp index 7b8c6000..e09ef5e5 100644 --- a/plugin-dev/Source/Sentry/Private/SentrySpan.cpp +++ b/plugin-dev/Source/Sentry/Private/SentrySpan.cpp @@ -64,6 +64,14 @@ void USentrySpan::RemoveData(const FString& key) SentrySpanNativeImpl->RemoveData(key); } +void USentrySpan::GetTrace(FString& name, FString& value) +{ + if (!SentrySpanNativeImpl || SentrySpanNativeImpl->IsFinished()) + return; + + SentrySpanNativeImpl->GetTrace(name, value); +} + void USentrySpan::InitWithNativeImpl(TSharedPtr spanImpl) { SentrySpanNativeImpl = spanImpl; diff --git a/plugin-dev/Source/Sentry/Private/SentrySubsystem.cpp b/plugin-dev/Source/Sentry/Private/SentrySubsystem.cpp index 42af22f6..ba51d875 100644 --- a/plugin-dev/Source/Sentry/Private/SentrySubsystem.cpp +++ b/plugin-dev/Source/Sentry/Private/SentrySubsystem.cpp @@ -392,6 +392,14 @@ USentryTransaction* USentrySubsystem::StartTransactionWithContextAndOptions(USen return SubsystemNativeImpl->StartTransactionWithContextAndOptions(Context, Options); } +USentryTransactionContext* USentrySubsystem::ContinueTrace(const FString& SentryTrace, const TArray& BaggageHeaders) +{ + if (!SubsystemNativeImpl || !SubsystemNativeImpl->IsEnabled()) + return nullptr; + + return SubsystemNativeImpl->ContinueTrace(SentryTrace, BaggageHeaders); +} + bool USentrySubsystem::IsSupportedForCurrentSettings() { if(!IsCurrentBuildConfigurationEnabled() || !IsCurrentBuildTargetEnabled() || !IsCurrentPlatformEnabled()) diff --git a/plugin-dev/Source/Sentry/Private/SentryTransaction.cpp b/plugin-dev/Source/Sentry/Private/SentryTransaction.cpp index 42159ac1..fe8b2e9c 100644 --- a/plugin-dev/Source/Sentry/Private/SentryTransaction.cpp +++ b/plugin-dev/Source/Sentry/Private/SentryTransaction.cpp @@ -81,6 +81,14 @@ void USentryTransaction::RemoveData(const FString& key) SentryTransactionNativeImpl->RemoveData(key); } +void USentryTransaction::GetTrace(FString& name, FString& value) +{ + if (!SentryTransactionNativeImpl || SentryTransactionNativeImpl->IsFinished()) + return; + + SentryTransactionNativeImpl->GetTrace(name, value); +} + void USentryTransaction::InitWithNativeImpl(TSharedPtr transactionImpl) { SentryTransactionNativeImpl = transactionImpl; diff --git a/plugin-dev/Source/Sentry/Private/Tests/SentrySubsystem.spec.cpp b/plugin-dev/Source/Sentry/Private/Tests/SentrySubsystem.spec.cpp index a13ac8c4..9c2c8cc5 100644 --- a/plugin-dev/Source/Sentry/Private/Tests/SentrySubsystem.spec.cpp +++ b/plugin-dev/Source/Sentry/Private/Tests/SentrySubsystem.spec.cpp @@ -100,6 +100,31 @@ void SentrySubsystemSpec::Define() }); }); + Describe("Transaction context", [this]() + { + It("should be created from trace and used to start a transaction", [this]() + { + const FString inTrace = FString(TEXT("2674eb52d5874b13b560236d6c79ce8a-a0f9fdf04f1a63df-1")); + + USentryTransactionContext* transactionContext = SentrySubsystem->ContinueTrace(inTrace, TArray()); + TestNotNull("Transaction context non-null", transactionContext); + + USentryTransaction* transaction = SentrySubsystem->StartTransactionWithContext(transactionContext); + TestNotNull("Transaction is non-null", transaction); + + FString outTraceKey; + FString outTraceValue; + transaction->GetTrace(outTraceKey, outTraceValue); + + TArray outTraceParts; + outTraceValue.ParseIntoArray(outTraceParts, TEXT("-")); + TestTrue("Trace has valid amount of parts", outTraceParts.Num() >= 2); + + TestEqual("Trace header", outTraceKey, TEXT("sentry-trace")); + TestEqual("Trace ID", outTraceParts[0], TEXT("2674eb52d5874b13b560236d6c79ce8a")); + }); + }); + AfterEach([this] { SentrySubsystem->Close(); diff --git a/plugin-dev/Source/Sentry/Public/SentrySpan.h b/plugin-dev/Source/Sentry/Public/SentrySpan.h index 5347a109..1aa4dbd0 100644 --- a/plugin-dev/Source/Sentry/Public/SentrySpan.h +++ b/plugin-dev/Source/Sentry/Public/SentrySpan.h @@ -43,6 +43,10 @@ class SENTRY_API USentrySpan : public UObject UFUNCTION(BlueprintCallable, Category = "Sentry") void RemoveData(const FString& key); + /** Gets trace information that could be sent as a `sentry-trace` header */ + UFUNCTION(BlueprintCallable, Category = "Sentry") + void GetTrace(FString& name, FString& value); + void InitWithNativeImpl(TSharedPtr spanImpl); TSharedPtr GetNativeImpl(); diff --git a/plugin-dev/Source/Sentry/Public/SentrySubsystem.h b/plugin-dev/Source/Sentry/Public/SentrySubsystem.h index ceb21d6f..7840c36a 100644 --- a/plugin-dev/Source/Sentry/Public/SentrySubsystem.h +++ b/plugin-dev/Source/Sentry/Public/SentrySubsystem.h @@ -269,6 +269,16 @@ class SENTRY_API USentrySubsystem : public UEngineSubsystem UFUNCTION(BlueprintCallable, Category = "Sentry") USentryTransaction* StartTransactionWithContextAndOptions(USentryTransactionContext* Context, const TMap& Options); + /** + * Creates a transaction context to propagate distributed tracing metadata from upstream + * services and continue a trace based on corresponding HTTP header values. + * + * @param SentryTrace Incoming request 'sentry-trace' header + * @param BaggageHeaders Incoming request 'baggage' headers + */ + UFUNCTION(BlueprintCallable, Category = "Sentry", meta = (AutoCreateRefTerm = "BaggageHeaders")) + USentryTransactionContext* ContinueTrace(const FString& SentryTrace, const TArray& BaggageHeaders); + /** Checks if Sentry event capturing is supported for current settings. */ UFUNCTION(BlueprintCallable, Category = "Sentry") bool IsSupportedForCurrentSettings(); diff --git a/plugin-dev/Source/Sentry/Public/SentryTransaction.h b/plugin-dev/Source/Sentry/Public/SentryTransaction.h index f17aab57..e90ca02d 100644 --- a/plugin-dev/Source/Sentry/Public/SentryTransaction.h +++ b/plugin-dev/Source/Sentry/Public/SentryTransaction.h @@ -52,6 +52,10 @@ class SENTRY_API USentryTransaction : public UObject UFUNCTION(BlueprintCallable, Category = "Sentry") void RemoveData(const FString& key); + /** Gets trace information that could be sent as a `sentry-trace` header */ + UFUNCTION(BlueprintCallable, Category = "Sentry") + void GetTrace(FString& name, FString& value); + void InitWithNativeImpl(TSharedPtr transactionImpl); TSharedPtr GetNativeImpl();