diff --git a/pdf-umbenenner-application/src/main/java/de/gecheckt/pdf/umbenenner/application/port/out/ProcessingLogger.java b/pdf-umbenenner-application/src/main/java/de/gecheckt/pdf/umbenenner/application/port/out/ProcessingLogger.java index 61f218c..0210f7b 100644 --- a/pdf-umbenenner-application/src/main/java/de/gecheckt/pdf/umbenenner/application/port/out/ProcessingLogger.java +++ b/pdf-umbenenner-application/src/main/java/de/gecheckt/pdf/umbenenner/application/port/out/ProcessingLogger.java @@ -5,6 +5,22 @@ package de.gecheckt.pdf.umbenenner.application.port.out; *
* The application delegates all logging to this port to remain decoupled from * specific logging frameworks. Concrete implementations are provided by adapters. + *
+ *
+ * The {@link #debugSensitiveAiContent(String, Object[])} method allows logging + * of sensitive AI-generated content (complete raw response, complete reasoning) + * subject to the {@link AiContentSensitivity} setting: + *
+ * The complete sensitive content is always persisted in SQLite for traceability, + * regardless of this logging setting. The logging decision controls only whether + * the content also appears in log files. */ public interface ProcessingLogger { @@ -24,6 +40,29 @@ public interface ProcessingLogger { */ void debug(String message, Object... args); + /** + * Logs a debug-level message containing sensitive AI-generated content, + * subject to the configured {@link AiContentSensitivity}. + *
+ * This method is called with message and arguments containing sensitive AI content + * (e.g., complete raw response, complete reasoning). The implementation must: + *
+ * This is the only method where sensitive AI content may be logged based on
+ * configuration. Other logging methods ({@link #info}, {@link #debug}, etc.)
+ * must never log sensitive content.
+ *
+ * @param message the message template (may contain {} placeholders)
+ * @param args optional message arguments that may include sensitive AI content
+ */
+ void debugSensitiveAiContent(String message, Object... args);
+
/**
* Logs a warning-level message.
*
diff --git a/pdf-umbenenner-application/src/main/java/de/gecheckt/pdf/umbenenner/application/service/DocumentProcessingCoordinator.java b/pdf-umbenenner-application/src/main/java/de/gecheckt/pdf/umbenenner/application/service/DocumentProcessingCoordinator.java
index 9ccc80e..e5e722c 100644
--- a/pdf-umbenenner-application/src/main/java/de/gecheckt/pdf/umbenenner/application/service/DocumentProcessingCoordinator.java
+++ b/pdf-umbenenner-application/src/main/java/de/gecheckt/pdf/umbenenner/application/service/DocumentProcessingCoordinator.java
@@ -393,6 +393,16 @@ public class DocumentProcessingCoordinator {
"Status is PROPOSAL_READY but no PROPOSAL_READY attempt exists in history");
}
+ // Log sensitive AI content (raw response, reasoning) if configured
+ if (proposalAttempt.aiRawResponse() != null) {
+ logger.debugSensitiveAiContent("AI raw response for '{}' (fingerprint: {}): {}",
+ candidate.uniqueIdentifier(), fingerprint.sha256Hex(), proposalAttempt.aiRawResponse());
+ }
+ if (proposalAttempt.aiReasoning() != null) {
+ logger.debugSensitiveAiContent("AI reasoning for '{}' (fingerprint: {}): {}",
+ candidate.uniqueIdentifier(), fingerprint.sha256Hex(), proposalAttempt.aiReasoning());
+ }
+
// --- Step 2: Build base filename from the proposal ---
TargetFilenameBuildingService.BaseFilenameResult filenameResult =
TargetFilenameBuildingService.buildBaseFilename(proposalAttempt);
diff --git a/pdf-umbenenner-application/src/test/java/de/gecheckt/pdf/umbenenner/application/service/DocumentProcessingCoordinatorTest.java b/pdf-umbenenner-application/src/test/java/de/gecheckt/pdf/umbenenner/application/service/DocumentProcessingCoordinatorTest.java
index 810b471..a00b210 100644
--- a/pdf-umbenenner-application/src/test/java/de/gecheckt/pdf/umbenenner/application/service/DocumentProcessingCoordinatorTest.java
+++ b/pdf-umbenenner-application/src/test/java/de/gecheckt/pdf/umbenenner/application/service/DocumentProcessingCoordinatorTest.java
@@ -1280,6 +1280,11 @@ class DocumentProcessingCoordinatorTest {
// No-op
}
+ @Override
+ public void debugSensitiveAiContent(String message, Object... args) {
+ // No-op: sensitivity is controlled by the Log4jProcessingLogger adapter
+ }
+
@Override
public void warn(String message, Object... args) {
// No-op
@@ -1367,6 +1372,7 @@ class DocumentProcessingCoordinatorTest {
private static class CapturingProcessingLogger implements ProcessingLogger {
int infoCallCount = 0;
int debugCallCount = 0;
+ int debugSensitiveAiContentCallCount = 0;
int warnCallCount = 0;
int errorCallCount = 0;
@@ -1380,6 +1386,11 @@ class DocumentProcessingCoordinatorTest {
debugCallCount++;
}
+ @Override
+ public void debugSensitiveAiContent(String message, Object... args) {
+ debugSensitiveAiContentCallCount++;
+ }
+
@Override
public void warn(String message, Object... args) {
warnCallCount++;
diff --git a/pdf-umbenenner-application/src/test/java/de/gecheckt/pdf/umbenenner/application/usecase/BatchRunProcessingUseCaseTest.java b/pdf-umbenenner-application/src/test/java/de/gecheckt/pdf/umbenenner/application/usecase/BatchRunProcessingUseCaseTest.java
index 2eeb5f3..1e65278 100644
--- a/pdf-umbenenner-application/src/test/java/de/gecheckt/pdf/umbenenner/application/usecase/BatchRunProcessingUseCaseTest.java
+++ b/pdf-umbenenner-application/src/test/java/de/gecheckt/pdf/umbenenner/application/usecase/BatchRunProcessingUseCaseTest.java
@@ -1157,6 +1157,11 @@ class BatchRunProcessingUseCaseTest {
// No-op
}
+ @Override
+ public void debugSensitiveAiContent(String message, Object... args) {
+ // No-op: sensitivity is controlled by the Log4jProcessingLogger adapter
+ }
+
@Override
public void warn(String message, Object... args) {
// No-op
@@ -1175,6 +1180,7 @@ class BatchRunProcessingUseCaseTest {
private static class MessageCapturingProcessingLogger implements ProcessingLogger {
final List
@@ -13,18 +16,45 @@ import org.apache.logging.log4j.Logger;
*
* The error method intelligently detects if the last argument is a Throwable
* and logs accordingly.
+ *
+ *
+ * The adapter is initialized with an {@link AiContentSensitivity} setting that
+ * controls whether sensitive AI-generated content (complete raw response, reasoning)
+ * may be written to log files:
+ *
+ * Uses {@link AiContentSensitivity#PROTECT_SENSITIVE_CONTENT} as the default.
*
* @param clazz the class to derive the logger name from; must not be null
*/
public Log4jProcessingLogger(Class> clazz) {
+ this(clazz, AiContentSensitivity.PROTECT_SENSITIVE_CONTENT);
+ }
+
+ /**
+ * Creates a logger instance for the given class with the specified sensitivity setting.
+ *
+ * @param clazz the class to derive the logger name from; must not be null
+ * @param aiContentSensitivity the sensitivity setting for AI content logging; must not be null
+ */
+ public Log4jProcessingLogger(Class> clazz, AiContentSensitivity aiContentSensitivity) {
this.log4jLogger = LogManager.getLogger(clazz);
+ this.aiContentSensitivity = Objects.requireNonNull(aiContentSensitivity,
+ "aiContentSensitivity must not be null");
}
@Override
@@ -37,6 +67,15 @@ public class Log4jProcessingLogger implements ProcessingLogger {
log4jLogger.debug(message, args);
}
+ @Override
+ public void debugSensitiveAiContent(String message, Object... args) {
+ // Only log sensitive content if explicitly enabled
+ if (aiContentSensitivity == AiContentSensitivity.LOG_SENSITIVE_CONTENT) {
+ log4jLogger.debug(message, args);
+ }
+ // Otherwise emit nothing (protect by default)
+ }
+
@Override
public void warn(String message, Object... args) {
log4jLogger.warn(message, args);
diff --git a/pdf-umbenenner-bootstrap/src/test/java/de/gecheckt/pdf/umbenenner/bootstrap/adapter/Log4jProcessingLoggerTest.java b/pdf-umbenenner-bootstrap/src/test/java/de/gecheckt/pdf/umbenenner/bootstrap/adapter/Log4jProcessingLoggerTest.java
index 8625318..3083aba 100644
--- a/pdf-umbenenner-bootstrap/src/test/java/de/gecheckt/pdf/umbenenner/bootstrap/adapter/Log4jProcessingLoggerTest.java
+++ b/pdf-umbenenner-bootstrap/src/test/java/de/gecheckt/pdf/umbenenner/bootstrap/adapter/Log4jProcessingLoggerTest.java
@@ -1,5 +1,6 @@
package de.gecheckt.pdf.umbenenner.bootstrap.adapter;
+import de.gecheckt.pdf.umbenenner.application.port.out.AiContentSensitivity;
import de.gecheckt.pdf.umbenenner.application.port.out.ProcessingLogger;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.BeforeEach;
@@ -188,4 +189,54 @@ class Log4jProcessingLoggerTest {
() -> assertDoesNotThrow(() -> logger.error(testMessage, exception))
);
}
+
+ @Test
+ void debugSensitiveAiContent_withDefaultSensitivity_acceptsMessage() {
+ // Verify debugSensitiveAiContent method exists and executes with default PROTECT sensitivity
+ Log4jProcessingLogger protectedLogger = new Log4jProcessingLogger(
+ Log4jProcessingLoggerTest.class,
+ AiContentSensitivity.PROTECT_SENSITIVE_CONTENT);
+
+ assertDoesNotThrow(() -> {
+ protectedLogger.debugSensitiveAiContent("Sensitive content: {}", "raw AI response");
+ }, "debugSensitiveAiContent() should execute without throwing with PROTECT_SENSITIVE_CONTENT");
+ }
+
+ @Test
+ void debugSensitiveAiContent_withLogSensitivity_acceptsMessage() {
+ // Verify debugSensitiveAiContent method executes with LOG_SENSITIVE_CONTENT setting
+ Log4jProcessingLogger logSensitiveLogger = new Log4jProcessingLogger(
+ Log4jProcessingLoggerTest.class,
+ AiContentSensitivity.LOG_SENSITIVE_CONTENT);
+
+ assertDoesNotThrow(() -> {
+ logSensitiveLogger.debugSensitiveAiContent("AI reasoning: {}", "complete reasoning text");
+ }, "debugSensitiveAiContent() should execute without throwing with LOG_SENSITIVE_CONTENT");
+ }
+
+ @Test
+ void constructorWithSensitivity_acceptsProtectSensitiveContent() {
+ // Verify constructor accepts PROTECT_SENSITIVE_CONTENT
+ Log4jProcessingLogger logger1 = new Log4jProcessingLogger(
+ Log4jProcessingLoggerTest.class,
+ AiContentSensitivity.PROTECT_SENSITIVE_CONTENT);
+ assertNotNull(logger1, "Logger should be created with PROTECT_SENSITIVE_CONTENT");
+ }
+
+ @Test
+ void constructorWithSensitivity_acceptsLogSensitiveContent() {
+ // Verify constructor accepts LOG_SENSITIVE_CONTENT
+ Log4jProcessingLogger logger2 = new Log4jProcessingLogger(
+ Log4jProcessingLoggerTest.class,
+ AiContentSensitivity.LOG_SENSITIVE_CONTENT);
+ assertNotNull(logger2, "Logger should be created with LOG_SENSITIVE_CONTENT");
+ }
+
+ @Test
+ void constructorWithSensitivity_rejectNullSensitivity() {
+ // Verify constructor requires non-null AiContentSensitivity
+ assertThrows(NullPointerException.class, () -> {
+ new Log4jProcessingLogger(Log4jProcessingLoggerTest.class, null);
+ }, "Constructor should reject null AiContentSensitivity");
+ }
}
Sensitive AI content control
+ *
+ *
*/
public class Log4jProcessingLogger implements ProcessingLogger {
private final Logger log4jLogger;
+ private final AiContentSensitivity aiContentSensitivity;
/**
- * Creates a logger instance for the given class.
+ * Creates a logger instance for the given class with default sensitivity setting.
+ *