Inhaltsübersicht:
- AOP und Logging - Teil I
- Einführung in das Thema Logging und AOP. Was ist Logging? Was ist Tracing? Motivation für einen neuen Ansatz.
- AOP und Logging - Teil II
- Eine Lösungsansatz ohne AspectJ.
- AOP und Logging - Teil III
- Logging vs. Debugging und eine erste einfache Lösung.
In meinem ersten Beiträgen möchte ich mit einem meiner Lieblingsthemen beschäftigen: Aspect Oriented Programming (AOP).
AOP hat längst den Mainstream der (Java) Entwickler erreicht, ohne das die meisten Aspekte selber entwickeln (das muss nicht unbedingt schlecht sein): Man nutzt AOP im Rahmen eines Frameworks wie z.B. Spring ohne jedoch eigene Aspekte zu entwickeln. Dabei will AOP ein Problem lösen, das jeden Entwickler früher oder später (be-)trifft: Seperation of Concerns. Wenn eine neue Applikation erstellt wird, dann wird zunächst von den Kernfunktionalitäten (core concerns) gesprochen, den dahinter steht die Motivation die Applikation überhaupt zu erstellen. Aber leider ist dies nur die halbe Wahrheit ist. Die Kernfunktionalität ist vielleicht hinreichend für einen Prototyp, aber für eine lieferbare, wartbare Applikation, die den Entwickler ruhig schlafen lässt, ist dies nicht ausreichend. Ernsthafte Softwareentwicklung muss sich um die cross-cutting concerns kümmern, wie zum Beispiel die allseits bekannten nicht funktionale Anforderungen.
Das bekanntesten und damit am häufigsten genannten cross-cutting concerns sind Tracing und Logging. Die Anwendung dieser Aspekte durchziehen die meisten Applikationen. Diese beiden Aspekte sind so essentiell, das sie auch zur Motivation und als Standardbeispiele von AOP herhalten müssen. Leider hat Logging als Beispiel für AOP einen Webfehler: Logging und Tracing werden nicht sauber unterschieden.
Das bekanntesten und damit am häufigsten genannten cross-cutting concerns sind Tracing und Logging. Die Anwendung dieser Aspekte durchziehen die meisten Applikationen. Diese beiden Aspekte sind so essentiell, das sie auch zur Motivation und als Standardbeispiele von AOP herhalten müssen. Leider hat Logging als Beispiel für AOP einen Webfehler: Logging und Tracing werden nicht sauber unterschieden.
Eine Anmerkung zum Thema Logging
Immer wieder wird AOP mit Logging erklärt. Warum schon wieder Logging? Gibt es nichts anderes auf dieser Welt wozu AOP dienen kann? Und ob. Aber Logging (das meist mit Tracing verwechselt wird) verunstaltet wie kein anderer Aspekt unseren Code. Ausserdem will ich zeigen, das hinter dem Thema Logging mehr steckt als man auf den ersten Blick erkennen kann.
Ich habe also eine Bitte an euch: Geduld. Am Ende soll neben eine verbessertes Verständnis von Logging und AspectJ, eine kleine aber feine AspectJ-Library entstehen: ajlog.
Wozu AOP noch taugt ausser Tracing und Logging, werde ich in weiteren Artikeln behandeln.
Immer wieder wird AOP mit Logging erklärt. Warum schon wieder Logging? Gibt es nichts anderes auf dieser Welt wozu AOP dienen kann? Und ob. Aber Logging (das meist mit Tracing verwechselt wird) verunstaltet wie kein anderer Aspekt unseren Code. Ausserdem will ich zeigen, das hinter dem Thema Logging mehr steckt als man auf den ersten Blick erkennen kann.
Ich habe also eine Bitte an euch: Geduld. Am Ende soll neben eine verbessertes Verständnis von Logging und AspectJ, eine kleine aber feine AspectJ-Library entstehen: ajlog.
Wozu AOP noch taugt ausser Tracing und Logging, werde ich in weiteren Artikeln behandeln.
Wenn eine Library wie Log4J oder SLF4J eingesetzt wird, dann ist Tracing ein Level, der alle anderen Levels (DEBUG, INFO, ...) enthält. Aus Sicht der Levelhierachie ist somit ein Loggingeintrag ein verfolgbarer (traceable) Eintrag. Aus technischer Sicht ist dies auch richtig. Macht dies aber auch tatsächlich Sinn?
Schauen wir uns mal ein (zugegeben künstliches) Beispiel an:
Schauen wir uns mal ein (zugegeben künstliches) Beispiel an:
package ajlog.example; // #7# import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class MyClass { // #7# private static Logger logger = LoggerFactory.getLogger(MyClass.class); public Object doSomethingWith2Objects(Object a, Object b) { // #1# Tracing : Enter method logger.trace("Enter doSomethingWith2Objects({},{})", a, b); try { // #5# : throws Exception Number ai = (Number) a; Number bi = (Number) b; if (ai.intValue() > 100) logger.warn("a is not a tiny number"); // #6# if (bi.intValue() > 100) logger.warn("b is not a tiny number"); // #6# Number result = new Long(ai.intValue() + bi.longValue()); // #2# Tracing : Leave method logger.trace("Leave doSomethingWith2Objects(...) -> {}", result); return result; } catch (RuntimeException ex) { // #4# Tracing? : Report unexpected behaviour logger.error("a or b is not a number", ex); } // #3# Tracing : Leave method logger.trace("Leave doSomethingWith2Objects(...) -> null"); return null; } }
#1#, #2# und #3# sind unbestreitbar Tracing-Einträge und dies nicht wegen des Levels. Über #4# lässt sich streiten, da hier nicht nur die Exception ausgegeben wird, sondern auch in den Verlauf eingegriffen wird. Würde die Exception anschliessend wieder geworfen wäre dies IMHO ein Tracing (trotz des Levels ERROR). Diese Tracingmeldung kann durch eine potentielle Exception bei #5# ausgelöst werden.
Bleiben die beiden logger.warn() Einträge (#6#): Dies sind Logging-Einträge, wie sie Entwickler immer wieder in den Code einstreuen, die dem Applikationsbetreuer wertvolle Informationen während der Laufzeit oder post mortem hinterlässt - auch wenn sie in diesem Fall sehr technisch erscheinen.
Die Tracing-Einträge lassen sich sehr einfach mit
Sie haben eine Bedeutung, die nicht durch den Aufruf von logger.warn() abgedeckt ist. Ihre Expressivität erhalten sie durch den Logging Inhalt ("a is not a tiny number") und der ist für Code Weaver (so) nicht erreichbar. Eine mögliche Lösung wäre, das man ein zusätzliche Methode einführt, dann wäre diese wieder verfolgbar, aber dann wäre Logging identisch mit Tracing und man hätte Schwierigkeiten den Level gescheit zu setzen.
Natürlich würde kein Entwickler, der bei Sinnen ist, auf so wenig Codezeilen soviele Tracing- respektive Logging-Einträge vornehmen - es ist ein künstliches Beispiel. Aber selbst wenn man die offensichtlichen Tracing-Einträge entfernt, verbleibt die direkte Abhängigkeit zur Logging-Library (#7#) und die Logging-Einträge. Und diese müssen bleiben! Was nicht bleiben muss ist die direkte Abhängigkeit zur Logging-Library.
Zusammenfassend:
- Logging als Motivation für AOP ist in Ordnung, den unbestreitbar handelt es sich um ein cross-cutting concern. Aber Logging ist aber kein einfach zu lösendes Beispiel für den Einsatz von AOP.
- LOG4J und der legitime Nachfolger SLF4J (bzw. logback) sind grossartige Software. Aber leider reduzieren sie Logging auf den technischen Aspekt. Sie sind (leider?) so erfolgreich, das sie über die Welt von Java hinaus Einfluss hatten und haben.
- Auch das automatische Induzieren von Logging-Instanzen (wie z.B. in Grails) löst dieses Problem nicht. Sie spart nur Tipparbeit, mit dem kleinen Haken, dass man sich an den Namen der Logging-Instanz erinnern muss.
Kurz: Es bedarf einer besseren Lösung. Aber einer Lösung, die auf bestehenden aufsetzt.
Im den nächsten Beiträgen werde ich eine elegante Lösung Logging mit AOP erarbeiten, die einige Vorteile in sich trägt:
- Logging Einträge lassen sich sammeln
- Logging Einträge lassen sich klassifizieren (unabhängig vom Level)
- Logging Einträge lassen sich systematisieren und sind nicht mehr von den persönlichen Vorlieben des Entwicklers abhängig
- Möglichkeit der Internationalisierung
- Wiederverwendung von Logging Einträgen
- Logging Texte lassen sich ändern (ohne Rebuild)
- Logging Level lassen sich ändern (ohne Rebuild)
- Die Entscheidung für eine Logging Library kann spät getroffen oder sogar revidiert werden
- Cross Referenzen können gebildet werden
- Unittest ohne Logging möglich
Keine Kommentare:
Kommentar veröffentlichen