Quelle est la bonne façon de gérer la sortie de débogage en Java?


32

Comme mes projets Java actuels deviennent de plus en plus gros, je ressens un besoin croissant d'insérer une sortie de débogage dans plusieurs points de mon code.

Pour activer ou désactiver cette fonctionnalité de manière appropriée, en fonction de l'ouverture ou de la fermeture des sessions de test, je place généralement un private static final boolean DEBUG = falseau début des classes que mes tests inspectent, et je l'utilise de cette manière (par exemple):

public MyClass {
  private static final boolean DEBUG = false;

  ... some code ...

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

etc.

Mais cela ne me dérange pas, car cela fonctionne bien sûr, mais il pourrait y avoir trop de classes pour définir DEBUG sur true, si vous n'en fixez pas quelques-unes.

Inversement, je (comme - je pense - beaucoup d'autres) n'aimerais pas mettre toute l'application en mode débogage, car la quantité de texte produite pourrait être écrasante.

Alors, existe-t-il une manière correcte de gérer cette situation sur le plan architectural ou la manière la plus correcte est d'utiliser le membre de classe DEBUG?


14
en Java, la bonne façon n'est PAS d'utiliser le code homebrew pour la journalisation. Choisissez un cadre établi, ne réinventez pas la roue
moucher

J'utilise un DEBUG booléen dans certaines de mes classes les plus compliquées, pour la même raison que vous l'avez dit. Je ne veux généralement pas déboguer l'application entière, juste la classe qui me pose des problèmes. L'habitude est venue de mes jours COBOL, où les instructions DISPLAY étaient la seule forme de débogage disponible.
Gilbert Le Blanc

1
Je recommanderais également de s'appuyer davantage sur un débogueur lorsque cela est possible et de ne pas salir votre code avec des instructions de débogage.
Andrew T Finnell

1
Pratiquez-vous le développement piloté par les tests (TDD) avec des tests unitaires? Une fois que j'ai commencé à le faire, j'ai remarqué une réduction massive du «code de débogage».
JW01

Réponses:


52

Vous voulez regarder un cadre de journalisation, et peut-être un cadre de façade de journalisation.

Il existe plusieurs cadres de journalisation, souvent avec des fonctionnalités qui se chevauchent, à tel point qu'au fil du temps, beaucoup ont évolué pour s'appuyer sur une API commune, ou ont fini par être utilisés à travers un cadre de façade pour abstraire leur utilisation et leur permettre d'être échangés sur place si besoin.

Cadres

Quelques cadres de journalisation

Quelques façades forestières

Usage

Exemple de base

La plupart de ces frameworks vous permettraient d'écrire quelque chose du formulaire (ici en utilisant slf4j-apiet 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");
  }
}

Notez l'utilisation d'une classe actuelle pour créer un enregistreur dédié, ce qui permettrait à SLF4J / LogBack de formater la sortie et d'indiquer la provenance du message de journalisation.

Comme indiqué dans le manuel SLF4J , un modèle d'utilisation typique dans une classe est généralement:

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!");
        }
    }
}

Mais en fait, il est encore plus courant de déclarer l'enregistreur avec le formulaire:

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

Cela permet également à l'enregistreur d'être utilisé à partir de méthodes statiques, et il est partagé entre toutes les instances de la classe. Il s'agit très probablement de votre forme préférée. Cependant, comme l'a noté Brendan Long dans les commentaires, vous voulez être sûr de comprendre les implications et de décider en conséquence (cela s'applique à tous les cadres de journalisation suivant ces idiomes).

Il existe d'autres façons d'instancier des enregistreurs, par exemple en utilisant un paramètre de chaîne pour créer un enregistreur nommé:

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

Niveaux de débogage

Les niveaux de débogage varient d'un framework à l'autre, mais les plus courants sont (par ordre de criticité, de bénin à mauvais, et de probablement très commun à, espérons-le, très rare):

  • TRACE Information très détaillée. Doit être écrit dans les journaux uniquement. Utilisé uniquement pour suivre le flux du programme aux points de contrôle.

  • DEBUG Des informations détaillées. Doit être écrit dans les journaux uniquement.

  • INFO Événements d'exécution notables. Doit être immédiatement visible sur une console, donc utilisez-le avec parcimonie.

  • WARNING Curiosités d'exécution et erreurs récupérables.

  • ERROR Autres erreurs d'exécution ou conditions inattendues.

  • FATAL Erreurs graves entraînant une résiliation prématurée.

Blocs et gardes

Supposons maintenant que vous ayez une section de code où vous êtes sur le point d'écrire un certain nombre d'instructions de débogage. Cela pourrait rapidement affecter vos performances, à la fois en raison de l'impact de la journalisation elle-même et de la génération de tous les paramètres que vous pourriez transmettre à la méthode de journalisation.

Pour éviter ce genre de problème, vous voulez souvent écrire quelque chose de la forme:

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

Si vous n'aviez pas utilisé ce garde avant votre bloc d'instructions de débogage, même si les messages peuvent ne pas être émis (si, par exemple, votre enregistreur est actuellement configuré pour imprimer uniquement les choses au-dessus du INFOniveau), la heavyComputation()méthode aurait quand même été exécutée .

Configuration

La configuration dépend assez de votre infrastructure de journalisation, mais ils offrent principalement les mêmes techniques pour cela:

  • configuration programmatique (au moment de l'exécution, via une API - permet des changements d'exécution ),
  • configuration déclarative statique (au démarrage, généralement via un fichier XML ou de propriétés - probablement ce dont vous avez besoin au début ).

Ils offrent également principalement les mêmes capacités:

  • configuration du format du message de sortie (horodatages, marqueurs, etc ...),
  • configuration des niveaux de sortie,
  • configuration de filtres fins (par exemple pour inclure / exclure des packages ou des classes),
  • configuration des annexes pour déterminer où se connecter (à la console, au fichier, à un service Web ...) et éventuellement ce qu'il faut faire avec les anciens journaux (par exemple, avec les fichiers de roulement automatique).

Voici un exemple courant de configuration déclarative, à l'aide d'un logback.xmlfichier.

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

Comme mentionné, cela dépend de votre framework et il peut y avoir d'autres alternatives (par exemple, LogBack permet également d'utiliser un script Groovy). Le format de configuration XML peut également varier d'une implémentation à l'autre.

