fix(appointment): retain safe synchronization failure diagnostics

This commit is contained in:
2026-10-15 17:18:09 +08:00
parent 4f9a63fb01
commit 14b8cc625b
2 changed files with 30 additions and 1 deletions
@@ -21,7 +21,20 @@ public class AppointmentWorkSynchronizationJob {
cursor=result.complete()?null:result.nextCursor();
} catch(RuntimeException error) {
cursor=null;
log.warn("Appointment work synchronization incomplete; next scan restarts from first page: {}",error.getClass().getSimpleName());
log.warn("Appointment work synchronization incomplete; next scan restarts from first page: {}",failureDiagnostic(error));
}
}
/** Keep causes and code locations, never exception messages that may contain remote payloads or credentials. */
static String failureDiagnostic(Throwable error) {
var causes=new java.util.ArrayList<String>();
var seen=java.util.Collections.newSetFromMap(new java.util.IdentityHashMap<Throwable,Boolean>());
for(var current=error;current!=null && causes.size()<6 && seen.add(current);current=current.getCause()) {
String status=current instanceof org.springframework.web.server.ResponseStatusException response?"["+response.getStatusCode().value()+"]":"";
String location=java.util.Arrays.stream(current.getStackTrace())
.filter(frame->frame.getClassName().startsWith("com.baiyue.") && !frame.getClassName().equals(AppointmentWorkPolicy.class.getName()))
.findFirst().map(frame->"@"+frame.getClassName()+":"+frame.getLineNumber()).orElse("");
causes.add(current.getClass().getSimpleName()+status+location);
}
return String.join(" <- ",causes);
}
}
@@ -0,0 +1,16 @@
package com.baiyue.lastleg.appointmentwork;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpStatus;
import org.springframework.web.server.ResponseStatusException;
import static org.assertj.core.api.Assertions.assertThat;
class AppointmentWorkSynchronizationJobTest {
@Test void diagnosticKeepsCauseAndCodeWithoutRemoteMessages() {
var cause=new IllegalArgumentException("Authorization: Bearer secret; private remote body");
var error=new ResponseStatusException(HttpStatus.SERVICE_UNAVAILABLE,"private response",cause);
assertThat(AppointmentWorkSynchronizationJob.failureDiagnostic(error))
.contains("ResponseStatusException[503]", "IllegalArgumentException", "AppointmentWorkSynchronizationJobTest:")
.doesNotContain("secret", "private", "Authorization", "Bearer");
}
}