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

32

Da meine aktuellen Java-Projekte immer größer werden, muss ich immer häufiger Debug-Ausgaben in mehrere Punkte meines Codes einfügen.

Um diese Funktion abhängig vom Öffnen oder Schließen der Testsitzungen entsprechend zu aktivieren oder zu deaktivieren, setze ich in der Regel private static final boolean DEBUG = falsezu Beginn der von meinen Tests überprüften Klassen ein und verwende es trivial wie folgt:

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 freut mich nicht, 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 von ihnen anstarren.

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

Gibt es also eine richtige Methode, um mit einer solchen Situation architektonisch umzugehen, oder die richtige Methode, um das DEBUG-Klassenmitglied zu verwenden?

Federico Zancan
quelle
14
In Java ist es NICHT die richtige Methode, Homebrew-Code für die Protokollierung zu verwenden. Wählen Sie einen festgelegten Rahmen, nicht neu erfinden das Rad nicht
gnat
Ich verwende einen booleschen DEBUG in einigen meiner komplizierteren Klassen, aus demselben Grund, den Sie angegeben haben. Normalerweise möchte ich nicht die gesamte Anwendung debuggen, sondern nur die Klasse, die mir Probleme bereitet. Die Angewohnheit kam aus meinen COBOL-Tagen, als DISPLAY-Anweisungen die einzige verfügbare Form des Debuggens waren.
Gilbert Le Blanc
1
Ich würde auch empfehlen, sich nach Möglichkeit mehr auf einen Debugger zu verlassen und Ihren Code nicht mit Debug-Anweisungen zu verunreinigen.
Andrew T Finnell
1
Üben Sie Test Driven Development (TDD) mit Unit-Tests? Als ich damit anfing, bemerkte ich eine massive Reduzierung des 'Debug-Codes'.
JW01

Antworten:

52

Sie möchten sich ein Protokollierungsframework und möglicherweise ein Protokollierungsframework für Fassaden ansehen.

Es gibt mehrere Protokollierungsframeworks, die häufig überlappende Funktionalitäten aufweisen, sodass sich im Laufe der Zeit viele davon auf eine gemeinsame API stützen oder über ein Fassadenframework verwendet werden, um ihre Verwendung zu abstrahieren und zu ermöglichen, dass sie an Ort und Stelle ausgetauscht werden wenn benötigt.

Frameworks

Einige Protokollierungsframeworks

Einige Logging-Fassaden

Verwendung

Grundlegendes Beispiel

