Qual è il modo corretto di gestire l'output di debug in Java?


32

Man mano che i miei attuali progetti Java diventano sempre più grandi, sento anche la necessità crescente di inserire l'output di debug in diversi punti del mio codice.

Per abilitare o disabilitare questa funzione in modo appropriato, a seconda dell'apertura o della chiusura delle sessioni di test, di solito inserisco private static final boolean DEBUG = falseall'inizio delle lezioni che i miei test stanno esaminando e la utilizzo banalmente in questo modo (ad esempio):

public MyClass {
  private static final boolean DEBUG = false;

  ... some code ...

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

e simili.

Ma questo non mi fa impazzire, perché ovviamente funziona ma potrebbero esserci troppe classi in cui impostare DEBUG su true, se non li stai fissando solo in un paio.

Al contrario, io (come - penso - molti altri) non mi piacerebbe mettere l'intera applicazione in modalità debug, poiché la quantità di testo in uscita potrebbe essere schiacciante.

Quindi, esiste un modo corretto per gestire architettonicamente tale situazione o il modo più corretto è utilizzare il membro della classe DEBUG?


14
in Java, il modo corretto NON è usare il codice homebrew per la registrazione. Scegli un quadro consolidato, non reinventare la ruota
moscerino del

Uso un DEBUG booleano in alcune delle mie classi più complicate, per lo stesso motivo che hai affermato. Di solito non voglio eseguire il debug dell'intera applicazione, ma solo la classe mi dà problemi. L'abitudine è venuta dai miei giorni COBOL, in cui le istruzioni DISPLAY erano l'unica forma di debug disponibile.
Gilbert Le Blanc,

1
Vorrei anche raccomandare di fare più affidamento su un debugger quando possibile e di non sporcare il codice con le dichiarazioni di debug.
Andrew T Finnell,

1
Pratichi Test Driven Development (TDD) con unit test? Una volta che ho iniziato a farlo, ho notato una forte riduzione del "codice di debug".
JW01

Risposte:


52

Si desidera esaminare un framework di registrazione e forse un framework di facciata di registrazione.

Esistono più framework di registrazione là fuori, spesso con funzionalità sovrapposte, tanto che nel tempo molti si sono evoluti per fare affidamento su un'API comune o sono stati utilizzati attraverso un framework di facciate per astrarre il loro uso e consentire loro di essere scambiati sul posto se necessario.

Frameworks

Alcuni frame di registrazione

Alcune facciate di registrazione

uso

Esempio di base

La maggior parte di questi framework ti consentirebbe di scrivere qualcosa del modulo (qui usando slf4j-apie 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");
  }
}

Si noti l'uso di una classe corrente per creare un logger dedicato, che consentirebbe a SLF4J / LogBack di formattare l'output e indicare da dove proviene il messaggio di registrazione.

Come notato nel manuale SLF4J , un tipico modello di utilizzo in una classe è di solito:

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

In realtà, è ancora più comune dichiarare il logger con il modulo:

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

Ciò consente al logger di essere utilizzato anche all'interno di metodi statici ed è condiviso tra tutte le istanze della classe. È molto probabile che questa sia la tua forma preferita. Tuttavia, come osservato da Brendan Long nei commenti, si desidera essere sicuri di comprendere le implicazioni e decidere di conseguenza (questo vale per tutti i framework di registrazione che seguono questi idiomi).

Esistono altri modi per creare un'istanza dei logger, ad esempio utilizzando un parametro stringa per creare un logger denominato:

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

Livelli di debug

I livelli di debug variano da una struttura all'altra, ma quelli comuni sono (in ordine di criticità, da benigni a quelli di merda di pipistrello e da probabilmente molto comuni a speriamo molto rari):

  • TRACE Informazioni molto dettagliate Dovrebbe essere scritto solo nei registri. Utilizzato solo per tenere traccia del flusso del programma ai punti di controllo.

  • DEBUG Informazioni dettagliate. Dovrebbe essere scritto solo nei registri.

  • INFO Notevoli eventi di runtime. Dovrebbe essere immediatamente visibile su una console, quindi usa con parsimonia.

  • WARNING Stranezze di runtime ed errori recuperabili.

  • ERROR Altri errori di runtime o condizioni impreviste.

  • FATAL Gravi errori che causano la risoluzione anticipata.

Blocchi e protezioni

Ora, supponiamo che tu abbia una sezione di codice in cui stai per scrivere una serie di istruzioni di debug. Ciò potrebbe influire rapidamente sulle prestazioni, sia a causa dell'impatto della registrazione stessa sia della generazione di qualsiasi parametro che si potrebbe passare al metodo di registrazione.

Per evitare questo tipo di problema, spesso vuoi scrivere qualcosa del modulo:

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

Se non avessi usato questa protezione prima del blocco delle istruzioni di debug, anche se i messaggi potrebbero non essere emessi (se, ad esempio, il tuo logger è attualmente configurato per stampare solo cose al di sopra del INFOlivello), il heavyComputation()metodo sarebbe comunque stato eseguito .

