diff --git a/CHANGELOG.md b/CHANGELOG.md index fb4de5057c6..4353044f84e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ ### Features +- Add scope-level attributes API ([#5118](https://github.com/getsentry/sentry-java/pull/5118)) via ([#5148](https://github.com/getsentry/sentry-java/pull/5148)) + - Automatically include scope attributes in logs and metrics ([#5120](https://github.com/getsentry/sentry-java/pull/5120)) + - New APIs are `Sentry.setAttribute`, `Sentry.setAttributes`, `Sentry.removeAttribute` +- Support collections and arrays in attribute type inference ([#5124](https://github.com/getsentry/sentry-java/pull/5124)) - Add support for `SENTRY_SAMPLE_RATE` environment variable / `sample-rate` property ([#5112](https://github.com/getsentry/sentry-java/pull/5112)) - Create `sentry-opentelemetry-otlp` and `sentry-opentelemetry-otlp-spring` modules for combining OpenTelemetry SDK OTLP export with Sentry SDK ([#5100](https://github.com/getsentry/sentry-java/pull/5100)) - OpenTelemetry is configured to send spans to Sentry directly using an OTLP endpoint. @@ -32,6 +36,7 @@ ### Fixes +- Fix attribute type detection for `Long`, `Short`, `Byte`, `BigInteger`, `AtomicInteger`, and `AtomicLong` being incorrectly inferred as `double` instead of `integer` ([#5122](https://github.com/getsentry/sentry-java/pull/5122)) - Remove `AndroidRuntimeManager` StrictMode relaxation to prevent ANRs during SDK init ([#5127](https://github.com/getsentry/sentry-java/pull/5127)) - **IMPORTANT:** StrictMode violations may appear again in debug builds. This is intentional to prevent ANRs in production releases. - Fix crash when unregistering `SystemEventsBroadcastReceiver` with try-catch block. ([#5106](https://github.com/getsentry/sentry-java/pull/5106)) diff --git a/sentry-samples/sentry-samples-spring-boot-4/src/main/java/io/sentry/samples/spring/boot4/MetricController.java b/sentry-samples/sentry-samples-spring-boot-4/src/main/java/io/sentry/samples/spring/boot4/MetricController.java index 2a969ec8849..be75f5e3002 100644 --- a/sentry-samples/sentry-samples-spring-boot-4/src/main/java/io/sentry/samples/spring/boot4/MetricController.java +++ b/sentry-samples/sentry-samples-spring-boot-4/src/main/java/io/sentry/samples/spring/boot4/MetricController.java @@ -16,6 +16,8 @@ public class MetricController { @GetMapping("count") String count() { + Sentry.setAttribute("user.type", "admin"); + Sentry.setAttribute("feature.version", 2); Sentry.metrics().count("countMetric"); return "count metric increased"; } diff --git a/sentry-samples/sentry-samples-spring-boot-4/src/main/java/io/sentry/samples/spring/boot4/PersonController.java b/sentry-samples/sentry-samples-spring-boot-4/src/main/java/io/sentry/samples/spring/boot4/PersonController.java index c65c9040d5c..489dc629d28 100644 --- a/sentry-samples/sentry-samples-spring-boot-4/src/main/java/io/sentry/samples/spring/boot4/PersonController.java +++ b/sentry-samples/sentry-samples-spring-boot-4/src/main/java/io/sentry/samples/spring/boot4/PersonController.java @@ -27,6 +27,10 @@ Person person(@PathVariable Long id) { ISpan currentSpan = Sentry.getSpan(); ISpan sentrySpan = currentSpan.startChild("spanCreatedThroughSentryApi"); try { + Sentry.setAttribute("user.type", "admin"); + Sentry.setAttribute("feature.version", 2); + Sentry.setAttribute("debug.enabled", true); + Sentry.logger().warn("warn Sentry logging"); Sentry.logger().error("error Sentry logging"); Sentry.logger().info("hello %s %s", "there", "world!"); diff --git a/sentry-samples/sentry-samples-spring-boot-4/src/test/kotlin/io/sentry/systemtest/MetricsSystemTest.kt b/sentry-samples/sentry-samples-spring-boot-4/src/test/kotlin/io/sentry/systemtest/MetricsSystemTest.kt index dc2ca2a10ae..039d9d640c7 100644 --- a/sentry-samples/sentry-samples-spring-boot-4/src/test/kotlin/io/sentry/systemtest/MetricsSystemTest.kt +++ b/sentry-samples/sentry-samples-spring-boot-4/src/test/kotlin/io/sentry/systemtest/MetricsSystemTest.kt @@ -21,7 +21,9 @@ class MetricsSystemTest { assertEquals(200, restClient.lastKnownStatusCode) testHelper.ensureMetricsReceived { event, header -> - testHelper.doesContainMetric(event, "countMetric", "counter", 1.0) + testHelper.doesContainMetric(event, "countMetric", "counter", 1.0) && + testHelper.doesMetricHaveAttribute(event, "countMetric", "user.type", "admin") && + testHelper.doesMetricHaveAttribute(event, "countMetric", "feature.version", 2) } } diff --git a/sentry-samples/sentry-samples-spring-boot-4/src/test/kotlin/io/sentry/systemtest/PersonSystemTest.kt b/sentry-samples/sentry-samples-spring-boot-4/src/test/kotlin/io/sentry/systemtest/PersonSystemTest.kt index 362a8577148..2389734a8b3 100644 --- a/sentry-samples/sentry-samples-spring-boot-4/src/test/kotlin/io/sentry/systemtest/PersonSystemTest.kt +++ b/sentry-samples/sentry-samples-spring-boot-4/src/test/kotlin/io/sentry/systemtest/PersonSystemTest.kt @@ -50,7 +50,20 @@ class PersonSystemTest { testHelper.ensureLogsReceived { logs, envelopeHeader -> testHelper.doesContainLogWithBody(logs, "warn Sentry logging") && testHelper.doesContainLogWithBody(logs, "error Sentry logging") && - testHelper.doesContainLogWithBody(logs, "hello there world!") + testHelper.doesContainLogWithBody(logs, "hello there world!") && + testHelper.doesLogWithBodyHaveAttribute( + logs, + "warn Sentry logging", + "user.type", + "admin", + ) && + testHelper.doesLogWithBodyHaveAttribute( + logs, + "warn Sentry logging", + "feature.version", + 2, + ) && + testHelper.doesLogWithBodyHaveAttribute(logs, "warn Sentry logging", "debug.enabled", true) } } diff --git a/sentry-system-test-support/api/sentry-system-test-support.api b/sentry-system-test-support/api/sentry-system-test-support.api index ff620c7d809..51ef7da55d9 100644 --- a/sentry-system-test-support/api/sentry-system-test-support.api +++ b/sentry-system-test-support/api/sentry-system-test-support.api @@ -574,6 +574,8 @@ public final class io/sentry/systemtest/util/TestHelper { public static synthetic fun doesContainMetric$default (Lio/sentry/systemtest/util/TestHelper;Lio/sentry/SentryMetricsEvents;Ljava/lang/String;Ljava/lang/String;DLjava/lang/String;ILjava/lang/Object;)Z public final fun doesEventHaveExceptionMessage (Lio/sentry/SentryEvent;Ljava/lang/String;)Z public final fun doesEventHaveFlag (Lio/sentry/SentryEvent;Ljava/lang/String;Z)Z + public final fun doesLogWithBodyHaveAttribute (Lio/sentry/SentryLogEvents;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Object;)Z + public final fun doesMetricHaveAttribute (Lio/sentry/SentryMetricsEvents;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Object;)Z public final fun doesTransactionContainSpanWithDescription (Lio/sentry/protocol/SentryTransaction;Ljava/lang/String;)Z public final fun doesTransactionContainSpanWithOp (Lio/sentry/protocol/SentryTransaction;Ljava/lang/String;)Z public final fun doesTransactionContainSpanWithOpAndDescription (Lio/sentry/protocol/SentryTransaction;Ljava/lang/String;Ljava/lang/String;)Z diff --git a/sentry-system-test-support/src/main/kotlin/io/sentry/systemtest/util/TestHelper.kt b/sentry-system-test-support/src/main/kotlin/io/sentry/systemtest/util/TestHelper.kt index 2881460a2d0..19817c34ac8 100644 --- a/sentry-system-test-support/src/main/kotlin/io/sentry/systemtest/util/TestHelper.kt +++ b/sentry-system-test-support/src/main/kotlin/io/sentry/systemtest/util/TestHelper.kt @@ -190,6 +190,68 @@ class TestHelper(backendUrl: String) { return true } + fun doesLogWithBodyHaveAttribute( + logs: SentryLogEvents, + body: String, + attributeKey: String, + attributeValue: Any?, + ): Boolean { + val logItem = logs.items.firstOrNull { logItem -> logItem.body == body } + if (logItem == null) { + println("Unable to find log item with body $body in logs:") + logObject(logs) + return false + } + + val attr = logItem.attributes?.get(attributeKey) + if (attr == null) { + println("Unable to find attribute $attributeKey on log with body $body:") + logObject(logItem) + return false + } + + if (attr.value != attributeValue) { + println( + "Attribute $attributeKey has value ${attr.value} but expected $attributeValue on log with body $body:" + ) + logObject(logItem) + return false + } + + return true + } + + fun doesMetricHaveAttribute( + metrics: SentryMetricsEvents, + metricName: String, + attributeKey: String, + attributeValue: Any?, + ): Boolean { + val metricItem = metrics.items.firstOrNull { it.name == metricName } + if (metricItem == null) { + println("Unable to find metric with name $metricName in metrics:") + logObject(metrics) + return false + } + + val attr = metricItem.attributes?.get(attributeKey) + if (attr == null) { + println("Unable to find attribute $attributeKey on metric $metricName:") + logObject(metricItem) + return false + } + + if (attr.value != attributeValue) { + println( + "Attribute $attributeKey has value ${attr.value} but expected $attributeValue on metric $metricName:" + ) + logObject(metricItem) + return false + } + + return true + } + private fun checkIfTransactionMatches( envelopeString: String, callback: ((SentryTransaction, SentryEnvelopeHeader) -> Boolean), diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index 9b8413630c2..a7bbb6c6cfa 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -276,6 +276,7 @@ public final class io/sentry/CombinedScopeView : io/sentry/IScope { public synthetic fun clone ()Ljava/lang/Object; public fun endSession ()Lio/sentry/Session; public fun getAttachments ()Ljava/util/List; + public fun getAttributes ()Ljava/util/Map; public fun getBreadcrumbs ()Ljava/util/Queue; public fun getClient ()Lio/sentry/ISentryClient; public fun getContexts ()Lio/sentry/protocol/Contexts; @@ -298,11 +299,15 @@ public final class io/sentry/CombinedScopeView : io/sentry/IScope { public fun getTransaction ()Lio/sentry/ITransaction; public fun getTransactionName ()Ljava/lang/String; public fun getUser ()Lio/sentry/protocol/User; + public fun removeAttribute (Ljava/lang/String;)V public fun removeContexts (Ljava/lang/String;)V public fun removeExtra (Ljava/lang/String;)V public fun removeTag (Ljava/lang/String;)V public fun replaceOptions (Lio/sentry/SentryOptions;)V public fun setActiveSpan (Lio/sentry/ISpan;)V + public fun setAttribute (Lio/sentry/SentryAttribute;)V + public fun setAttribute (Ljava/lang/String;Ljava/lang/Object;)V + public fun setAttributes (Lio/sentry/SentryAttributes;)V public fun setContexts (Ljava/lang/String;Ljava/lang/Boolean;)V public fun setContexts (Ljava/lang/String;Ljava/lang/Character;)V public fun setContexts (Ljava/lang/String;Ljava/lang/Number;)V @@ -672,10 +677,14 @@ public final class io/sentry/HubAdapter : io/sentry/IHub { public fun popScope ()V public fun pushIsolationScope ()Lio/sentry/ISentryLifecycleToken; public fun pushScope ()Lio/sentry/ISentryLifecycleToken; + public fun removeAttribute (Ljava/lang/String;)V public fun removeExtra (Ljava/lang/String;)V public fun removeTag (Ljava/lang/String;)V public fun reportFullyDisplayed ()V public fun setActiveSpan (Lio/sentry/ISpan;)V + public fun setAttribute (Lio/sentry/SentryAttribute;)V + public fun setAttribute (Ljava/lang/String;Ljava/lang/Object;)V + public fun setAttributes (Lio/sentry/SentryAttributes;)V public fun setExtra (Ljava/lang/String;Ljava/lang/String;)V public fun setFingerprint (Ljava/util/List;)V public fun setLevel (Lio/sentry/SentryLevel;)V @@ -744,10 +753,14 @@ public final class io/sentry/HubScopesWrapper : io/sentry/IHub { public fun popScope ()V public fun pushIsolationScope ()Lio/sentry/ISentryLifecycleToken; public fun pushScope ()Lio/sentry/ISentryLifecycleToken; + public fun removeAttribute (Ljava/lang/String;)V public fun removeExtra (Ljava/lang/String;)V public fun removeTag (Ljava/lang/String;)V public fun reportFullyDisplayed ()V public fun setActiveSpan (Lio/sentry/ISpan;)V + public fun setAttribute (Lio/sentry/SentryAttribute;)V + public fun setAttribute (Ljava/lang/String;Ljava/lang/Object;)V + public fun setAttributes (Lio/sentry/SentryAttributes;)V public fun setExtra (Ljava/lang/String;Ljava/lang/String;)V public fun setFingerprint (Ljava/util/List;)V public fun setLevel (Lio/sentry/SentryLevel;)V @@ -867,6 +880,7 @@ public abstract interface class io/sentry/IScope { public abstract fun clone ()Lio/sentry/IScope; public abstract fun endSession ()Lio/sentry/Session; public abstract fun getAttachments ()Ljava/util/List; + public abstract fun getAttributes ()Ljava/util/Map; public abstract fun getBreadcrumbs ()Ljava/util/Queue; public abstract fun getClient ()Lio/sentry/ISentryClient; public abstract fun getContexts ()Lio/sentry/protocol/Contexts; @@ -889,11 +903,15 @@ public abstract interface class io/sentry/IScope { public abstract fun getTransaction ()Lio/sentry/ITransaction; public abstract fun getTransactionName ()Ljava/lang/String; public abstract fun getUser ()Lio/sentry/protocol/User; + public abstract fun removeAttribute (Ljava/lang/String;)V public abstract fun removeContexts (Ljava/lang/String;)V public abstract fun removeExtra (Ljava/lang/String;)V public abstract fun removeTag (Ljava/lang/String;)V public abstract fun replaceOptions (Lio/sentry/SentryOptions;)V public abstract fun setActiveSpan (Lio/sentry/ISpan;)V + public abstract fun setAttribute (Lio/sentry/SentryAttribute;)V + public abstract fun setAttribute (Ljava/lang/String;Ljava/lang/Object;)V + public abstract fun setAttributes (Lio/sentry/SentryAttributes;)V public abstract fun setContexts (Ljava/lang/String;Ljava/lang/Boolean;)V public abstract fun setContexts (Ljava/lang/String;Ljava/lang/Character;)V public abstract fun setContexts (Ljava/lang/String;Ljava/lang/Number;)V @@ -1005,10 +1023,14 @@ public abstract interface class io/sentry/IScopes { public abstract fun popScope ()V public abstract fun pushIsolationScope ()Lio/sentry/ISentryLifecycleToken; public abstract fun pushScope ()Lio/sentry/ISentryLifecycleToken; + public abstract fun removeAttribute (Ljava/lang/String;)V public abstract fun removeExtra (Ljava/lang/String;)V public abstract fun removeTag (Ljava/lang/String;)V public abstract fun reportFullyDisplayed ()V public abstract fun setActiveSpan (Lio/sentry/ISpan;)V + public abstract fun setAttribute (Lio/sentry/SentryAttribute;)V + public abstract fun setAttribute (Ljava/lang/String;Ljava/lang/Object;)V + public abstract fun setAttributes (Lio/sentry/SentryAttributes;)V public abstract fun setExtra (Ljava/lang/String;Ljava/lang/String;)V public abstract fun setFingerprint (Ljava/util/List;)V public abstract fun setLevel (Lio/sentry/SentryLevel;)V @@ -1581,10 +1603,14 @@ public final class io/sentry/NoOpHub : io/sentry/IHub { public fun popScope ()V public fun pushIsolationScope ()Lio/sentry/ISentryLifecycleToken; public fun pushScope ()Lio/sentry/ISentryLifecycleToken; + public fun removeAttribute (Ljava/lang/String;)V public fun removeExtra (Ljava/lang/String;)V public fun removeTag (Ljava/lang/String;)V public fun reportFullyDisplayed ()V public fun setActiveSpan (Lio/sentry/ISpan;)V + public fun setAttribute (Lio/sentry/SentryAttribute;)V + public fun setAttribute (Ljava/lang/String;Ljava/lang/Object;)V + public fun setAttributes (Lio/sentry/SentryAttributes;)V public fun setExtra (Ljava/lang/String;Ljava/lang/String;)V public fun setFingerprint (Ljava/util/List;)V public fun setLevel (Lio/sentry/SentryLevel;)V @@ -1651,6 +1677,7 @@ public final class io/sentry/NoOpScope : io/sentry/IScope { public synthetic fun clone ()Ljava/lang/Object; public fun endSession ()Lio/sentry/Session; public fun getAttachments ()Ljava/util/List; + public fun getAttributes ()Ljava/util/Map; public fun getBreadcrumbs ()Ljava/util/Queue; public fun getClient ()Lio/sentry/ISentryClient; public fun getContexts ()Lio/sentry/protocol/Contexts; @@ -1674,11 +1701,15 @@ public final class io/sentry/NoOpScope : io/sentry/IScope { public fun getTransaction ()Lio/sentry/ITransaction; public fun getTransactionName ()Ljava/lang/String; public fun getUser ()Lio/sentry/protocol/User; + public fun removeAttribute (Ljava/lang/String;)V public fun removeContexts (Ljava/lang/String;)V public fun removeExtra (Ljava/lang/String;)V public fun removeTag (Ljava/lang/String;)V public fun replaceOptions (Lio/sentry/SentryOptions;)V public fun setActiveSpan (Lio/sentry/ISpan;)V + public fun setAttribute (Lio/sentry/SentryAttribute;)V + public fun setAttribute (Ljava/lang/String;Ljava/lang/Object;)V + public fun setAttributes (Lio/sentry/SentryAttributes;)V public fun setContexts (Ljava/lang/String;Ljava/lang/Boolean;)V public fun setContexts (Ljava/lang/String;Ljava/lang/Character;)V public fun setContexts (Ljava/lang/String;Ljava/lang/Number;)V @@ -1758,10 +1789,14 @@ public final class io/sentry/NoOpScopes : io/sentry/IScopes { public fun popScope ()V public fun pushIsolationScope ()Lio/sentry/ISentryLifecycleToken; public fun pushScope ()Lio/sentry/ISentryLifecycleToken; + public fun removeAttribute (Ljava/lang/String;)V public fun removeExtra (Ljava/lang/String;)V public fun removeTag (Ljava/lang/String;)V public fun reportFullyDisplayed ()V public fun setActiveSpan (Lio/sentry/ISpan;)V + public fun setAttribute (Lio/sentry/SentryAttribute;)V + public fun setAttribute (Ljava/lang/String;Ljava/lang/Object;)V + public fun setAttributes (Lio/sentry/SentryAttributes;)V public fun setExtra (Ljava/lang/String;Ljava/lang/String;)V public fun setFingerprint (Ljava/util/List;)V public fun setLevel (Lio/sentry/SentryLevel;)V @@ -2327,6 +2362,7 @@ public final class io/sentry/Scope : io/sentry/IScope { public synthetic fun clone ()Ljava/lang/Object; public fun endSession ()Lio/sentry/Session; public fun getAttachments ()Ljava/util/List; + public fun getAttributes ()Ljava/util/Map; public fun getBreadcrumbs ()Ljava/util/Queue; public fun getClient ()Lio/sentry/ISentryClient; public fun getContexts ()Lio/sentry/protocol/Contexts; @@ -2349,11 +2385,15 @@ public final class io/sentry/Scope : io/sentry/IScope { public fun getTransaction ()Lio/sentry/ITransaction; public fun getTransactionName ()Ljava/lang/String; public fun getUser ()Lio/sentry/protocol/User; + public fun removeAttribute (Ljava/lang/String;)V public fun removeContexts (Ljava/lang/String;)V public fun removeExtra (Ljava/lang/String;)V public fun removeTag (Ljava/lang/String;)V public fun replaceOptions (Lio/sentry/SentryOptions;)V public fun setActiveSpan (Lio/sentry/ISpan;)V + public fun setAttribute (Lio/sentry/SentryAttribute;)V + public fun setAttribute (Ljava/lang/String;Ljava/lang/Object;)V + public fun setAttributes (Lio/sentry/SentryAttributes;)V public fun setContexts (Ljava/lang/String;Ljava/lang/Boolean;)V public fun setContexts (Ljava/lang/String;Ljava/lang/Character;)V public fun setContexts (Ljava/lang/String;Ljava/lang/Number;)V @@ -2484,10 +2524,14 @@ public final class io/sentry/Scopes : io/sentry/IScopes { public fun popScope ()V public fun pushIsolationScope ()Lio/sentry/ISentryLifecycleToken; public fun pushScope ()Lio/sentry/ISentryLifecycleToken; + public fun removeAttribute (Ljava/lang/String;)V public fun removeExtra (Ljava/lang/String;)V public fun removeTag (Ljava/lang/String;)V public fun reportFullyDisplayed ()V public fun setActiveSpan (Lio/sentry/ISpan;)V + public fun setAttribute (Lio/sentry/SentryAttribute;)V + public fun setAttribute (Ljava/lang/String;Ljava/lang/Object;)V + public fun setAttributes (Lio/sentry/SentryAttributes;)V public fun setExtra (Ljava/lang/String;Ljava/lang/String;)V public fun setFingerprint (Ljava/util/List;)V public fun setLevel (Lio/sentry/SentryLevel;)V @@ -2557,10 +2601,14 @@ public final class io/sentry/ScopesAdapter : io/sentry/IScopes { public fun popScope ()V public fun pushIsolationScope ()Lio/sentry/ISentryLifecycleToken; public fun pushScope ()Lio/sentry/ISentryLifecycleToken; + public fun removeAttribute (Ljava/lang/String;)V public fun removeExtra (Ljava/lang/String;)V public fun removeTag (Ljava/lang/String;)V public fun reportFullyDisplayed ()V public fun setActiveSpan (Lio/sentry/ISpan;)V + public fun setAttribute (Lio/sentry/SentryAttribute;)V + public fun setAttribute (Ljava/lang/String;Ljava/lang/Object;)V + public fun setAttributes (Lio/sentry/SentryAttributes;)V public fun setExtra (Ljava/lang/String;Ljava/lang/String;)V public fun setFingerprint (Ljava/util/List;)V public fun setLevel (Lio/sentry/SentryLevel;)V @@ -2678,10 +2726,14 @@ public final class io/sentry/Sentry { public static fun popScope ()V public static fun pushIsolationScope ()Lio/sentry/ISentryLifecycleToken; public static fun pushScope ()Lio/sentry/ISentryLifecycleToken; + public static fun removeAttribute (Ljava/lang/String;)V public static fun removeExtra (Ljava/lang/String;)V public static fun removeTag (Ljava/lang/String;)V public static fun replay ()Lio/sentry/IReplayApi; public static fun reportFullyDisplayed ()V + public static fun setAttribute (Lio/sentry/SentryAttribute;)V + public static fun setAttribute (Ljava/lang/String;Ljava/lang/Object;)V + public static fun setAttributes (Lio/sentry/SentryAttributes;)V public static fun setCurrentHub (Lio/sentry/IHub;)Lio/sentry/ISentryLifecycleToken; public static fun setCurrentScopes (Lio/sentry/IScopes;)Lio/sentry/ISentryLifecycleToken; public static fun setExtra (Ljava/lang/String;Ljava/lang/String;)V @@ -2763,6 +2815,8 @@ public final class io/sentry/SentryAppStartProfilingOptions$JsonKeys { } public final class io/sentry/SentryAttribute { + public static fun arrayAttribute (Ljava/lang/String;Ljava/util/Collection;)Lio/sentry/SentryAttribute; + public static fun arrayAttribute (Ljava/lang/String;[Ljava/lang/Object;)Lio/sentry/SentryAttribute; public static fun booleanAttribute (Ljava/lang/String;Ljava/lang/Boolean;)Lio/sentry/SentryAttribute; public static fun doubleAttribute (Ljava/lang/String;Ljava/lang/Double;)Lio/sentry/SentryAttribute; public fun getName ()Ljava/lang/String; @@ -2774,11 +2828,13 @@ public final class io/sentry/SentryAttribute { } public final class io/sentry/SentryAttributeType : java/lang/Enum { + public static final field ARRAY Lio/sentry/SentryAttributeType; public static final field BOOLEAN Lio/sentry/SentryAttributeType; public static final field DOUBLE Lio/sentry/SentryAttributeType; public static final field INTEGER Lio/sentry/SentryAttributeType; public static final field STRING Lio/sentry/SentryAttributeType; public fun apiName ()Ljava/lang/String; + public static fun inferFrom (Ljava/lang/Object;)Lio/sentry/SentryAttributeType; public static fun valueOf (Ljava/lang/String;)Lio/sentry/SentryAttributeType; public static fun values ()[Lio/sentry/SentryAttributeType; } @@ -3288,6 +3344,7 @@ public final class io/sentry/SentryLogEvent$JsonKeys { public final class io/sentry/SentryLogEventAttributeValue : io/sentry/JsonSerializable, io/sentry/JsonUnknown { public fun (Lio/sentry/SentryAttributeType;Ljava/lang/Object;)V public fun (Ljava/lang/String;Ljava/lang/Object;)V + public static fun fromAttribute (Lio/sentry/SentryAttribute;)Lio/sentry/SentryLogEventAttributeValue; public fun getType ()Ljava/lang/String; public fun getUnknown ()Ljava/util/Map; public fun getValue ()Ljava/lang/Object; diff --git a/sentry/src/main/java/io/sentry/CombinedScopeView.java b/sentry/src/main/java/io/sentry/CombinedScopeView.java index fc90e6255bd..0c61bdf9126 100644 --- a/sentry/src/main/java/io/sentry/CombinedScopeView.java +++ b/sentry/src/main/java/io/sentry/CombinedScopeView.java @@ -241,6 +241,35 @@ public void removeTag(@Nullable String key) { getDefaultWriteScope().removeTag(key); } + @Override + public @NotNull Map getAttributes() { + final @NotNull Map allAttributes = new ConcurrentHashMap<>(); + allAttributes.putAll(globalScope.getAttributes()); + allAttributes.putAll(isolationScope.getAttributes()); + allAttributes.putAll(scope.getAttributes()); + return allAttributes; + } + + @Override + public void setAttribute(@Nullable String key, @Nullable Object value) { + getDefaultWriteScope().setAttribute(key, value); + } + + @Override + public void setAttribute(@Nullable SentryAttribute attribute) { + getDefaultWriteScope().setAttribute(attribute); + } + + @Override + public void setAttributes(@Nullable SentryAttributes attributes) { + getDefaultWriteScope().setAttributes(attributes); + } + + @Override + public void removeAttribute(@Nullable String key) { + getDefaultWriteScope().removeAttribute(key); + } + @Override public @NotNull Map getExtras() { final @NotNull Map allTags = new ConcurrentHashMap<>(); diff --git a/sentry/src/main/java/io/sentry/HubAdapter.java b/sentry/src/main/java/io/sentry/HubAdapter.java index 5715d061144..cf90eb1fe65 100644 --- a/sentry/src/main/java/io/sentry/HubAdapter.java +++ b/sentry/src/main/java/io/sentry/HubAdapter.java @@ -395,6 +395,26 @@ public void reportFullyDisplayed() { return Sentry.getCurrentScopes().metrics(); } + @Override + public void setAttribute(final @Nullable String key, final @Nullable Object value) { + Sentry.setAttribute(key, value); + } + + @Override + public void setAttribute(final @Nullable SentryAttribute attribute) { + Sentry.setAttribute(attribute); + } + + @Override + public void setAttributes(final @Nullable SentryAttributes attributes) { + Sentry.setAttributes(attributes); + } + + @Override + public void removeAttribute(final @Nullable String key) { + Sentry.removeAttribute(key); + } + @Override public void addFeatureFlag(final @Nullable String flag, final @Nullable Boolean result) { Sentry.addFeatureFlag(flag, result); diff --git a/sentry/src/main/java/io/sentry/HubScopesWrapper.java b/sentry/src/main/java/io/sentry/HubScopesWrapper.java index c04aad9ed8c..66a34b4dc36 100644 --- a/sentry/src/main/java/io/sentry/HubScopesWrapper.java +++ b/sentry/src/main/java/io/sentry/HubScopesWrapper.java @@ -380,6 +380,26 @@ public void reportFullyDisplayed() { return scopes.metrics(); } + @Override + public void setAttribute(final @Nullable String key, final @Nullable Object value) { + scopes.setAttribute(key, value); + } + + @Override + public void setAttribute(final @Nullable SentryAttribute attribute) { + scopes.setAttribute(attribute); + } + + @Override + public void setAttributes(final @Nullable SentryAttributes attributes) { + scopes.setAttributes(attributes); + } + + @Override + public void removeAttribute(final @Nullable String key) { + scopes.removeAttribute(key); + } + @Override public void addFeatureFlag(final @Nullable String flag, final @Nullable Boolean result) { scopes.addFeatureFlag(flag, result); diff --git a/sentry/src/main/java/io/sentry/IScope.java b/sentry/src/main/java/io/sentry/IScope.java index f41ea1cbbe1..ccab8dbdeb3 100644 --- a/sentry/src/main/java/io/sentry/IScope.java +++ b/sentry/src/main/java/io/sentry/IScope.java @@ -425,6 +425,44 @@ void setSpanContext( @ApiStatus.Internal void replaceOptions(final @NotNull SentryOptions options); + /** + * Sets an attribute on the Scope. + * + * @param key the key + * @param value the value + */ + void setAttribute(final @Nullable String key, final @Nullable Object value); + + /** + * Sets an attribute on the Scope. + * + * @param attribute the attribute + */ + void setAttribute(final @Nullable SentryAttribute attribute); + + /** + * Sets multiple attributes on the Scope. + * + * @param attributes the attributes + */ + void setAttributes(final @Nullable SentryAttributes attributes); + + /** + * Removes an attribute from the Scope. + * + * @param key the key + */ + void removeAttribute(final @Nullable String key); + + /** + * Returns the Scope's attributes + * + * @return the attributes map + */ + @ApiStatus.Internal + @NotNull + Map getAttributes(); + void addFeatureFlag(final @Nullable String flag, final @Nullable Boolean result); @ApiStatus.Internal diff --git a/sentry/src/main/java/io/sentry/IScopes.java b/sentry/src/main/java/io/sentry/IScopes.java index 0a7c86fa8e6..b1b437f72e5 100644 --- a/sentry/src/main/java/io/sentry/IScopes.java +++ b/sentry/src/main/java/io/sentry/IScopes.java @@ -748,5 +748,34 @@ default boolean isNoOp() { @NotNull IMetricsApi metrics(); + /** + * Sets an attribute. + * + * @param key the key + * @param value the value + */ + void setAttribute(final @Nullable String key, final @Nullable Object value); + + /** + * Sets an attribute. + * + * @param attribute the attribute + */ + void setAttribute(final @Nullable SentryAttribute attribute); + + /** + * Sets multiple attributes. + * + * @param attributes the attributes + */ + void setAttributes(final @Nullable SentryAttributes attributes); + + /** + * Removes an attribute. + * + * @param key the key + */ + void removeAttribute(final @Nullable String key); + void addFeatureFlag(final @Nullable String flag, final @Nullable Boolean result); } diff --git a/sentry/src/main/java/io/sentry/NoOpHub.java b/sentry/src/main/java/io/sentry/NoOpHub.java index 2885d8017d1..4a02be1bd40 100644 --- a/sentry/src/main/java/io/sentry/NoOpHub.java +++ b/sentry/src/main/java/io/sentry/NoOpHub.java @@ -338,6 +338,18 @@ public boolean isNoOp() { return NoOpMetricsApi.getInstance(); } + @Override + public void setAttribute(final @Nullable String key, final @Nullable Object value) {} + + @Override + public void setAttribute(final @Nullable SentryAttribute attribute) {} + + @Override + public void setAttributes(final @Nullable SentryAttributes attributes) {} + + @Override + public void removeAttribute(final @Nullable String key) {} + @Override public void addFeatureFlag(final @Nullable String flag, final @Nullable Boolean result) {} } diff --git a/sentry/src/main/java/io/sentry/NoOpScope.java b/sentry/src/main/java/io/sentry/NoOpScope.java index c04c5af87bd..7693ab81deb 100644 --- a/sentry/src/main/java/io/sentry/NoOpScope.java +++ b/sentry/src/main/java/io/sentry/NoOpScope.java @@ -300,6 +300,24 @@ public void setSpanContext( @Override public void replaceOptions(@NotNull SentryOptions options) {} + @Override + public void setAttribute(@Nullable String key, @Nullable Object value) {} + + @Override + public void setAttribute(@Nullable SentryAttribute attribute) {} + + @Override + public void setAttributes(@Nullable SentryAttributes attributes) {} + + @Override + public void removeAttribute(@Nullable String key) {} + + @ApiStatus.Internal + @Override + public @NotNull Map getAttributes() { + return new HashMap<>(); + } + @Override public void addFeatureFlag(final @Nullable String flag, final @Nullable Boolean result) {} diff --git a/sentry/src/main/java/io/sentry/NoOpScopes.java b/sentry/src/main/java/io/sentry/NoOpScopes.java index 5abb20226ac..1ae357d502e 100644 --- a/sentry/src/main/java/io/sentry/NoOpScopes.java +++ b/sentry/src/main/java/io/sentry/NoOpScopes.java @@ -336,6 +336,18 @@ public boolean isNoOp() { return NoOpMetricsApi.getInstance(); } + @Override + public void setAttribute(final @Nullable String key, final @Nullable Object value) {} + + @Override + public void setAttribute(final @Nullable SentryAttribute attribute) {} + + @Override + public void setAttributes(final @Nullable SentryAttributes attributes) {} + + @Override + public void removeAttribute(final @Nullable String key) {} + @Override public void addFeatureFlag(final @Nullable String flag, final @Nullable Boolean result) {} } diff --git a/sentry/src/main/java/io/sentry/Scope.java b/sentry/src/main/java/io/sentry/Scope.java index 5fc82a648d3..1aab545b80c 100644 --- a/sentry/src/main/java/io/sentry/Scope.java +++ b/sentry/src/main/java/io/sentry/Scope.java @@ -65,6 +65,9 @@ public final class Scope implements IScope { /** Scope's tags */ private @NotNull Map tags = new ConcurrentHashMap<>(); + /** Scope's attributes */ + private @NotNull Map attributes = new ConcurrentHashMap<>(); + /** Scope's extras */ private @NotNull Map extra = new ConcurrentHashMap<>(); @@ -164,6 +167,18 @@ private Scope(final @NotNull Scope scope) { this.tags = tagsClone; + final Map attributesRef = scope.attributes; + + final Map attributesClone = new ConcurrentHashMap<>(); + + for (Map.Entry item : attributesRef.entrySet()) { + if (item != null) { + attributesClone.put(item.getKey(), item.getValue()); // shallow copy + } + } + + this.attributes = attributesClone; + final Map extraRef = scope.extra; Map extraClone = new ConcurrentHashMap<>(); @@ -554,6 +569,7 @@ public void clear() { fingerprint.clear(); clearBreadcrumbs(); tags.clear(); + attributes.clear(); extra.clear(); eventProcessors.clear(); clearTransaction(); @@ -613,6 +629,60 @@ public void removeTag(final @Nullable String key) { } } + /** + * Returns the Scope's attributes + * + * @return the attributes map + */ + @ApiStatus.Internal + @SuppressWarnings("NullAway") // attributes are never null + @Override + public @NotNull Map getAttributes() { + return CollectionUtils.newConcurrentHashMap(attributes); + } + + /** {@inheritDoc} */ + @Override + public void setAttribute(final @Nullable String key, final @Nullable Object value) { + if (key == null) { + return; + } + if (value == null) { + removeAttribute(key); + } else { + this.attributes.put(key, SentryAttribute.named(key, value)); + } + } + + /** {@inheritDoc} */ + @Override + public void setAttribute(final @Nullable SentryAttribute attribute) { + if (attribute == null) { + return; + } + this.attributes.put(attribute.getName(), attribute); + } + + /** {@inheritDoc} */ + @Override + public void setAttributes(final @Nullable SentryAttributes attributes) { + if (attributes == null) { + return; + } + for (SentryAttribute attribute : attributes.getAttributes().values()) { + this.attributes.put(attribute.getName(), attribute); + } + } + + /** {@inheritDoc} */ + @Override + public void removeAttribute(final @Nullable String key) { + if (key == null) { + return; + } + this.attributes.remove(key); + } + /** * Returns the Scope's extra map * diff --git a/sentry/src/main/java/io/sentry/Scopes.java b/sentry/src/main/java/io/sentry/Scopes.java index ee3d55f2291..e155979e064 100644 --- a/sentry/src/main/java/io/sentry/Scopes.java +++ b/sentry/src/main/java/io/sentry/Scopes.java @@ -1244,6 +1244,56 @@ public void reportFullyDisplayed() { return metrics; } + @Override + public void setAttribute(final @Nullable String key, final @Nullable Object value) { + if (!isEnabled()) { + getOptions() + .getLogger() + .log( + SentryLevel.WARNING, "Instance is disabled and this 'setAttribute' call is a no-op."); + } else { + getCombinedScopeView().setAttribute(key, value); + } + } + + @Override + public void setAttribute(final @Nullable SentryAttribute attribute) { + if (!isEnabled()) { + getOptions() + .getLogger() + .log( + SentryLevel.WARNING, "Instance is disabled and this 'setAttribute' call is a no-op."); + } else { + getCombinedScopeView().setAttribute(attribute); + } + } + + @Override + public void setAttributes(final @Nullable SentryAttributes attributes) { + if (!isEnabled()) { + getOptions() + .getLogger() + .log( + SentryLevel.WARNING, + "Instance is disabled and this 'setAttributes' call is a no-op."); + } else { + getCombinedScopeView().setAttributes(attributes); + } + } + + @Override + public void removeAttribute(final @Nullable String key) { + if (!isEnabled()) { + getOptions() + .getLogger() + .log( + SentryLevel.WARNING, + "Instance is disabled and this 'removeAttribute' call is a no-op."); + } else { + getCombinedScopeView().removeAttribute(key); + } + } + @Override public void addFeatureFlag(final @Nullable String flag, final @Nullable Boolean result) { combinedScope.addFeatureFlag(flag, result); diff --git a/sentry/src/main/java/io/sentry/ScopesAdapter.java b/sentry/src/main/java/io/sentry/ScopesAdapter.java index ba7e74d23bb..b66b681a332 100644 --- a/sentry/src/main/java/io/sentry/ScopesAdapter.java +++ b/sentry/src/main/java/io/sentry/ScopesAdapter.java @@ -392,6 +392,26 @@ public void reportFullyDisplayed() { return Sentry.getCurrentScopes().metrics(); } + @Override + public void setAttribute(final @Nullable String key, final @Nullable Object value) { + Sentry.setAttribute(key, value); + } + + @Override + public void setAttribute(final @Nullable SentryAttribute attribute) { + Sentry.setAttribute(attribute); + } + + @Override + public void setAttributes(final @Nullable SentryAttributes attributes) { + Sentry.setAttributes(attributes); + } + + @Override + public void removeAttribute(final @Nullable String key) { + Sentry.removeAttribute(key); + } + @Override public void addFeatureFlag(final @Nullable String flag, final @Nullable Boolean result) { Sentry.addFeatureFlag(flag, result); diff --git a/sentry/src/main/java/io/sentry/Sentry.java b/sentry/src/main/java/io/sentry/Sentry.java index 8acc051f522..63caf829fc9 100644 --- a/sentry/src/main/java/io/sentry/Sentry.java +++ b/sentry/src/main/java/io/sentry/Sentry.java @@ -1368,6 +1368,43 @@ public static void showUserFeedbackDialog( options.getFeedbackOptions().getDialogHandler().showDialog(associatedEventId, configurator); } + /** + * Sets an attribute on the scope. + * + * @param key the key + * @param value the value + */ + public static void setAttribute(final @Nullable String key, final @Nullable Object value) { + getCurrentScopes().setAttribute(key, value); + } + + /** + * Sets an attribute on the scope. + * + * @param attribute the attribute + */ + public static void setAttribute(final @Nullable SentryAttribute attribute) { + getCurrentScopes().setAttribute(attribute); + } + + /** + * Sets multiple attributes on the scope. + * + * @param attributes the attributes + */ + public static void setAttributes(final @Nullable SentryAttributes attributes) { + getCurrentScopes().setAttributes(attributes); + } + + /** + * Removes an attribute from the scope. + * + * @param key the key + */ + public static void removeAttribute(final @Nullable String key) { + getCurrentScopes().removeAttribute(key); + } + public static void addFeatureFlag(final @Nullable String flag, final @Nullable Boolean result) { getCurrentScopes().addFeatureFlag(flag, result); } diff --git a/sentry/src/main/java/io/sentry/SentryAttribute.java b/sentry/src/main/java/io/sentry/SentryAttribute.java index 4bcef14ee8c..213ef53d91e 100644 --- a/sentry/src/main/java/io/sentry/SentryAttribute.java +++ b/sentry/src/main/java/io/sentry/SentryAttribute.java @@ -1,5 +1,6 @@ package io.sentry; +import java.util.Collection; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -54,4 +55,14 @@ private SentryAttribute( final @NotNull String name, final @Nullable String value) { return new SentryAttribute(name, SentryAttributeType.STRING, value); } + + public static @NotNull SentryAttribute arrayAttribute( + final @NotNull String name, final @Nullable Collection value) { + return new SentryAttribute(name, SentryAttributeType.ARRAY, value); + } + + public static @NotNull SentryAttribute arrayAttribute( + final @NotNull String name, final @Nullable Object[] value) { + return new SentryAttribute(name, SentryAttributeType.ARRAY, value); + } } diff --git a/sentry/src/main/java/io/sentry/SentryAttributeType.java b/sentry/src/main/java/io/sentry/SentryAttributeType.java index a47d7e71f0e..b6648179631 100644 --- a/sentry/src/main/java/io/sentry/SentryAttributeType.java +++ b/sentry/src/main/java/io/sentry/SentryAttributeType.java @@ -1,15 +1,43 @@ package io.sentry; +import java.math.BigInteger; +import java.util.Collection; import java.util.Locale; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; public enum SentryAttributeType { STRING, BOOLEAN, INTEGER, - DOUBLE; + DOUBLE, + ARRAY; public @NotNull String apiName() { return name().toLowerCase(Locale.ROOT); } + + public static @NotNull SentryAttributeType inferFrom(final @Nullable Object value) { + if (value instanceof Boolean) { + return BOOLEAN; + } + if (value instanceof Integer + || value instanceof Long + || value instanceof Short + || value instanceof Byte + || value instanceof BigInteger + || value instanceof AtomicInteger + || value instanceof AtomicLong) { + return INTEGER; + } + if (value instanceof Number) { + return DOUBLE; + } + if (value instanceof Collection || (value != null && value.getClass().isArray())) { + return ARRAY; + } + return STRING; + } } diff --git a/sentry/src/main/java/io/sentry/SentryLogEventAttributeValue.java b/sentry/src/main/java/io/sentry/SentryLogEventAttributeValue.java index 1fd7c8c4528..6f6542927f9 100644 --- a/sentry/src/main/java/io/sentry/SentryLogEventAttributeValue.java +++ b/sentry/src/main/java/io/sentry/SentryLogEventAttributeValue.java @@ -27,6 +27,21 @@ public SentryLogEventAttributeValue( this(type.apiName(), value); } + /** + * Creates a {@link SentryLogEventAttributeValue} from a {@link SentryAttribute}, inferring the + * type if not explicitly set. + * + * @param attribute the attribute + * @return the attribute value + */ + public static @NotNull SentryLogEventAttributeValue fromAttribute( + final @NotNull SentryAttribute attribute) { + final @Nullable Object value = attribute.getValue(); + final @NotNull SentryAttributeType type = + attribute.getType() == null ? SentryAttributeType.inferFrom(value) : attribute.getType(); + return new SentryLogEventAttributeValue(type, value); + } + public @NotNull String getType() { return type; } diff --git a/sentry/src/main/java/io/sentry/logger/LoggerApi.java b/sentry/src/main/java/io/sentry/logger/LoggerApi.java index 37a485df315..c203dcbfb8f 100644 --- a/sentry/src/main/java/io/sentry/logger/LoggerApi.java +++ b/sentry/src/main/java/io/sentry/logger/LoggerApi.java @@ -21,6 +21,7 @@ import io.sentry.util.Platform; import io.sentry.util.TracingUtils; import java.util.HashMap; +import java.util.Map; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -163,6 +164,14 @@ private void captureLog( final @NotNull String message, final @Nullable Object... args) { final @NotNull HashMap attributes = new HashMap<>(); + + final @NotNull Map scopeAttributes = + scopes.getCombinedScopeView().getAttributes(); + for (SentryAttribute scopeAttribute : scopeAttributes.values()) { + attributes.put( + scopeAttribute.getName(), SentryLogEventAttributeValue.fromAttribute(scopeAttribute)); + } + final @NotNull String origin = params.getOrigin(); if (!"manual".equalsIgnoreCase(origin)) { attributes.put( @@ -173,17 +182,14 @@ private void captureLog( if (incomingAttributes != null) { for (SentryAttribute attribute : incomingAttributes.getAttributes().values()) { - final @Nullable Object value = attribute.getValue(); - final @NotNull SentryAttributeType type = - attribute.getType() == null ? getType(value) : attribute.getType(); - attributes.put(attribute.getName(), new SentryLogEventAttributeValue(type, value)); + attributes.put(attribute.getName(), SentryLogEventAttributeValue.fromAttribute(attribute)); } } if (args != null) { int i = 0; for (Object arg : args) { - final @NotNull SentryAttributeType type = getType(arg); + final @NotNull SentryAttributeType type = SentryAttributeType.inferFrom(arg); attributes.put( "sentry.message.parameter." + i, new SentryLogEventAttributeValue(type, arg)); i++; @@ -292,17 +298,4 @@ private void setUser(final @NotNull HashMap createAttributes( final @NotNull SentryMetricsParameters params) { final @NotNull HashMap attributes = new HashMap<>(); + + final @NotNull Map scopeAttributes = + scopes.getCombinedScopeView().getAttributes(); + for (SentryAttribute scopeAttribute : scopeAttributes.values()) { + attributes.put( + scopeAttribute.getName(), SentryLogEventAttributeValue.fromAttribute(scopeAttribute)); + } + final @NotNull String origin = params.getOrigin(); if (!"manual".equalsIgnoreCase(origin)) { attributes.put( @@ -177,10 +186,7 @@ private void captureMetrics( if (incomingAttributes != null) { for (SentryAttribute attribute : incomingAttributes.getAttributes().values()) { - final @Nullable Object value = attribute.getValue(); - final @NotNull SentryAttributeType type = - attribute.getType() == null ? getType(value) : attribute.getType(); - attributes.put(attribute.getName(), new SentryLogEventAttributeValue(type, value)); + attributes.put(attribute.getName(), SentryLogEventAttributeValue.fromAttribute(attribute)); } } @@ -279,17 +285,4 @@ private void setUser(final @NotNull HashMap())) + } + + @Test + fun `inferFrom returns ARRAY for mixed-type list`() { + assertEquals(SentryAttributeType.ARRAY, SentryAttributeType.inferFrom(listOf("a", 1, true))) + } + + @Test + fun `arrayAttribute factory accepts Object array`() { + val attr = SentryAttribute.arrayAttribute("key", arrayOf("a", "b")) + assertEquals("key", attr.name) + assertEquals(SentryAttributeType.ARRAY, attr.type) + } +} diff --git a/sentry/src/test/java/io/sentry/protocol/SentryLogsSerializationTest.kt b/sentry/src/test/java/io/sentry/protocol/SentryLogsSerializationTest.kt index c65a3cca709..ade038a408b 100644 --- a/sentry/src/test/java/io/sentry/protocol/SentryLogsSerializationTest.kt +++ b/sentry/src/test/java/io/sentry/protocol/SentryLogsSerializationTest.kt @@ -38,6 +38,7 @@ class SentryLogsSerializationTest { "sentry.sdk.name" to SentryLogEventAttributeValue("string", "sentry.java.spring-boot.jakarta"), "sentry.environment" to SentryLogEventAttributeValue("string", "production"), + "custom.array" to SentryLogEventAttributeValue("array", listOf("a", "b")), "sentry.sdk.version" to SentryLogEventAttributeValue("string", "8.11.1"), "sentry.trace.parent_span_id" to SentryLogEventAttributeValue("string", "f28b86350e534671"), diff --git a/sentry/src/test/resources/json/sentry_logs.json b/sentry/src/test/resources/json/sentry_logs.json index e78f5af1b09..1674a4f5764 100644 --- a/sentry/src/test/resources/json/sentry_logs.json +++ b/sentry/src/test/resources/json/sentry_logs.json @@ -20,6 +20,11 @@ "type": "string", "value": "production" }, + "custom.array": + { + "type": "array", + "value": ["a", "b"] + }, "sentry.sdk.version": { "type": "string",