it-swarm.com.de

Wie gehe ich mit der Debug-Ausgabe in Java richtig um?

Da meine aktuellen Java - Projekte immer größer werden, habe ich das ebenfalls wachsende Bedürfnis, Debug-Ausgaben in mehrere Punkte meines Codes einzufügen.

Um diese Funktion je nach Öffnen oder Schließen der Testsitzungen entsprechend zu aktivieren oder zu deaktivieren, setze ich normalerweise ein private static final boolean DEBUG = false zu Beginn des Unterrichts prüfen meine Tests und verwenden es trivial auf diese Weise (zum Beispiel):

public MyClass {
  private static final boolean DEBUG = false;

  ... some code ...

  public void myMethod(String s) {
    if (DEBUG) {
      System.out.println(s);
    }
  }
}

und dergleichen.

Aber das macht mich nicht glücklich, denn natürlich funktioniert es, aber es könnte zu viele Klassen geben, in denen DEBUG auf true gesetzt werden kann, wenn Sie nicht nur ein paar davon anstarren.

Umgekehrt würde ich (wie - ich denke - viele andere) nicht gerne die gesamte Anwendung in den Debug-Modus versetzen, da die ausgegebene Textmenge überwältigend sein könnte.

Gibt es also einen richtigen Weg, um mit einer solchen Situation architektonisch umzugehen, oder den richtigen Weg, das DEBUG-Klassenmitglied zu verwenden?

32
Federico Zancan

Sie möchten sich ein Protokollierungsframework und möglicherweise ein Protokollierungsfassadenframework ansehen.

Es gibt mehrere Protokollierungsframeworks, häufig mit überlappenden Funktionen, so dass sich im Laufe der Zeit viele auf eine gemeinsame API stützten oder über ein Fassadenframework verwendet wurden, um ihre Verwendung zu abstrahieren und ihren Austausch zu ermöglichen wenn benötigt.

Frameworks

Einige Protokollierungs-Frameworks

Einige Holzfassaden

Verwendungszweck

Grundlegendes Beispiel

Mit den meisten dieser Frameworks können Sie etwas von dem Formular schreiben (hier mit slf4j-api Und logback-core):

package chapters.introduction;

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

// copied from: http://www.slf4j.org/manual.html
public class HelloWorld {

  public static void main(String[] args) {
    final Logger logger = LoggerFactory.getLogger(HelloWorld.class);

    logger.debug("Hello world, I'm a DEBUG level message");
    logger.info("Hello world, I'm an INFO level message");
    logger.warn("Hello world, I'm a WARNING level message");
    logger.error("Hello world, I'm an ERROR level message");
  }
}

Beachten Sie die Verwendung einer aktuellen Klasse zum Erstellen eines dedizierten Protokollierers, mit dem SLF4J/LogBack die Ausgabe formatieren und angeben kann, woher die Protokollierungsnachricht stammt.

Wie im SLF4J-Handbuch angegeben, ist ein typisches Verwendungsmuster in einer Klasse normalerweise:

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

public class MyClass {

    final Logger logger = LoggerFactory.getLogger(MyCLASS.class);

    public void doSomething() {
        // some code here
        logger.debug("this is useful");

        if (isSomeConditionTrue()) {
            logger.info("I entered by conditional block!");
        }
    }
}

Tatsächlich ist es jedoch noch üblicher, den Logger mit dem folgenden Formular zu deklarieren:

private static final Logger LOGGER = LoggerFactory.getLogger(MyClass.class);

Auf diese Weise kann der Logger auch innerhalb statischer Methoden verwendet werden und wird von allen Instanzen der Klasse gemeinsam genutzt. Dies ist sehr wahrscheinlich Ihre bevorzugte Form. Wie von Brendan Long in den Kommentaren erwähnt, möchten Sie jedoch die Auswirkungen verstehen und entsprechend entscheiden (dies gilt für alle Protokollierungsframeworks, die diesen Redewendungen folgen).

Es gibt andere Möglichkeiten, Logger zu instanziieren, z. B. mithilfe eines Zeichenfolgenparameters, um einen benannten Logger zu erstellen:

Logger logger = LoggerFactory.getLogger("MyModuleName");

Debug-Ebenen

Die Debug-Levels variieren von Framework zu Framework, aber die häufigsten sind (in der Reihenfolge der Kritikalität, von gutartig bis schlecht und von wahrscheinlich sehr häufig bis hoffentlich sehr selten):

  • TRACE  Sehr detaillierte Informationen. Sollte nur in Protokolle geschrieben werden. Wird nur verwendet, um den Programmfluss an Kontrollpunkten zu verfolgen.

  • DEBUG  Detaillierte Informationen. Sollte nur in Protokolle geschrieben werden.

  • INFO  Bemerkenswerte Laufzeitereignisse. Sollte auf einer Konsole sofort sichtbar sein, also sparsam verwenden.

  • WARNING  Laufzeit-Kuriositäten und behebbare Fehler.

  • ERROR  Andere Laufzeitfehler oder unerwartete Bedingungen.

  • FATAL  Schwere Fehler, die zu einer vorzeitigen Beendigung führen.

Blöcke und Wachen

