Logging
Serverseitiges Logging
Serverseitig wird im jadice web toolkit das Logging der jadice document platform 5 eingesetzt, das das Anbinden unterschiedlicher Frameworks erlaubt.
Eine ausführliche Beschreibung kann der Dokumentation der jadice document platform 5entnommen werden.
Kontext im Log (MDC)
Der Server setzt für jede Nachricht und jede Kachelanfrage drei MDC-Schlüssel, die sich in das Log-Pattern aufnehmen lassen:
| MDC-Schlüssel | Inhalt |
|---|---|
jwt.clientId | Transport-Client-ID der Viewer-Sitzung |
jwt.docIdentifier | Kennung des gerade bearbeiteten Dokuments |
jwt.userId | Benutzer hinter der Sitzung, sofern eine Integration ihn registriert hat (siehe unten); sonst leer |
Beispiel für Logback (application.yml einer Spring-Boot-Anwendung):
logging:
pattern:
console: "%d %-5level [%thread] [clientId:%X{jwt.clientId}] [user:%X{jwt.userId}] [docId:%X{jwt.docIdentifier}] %logger: %msg%n"
MDCFilter räumt die Schlüssel am Ende jeder Anfrage wieder ab (siehe
Servlet-Unterstützung).
Benutzer je Viewer-Sitzung: ClientUserRegistry
Das jadice web toolkit kennt die Benutzer der integrierenden Anwendung nicht. Damit Log-Zeilen trotzdem einem
Benutzer zugeordnet werden können, gibt es die
com.levigo.jadice.web.transport.server.session.ClientUserRegistry: eine serverweite Zuordnung von
Transport-Client-ID zu einer Benutzerkennung, die die Integration setzt, sobald sie den Benutzer kennt. Der
Transport trägt die Kennung danach bei jeder eingehenden Nachricht und Kachelanfrage dieser Sitzung als
jwt.userId ins MDC ein.
// z. B. in einem DocumentDataProvider, einem MessageListener oder einem eigenen Servlet-Filter,
// sobald der Benutzer bekannt ist (Session, Token, Header):
ClientUserRegistry.getInstance().register(context.getClientId(), userId);
Eigenschaften:
- Nur Logging-Kontext. Die Registry prüft oder vergibt keine Rechte; Autorisierung bleibt Sache der Integration.
- Lebenszyklus. Ein Eintrag wird beim Schließen der Transport-Sitzung entfernt. Sitzungen, die ohne Abmeldung verschwinden (Tab geschlossen, Netz weg), verfallen nach zwei Stunden ohne Zugriff; abgelaufene Einträge werden beim nächsten Zugriff bzw. bei der nächsten Registrierung entsorgt.
- Leere Werte werden ignoriert,
jwt.userIdbleibt dann leer.ClientUserRegistry.mdcValue(userId)liefert den MDC-tauglichen Wert (nienull). - Personenbezogene Daten. Die Kennung landet in jeder Log-Zeile der Sitzung; Aufbewahrungsfristen und Zugriff auf die Logs sind entsprechend zu regeln.
Die ICN-Anbindung
nutzt die Registry, um den ICN-Benutzer aus dem signierten Token (sub) zu übernehmen; sie schreibt ihn zusätzlich
als Dokument-Property server.icn.user.
Clientseitiges Logging
Auf der Clientseite wird das Logging auf das von GWT unterstützte
java.util.logging umgeleitet. Die Logging-Konfiguration kann in den
jeweiligen .gwt.xml Dateien der Module modifiziert werden:
-
Loglevel setzen:
<set-property name="gwt.logging.logLevel" value="SEVERE"/>. Hier können die Loglevel vonjava.util.loggingverwendet werden, die das Webtoolkit unterstützt:FINEST, FINER, FINE, CONFIG, INFO, WARNING, SEVEREAls Default ist im jadice web toolkitWARNINGgesetzt. -
Console- und RemoteHandler aktivieren: <set-property name="gwt.logging.consoleHandler" value="ENABLED"/>.
consoleHandlerundsimpleRemoteHandlerkönnen über das Attribut ENABLED bzw. DISABLED aktiviert bzw. deaktiviert werden. Per Default ist im jadice web toolkit derconsoleHandleraktiviert und dersimpleRemoteHandlerdeaktiviert. -
Falls GWT Logging bereits genutzt wird, kann integrationsseitig das im JWT voreingestellte Loglevel durch folgende Reihenfolge in der .gwt.xml Datei überschrieben werden: `
`
In der gewünschten Klasse kann nun über Logger logger = LoggerFactory.getLogger(classname.class) ein Logger
instanziiert werden. Dieser übernimmt die Konfigurationen der
zugehörigen .gwt.xml Datei. Über verschiedene Methoden können dann
Messages auf verschiedenen Levels geloggt werden:
| Methode | java.util.logging Level |
|---|---|
| logger.trace(String message, Throwable t) | FINEST |
| logger.debug(message, t) | CONFIG |
| logger.info(message, t) | INFO |
| logger.warn(message, t) | WARNING |
| logger.error(message, t) | SEVERE |
Der Parameter t ist dabei optional.
Weitere Informationen können unter GWT Logging gefunden werden.
Remote-Logging (Client → Server)
Clients können Log-Meldungen über die Transport-Verbindung an den Server senden (Nachricht
LOG_REMOTE); beim GWT-Client geschieht das bei aktiviertem simpleRemoteHandler (s.o.).
Serverseitig nimmt der RemoteLoggingMessageListener die Meldungen entgegen und schreibt sie mit
dem übermittelten Loglevel in das Server-Log. Ein mitgesendetes Throwable wird dabei serverseitig
rekonstruiert.
Inhalt, Loglevel und Client-ID solcher Meldungen sind vollständig vom Client kontrolliert. Der Server härtet die Verarbeitung deshalb wie folgt ab:
- Sanitisierung (Log-Injection, CWE-117): Steuerzeichen einschließlich Zeilenumbrüchen (CR/LF)
werden durch Leerzeichen ersetzt. Eine Meldung belegt damit immer genau eine Log-Zeile und kann
keine fremden Log-Einträge vortäuschen. Meldungen werden auf 4096 Zeichen gekürzt (Markierung
…[truncated]). Jede Meldung erhält das Präfix[remote-client:<clientId>]; die Client-ID wird ebenfalls sanitisiert. - Drosselung (Flooding): Pro Client-ID werden höchstens 100 Meldungen pro Sekunde verarbeitet. Darüber hinausgehende Meldungen werden verworfen; der Client erhält dennoch eine Bestätigung, der Fire-and-forget-Vertrag bleibt also erhalten. Die Grenzwerte sind nicht konfigurierbar.
- Begrenzte Throwable-Rekonstruktion: Mitgesendete Exceptions werden auf 25 verkettete Ursachen
(
cause) und 1024 Stacktrace-Elemente begrenzt; ein fehlender Stacktrace wird toleriert. Fehler bei der Verarbeitung, auchError-Instanzen wieStackOverflowError, werden abgefangen und beenden nicht den Worker-Thread. - Abschaltbar: Über
ConfigurationManager.getServerConfiguration().setRemoteLoggingEnabled(false);(Spring-Propertywebtoolkit.remoteLoggingEnabled, Defaulttrue) lässt sich die serverseitige Verarbeitung vollständig deaktivieren. Remote-Meldungen werden dann quittiert, aber nicht ins Log geschrieben.
Sanitisierung und Drosselung greifen unabhängig von diesem Schalter immer. Integrationen, die kein
Remote-Logging nutzen, sollten den Schalter in Produktion auf false setzen: Das Server-Log
enthält dann keine vom Client frei wählbaren Inhalte (Relevanz für DSGVO Art. 32 und
Log-Integrität nach ISO 27001).
Beispiel application.yml:
webtoolkit:
remoteLoggingEnabled: false