From 78b69f0a0bfd5e1bbcc1b08c221b88509c6ce1eb Mon Sep 17 00:00:00 2001 From: Ivan Tustanivskyi Date: Fri, 6 Mar 2026 14:58:56 +0200 Subject: [PATCH 1/7] Add global attributes support for Android --- .../Sentry/Private/Android/AndroidSentrySubsystem.cpp | 10 ++++++++-- .../Sentry/Private/Android/Java/SentryBridgeJava.java | 8 ++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/plugin-dev/Source/Sentry/Private/Android/AndroidSentrySubsystem.cpp b/plugin-dev/Source/Sentry/Private/Android/AndroidSentrySubsystem.cpp index 6ee9392c9..82629d580 100644 --- a/plugin-dev/Source/Sentry/Private/Android/AndroidSentrySubsystem.cpp +++ b/plugin-dev/Source/Sentry/Private/Android/AndroidSentrySubsystem.cpp @@ -371,12 +371,18 @@ void FAndroidSentrySubsystem::RemoveTag(const FString& key) void FAndroidSentrySubsystem::SetAttribute(const FString& key, const FSentryVariant& value) { - // No-op: Android SDK doesn't support global log attributes + TSharedPtr nativeValue = FAndroidSentryConverters::VariantToNative(value); + if (nativeValue.IsValid()) + { + FSentryJavaObjectWrapper::CallStaticMethod(SentryJavaClasses::SentryBridgeJava, "setAttribute", "(Ljava/lang/String;Ljava/lang/Object;)V", + *FSentryJavaObjectWrapper::GetJString(key), nativeValue->GetJObject()); + } } void FAndroidSentrySubsystem::RemoveAttribute(const FString& key) { - // No-op: Android SDK doesn't support global log attributes + FSentryJavaObjectWrapper::CallStaticMethod(SentryJavaClasses::SentryBridgeJava, "removeAttribute", "(Ljava/lang/String;)V", + *FSentryJavaObjectWrapper::GetJString(key)); } void FAndroidSentrySubsystem::SetLevel(ESentryLevel level) diff --git a/plugin-dev/Source/Sentry/Private/Android/Java/SentryBridgeJava.java b/plugin-dev/Source/Sentry/Private/Android/Java/SentryBridgeJava.java index 7db54cc2d..1837e7361 100644 --- a/plugin-dev/Source/Sentry/Private/Android/Java/SentryBridgeJava.java +++ b/plugin-dev/Source/Sentry/Private/Android/Java/SentryBridgeJava.java @@ -210,6 +210,14 @@ public void run(@NonNull IScope scope) { }); } + public static void setAttribute(final String key, final Object value) { + Sentry.setAttribute(key, value); + } + + public static void removeAttribute(final String key) { + Sentry.removeAttribute(key); + } + public static void setLevel(final SentryLevel level) { Sentry.configureScope(new ScopeCallback() { @Override From 2b8a5364a303cc948c146e5ca0b3a0368cf4f40d Mon Sep 17 00:00:00 2001 From: Ivan Tustanivskyi Date: Fri, 6 Mar 2026 15:34:58 +0200 Subject: [PATCH 2/7] Add native array type support for attribute --- .../Private/Android/Java/SentryBridgeJava.java | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/plugin-dev/Source/Sentry/Private/Android/Java/SentryBridgeJava.java b/plugin-dev/Source/Sentry/Private/Android/Java/SentryBridgeJava.java index 1837e7361..297269c86 100644 --- a/plugin-dev/Source/Sentry/Private/Android/Java/SentryBridgeJava.java +++ b/plugin-dev/Source/Sentry/Private/Android/Java/SentryBridgeJava.java @@ -399,17 +399,13 @@ private static void setAttributeInternal(final String key, final Object value, A } else if (value instanceof Float) { // Unreal's variant doesn't support Double so manual conversion is required attributeValue = new SentryLogEventAttributeValue(SentryAttributeType.DOUBLE, ((Float) value).doubleValue()); + } else if (value instanceof java.util.Collection) { + attributeValue = new SentryLogEventAttributeValue(SentryAttributeType.ARRAY, value); + } else if (value instanceof java.util.Map) { + // Maps are not directly supported as attribute type - convert to JSON string + attributeValue = new SentryLogEventAttributeValue(SentryAttributeType.STRING, new JSONObject((java.util.Map) value).toString()); } else { - // Unsupported type (e.g. ArrayList, HashMap) - convert to JSON string for consistency with other platforms - String jsonString; - if (value instanceof java.util.List) { - jsonString = new JSONArray((java.util.List) value).toString(); - } else if (value instanceof java.util.Map) { - jsonString = new JSONObject((java.util.Map) value).toString(); - } else { - jsonString = value.toString(); - } - attributeValue = new SentryLogEventAttributeValue(SentryAttributeType.STRING, jsonString); + attributeValue = new SentryLogEventAttributeValue(SentryAttributeType.STRING, value.toString()); } setter.setAttribute(key, attributeValue); From 82c2244eeceaf3fb83759608e597c7d60af6f828 Mon Sep 17 00:00:00 2001 From: Ivan Tustanivskyi Date: Fri, 6 Mar 2026 15:35:26 +0200 Subject: [PATCH 3/7] Add global attributes integration tests for Android --- .../Integration.Android.Tests.ps1 | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/integration-test/Integration.Android.Tests.ps1 b/integration-test/Integration.Android.Tests.ps1 index e81e21cc6..d9009e58a 100644 --- a/integration-test/Integration.Android.Tests.ps1 +++ b/integration-test/Integration.Android.Tests.ps1 @@ -432,7 +432,7 @@ Describe 'Sentry Unreal Android Integration Tests ()' -ForEach $TestTa # Fetch logs from Sentry with automatic polling try { - $script:CapturedLogs = Get-SentryTestLog -AttributeName 'test_id' -AttributeValue $script:TestId -Fields @('handler_added', 'to_be_removed') + $script:CapturedLogs = Get-SentryTestLog -AttributeName 'test_id' -AttributeValue $script:TestId -Fields @('handler_added', 'to_be_removed', 'global_attr', 'global_removed') } catch { Write-Host "Warning: $_" -ForegroundColor Red @@ -483,8 +483,15 @@ Describe 'Sentry Unreal Android Integration Tests ()' -ForEach $TestTa $log.'to_be_removed' | Should -BeNullOrEmpty } - # Note: Global log attributes (SetAttribute/RemoveAttribute on subsystem) are not supported - # on Android (sentry-java) - the implementation is a no-op. These are tested in desktop tests only. + It "Should have global attribute set on subsystem" { + $log = $script:CapturedLogs[0] + $log.global_attr | Should -Be 'global_value' + } + + It "Should not have global attribute that was removed from subsystem" { + $log = $script:CapturedLogs[0] + $log.global_removed | Should -BeNullOrEmpty + } } Context "Metrics Capture Tests" { @@ -502,7 +509,7 @@ Describe 'Sentry Unreal Android Integration Tests ()' -ForEach $TestTa Write-Host "Captured Test ID: $($script:TestId)" -ForegroundColor Cyan # Fetch all three metric types from Sentry with automatic polling - $metricFields = @('handler_added', 'to_be_removed') + $metricFields = @('handler_added', 'to_be_removed', 'global_attr', 'global_removed') try { $script:CapturedCounterMetrics = Get-SentryTestMetric -MetricName 'test.integration.counter' -AttributeName 'test_id' -AttributeValue $script:TestId -Fields $metricFields @@ -603,6 +610,16 @@ Describe 'Sentry Unreal Android Integration Tests ()' -ForEach $TestTa $metric = $script:CapturedCounterMetrics[0] $metric.test_id | Should -Be $script:TestId } + + It "Should have global attribute set on subsystem" { + $metric = $script:CapturedCounterMetrics[0] + $metric.global_attr | Should -Be 'global_value' + } + + It "Should not have global attribute that was removed from subsystem" { + $metric = $script:CapturedCounterMetrics[0] + $metric.global_removed | Should -BeNullOrEmpty + } } Context "Tracing Capture Tests" { From b1c7e1ca00eb81f8000c585baee056672d335ece Mon Sep 17 00:00:00 2001 From: Ivan Tustanivskyi Date: Fri, 6 Mar 2026 15:35:48 +0200 Subject: [PATCH 4/7] Add global attributes integration tests for desktop --- integration-test/Integration.Desktop.Tests.ps1 | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/integration-test/Integration.Desktop.Tests.ps1 b/integration-test/Integration.Desktop.Tests.ps1 index d787b9ba7..15ae9ca8b 100644 --- a/integration-test/Integration.Desktop.Tests.ps1 +++ b/integration-test/Integration.Desktop.Tests.ps1 @@ -530,7 +530,7 @@ Describe "Sentry Unreal Desktop Integration Tests ()" -ForEach $TestTa Write-Host "Captured Test ID: $($script:TestId)" -ForegroundColor Cyan # Fetch all three metric types from Sentry with automatic polling - $metricFields = @('handler_added', 'to_be_removed') + $metricFields = @('handler_added', 'to_be_removed', 'global_attr', 'global_removed') try { $script:CapturedCounterMetrics = Get-SentryTestMetric -MetricName 'test.integration.counter' -AttributeName 'test_id' -AttributeValue $script:TestId -Fields $metricFields @@ -635,6 +635,16 @@ Describe "Sentry Unreal Desktop Integration Tests ()" -ForEach $TestTa $metric = $script:CapturedCounterMetrics[0] $metric.test_id | Should -Be $script:TestId } + + It "Should have global attribute set on subsystem" { + $metric = $script:CapturedCounterMetrics[0] + $metric.global_attr | Should -Be 'global_value' + } + + It "Should not have global attribute that was removed from subsystem" { + $metric = $script:CapturedCounterMetrics[0] + $metric.global_removed | Should -BeNullOrEmpty + } } Context "Tracing Capture Tests" { From afb368f3acc48670b41c28d304ce3e2d3a12889f Mon Sep 17 00:00:00 2001 From: Ivan Tustanivskyi Date: Fri, 6 Mar 2026 15:35:56 +0200 Subject: [PATCH 5/7] Update sample app --- .../Source/SentryPlayground/SentryPlaygroundGameInstance.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sample/Source/SentryPlayground/SentryPlaygroundGameInstance.cpp b/sample/Source/SentryPlayground/SentryPlaygroundGameInstance.cpp index 3d473dbf0..7f9db131b 100644 --- a/sample/Source/SentryPlayground/SentryPlaygroundGameInstance.cpp +++ b/sample/Source/SentryPlayground/SentryPlaygroundGameInstance.cpp @@ -214,6 +214,11 @@ void USentryPlaygroundGameInstance::RunMetricTest() FString TestId = FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); + SentrySubsystem->SetAttribute(TEXT("global_attr"), FSentryVariant(TEXT("global_value"))); + + SentrySubsystem->SetAttribute(TEXT("global_removed"), FSentryVariant(TEXT("should_not_appear"))); + SentrySubsystem->RemoveAttribute(TEXT("global_removed")); + TMap CounterAttributes; CounterAttributes.Add(TEXT("test_id"), FSentryVariant(TestId)); CounterAttributes.Add(TEXT("to_be_removed"), FSentryVariant(TEXT("original_value"))); From da110b8b027e24d5755324f7e993d5ef0694b3b1 Mon Sep 17 00:00:00 2001 From: Ivan Tustanivskyi Date: Fri, 6 Mar 2026 15:53:02 +0200 Subject: [PATCH 6/7] Update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 287a132e5..4d63a21ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Features + +- Add global attributes support for Android ([#1274](https://github.com/getsentry/sentry-unreal/pull/1274)) + ### Fixes - Fix debug symbol upload for build products located in the engine directory ([#1262](https://github.com/getsentry/sentry-unreal/pull/1262)) From 1e4e34d7a02899d1f39f9558663d8ab414ebf7dd Mon Sep 17 00:00:00 2001 From: Ivan Tustanivskyi Date: Fri, 6 Mar 2026 16:02:35 +0200 Subject: [PATCH 7/7] Rever array type change --- .../Private/Android/Java/SentryBridgeJava.java | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/plugin-dev/Source/Sentry/Private/Android/Java/SentryBridgeJava.java b/plugin-dev/Source/Sentry/Private/Android/Java/SentryBridgeJava.java index 297269c86..1837e7361 100644 --- a/plugin-dev/Source/Sentry/Private/Android/Java/SentryBridgeJava.java +++ b/plugin-dev/Source/Sentry/Private/Android/Java/SentryBridgeJava.java @@ -399,13 +399,17 @@ private static void setAttributeInternal(final String key, final Object value, A } else if (value instanceof Float) { // Unreal's variant doesn't support Double so manual conversion is required attributeValue = new SentryLogEventAttributeValue(SentryAttributeType.DOUBLE, ((Float) value).doubleValue()); - } else if (value instanceof java.util.Collection) { - attributeValue = new SentryLogEventAttributeValue(SentryAttributeType.ARRAY, value); - } else if (value instanceof java.util.Map) { - // Maps are not directly supported as attribute type - convert to JSON string - attributeValue = new SentryLogEventAttributeValue(SentryAttributeType.STRING, new JSONObject((java.util.Map) value).toString()); } else { - attributeValue = new SentryLogEventAttributeValue(SentryAttributeType.STRING, value.toString()); + // Unsupported type (e.g. ArrayList, HashMap) - convert to JSON string for consistency with other platforms + String jsonString; + if (value instanceof java.util.List) { + jsonString = new JSONArray((java.util.List) value).toString(); + } else if (value instanceof java.util.Map) { + jsonString = new JSONObject((java.util.Map) value).toString(); + } else { + jsonString = value.toString(); + } + attributeValue = new SentryLogEventAttributeValue(SentryAttributeType.STRING, jsonString); } setter.setAttribute(key, attributeValue);