In log4j, il controllo isDebugEnabled prima della registrazione migliora le prestazioni?


207

Sto usando Log4J nella mia applicazione per la registrazione. In precedenza stavo usando la chiamata di debug come:

Opzione 1:

logger.debug("some debug text");

ma alcuni link suggeriscono che è meglio controllare isDebugEnabled()prima, come:

Opzione 2:

boolean debugEnabled = logger.isDebugEnabled();
if (debugEnabled) {
    logger.debug("some debug text");
}

Quindi la mia domanda è " L'opzione 2 migliora le prestazioni in qualche modo? ".

Perché in ogni caso il framework Log4J ha lo stesso controllo per debugEnabled. Per l'opzione 2 potrebbe essere utile se stiamo usando più istruzioni di debug in un singolo metodo o classe, in cui il framework non ha bisogno di chiamare il isDebugEnabled()metodo più volte (su ogni chiamata); in questo caso chiama il isDebugEnabled()metodo una sola volta e se Log4J è configurato per il debug a livello, in realtà chiama il isDebugEnabled()metodo due volte:

  1. In caso di assegnazione di valore alla variabile debugEnabled, e
  2. In realtà chiamato dal metodo logger.debug ().

Non penso che se scriviamo più logger.debug()istruzioni in metodo o classe e chiamando il debug()metodo in base all'opzione 1, allora è un sovraccarico per il framework Log4J rispetto all'opzione 2. Poiché isDebugEnabled()è un metodo molto piccolo (in termini di codice), potrebbe essere un buon candidato per l'allineamento.

Risposte:


248

In questo caso particolare, l'opzione 1 è migliore.

L'istruzione guard (controllo isDebugEnabled()) serve a prevenire il calcolo potenzialmente costoso del messaggio di registro quando implica l'invocazione dei toString()metodi di vari oggetti e la concatenazione dei risultati.

Nell'esempio dato, il messaggio di registro è una stringa costante, quindi lasciare che il logger lo scarti è efficace quanto controllare se il logger è abilitato e riduce la complessità del codice perché ci sono meno rami.

Meglio ancora è utilizzare un framework di registrazione più aggiornato in cui le istruzioni di registro adottano una specifica di formato e un elenco di argomenti che devono essere sostituiti dal logger, ma "pigramente" solo se il logger è abilitato. Questo è l'approccio adottato da slf4j .

Vedi la mia risposta a una domanda correlata per ulteriori informazioni e un esempio di come fare qualcosa del genere con log4j.


3
log5j estende log4j più o meno allo stesso modo di slf4j
Bill Michell,

Questo è anche l'approccio di java.util.Logging.
Paul,

@Geek È più efficiente quando l'evento di registro è disabilitato perché il livello di registro è impostato su alto. Vedere la sezione intitolata "La necessità di registrazione condizionale" nella mia risposta qui.
Erickson,

1
Questo è cambiato in log4j 2?
SnakeDoc,

3
@SnakeDoc No. È fondamentale chiamare il metodo: le espressioni nelle liste arg del metodo vengono effettivamente valutate prima della chiamata. Se quelle espressioni sono a) ritenute costose eb) ricercate solo a determinate condizioni (ad esempio quando il debug è abilitato), l'unica scelta è quella di mettere un controllo delle condizioni intorno alla chiamata e il framework non può farlo per te. La cosa con i metodi di log basati sul formatter è che puoi passare alcuni Oggetti (che è essenzialmente gratuito), e il logger chiamerà toString()solo se richiesto.
SusanW,

31

Poiché nell'opzione 1 la stringa di messaggio è una costante, non c'è assolutamente alcun vantaggio nel racchiudere l'istruzione di registrazione con una condizione, al contrario, se l'istruzione di registro è abilitata per il debug, verrà valutata due volte, una volta nel isDebugEnabled()metodo e una volta in debug()metodo. Il costo di invocazione isDebugEnabled()è dell'ordine di 5-30 nanosecondi, che dovrebbe essere trascurabile per la maggior parte degli scopi pratici. Pertanto, l'opzione 2 non è auspicabile perché inquina il codice e non fornisce altri guadagni.


17