Mit den meisten dieser Frameworks können Sie etwas aus dem Formular schreiben (hier mit slf4j-apiund 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 der aktuellen Klasse, um einen dedizierten Protokollierer zu erstellen, mit dem SLF4J / LogBack die Ausgabe formatieren und angeben kann, woher die Protokollierungsnachricht stammt.

Wie im SLF4J-Handbuch erwähnt , 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 in statischen Methoden verwendet werden und wird von allen Instanzen der Klasse gemeinsam genutzt. Dies ist höchstwahrscheinlich Ihre bevorzugte Form. Wie Brendan Long in seinen Kommentaren feststellte, sollten Sie die Auswirkungen verstehen und entsprechend entscheiden (dies gilt für alle Protokollierungsframeworks, die diesen Redewendungen folgen).

Es gibt andere Möglichkeiten, Logger zu instanziieren, indem Sie beispielsweise einen String-Parameter verwenden, um einen benannten Logger zu erstellen:

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

Debug-Ebenen

Die Debug-Level variieren von einem Framework zum anderen, aber die häufigsten sind (in der Reihenfolge der Kritikalität, von harmlos 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 Genaue Information. Sollte nur in Protokolle geschrieben werden.

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

  • WARNING Laufzeitunregelmäßigkeiten und behebbare Fehler.

  • ERROR Andere Laufzeitfehler oder unerwartete Bedingungen.

  • FATAL Schwerwiegende 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 werden. 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 solche Probleme zu vermeiden, möchten Sie häufig etwas in 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 hätten, obwohl die Meldungen möglicherweise nicht ausgegeben werden (wenn Ihr Logger beispielsweise derzeit so konfiguriert ist, dass nur Dinge über dem INFOLevel gedruckt werden), wäre die heavyComputation()Methode dennoch ausgeführt worden .

Aufbau

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

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

Sie bieten auch meist die gleichen Funktionen:

  • Konfiguration des Formats der Ausgangsnachricht (Zeitstempel, Markierungen usw.),
  • Konfiguration der Ausgangspegel,
  • Konfiguration von feinkörnigen Filtern (zum Beispiel um Pakete oder Klassen einzuschließen / auszuschließen),
  • Konfiguration von Appendern, um zu bestimmen, wo protokolliert werden soll (zur Konsole, zur Datei, zu einem Web-Service ...) und möglicherweise was mit älteren Protokollen zu tun ist (zum Beispiel mit automatisch rollenden Dateien).

Im Folgenden finden Sie ein allgemeines Beispiel für eine deklarative Konfiguration mithilfe einer logback.xmlDatei.

<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 gibt möglicherweise andere Alternativen (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:

Ein bisschen historischer Spaß

Bitte beachten Sie, dass Log4J im Moment ein großes Update sieht, das von Version 1.x auf 2.x übergeht . Vielleicht möchten Sie sich beide ansehen, um mehr historischen Spaß zu haben oder Verwirrung zu stiften , und wenn Sie sich für Log4J entscheiden, bevorzugen Sie wahrscheinlich die 2.x-Version.

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

Empfehlung

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

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

Haylem
quelle
1
SLF4J und LogBack wurden von dem Typ geschrieben, der ursprünglich Log4J geschrieben hat.
Mike Partridge
4
Für diejenigen, die sich über die Auswirkungen der Protokollierung auf die Leistung Sorgen machen könnten: slf4j.org/faq.html#logging_performance
Mike Partridge
2
Es ist erwähnenswert, dass es nicht so eindeutig ist, ob Sie Ihre Logger erstellen sollten static, da dies wenig Speicherplatz spart, aber in bestimmten Fällen Probleme verursacht: slf4j.org/faq.html#declared_static
Monica
1
@MikePartridge: Mir ist der Inhalt des Links bekannt, aber er verhindert beispielsweise die Parameterauswertung nicht. Der Grund dafür, dass die parametrisierte Protokollierung von byt leistungsfähiger ist, besteht darin, dass die Verarbeitung der Protokollnachricht nicht erfolgt (Zeichenfolge, insbesondere Verkettung). Ein Methodenaufruf wird jedoch ausgeführt, wenn er als Parameter übergeben wird. Je nach Anwendungsfall können also Blöcke nützlich sein. Und wie im Beitrag erwähnt, können sie auch nützlich sein, um andere Debug-bezogene Aktivitäten (nicht nur die Protokollierung) zu gruppieren, die auftreten, wenn die DEBUG-Ebene aktiviert ist.
Haylem
1
@ Haylem - das ist wahr, mein Fehler.
Mike Partridge
2

Verwenden Sie ein Protokollierungsframework

Meistens gibt es eine statische Fabrikmethode

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 definieren können, welche Nachrichten für jede Klasse in die Protokollausgabe geschrieben werden sollen (Datei oder stderr).

Ratschenfreak
quelle
1

Genau dafür sind Logging-Frameworks wie log4j oder das neuere slf4j gedacht. Mit ihnen können Sie die Protokollierung sehr detailliert steuern und sogar konfigurieren, während die Anwendung ausgeführt wird.

Michael Borgwardt
quelle
0

Ein Protokollierungsframework ist definitiv der richtige Weg. Sie müssen jedoch auch eine gute Testsuite haben. Durch eine gute Testabdeckung kann häufig die Notwendigkeit einer Debug-Ausgabe insgesamt beseitigt werden.

Dima
quelle
Wenn Sie ein Protokollierungsframework verwenden und über Debugprotokollierung verfügen, wird es eine Zeit geben, in der Sie keinen wirklich schlechten Tag haben.
Fortyrunner
1
Ich habe nicht gesagt, dass Sie keine Protokollierung haben sollten. Ich sagte, Sie müssen zuerst Tests haben.
Dima