446 Stimmen

Warum nicht java.util.logging verwenden?

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:

  1. "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)

  2. "Mir gefallen die Namen der Protokollierungsebenen in JUL nicht". . Ok, ernsthaft, das ist einfach kein ausreichender Grund, um eine neue Abhängigkeit einzuführen.

  3. "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).

  4. "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?

  5. "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:

  1. 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.

  2. 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.

  3. 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

    1. 99,5 % des Bedarfs an Ausgabezielen werden durch das abgedeckt, was in JUL out-of-the-box vorhanden ist.
    2. 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:

  1. Sie bietet Flexibilität bei der Wahl der Protokollimplementierung zum Zeitpunkt der Bereitstellung.

  2. Probleme mit der mangelnden Flexibilität der JUL-Konfiguration bei der Ausführung innerhalb eines Anwendungsservers.

  3. 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.

  4. Dokumentation. Die Dokumentation für SLF4J ist einfach viel umfangreicher und präziser.

  5. 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!

  6. 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)

256voto

Ceki Punkte 25510

Haftungsausschluss : Ich bin der Gründer der Projekte log4j, SLF4J und logback.

Es gibt objektive Gründe, SLF4J den Vorzug zu geben. Zum einen, SLF4J lässt dem Endbenutzer die Freiheit, das zugrunde liegende Logging-Framework zu wählen . Außerdem bevorzugen versiertere Nutzer eher logback, das über log4j hinausgehende Möglichkeiten bietet , wobei J.U.L. weit zurückfällt. Die Funktionen von j.u.l. mögen für einige Benutzer ausreichend sein, für viele andere ist es das nicht. Kurz gesagt, wenn Ihnen die Protokollierung wichtig ist, sollten Sie SLF4J mit logback als zugrunde liegende Implementierung verwenden. Wenn die Protokollierung unwichtig ist, ist j.u.l in Ordnung.

Als OSS-Entwickler müssen Sie jedoch die Vorlieben Ihrer Benutzer berücksichtigen und nicht nur Ihre eigenen. Daraus folgt, dass Sie SLF4J nicht übernehmen sollten, weil vous überzeugt sind, dass SLF4J besser ist als j.u.l, sondern weil die meisten Java-Entwickler derzeit (Juli 2012) SLF4J als Logging-API bevorzugen. Wenn Sie sich letztendlich entscheiden, sich nicht um die öffentliche Meinung zu kümmern, sollten Sie die folgenden Fakten berücksichtigen:

  1. Diejenigen, die J.U.L. bevorzugen, tun dies aus Bequemlichkeit, weil J.U.L. mit dem JDK gebündelt ist. Meines Wissens gibt es keine anderen objektiven Argumente, die für j.u.l. sprechen.
  2. Ihre eigene Vorliebe für J.U.L. ist genau das, eine Vorliebe .

Harte Fakten" über die öffentliche Meinung zu stellen, scheint zwar mutig zu sein, ist in diesem Fall aber ein logischer Trugschluss.

Falls Sie noch nicht überzeugt sind, JB Nizet liefert ein zusätzliches, schlagkräftiges Argument:

Der Endnutzer könnte diese Anpassung jedoch bereits für seine eigenen Zwecke vorgenommen haben. eigenen Code oder eine andere Bibliothek, die log4j oder logback verwendet. j.u.l ist erweiterbar, aber wenn man logback, j.u.l, log4j und Gott weiß weiß, welches andere Logging-Framework, weil er vier Bibliotheken verwendet, die vier verschiedene Logging-Frameworks verwenden, ist mühsam. Wenn Sie SLF4J verwenden, können Sie erlauben Sie ihm, die gewünschten Logging-Frameworks zu konfigurieren, nicht das das Sie ausgewählt haben. Denken Sie daran, dass ein typisches Projekt Myriaden von Bibliotheken verwendet, und nicht nur Ihre .

Wenn Sie, aus welchen Gründen auch immer, die SLF4J-API hassen und ihre Verwendung Ihnen den Spaß an der Arbeit verderben würde, dann entscheiden Sie sich für j.u.l. Schließlich gibt es Mittel und Wege, um j.u.l. auf SLF4J umlenken .

Übrigens ist die j.u.l-Parametrisierung mindestens 10-mal langsamer als die von SLF4J, was am Ende einen spürbaren Unterschied ausmacht.

37voto