L'uso di isDebugEnabled()è riservato per la creazione di messaggi di registro concatenando Stringhe:

Var myVar = new MyVar();
log.debug("My var is " + myVar + ", value:" + myVar.someCall());

Tuttavia, nel tuo esempio non c'è guadagno di velocità in quanto stai semplicemente registrando una stringa e non stai eseguendo operazioni come la concatenazione. Pertanto, stai solo aggiungendo gonfiore al codice e rendendolo più difficile da leggere.

Personalmente uso le chiamate in formato Java 1.5 nella classe String in questo modo:

Var myVar = new MyVar();
log.debug(String.format("My var is '%s', value: '%s'", myVar, myVar.someCall()));

Dubito che ci sia molta ottimizzazione, ma è più facile da leggere.

Si noti tuttavia che la maggior parte delle API di registrazione offre una formattazione come questa predefinita: slf4j ad esempio fornisce quanto segue:

logger.debug("My var is {}", myVar);

che è ancora più facile da leggere.


8
L'utilizzo di String.format (...), rendendo più semplice la lettura della riga di registro, potrebbe infatti influire negativamente sulle prestazioni. Il modo SLF4J di fare ciò invia i parametri nel metodo logger.debug e lì viene eseguita la valutazione di isDebugEnabled prima che la stringa venga costruita. Nel modo in cui lo fai, con String.format (...), la stringa verrà costruita prima che venga eseguita la chiamata del metodo a logger.debug in modo da aver pagato la penalità della creazione della stringa anche se il livello di debug è non abilitato.
Ci

2
String.format è 40 volte più lento di concat e slf4j ha una limitazione di 2 parametri Vedi i numeri qui: stackoverflow.com/questions/925423/… Ho visto molti grafici di profiler in cui l'operazione di formattazione è sprecata in dichiarazioni di debug quando il sistema di produzione è in esecuzione a livello di registro di INFO o ERRORE
AztecWarrior_25


8

Versione breve: è possibile eseguire anche il controllo isDebugEnabled () booleano.

Motivi:
1- Se complicato logica / stringa concat. viene aggiunto alla tua dichiarazione di debug avrai già il check in atto.
2- Non è necessario includere selettivamente l'istruzione nelle istruzioni di debug "complesse". Tutte le dichiarazioni sono incluse in questo modo.
3- La chiamata di log.debug esegue quanto segue prima della registrazione:

if(repository.isDisabled(Level.DEBUG_INT))
return;

Questo è sostanzialmente lo stesso del registro delle chiamate. o gatto. isDebugEnabled ().

PERÒ! Questo è ciò che pensano gli sviluppatori di log4j (come è nel loro javadoc e probabilmente dovresti seguirlo.)

Questo è il metodo

public
  boolean isDebugEnabled() {
     if(repository.isDisabled( Level.DEBUG_INT))
      return false;
    return Level.DEBUG.isGreaterOrEqual(this.getEffectiveLevel());
  }

Questo è il javadoc per questo

/**
*  Check whether this category is enabled for the <code>DEBUG</code>
*  Level.
*
*  <p> This function is intended to lessen the computational cost of
*  disabled log debug statements.
*
*  <p> For some <code>cat</code> Category object, when you write,
*  <pre>
*      cat.debug("This is entry number: " + i );
*  </pre>
*
*  <p>You incur the cost constructing the message, concatenatiion in
*  this case, regardless of whether the message is logged or not.
*
*  <p>If you are worried about speed, then you should write
*  <pre>
*    if(cat.isDebugEnabled()) {
*      cat.debug("This is entry number: " + i );
*    }
*  </pre>
*
*  <p>This way you will not incur the cost of parameter
*  construction if debugging is disabled for <code>cat</code>. On
*  the other hand, if the <code>cat</code> is debug enabled, you
*  will incur the cost of evaluating whether the category is debug
*  enabled twice. Once in <code>isDebugEnabled</code> and once in
*  the <code>debug</code>.  This is an insignificant overhead
*  since evaluating a category takes about 1%% of the time it
*  takes to actually log.
*
*  @return boolean - <code>true</code> if this category is debug
*  enabled, <code>false</code> otherwise.
*   */

