Zum ersten Mal in meinem Leben befinde ich mich in einer Position, in der ich eine Java-API schreibe, die als Open Source verfügbar sein wird. Hoffentlich wird sie in viele andere Projekte einfließen.
Für die Protokollierung habe ich (und auch die Leute, mit denen ich zusammenarbeite) immer JUL verwendet ( java.util.logging
) und hatte nie irgendwelche Probleme damit. Allerdings muss ich jetzt im Detail zu verstehen, was ich für meine API-Entwicklung tun sollte. Ich habe einige Nachforschungen angestellt, und die Informationen, die ich erhalten habe, verwirren mich nur noch mehr. Daher dieser Beitrag.
Da ich aus JUL komme, bin ich in dieser Hinsicht voreingenommen. Mein Wissen über den Rest ist nicht so groß.
Bei meinen Recherchen habe ich folgende Gründe gefunden, warum die Menschen JUL nicht mögen:
-
"Ich habe lange vor der Veröffentlichung von JUL durch Sun mit der Entwicklung von Java begonnen, und es war einfach einfacher für mich, mit Logging-Framework-X weiterzumachen, als etwas Neues zu lernen. . Hmm. Ich mache keine Witze, das sagen die Leute tatsächlich. Mit diesem Argument könnten wir alle COBOL machen. (Allerdings kann ich das durchaus nachvollziehen, da ich selbst ein fauler Kerl bin)
-
"Mir gefallen die Namen der Protokollierungsebenen in JUL nicht". . Ok, ernsthaft, das ist einfach kein ausreichender Grund, um eine neue Abhängigkeit einzuführen.
-
"Mir gefällt das Standardformat der Ausgabe von JUL nicht" . Hm. Das ist nur eine Konfiguration. Sie müssen noch nicht einmal irgendetwas codetechnisch tun. (Stimmt, früher musste man vielleicht eine eigene Formatierungsklasse erstellen, um es richtig zu machen).
-
"Ich verwende andere Bibliotheken, die auch Logging-Framework-X verwenden, also dachte ich, es wäre einfacher, nur diese eine zu verwenden. . Das ist ein Zirkelschluss, nicht wahr? Warum verwendet "jeder" das Logging-Framework-X und nicht JUL?
-
"Alle anderen verwenden Logging-Framework-X" . Für mich ist dies nur ein Sonderfall des oben Gesagten. Die Mehrheit hat nicht immer Recht.
Die große Frage ist also warum nicht JUL? . Was habe ich übersehen? Der Grund für die Logging-Fassaden (SLF4J, JCL) ist, dass es in der Vergangenheit mehrere Logging-Implementierungen gab, und der Grund dafür geht meiner Meinung nach auf die Zeit vor JUL zurück. Wenn JUL perfekt wäre, dann gäbe es keine Logging Facades, oder was? Um die Sache noch verwirrender zu machen, ist JUL in gewisser Weise selbst eine Fassade, die es erlaubt, Handler, Formatierer und sogar den LogManager auszutauschen.
Anstatt mehrere Möglichkeiten zu akzeptieren, die gleiche Sache zu tun (Protokollierung), sollten wir uns nicht fragen, warum sie überhaupt notwendig waren? (und prüfen, ob diese Gründe immer noch bestehen)
Ok, meine bisherigen Nachforschungen haben ein paar Dinge ergeben, die ich für möglich halte wirkliche Probleme mit JUL:
-
Leistung . Einige sagen, dass die Leistung von SLF4J besser ist als die der anderen. Dies scheint mir ein Fall von verfrühter Optimierung zu sein. Wenn Sie Hunderte von Megabytes pro Sekunde protokollieren müssen, bin ich mir nicht sicher, ob Sie überhaupt auf dem richtigen Weg sind. JUL hat sich auch weiterentwickelt, und die Tests, die Sie mit Java 1.4 durchgeführt haben, sind möglicherweise nicht mehr zutreffend. Sie können darüber lesen aquí und diese Korrektur hat es in Java 7 geschafft. Viele sprechen auch über den Overhead der String-Verkettung in Protokollierungsmethoden. Die template-basierte Protokollierung vermeidet diesen Aufwand jedoch und ist auch in JUL vorhanden. Ich persönlich schreibe nie wirklich Template-basiertes Logging. Dafür bin ich zu faul. Wenn ich das zum Beispiel mit JUL mache:
log.finest("Lookup request from username=" + username + ", valueX=" + valueX + ", valueY=" + valueY));
meine IDE warnt mich und bittet um Erlaubnis, dass sie es ändern soll:
log.log(Level.FINEST, "Lookup request from username={0}, valueX={1}, valueY={2}", new Object[]{username, valueX, valueY});
was ich natürlich akzeptieren werde. Erlaubnis erteilt! Ich danke Ihnen für Ihre Hilfe.
Ich schreibe solche Anweisungen also nicht selbst, das macht die IDE.
Zusammenfassend kann ich sagen, dass ich nichts gefunden habe, was darauf hindeutet, dass die Leistung von JUL im Vergleich zur Konkurrenz nicht in Ordnung ist.
-
Konfiguration aus dem Klassenpfad . Standardmäßig kann JUL keine Konfigurationsdatei aus dem Klassenpfad laden. Es ist eine einige Zeilen Code um sie dazu zu bringen, dies zu tun. Ich kann verstehen, warum dies ärgerlich sein kann, aber die Lösung ist kurz und einfach.
-
Verfügbarkeit von Output-Handlern . JUL wird mit 5 Ausgabehandlern ausgeliefert: Konsole, Dateistrom, Socket und Speicher. Diese können erweitert werden oder es können neue geschrieben werden. Dies kann zum Beispiel das Schreiben in UNIX/Linux Syslog und Windows Event Log sein. Ich persönlich hatte noch nie diese Anforderung und habe auch noch nie gesehen, dass sie verwendet wurde, aber ich kann durchaus nachvollziehen, warum dies eine nützliche Funktion sein kann. Logback wird zum Beispiel mit einem Appender für Syslog geliefert. Dennoch würde ich argumentieren, dass
- 99,5 % des Bedarfs an Ausgabezielen werden durch das abgedeckt, was in JUL out-of-the-box vorhanden ist.
- Besondere Bedürfnisse könnten durch benutzerdefinierte Handler zusätzlich zu JUL und nicht zusätzlich zu etwas anderem befriedigt werden. Nichts deutet für mich darauf hin, dass es mehr Zeit kostet, einen Syslog-Ausgabe-Handler für JUL zu schreiben als für ein anderes Logging-Framework.
Ich mache mir wirklich Sorgen, dass ich etwas übersehen habe. Die Verwendung von Logging-Fassaden und anderen Logging-Implementierungen als JUL ist so weit verbreitet, dass ich zu dem Schluss kommen muss, dass ich es einfach nicht verstehe. Das wäre nicht das erste Mal, fürchte ich :-)
Was soll ich also mit meiner API machen? Ich möchte, dass sie erfolgreich wird. Ich kann natürlich einfach "mit dem Strom schwimmen" und SLF4J implementieren (was heutzutage am populärsten zu sein scheint), aber für mein eigenes Wohl muss ich immer noch verstehen, was genau an der heutigen JUL falsch ist, das den ganzen Wirbel rechtfertigt? Werde ich mich selbst sabotieren, wenn ich JUL für meine Bibliothek wähle?
Leistung prüfen
(Abschnitt hinzugefügt von nolan600 am 07-JUL-2012)
Unten finden Sie einen Hinweis von Ceki, dass die Parametrisierung von SLF4J 10 Mal oder mehr schneller ist als die von JUL. Also habe ich begonnen, einige einfache Tests durchzuführen. Auf den ersten Blick ist die Behauptung sicherlich richtig. Hier sind die vorläufigen Ergebnisse (aber lesen Sie weiter!):
- Ausführungszeit SLF4J, Backend Logback: 1515
- Ausführungszeit SLF4J, Backend JUL: 12938
- Ausführungszeit JUL: 16911
Die obigen Zahlen sind in Millisekunden angegeben, weniger ist also besser. Der 10-fache Leistungsunterschied ist also zunächst einmal ziemlich knapp. Meine erste Reaktion: Das ist eine Menge!
Dies ist der Kern des Tests. Wie man sieht, werden in einer Schleife ein Integer und ein String gebildet, die dann in der Log-Anweisung verwendet werden:
for (int i = 0; i < noOfExecutions; i++) {
for (char x=32; x<88; x++) {
String someString = Character.toString(x);
// here we log
}
}
(Ich wollte, dass die Protokollanweisung sowohl einen primitiven Datentyp (in diesem Fall einen int) als auch einen komplexeren Datentyp (in diesem Fall einen String) enthält. Ich bin mir nicht sicher, ob das wichtig ist, aber so ist es).
Die Protokollanweisung für SLF4J:
logger.info("Logging {} and {} ", i, someString);
Die Protokollanweisung für JUL:
logger.log(Level.INFO, "Logging {0} and {1}", new Object[]{i, someString});
Die JVM wurde mit demselben Test "aufgewärmt", bevor die eigentliche Messung durchgeführt wurde. Es wurde Java 1.7.03 auf Windows 7 verwendet. Es wurden die neuesten Versionen von SLF4J (v1.6.6) und Logback (v1.0.6) verwendet. Stdout und stderr wurden auf das Gerät null umgeleitet.
Doch wie sich jetzt herausstellt, verbringt JUL die meiste Zeit in getSourceClassName()
denn JUL gibt standardmäßig den Namen der Quellklasse in der Ausgabe aus, während Logback dies nicht tut. Wir vergleichen also Äpfel mit Birnen. Ich muss den Test wiederholen und die Logging-Implementierungen auf ähnliche Weise konfigurieren, damit sie tatsächlich dasselbe ausgeben. Ich vermute jedoch, dass SLF4J+Logback immer noch am besten abschneidet, aber weit entfernt von den ursprünglichen Zahlen wie oben angegeben. Bleiben Sie dran.
Btw: Der Test war das erste Mal, dass ich tatsächlich mit SLF4J oder Logback gearbeitet habe. Eine angenehme Erfahrung. JUL ist sicherlich viel weniger einladend, wenn man damit anfängt.
Prüfung der Leistung (Teil 2)
(Abschnitt hinzugefügt von nolan600 am 08-JUL-2012)
Wie sich herausstellte, spielt es für die Leistung keine Rolle, wie Sie Ihr Muster in JUL konfigurieren, d.h. ob es den Quellennamen enthält oder nicht. Ich habe es mit einem sehr einfachen Muster versucht:
java.util.logging.SimpleFormatter.format="%4$s: %5$s [%1$tc]%n"
und das änderte nichts an den oben genannten Zeitangaben. Mein Profiler zeigte, dass der Logger immer noch viel Zeit mit Aufrufen von getSourceClassName()
auch wenn dies nicht Teil meines Musters war. Das Muster spielt keine Rolle.
Ich komme daher zu dem Schluss, dass zumindest bei der getesteten vorlagenbasierten Protokollanweisung ein Leistungsunterschied von etwa Faktor 10 zwischen JUL (langsam) und SLF4J+Logback (schnell) zu bestehen scheint. Genau wie Ceki sagte.
Ich kann auch eine andere Sache sehen, nämlich dass SLF4J's getLogger()
Anruf ist viel teurer als der von JUL dito. (95 ms gegenüber 0,3 ms, wenn mein Profiler korrekt ist). Das macht Sinn. SLF4J hat einige Zeit auf die Bindung der zugrunde liegenden Logging-Implementierung zu tun. Das macht mir keine Angst. Diese Aufrufe sollten während der Lebensdauer einer Anwendung eher selten sein. Die Schnelligkeit sollte in den eigentlichen Log-Aufrufen liegen.
Endgültige Schlussfolgerung
(Abschnitt hinzugefügt von peterh am 08-JUL-2012)
Ich danke Ihnen für Ihre Antworten. Im Gegensatz zu dem, was ich anfangs dachte, habe ich mich letztendlich für die Verwendung von SLF4J für meine API entschieden. Dies ist auf eine Reihe von Dingen und Ihre Eingabe basiert:
-
Sie bietet Flexibilität bei der Wahl der Protokollimplementierung zum Zeitpunkt der Bereitstellung.
-
Probleme mit der mangelnden Flexibilität der JUL-Konfiguration bei der Ausführung innerhalb eines Anwendungsservers.
-
SLF4J ist sicherlich viel schneller, wie oben beschrieben, insbesondere wenn man es mit Logback koppelt. Auch wenn dies nur ein grober Test war, habe ich Grund zu der Annahme, dass bei SLF4J+Logback viel mehr Aufwand in die Optimierung geflossen ist als bei JUL.
-
Dokumentation. Die Dokumentation für SLF4J ist einfach viel umfangreicher und präziser.
-
Flexibilität des Musters. Bei den Tests wollte ich erreichen, dass JUL das Standardmuster von Logback nachahmt. Dieses Muster enthält den Namen des Threads. Es stellte sich heraus, dass JUL dies nicht von Haus aus kann. Ok, ich habe es bis jetzt nicht vermisst, aber ich denke nicht, dass es eine Sache ist, die in einem Log-Framework fehlen sollte. Punkt!
-
Die meisten (oder viele) Java-Projekte verwenden heute Maven, so dass das Hinzufügen einer Abhängigkeit keine große Sache ist, insbesondere wenn diese Abhängigkeit ziemlich stabil ist, d.h. ihre API nicht ständig ändert. Dies scheint für SLF4J der Fall zu sein. Auch das SLF4J jar und seine Freunde sind von der Größe her klein.
Das Seltsame war, dass ich mich über JUL ziemlich geärgert habe, nachdem ich ein wenig mit SLF4J gearbeitet hatte. Ich bedaure immer noch, dass es mit JUL so sein muss. JUL ist weit davon entfernt, perfekt zu sein, aber es erfüllt seine Aufgabe irgendwie. Nur nicht ganz gut genug. Das Gleiche kann man über Properties
als Beispiel, aber wir denken nicht daran, das zu abstrahieren, damit die Leute ihre eigene Konfigurationsbibliothek einfügen können und so weiter. Ich denke, der Grund dafür ist, dass Properties
liegt knapp über dem Balken, während das Gegenteil für JUL von heute zutrifft ... und in der Vergangenheit lag er bei Null, weil es ihn nicht gab.
Endgültige Schlussfolgerung (vielleicht)
(Abschnitt hinzugefügt von peterh am 02-OCT-2022)
Mit Java 9 wurde die System.Logger die als Fassade für Protokollierungsimplementierungen gedacht ist. Soweit ich das beurteilen kann, konkurriert es also mit SLF4J, hat aber den Vorteil, dass es im JDK enthalten ist. Vielleicht sollten Bibliotheksentwickler also eher System.Logger als SLF4J verwenden. ?
Ich finde dieser Blogbeitrag von Renato Athaydes, der dies sehr gut erklärt. (btw: der von Renato erwähnte Fehler in der Log4j-v2-Bridge scheint fest mit v2.13.2 von Log4j v2)