Angenommen, Sie haben einen Codeabschnitt, in dem Sie eine Reihe von Debug-Anweisungen schreiben möchten. Dies kann sich schnell auf Ihre Leistung auswirken, sowohl aufgrund der Auswirkungen der Protokollierung selbst als auch aufgrund der Generierung von Parametern, die Sie möglicherweise an die Protokollierungsmethode übergeben.

Um diese Art von Problem zu vermeiden, möchten Sie häufig etwas von der Form schreiben:

if (LOGGER.isDebugEnabled()) {
   // lots of debug logging here, or even code that
   // is only used in a debugging context.
   LOGGER.debug(" result: " + heavyComputation());
}

Wenn Sie diesen Schutz vor Ihrem Block von Debug-Anweisungen nicht verwendet haben, obwohl die Nachrichten möglicherweise nicht ausgegeben werden (wenn beispielsweise Ihr Logger derzeit so konfiguriert ist, dass nur Dinge über der Ebene INFO gedruckt werden), wird die heavyComputation() Methode wäre noch ausgeführt worden.

Aufbau

Die Konfiguration hängt stark von Ihrem Protokollierungsframework ab, bietet jedoch meist dieselben Techniken:

  • programmatische Konfiguration (zur Laufzeit über eine API - ermöglicht Laufzeitänderungen),
  • statische deklarative Konfiguration (zum Start, normalerweise über eine XML- oder Eigenschaftendatei - wahrscheinlich das, was Sie zuerst benötigen).

Sie bieten auch meist die gleichen Fähigkeiten:

  • konfiguration des Formats der Ausgabemeldung (Zeitstempel, Markierungen usw.),
  • konfiguration der Ausgangspegel,
  • konfiguration feinkörniger Filter (zum Beispiel zum Einschließen/Ausschließen von Paketen oder Klassen),
  • konfiguration von Appendern, um zu bestimmen, wo protokolliert werden soll (Konsole, Datei, Webdienst ...) und möglicherweise was mit älteren Protokollen zu tun ist (z. B. mit automatisch rollenden Dateien).

Hier ist ein allgemeines Beispiel für eine deklarative Konfiguration unter Verwendung einer logback.xml - Datei.

<configuration>

  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <!-- encoders are assigned the type
         ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
    <encoder>
      <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
    </encoder>
  </appender>

  <root level="debug">
    <appender-ref ref="STDOUT" />
  </root>
</configuration>

Wie bereits erwähnt, hängt dies von Ihrem Framework ab und es kann andere Alternativen geben (LogBack ermöglicht beispielsweise auch die Verwendung eines Groovy-Skripts). Das XML-Konfigurationsformat kann auch von Implementierung zu Implementierung variieren.

Weitere Konfigurationsbeispiele finden Sie unter anderem unter:

Etwas historischer Spaß

Bitte beachten Sie, dass Log4J derzeit ein umfangreiches Update erhält, das von Version 1.x auf 2.x übergeht. Vielleicht möchten Sie sich beide ansehen, um mehr historischen Spaß oder Verwirrung zu haben, und wenn Sie Log4J auswählen, bevorzugen Sie wahrscheinlich die 2.x-Version.

Wie Mike Partridge in den Kommentaren erwähnte, ist es erwähnenswert, dass LogBack von einem ehemaligen Log4J-Teammitglied erstellt wurde. Das wurde erstellt, um die Mängel des Java Logging Frameworks) zu beheben. Und dass die kommende Hauptversion von Log4J 2.x selbst jetzt einige Funktionen aus LogBack integriert.

Empfehlung

Fazit: Bleiben Sie so weit wie möglich entkoppelt, spielen Sie mit ein paar herum und finden Sie heraus, was für Sie am besten funktioniert. Am Ende ist es nur ein Protokollierungsframework . Außer wenn Sie einen ganz bestimmten Grund haben, abgesehen von der Benutzerfreundlichkeit und den persönlichen Vorlieben, würde einer dieser Gründe eher in Ordnung sein, sodass es keinen Sinn macht, darüber zu hängen. Die meisten davon können auch auf Ihre Bedürfnisse erweitert werden.

Wenn ich heute eine Kombination auswählen müsste, würde ich mich für LogBack + SLF4J entscheiden. Aber wenn Sie mich einige Jahre später gefragt hätten, hätte ich Log4J mit Apache Commons Logging empfohlen. Behalten Sie also Ihre Abhängigkeiten im Auge und entwickeln Sie sich mit ihnen weiter.

52
haylem

verwenden Sie ein Protokollierungsframework

meistens gibt es eine statische Factory-Methode

private static final Logger logger = Logger.create("classname");

dann können Sie Ihren Protokollierungscode mit verschiedenen Ebenen ausgeben:

logger.warning("error message");
logger.info("informational message");
logger.trace("detailed message");

dann gibt es eine einzelne Datei, in der Sie festlegen können, welche Nachrichten für jede Klasse in die Protokollausgabe geschrieben werden sollen (Datei oder stderr).

2
ratchet freak

Genau dafür sind Protokollierungsframeworks wie log4j oder das neuere slf4j gedacht. Mit ihnen können Sie die Protokollierung detailliert steuern und auch während der Ausführung der Anwendung konfigurieren.

1

Ein Protokollierungsframework ist definitiv der richtige Weg. Sie müssen jedoch auch eine gute Testsuite haben. Eine gute Testabdeckung macht häufig die Notwendigkeit einer Debug-Ausgabe insgesamt überflüssig.

0
Dima