1
Grazie per aver incluso JavaDoc. Sapevo di aver già visto questo consiglio da qualche parte e che cercavo di trovare un riferimento definitivo. Questo è, se non definitivo, almeno molto ben informato.
Simon Peter Chappell,

7

Come altri hanno già detto, usare l'istruzione guard è davvero utile solo se la creazione della stringa è una chiamata che richiede tempo. Esempi specifici di questo sono quando la creazione della stringa attiverà un caricamento lento.

Vale la pena notare che questo problema può essere evitato utilizzando Simple Logging Facade per Java o (SLF4J) - http://www.slf4j.org/manual.html . Ciò consente chiamate di metodo come:

logger.debug("Temperature set to {}. Old temperature was {}.", t, oldT);

Questo convertirà i parametri passati in stringhe solo se il debug è abilitato. SLF4J come suggerisce il nome è solo una facciata e le chiamate di registrazione possono essere passate a log4j.

Puoi anche facilmente "creare la tua versione" di questo.

Spero che questo ti aiuti.


6

L'opzione 2 è migliore.

Di per sé non migliora le prestazioni. Ma assicura che le prestazioni non diminuiscano. Ecco come.

Normalmente ci aspettiamo logger.debug (someString);

Ma di solito, man mano che l'applicazione cresce, cambia molte mani, specialmente agli sviluppatori alle prime armi, si potrebbe vedere

logger.debug (str1 + str2 + str3 + str4);

e simili.

Anche se il livello di registro è impostato su ERRORE o FATALE, si verifica la concatenazione delle stringhe! Se l'applicazione contiene molti messaggi a livello di DEBUG con concatenazioni di stringhe, sicuramente avrà un impatto sulle prestazioni soprattutto con jdk 1.4 o versioni precedenti. (Non sono sicuro che le versioni successive di jdk internall eseguano stringbuffer.append ()).

Ecco perché l'opzione 2 è sicura. Anche le concatenazioni di stringhe non si verificano.


3

Come @erickson dipende. Se ricordo, isDebugEnabledè già integrato nel debug()metodo di Log4j.
Finché non stai eseguendo calcoli costosi nelle tue dichiarazioni di debug, come loop su oggetti, eseguire calcoli e concatenare stringhe, secondo me va bene.

StringBuilder buffer = new StringBuilder();
for(Object o : myHugeCollection){
  buffer.append(o.getName()).append(":");
  buffer.append(o.getResultFromExpensiveComputation()).append(",");
}
log.debug(buffer.toString());

sarebbe meglio come

