Skip to content

Commit 8607a39

Browse files
Konstantin PavlovKonstantin Pavlov
authored andcommitted
Introduce PII marker for logging sensitive data
Introduce a utility class `LoggingMarkers` providing an SLF4J marker for tagging log entries with Personally Identifiable Information (PII). Update `BeanOutputConverter` to use the `PII_MARKER` in error logs for invalid JSON conversions. Enhance tests to verify PII marker usage in logging. Signed-off-by: Konstantin Pavlov <{ID}+{username}@users.noreply.github.com>
1 parent 3c539a3 commit 8607a39

File tree

4 files changed

+64
-6
lines changed

4 files changed

+64
-6
lines changed

spring-ai-core/src/main/java/org/springframework/ai/converter/BeanOutputConverter.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@
4141
import org.springframework.core.ParameterizedTypeReference;
4242
import org.springframework.lang.NonNull;
4343

44+
import static org.springframework.ai.util.LoggingMarkers.PII_MARKER;
45+
4446
/**
4547
* An implementation of {@link StructuredOutputConverter} that transforms the LLM output
4648
* to a specific object type using JSON schema. This converter works by generating a JSON
@@ -180,7 +182,8 @@ public T convert(@NonNull String text) {
180182
return (T) this.objectMapper.readValue(text, this.objectMapper.constructType(this.type));
181183
}
182184
catch (JsonProcessingException e) {
183-
logger.error("Could not parse the given text to the desired target type:" + text + " into " + this.type);
185+
logger.error(PII_MARKER,
186+
"Could not parse the given text to the desired target type:" + text + " into " + this.type);
184187
throw new RuntimeException(e);
185188
}
186189
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package org.springframework.ai.util;
2+
3+
import org.slf4j.Marker;
4+
import org.slf4j.MarkerFactory;
5+
6+
/**
7+
* Utility class that provides predefined SLF4J {@link Marker} instances used in logging
8+
* operations within the application. <br>
9+
* This class is not intended to be instantiated.
10+
*/
11+
public class LoggingMarkers {
12+
13+
/**
14+
* Marker instance representing Personally Identifiable Information (PII) used in
15+
* logging operations to classify or tag log entries for sensitive data. This can be
16+
* utilized to allow selective filtering, handling, or analysis of log messages
17+
* containing PII.
18+
*/
19+
public static final Marker PII_MARKER = MarkerFactory.getMarker("PII");
20+
21+
private LoggingMarkers() {
22+
// Prevent instantiation of this utility class
23+
}
24+
25+
}

spring-ai-core/src/test/java/org/springframework/ai/converter/BeanOutputConverterTest.java

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,25 +16,32 @@
1616

1717
package org.springframework.ai.converter;
1818

19-
import java.time.LocalDate;
20-
import java.util.ArrayList;
21-
import java.util.List;
22-
19+
import ch.qos.logback.classic.Logger;
20+
import ch.qos.logback.classic.spi.ILoggingEvent;
21+
import ch.qos.logback.core.read.ListAppender;
2322
import com.fasterxml.jackson.annotation.JsonProperty;
2423
import com.fasterxml.jackson.annotation.JsonPropertyDescription;
2524
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
25+
import com.fasterxml.jackson.core.JsonParseException;
2626
import com.fasterxml.jackson.databind.DeserializationFeature;
2727
import com.fasterxml.jackson.databind.JsonNode;
2828
import com.fasterxml.jackson.databind.ObjectMapper;
29+
import org.junit.jupiter.api.BeforeEach;
2930
import org.junit.jupiter.api.Nested;
3031
import org.junit.jupiter.api.Test;
3132
import org.junit.jupiter.api.extension.ExtendWith;
3233
import org.mockito.Mock;
3334
import org.mockito.junit.jupiter.MockitoExtension;
34-
35+
import org.slf4j.LoggerFactory;
3536
import org.springframework.core.ParameterizedTypeReference;
3637

38+
import java.time.LocalDate;
39+
import java.util.ArrayList;
40+
import java.util.List;
41+
3742
import static org.assertj.core.api.Assertions.assertThat;
43+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
44+
import static org.springframework.ai.util.LoggingMarkers.PII_MARKER;
3845

3946
/**
4047
* @author Sebastian Ullrich
@@ -45,9 +52,21 @@
4552
@ExtendWith(MockitoExtension.class)
4653
class BeanOutputConverterTest {
4754

55+
private ListAppender<ILoggingEvent> logAppender;
56+
4857
@Mock
4958
private ObjectMapper objectMapperMock;
5059

60+
@BeforeEach
61+
void beforeEach() {
62+
63+
var logger = (Logger) LoggerFactory.getLogger(BeanOutputConverter.class);
64+
65+
logAppender = new ListAppender<>();
66+
logAppender.start();
67+
logger.addAppender(logAppender);
68+
}
69+
5170
@Test
5271
void shouldHavePreConfiguredDefaultObjectMapper() {
5372
var converter = new BeanOutputConverter<>(new ParameterizedTypeReference<TestClass>() {
@@ -135,6 +154,16 @@ void convertClassType() {
135154
assertThat(testClass.getSomeString()).isEqualTo("some value");
136155
}
137156

157+
@Test
158+
void failToConvertInvalidJson() {
159+
var converter = new BeanOutputConverter<>(TestClass.class);
160+
assertThatThrownBy(() -> converter.convert("{invalid json")).hasCauseInstanceOf(JsonParseException.class);
161+
final var loggingEvent = logAppender.list.getFirst();
162+
assertThat(loggingEvent.getMessage()).isEqualTo(
163+
"Could not parse the given text to the desired target type:{invalid json into " + TestClass.class);
164+
assertThat(loggingEvent.getMarkerList()).contains(PII_MARKER);
165+
}
166+
138167
@Test
139168
void convertClassWithDateType() {
140169
var converter = new BeanOutputConverter<>(TestClassWithDateProperty.class);

spring-ai-docs/src/main/antora/modules/ROOT/pages/contribution-guidelines.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ you'll need to develop a low-level client API class. This often involves utilizi
3030
Ensure your client conforms to the link:https://docs.spring.io/spring-ai/reference/api/generic-model.html[Generic Model API].
3131
Use existing request and response classes if your model's inputs and outputs are supported.
3232
If not, create new classes for the Generic Model API and establish a new Java package.
33+
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.
3334

3435
. *Implement Auto-Configuration and a Spring Boot Starter*: This step involves creating the
3536
necessary auto-configuration and Spring Boot Starter to easily instantiate the new model with

0 commit comments

Comments
 (0)