diff --git a/pom.xml b/pom.xml
index 84187c76c60..2badae5e717 100644
--- a/pom.xml
+++ b/pom.xml
@@ -168,8 +168,9 @@
UTF-8
UTF-8
17
- 17
- 17
+ ${java.version}
+ ${java.version}
+ ${java.version}
3.3.6
@@ -344,6 +345,10 @@
org.jetbrains.kotlin
kotlin-maven-plugin
${kotlin.version}
+
+ ${java.version}
+ true
+
compile
diff --git a/spring-ai-core/src/main/java/org/springframework/ai/converter/BeanOutputConverter.java b/spring-ai-core/src/main/java/org/springframework/ai/converter/BeanOutputConverter.java
index ea7cf314194..64b64a77a78 100644
--- a/spring-ai-core/src/main/java/org/springframework/ai/converter/BeanOutputConverter.java
+++ b/spring-ai-core/src/main/java/org/springframework/ai/converter/BeanOutputConverter.java
@@ -41,6 +41,8 @@
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.lang.NonNull;
+import static org.springframework.ai.util.LoggingMarkers.SENSITIVE_DATA_MARKER;
+
/**
* An implementation of {@link StructuredOutputConverter} that transforms the LLM output
* to a specific object type using JSON schema. This converter works by generating a JSON
@@ -143,7 +145,7 @@ private void generateSchema() {
this.jsonSchema = objectWriter.writeValueAsString(jsonNode);
}
catch (JsonProcessingException e) {
- logger.error("Could not pretty print json schema for jsonNode: " + jsonNode);
+ logger.error("Could not pretty print json schema for jsonNode: {}", jsonNode);
throw new RuntimeException("Could not pretty print json schema for " + this.type, e);
}
}
@@ -180,7 +182,8 @@ public T convert(@NonNull String text) {
return (T) this.objectMapper.readValue(text, this.objectMapper.constructType(this.type));
}
catch (JsonProcessingException e) {
- logger.error("Could not parse the given text to the desired target type:" + text + " into " + this.type);
+ logger.error(SENSITIVE_DATA_MARKER,
+ "Could not parse the given text to the desired target type: \"{}\" into {}", text, this.type);
throw new RuntimeException(e);
}
}
diff --git a/spring-ai-core/src/main/java/org/springframework/ai/util/LoggingMarkers.java b/spring-ai-core/src/main/java/org/springframework/ai/util/LoggingMarkers.java
new file mode 100644
index 00000000000..a4c31e3b6df
--- /dev/null
+++ b/spring-ai-core/src/main/java/org/springframework/ai/util/LoggingMarkers.java
@@ -0,0 +1,69 @@
+package org.springframework.ai.util;
+
+import org.slf4j.Marker;
+import org.slf4j.MarkerFactory;
+
+/**
+ * Utility class that provides predefined SLF4J {@link Marker} instances used in logging
+ * operations within the application.
+ * This class is not intended to be instantiated, but is open for extension.
+ */
+public class LoggingMarkers {
+
+ /**
+ * Marker used to identify log statements associated with sensitive
+ * data, such as:
+ *
+ * - Internal business information
+ * - Employee data
+ * - Customer non-regulated data
+ * - Business processes and logic
+ * - etc.
+ *
+ * Typically, logging this information should be avoided.
+ */
+ public static final Marker SENSITIVE_DATA_MARKER = MarkerFactory.getMarker("SENSITIVE");
+
+ /**
+ * Marker used to identify log statements associated with restricted
+ * data, such as:
+ *
+ * - Authentication credentials
+ * - Keys and secrets
+ * - Core intellectual property
+ * - Critical security configs
+ * - Trade secrets
+ * - etc.
+ *
+ * Logging of such information is usually prohibited in any circumstances.
+ */
+ public static final Marker RESTRICTED_DATA_MARKER = MarkerFactory.getMarker("RESTRICTED");
+
+ /**
+ * Marker used to identify log statements associated with regulated
+ * data, such as:
+ *
+ * - PCI (credit card data)
+ * - PHI (health information)
+ * - PII (personally identifiable info)
+ * - Financial records
+ * - Compliance-controlled data
+ * - etc.
+ *
+ * Logging of such information should be avoided.
+ */
+ public static final Marker REGULATED_DATA_MARKER = MarkerFactory.getMarker("REGULATED");
+
+ /**
+ * Marker used to identify log statements associated with public
+ * data, such as:
+ *
+ * - Public documentation
+ * - Marketing materials
+ * - etc.
+ *
+ * There are no restriction for logging such information.
+ */
+ public static final Marker PUBLIC_DATA_MARKER = MarkerFactory.getMarker("PUBLIC");
+
+}
diff --git a/spring-ai-core/src/test/java/org/springframework/ai/converter/BeanOutputConverterTest.java b/spring-ai-core/src/test/java/org/springframework/ai/converter/BeanOutputConverterTest.java
index ec3f5c5f92f..d8b83c0b115 100644
--- a/spring-ai-core/src/test/java/org/springframework/ai/converter/BeanOutputConverterTest.java
+++ b/spring-ai-core/src/test/java/org/springframework/ai/converter/BeanOutputConverterTest.java
@@ -16,25 +16,32 @@
package org.springframework.ai.converter;
-import java.time.LocalDate;
-import java.util.ArrayList;
-import java.util.List;
-
+import ch.qos.logback.classic.Logger;
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.core.read.ListAppender;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyDescription;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
+import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
-
+import org.slf4j.LoggerFactory;
import org.springframework.core.ParameterizedTypeReference;
+import java.time.LocalDate;
+import java.util.ArrayList;
+import java.util.List;
+
import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.springframework.ai.util.LoggingMarkers.SENSITIVE_DATA_MARKER;
/**
* @author Sebastian Ullrich
@@ -45,9 +52,21 @@
@ExtendWith(MockitoExtension.class)
class BeanOutputConverterTest {
+ private ListAppender logAppender;
+
@Mock
private ObjectMapper objectMapperMock;
+ @BeforeEach
+ void beforeEach() {
+
+ var logger = (Logger) LoggerFactory.getLogger(BeanOutputConverter.class);
+
+ logAppender = new ListAppender<>();
+ logAppender.start();
+ logger.addAppender(logAppender);
+ }
+
@Test
void shouldHavePreConfiguredDefaultObjectMapper() {
var converter = new BeanOutputConverter<>(new ParameterizedTypeReference() {
@@ -135,6 +154,19 @@ void convertClassType() {
assertThat(testClass.getSomeString()).isEqualTo("some value");
}
+ @Test
+ void failToConvertInvalidJson() {
+ var converter = new BeanOutputConverter<>(TestClass.class);
+ assertThatThrownBy(() -> converter.convert("{invalid json")).hasCauseInstanceOf(JsonParseException.class);
+ assertThat(logAppender.list).hasSize(1);
+ final var loggingEvent = logAppender.list.get(0);
+ assertThat(loggingEvent.getFormattedMessage())
+ .isEqualTo("Could not parse the given text to the desired target type: \"{invalid json\" into "
+ + TestClass.class);
+
+ assertThat(loggingEvent.getMarkerList()).contains(SENSITIVE_DATA_MARKER);
+ }
+
@Test
void convertClassWithDateType() {
var converter = new BeanOutputConverter<>(TestClassWithDateProperty.class);
diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/contribution-guidelines.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/contribution-guidelines.adoc
index 28ecfad59c5..8776bdcc70d 100644
--- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/contribution-guidelines.adoc
+++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/contribution-guidelines.adoc
@@ -30,6 +30,7 @@ you'll need to develop a low-level client API class. This often involves utilizi
Ensure your client conforms to the link:https://docs.spring.io/spring-ai/reference/api/generic-model.html[Generic Model API].
Use existing request and response classes if your model's inputs and outputs are supported.
If not, create new classes for the Generic Model API and establish a new Java package.
+Be careful with logging Personally Identifiable Information (PII), mark it with https://github.com/spring-projects/spring-ai/tree/main/spring-ai-core/src/main/java/org/springframework/ai/util/LoggingMarkers.java[`PII_MARKER`] Slf4j marker.
. *Implement Auto-Configuration and a Spring Boot Starter*: This step involves creating the
necessary auto-configuration and Spring Boot Starter to easily instantiate the new model with