if (log.isDebugEnabled(){
  StringBuilder buffer = new StringBuilder();
  for(Object o : myHugeCollection){
    buffer.append(o.getName()).append(":");
    buffer.append(o.getResultFromExpensiveComputation()).append(",");
  }
  log.debug(buffer.toString());
}

3

Per una singola riga , utilizzo un ternario all'interno del messaggio di log, in questo modo non eseguo la concatenazione:

ej:

logger.debug(str1 + str2 + str3 + str4);

Lo voglio:

logger.debug(logger.isDebugEnable()?str1 + str2 + str3 + str4:null);

Ma per più righe di codice

ej.

for(Message mess:list) {
    logger.debug("mess:" + mess.getText());
}

Lo voglio:

if(logger.isDebugEnable()) {
    for(Message mess:list) {
         logger.debug("mess:" + mess.getText());
    }
}

3

Poiché molte persone stanno probabilmente visualizzando questa risposta durante la ricerca di log4j2 e quasi tutte le risposte correnti non considerano log4j2 o le modifiche recenti in essa, si spera che questo risponda alla domanda.

log4j2 supporta i fornitori (attualmente la propria implementazione, ma secondo la documentazione è previsto l'utilizzo dell'interfaccia del fornitore Java nella versione 3.0). Puoi leggere un po 'di più su questo nel manuale . Ciò consente di inserire costose creazioni di messaggi di log in un fornitore che crea il messaggio solo se verrà registrato:

LogManager.getLogger().debug(() -> createExpensiveLogMessage());

2

Migliora la velocità perché è comune concatenare le stringhe nel testo di debug che è costoso, ad esempio:

boolean debugEnabled = logger.isDebugEnabled();
if (debugEnabled) {
    logger.debug("some debug text" + someState);
}

1
Se stiamo usando jdk 1.5 e successivi, penso che le stringhe concatenate non faranno alcuna differenza.
Silent Warrior,

Come mai? Cosa farebbe JDK5 in modo diverso?
javashlook,

1
In jdk 1.5 se concateniamo stringhe in una singola istruzione, utilizza internamente solo il metodo StringBuffer.append (). Quindi non influisce sulle prestazioni.
Silent Warrior,

2
La concatenazione di stringhe richiede senza dubbio tempo. Tuttavia non sono sicuro che lo descriverei come "costoso". Quanto tempo viene risparmiato nell'esempio sopra? Rispetto a ciò che sta realmente facendo il codice circostante? (ad es. letture del database o calcolo in memoria). Penso che questo tipo di dichiarazioni debbano essere qualificate
Brian Agnew,

1
Anche JDK 1.4 non creerà nuovi oggetti String con una concatenazione di stringhe semplice. La penalità prestazionale deriva dall'utilizzo di StringBuffer.append () quando nessuna stringa deve essere visualizzata affatto.
javashlook,

1

Dalla versione di Log4j2.4 (o slf4j-api 2.0.0-alpha1) è molto meglio usare l' API fluente (o il supporto lambda Java 8 per il logging pigro ), che supporta l' Supplier<?>argomento del messaggio di log, che può essere dato da lambda :

log.debug("Debug message with expensive data : {}", 
           () -> doExpensiveCalculation());

O con API slf4j:

log.atDebug()
            .addArgument(() -> doExpensiveCalculation())
            .log("Debug message with expensive data : {}");

0

Se usi l'opzione 2 stai facendo un controllo booleano che è veloce. Nell'opzione 1 stai facendo una chiamata di metodo (spingendo roba in pila) e poi facendo un controllo booleano che è ancora veloce. Il problema che vedo è la coerenza. Se alcune delle tue dichiarazioni di debug e informazioni sono racchiuse e altre no, non è uno stile di codice coerente. Inoltre, qualcuno in seguito potrebbe modificare l'istruzione debug per includere stringhe concatenate, che è ancora piuttosto veloce. Ho scoperto che quando abbiamo inserito la dichiarazione di debug e informazioni in una grande applicazione e l'abbiamo profilata, abbiamo risparmiato un paio di punti percentuali nelle prestazioni. Non molto, ma abbastanza per far valere il lavoro. Ora ho un paio di macro installate in IntelliJ per generare automaticamente istruzioni di debug e informazioni complete.


0

Consiglierei di utilizzare l'opzione 2 di fatto per la maggior parte in quanto non è super costoso.

Caso 1: log.debug ("una stringa")

Caso2: log.debug ("una stringa" + "due stringhe" + object.toString + object2.toString)

Quando viene chiamato uno di questi, deve essere valutata la stringa di parametri all'interno di log.debug (sia CASE 1 o Case2). Questo è ciò che tutti intendono per "costoso". Se prima hai una condizione, 'isDebugEnabled ()', questi non devono essere valutati dove si trova la prestazione.


0

A partire da 2.x, Apache Log4j ha questo controllo integrato, quindi isDebugEnabled()non è più necessario. Basta fare un debug()e i messaggi saranno soppressi se non abilitati.


-1

Log4j2 ti consente di formattare i parametri in un modello di messaggio, simile a String.format(), eliminando così la necessità di fare isDebugEnabled().

Logger log = LogManager.getFormatterLogger(getClass());
log.debug("Some message [myField=%s]", myField);

Esempio di log4j2.properties semplice:

filter.threshold.type = ThresholdFilter
filter.threshold.level = debug
appender.console.type = Console
appender.console.name = STDOUT
appender.console.layout.type = PatternLayout
appender.console.layout.pattern = %d %-5p: %c - %m%n
appender.console.filter.threshold.type = ThresholdFilter
appender.console.filter.threshold.level = debug
rootLogger.level = info
rootLogger.appenderRef.stdout.ref = STDOUT
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.