Configurazione

La configurazione dipende abbastanza dal tuo framework di registrazione, ma offrono principalmente le stesse tecniche per questo:

  • configurazione programmatica (in fase di runtime, tramite un'API - consente modifiche di runtime ),
  • configurazione dichiarativa statica (all'avvio, di solito tramite un file XML o proprietà - probabilmente sarà quello che ti serve all'inizio ).

Offrono anche per lo più le stesse funzionalità:

  • configurazione del formato del messaggio di output (timestamp, marker, ecc ...),
  • configurazione dei livelli di uscita,
  • configurazione di filtri a grana fine (ad esempio per includere / escludere pacchetti o classi),
  • configurazione di appendici per determinare dove accedere (alla console, al file, a un servizio Web ...) e possibilmente cosa fare con i registri più vecchi (ad esempio, con i file con rotazione automatica).

Ecco un esempio comune di una configurazione dichiarativa, usando un logback.xmlfile.

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

Come accennato, questo dipende dal tuo framework e potrebbero esserci altre alternative (ad esempio, LogBack consente anche di utilizzare uno script Groovy). Il formato di configurazione XML può anche variare da un'implementazione all'altra.

Per ulteriori esempi di configurazione, fare riferimento (tra gli altri) a:

Un po 'di divertimento storico

Si prega di notare che Log4J sta vedendo un importante aggiornamento per il momento, la transizione dalla versione 1.x alla 2.x . Potresti dare un'occhiata a entrambi per divertimento o confusione più storici, e se scegli Log4J probabilmente preferisci andare con la versione 2.x.

Vale la pena notare, come Mike Partridge ha menzionato nei commenti, che LogBack è stato creato da un ex membro del team Log4J. Che è stato creato per affrontare le carenze del framework di registrazione Java. E che l'imminente versione principale di Log4J 2.x si sta integrando ora con alcune funzionalità tratte da LogBack.

Raccomandazione

In conclusione, rimanete disaccoppiati il ​​più possibile, giocatene con alcuni e vedete cosa funziona meglio per voi. Alla fine è solo un framework di registrazione . Tranne se hai una ragione molto specifica, a parte la facilità d'uso e le preferenze personali, ognuna di queste cose farebbe piuttosto bene, quindi non ha senso essere sospesi. La maggior parte di essi può anche essere estesa alle tue esigenze.

Tuttavia, se dovessi scegliere una combinazione oggi, sceglierei LogBack + SLF4J. Ma se mi avessi chiesto qualche anno dopo, avrei raccomandato Log4J con Apache Commons Logging, quindi tieni d'occhio le tue dipendenze ed evolvi con esse.


1
SLF4J e LogBack sono stati scritti dal ragazzo che originariamente ha scritto Log4J.
Mike Partridge,

4
Per coloro che potrebbero essere preoccupati per l'impatto delle prestazioni della registrazione: slf4j.org/faq.html#logging_performance
Mike Partridge,

2
Vale la pena ricordare che non è così chiaro se dovresti creare i tuoi logger static, dal momento che salva una piccola quantità di memoria, ma causa problemi in alcuni casi: slf4j.org/faq.html#declared_static
Ripristina Monica il

1
@MikePartridge: sono a conoscenza del contenuto del collegamento, ma ad esempio non impedirà la valutazione dei parametri. il motivo per cui la registrazione con parametri è più performante è perché non si verificherà l'elaborazione del messaggio di registro (in particolare la concatenazione della stringa). Tuttavia, qualsiasi chiamata di metodo verrà eseguita se viene passata come parametro. Quindi, a seconda del caso d'uso, i blocchi possono essere utili. E come menzionato nel post, possono anche essere utili solo per raggruppare altre attività correlate al debug (non solo la registrazione) che si verificano quando il livello DEBUG è abilitato.
haylem,

1
@haylem - è vero, errore mio.
Mike Partridge,

2

utilizzare un framework di registrazione

il più delle volte esiste un metodo statico di fabbrica

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

quindi puoi generare il tuo codice di registrazione con diversi livelli:

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

quindi ci sarà un singolo file in cui è possibile definire quali messaggi per ogni classe devono essere scritti nell'output del registro (file o stderr)


1

Questo è esattamente ciò per cui sono intesi i framework di registrazione come log4j o il più recente slf4j. Consentono di controllare la registrazione in modo dettagliato e di configurarla anche mentre l'applicazione è in esecuzione.


0

Un framework di registrazione è sicuramente la strada da percorrere. Tuttavia, devi anche avere una buona suite di test. Una buona copertura dei test può spesso eliminare la necessità di un output di debug tutti insieme.


Se si utilizza un framework di registrazione e si dispone della registrazione di debug, verrà un momento in cui questo ti salverà da una brutta giornata.
Fortyrunner,

1
Non ho detto che non dovresti avere la registrazione. Ho detto che devi prima fare dei test.
Dima,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.