diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8c0d693..708c6af 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,7 +20,7 @@ jobs: with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} + password: ${{ secrets.PAT_TOKEN }} - name: Start Docker container run: | @@ -28,6 +28,10 @@ jobs: --name unreal ` --volume "${{ github.workspace }}:C:\workspace" ` --workdir C:\workspace ` + --env SENTRY_DSN="${{ secrets.SENTRY_DSN }}" ` + --env SENTRY_ORG="${{ secrets.SENTRY_ORG }}" ` + --env SENTRY_PROJECT="${{ secrets.SENTRY_PROJECT }}" ` + --env SENTRY_AUTH_TOKEN="${{ secrets.SENTRY_AUTH_TOKEN }}" ` ghcr.io/getsentry/unreal-docker:${{ env.UE_VERSION }} - uses: actions/checkout@v4 diff --git a/.github/workflows/run-demo.yml b/.github/workflows/run-demo.yml index f2c85b7..e6ebad9 100644 --- a/.github/workflows/run-demo.yml +++ b/.github/workflows/run-demo.yml @@ -1,8 +1,8 @@ name: run-demo on: - # schedule: - # - cron: '0 */2 * * *' # every two hours + schedule: + - cron: '*/50 * * * *' # every 50 minutes workflow_dispatch: jobs: @@ -28,6 +28,8 @@ jobs: run-id: ${{ steps.get-run.outputs.run-id }} - name: Run Simulation + env: + SENTRY_DSN: ${{ secrets.SENTRY_DSN }} run: | $downloadPath = "${{ steps.download.outputs.download-path }}" Write-Output "Download path: $downloadPath" diff --git a/.gitignore b/.gitignore index 8e3d2ef..9b75621 100644 --- a/.gitignore +++ b/.gitignore @@ -21,7 +21,6 @@ # Compiled Dynamic libraries *.so -*.dylib *.dll # Fortran module files diff --git a/.ignore b/.ignore index ede1541..361b7b6 100644 --- a/.ignore +++ b/.ignore @@ -10,4 +10,3 @@ /Plugins/Sentry/Gradle /Plugins/Sentry/Scripts /Plugins/Sentry/Intermediate -/*.code-workspace \ No newline at end of file diff --git a/Config/DefaultEngine.ini b/Config/DefaultEngine.ini index 07b0255..dc87885 100644 --- a/Config/DefaultEngine.ini +++ b/Config/DefaultEngine.ini @@ -285,6 +285,7 @@ KeyPassword=android +CollisionChannelRedirects=(OldName="PawnMovement",NewName="Pawn") [/Script/Sentry.SentrySettings] +InitAutomatically=False EnableAutoLogAttachment=True SendDefaultPii=True AttachScreenshot=True @@ -293,7 +294,7 @@ IncludeSources=True AutomaticBreadcrumbsForLogs=(bOnFatalLog=True,bOnErrorLog=False,bOnWarningLog=False,bOnInfoLog=False,bOnDebugLog=False) EnableTracing=True TracesSampleRate=1.000000 -Dsn="https://f1818b87d128882c28e8a876858d2d1e@o447951.ingest.us.sentry.io/4508816831873030" +Dsn="https://694ee0c1bc90596005001e4acef50cd7@o87286.ingest.us.sentry.io/4509724070903808" [/Script/MacTargetPlatform.MacTargetSettings] -TargetedRHIs=SF_METAL_SM5 diff --git a/Plugins/Sentry/Source/Sentry/Private/GenericPlatform/GenericPlatformSentrySubsystem.cpp b/Plugins/Sentry/Source/Sentry/Private/GenericPlatform/GenericPlatformSentrySubsystem.cpp index a4cf4d7..0795226 100644 --- a/Plugins/Sentry/Source/Sentry/Private/GenericPlatform/GenericPlatformSentrySubsystem.cpp +++ b/Plugins/Sentry/Source/Sentry/Private/GenericPlatform/GenericPlatformSentrySubsystem.cpp @@ -611,6 +611,9 @@ TSharedPtr FGenericPlatformSentrySubsystem::StartTransaction { if (sentry_transaction_t* nativeTransaction = sentry_transaction_start(platformTransactionContext->GetNativeObject(), sentry_value_new_null())) { + // TODO: Replace this hack with a proper transaction binding to the current scope + sentry_set_transaction_object(nativeTransaction); + return MakeShareable(new FGenericPlatformSentryTransaction(nativeTransaction)); } } diff --git a/Plugins/Sentry/Source/Sentry/Private/GenericPlatform/GenericPlatformSentryTransaction.cpp b/Plugins/Sentry/Source/Sentry/Private/GenericPlatform/GenericPlatformSentryTransaction.cpp index 917c377..8646207 100644 --- a/Plugins/Sentry/Source/Sentry/Private/GenericPlatform/GenericPlatformSentryTransaction.cpp +++ b/Plugins/Sentry/Source/Sentry/Private/GenericPlatform/GenericPlatformSentryTransaction.cpp @@ -28,6 +28,8 @@ TSharedPtr FGenericPlatformSentryTransaction::StartChildSpan(const { if (sentry_span_t* nativeSpan = sentry_transaction_start_child(Transaction, TCHAR_TO_ANSI(*operation), TCHAR_TO_ANSI(*description))) { + // TODO: Replace this hack with a proper span binding to the current scope + sentry_set_span(nativeSpan); return MakeShareable(new FGenericPlatformSentrySpan(nativeSpan)); } else diff --git a/Plugins/Sentry/Source/ThirdParty/Mac/bin/sentry.dylib b/Plugins/Sentry/Source/ThirdParty/Mac/bin/sentry.dylib new file mode 100644 index 0000000..ea1a41d Binary files /dev/null and b/Plugins/Sentry/Source/ThirdParty/Mac/bin/sentry.dylib differ diff --git a/Source/SentryTower/SentryTowerGameInstance.cpp b/Source/SentryTower/SentryTowerGameInstance.cpp index c12affe..55d78ee 100644 --- a/Source/SentryTower/SentryTowerGameInstance.cpp +++ b/Source/SentryTower/SentryTowerGameInstance.cpp @@ -4,22 +4,45 @@ #include "HttpModule.h" #include "SentryLibrary.h" +#include "SentrySettings.h" #include "SentrySpan.h" #include "SentrySubsystem.h" #include "SentryTransaction.h" +#include "SentryTransactionContext.h" #include "Interfaces/IHttpResponse.h" void USentryTowerGameInstance::Init() { Super::Init(); + // Initialize Sentry with environment variable override for DSN + USentrySubsystem* SentrySubsystem = GEngine->GetEngineSubsystem(); + if (SentrySubsystem) + { + FString EnvironmentDsn = FPlatformMisc::GetEnvironmentVariable(TEXT("SENTRY_DSN")); + if (!EnvironmentDsn.IsEmpty()) + { + // Override DSN with environment variable + UE_LOG(LogTemp, Log, TEXT("Using SENTRY_DSN environment variable")); + SentrySubsystem->InitializeWithSettings(FConfigureSettingsNativeDelegate::CreateLambda([EnvironmentDsn](USentrySettings* Settings) + { + Settings->Dsn = EnvironmentDsn; + })); + } + else + { + // Use default settings + UE_LOG(LogTemp, Log, TEXT("SENTRY_DSN environment variable not set, using default settings")); + SentrySubsystem->Initialize(); + } + } + if (FParse::Param(FCommandLine::Get(), TEXT("NullRHI"))) { // For CI simulation (no RHI available) copy pre-made screenshot to dest where Unreal SDK can pick it up during crash handling const FString FakeScreenshotPath = FPaths::Combine(FPaths::ProjectContentDir(), TEXT("Resources"), TEXT("screenshot.png")); - // Get the Sentry subsystem - USentrySubsystem* SentrySubsystem = GEngine->GetEngineSubsystem(); + // Add screenshot attachment to Sentry if (SentrySubsystem) { // Create the attachment @@ -46,9 +69,7 @@ void USentryTowerGameInstance::BuyUpgrade(const FOnBuyComplete& OnBuyComplete) USentrySpan* ProcessSpan = CheckoutTransaction->StartChildSpan(TEXT("task"), TEXT("process_upgrade_data")); - TSharedPtr UpgradeDataJsonObject = MakeShareable(new FJsonObject()); - UpgradeDataJsonObject->SetStringField(TEXT("UpgradeName"), TEXT("NewTower")); - UpgradeDataJsonObject->SetStringField(TEXT("PlayerEmail"), TEXT("player@sentry-tower.com")); + TSharedPtr UpgradeDataJsonObject = BuildCheckoutRequestJson(); FString JsonString; TSharedRef> Writer = TJsonWriterFactory<>::Create(&JsonString); @@ -61,7 +82,8 @@ void USentryTowerGameInstance::BuyUpgrade(const FOnBuyComplete& OnBuyComplete) ProcessSpan->Finish(); - FString Domain = TEXT("https://aspnetcore.empower-plant.com"); + USentrySpan* CheckoutSpan = CheckoutTransaction->StartChildSpan(TEXT("task"), TEXT("checkout_request")); + FString Domain = TEXT("https://flask.empower-plant.com"); FString Endpoint = TEXT("/checkout"); FString CheckoutURL = Domain + Endpoint; @@ -72,10 +94,21 @@ void USentryTowerGameInstance::BuyUpgrade(const FOnBuyComplete& OnBuyComplete) HttpRequest->SetVerb("POST"); HttpRequest->SetHeader(TEXT("Content-Type"), TEXT("application/json")); + FString TraceKey; + FString TraceValue; + CheckoutSpan->GetTrace(TraceKey, TraceValue); + + UE_LOG(LogTemp, Log, TEXT("TraceValue - %s"), *TraceValue); + + HttpRequest->SetHeader(TraceKey, TraceValue); + HttpRequest->SetContentAsString(JsonString); HttpRequest->OnProcessRequestComplete().BindLambda([=](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful) { + CheckoutSpan->Finish(); + + USentrySpan* ResponseSpan = CheckoutTransaction->StartChildSpan(TEXT("task"), TEXT("process_checkout_response")); ensureMsgf(bWasSuccessful && Response.IsValid() && Response->GetResponseCode() == 200, TEXT("Checkout HTTP request failed")); if (bWasSuccessful && Response.IsValid() && Response->GetResponseCode() == 200) @@ -89,8 +122,56 @@ void USentryTowerGameInstance::BuyUpgrade(const FOnBuyComplete& OnBuyComplete) OnBuyComplete.ExecuteIfBound(false); } + ResponseSpan->Finish(); CheckoutTransaction->Finish(); }); HttpRequest->ProcessRequest(); } + +TSharedPtr USentryTowerGameInstance::BuildCheckoutRequestJson() +{ + TSharedPtr JsonObject = MakeShareable(new FJsonObject()); + + // Build cart object + TSharedPtr CartObject = MakeShareable(new FJsonObject()); + + // Build items array + TArray> ItemsArray; + TSharedPtr ItemObject = MakeShareable(new FJsonObject()); + ItemObject->SetStringField(TEXT("description"), TEXT("The mood ring for plants.")); + ItemObject->SetStringField(TEXT("descriptionfull"), TEXT("This is an example of what you can do with just a few things, a little imagination and a happy dream in your heart. I'm a water fanatic. I love water. There's not a thing in the world wrong with washing your brush. Everybody needs a friend. Here we're limited by the time we have.")); + ItemObject->SetNumberField(TEXT("id"), 3); + ItemObject->SetStringField(TEXT("img"), TEXT("https://storage.googleapis.com/application-monitoring/mood-planter.jpg")); + ItemObject->SetStringField(TEXT("imgcropped"), TEXT("https://storage.googleapis.com/application-monitoring/mood-planter-cropped.jpg")); + ItemObject->SetNumberField(TEXT("price"), 155); + ItemObject->SetArrayField(TEXT("reviews"), TArray>()); + ItemObject->SetStringField(TEXT("title"), TEXT("Plant Mood")); + ItemsArray.Add(MakeShareable(new FJsonValueObject(ItemObject))); + CartObject->SetArrayField(TEXT("items"), ItemsArray); + + // Build quantities object + TSharedPtr QuantitiesObject = MakeShareable(new FJsonObject()); + QuantitiesObject->SetNumberField(TEXT("3"), 3); + CartObject->SetObjectField(TEXT("quantities"), QuantitiesObject); + CartObject->SetNumberField(TEXT("total"), 465); + + // Build form object + TSharedPtr FormObject = MakeShareable(new FJsonObject()); + FormObject->SetStringField(TEXT("address"), TEXT("")); + FormObject->SetStringField(TEXT("city"), TEXT("")); + FormObject->SetStringField(TEXT("country"), TEXT("")); + FormObject->SetStringField(TEXT("email"), TEXT("sampleEmail@email.com")); + FormObject->SetStringField(TEXT("firstName"), TEXT("")); + FormObject->SetStringField(TEXT("lastName"), TEXT("")); + FormObject->SetStringField(TEXT("state"), TEXT("")); + FormObject->SetStringField(TEXT("subscribe"), TEXT("")); + FormObject->SetStringField(TEXT("zipCode"), TEXT("")); + + // Set all objects to main JSON + JsonObject->SetObjectField(TEXT("cart"), CartObject); + JsonObject->SetObjectField(TEXT("form"), FormObject); + JsonObject->SetStringField(TEXT("validate_inventory"), TEXT("true")); + + return JsonObject; +} diff --git a/Source/SentryTower/SentryTowerGameInstance.h b/Source/SentryTower/SentryTowerGameInstance.h index 99e3823..efd31b8 100644 --- a/Source/SentryTower/SentryTowerGameInstance.h +++ b/Source/SentryTower/SentryTowerGameInstance.h @@ -18,4 +18,7 @@ class SENTRYTOWER_API USentryTowerGameInstance : public UGameInstance UFUNCTION(BlueprintCallable, Meta = (AutoCreateRefTerm = "OnBuyComplete")) void BuyUpgrade(const FOnBuyComplete& OnBuyComplete); + +private: + TSharedPtr BuildCheckoutRequestJson(); };