diff --git a/etl-service/src/main/java/com/xtrmetl/etl/controller/EtlApiProblemHandler.java b/etl-service/src/main/java/com/xtrmetl/etl/controller/EtlApiProblemHandler.java
index 6ab4ff3e..81a0e489 100644
--- a/etl-service/src/main/java/com/xtrmetl/etl/controller/EtlApiProblemHandler.java
+++ b/etl-service/src/main/java/com/xtrmetl/etl/controller/EtlApiProblemHandler.java
@@ -24,7 +24,8 @@
*
The advice is scoped to the synchronous and durable-job ETL controllers; unrelated
* controllers retain their existing exception contracts. Every client-visible field is fixed by a
* typed definition. Raw exception messages, SQL details, credentials, paths, causes, and stack
- * traces are never copied into the HTTP body. Covered responses use
+ * traces are never copied into the HTTP body. Ordinary failure logs keep bounded classifications
+ * without request resource paths or owner-scoped identifiers. Covered responses use
* {@code Cache-Control: no-store} so authenticated operational failures are not retained by shared
* or private caches. Spring MVC routing and response-negotiation failures are deliberately not
* captured by a broad exception handler and therefore retain their framework-owned status
@@ -58,11 +59,7 @@ public ResponseEntity handleRequestFailure(
HttpServletRequest request
) {
EtlRequestError error = exception.error();
- log.debug(
- "Rejected ETL request path={} code={}",
- request.getRequestURI(),
- error.errorCode()
- );
+ log.debug("Rejected ETL request code={}", error.errorCode());
return problem(
error.status(),
error.type(),
@@ -86,8 +83,7 @@ public ResponseEntity handleUnreadableBody(
HttpServletRequest request
) {
log.debug(
- "Rejected unreadable ETL request path={} exceptionType={}",
- request.getRequestURI(),
+ "Rejected unreadable ETL request exceptionType={}",
exception.getClass().getName()
);
EtlRequestError error = EtlRequestError.INVALID_JSON;
@@ -114,8 +110,7 @@ public ResponseEntity handleTransientTargetFailure(
HttpServletRequest request
) {
log.warn(
- "Transient ETL target failure path={} exceptionType={}",
- request.getRequestURI(),
+ "Transient ETL target failure exceptionType={}",
exception.getClass().getName()
);
return problem(
@@ -141,8 +136,7 @@ public ResponseEntity handleTargetFailure(
HttpServletRequest request
) {
log.error(
- "ETL target failure path={} exceptionType={}",
- request.getRequestURI(),
+ "ETL target failure exceptionType={}",
exception.getClass().getName()
);
return problem(
@@ -168,8 +162,7 @@ public ResponseEntity handleUnexpectedFailure(
HttpServletRequest request
) {
log.error(
- "Unexpected ETL failure path={} exceptionType={}",
- request.getRequestURI(),
+ "Unexpected ETL failure exceptionType={}",
exception.getCause().getClass().getName()
);
return problem(
diff --git a/etl-service/src/test/java/com/xtrmetl/etl/controller/EtlApiProblemHandlerLoggingTest.java b/etl-service/src/test/java/com/xtrmetl/etl/controller/EtlApiProblemHandlerLoggingTest.java
new file mode 100644
index 00000000..2074d590
--- /dev/null
+++ b/etl-service/src/test/java/com/xtrmetl/etl/controller/EtlApiProblemHandlerLoggingTest.java
@@ -0,0 +1,38 @@
+package com.xtrmetl.etl.controller;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.boot.test.system.CapturedOutput;
+import org.springframework.boot.test.system.OutputCaptureExtension;
+import org.springframework.dao.DataIntegrityViolationException;
+import org.springframework.mock.web.MockHttpServletRequest;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Verifies that ordinary ETL failure logs retain bounded classifications without resource IDs.
+ */
+@ExtendWith(OutputCaptureExtension.class)
+class EtlApiProblemHandlerLoggingTest {
+
+ private static final String JOB_RECORD_ID = "0198f4cf-41c8-7f52-9e5d-private-job-marker";
+
+ @Test
+ void targetFailureLogDoesNotRepublishOwnerScopedResourceIdentifier(CapturedOutput output) {
+ MockHttpServletRequest request = new MockHttpServletRequest(
+ "GET",
+ "/api/etl/jobs/" + JOB_RECORD_ID
+ );
+ EtlApiProblemHandler handler = new EtlApiProblemHandler();
+
+ handler.handleTargetFailure(
+ new DataIntegrityViolationException("synthetic database diagnostic"),
+ request
+ );
+
+ String logs = output.getOut() + output.getErr();
+ assertTrue(logs.contains("ETL target failure"));
+ assertFalse(logs.contains(JOB_RECORD_ID));
+ }
+}