Robustheit und Betrieb: Fehlerseiten, Actuator und Logging für den Flurmonitor
https://bildung.social/@oerinformatik/
https://oer-informatik.de/flurmonitor-robustheit-betrieb
Spring Boot, Thymeleaf, Fehlerseite, ControllerAdvice, Actuator, Health-Check, Logging, SLF4J, Nu Html Checker, Sprint Review, Sprint Retrospective, Scrum
tl/dr; (ca. 38 min Lesezeit): Ein Neustart der Datenbank genügt, und der Flurmonitor zeigt eine „Whitelabel Error Page“, ein falsch eingerichteter Monitor sogar JSON. In diesem Artikel wird der Monitor robust: Eigene Fehlerseiten unter
templates/error/ersetzen die Seiten von Spring Boot, ein@ControllerAdvicenur für den Flurmonitor zeigt bei einer Störung einen verständlichen Hinweis, und die Reihenfolge der Advices sorgt dafür, dass Browser HTML und die REST-API weiter Problem Details bekommen. Logging mit SLF4J hält fest, was passiert ist, Spring Boot Actuator meldet über/actuator/healthund/actuator/info, ob die Anwendung läuft. Ein Job in der Pipeline prüft die gerenderten Templates mit dem Nu Html Checker. Zum Schluss schließt das Team Sprint 2 mit Review und Retrospektive ab [AI-Assisted]. (Zuletzt geändert am 11.11.2026)
Dieser Artikel ist Teil einer Artikelreihe über den Flurmonitor mit Thymeleaf: Projektübersicht / Scrum-Projektstart / Thymeleaf einbinden / Templates / Ansichtsmodell / Integrationstest der Web-Schicht / Konfiguration und Formatierung / Aktualisierung / Hinweise mit Formularen / Robustheit und Betrieb / GUI-Test mit Selenium / Akzeptanztest / Change Request Keycloak
Schon fit in diesem Thema? Nutze die Kann-Liste und die Leitfragen unter dem Artikel als Checkliste, ob Du die relevanten Kompetenzen bereits hast.
Dieser Artikel baut auf der Kann-Liste des Artikels Formulare mit Thymeleaf auf.
Als Betriebs-Grundlagen kann ich nach Durchlesen des Artikels…
… beschreiben, wie Spring Boot Fehler einer Web-Anwendung behandelt (
@ExceptionHandler, Weiterleitung an/error, Whitelabel-Seite).… eigene Fehlerseiten unter
templates/error/anlegen (404.html,5xx.html) und die Attributestatusundpathdarin nutzen.… einen
@ControllerAdviceschreiben, der Ausnahmen einzelner Controller in eine verständliche Seite mit passendem Statuscode übersetzt.… mit SLF4J Meldungen in der passenden Stufe (
TRACEbisERROR) mit Platzhaltern loggen und die Stufen je Paket inapplication.propertieseinstellen.… Spring Boot Actuator einbinden und erklären, was
/actuator/healthund/actuator/infomelden.
Darüber hinaus kann ich als Betriebs-Expertenwissen…
… begründen, warum ein
@RestControllerAdviceohne Einschränkung auch HTML-Seiten betrifft, und Advices mitassignableTypes,@Orderundproducesgegeneinander abgrenzen.… Endpunkte des Actuators gezielt freigeben, Details der Health nur für angemeldete Rollen zeigen und den Statuscode
503beiDOWNeinordnen.… gerenderte Templates in der Pipeline mit dem Nu Html Checker prüfen lassen.
… ein Sprint Review und eine Retrospektive mit Velocity, Burn-up-Chart und überprüfbaren Maßnahmen auswerten.
Am Abend von Tag 9 spielt Svenja Krämer von der Schul-IT ein Update auf den Datenbankserver des Mitfahrlabors und startet ihn neu. Am nächsten Morgen meldet sie im Daily Scrum zwei Beobachtungen: Der Monitor in der Mensa, der während des Neustarts eingeschaltet wurde, zeigte bis zum Mittag eine weiße Seite mit der Überschrift „Whitelabel Error Page“ und dem Satz „There was an unexpected error (type=Internal Server Error, status=500)“. Der Monitor im Flur B war nach einem Tippfehler in der Einrichtung auf /flurmonitor/flur-c eingestellt und zeigte eine Zeile JSON. Beides ist genau das, was MON-10 verhindern soll. Der Ablauf, um den sich dieser Artikel dreht, ist der Weg einer Ausnahme durch die Anwendung: Wer fängt sie, und welche Antwort entsteht daraus?
Stand im Projekt: Sprint 2, Tag 10
Nach Tag 9 sind 18 Story Points fertig: die 13 festen und MON-07 aus der Option. Offen ist MON-10 („Verständliche Störungsanzeige“, 3 Story Points), die letzte Option im Sprint Backlog. Nach den Meldungen von Svenja Krämer fragt das Team Product Owner Thomas Becker, ob es MON-10 am letzten Tag noch angehen soll. Er stimmt zu, weil die Störung das Vertrauen in die Monitore gerade erst beschädigt hat, und ergänzt im Refinement zwei Abnahmekriterien:
AK-MON-10.1: Angenommen, die Datenbank ist nicht erreichbar. Wenn der Monitor die Seite lädt, dann zeigt er „Anzeige gerade gestört – die Fahrten findest Du in der App.“ und keine technische Fehlermeldung.
AK-MON-10.2 (neu): Angenommen, die Störung ist behoben. Wenn eine Minute vergangen ist, dann zeigt der Monitor ohne Eingriff wieder die Fahrten.
AK-MON-10.3 (neu): Angenommen, ein Monitor ist mit einer unbekannten Adresse eingerichtet. Wenn er die Seite lädt, dann zeigt er „Seite nicht gefunden“ mit der Adresse statt technischer Daten.
Svenja Krämer bringt einen weiteren Wunsch mit: Die Schul-IT möchte jede Minute automatisch prüfen, ob die Anwendung läuft, ohne dafür die Monitorseite auszuwerten. Und Andrea Wolf möchte als Datenschutzbeauftragte wissen, was die Anwendung bei einer Störung aufschreibt. Die Developers zerlegen MON-10 und setzen die Nummern aus dem letzten Artikel fort:
| Nr. | Aufgabe im Sprint Backlog | Abschnitt |
|---|---|---|
| T43 | Eigene Fehlerseiten error/404.html, error/5xx.html, Sprache auch auf Fehlerseiten |
Fehlerseiten |
| T44 | @ControllerAdvice für den Flurmonitor: Störungsseite mit 503, Neuladen nach einer Minute |
Advice |
| T45 | HTML statt JSON für Browser bei unbekannten Adressen | Reihenfolge |
| T46 | Logging mit SLF4J: Logger, Stufen je Paket, Logdatei, keine personenbezogenen Daten | Logging |
| T47 | Actuator: Health und Info freigeben, Build-Informationen | Actuator |
| T48 | Pipeline-Job templates: gerenderte Seiten mit dem Nu Html Checker prüfen |
Pipeline |
| T49 | Tests und Probe der Störung im Testsystem | Testen |
Wie Spring Boot Fehler behandelt
Wirft eine Controller-Methode eine Ausnahme, fragt Spring MVC der Reihe nach seine HandlerExceptionResolver. Der erste sucht eine Methode mit @ExceptionHandler, zuerst im Controller selbst, dann in allen Klassen mit @ControllerAdvice bzw. @RestControllerAdvice. Der zweite wertet @ResponseStatus an der Ausnahme und ResponseStatusException aus, der dritte übersetzt Standardfehler von Spring MVC (etwa einen falschen Parametertyp) in Statuscodes. Fühlt sich keiner zuständig oder setzt ein Handler nur einen Fehlerstatus mit sendError, übernimmt der Servlet-Container: Er leitet die Anfrage intern an die Adresse /error weiter.
Unter /error antwortet der BasicErrorController von Spring Boot. Er arbeitet mit Content Negotiation: Fragt ein Browser nach text/html, rendert er eine Fehlerseite; jeder andere Client bekommt ein JSON-Objekt mit timestamp, status, error und path. Für die Fehlerseite sucht Spring Boot ein Template in dieser Reihenfolge:
templates/error/<Status>.html, z. B.error/404.html,templates/error/<Statusklasse>.html, alsoerror/4xx.htmlodererror/5xx.html,ein Template
templates/error.html,sonst die eingebaute Whitelabel Error Page.
Damit sind beide Beobachtungen aus dem Daily Scrum erklärt. Der Monitor in der Mensa lief in eine Ausnahme, für die es keinen @ExceptionHandler gab. Spring Boot fand kein eigenes Template und zeigte die Whitelabel-Seite. Der Monitor im Flur B dagegen lief in die ResponseStatusException für unbekannte Monitore. Für die gibt es einen Handler: die ProblemBehandlung aus dem Backend-Artikel zur Fehlerbehandlung der REST-API. Sie ist ein @RestControllerAdvice ohne Einschränkung und gilt deshalb für alle Controller, auch für den FlurmonitorController, der HTML liefern soll. Ihre Antwort ist immer JSON nach RFC 9457 (Problem Details).
Was eine Fehlerseite zeigen darf, regeln Einstellungen mit dem Präfix server.error. Standardmäßig enthält sie weder die Meldung der Ausnahme (server.error.include-message=never) noch den Stacktrace (server.error.include-stacktrace=never). Dabei bleibt es: Eine Fehlermeldung wie „Connection to mariadb:3306 refused“ verrät Angreifenden Details über die Infrastruktur und hilft den Auszubildenden im Flur nicht. Details gehören ins Log.
Eigene Fehlerseiten unter templates/error/
Die erste Aufgabe (T43) ersetzt die Whitelabel-Seite durch Seiten im Aussehen des Monitors. templates/error/404.html nutzt dieselben Fragmente wie die Monitorseite: Kopf, Navigation und Fußzeile aus fragmente/rahmen.html. Das Model füllt der BasicErrorController; das Attribut path enthält die Adresse, die nicht gefunden wurde:
<!DOCTYPE html>
<html lang="de" th:lang="${#locale.language}" xmlns:th="http://www.thymeleaf.org">
<head th:replace="~{fragmente/rahmen :: kopf(#{fehler.404.titel})}">
<meta charset="utf-8">
<title>Mitfahrlabor – Seite nicht gefunden</title>
</head>
<body>
<header>
<nav th:replace="~{fragmente/rahmen :: navigation}" class="navbar bg-primary" data-bs-theme="dark">
<span class="navbar-brand px-4 fs-3 fw-bold">Mitfahrlabor</span>
</nav>
</header>
<main class="container-fluid px-4 py-5">
<h1 class="display-5 fw-bold text-primary" th:text="#{fehler.404.ueberschrift}">Seite nicht gefunden</h1>
<p class="fs-4"><span th:text="#{fehler.404.text}">Diese Adresse gibt es nicht:</span>
<code th:text="${path}">/flurmonitor/flur-c</code></p>
<p class="fs-5"><a href="/flurmonitor/foyer" th:href="@{/flurmonitor/foyer}" th:text="#{fehler.zurueck}">Zum
Flurmonitor im Foyer</a></p>
</main>
<footer th:replace="~{fragmente/rahmen :: fusszeile}"></footer>
</body>
</html>error/5xx.html ist genauso aufgebaut und zeigt nur ${status} und die Bitte, es später noch einmal zu versuchen. Neben status und path stellt der BasicErrorController timestamp und error (den Text zum Statuscode, etwa „Not Found“) bereit, message, exception und trace nur, wenn die passenden server.error.include-…-Einstellungen es erlauben.
Zwei Stellen außerhalb des Templates gehören dazu. Erstens muss /error ohne Anmeldung erreichbar sein, sonst antwortet Spring Security beim Rendern der Fehlerseite mit 401. Die Filterkette für API und Monitor erlaubt das seit dem Backend-Artikel zur Authentifizierung (requestMatchers("/error", …).permitAll()). Zweitens sprach die Seite im ersten Versuch Englisch. Die Klasse MonitorSprache aus dem Artikel Konfiguration und Formatierung wählt die eingestellte Sprache nur für Adressen, die mit /flurmonitor beginnen. Beim Rendern der Fehlerseite lautet die Adresse aber /error. Die ursprüngliche Adresse legt der Servlet-Container in das Attribut jakarta.servlet.error.request_uri:
@Override
public Locale resolveLocale(HttpServletRequest anfrage) {
return adresse(anfrage).startsWith("/flurmonitor") ? monitorSprache : nachBrowser.resolveLocale(anfrage);
}
/**
* Sprint 2 (MON-10): Rendert der Servlet-Container eine Fehlerseite, lautet die Adresse /error; die ursprüngliche
* Adresse steht dann im Attribut jakarta.servlet.error.request_uri.
*/
private static String adresse(HttpServletRequest anfrage) {
Object urspruenglich = anfrage.getAttribute(RequestDispatcher.ERROR_REQUEST_URI);
return urspruenglich instanceof String uri ? uri : anfrage.getRequestURI();
}Ein @ControllerAdvice nur für den Flurmonitor
Eine Fehlerseite für 5xx reicht für AK-MON-10.1 nicht: „Da ist etwas schiefgegangen“ sagt den Auszubildenden nicht, dass sie in der App nachsehen können, und sie antwortet erst, nachdem die Ausnahme den ganzen Weg bis zum Container genommen hat. Für die Störung der Datenbank gibt es deshalb einen eigenen Handler (T44). Ein @ControllerAdvice sammelt @ExceptionHandler-Methoden für mehrere Controller an einer Stelle. Über seine Attribute lässt sich einschränken, für welche Controller er gilt:
| Attribut | gilt für | Beispiel |
|---|---|---|
| ohne Attribut | alle Controller | ProblemBehandlung der REST-API |
assignableTypes |
die genannten Klassen und ihre Unterklassen | assignableTypes = FlurmonitorController.class |
basePackages / basePackageClasses |
alle Controller in diesen Paketen | basePackages = "de.dbh.mitfahrlabor.web.verwaltung" |
annotations |
Controller mit dieser Annotation | annotations = RestController.class |
Die Klasse FlurmonitorStoerung gilt nur für den FlurmonitorController:
@ControllerAdvice(assignableTypes = FlurmonitorController.class)
@Order(Ordered.HIGHEST_PRECEDENCE)
public class FlurmonitorStoerung {
/** View-Name: templates/error/stoerung.html. */
static final String SEITE = "error/stoerung";
private static final Logger LOG = LoggerFactory.getLogger(FlurmonitorStoerung.class);
@ExceptionHandler({ DataAccessException.class, TransactionException.class })
@ResponseStatus(HttpStatus.SERVICE_UNAVAILABLE)
public String datenGestoert(RuntimeException fehler, HttpServletRequest anfrage) {
LOG.error("Flurmonitor {}: Daten nicht lesbar, zeige Störungsseite", anfrage.getRequestURI(), fehler);
return SEITE;
}
@ExceptionHandler
public void statusMelden(ResponseStatusException fehler, HttpServletRequest anfrage, HttpServletResponse antwort)
throws IOException {
LOG.warn("Flurmonitor {}: {}", anfrage.getRequestURI(), fehler.getReason());
antwort.sendError(fehler.getStatusCode().value());
}
}Die erste Methode fängt zwei Arten von Ausnahmen. Spring übersetzt Fehler beim Zugriff auf die Datenbank in Unterklassen von DataAccessException, unabhängig davon, ob darunter H2, MariaDB oder ein anderes System arbeitet. Ist die Datenbank aber gar nicht erreichbar, scheitert schon der Beginn der Transaktion im @Transactional-Service, und Spring wirft eine CannotCreateTransactionException. Die ist eine TransactionException und keine DataAccessException. Bei der Probe im Testsystem war genau sie die Ursache. Wie eine Controller-Methode gibt die Handler-Methode einen View-Namen zurück; @ResponseStatus setzt den Statuscode 503 Service Unavailable. Er sagt Clients und Überwachung, dass der Dienst vorübergehend nicht verfügbar ist, und gilt auch für das Fragment, das das Skript aus dem Artikel Automatisch aktualisieren nachlädt. Das Skript wertet jeden Status außer 2xx als Fehler, lässt den letzten Stand stehen und zeigt den Hinweis „Keine Verbindung zum Server“, so wie in AK-MON-06.2 vereinbart.
Die zweite Methode ersetzt für den Flurmonitor die Antwort der ProblemBehandlung bei unbekannten Monitoren. Sie meldet den Status mit sendError an den Servlet-Container. Dadurch übernimmt wieder der BasicErrorController, und der Browser bekommt error/404.html (AK-MON-10.3).
Das Template der Störungsseite lädt sich selbst nach einer Minute neu. So zeigt ein Monitor, der während der Störung eingeschaltet wurde, nach ihrem Ende wieder Fahrten (AK-MON-10.2), auch wenn auf der Seite kein Skript läuft:
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta http-equiv="refresh" content="60">
<title th:text="#{stoerung.titel}">Mitfahrlabor – Anzeige gestört</title>
<!-- Stylesheets wie im Fragment kopf -->
</head>
<body>
<!-- Navigation aus dem Fragment -->
<main class="container-fluid px-4 py-5">
<h1 class="display-5 fw-bold text-primary" th:text="#{flurmonitor.ueberschrift}">Abfahrten</h1>
<p class="alert alert-warning fs-3 fw-bold mt-4" role="status" th:text="#{stoerung.text}">Anzeige gerade gestört –
die Fahrten findest Du in der App.</p>
<p class="fs-5" th:text="#{stoerung.neuladen}">Der Monitor versucht es jede Minute erneut.</p>
</main>
<!-- Fußzeile aus dem Fragment -->
</body>Den Kopf schreibt das Template selbst aus, weil das Fragment kopf das Neuladen nur als Rückfallebene ohne JavaScript einbaut. Bei der Probe im Testsystem hielt Jonas Wolff die Datenbank an und lud die Seite im Foyer neu:
Reihenfolge der Advices und Content Negotiation
Jetzt gibt es zwei Advices, die beide eine ResponseStatusException behandeln können: die ProblemBehandlung für alle Controller und die FlurmonitorStoerung für den Flurmonitor. Spring fragt die Advices in der Reihenfolge ihrer @Order; der erste, der einen passenden @ExceptionHandler hat und für den Controller gilt, gewinnt. Ohne @Order ist die Reihenfolge nicht festgelegt. Ordered.HIGHEST_PRECEDENCE sorgt dafür, dass der eingeschränkte Advice vor dem allgemeinen an der Reihe ist.
Eine Lücke bleibt. Ruft ein Browser eine Adresse auf, zu der es gar keine Controller-Methode gibt, etwa /flurmonitor/foyer/alt, wirft Spring eine NoResourceFoundException. Weil kein Controller beteiligt ist, greift assignableTypes nicht. Die ProblemBehandlung dagegen gilt ohne Einschränkung auch hier, und der Browser bekommt wieder JSON. Seit Spring Framework 6.2 kann ein @ExceptionHandler mit produces angeben, für welches Format er antwortet (T45):
@ControllerAdvice
@Order(Ordered.HIGHEST_PRECEDENCE)
public class HtmlFehlerseiten {
@ExceptionHandler(produces = MediaType.TEXT_HTML_VALUE)
public void nichtGefunden(NoResourceFoundException fehler, HttpServletResponse antwort) throws IOException {
antwort.sendError(HttpStatus.NOT_FOUND.value());
}
}Schickt der Client Accept: text/html, passt dieser Handler; Spring Boot zeigt error/404.html. Schickt er Accept: application/json, passt er nicht, und die ProblemBehandlung antwortet wie bisher mit Problem Details. Die REST-API und ihre Vertragstests aus der Backend-Serie bleiben damit unverändert. Eine Alternative wäre, die ProblemBehandlung mit annotations = RestController.class auf die REST-Controller zu beschränken. Dann fehlt ihr aber jede Anfrage ohne Controller, auch die Fehler aus Spring Security, die die Filterkette an den HandlerExceptionResolver weiterreicht; die REST-API würde dort plötzlich das JSON des BasicErrorController statt Problem Details liefern.
Logging mit SLF4J
Die Störungsseite verschweigt bewusst, was passiert ist. Damit Svenja Krämer und die Developers es trotzdem nachvollziehen können, schreibt die Anwendung ein Log (T46). Spring Boot nutzt dafür zwei Bausteine: SLF4J (Simple Logging Facade for Java) ist die Schnittstelle, gegen die der eigene Code programmiert. Logback ist die Implementierung, die Spring Boot mitbringt und die die Meldungen formatiert und ausgibt. Weil der Code nur die Fassade kennt, lässt sich die Implementierung austauschen, etwa gegen Log4j 2, ohne eine Zeile Code zu ändern. Andere Bibliotheken wie Hibernate oder Tomcat schreiben über dieselbe Fassade, sodass alle Meldungen in einem Log zusammenlaufen.
Jede Klasse holt sich einen eigenen Logger, meist als Konstante mit dem Klassennamen. Der Name bestimmt später, über welches Paket sich die Ausgabe einstellen lässt:
private static final Logger LOG = LoggerFactory.getLogger(HinweisService.class);
@Transactional
public Hinweis legeAn(String text, Long haltepunktId, LocalDate bis, Wichtigkeit wichtigkeit) {
// … Haltepunkt suchen
Hinweis gespeichert = hinweise.save(new Hinweis(text.strip(), haltepunkt, bis, wichtigkeit));
// Platzhalter {} statt Verkettung: der Text entsteht nur, wenn die Stufe INFO eingeschaltet ist
LOG.info("Hinweis {} angelegt: {}, gilt bis {}", gespeichert.getId(), wichtigkeit, bis);
return gespeichert;
}Die Platzhalter {} füllt SLF4J der Reihe nach mit den weiteren Argumenten. Anders als bei "Hinweis " + id + … entsteht der Text nur, wenn die Meldung wirklich ausgegeben wird. Ist das letzte Argument eine Ausnahme, für die kein Platzhalter mehr übrig ist, schreibt SLF4J sie mit Stacktrace dahinter, so wie im FlurmonitorStoerung-Advice. Jede Meldung hat eine von fünf Stufen (Levels):
| Stufe | wofür | Beispiel im Projekt |
|---|---|---|
ERROR |
Ein Fehler, um den sich jemand kümmern muss; die Anfrage konnte nicht wie vorgesehen beantwortet werden. | Datenbank nicht erreichbar, Störungsseite gezeigt |
WARN |
Etwas Ungewöhnliches, das die Anwendung abfängt, das aber auf ein Problem hindeuten kann. | Monitor mit unbekannter Adresse angefragt |
INFO |
Wichtige Ereignisse im normalen Betrieb, wenige je Minute. | Hinweis angelegt oder gelöscht, Anwendung gestartet |
DEBUG |
Details für die Fehlersuche durch Developers. | Hinweise für Haltepunkt 2 am 20.10. gesucht |
TRACE |
Sehr feine Einzelschritte, im Betrieb fast nie eingeschaltet. | jede einzelne Zeile, die die Aufbereitung prüft |
Eingestellt wird je Logger-Name, also je Paket oder Klasse. Eine Stufe schaltet sich und alle schwereren ein: INFO gibt INFO, WARN und ERROR aus. Die genaueste Angabe gewinnt; was nicht eingestellt ist, erbt vom übergeordneten Paket und zuletzt von root. In application.properties:
# Logging (Sprint 2, MON-10): Stufe je Paket; eigene Klassen ab INFO, Spring und Bibliotheken ab WARN
logging.level.root=WARN
logging.level.de.dbh.mitfahrlabor=INFO
# Start- und Portmeldung von Spring Boot weiter zeigen
logging.level.org.springframework.boot.web.server=INFO
logging.level.org.springframework.boot.tomcat=INFO
# zusätzlich in eine Datei schreiben (rollt bei 10 MB, sieben Dateien bleiben)
logging.file.name=logs/mitfahrlabor.log
logging.logback.rollingpolicy.max-file-size=10MB
logging.logback.rollingpolicy.max-history=7
Für die Fehlersuche lässt sich eine Stufe beim Start ändern, ohne die Datei anzufassen, z. B. mit --logging.level.de.dbh.mitfahrlabor.service=DEBUG. Bisher stand in application.properties außerdem spring.jpa.show-sql=true. Diese Einstellung schreibt das SQL von Hibernate direkt auf die Konsole, an Logback vorbei: ohne Zeitstempel, nicht in der Logdatei und nicht abschaltbar über eine Stufe. Sie steht jetzt auf false; wer das SQL sehen will, setzt logging.level.org.hibernate.SQL=DEBUG. Eine Zeile im Standardmuster von Spring Boot sieht so aus:
2026-11-10T08:40:37.166Z ERROR 17237 --- [mitfahrlabor] [nio-8080-exec-3] d.d.m.web.FlurmonitorStoerung : Flurmonitor /flurmonitor/foyer: Daten nicht lesbar, zeige Störungsseite
org.springframework.transaction.CannotCreateTransactionException: Could not open JPA EntityManager for transaction
at …
Caused by: java.sql.SQLTransientConnectionException: HikariPool-1 - Connection is not available, request timed out after 3002ms
Von links nach rechts: Zeitpunkt in UTC, Stufe, Prozessnummer, Name der Anwendung, Thread (hier ein Thread von Tomcat, der eine Anfrage bearbeitet), gekürzter Logger-Name und die Meldung. Darunter steht die Ausnahme mit der Kette ihrer Ursachen (Caused by). Die unterste Ursache sagt meist am meisten: Der Verbindungspool HikariCP hat drei Sekunden vergeblich auf eine Verbindung gewartet.
Andrea Wolf hat im Daily Scrum eine klare Bitte: Ins Log gehören keine personenbezogenen Daten. Das betrifft nicht nur Namen und Pseudonyme, sondern auch Freitexte. Ein Hinweis wie „Frau B. ist heute krank, Fahrgemeinschaft fällt aus“ wäre im Log über Wochen lesbar, auch nachdem er vom Monitor verschwunden ist. Der HinweisService loggt deshalb nur Nummer, Wichtigkeit und Datum des Hinweises, nie seinen Text, und der Advice nur die Adresse der Anfrage, keine Kopfzeilen oder Anmeldedaten.
Spring Boot Actuator: Health und Info
Für den Wunsch der Schul-IT, regelmäßig zu prüfen, ob die Anwendung läuft, bringt Spring Boot ein eigenes Modul mit: Actuator stellt Endpunkte für den Betrieb bereit, z. B. für den Gesundheitszustand (health), Informationen zur Anwendung (info), Kennzahlen (metrics) oder die aktuelle Konfiguration (env). Für T47 kommt der Starter dazu; die Build-Informationen erzeugt das Build-Werkzeug:
In build.gradle.kts unter dependencies und als eigener Block:
// Sprint 2 (MON-10): Betriebsendpunkte /actuator/health und /actuator/info
implementation("org.springframework.boot:spring-boot-starter-actuator")// Version, Name und Zeitpunkt des Builds nach META-INF/build-info.properties; /actuator/info zeigt sie an
springBoot {
buildInfo()
}Ohne weitere Einstellung gibt Actuator über HTTP nur /actuator/health frei. Endpunkte wie env würden Einstellungen und damit unter Umständen Passwörter zeigen; sie bleiben geschlossen. Die Einstellungen in application.properties geben genau zwei Endpunkte frei:
# Actuator (Sprint 2, MON-10): nur Health und Info über HTTP; Details der Health nur für angemeldete ORGA-Konten
management.endpoints.web.exposure.include=health,info
management.endpoint.health.show-details=when-authorized
management.endpoint.health.roles=ORGA
management.info.java.enabled=true
# Einträge info.* aus den Einstellungen anzeigen (seit Spring Boot 2.6 standardmäßig aus)
management.info.env.enabled=true
info.anwendung.name=Mitfahrlabor
info.anwendung.beschreibung=Backend mit REST-API und Flurmonitor
Spring Security muss die Endpunkte ebenfalls kennen. In der Filterkette für API und Monitor stehen sie ohne Anmeldung offen:
.requestMatchers(HttpMethod.GET, "/actuator/health/**", "/actuator/info").permitAll()Die Health fasst mehrere Prüfungen zusammen: db fragt die Datenbank über den Verbindungspool, diskSpace prüft freien Speicher, ping antwortet immer. Spring Boot fügt solche Health Indicators automatisch hinzu, sobald die passende Technik im Projekt ist. Der Gesamtstatus ist UP, wenn alle Prüfungen UP melden; meldet eine DOWN, ist auch der Gesamtstatus DOWN, und der Endpunkt antwortet mit 503 statt 200. Ein Überwachungswerkzeug braucht deshalb nur den Statuscode auszuwerten. Ausprobieren lässt es sich mit der laufenden Anwendung:
Invoke-RestMethod http://localhost:8080/actuator/health
# mit Anmeldung als Andrea Wolf (Rolle ORGA); alle Demo-Konten haben das Passwort "mitfahren"
$kopf = @{ Authorization = "Basic " + [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes("Admin-AW:mitfahren")) }
Invoke-RestMethod http://localhost:8080/actuator/health -Headers $kopf | ConvertTo-Json -Depth 5Ohne Anmeldung zeigt der Endpunkt nur den Gesamtstatus und die Gruppen liveness und readiness, die Spring Boot für Plattformen wie Kubernetes vorbereitet:
{"groups":["liveness","readiness"],"status":"UP"}Mit einem Konto der Rolle ORGA (show-details=when-authorized) kommen die einzelnen Prüfungen dazu (gekürzt):
{
"components": {
"db": { "status": "UP", "details": { "database": "H2", "validationQuery": "isValid()" } },
"diskSpace": { "status": "UP", "details": { "total": 270553174016, "free": 29809889280, "threshold": 10485760 } },
"ping": { "status": "UP" }
},
"groups": ["liveness", "readiness"],
"status": "UP"
}Bei der Probe der Störung meldete /actuator/health ohne Anmeldung {"groups":["liveness","readiness"],"status":"DOWN"} mit Status 503. Die Details waren in dieser Zeit nicht abrufbar: Die Konten liegen in der Datenbank, also scheiterte auch die Anmeldung. Für die Fehlersuche während einer Störung ist das Log deshalb die wichtigere Quelle.
/actuator/info zeigt alles, was Info-Beiträge liefern: die Einträge info.* aus den Einstellungen, die Build-Informationen (Name, Version, Zeitpunkt des Builds) und mit management.info.java.enabled die Java-Version. Svenja Krämer sieht damit nach jedem Update, welcher Stand tatsächlich läuft:
{
"anwendung": { "name": "Mitfahrlabor", "beschreibung": "Backend mit REST-API und Flurmonitor" },
"build": { "artifact": "mitfahrlabor", "name": "mitfahrlabor", "time": "2026-11-10T08:38:59.955Z",
"version": "0.0.1-SNAPSHOT", "group": "de.dbh" },
"java": { "version": "25.0.5.0.3", "…": "…" }
}Mit dem Actuator kommt auch Micrometer ins Projekt, die Bibliothek für Kennzahlen. Sie meldete beim Start eine Warnung, weil der Cache der Monitor-Abfrage keine Statistik führt. Der Zusatz recordStats in spring.cache.caffeine.spec schaltet sie ein; Treffer und Fehlgriffe des Caches stehen damit später für metrics bereit, falls das Team diesen Endpunkt freigibt.
Ein Pipeline-Job für die Templates
Die Definition of Done verlangt seit Sprint 0, dass das HTML ohne Fehler durch den Nu Html Checker läuft. Bisher hat Oksana Melnyk die Seiten dafür von Hand in den Checker kopiert, nach jeder Änderung an einem Template. Mit drei neuen Templates in diesem Sprint wird das zur Fehlerquelle. T48 automatisiert die Prüfung in zwei Schritten: Tests legen die gerenderten Seiten als Dateien ab, und ein Job in der Pipeline prüft sie.
Den ersten Schritt erledigt eine kleine Hilfsklasse im Testcode. Sie schreibt eine Seite nach build/html-schnappschuss/<name>.html:
public static void ablegen(String name, String html) {
try {
Files.createDirectories(ORDNER);
Files.writeString(ORDNER.resolve(name + ".html"), html, StandardCharsets.UTF_8);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}Der FlurmonitorStoerungTest legt damit die Störungsseite ab, der Systemtest die Monitorseite im Foyer und die Seite für 404, die nur der Servlet-Container rendert. Die Tests prüfen den Inhalt, der Checker die Gültigkeit. Den zweiten Schritt übernimmt der Nu Html Checker als Java-Bibliothek von Maven Central:
In build.gradle.kts eine eigene Konfiguration für den Checker und eine Aufgabe, die ihn startet:
val htmlChecker = configurations.create("htmlChecker")
dependencies {
htmlChecker("nu.validator:validator:26.11.4")
}
val htmlPruefen by tasks.registering(JavaExec::class) {
description = "Prüft die gerenderten Seiten aus build/html-schnappschuss mit dem Nu Html Checker."
group = "verification"
dependsOn(tasks.test, systemTest)
classpath = htmlChecker
mainClass = "nu.validator.client.SimpleCommandLineValidator"
args("--skip-non-html", "--also-check-css", layout.buildDirectory.dir("html-schnappschuss").get().asFile.path)
}Der Job in .gitlab-ci.yml steht in der Stufe quality:
templates:
stage: quality
script:
- ./gradlew htmlPruefen
artifacts:
when: on_failure
paths:
- build/html-schnappschuss/Findet der Checker einen Fehler, scheitert der Job, und die abgelegten Seiten bleiben als Artefakt der Pipeline zum Nachsehen. Bei der Probe mit einer absichtlich kaputten Datei meldete er Fehler wie Unclosed element “div” mit Zeile und Spalte.
Robustheit testen
Die Abnahmekriterien sichern Tests auf zwei Ebenen (T49). Der Integrationstest der Web-Schicht mockt die Aufbereitung und lässt sie die Ausnahme werfen, die Spring bei einer gestörten Datenbank meldet. Die Erweiterung OutputCaptureExtension von Spring Boot liest dabei mit, was auf der Konsole landet, und prüft so auch das Log:
@WebMvcTest(FlurmonitorController.class)
@Import({ SicherheitsKonfiguration.class, FlurmonitorKonfiguration.class })
@ExtendWith(OutputCaptureExtension.class)
class FlurmonitorStoerungTest {
// … MockMvcTester, @MockitoBean MonitorAufbereitung und HinweisService
@Test
void datenbankWegZeigtDieStoerungsseite(CapturedOutput log) throws Exception {
// AK-MON-10.1
given(aufbereitung.ansicht(any()))
.willThrow(new DataAccessResourceFailureException("Connection to mariadb:3306 refused"));
MvcTestResult antwort = mvc.get().uri("/flurmonitor/foyer").accept(MediaType.TEXT_HTML).exchange();
assertThat(antwort)
.hasStatus(HttpStatus.SERVICE_UNAVAILABLE)
.hasViewName(FlurmonitorStoerung.SEITE)
.bodyText()
.contains(STOERUNG, "<meta http-equiv=\"refresh\" content=\"60\">")
.doesNotContain("mariadb", "Exception", "\"status\":");
// Details nur im Log: Stufe, Adresse, Ausnahme mit Stacktrace
assertThat(log.getOut())
.contains("ERROR", "Flurmonitor /flurmonitor/foyer: Daten nicht lesbar")
.contains("DataAccessResourceFailureException: Connection to mariadb:3306 refused");
HtmlSchnappschuss.ablegen("stoerung", antwort.getResponse().getContentAsString(StandardCharsets.UTF_8));
}
}Weitere Tests derselben Klasse prüfen die CannotCreateTransactionException, den Status 503 des Fragments und den Status 404 für einen unbekannten Monitor. Die Seite error/404.html sieht MockMvc allerdings nicht: Die Weiterleitung an /error macht erst der Servlet-Container. Das prüft ein Systemtest, der die ganze Anwendung mit Tomcat startet. Er prüft auch die Actuator-Endpunkte von außen:
@Test
void unbekannterMonitorZeigtImBrowserDieEigeneFehlerseite() {
String html = client.get().uri("/flurmonitor/flur-c").accept(MediaType.TEXT_HTML).exchange()
.expectStatus().isNotFound()
.expectHeader().contentTypeCompatibleWith(MediaType.TEXT_HTML)
.expectBody(String.class).returnResult().getResponseBody();
assertThat(html).contains("Seite nicht gefunden", "/flurmonitor/flur-c", "Zum Flurmonitor im Foyer")
.doesNotContain("Whitelabel", "\"status\":");
HtmlSchnappschuss.ablegen("fehler-404", html);
}
@Test
void healthZeigtDerOrgaDieEinzelnenPruefungen() {
als(ORGA).get().uri("/actuator/health").exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$.components.db.status").isEqualTo("UP")
.jsonPath("$.components.db.details.database").isEqualTo("H2");
}| Abnahmekriterium | Test | Ebene |
|---|---|---|
| AK-MON-10.1 Störungshinweis statt Fehlermeldung | datenbankWegZeigtDieStoerungsseite, keineVerbindungFuerDieTransaktionZeigtEbenfallsDieStoerungsseite |
Integrationstest Web-Schicht |
| AK-MON-10.2 nach einer Minute wieder Fahrten | Neuladen im Template (datenbankWegZeigtDieStoerungsseite), Probe im Testsystem |
Integrationstest, Sichtprüfung |
| AK-MON-10.3 unbekannte Adresse verständlich | unbekannterMonitorZeigtImBrowserDieEigeneFehlerseite, unbekannteAdresseUnterFlurmonitorZeigtDieFehlerseiteInDerSpracheDesMonitors |
Systemtest |
| REST-API unverändert | restApiAntwortetWeiterMitProblemDetails und die Vertragstests der Backend-Serie |
Systemtest |
Die Probe im Testsystem ersetzt keinen Test, sie zeigt aber, ob die Annahmen stimmen. Jonas Wolff hat sie mit Svenja Krämer gemacht: Datenbank anhalten, Monitor neu laden, Log und Health ansehen, Datenbank wieder starten und eine Minute warten. Erst dabei fiel auf, dass die echte Ursache eine CannotCreateTransactionException ist.
Code-Stand
Den vollständigen Stand dieses Artikels findest Du im Ordner src/a09-robustheit-betrieb des Backend-Repositorys. Gegenüber dem Stand a08 sind neu bzw. geändert:
| Datei | Änderung |
|---|---|
build.gradle.kts, pom.xml |
Actuator, Build-Informationen, Aufgabe htmlPruefen bzw. Profil html-pruefen |
.gitlab-ci.yml, ci/maven.gitlab-ci.yml |
neuer Job templates |
web/FlurmonitorStoerung.java |
neu: Advice für den Flurmonitor (Störungsseite, 404 an den Container) |
web/HtmlFehlerseiten.java |
neu: HTML-Fehlerseite für Browser bei Adressen ohne Controller |
web/MonitorSprache.java |
Sprache des Monitors auch auf Fehlerseiten |
web/SicherheitsKonfiguration.java |
/actuator/health und /actuator/info ohne Anmeldung |
service/HinweisService.java |
Logger, Meldungen ohne Hinweistext |
templates/error/404.html, 5xx.html, stoerung.html |
neu: Fehlerseiten |
application.properties |
Log-Stufen, Logdatei, Actuator, show-sql aus, Cache-Statistik |
messages.properties, messages_en.properties |
Texte der Fehlerseiten |
Tests unter test/… |
neu: FlurmonitorStoerungTest, BetriebSystemTest, HtmlSchnappschuss |
sprintplan.md, product-backlog.md, definition-of-done.md, review-retro-sprint-2.md |
Sprint Backlog T43–T49, Review, Retrospektive |
Der Startcode für die Übungen mit den Mitfahrgesuchen liegt wie bisher im Unterordner uebung-mitfahrgesuche; seine messages.properties enthält jetzt auch die Texte der Fehlerseiten.
Das Sprint Review von Sprint 2
Am Nachmittag von Tag 10 treffen sich Scrum Team und Stakeholder wieder vor dem Monitor im Foyer, diesmal mit einem zweiten Bildschirm, auf dem der Monitor der Mensa läuft. Das Review folgt denselben fünf Schritten wie in Sprint 1:
Was war geplant? Thomas Becker nennt das Sprint-Ziel „Monitor im Alltag – lesbar mit Wochentag, je Standort einstellbar“: 13 Story Points fest (MON-02, MON-05, MON-09, MON-06, MON-08) und 8 als Option (MON-07, MON-10).
Was ist fertig? Alle sieben Einträge erfüllen die Definition of Done, auch die beiden aus der Option. MON-10 wurde erst am letzten Tag begonnen, ist aber klein geschnitten und hat für jedes Abnahmekriterium einen grünen Test.
Inkrement benutzen. Heike Brinkmann legt einen Hinweis an und sieht ihn auf beiden Monitoren. Dann hält Svenja Krämer im Testsystem die Datenbank an: Die Mensa zeigt die Störungsseite, das Foyer behält den letzten Stand mit dem roten Hinweis „Keine Verbindung zum Server“. Eine Minute nach dem Neustart zeigen beide wieder Fahrten.
Rückmeldungen sammeln. Svenja Krämer bindet
/actuator/healthgleich in ihre Überwachung ein und fragt, ob die Schul-IT eine Nachricht bekommen kann, wenn der Status länger als fünf MinutenDOWNist. Das ist eine Einstellung in ihrem Werkzeug, kein Eintrag für das Team. Andrea Wolf sieht sich die Logdatei an und findet keine Namen. Heike Brinkmann wünscht sich, Hinweise schon am Freitag für den Montag anzulegen, ohne dass sie übers Wochenende angezeigt werden.Product Backlog anpassen. Thomas Becker legt MON-15 „Hinweise mit Beginn“ an; das Team schätzt im Review grob 2 Story Points. Er sortiert MON-15 hinter MON-11 und MON-12 ein, damit die Tests über den Browser nicht wieder nach hinten rutschen.
Weil zwei Einträge im Laufe des Sprints dazukamen, zeigt das Team den Verlauf diesmal als Burn-up-Chart. Es trägt zwei Linien auf: die fertigen Story Points und den Umfang des Sprints. Anders als beim Burndown, bei dem eine Erweiterung des Umfangs wie ein Rückschritt aussieht, bleiben beide Bewegungen sichtbar:
Die Velocity von Sprint 2 beträgt 21 Story Points, nach 10 in Sprint 1. Das ist kein Grund, Sprint 3 mit 21 Punkten zu planen. Ein Teil der Steigerung kommt daher, dass die Einrichtung aus Sprint 1 erledigt war, und MON-10 war mit drei Punkten großzügig geschätzt, weil Fehlerbehandlung im Backend schon vorbereitet war. Thomas Becker rechnet mit dem Durchschnitt der beiden Sprints, (10 + 21) / 2 = 15,5, und aktualisiert den Release-Plan in sprintplan.md:
| Sprint | Einträge | Story Points | Release | Änderung |
|---|---|---|---|---|
| 1 | MON-01, MON-03, MON-04 | 10 (von 13) | Pilot im Foyer | MON-02 zurück ins Backlog |
| 2 | MON-02, MON-05 bis MON-10 | 21 (13 fest + 8 Option) | alle vier Monitore | MON-07 und MON-10 aus der Option |
| 3 | MON-11, MON-12, dazu MON-15, soweit die Velocity reicht | 13 sicher, bis 15 möglich | Abnahme durch den Product Owner | MON-15 neu nach Review 2 |
Die Sprint Retrospective von Sprint 2
Die zweite Retrospektive beginnt mit dem Blick zurück auf die Maßnahmen aus Retro 1. Eine Maßnahme, die niemand überprüft, wird schnell vergessen; deshalb hatte jede einen Prüfpunkt:
| Nr. | Maßnahme aus Retro 1 | Prüfpunkt | Ergebnis |
|---|---|---|---|
| M1 | Sichtprüfung an Tag 5 | Sichtprüfung hat an Tag 5 stattgefunden, Befunde vor Sprintende umgesetzt | erfüllt: Umbruch des Datums gefunden und an Tag 6 behoben |
| M2 | höchstens zwei Stories gleichzeitig | Burndown fällt vor Tag 5 zum ersten Mal | erfüllt: erster fertiger Eintrag an Tag 3 |
| M3 | @WebMvcTest für jede Darstellungsregel |
jede neue Darstellungsregel hat einen Test | erfüllt; bleibt in der Definition of Done |
Für die Daten und Einsichten wählt der Scrum Master diesmal den Seestern: Fünf Felder fragen, wovon das Team mehr und wovon weniger tun soll, was es weitermachen, neu anfangen und aufhören soll. Das Format eignet sich, wenn ein Sprint gut lief und es um Feinheiten geht. Nach der stillen Schreibphase und dem Dot Voting hängen diese Zettel:
| Feld | Zettel | Punkte |
|---|---|---|
| Mehr davon | Probe mit echten Störungen im Testsystem statt nur Mocks | 5 |
| Weniger davon | Nu Html Checker von Hand | 4 |
| Weitermachen | Sichtprüfung an Tag 5, höchstens zwei Stories gleichzeitig | 2 |
| Anfangen | bei jeder neuen Seite überlegen, was sie bei einem Fehler zeigt | 3 |
| Aufhören | Optionen erst am letzten Tag ziehen | 1 |
Die meisten Punkte bekommt die Probe der Störung. Erst sie hat gezeigt, dass die gemockte DataAccessException nicht die Ausnahme ist, die im Betrieb auftritt. Daraus entstehen drei Maßnahmen:
| Nr. | Maßnahme | Verantwortlich | Wohin? | Prüfpunkt in Retro 3 |
|---|---|---|---|---|
| M4 | Einmal je Sprint eine Störung im Testsystem proben (Datenbank anhalten, Monitor, Log und Health ansehen) | Jonas Wolff mit Svenja Krämer | Aufgabe im Sprint Backlog von Sprint 3 | Probe hat stattgefunden, Befunde stehen im Product Backlog |
| M5 | HTML-Prüfung nur noch über den Pipeline-Job templates; jede neue Seite legt im Test einen Schnappschuss ab |
Oksana Melnyk | Definition of Done, Zeile „Barrierefreiheit“ | keine Prüfung mehr von Hand, jede neue Seite im Job |
| M6 | Jede neue Seite zeigt bei einem Fehler eine verständliche Seite und schreibt einen Log-Eintrag (WARN oder ERROR) ohne personenbezogene Daten |
alle Developers | neue Zeile „Betrieb“ in der Definition of Done | Review der Merge Requests prüft Fehlerfall und Log |
Die in Retro 1 angekündigte Verschärfung „Systemtest über den Browser grün“ verschiebt das Team bewusst: Ohne Selenium-Tests (MON-11) ließe sie sich nicht erfüllen. Sie kommt in Retro 3 wieder auf den Tisch. Review und Retrospektive stehen in review-retro-sprint-2.md, die geschärfte Definition of Done in definition-of-done.md im Code-Stand.
Nächster Schritt: Sprint 3 und Tests über den Browser
Sprint 2 ist abgeschlossen. Alle vier Monitore zeigen ihre Fahrten und die Hinweise der Schulverwaltung, sie überstehen eine Störung der Datenbank ohne technische Fehlermeldung, und die Schul-IT sieht über den Actuator, ob die Anwendung läuft. Bisher prüft das Team das Verhalten im Browser aber nur von Hand: das Nachladen, die Anmeldung, das Neuladen der Störungsseite. Im Artikel GUI-Test mit Selenium beginnt Sprint 3 mit dem Sprint Planning, und Selenium WebDriver steuert einen echten Browser durch die Seiten (MON-11).
In den Übungsaufgaben zu diesem Artikel ordnest Du Log-Meldungen den passenden Stufen zu, baust eine Störungsseite für die Mitfahrgesuche und rekonstruierst aus Log und Health, was bei einer Störung passiert ist.
Leitfragen
- Was passiert in Spring Boot mit einer Ausnahme, für die es keinen
@ExceptionHandlergibt?
Der Servlet-Container leitet die Anfrage an /error weiter. Dort antwortet der BasicErrorController: einem Browser mit einer Fehlerseite (templates/error/<Status>.html, dann 4xx.html/5xx.html, dann error.html, sonst Whitelabel-Seite), anderen Clients mit JSON. siehe Abschnitt „Wie Spring Boot Fehler behandelt“
- Warum bekam der Monitor im Flur B JSON statt einer Fehlerseite?
Die ProblemBehandlung der REST-API ist ein @RestControllerAdvice ohne Einschränkung. Sie gilt für alle Controller, also auch für den FlurmonitorController, und übersetzt dessen ResponseStatusException in Problem Details als JSON. siehe Abschnitt „Wie Spring Boot Fehler behandelt“
- Wie legst Du fest, für welche Controller ein
@ControllerAdvicegilt, und welcher von zwei passenden Advices antwortet?
Über die Attribute assignableTypes, basePackages/basePackageClasses oder annotations. Passen mehrere, antwortet der erste in der Reihenfolge von @Order; Ordered.HIGHEST_PRECEDENCE kommt zuerst. Bei Adressen ohne Controller greifen Einschränkungen auf Controller nicht; dort hilft produces am @ExceptionHandler. siehe Abschnitt „Reihenfolge der Advices“
- Warum fängt
FlurmonitorStoerungnebenDataAccessExceptionauchTransactionException?
Ist die Datenbank gar nicht erreichbar, scheitert schon der Beginn der Transaktion im @Transactional-Service. Spring wirft dann eine CannotCreateTransactionException, die keine DataAccessException ist. siehe Abschnitt „Ein @ControllerAdvice nur für den Flurmonitor“
- Welche Stufe passt zu welcher Meldung, und welche Meldungen erscheinen mit
logging.level.de.dbh.mitfahrlabor=INFO?
ERROR für Fehler, um die sich jemand kümmern muss, WARN für Ungewöhnliches, das abgefangen wurde, INFO für wichtige Ereignisse im normalen Betrieb, DEBUG und TRACE für die Fehlersuche. INFO gibt INFO, WARN und ERROR der eigenen Pakete aus, DEBUG und TRACE nicht. siehe Abschnitt „Logging mit SLF4J“
- Warum schreibt man
LOG.info("Hinweis {} angelegt", id)stattLOG.info("Hinweis " + id + " angelegt"), und was gehört nicht ins Log?
Mit Platzhaltern setzt SLF4J den Text nur zusammen, wenn die Stufe eingeschaltet ist; eine Ausnahme als letztes Argument erscheint mit Stacktrace. Nicht ins Log gehören personenbezogene Daten, Passwörter und Freitexte, die solche Daten enthalten können, z. B. der Text eines Hinweises. siehe Abschnitt „Logging mit SLF4J“
- Was meldet
/actuator/health, und warum zeigt es ohne Anmeldung nur den Gesamtstatus?
Den zusammengefassten Status der Health Indicators (db, diskSpace, ping …) mit 200 bei UP und 503 bei DOWN. Die Details verraten Datenbanksystem, Speicherplatz und Pfade; mit show-details=when-authorized und roles=ORGA sehen sie nur angemeldete Konten dieser Rolle. siehe Abschnitt „Spring Boot Actuator“
- Wie prüft die Pipeline die Templates, obwohl Thymeleaf-Templates allein kein fertiges HTML sind?
Tests rendern die Seiten mit Testdaten und legen das Ergebnis unter build/html-schnappschuss/ ab. Der Job templates startet danach den Nu Html Checker auf diesem Ordner und scheitert bei jedem Fehler. siehe Abschnitt „Ein Pipeline-Job für die Templates“
- Warum zeigt das Team den Verlauf von Sprint 2 als Burn-up-Chart, und warum plant es Sprint 3 nicht mit 21 Story Points?
Der Umfang des Sprints ist zweimal gewachsen. Ein Burn-up-Chart zeigt fertige Punkte und Umfang als getrennte Linien; im Burndown sähe die Erweiterung wie ein Rückschritt aus. Eine einzelne hohe Velocity enthält Sondereffekte (Einrichtung erledigt, großzügige Schätzung); der Durchschnitt aus mehreren Sprints ist eine verlässlichere Grundlage. siehe Abschnitt „Das Sprint Review von Sprint 2“
Links und weitere Informationen
für den Einstieg: Ein Flurmonitor mit Thymeleaf: das Projekt im Überblick
als Grundlage: Formulare mit Thymeleaf, Fehlerbehandlung der REST-API, Build, Qualität und CI und Scrum
zum Szenario: Szenario Mitfahrlabor mit Personas und Flurmonitoren
als Nachschlagewerk: Spring Boot Reference: Error Handling und Spring Framework Reference: Exceptions
als Nachschlagewerk: Spring Boot Reference: Logging und SLF4J-Handbuch
als Nachschlagewerk: Spring Boot Reference: Actuator Endpoints
zum Weiterlesen: Nu Html Checker und Scrum Guide 2020
Hinweis zur Nachnutzung als Open Educational Resource (OER)
Dieser Artikel und seine Texte, Bilder, Grafiken, Code und sonstiger Inhalt sind - sofern nicht anders angegeben - lizenziert unter CC BY 4.0. Nennung gemäß TULLU-Regel bitte wie folgt: “Robustheit und Betrieb: Fehlerseiten, Actuator und Logging für den Flurmonitor” von oer-informatik.de (H. Stein), Lizenz: CC BY 4.0. Der Artikel wurde unter https://oer-informatik.de/flurmonitor-robustheit-betrieb veröffentlicht, die Quelltexte sind in weiterverarbeitbarer Form verfügbar im Repository unter https://gitlab.com/oer-informatik/java-springboot/Backend. Erstellt mit KI-Assistenz; von mir umfassend redaktionell überarbeitet, geprüft und freigegeben. Stand: 11.11.2026.
[Kommentare zum Artikel lesen, schreiben] / [Artikel teilen] / [gitlab-Issue zum Artikel schreiben]