Pour plus d'exemples de configuration, veuillez vous référer (entre autres) à:

Du plaisir historique

S'il vous plaît noter que Log4J est de voir une mise à jour majeure pour le moment, la transition de la version 1.x à 2.x . Vous voudrez peut-être jeter un œil à la fois pour plus de plaisir historique ou de confusion, et si vous choisissez Log4J, vous préférerez probablement la version 2.x.

Il convient de noter, comme Mike Partridge l'a mentionné dans les commentaires, que LogBack a été créé par un ancien membre de l'équipe Log4J. Qui a été créé pour combler les lacunes du cadre de journalisation Java. Et que la prochaine version majeure de Log4J 2.x intègre elle-même maintenant quelques fonctionnalités tirées de LogBack.

Recommandation

En bout de ligne, restez découplé autant que vous le pouvez, jouez avec quelques-uns et voyez ce qui vous convient le mieux. En fin de compte, ce n'est qu'un cadre de journalisation . Sauf si vous avez une raison très spécifique, en dehors de la facilité d'utilisation et des préférences personnelles, l'une d'entre elles serait plutôt OK, il n'y a donc pas de raison de s'y accrocher. La plupart d'entre eux peuvent également être étendus à vos besoins.

Pourtant, si je devais choisir une combinaison aujourd'hui, j'irais avec LogBack + SLF4J. Mais si vous me l'aviez demandé quelques années plus tard, j'aurais recommandé Log4J avec Apache Commons Logging, alors gardez un œil sur vos dépendances et évoluez avec elles.


1
SLF4J et LogBack ont ​​été écrits par le gars qui a écrit Log4J à l'origine.
Mike Partridge

4
Pour ceux qui pourraient s'inquiéter des impacts de la journalisation sur les performances: slf4j.org/faq.html#logging_performance
Mike Partridge

2
Il convient de mentionner qu'il n'est pas si clair si vous devez créer vos enregistreurs static, car cela économise une petite quantité de mémoire, mais provoque des problèmes dans certains cas: slf4j.org/faq.html#declared_static
Monica

1
@MikePartridge: Je connais le contenu du lien, mais cela n'empêchera toujours pas l'évaluation des paramètres par exemple. la raison pour laquelle la journalisation paramétrée est plus performante est que le traitement du message de journal ne se produira pas (chaîne notamment concaténation). Cependant, tout appel de méthode sera exécuté s'il est passé en tant que paramètre. Ainsi, selon votre cas d'utilisation, les blocs peuvent être utiles. Et comme mentionné dans le billet, ils peuvent également être utiles pour vous simplement pour regrouper d'autres activités liées au débogage (pas seulement la journalisation) pour se produire lorsque le niveau DEBUG est activé.
haylem

1
@haylem - c'est vrai, mon erreur.
Mike Partridge

2

utiliser un cadre de journalisation

la plupart du temps, il y a une méthode d'usine statique

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

alors vous pouvez sortir votre code de journalisation avec différents niveaux:

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

alors il y aura un seul fichier où vous pouvez définir quels messages pour chaque classe doivent être écrits dans la sortie du journal (fichier ou stderr)


1

C'est exactement à cela que servent les frameworks de journalisation comme log4j ou slf4j plus récent. Ils vous permettent de contrôler la journalisation dans les moindres détails et de la configurer même lorsque l'application est en cours d'exécution.


0

Un cadre de journalisation est certainement la voie à suivre. Cependant, vous devez également disposer d'une bonne suite de tests. Une bonne couverture des tests peut souvent éliminer la nécessité d'une sortie de débogage tous ensemble.


Si vous utilisez un cadre de journalisation et que la journalisation de débogage est disponible - il arrivera un moment où cela vous évitera d'avoir une très mauvaise journée.
Fortyrunner

1
Je n'ai pas dit que vous ne devriez pas avoir de journalisation. J'ai dit que vous devez d'abord passer des tests.
Dima
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.