From 5ca032df2d71b18536714bea65c7c96f423c3e7a Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Wed, 14 May 2025 15:41:43 +0200 Subject: [PATCH 1/7] Extend Logs API to allow passing in attributes --- sentry/api/sentry.api | 6 + .../java/io/sentry/logger/ILoggerApi.java | 14 +++ .../main/java/io/sentry/logger/LoggerApi.java | 41 ++++++- .../java/io/sentry/logger/NoOpLoggerApi.java | 20 ++++ sentry/src/test/java/io/sentry/ScopesTest.kt | 107 ++++++++++++++++++ 5 files changed, 184 insertions(+), 4 deletions(-) diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index a38986a65f..28a5df0e0b 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -4712,6 +4712,8 @@ public abstract interface class io/sentry/logger/ILoggerApi { public abstract fun info (Ljava/lang/String;[Ljava/lang/Object;)V public abstract fun log (Lio/sentry/SentryLogLevel;Lio/sentry/SentryDate;Ljava/lang/String;[Ljava/lang/Object;)V public abstract fun log (Lio/sentry/SentryLogLevel;Ljava/lang/String;[Ljava/lang/Object;)V + public abstract fun log (Ljava/util/Map;Lio/sentry/SentryLogLevel;Lio/sentry/SentryDate;Ljava/lang/String;[Ljava/lang/Object;)V + public abstract fun log (Ljava/util/Map;Lio/sentry/SentryLogLevel;Ljava/lang/String;[Ljava/lang/Object;)V public abstract fun trace (Ljava/lang/String;[Ljava/lang/Object;)V public abstract fun warn (Ljava/lang/String;[Ljava/lang/Object;)V } @@ -4729,6 +4731,8 @@ public final class io/sentry/logger/LoggerApi : io/sentry/logger/ILoggerApi { public fun info (Ljava/lang/String;[Ljava/lang/Object;)V public fun log (Lio/sentry/SentryLogLevel;Lio/sentry/SentryDate;Ljava/lang/String;[Ljava/lang/Object;)V public fun log (Lio/sentry/SentryLogLevel;Ljava/lang/String;[Ljava/lang/Object;)V + public fun log (Ljava/util/Map;Lio/sentry/SentryLogLevel;Lio/sentry/SentryDate;Ljava/lang/String;[Ljava/lang/Object;)V + public fun log (Ljava/util/Map;Lio/sentry/SentryLogLevel;Ljava/lang/String;[Ljava/lang/Object;)V public fun trace (Ljava/lang/String;[Ljava/lang/Object;)V public fun warn (Ljava/lang/String;[Ljava/lang/Object;)V } @@ -4749,6 +4753,8 @@ public final class io/sentry/logger/NoOpLoggerApi : io/sentry/logger/ILoggerApi public fun info (Ljava/lang/String;[Ljava/lang/Object;)V public fun log (Lio/sentry/SentryLogLevel;Lio/sentry/SentryDate;Ljava/lang/String;[Ljava/lang/Object;)V public fun log (Lio/sentry/SentryLogLevel;Ljava/lang/String;[Ljava/lang/Object;)V + public fun log (Ljava/util/Map;Lio/sentry/SentryLogLevel;Lio/sentry/SentryDate;Ljava/lang/String;[Ljava/lang/Object;)V + public fun log (Ljava/util/Map;Lio/sentry/SentryLogLevel;Ljava/lang/String;[Ljava/lang/Object;)V public fun trace (Ljava/lang/String;[Ljava/lang/Object;)V public fun warn (Ljava/lang/String;[Ljava/lang/Object;)V } diff --git a/sentry/src/main/java/io/sentry/logger/ILoggerApi.java b/sentry/src/main/java/io/sentry/logger/ILoggerApi.java index 40d0d3c36f..61ae5219aa 100644 --- a/sentry/src/main/java/io/sentry/logger/ILoggerApi.java +++ b/sentry/src/main/java/io/sentry/logger/ILoggerApi.java @@ -2,6 +2,7 @@ import io.sentry.SentryDate; import io.sentry.SentryLogLevel; +import java.util.Map; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -28,4 +29,17 @@ void log( @Nullable SentryDate timestamp, @Nullable String message, @Nullable Object... args); + + void log( + @Nullable Map attributes, + @NotNull SentryLogLevel level, + @Nullable String message, + @Nullable Object... args); + + void log( + @Nullable Map attributes, + @NotNull SentryLogLevel level, + @Nullable SentryDate timestamp, + @Nullable String message, + @Nullable Object... args); } diff --git a/sentry/src/main/java/io/sentry/logger/LoggerApi.java b/sentry/src/main/java/io/sentry/logger/LoggerApi.java index 957b08f509..921ebe94b0 100644 --- a/sentry/src/main/java/io/sentry/logger/LoggerApi.java +++ b/sentry/src/main/java/io/sentry/logger/LoggerApi.java @@ -17,6 +17,7 @@ import io.sentry.util.Platform; import io.sentry.util.TracingUtils; import java.util.HashMap; +import java.util.Map; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -65,7 +66,7 @@ public void log( final @NotNull SentryLogLevel level, final @Nullable String message, final @Nullable Object... args) { - log(level, null, message, args); + captureLog(null, level, null, message, args); } @Override @@ -74,11 +75,31 @@ public void log( final @Nullable SentryDate timestamp, final @Nullable String message, final @Nullable Object... args) { - captureLog(level, timestamp, message, args); + captureLog(null, level, timestamp, message, args); + } + + @Override + public void log( + final @Nullable Map attributes, + final @NotNull SentryLogLevel level, + final @Nullable SentryDate timestamp, + final @Nullable String message, + final @Nullable Object... args) { + captureLog(attributes, level, timestamp, message, args); + } + + @Override + public void log( + final @Nullable Map attributes, + final @NotNull SentryLogLevel level, + final @Nullable String message, + final @Nullable Object... args) { + captureLog(attributes, level, null, message, args); } @SuppressWarnings("AnnotateFormatMethod") private void captureLog( + final @Nullable Map attributes, final @NotNull SentryLogLevel level, final @Nullable SentryDate timestamp, final @Nullable String message, @@ -119,7 +140,7 @@ private void captureLog( span == null ? propagationContext.getSpanId() : span.getSpanContext().getSpanId(); final SentryLogEvent logEvent = new SentryLogEvent(traceId, timestampToUse, messageToUse, level); - logEvent.setAttributes(createAttributes(message, spanId, args)); + logEvent.setAttributes(createAttributes(attributes, message, spanId, args)); logEvent.setSeverityNumber(level.getSeverityNumber()); scopes.getClient().captureLog(logEvent, combinedScope); @@ -146,8 +167,20 @@ private void captureLog( } private @NotNull HashMap createAttributes( - final @NotNull String message, final @NotNull SpanId spanId, final @Nullable Object... args) { + final @Nullable Map incomingAttributes, + final @NotNull String message, + final @NotNull SpanId spanId, + final @Nullable Object... args) { final @NotNull HashMap attributes = new HashMap<>(); + + if (incomingAttributes != null) { + for (Map.Entry attributeEntry : incomingAttributes.entrySet()) { + final @Nullable Object value = attributeEntry.getValue(); + final @NotNull String type = getType(value); + attributes.put(attributeEntry.getKey(), new SentryLogEventAttributeValue(type, value)); + } + } + if (args != null) { int i = 0; for (Object arg : args) { diff --git a/sentry/src/main/java/io/sentry/logger/NoOpLoggerApi.java b/sentry/src/main/java/io/sentry/logger/NoOpLoggerApi.java index 5c1e0d850f..f9218be8be 100644 --- a/sentry/src/main/java/io/sentry/logger/NoOpLoggerApi.java +++ b/sentry/src/main/java/io/sentry/logger/NoOpLoggerApi.java @@ -2,6 +2,7 @@ import io.sentry.SentryDate; import io.sentry.SentryLogLevel; +import java.util.Map; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -61,4 +62,23 @@ public void log( @Nullable Object... args) { // do nothing } + + @Override + public void log( + @Nullable Map attributes, + @NotNull SentryLogLevel level, + @Nullable String message, + @Nullable Object... args) { + // do nothing + } + + @Override + public void log( + @Nullable Map attributes, + @NotNull SentryLogLevel level, + @Nullable SentryDate timestamp, + @Nullable String message, + @Nullable Object... args) { + // do nothing + } } diff --git a/sentry/src/test/java/io/sentry/ScopesTest.kt b/sentry/src/test/java/io/sentry/ScopesTest.kt index 38d488fffa..87d2d6c397 100644 --- a/sentry/src/test/java/io/sentry/ScopesTest.kt +++ b/sentry/src/test/java/io/sentry/ScopesTest.kt @@ -2628,6 +2628,113 @@ class ScopesTest { ) } + @Test + fun `creating log with timestamp works`() { + val (sut, mockClient) = getEnabledScopes { + it.logs.isEnabled = true + } + + sut.logger().log(SentryLogLevel.WARN, SentryLongDate(123), "log message") + + verify(mockClient).captureLog( + check { + assertEquals("log message", it.body) + assertEquals(0.000000123, it.timestamp) + assertEquals(SentryLogLevel.WARN, it.level) + assertEquals(13, it.severityNumber) + }, + anyOrNull() + ) + } + + @Test + fun `creating log with attributes works`() { + val (sut, mockClient) = getEnabledScopes { + it.logs.isEnabled = true + } + + sut.logger().log(mapOf("attrname1" to "attrval1"), SentryLogLevel.WARN, "log message") + + verify(mockClient).captureLog( + check { + assertEquals("log message", it.body) + assertEquals(SentryLogLevel.WARN, it.level) + assertEquals(13, it.severityNumber) + + val attr1 = it.attributes?.get("attrname1")!! + assertEquals("attrval1", attr1.value) + assertEquals("string", attr1.type) + }, + anyOrNull() + ) + } + + @Test + fun `creating log with attributes and timestamp works`() { + val (sut, mockClient) = getEnabledScopes { + it.logs.isEnabled = true + } + + sut.logger().log(mapOf("attrname1" to "attrval1"), SentryLogLevel.WARN, SentryLongDate(123), "log message") + + verify(mockClient).captureLog( + check { + assertEquals("log message", it.body) + assertEquals(0.000000123, it.timestamp) + assertEquals(SentryLogLevel.WARN, it.level) + assertEquals(13, it.severityNumber) + + val attr1 = it.attributes?.get("attrname1")!! + assertEquals("attrval1", attr1.value) + assertEquals("string", attr1.type) + }, + anyOrNull() + ) + } + + @Test + fun `creating log with attributes and timestamp and format string works`() { + val (sut, mockClient) = getEnabledScopes { + it.logs.isEnabled = true + } + + sut.logger().log(mapOf("attrname1" to "attrval1"), SentryLogLevel.WARN, SentryLongDate(123), "log %s %d %b %.0f", "message", 1, true, 3.2) + + verify(mockClient).captureLog( + check { + assertEquals("log message 1 true 3", it.body) + assertEquals(0.000000123, it.timestamp) + assertEquals(SentryLogLevel.WARN, it.level) + assertEquals(13, it.severityNumber) + + val attr1 = it.attributes?.get("attrname1")!! + assertEquals("attrval1", attr1.value) + assertEquals("string", attr1.type) + + val template = it.attributes?.get("sentry.message.template")!! + assertEquals("log %s %d %b %.0f", template.value) + assertEquals("string", template.type) + + val param0 = it.attributes?.get("sentry.message.parameter.0")!! + assertEquals("message", param0.value) + assertEquals("string", param0.type) + + val param1 = it.attributes?.get("sentry.message.parameter.1")!! + assertEquals(1, param1.value) + assertEquals("integer", param1.type) + + val param2 = it.attributes?.get("sentry.message.parameter.2")!! + assertEquals(true, param2.value) + assertEquals("boolean", param2.type) + + val param3 = it.attributes?.get("sentry.message.parameter.3")!! + assertEquals(3.2, param3.value) + assertEquals("double", param3.type) + }, + anyOrNull() + ) + } + @Test fun `creating log with without args does not add template attribute`() { val (sut, mockClient) = getEnabledScopes { From ffb15c2aea75bc4af04679251e395977046fd32d Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Fri, 16 May 2025 13:08:17 +0200 Subject: [PATCH 2/7] Improve logger attributes API --- sentry/api/sentry.api | 48 ++++++++++++++-- .../main/java/io/sentry/SentryAttribute.java | 57 +++++++++++++++++++ .../java/io/sentry/SentryAttributeType.java | 15 +++++ .../main/java/io/sentry/SentryAttributes.java | 44 ++++++++++++++ .../sentry/SentryLogEventAttributeValue.java | 6 ++ .../java/io/sentry/logger/ILoggerApi.java | 10 +--- .../main/java/io/sentry/logger/LogParams.java | 42 ++++++++++++++ .../main/java/io/sentry/logger/LoggerApi.java | 51 +++++++---------- .../java/io/sentry/logger/NoOpLoggerApi.java | 13 +---- 9 files changed, 230 insertions(+), 56 deletions(-) create mode 100644 sentry/src/main/java/io/sentry/SentryAttribute.java create mode 100644 sentry/src/main/java/io/sentry/SentryAttributeType.java create mode 100644 sentry/src/main/java/io/sentry/SentryAttributes.java create mode 100644 sentry/src/main/java/io/sentry/logger/LogParams.java diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index 28a5df0e0b..e553250800 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -2658,6 +2658,34 @@ public final class io/sentry/SentryAppStartProfilingOptions$JsonKeys { public fun ()V } +public final class io/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; + public fun getType ()Lio/sentry/SentryAttributeType; + public fun getValue ()Ljava/lang/Object; + public static fun integerAttribute (Ljava/lang/String;Ljava/lang/Integer;)Lio/sentry/SentryAttribute; + public static fun named (Ljava/lang/String;Ljava/lang/Object;)Lio/sentry/SentryAttribute; + public static fun stringAttribute (Ljava/lang/String;Ljava/lang/String;)Lio/sentry/SentryAttribute; +} + +public final class io/sentry/SentryAttributeType : java/lang/Enum { + 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 valueOf (Ljava/lang/String;)Lio/sentry/SentryAttributeType; + public static fun values ()[Lio/sentry/SentryAttributeType; +} + +public final class io/sentry/SentryAttributes { + public fun add (Lio/sentry/SentryAttribute;)V + public static fun fromMap (Ljava/util/Map;)Lio/sentry/SentryAttributes; + public fun getAttributes ()Ljava/util/List; + public static fun of ([Lio/sentry/SentryAttribute;)Lio/sentry/SentryAttributes; +} + public final class io/sentry/SentryAutoDateProvider : io/sentry/SentryDateProvider { public fun ()V public fun now ()Lio/sentry/SentryDate; @@ -3077,6 +3105,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 fun getType ()Ljava/lang/String; public fun getUnknown ()Ljava/util/Map; @@ -4711,9 +4740,8 @@ public abstract interface class io/sentry/logger/ILoggerApi { public abstract fun fatal (Ljava/lang/String;[Ljava/lang/Object;)V public abstract fun info (Ljava/lang/String;[Ljava/lang/Object;)V public abstract fun log (Lio/sentry/SentryLogLevel;Lio/sentry/SentryDate;Ljava/lang/String;[Ljava/lang/Object;)V + public abstract fun log (Lio/sentry/SentryLogLevel;Lio/sentry/logger/LogParams;Ljava/lang/String;[Ljava/lang/Object;)V public abstract fun log (Lio/sentry/SentryLogLevel;Ljava/lang/String;[Ljava/lang/Object;)V - public abstract fun log (Ljava/util/Map;Lio/sentry/SentryLogLevel;Lio/sentry/SentryDate;Ljava/lang/String;[Ljava/lang/Object;)V - public abstract fun log (Ljava/util/Map;Lio/sentry/SentryLogLevel;Ljava/lang/String;[Ljava/lang/Object;)V public abstract fun trace (Ljava/lang/String;[Ljava/lang/Object;)V public abstract fun warn (Ljava/lang/String;[Ljava/lang/Object;)V } @@ -4723,6 +4751,16 @@ public abstract interface class io/sentry/logger/ILoggerBatchProcessor { public abstract fun close (Z)V } +public final class io/sentry/logger/LogParams { + public fun ()V + public static fun create (Lio/sentry/SentryAttributes;)Lio/sentry/logger/LogParams; + public static fun create (Lio/sentry/SentryDate;Lio/sentry/SentryAttributes;)Lio/sentry/logger/LogParams; + public fun getAttributes ()Lio/sentry/SentryAttributes; + public fun getTimestamp ()Lio/sentry/SentryDate; + public fun setAttributes (Lio/sentry/SentryAttributes;)V + public fun setTimestamp (Lio/sentry/SentryDate;)V +} + public final class io/sentry/logger/LoggerApi : io/sentry/logger/ILoggerApi { public fun (Lio/sentry/Scopes;)V public fun debug (Ljava/lang/String;[Ljava/lang/Object;)V @@ -4730,9 +4768,8 @@ public final class io/sentry/logger/LoggerApi : io/sentry/logger/ILoggerApi { public fun fatal (Ljava/lang/String;[Ljava/lang/Object;)V public fun info (Ljava/lang/String;[Ljava/lang/Object;)V public fun log (Lio/sentry/SentryLogLevel;Lio/sentry/SentryDate;Ljava/lang/String;[Ljava/lang/Object;)V + public fun log (Lio/sentry/SentryLogLevel;Lio/sentry/logger/LogParams;Ljava/lang/String;[Ljava/lang/Object;)V public fun log (Lio/sentry/SentryLogLevel;Ljava/lang/String;[Ljava/lang/Object;)V - public fun log (Ljava/util/Map;Lio/sentry/SentryLogLevel;Lio/sentry/SentryDate;Ljava/lang/String;[Ljava/lang/Object;)V - public fun log (Ljava/util/Map;Lio/sentry/SentryLogLevel;Ljava/lang/String;[Ljava/lang/Object;)V public fun trace (Ljava/lang/String;[Ljava/lang/Object;)V public fun warn (Ljava/lang/String;[Ljava/lang/Object;)V } @@ -4752,9 +4789,8 @@ public final class io/sentry/logger/NoOpLoggerApi : io/sentry/logger/ILoggerApi public static fun getInstance ()Lio/sentry/logger/NoOpLoggerApi; public fun info (Ljava/lang/String;[Ljava/lang/Object;)V public fun log (Lio/sentry/SentryLogLevel;Lio/sentry/SentryDate;Ljava/lang/String;[Ljava/lang/Object;)V + public fun log (Lio/sentry/SentryLogLevel;Lio/sentry/logger/LogParams;Ljava/lang/String;[Ljava/lang/Object;)V public fun log (Lio/sentry/SentryLogLevel;Ljava/lang/String;[Ljava/lang/Object;)V - public fun log (Ljava/util/Map;Lio/sentry/SentryLogLevel;Lio/sentry/SentryDate;Ljava/lang/String;[Ljava/lang/Object;)V - public fun log (Ljava/util/Map;Lio/sentry/SentryLogLevel;Ljava/lang/String;[Ljava/lang/Object;)V public fun trace (Ljava/lang/String;[Ljava/lang/Object;)V public fun warn (Ljava/lang/String;[Ljava/lang/Object;)V } diff --git a/sentry/src/main/java/io/sentry/SentryAttribute.java b/sentry/src/main/java/io/sentry/SentryAttribute.java new file mode 100644 index 0000000000..4bcef14ee8 --- /dev/null +++ b/sentry/src/main/java/io/sentry/SentryAttribute.java @@ -0,0 +1,57 @@ +package io.sentry; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public final class SentryAttribute { + + private final @NotNull String name; + private final @Nullable SentryAttributeType type; + private final @Nullable Object value; + + private SentryAttribute( + final @NotNull String name, + final @Nullable SentryAttributeType type, + final @Nullable Object value) { + this.name = name; + this.type = type; + this.value = value; + } + + public @NotNull String getName() { + return name; + } + + public @Nullable SentryAttributeType getType() { + return type; + } + + public @Nullable Object getValue() { + return value; + } + + public static @NotNull SentryAttribute named( + final @NotNull String name, final @Nullable Object value) { + return new SentryAttribute(name, null, value); + } + + public static @NotNull SentryAttribute booleanAttribute( + final @NotNull String name, final @Nullable Boolean value) { + return new SentryAttribute(name, SentryAttributeType.BOOLEAN, value); + } + + public static @NotNull SentryAttribute integerAttribute( + final @NotNull String name, final @Nullable Integer value) { + return new SentryAttribute(name, SentryAttributeType.INTEGER, value); + } + + public static @NotNull SentryAttribute doubleAttribute( + final @NotNull String name, final @Nullable Double value) { + return new SentryAttribute(name, SentryAttributeType.DOUBLE, value); + } + + public static @NotNull SentryAttribute stringAttribute( + final @NotNull String name, final @Nullable String value) { + return new SentryAttribute(name, SentryAttributeType.STRING, value); + } +} diff --git a/sentry/src/main/java/io/sentry/SentryAttributeType.java b/sentry/src/main/java/io/sentry/SentryAttributeType.java new file mode 100644 index 0000000000..a47d7e71f0 --- /dev/null +++ b/sentry/src/main/java/io/sentry/SentryAttributeType.java @@ -0,0 +1,15 @@ +package io.sentry; + +import java.util.Locale; +import org.jetbrains.annotations.NotNull; + +public enum SentryAttributeType { + STRING, + BOOLEAN, + INTEGER, + DOUBLE; + + public @NotNull String apiName() { + return name().toLowerCase(Locale.ROOT); + } +} diff --git a/sentry/src/main/java/io/sentry/SentryAttributes.java b/sentry/src/main/java/io/sentry/SentryAttributes.java new file mode 100644 index 0000000000..d4165d4f7a --- /dev/null +++ b/sentry/src/main/java/io/sentry/SentryAttributes.java @@ -0,0 +1,44 @@ +package io.sentry; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public final class SentryAttributes { + + private final @NotNull List attributes; + + private SentryAttributes(final @NotNull List attributes) { + this.attributes = attributes; + } + + public void add(final @Nullable SentryAttribute attribute) { + if (attribute == null) { + return; + } + attributes.add(attribute); + } + + public @NotNull List getAttributes() { + return attributes; + } + + public static @NotNull SentryAttributes of(SentryAttribute... attributes) { + return new SentryAttributes(Arrays.asList(attributes)); + } + + public static @NotNull SentryAttributes fromMap(final @Nullable Map attributes) { + if (attributes == null) { + return new SentryAttributes(new ArrayList<>()); + } + SentryAttributes sentryAttributes = new SentryAttributes(new ArrayList<>(attributes.size())); + for (Map.Entry attribute : attributes.entrySet()) { + sentryAttributes.add(SentryAttribute.named(attribute.getKey(), attribute.getValue())); + } + + return sentryAttributes; + } +} diff --git a/sentry/src/main/java/io/sentry/SentryLogEventAttributeValue.java b/sentry/src/main/java/io/sentry/SentryLogEventAttributeValue.java index a733b42ad0..cf92b21de8 100644 --- a/sentry/src/main/java/io/sentry/SentryLogEventAttributeValue.java +++ b/sentry/src/main/java/io/sentry/SentryLogEventAttributeValue.java @@ -18,6 +18,12 @@ public SentryLogEventAttributeValue(final @NotNull String type, final @Nullable this.value = value; } + public SentryLogEventAttributeValue( + final @NotNull SentryAttributeType type, final @Nullable Object value) { + this.type = type.apiName(); + this.value = value; + } + public @NotNull String getType() { return type; } diff --git a/sentry/src/main/java/io/sentry/logger/ILoggerApi.java b/sentry/src/main/java/io/sentry/logger/ILoggerApi.java index 61ae5219aa..78787ef461 100644 --- a/sentry/src/main/java/io/sentry/logger/ILoggerApi.java +++ b/sentry/src/main/java/io/sentry/logger/ILoggerApi.java @@ -2,7 +2,6 @@ import io.sentry.SentryDate; import io.sentry.SentryLogLevel; -import java.util.Map; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -31,15 +30,8 @@ void log( @Nullable Object... args); void log( - @Nullable Map attributes, @NotNull SentryLogLevel level, - @Nullable String message, - @Nullable Object... args); - - void log( - @Nullable Map attributes, - @NotNull SentryLogLevel level, - @Nullable SentryDate timestamp, + @NotNull LogParams params, @Nullable String message, @Nullable Object... args); } diff --git a/sentry/src/main/java/io/sentry/logger/LogParams.java b/sentry/src/main/java/io/sentry/logger/LogParams.java new file mode 100644 index 0000000000..2e943766e0 --- /dev/null +++ b/sentry/src/main/java/io/sentry/logger/LogParams.java @@ -0,0 +1,42 @@ +package io.sentry.logger; + +import io.sentry.SentryAttributes; +import io.sentry.SentryDate; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public final class LogParams { + + private @Nullable SentryDate timestamp; + private @Nullable SentryAttributes attributes; + + public @Nullable SentryDate getTimestamp() { + return timestamp; + } + + public void setTimestamp(final @Nullable SentryDate timestamp) { + this.timestamp = timestamp; + } + + public @Nullable SentryAttributes getAttributes() { + return attributes; + } + + public void setAttributes(final @Nullable SentryAttributes attributes) { + this.attributes = attributes; + } + + public static @NotNull LogParams create( + final @Nullable SentryDate timestamp, final @Nullable SentryAttributes attributes) { + final @NotNull LogParams params = new LogParams(); + + params.setTimestamp(timestamp); + params.setAttributes(attributes); + + return params; + } + + public static @NotNull LogParams create(final @Nullable SentryAttributes attributes) { + return create(null, attributes); + } +} diff --git a/sentry/src/main/java/io/sentry/logger/LoggerApi.java b/sentry/src/main/java/io/sentry/logger/LoggerApi.java index 921ebe94b0..3f8d53154f 100644 --- a/sentry/src/main/java/io/sentry/logger/LoggerApi.java +++ b/sentry/src/main/java/io/sentry/logger/LoggerApi.java @@ -5,6 +5,9 @@ import io.sentry.ISpan; import io.sentry.PropagationContext; import io.sentry.Scopes; +import io.sentry.SentryAttribute; +import io.sentry.SentryAttributeType; +import io.sentry.SentryAttributes; import io.sentry.SentryDate; import io.sentry.SentryLevel; import io.sentry.SentryLogEvent; @@ -17,7 +20,6 @@ import io.sentry.util.Platform; import io.sentry.util.TracingUtils; import java.util.HashMap; -import java.util.Map; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -66,7 +68,7 @@ public void log( final @NotNull SentryLogLevel level, final @Nullable String message, final @Nullable Object... args) { - captureLog(null, level, null, message, args); + captureLog(level, LogParams.create(null, null), message, args); } @Override @@ -75,33 +77,22 @@ public void log( final @Nullable SentryDate timestamp, final @Nullable String message, final @Nullable Object... args) { - captureLog(null, level, timestamp, message, args); + captureLog(level, LogParams.create(timestamp, null), message, args); } @Override public void log( - final @Nullable Map attributes, final @NotNull SentryLogLevel level, - final @Nullable SentryDate timestamp, + final @NotNull LogParams params, final @Nullable String message, final @Nullable Object... args) { - captureLog(attributes, level, timestamp, message, args); - } - - @Override - public void log( - final @Nullable Map attributes, - final @NotNull SentryLogLevel level, - final @Nullable String message, - final @Nullable Object... args) { - captureLog(attributes, level, null, message, args); + captureLog(level, params, message, args); } @SuppressWarnings("AnnotateFormatMethod") private void captureLog( - final @Nullable Map attributes, final @NotNull SentryLogLevel level, - final @Nullable SentryDate timestamp, + final @NotNull LogParams params, final @Nullable String message, final @Nullable Object... args) { final @NotNull SentryOptions options = scopes.getOptions(); @@ -124,6 +115,7 @@ private void captureLog( return; } + final @Nullable SentryDate timestamp = params.getTimestamp(); final @NotNull SentryDate timestampToUse = timestamp == null ? options.getDateProvider().now() : timestamp; final @NotNull String messageToUse = maybeFormatMessage(message, args); @@ -140,7 +132,7 @@ private void captureLog( span == null ? propagationContext.getSpanId() : span.getSpanContext().getSpanId(); final SentryLogEvent logEvent = new SentryLogEvent(traceId, timestampToUse, messageToUse, level); - logEvent.setAttributes(createAttributes(attributes, message, spanId, args)); + logEvent.setAttributes(createAttributes(params.getAttributes(), message, spanId, args)); logEvent.setSeverityNumber(level.getSeverityNumber()); scopes.getClient().captureLog(logEvent, combinedScope); @@ -167,24 +159,25 @@ private void captureLog( } private @NotNull HashMap createAttributes( - final @Nullable Map incomingAttributes, + final @Nullable SentryAttributes incomingAttributes, final @NotNull String message, final @NotNull SpanId spanId, final @Nullable Object... args) { final @NotNull HashMap attributes = new HashMap<>(); if (incomingAttributes != null) { - for (Map.Entry attributeEntry : incomingAttributes.entrySet()) { - final @Nullable Object value = attributeEntry.getValue(); - final @NotNull String type = getType(value); - attributes.put(attributeEntry.getKey(), new SentryLogEventAttributeValue(type, value)); + for (SentryAttribute attribute : incomingAttributes.getAttributes()) { + 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)); } } if (args != null) { int i = 0; for (Object arg : args) { - final @NotNull String type = getType(arg); + final @NotNull SentryAttributeType type = getType(arg); attributes.put( "sentry.message.parameter." + i, new SentryLogEventAttributeValue(type, arg)); i++; @@ -238,16 +231,16 @@ private void setServerName( } } - private @NotNull String getType(final @Nullable Object arg) { + private @NotNull SentryAttributeType getType(final @Nullable Object arg) { if (arg instanceof Boolean) { - return "boolean"; + return SentryAttributeType.BOOLEAN; } if (arg instanceof Integer) { - return "integer"; + return SentryAttributeType.INTEGER; } if (arg instanceof Number) { - return "double"; + return SentryAttributeType.DOUBLE; } - return "string"; + return SentryAttributeType.STRING; } } diff --git a/sentry/src/main/java/io/sentry/logger/NoOpLoggerApi.java b/sentry/src/main/java/io/sentry/logger/NoOpLoggerApi.java index f9218be8be..f4f2a3959d 100644 --- a/sentry/src/main/java/io/sentry/logger/NoOpLoggerApi.java +++ b/sentry/src/main/java/io/sentry/logger/NoOpLoggerApi.java @@ -2,7 +2,6 @@ import io.sentry.SentryDate; import io.sentry.SentryLogLevel; -import java.util.Map; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -65,18 +64,8 @@ public void log( @Override public void log( - @Nullable Map attributes, @NotNull SentryLogLevel level, - @Nullable String message, - @Nullable Object... args) { - // do nothing - } - - @Override - public void log( - @Nullable Map attributes, - @NotNull SentryLogLevel level, - @Nullable SentryDate timestamp, + @NotNull LogParams params, @Nullable String message, @Nullable Object... args) { // do nothing From 42bdc88f0134ae3ebd29e9875d7ecced6e89dda8 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Mon, 19 May 2025 06:42:04 +0200 Subject: [PATCH 3/7] Attribute.flattened --- sentry/api/sentry.api | 4 ++ .../JsonReflectionObjectSerializer.java | 2 +- .../main/java/io/sentry/SentryAttribute.java | 29 ++++++++++++--- .../main/java/io/sentry/logger/LoggerApi.java | 37 ++++++++++++++++++- 4 files changed, 64 insertions(+), 8 deletions(-) diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index e553250800..cd5d38a882 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -1250,6 +1250,7 @@ public final class io/sentry/JsonObjectWriter : io/sentry/ObjectWriter { } public final class io/sentry/JsonReflectionObjectSerializer { + public fun (I)V public fun serialize (Ljava/lang/Object;Lio/sentry/ILogger;)Ljava/lang/Object; public fun serializeObject (Ljava/lang/Object;Lio/sentry/ILogger;)Ljava/util/Map; } @@ -2661,6 +2662,9 @@ public final class io/sentry/SentryAppStartProfilingOptions$JsonKeys { public final class io/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 static fun flattened (Ljava/lang/String;Ljava/lang/Object;)Lio/sentry/SentryAttribute; + public static fun flattened (Ljava/lang/String;Ljava/lang/Object;I)Lio/sentry/SentryAttribute; + public fun getFlattenDepth ()I public fun getName ()Ljava/lang/String; public fun getType ()Lio/sentry/SentryAttributeType; public fun getValue ()Ljava/lang/Object; diff --git a/sentry/src/main/java/io/sentry/JsonReflectionObjectSerializer.java b/sentry/src/main/java/io/sentry/JsonReflectionObjectSerializer.java index 97c2303104..28e6129332 100644 --- a/sentry/src/main/java/io/sentry/JsonReflectionObjectSerializer.java +++ b/sentry/src/main/java/io/sentry/JsonReflectionObjectSerializer.java @@ -33,7 +33,7 @@ public final class JsonReflectionObjectSerializer { private final Set visiting = new HashSet<>(); private final int maxDepth; - JsonReflectionObjectSerializer(int maxDepth) { + public JsonReflectionObjectSerializer(int maxDepth) { this.maxDepth = maxDepth; } diff --git a/sentry/src/main/java/io/sentry/SentryAttribute.java b/sentry/src/main/java/io/sentry/SentryAttribute.java index 4bcef14ee8..7e7dd9aa79 100644 --- a/sentry/src/main/java/io/sentry/SentryAttribute.java +++ b/sentry/src/main/java/io/sentry/SentryAttribute.java @@ -8,14 +8,17 @@ public final class SentryAttribute { private final @NotNull String name; private final @Nullable SentryAttributeType type; private final @Nullable Object value; + private final int flattenDepth; private SentryAttribute( final @NotNull String name, final @Nullable SentryAttributeType type, - final @Nullable Object value) { + final @Nullable Object value, + final int flattenDepth) { this.name = name; this.type = type; this.value = value; + this.flattenDepth = flattenDepth; } public @NotNull String getName() { @@ -30,28 +33,42 @@ private SentryAttribute( return value; } + public int getFlattenDepth() { + return flattenDepth; + } + public static @NotNull SentryAttribute named( final @NotNull String name, final @Nullable Object value) { - return new SentryAttribute(name, null, value); + return new SentryAttribute(name, null, value, 0); + } + + public static @NotNull SentryAttribute flattened( + final @NotNull String name, final @Nullable Object value) { + return new SentryAttribute(name, null, value, 1); + } + + public static @NotNull SentryAttribute flattened( + final @NotNull String name, final @Nullable Object value, final int flattenDepth) { + return new SentryAttribute(name, null, value, flattenDepth); } public static @NotNull SentryAttribute booleanAttribute( final @NotNull String name, final @Nullable Boolean value) { - return new SentryAttribute(name, SentryAttributeType.BOOLEAN, value); + return new SentryAttribute(name, SentryAttributeType.BOOLEAN, value, 0); } public static @NotNull SentryAttribute integerAttribute( final @NotNull String name, final @Nullable Integer value) { - return new SentryAttribute(name, SentryAttributeType.INTEGER, value); + return new SentryAttribute(name, SentryAttributeType.INTEGER, value, 0); } public static @NotNull SentryAttribute doubleAttribute( final @NotNull String name, final @Nullable Double value) { - return new SentryAttribute(name, SentryAttributeType.DOUBLE, value); + return new SentryAttribute(name, SentryAttributeType.DOUBLE, value, 0); } public static @NotNull SentryAttribute stringAttribute( final @NotNull String name, final @Nullable String value) { - return new SentryAttribute(name, SentryAttributeType.STRING, value); + return new SentryAttribute(name, SentryAttributeType.STRING, value, 0); } } diff --git a/sentry/src/main/java/io/sentry/logger/LoggerApi.java b/sentry/src/main/java/io/sentry/logger/LoggerApi.java index 3f8d53154f..0283198f1f 100644 --- a/sentry/src/main/java/io/sentry/logger/LoggerApi.java +++ b/sentry/src/main/java/io/sentry/logger/LoggerApi.java @@ -3,6 +3,7 @@ import io.sentry.HostnameCache; import io.sentry.IScope; import io.sentry.ISpan; +import io.sentry.JsonReflectionObjectSerializer; import io.sentry.PropagationContext; import io.sentry.Scopes; import io.sentry.SentryAttribute; @@ -20,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.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -170,7 +172,9 @@ private void captureLog( 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)); + final @Nullable Object convertedValue = + maybeConvertValue(attribute.getName(), value, attribute.getFlattenDepth(), attributes); + attributes.put(attribute.getName(), new SentryLogEventAttributeValue(type, convertedValue)); } } @@ -216,6 +220,37 @@ private void captureLog( return attributes; } + private @Nullable Object maybeConvertValue( + final @Nullable Object name, + final @Nullable Object value, + final int flattenDepth, + final @NotNull HashMap attributes) { + if (value == null) { + return null; + } + + if (flattenDepth > 0) { + JsonReflectionObjectSerializer serializer = new JsonReflectionObjectSerializer(1); + try { + Map stringObjectMap = + serializer.serializeObject(value, scopes.getOptions().getLogger()); + for (final @NotNull Map.Entry entry : stringObjectMap.entrySet()) { + attributes.put( + name + "." + entry.getKey(), + new SentryLogEventAttributeValue(getType(entry.getValue()), entry.getValue())); + } + return value; + } catch (Exception e) { + scopes + .getOptions() + .getLogger() + .log(SentryLevel.DEBUG, "Unable to flatten log attribute value", e); + } + } + + return value; + } + private void setServerName( final @NotNull HashMap attributes) { final @NotNull SentryOptions options = scopes.getOptions(); From 43f9c85ca43e8aea7c4ba10c8076cd57d7ed93ca Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Thu, 22 May 2025 09:23:05 +0200 Subject: [PATCH 4/7] Review feedback --- sentry/api/sentry.api | 28 +++---- .../main/java/io/sentry/SentryAttributes.java | 34 ++++++--- .../java/io/sentry/logger/ILoggerApi.java | 2 +- .../main/java/io/sentry/logger/LoggerApi.java | 10 +-- .../java/io/sentry/logger/NoOpLoggerApi.java | 2 +- ...ogParams.java => SentryLogParameters.java} | 8 +- sentry/src/test/java/io/sentry/ScopesTest.kt | 74 ++++++++++++++++++- 7 files changed, 117 insertions(+), 41 deletions(-) rename sentry/src/main/java/io/sentry/logger/{LogParams.java => SentryLogParameters.java} (76%) diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index e553250800..6966361a03 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -2682,7 +2682,7 @@ public final class io/sentry/SentryAttributeType : java/lang/Enum { public final class io/sentry/SentryAttributes { public fun add (Lio/sentry/SentryAttribute;)V public static fun fromMap (Ljava/util/Map;)Lio/sentry/SentryAttributes; - public fun getAttributes ()Ljava/util/List; + public fun getAttributes ()Ljava/util/Map; public static fun of ([Lio/sentry/SentryAttribute;)Lio/sentry/SentryAttributes; } @@ -4740,7 +4740,7 @@ public abstract interface class io/sentry/logger/ILoggerApi { public abstract fun fatal (Ljava/lang/String;[Ljava/lang/Object;)V public abstract fun info (Ljava/lang/String;[Ljava/lang/Object;)V public abstract fun log (Lio/sentry/SentryLogLevel;Lio/sentry/SentryDate;Ljava/lang/String;[Ljava/lang/Object;)V - public abstract fun log (Lio/sentry/SentryLogLevel;Lio/sentry/logger/LogParams;Ljava/lang/String;[Ljava/lang/Object;)V + public abstract fun log (Lio/sentry/SentryLogLevel;Lio/sentry/logger/SentryLogParameters;Ljava/lang/String;[Ljava/lang/Object;)V public abstract fun log (Lio/sentry/SentryLogLevel;Ljava/lang/String;[Ljava/lang/Object;)V public abstract fun trace (Ljava/lang/String;[Ljava/lang/Object;)V public abstract fun warn (Ljava/lang/String;[Ljava/lang/Object;)V @@ -4751,16 +4751,6 @@ public abstract interface class io/sentry/logger/ILoggerBatchProcessor { public abstract fun close (Z)V } -public final class io/sentry/logger/LogParams { - public fun ()V - public static fun create (Lio/sentry/SentryAttributes;)Lio/sentry/logger/LogParams; - public static fun create (Lio/sentry/SentryDate;Lio/sentry/SentryAttributes;)Lio/sentry/logger/LogParams; - public fun getAttributes ()Lio/sentry/SentryAttributes; - public fun getTimestamp ()Lio/sentry/SentryDate; - public fun setAttributes (Lio/sentry/SentryAttributes;)V - public fun setTimestamp (Lio/sentry/SentryDate;)V -} - public final class io/sentry/logger/LoggerApi : io/sentry/logger/ILoggerApi { public fun (Lio/sentry/Scopes;)V public fun debug (Ljava/lang/String;[Ljava/lang/Object;)V @@ -4768,7 +4758,7 @@ public final class io/sentry/logger/LoggerApi : io/sentry/logger/ILoggerApi { public fun fatal (Ljava/lang/String;[Ljava/lang/Object;)V public fun info (Ljava/lang/String;[Ljava/lang/Object;)V public fun log (Lio/sentry/SentryLogLevel;Lio/sentry/SentryDate;Ljava/lang/String;[Ljava/lang/Object;)V - public fun log (Lio/sentry/SentryLogLevel;Lio/sentry/logger/LogParams;Ljava/lang/String;[Ljava/lang/Object;)V + public fun log (Lio/sentry/SentryLogLevel;Lio/sentry/logger/SentryLogParameters;Ljava/lang/String;[Ljava/lang/Object;)V public fun log (Lio/sentry/SentryLogLevel;Ljava/lang/String;[Ljava/lang/Object;)V public fun trace (Ljava/lang/String;[Ljava/lang/Object;)V public fun warn (Ljava/lang/String;[Ljava/lang/Object;)V @@ -4789,7 +4779,7 @@ public final class io/sentry/logger/NoOpLoggerApi : io/sentry/logger/ILoggerApi public static fun getInstance ()Lio/sentry/logger/NoOpLoggerApi; public fun info (Ljava/lang/String;[Ljava/lang/Object;)V public fun log (Lio/sentry/SentryLogLevel;Lio/sentry/SentryDate;Ljava/lang/String;[Ljava/lang/Object;)V - public fun log (Lio/sentry/SentryLogLevel;Lio/sentry/logger/LogParams;Ljava/lang/String;[Ljava/lang/Object;)V + public fun log (Lio/sentry/SentryLogLevel;Lio/sentry/logger/SentryLogParameters;Ljava/lang/String;[Ljava/lang/Object;)V public fun log (Lio/sentry/SentryLogLevel;Ljava/lang/String;[Ljava/lang/Object;)V public fun trace (Ljava/lang/String;[Ljava/lang/Object;)V public fun warn (Ljava/lang/String;[Ljava/lang/Object;)V @@ -4801,6 +4791,16 @@ public final class io/sentry/logger/NoOpLoggerBatchProcessor : io/sentry/logger/ public static fun getInstance ()Lio/sentry/logger/NoOpLoggerBatchProcessor; } +public final class io/sentry/logger/SentryLogParameters { + public fun ()V + public static fun create (Lio/sentry/SentryAttributes;)Lio/sentry/logger/SentryLogParameters; + public static fun create (Lio/sentry/SentryDate;Lio/sentry/SentryAttributes;)Lio/sentry/logger/SentryLogParameters; + public fun getAttributes ()Lio/sentry/SentryAttributes; + public fun getTimestamp ()Lio/sentry/SentryDate; + public fun setAttributes (Lio/sentry/SentryAttributes;)V + public fun setTimestamp (Lio/sentry/SentryDate;)V +} + public final class io/sentry/opentelemetry/OpenTelemetryUtil { public fun ()V public static fun applyIgnoredSpanOrigins (Lio/sentry/SentryOptions;)V diff --git a/sentry/src/main/java/io/sentry/SentryAttributes.java b/sentry/src/main/java/io/sentry/SentryAttributes.java index d4165d4f7a..86dece4c88 100644 --- a/sentry/src/main/java/io/sentry/SentryAttributes.java +++ b/sentry/src/main/java/io/sentry/SentryAttributes.java @@ -1,17 +1,15 @@ package io.sentry; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; public final class SentryAttributes { - private final @NotNull List attributes; + private final @NotNull Map attributes; - private SentryAttributes(final @NotNull List attributes) { + private SentryAttributes(final @NotNull Map attributes) { this.attributes = attributes; } @@ -19,24 +17,36 @@ public void add(final @Nullable SentryAttribute attribute) { if (attribute == null) { return; } - attributes.add(attribute); + attributes.put(attribute.getName(), attribute); } - public @NotNull List getAttributes() { + public @NotNull Map getAttributes() { return attributes; } - public static @NotNull SentryAttributes of(SentryAttribute... attributes) { - return new SentryAttributes(Arrays.asList(attributes)); + public static @NotNull SentryAttributes of(final @Nullable SentryAttribute... attributes) { + if (attributes == null) { + return new SentryAttributes(new ConcurrentHashMap<>()); + } + final @NotNull SentryAttributes sentryAttributes = + new SentryAttributes(new ConcurrentHashMap<>(attributes.length)); + for (SentryAttribute attribute : attributes) { + sentryAttributes.add(attribute); + } + return sentryAttributes; } public static @NotNull SentryAttributes fromMap(final @Nullable Map attributes) { if (attributes == null) { - return new SentryAttributes(new ArrayList<>()); + return new SentryAttributes(new ConcurrentHashMap<>()); } - SentryAttributes sentryAttributes = new SentryAttributes(new ArrayList<>(attributes.size())); + final @NotNull SentryAttributes sentryAttributes = + new SentryAttributes(new ConcurrentHashMap<>(attributes.size())); for (Map.Entry attribute : attributes.entrySet()) { - sentryAttributes.add(SentryAttribute.named(attribute.getKey(), attribute.getValue())); + final @Nullable String key = attribute.getKey(); + if (key != null) { + sentryAttributes.add(SentryAttribute.named(key, attribute.getValue())); + } } return sentryAttributes; diff --git a/sentry/src/main/java/io/sentry/logger/ILoggerApi.java b/sentry/src/main/java/io/sentry/logger/ILoggerApi.java index 78787ef461..bd892ea68f 100644 --- a/sentry/src/main/java/io/sentry/logger/ILoggerApi.java +++ b/sentry/src/main/java/io/sentry/logger/ILoggerApi.java @@ -31,7 +31,7 @@ void log( void log( @NotNull SentryLogLevel level, - @NotNull LogParams params, + @NotNull SentryLogParameters params, @Nullable String message, @Nullable Object... args); } diff --git a/sentry/src/main/java/io/sentry/logger/LoggerApi.java b/sentry/src/main/java/io/sentry/logger/LoggerApi.java index 3f8d53154f..71eccb4840 100644 --- a/sentry/src/main/java/io/sentry/logger/LoggerApi.java +++ b/sentry/src/main/java/io/sentry/logger/LoggerApi.java @@ -68,7 +68,7 @@ public void log( final @NotNull SentryLogLevel level, final @Nullable String message, final @Nullable Object... args) { - captureLog(level, LogParams.create(null, null), message, args); + captureLog(level, SentryLogParameters.create(null, null), message, args); } @Override @@ -77,13 +77,13 @@ public void log( final @Nullable SentryDate timestamp, final @Nullable String message, final @Nullable Object... args) { - captureLog(level, LogParams.create(timestamp, null), message, args); + captureLog(level, SentryLogParameters.create(timestamp, null), message, args); } @Override public void log( final @NotNull SentryLogLevel level, - final @NotNull LogParams params, + final @NotNull SentryLogParameters params, final @Nullable String message, final @Nullable Object... args) { captureLog(level, params, message, args); @@ -92,7 +92,7 @@ public void log( @SuppressWarnings("AnnotateFormatMethod") private void captureLog( final @NotNull SentryLogLevel level, - final @NotNull LogParams params, + final @NotNull SentryLogParameters params, final @Nullable String message, final @Nullable Object... args) { final @NotNull SentryOptions options = scopes.getOptions(); @@ -166,7 +166,7 @@ private void captureLog( final @NotNull HashMap attributes = new HashMap<>(); if (incomingAttributes != null) { - for (SentryAttribute attribute : incomingAttributes.getAttributes()) { + for (SentryAttribute attribute : incomingAttributes.getAttributes().values()) { final @Nullable Object value = attribute.getValue(); final @NotNull SentryAttributeType type = attribute.getType() == null ? getType(value) : attribute.getType(); diff --git a/sentry/src/main/java/io/sentry/logger/NoOpLoggerApi.java b/sentry/src/main/java/io/sentry/logger/NoOpLoggerApi.java index f4f2a3959d..16ea708f46 100644 --- a/sentry/src/main/java/io/sentry/logger/NoOpLoggerApi.java +++ b/sentry/src/main/java/io/sentry/logger/NoOpLoggerApi.java @@ -65,7 +65,7 @@ public void log( @Override public void log( @NotNull SentryLogLevel level, - @NotNull LogParams params, + @NotNull SentryLogParameters params, @Nullable String message, @Nullable Object... args) { // do nothing diff --git a/sentry/src/main/java/io/sentry/logger/LogParams.java b/sentry/src/main/java/io/sentry/logger/SentryLogParameters.java similarity index 76% rename from sentry/src/main/java/io/sentry/logger/LogParams.java rename to sentry/src/main/java/io/sentry/logger/SentryLogParameters.java index 2e943766e0..7eeae78fdb 100644 --- a/sentry/src/main/java/io/sentry/logger/LogParams.java +++ b/sentry/src/main/java/io/sentry/logger/SentryLogParameters.java @@ -5,7 +5,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -public final class LogParams { +public final class SentryLogParameters { private @Nullable SentryDate timestamp; private @Nullable SentryAttributes attributes; @@ -26,9 +26,9 @@ public void setAttributes(final @Nullable SentryAttributes attributes) { this.attributes = attributes; } - public static @NotNull LogParams create( + public static @NotNull SentryLogParameters create( final @Nullable SentryDate timestamp, final @Nullable SentryAttributes attributes) { - final @NotNull LogParams params = new LogParams(); + final @NotNull SentryLogParameters params = new SentryLogParameters(); params.setTimestamp(timestamp); params.setAttributes(attributes); @@ -36,7 +36,7 @@ public void setAttributes(final @Nullable SentryAttributes attributes) { return params; } - public static @NotNull LogParams create(final @Nullable SentryAttributes attributes) { + public static @NotNull SentryLogParameters create(final @Nullable SentryAttributes attributes) { return create(null, attributes); } } diff --git a/sentry/src/test/java/io/sentry/ScopesTest.kt b/sentry/src/test/java/io/sentry/ScopesTest.kt index 87d2d6c397..49a9500216 100644 --- a/sentry/src/test/java/io/sentry/ScopesTest.kt +++ b/sentry/src/test/java/io/sentry/ScopesTest.kt @@ -7,6 +7,7 @@ import io.sentry.clientreport.DiscardReason import io.sentry.clientreport.DiscardedEvent import io.sentry.hints.SessionEndHint import io.sentry.hints.SessionStartHint +import io.sentry.logger.SentryLogParameters import io.sentry.protocol.Feedback import io.sentry.protocol.SentryId import io.sentry.protocol.SentryTransaction @@ -2648,12 +2649,12 @@ class ScopesTest { } @Test - fun `creating log with attributes works`() { + fun `creating log with attributes from map works`() { val (sut, mockClient) = getEnabledScopes { it.logs.isEnabled = true } - sut.logger().log(mapOf("attrname1" to "attrval1"), SentryLogLevel.WARN, "log message") + sut.logger().log(SentryLogLevel.WARN, SentryLogParameters.create(SentryAttributes.fromMap(mapOf("attrname1" to "attrval1"))), "log message") verify(mockClient).captureLog( check { @@ -2669,13 +2670,78 @@ class ScopesTest { ) } + @Test + fun `creating log with attributes works`() { + val (sut, mockClient) = getEnabledScopes { + it.logs.isEnabled = true + } + + sut.logger().log( + SentryLogLevel.WARN, + SentryLogParameters.create( + SentryAttributes.of( + SentryAttribute.stringAttribute("strattr", "strval"), + SentryAttribute.booleanAttribute("boolattr", true), + SentryAttribute.integerAttribute("intattr", 17), + SentryAttribute.doubleAttribute("doubleattr", 3.8), + SentryAttribute.named("namedstrattr", "namedstrval"), + SentryAttribute.named("namedboolattr", false), + SentryAttribute.named("namedintattr", 18), + SentryAttribute.named("nameddoubleattr", 4.9) + ) + ), + "log message" + ) + + verify(mockClient).captureLog( + check { + assertEquals("log message", it.body) + assertEquals(SentryLogLevel.WARN, it.level) + assertEquals(13, it.severityNumber) + + val strattr = it.attributes?.get("strattr")!! + assertEquals("strval", strattr.value) + assertEquals("string", strattr.type) + + val boolattr = it.attributes?.get("boolattr")!! + assertEquals(true, boolattr.value) + assertEquals("boolean", boolattr.type) + + val intattr = it.attributes?.get("intattr")!! + assertEquals(17, intattr.value) + assertEquals("integer", intattr.type) + + val doubleattr = it.attributes?.get("doubleattr")!! + assertEquals(3.8, doubleattr.value) + assertEquals("double", doubleattr.type) + + val namedstrattr = it.attributes?.get("namedstrattr")!! + assertEquals("namedstrval", namedstrattr.value) + assertEquals("string", namedstrattr.type) + + val namedboolattr = it.attributes?.get("namedboolattr")!! + assertEquals(false, namedboolattr.value) + assertEquals("boolean", namedboolattr.type) + + val namedintattr = it.attributes?.get("namedintattr")!! + assertEquals(18, namedintattr.value) + assertEquals("integer", namedintattr.type) + + val nameddoubleattr = it.attributes?.get("nameddoubleattr")!! + assertEquals(4.9, nameddoubleattr.value) + assertEquals("double", nameddoubleattr.type) + }, + anyOrNull() + ) + } + @Test fun `creating log with attributes and timestamp works`() { val (sut, mockClient) = getEnabledScopes { it.logs.isEnabled = true } - sut.logger().log(mapOf("attrname1" to "attrval1"), SentryLogLevel.WARN, SentryLongDate(123), "log message") + sut.logger().log(SentryLogLevel.WARN, SentryLogParameters.create(SentryLongDate(123), SentryAttributes.of(SentryAttribute.named("attrname1", "attrval1"))), "log message") verify(mockClient).captureLog( check { @@ -2698,7 +2764,7 @@ class ScopesTest { it.logs.isEnabled = true } - sut.logger().log(mapOf("attrname1" to "attrval1"), SentryLogLevel.WARN, SentryLongDate(123), "log %s %d %b %.0f", "message", 1, true, 3.2) + sut.logger().log(SentryLogLevel.WARN, SentryLogParameters.create(SentryLongDate(123), SentryAttributes.of(SentryAttribute.named("attrname1", "attrval1"))), "log %s %d %b %.0f", "message", 1, true, 3.2) verify(mockClient).captureLog( check { From 4df31f79c9ec2bd43136cfac1f69108a52495ee8 Mon Sep 17 00:00:00 2001 From: Lorenzo Cian Date: Thu, 22 May 2025 11:00:36 +0200 Subject: [PATCH 5/7] update (#4409) --- .../sentry/SentryLogEventAttributeValue.java | 6 +++++- .../protocol/SentryLogsSerializationTest.kt | 14 +++++++++++++- .../src/test/resources/json/sentry_logs.json | 18 ++++++++++++++++++ 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/sentry/src/main/java/io/sentry/SentryLogEventAttributeValue.java b/sentry/src/main/java/io/sentry/SentryLogEventAttributeValue.java index a733b42ad0..5ff1252d23 100644 --- a/sentry/src/main/java/io/sentry/SentryLogEventAttributeValue.java +++ b/sentry/src/main/java/io/sentry/SentryLogEventAttributeValue.java @@ -15,7 +15,11 @@ public final class SentryLogEventAttributeValue implements JsonUnknown, JsonSeri public SentryLogEventAttributeValue(final @NotNull String type, final @Nullable Object value) { this.type = type; - this.value = value; + if (value != null && type.equals("string")) { + this.value = value.toString(); + } else { + this.value = value; + } } public @NotNull String getType() { diff --git a/sentry/src/test/java/io/sentry/protocol/SentryLogsSerializationTest.kt b/sentry/src/test/java/io/sentry/protocol/SentryLogsSerializationTest.kt index be1343024e..9610efe2bb 100644 --- a/sentry/src/test/java/io/sentry/protocol/SentryLogsSerializationTest.kt +++ b/sentry/src/test/java/io/sentry/protocol/SentryLogsSerializationTest.kt @@ -32,7 +32,11 @@ class SentryLogsSerializationTest { "sentry.sdk.name" to SentryLogEventAttributeValue("string", "sentry.java.spring-boot.jakarta"), "sentry.environment" to SentryLogEventAttributeValue("string", "production"), "sentry.sdk.version" to SentryLogEventAttributeValue("string", "8.11.1"), - "sentry.trace.parent_span_id" to SentryLogEventAttributeValue("string", "f28b86350e534671") + "sentry.trace.parent_span_id" to SentryLogEventAttributeValue("string", "f28b86350e534671"), + "custom.boolean" to SentryLogEventAttributeValue("boolean", true), + "custom.double" to SentryLogEventAttributeValue("double", 11.12.toDouble()), + "custom.point" to SentryLogEventAttributeValue("string", Point(20, 30)), + "custom.integer" to SentryLogEventAttributeValue("integer", 10) ) it.severityNumber = 10 } @@ -75,4 +79,12 @@ class SentryLogsSerializationTest { val reader = JsonObjectReader(StringReader(json)) return SentryLogEvents.Deserializer().deserialize(reader, fixture.logger) } + + companion object { + data class Point(val x: Int, val y: Int) { + override fun toString(): String { + return "Point{x:$x,y:$y}-Hello" + } + } + } } diff --git a/sentry/src/test/resources/json/sentry_logs.json b/sentry/src/test/resources/json/sentry_logs.json index 120ddec7db..7040012c29 100644 --- a/sentry/src/test/resources/json/sentry_logs.json +++ b/sentry/src/test/resources/json/sentry_logs.json @@ -28,6 +28,24 @@ { "type": "string", "value": "f28b86350e534671" + }, + "custom.boolean": + { + "type": "boolean", + "value": true + }, + "custom.double": { + "type": "double", + "value": 11.12 + }, + "custom.point": { + "type": "string", + "value": "Point{x:20,y:30}-Hello" + }, + "custom.integer": + { + "type": "integer", + "value": 10 } } } From a374fe81907c0a55b929de88dc908edfb52975cf Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Thu, 22 May 2025 11:01:56 +0200 Subject: [PATCH 6/7] remove variable depth flatten; add test --- sentry/api/sentry.api | 1 - .../src/main/java/io/sentry/SentryAttribute.java | 5 ----- sentry/src/test/java/io/sentry/ScopesTest.kt | 16 +++++++++++++++- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index 3721fe190f..2c95088441 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -2663,7 +2663,6 @@ public final class io/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 static fun flattened (Ljava/lang/String;Ljava/lang/Object;)Lio/sentry/SentryAttribute; - public static fun flattened (Ljava/lang/String;Ljava/lang/Object;I)Lio/sentry/SentryAttribute; public fun getFlattenDepth ()I public fun getName ()Ljava/lang/String; public fun getType ()Lio/sentry/SentryAttributeType; diff --git a/sentry/src/main/java/io/sentry/SentryAttribute.java b/sentry/src/main/java/io/sentry/SentryAttribute.java index 7e7dd9aa79..1d8dea6d93 100644 --- a/sentry/src/main/java/io/sentry/SentryAttribute.java +++ b/sentry/src/main/java/io/sentry/SentryAttribute.java @@ -47,11 +47,6 @@ public int getFlattenDepth() { return new SentryAttribute(name, null, value, 1); } - public static @NotNull SentryAttribute flattened( - final @NotNull String name, final @Nullable Object value, final int flattenDepth) { - return new SentryAttribute(name, null, value, flattenDepth); - } - public static @NotNull SentryAttribute booleanAttribute( final @NotNull String name, final @Nullable Boolean value) { return new SentryAttribute(name, SentryAttributeType.BOOLEAN, value, 0); diff --git a/sentry/src/test/java/io/sentry/ScopesTest.kt b/sentry/src/test/java/io/sentry/ScopesTest.kt index 49a9500216..d28d47154d 100644 --- a/sentry/src/test/java/io/sentry/ScopesTest.kt +++ b/sentry/src/test/java/io/sentry/ScopesTest.kt @@ -36,6 +36,7 @@ import org.mockito.kotlin.times import org.mockito.kotlin.verify import org.mockito.kotlin.verifyNoMoreInteractions import org.mockito.kotlin.whenever +import java.awt.Point import java.io.File import java.nio.file.Files import java.util.Queue @@ -2687,7 +2688,8 @@ class ScopesTest { SentryAttribute.named("namedstrattr", "namedstrval"), SentryAttribute.named("namedboolattr", false), SentryAttribute.named("namedintattr", 18), - SentryAttribute.named("nameddoubleattr", 4.9) + SentryAttribute.named("nameddoubleattr", 4.9), + SentryAttribute.flattened("flattenedpoint", Point(10, 20)) ) ), "log message" @@ -2730,6 +2732,18 @@ class ScopesTest { val nameddoubleattr = it.attributes?.get("nameddoubleattr")!! assertEquals(4.9, nameddoubleattr.value) assertEquals("double", nameddoubleattr.type) + + val flattenedpoint = it.attributes?.get("flattenedpoint")!! + assertEquals("java.awt.Point[x=10,y=20]", flattenedpoint.value) + assertEquals("string", flattenedpoint.type) + + val flattenedpointx = it.attributes?.get("flattenedpoint.x")!! + assertEquals(10, flattenedpointx.value) + assertEquals("integer", flattenedpointx.type) + + val flattenedpointy = it.attributes?.get("flattenedpoint.y")!! + assertEquals(20, flattenedpointy.value) + assertEquals("integer", flattenedpointy.type) }, anyOrNull() ) From 628254d5ef20f773fa27e63d59788b49c1f63c39 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Thu, 22 May 2025 12:07:59 +0200 Subject: [PATCH 7/7] fix ctor for type --- .../spring/boot/jakarta/PersonController.java | 29 +++++++++++++++++++ .../sentry/SentryLogEventAttributeValue.java | 3 +- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta/src/main/java/io/sentry/samples/spring/boot/jakarta/PersonController.java b/sentry-samples/sentry-samples-spring-boot-jakarta/src/main/java/io/sentry/samples/spring/boot/jakarta/PersonController.java index 94a4b9b852..9d55275e93 100644 --- a/sentry-samples/sentry-samples-spring-boot-jakarta/src/main/java/io/sentry/samples/spring/boot/jakarta/PersonController.java +++ b/sentry-samples/sentry-samples-spring-boot-jakarta/src/main/java/io/sentry/samples/spring/boot/jakarta/PersonController.java @@ -2,6 +2,12 @@ import io.sentry.ISpan; import io.sentry.Sentry; +import io.sentry.SentryAttribute; +import io.sentry.SentryAttributes; +import io.sentry.SentryLogLevel; +import io.sentry.logger.SentryLogParameters; +import java.awt.Point; +import java.util.Collections; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.bind.annotation.GetMapping; @@ -29,6 +35,29 @@ Person person(@PathVariable Long id) { Sentry.logger().warn("warn Sentry logging"); Sentry.logger().error("error Sentry logging"); Sentry.logger().info("hello %s %s", "there", "world!"); + Sentry.logger() + .log( + SentryLogLevel.ERROR, + SentryLogParameters.create( + null, + SentryAttributes.fromMap(Collections.singletonMap("extra-attr", "attr-value"))), + "hello %s %s", + "there", + "world!"); + Sentry.logger() + .log( + SentryLogLevel.ERROR, + SentryLogParameters.create( + SentryAttributes.of( + SentryAttribute.booleanAttribute("boolattr", true), + SentryAttribute.integerAttribute("intattr", 17), + SentryAttribute.doubleAttribute("doubleattr", 0.8), + SentryAttribute.stringAttribute("strattr", "strval"), + SentryAttribute.named("namedAttr", new Point(10, 20)), + SentryAttribute.flattened("flattenedAttr", new Point(10, 20)))), + "hello %s %s", + "there", + "world!"); LOGGER.error("Trying person with id={}", id, new RuntimeException("error while loading")); throw new IllegalArgumentException("Something went wrong [id=" + id + "]"); } finally { diff --git a/sentry/src/main/java/io/sentry/SentryLogEventAttributeValue.java b/sentry/src/main/java/io/sentry/SentryLogEventAttributeValue.java index e45c79564f..5be45b29e5 100644 --- a/sentry/src/main/java/io/sentry/SentryLogEventAttributeValue.java +++ b/sentry/src/main/java/io/sentry/SentryLogEventAttributeValue.java @@ -24,8 +24,7 @@ public SentryLogEventAttributeValue(final @NotNull String type, final @Nullable public SentryLogEventAttributeValue( final @NotNull SentryAttributeType type, final @Nullable Object value) { - this.type = type.apiName(); - this.value = value; + this(type.apiName(), value); } public @NotNull String getType() {