Zum ersten Mal in meinem Leben befinde ich mich in einer Position, in der ich eine Java-API schreibe, die Open Source ist. Hoffentlich in vielen anderen Projekten enthalten sein.
Für die Protokollierung habe ich (und in der Tat die Leute, mit denen ich arbeite) immer JUL (java.util.logging) verwendet und hatte nie Probleme damit. Jetzt muss ich jedoch genauer verstehen, was ich für meine API-Entwicklung tun soll. Ich habe einige Nachforschungen angestellt und mit den Informationen, die ich habe, werde ich nur verwirrter. Daher dieser Beitrag.
Da ich von JUL komme, bin ich voreingenommen. Mein Wissen über den Rest ist nicht so groß.
Aufgrund meiner Recherchen habe ich folgende Gründe gefunden, warum die Leute JUL nicht mögen:
„Ich begann in Java zu entwickeln , lange bevor Sun Juli veröffentlicht und es war einfach leichter für mich , mit Logging-Framework-X fortzusetzen , anstatt etwas Neues zu lernen“ . Hmm. Ich mache keine Witze, das sagen die Leute. Mit diesem Argument könnten wir alle COBOL machen. (Ich kann mich jedoch durchaus darauf beziehen, dass ich selbst ein fauler Typ bin.)
"Ich mag die Namen der Protokollierungsstufen in JUL nicht" . Ok, im Ernst, das ist einfach kein Grund genug, eine neue Abhängigkeit einzuführen.
"Ich mag das Standardformat der Ausgabe von JUL nicht" . Hmm. Dies ist nur eine Konfiguration. Sie müssen nicht einmal Code tun. (Richtig, früher mussten Sie möglicherweise Ihre eigene Formatter-Klasse erstellen, um es richtig zu machen).
„Ich benutze andere Bibliotheken , die auch Logging-Framework-X verwenden , so dass ich dachte , es einfacher , nur dass man verwenden“ . Dies ist ein zirkuläres Argument, nicht wahr? Warum verwendet "jeder" Logging-Framework-X und nicht JUL?
"Alle anderen verwenden Logging-Framework-X" . Dies ist für mich nur ein Sonderfall der oben genannten. Die Mehrheit ist nicht immer richtig.
Die wirklich große Frage ist also, warum nicht JUL? . Was habe ich vermisst? Das Grundprinzip für die Protokollierung von Fassaden (SLF4J, JCL) ist, dass es in der Vergangenheit mehrere Protokollierungsimplementierungen gegeben hat und der Grund dafür aus meiner Sicht wirklich auf die Zeit vor JUL zurückgeht. Wenn JUL perfekt wäre, gäbe es keine Holzfassaden, oder was? Um die Sache noch verwirrender zu machen, ist JUL bis zu einem gewissen Grad eine Fassade, mit der Handler, Formatierer und sogar der LogManager ausgetauscht werden können.
Sollten wir uns nicht fragen, warum sie überhaupt notwendig waren, anstatt mehrere Methoden zu nutzen, um dasselbe zu tun (Protokollierung)? (und sehen, ob diese Gründe noch existieren)
Ok, meine bisherigen Forschungen haben zu ein paar Dingen geführt, von denen ich sehe, dass sie echte Probleme mit JUL darstellen können:
Leistung . Einige sagen, dass die Leistung in SLF4J den anderen überlegen ist. Dies scheint mir ein Fall vorzeitiger Optimierung zu sein. Wenn Sie Hunderte von Megabyte pro Sekunde protokollieren müssen, bin ich mir nicht sicher, ob Sie auf dem richtigen Weg sind. JUL hat sich ebenfalls weiterentwickelt und die Tests, die Sie unter Java 1.4 durchgeführt haben, sind möglicherweise nicht mehr wahr. Sie können hier darüber lesen , und dieses Update hat es in Java 7 geschafft. Viele sprechen auch über den Overhead der Verkettung von Zeichenfolgen bei Protokollierungsmethoden. Die vorlagenbasierte Protokollierung vermeidet diese Kosten und ist auch in JUL vorhanden. Persönlich schreibe ich nie wirklich vorlagenbasierte Protokollierung. Dafür zu faul. Zum Beispiel, wenn ich das mit JUL mache:
log.finest("Lookup request from username=" + username + ", valueX=" + valueX + ", valueY=" + valueY));
Meine IDE warnt mich und bittet um Erlaubnis, dass sie geändert werden soll in:
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 ! Danke für deine Hilfe.
Ich schreibe solche Aussagen also nicht selbst, das wird von der IDE gemacht.
Zusammenfassend zum Thema Leistung habe ich nichts gefunden, was darauf hindeuten würde, dass die Leistung von JUL im Vergleich zur Konkurrenz nicht in Ordnung ist.
Konfiguration aus dem Klassenpfad . Out-of-the-Box JUL kann keine Konfigurationsdatei aus dem Klassenpfad laden. Es sind ein paar Codezeilen , um dies zu erreichen. Ich kann sehen, warum dies ärgerlich sein mag, aber die Lösung ist kurz und einfach.
Verfügbarkeit von Ausgabe-Handlern . JUL wird mit 5 sofort einsatzbereiten Ausgabehandlern geliefert: Konsole, Dateistream, Socket und Speicher. Diese können erweitert oder neue geschrieben werden. Dies kann beispielsweise das Schreiben in UNIX / Linux Syslog und Windows Event Log sein. Ich persönlich hatte diese Anforderung noch nie und habe sie auch noch nie gesehen, aber ich kann mich durchaus darauf beziehen, warum sie möglicherweise eine nützliche Funktion ist. Logback wird beispielsweise mit einem Appender für Syslog geliefert. Trotzdem würde ich das argumentieren
- 99,5% des Bedarfs an Ausgabezielen werden durch den sofort einsatzbereiten JUL gedeckt.
- Spezielle Bedürfnisse könnten von benutzerdefinierten Handlern zusätzlich zu JUL und nicht zu etwas anderem berücksichtigt werden. Nichts deutet darauf hin, dass das Schreiben eines Syslog-Ausgabehandlers für JUL länger dauert als für ein anderes Protokollierungsframework.
Ich bin wirklich besorgt, dass ich etwas übersehen habe. Die Verwendung von Protokollierungsfassaden und anderen Protokollierungsimplementierungen als JUL ist so weit verbreitet, dass ich zu dem Schluss kommen muss, dass ich es einfach nicht verstehe. Das wäre leider nicht das erste Mal. :-)
Was soll ich mit meiner API tun? Ich möchte, dass es erfolgreich wird. Ich kann natürlich einfach "mit dem Fluss gehen" und SLF4J implementieren (was heutzutage am beliebtesten zu sein scheint), aber um meinetwillen muss ich immer noch genau verstehen, was mit dem heutigen JUL falsch ist, der all den Flaum rechtfertigt? Werde ich mich selbst sabotieren, indem ich JUL für meine Bibliothek wähle?
Leistung testen
(Abschnitt hinzugefügt von nolan600 am 07-JUL-2012)
Im Folgenden wird von Ceki darauf hingewiesen, dass die Parametrisierung von SLF4J zehnmal oder schneller als die von JUL ist. Also habe ich angefangen, 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 ms, also ist weniger besser. Der 10-fache Leistungsunterschied ist also zunächst ziemlich eng. Meine erste Reaktion: Das ist viel!
Hier ist der Kern des Tests. Wie zu sehen ist, werden eine Ganzzahl und eine Zeichenfolge in einer Schleife zusammengefasst, die dann in der Protokollanweisung verwendet wird:
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 ein int) als auch einen komplexeren Datentyp (in diesem Fall einen String) hat. Ich bin mir nicht sicher, ob es wichtig ist, aber da haben Sie 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", der einmal ausgeführt wurde, bevor die eigentliche Messung durchgeführt wurde. Java 1.7.03 wurde unter Windows 7 verwendet. Die neuesten Versionen von SLF4J (v1.6.6) und Logback (v1.0.6) wurden verwendet. Stdout und stderr wurden auf das Nullgerät umgeleitet.
Es stellt sich jedoch heraus, dass JUL die meiste Zeit damit verbringt, getSourceClassName()
da JUL standardmäßig den Namen der Quellklasse in der Ausgabe druckt, während Logback dies nicht tut. Also vergleichen wir Äpfel und Orangen. Ich muss den Test erneut durchführen und die Protokollierungsimplementierungen auf ähnliche Weise konfigurieren, damit sie tatsächlich dasselbe Material ausgeben. Ich vermute jedoch, dass SLF4J + Logback immer noch die Nase vorn hat, aber weit von den oben angegebenen Anfangszahlen entfernt ist. Bleib dran.
Übrigens: 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 Sie anfangen.
Testleistung (Teil 2)
(Abschnitt hinzugefügt von nolan600 am 08-JUL-2012)
Wie sich herausstellt, spielt es für die Leistung keine Rolle, wie Sie Ihr Muster in JUL konfigurieren, dh 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 die obigen Zeiten überhaupt nicht. Mein Profiler stellte fest, dass der Logger immer noch viel Zeit mit Anrufen verbrachte, getSourceClassName()
auch wenn dies nicht Teil meines Musters war. Das Muster spielt keine Rolle.
Ich komme daher zu dem Thema Leistung, dass zumindest für die getestete vorlagenbasierte Protokollanweisung der tatsächliche Leistungsunterschied zwischen JUL (langsam) und SLF4J + Protokollierung (schnell) ungefähr um den Faktor 10 zu liegen scheint. Genau wie Ceki sagte.
Ich kann auch eine andere Sache sehen, nämlich dass der getLogger()
Anruf von SLF4J viel teurer ist als der von JUL. (95 ms vs 0,3 ms, wenn mein Profiler genau ist). Das macht Sinn. SLF4J muss einige Zeit für die Bindung der zugrunde liegenden Protokollierungsimplementierung aufwenden. Das macht mir keine Angst. Diese Aufrufe sollten während der Lebensdauer einer Anwendung eher selten sein. Die Echtheit sollte in den eigentlichen Protokollaufrufen enthalten sein.
Schlußfolgerung
(Abschnitt hinzugefügt von nolan600 am 08-JUL-2012)
Vielen Dank für all Ihre Antworten. Im Gegensatz zu dem, was ich ursprünglich dachte, habe ich mich letztendlich für SLF4J für meine API entschieden. Dies basiert auf einer Reihe von Dingen und Ihrer Eingabe:
Es bietet Flexibilität bei der Auswahl der Protokollimplementierung zur Bereitstellungszeit.
Probleme mit mangelnder Flexibilität der JUL-Konfiguration bei der Ausführung auf einem Anwendungsserver.
SLF4J ist sicherlich viel schneller als oben beschrieben, insbesondere wenn Sie es mit Logback koppeln. Auch wenn dies nur ein grober Test war, habe ich Grund zu der Annahme, dass bei SLF4J + Logback viel mehr Anstrengungen zur Optimierung unternommen wurden als bei JUL.
Dokumentation. Die Dokumentation für SLF4J ist einfach viel umfassender und präziser.
Musterflexibilität. Während ich die Tests durchführte, wollte ich, dass JUL das Standardmuster von Logback nachahmt. Dieses Muster enthält den Namen des Threads. Es stellt sich heraus, dass JUL dies nicht sofort tun kann. Ok, ich habe es bis jetzt nicht verpasst, aber ich denke nicht, dass es etwas ist, das in einem Protokoll-Framework fehlen sollte. Zeitraum!
Die meisten (oder viele) Java-Projekte verwenden heutzutage Maven, daher ist das Hinzufügen einer Abhängigkeit nicht so wichtig, insbesondere wenn diese Abhängigkeit ziemlich stabil ist, dh ihre API nicht ständig ändert. Dies scheint für SLF4J zu gelten. Auch das SLF4J-Glas und seine Freunde sind klein.
Das Seltsame war also, dass ich mich über JUL ziemlich aufgeregt habe, nachdem ich ein bisschen mit SLF4J gearbeitet hatte. Ich bedaure immer noch, dass es bei JUL so sein muss. JUL ist alles andere als perfekt, macht aber irgendwie den Job. Nur nicht gut genug. Das Gleiche kann Properties
als Beispiel angeführt werden, aber wir denken nicht daran, dies zu abstrahieren, damit die Leute ihre eigene Konfigurationsbibliothek anschließen können und was Sie haben. Ich denke, der Grund dafür ist, dass es Properties
knapp über der Latte liegt, während das Gegenteil für JUL von heute gilt ... und in der Vergangenheit bei Null, weil es nicht existierte.
java.lang.System.Logger
wurde. Hierbei handelt es sich um eine Schnittstelle , die auf das gewünschte Protokollierungsframework umgeleitet werden kann, sofern dieses Framework aufholt und eine Implementierung dieser Schnittstelle bereitstellt. In Kombination mit der Modularisierung können Sie sogar eine Anwendung mit einer gebündelten JRE bereitstellen, die keine enthält java.util.logging
, wenn Sie ein anderes Framework bevorzugen.