Joachim Sauer Punkte 290477
  1. java.util.logging wurde in Java 1.4 eingeführt. Es gab schon vorher Verwendungen für die Protokollierung. Deshalb gibt es auch viele andere Protokollierungs-APIs. Diese APIs wurden bereits vor Java 1.4 intensiv genutzt und hatten daher einen großen Marktanteil, der nicht einfach auf Null sank, als 1.4 veröffentlicht wurde.

  2. JUL hat nicht besonders gut angefangen. Viele der von Ihnen erwähnten Dinge waren in 1.4 noch viel schlimmer und wurden erst in 1.5 besser (und ich vermute, auch in 6, aber ich bin mir nicht ganz sicher).

  3. JUL ist nicht gut geeignet für mehrere Anwendungen mit unterschiedlichen Konfigurationen in derselben JVM (z. B. mehrere Webanwendungen, die nicht miteinander interagieren sollten). Tomcat muss einige Hürden überwinden, damit das funktioniert (und JUL neu implementieren, wenn ich das richtig verstanden habe).

  4. Sie können nicht immer beeinflussen, welches Logging-Framework Ihre Bibliotheken verwenden. Daher hilft die Verwendung von SLF4J (das eigentlich nur eine sehr dünne API-Schicht über anderen Bibliotheken ist) dabei, ein einigermaßen konsistentes Bild der gesamten Logging-Welt zu erhalten (so dass Sie das zugrundeliegende Logging-Framework bestimmen können, während Sie immer noch Bibliotheks-Logging im selben System haben).

  5. Bibliotheken können sich nicht einfach ändern. Wenn eine frühere Version einer Bibliothek die Protokollierungsbibliothek-X verwendet hat, kann sie nicht einfach zu Protokollierungsbibliothek-Y (z. B. JUL) wechseln, selbst wenn letztere eindeutig überlegen ist. Jeder Benutzer dieser Bibliothek müsste das neue Logging-Framework erlernen und (zumindest) sein Logging neu konfigurieren. Das ist ein absolutes Tabu, vor allem, wenn es für die meisten Leute keinen offensichtlichen Vorteil bringt.

Nach all dem denke ich, dass JUL mindestens heutzutage eine echte Alternative zu anderen Logging-Frameworks.

35voto

JB Nizet Punkte 654813

IMHO liegt der Hauptvorteil bei der Verwendung einer Logging-Fassade wie slf4j darin, dass Sie dem Endbenutzer der Bibliothek die Wahl lassen, welche konkrete Logging-Implementierung er wünscht, anstatt dem Endbenutzer Ihre Wahl aufzuzwingen.

Vielleicht hat er Zeit und Geld in Log4j oder LogBack investiert (spezielle Formatierer, Appenders, etc.) und zieht es vor, Log4j oder LogBack weiter zu verwenden, anstatt jul zu konfigurieren. Kein Problem: slf4j erlaubt das. Ist es eine kluge Entscheidung, Log4j statt jul zu verwenden? Vielleicht, vielleicht auch nicht. Aber das ist Ihnen egal. Lassen Sie den Endbenutzer wählen, was er bevorzugt.

9voto

OldCurmudgeon Punkte 62510

Ich habe, wie Sie vermutlich auch, mit JUL angefangen, weil es das einfachste war, um sofort loszulegen. Im Laufe der Jahre habe ich mir jedoch gewünscht, ich hätte etwas mehr Zeit für die Auswahl aufgewendet.

Mein Hauptproblem ist nun, dass wir eine beträchtliche Menge an "Bibliotheks"-Code haben, der in vielen Anwendungen verwendet wird, die alle JUL nutzen. Immer wenn ich diese Tools in einer Web-Service-Anwendung verwende, verschwindet die Protokollierung einfach oder geht an eine unvorhersehbare oder seltsame Stelle.

Unsere Lösung bestand darin, dem Bibliothekscode eine Fassade hinzuzufügen, die dazu führte, dass sich die Bibliotheksaufrufe nicht änderten, sondern dynamisch an den jeweils verfügbaren Protokollierungsmechanismus weitergeleitet wurden. Wenn sie in einem POJO-Tool enthalten sind, werden sie an JUL weitergeleitet, aber wenn sie als Web-App bereitgestellt werden, werden sie an LogBack weitergeleitet.

Wir bedauern natürlich, dass der Bibliothekscode keine parametrisierte Protokollierung verwendet, aber das kann jetzt bei Bedarf nachgerüstet werden.

Wir haben slf4j verwendet, um die Fassade zu erstellen.

3voto

weberjn Punkte 1688

Ich habe jul gegen slf4j-1.7.21 über logback-1.1.7, Ausgabe auf eine SSD, Java 1.8, Win64

jul lief 48449 ms, logback 27185 ms für eine 1M-Schleife.

Dennoch, ein wenig mehr Geschwindigkeit und eine etwas schönere API ist für mich nicht 3 Bibliotheken und 800K wert.

package log;

import java.util.logging.Level;
import java.util.logging.Logger;

public class LogJUL
{
    final static Logger logger = Logger.getLogger(LogJUL.class.getSimpleName());

    public static void main(String[] args) 
    {
        int N = 1024*1024;

        long l = System.currentTimeMillis();

        for (int i = 0; i < N; i++)
        {
            Long lc = System.currentTimeMillis();

            Object[] o = { lc };

            logger.log(Level.INFO,"Epoch time {0}", o);
        }

        l = System.currentTimeMillis() - l;

        System.out.printf("time (ms) %d%n", l);
    }
}

und

package log;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class LogSLF
{
    static Logger logger = LoggerFactory.getLogger(LogSLF.class);

    public static void main(String[] args) 
    {
        int N = 1024*1024;

        long l = System.currentTimeMillis();

        for (int i = 0; i < N; i++)
        {
            Long lc = System.currentTimeMillis();

            logger.info("Epoch time {}", lc);
        }

        l = System.currentTimeMillis() - l;

        System.out.printf("time (ms) %d%n", l);
    }

}

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X