#89 #90: Log-Verzeichnis-Prüfpunkt + betrieb.md MSI-Pfadwarnungen

#90: Neuer technischer Prüfpunkt LOG_DIRECTORY_USABLE (12. Checkpoint):
- Zeigt konfigurierten log.directory-Wert und aufgelösten absoluten Pfad
- Prüft ob Verzeichnis beschreibbar/anlegbar ist (WARNING, kein ERROR)
- Liest tatsächlichen Log-Datei-Pfad via Log4j2 LoggerContext → RollingFileAppender
- LogDiagnosticsPort als neuer Outbound-Port (application-Modul)
- Log4jLogDiagnosticsAdapter als Implementierung im bootstrap-Modul
- TechnicalTestRequest erhält logDirectory-Feld
- GuiTechnicalTestCoordinator erhält logDirectoryProvider-Supplier

#89: docs/betrieb.md – MSI-Betrieb um Pfadwarnungen erweitert:
- Warnung: relative Pfade lösen sich in schreibgeschütztes C:\Program Files\ auf
- Warnung: Backslashes in .properties werden als Java-Escape-Sequenzen interpretiert
- Betroffene Parameter mit Empfehlung zu absoluten Forward-Slash-Pfaden
- Beschreibung des neuen Log-Verzeichnis-Prüfpunkts

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-04 17:02:51 +02:00
parent bd2be347f6
commit 479d176536
23 changed files with 376 additions and 88 deletions
@@ -109,6 +109,7 @@ import de.gecheckt.pdf.umbenenner.application.validation.technicaltest.ProviderT
import de.gecheckt.pdf.umbenenner.application.validation.technicaltest.TechnicalTestOrchestrator;
import de.gecheckt.pdf.umbenenner.bootstrap.adapter.AiModelCatalogDispatcher;
import de.gecheckt.pdf.umbenenner.bootstrap.adapter.GuiConfigurationPropertiesWriter;
import de.gecheckt.pdf.umbenenner.bootstrap.adapter.Log4jLogDiagnosticsAdapter;
import de.gecheckt.pdf.umbenenner.bootstrap.adapter.Log4jProcessingLogger;
import de.gecheckt.pdf.umbenenner.bootstrap.singleinstance.AnotherInstanceRunningException;
import de.gecheckt.pdf.umbenenner.bootstrap.singleinstance.SingleInstanceGuard;
@@ -807,7 +808,8 @@ public class BootstrapRunner {
TechnicalTestOrchestrator technicalTestOrchestrator = new TechnicalTestOrchestrator(
new EditorConfigurationValidator(),
pathCheckPort,
providerTechnicalTestService);
providerTechnicalTestService,
new Log4jLogDiagnosticsAdapter());
de.gecheckt.pdf.umbenenner.application.validation.technicaltest.CorrectionExecutionService correctionExecutionService =
new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.CorrectionExecutionService(
new de.gecheckt.pdf.umbenenner.adapter.out.resourcecreation.FilesystemResourceCreationAdapter());
@@ -0,0 +1,68 @@
package de.gecheckt.pdf.umbenenner.bootstrap.adapter;
import java.nio.file.InvalidPathException;
import java.nio.file.Paths;
import java.util.Optional;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.appender.RollingFileAppender;
import de.gecheckt.pdf.umbenenner.application.validation.technicaltest.LogDiagnosticsPort;
/**
* Log4j2-basierte Implementierung von {@link LogDiagnosticsPort}.
* <p>
* Liest den aktiven {@link RollingFileAppender} aus dem laufenden {@link LoggerContext}
* und gibt dessen absoluten Dateipfad zurück. Diese Information zeigt, wo Log-Einträge
* tatsächlich landen unabhängig vom konfigurierten {@code log.directory}-Wert in der
* Properties-Datei.
* <p>
* Gibt einen leeren {@link Optional} zurück, wenn kein dateibasierter Appender aktiv ist,
* der Kontext nicht auslesbar ist oder der Pfad nicht zu einem absoluten Pfad aufgelöst
* werden kann.
*/
public class Log4jLogDiagnosticsAdapter implements LogDiagnosticsPort {
/**
* Erstellt einen neuen {@code Log4jLogDiagnosticsAdapter}.
*/
public Log4jLogDiagnosticsAdapter() {
// zustandslos
}
/**
* Ermittelt den absoluten Pfad der aktiven Log-Datei aus dem laufenden Log4j2-Kontext.
* <p>
* Durchsucht die Appender-Liste des aktiven {@link LoggerContext} nach dem ersten
* {@link RollingFileAppender} und gibt dessen {@code fileName} als absoluten Pfad zurück.
* Alle Ausnahmen werden abgefangen und als leeres Ergebnis zurückgegeben.
*
* @return absoluter Pfad der aktiven Log-Datei; leer wenn nicht bestimmbar
*/
@Override
public Optional<String> resolveActiveLogFilePath() {
try {
LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
return ctx.getConfiguration().getAppenders().values().stream()
.filter(a -> a instanceof RollingFileAppender)
.map(a -> (RollingFileAppender) a)
.map(RollingFileAppender::getFileName)
.filter(name -> name != null && !name.isBlank())
.map(this::toAbsolutePath)
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst();
} catch (Exception e) {
return Optional.empty();
}
}
private Optional<String> toAbsolutePath(String path) {
try {
return Optional.of(Paths.get(path).toAbsolutePath().toString());
} catch (InvalidPathException e) {
return Optional.empty();
}
}
}