Fehlerhandling
Ein Qualitätsmerkmal einer Software ist die Unterstützung des Benutzers im Fehlerfall. Fehlermeldungen, die dem Benutzer angezeigt werden, sollten in einer für ihn verständlichen Sprache verfasst sein. Dennoch müssen alle technischen Details festgehalten werden, die für eine weitere Bearbeitung notwendig sind. Ich möchte den Absatz "Fehlerhandling" in zwei Bereiche gliedern:
- Darstellung von Fehlern
- Logging
Die Darstellung von Fehlern ist technisch gesehen unproblematisch. Mit dem ErrorDialog bietet JFace ein entsprechendes Widget. Hier können sowohl eine lesbare Fehlermeldung im Dialog als auch technische Details im Detailbereich dargestellt werden. Interessant ist eher die Frage, woher die dargestellten Informationen stammen. Fehlersituationen innerhalb des Clients sind dabei relativ gut zu kontrollieren, da an entsprechenden Stellen die passenden Fehlermeldungen generiert werden können. Anders ist es bei der Kommunikation zum Server. Hier können serverseitig beispielsweise technische Ausnahmen wie SQLExceptions oder JMSExceptions auftreten, die zunächst übersetzt werden müssen. Es empfiehlt sich, für die Fehlerbenachrichtigung eine eigene Exception-Klasse für Serviceaufrufe zu verwenden. Die Serverseite konvertiert dabei sämtliche Fehlermeldungen in die entsprechende Exception-Klasse und entkoppelt sie von eventuell abhängigen Bibliotheken. Dieser Punkt ist insofern wichtig, da sonst Exceptions von Bibliotheken, die clientseitig nicht vorhanden sind, nicht dargestellt werden können bzw. zu ClassNotFoundExceptions bei der Deserialisierung der Response führen. Dieser Vorgang kann serverseitig beispielsweise durch den Spring-Interceptor-Mechanismus mit einem Advice umgesetzt werden, der für Methodenaufrufe deklariert wird.
<bean class="org.springframework.aop.framework.ProxyFactoryBean" id="fooRemoteService"><property name="proxyInterfaces" value="com.demo.IFooRemoteService"/><property name="target" ref="fooRemoteServiceTarget"/><property name="interceptorNames"><list><value>securityAdvice</value><value>exceptionAdvice</value></list></property></bean><bean class="com.foo.ExceptionAdvice" id="exceptionAdvice"/>
Der Aspekt kann dann die Exception-Klasse für den Serviceaufruf mit zusätzlichen, zur jeweiligen Fehlersituation vorhandenen Informationen anreichern.
public class ServiceExceptionAdvice implements ThrowsAdvice {public void afterThrowing(Throwable throwable) throws Throwable {if (throwable instanceof ServiceException) {throw throwable;}// zusätzliche Informationen können hier hinzugefügt werdenthrow new ServiceException(throwable);}}
Logging
Zum Abschluss möchte ich im Rahmen der Fehlerbehandlung noch auf das Thema "Logging" eingehen. Auch hier bietet die Eclipse-Plattform eine integrierte Lösung. Vom Activator eines Plug-ins lässt sich mit der Methode getLog() auf das ILog des Plug-ins zugreifen. Das ILog hat eine log(IStatus)-Methode, mit der in die Eclipse-Logdatei geschrieben wird. Das IStatus-Objekt beinhaltet dabei sowohl den Text als auch das Log-Level. Die Verwendung der Methode ist so etwas sperrig, da das Erzeugen des IStatus-Objekts sehr umständlich ist. Einfacher ist eine Fassade, die die Erzeugung kapselt. Das Interface der Fassade ähnelt dabei dem von bekannten Logging-Frameworks wie Log4J.
public interface IPluginLogger {void info(String message, Throwable throwable);void info(String message);void error(String message);void error(Throwable throwable);void error(String message, Throwable throwable);}
Die Implementierung delegiert dann die einzelnen Log-Methoden an eine einzige Methode, die das benötigte IStatus-Objekt erzeugt.
public final class PluginLogger implements IPluginLogger {private Plugin runtimePlugin;...public final void info(String message, Throwable throwable) {log(IStatus.INFO, IStatus.OK, message, throwable);}...private final void log(int severity, int code, String message, Throwable exception) {String symbolicName = runtimePlugin.getBundle().getSymbolicName();IStatus status = new Status(severity, symbolicName, code, message, exception);runtimePlugin.getLog().log(status);}}
So lässt sich das ILog wie ein gewöhnliches Logging-Interface verwenden. Was aber, wenn man serverseitig und clientseitig das gleiche Logging-Framework einsetzen möchte? Mithilfe eines ILogListener, der bei der Platform angemeldet werden kann, können sämtliche Logs des ILogs direkt an das Logging-Framework delegiert werden.
/*** {@link ILogListener} that transfers the <i>Eclipse Log</i> to the* corresponding <i>Log4J Log</i>.public class LogTransfer implements ILogListener {/** The Log4J logger. */private Logger logger = Logger.getLogger(LogTransfer.class);/** {@inheritDoc} */public void logging(IStatus status, String plugin) {String message = "[" + plugin + "] " + status.getMessage();Throwable exception = status.getException();switch (status.getSeverity()) {case IStatus.CANCEL:logger.error(message, exception);break;case IStatus.ERROR:logger.error(message, exception);break;case IStatus.INFO:logger.info(message, exception);break;case IStatus.OK:logger.info(message, exception);break;case IStatus.WARNING:logger.warn(message, exception);break;default:logger.info(message, exception);break;}}}
Ob das Logging-Framework direkt oder die zuvor beschriebene Fassade verwendet wird, bleibt dann Geschmackssache. Jedenfalls ist sichergestellt, dass sämtliche Logs, auch die Logs der Eclipse-Plattform, ihren Weg ins Logging-Framework finden. Typischerweise wird als externes Logging-Framework Log4J genutzt. Die Verwendung stellt im OSGi-Kontext allerdings zunächst ein kleines Problem dar. Die Log4J-Bibliothek sollte als eigenes Bundle eingebunden werden. Für die Konfiguration ist allerdings eine Konfigurationsdatei notwendig. Um Log4J diese Konfigurationsdatei im Klassenpfad zugänglich zu machen, kann leicht ein Fragment verwendet werden, das als Host das Log4J Bundle definiert.
In einer verteilten Anwendung ist das Logging im Produktivbetrieb ein wichtiges Instrument zur Identifizierung von Fehlern. Im Fehlerfall stehen die gewünschten Informationen jedoch in der Log-Datei der jeweiligen Installationen. Der Zugriff gestaltet sich teilweise schwierig und umständlich. Wünschenswert ist dann ein so genanntes End-to-End-Logging, die Log-Statements werden dabei zusätzlich an den Server transferiert. Das bedeutet selbstverständlich einen erheblichen zusätzlichen Datenverkehr, der sich allerdings über das Log-Level steuern lässt. Der Transfer sollte dabei auch "nur" ein sekundäres Log darstellen, also die Log-Datei des Clients nicht ersetzen. Zusätzlich sollte der Transfer nebenläufig geschehen und die Clientfunktionen nicht behindern.
Links & Literatur
- Enterprise Eclipse RCP, Teil 2: Remoting und Caching
- Equinox & JAAS
- Spring Security
- Equinox Secure Storage
- Equinox Security Project
- Eclipse Capabilities
- Kimberley Horne, Contexts and Capabilities
- Eclipse Commands und Handler
- Eclipse RCP im Unternehmenseinsatz, Stefan Reichert, dpunkt.verlag, August 2009



