Registrazione Java con contesto diagnostico annidato (NDC)

1. Panoramica

Il contesto diagnostico annidato (NDC) è un meccanismo che consente di distinguere i messaggi di registro interlacciati da origini diverse. NDC fa ciò fornendo la possibilità di aggiungere informazioni contestuali distintive a ciascuna voce di registro.

In questo articolo, esploreremo l'uso di NDC e il suo utilizzo / supporto in vari framework di registrazione Java.

2. Contesti diagnostici

In una tipica applicazione multi-thread come un'applicazione Web o API REST, ogni richiesta del client viene servita da un thread diverso. I log generati da tale applicazione saranno un mix di tutte le richieste e le fonti del client. Ciò rende difficile dare un senso commerciale ai log o eseguire il debug.

Nested Diagnostic Context (NDC) gestisce uno stack di informazioni contestuali, in base al thread. I dati in NDC sono disponibili per ogni richiesta di registro nel codice e possono essere configurati per accedere a ogni messaggio di registro, anche in luoghi in cui i dati non rientrano nell'ambito. Queste informazioni contestuali in ogni messaggio di registro aiutano a distinguere i registri in base all'origine e al contesto.

Anche il Mapped Diagnostic Context (MDC) gestisce le informazioni in base al thread, ma come mappa.

3. Lo stack NDC in un'applicazione di esempio

Per dimostrare l'utilizzo di uno stack NDC, prendiamo un esempio di un'API REST che invia denaro a un conto di investimento.

Le informazioni richieste come input sono rappresentate in una classe di investimento :

public class Investment { private String transactionId; private String owner; private Long amount; public Investment (String transactionId, String owner, Long amount) { this.transactionId = transactionId; this.owner = owner; this.amount = amount; } // standard getters and setters }

Il trasferimento al conto di investimento viene eseguito utilizzando InvestmentService . Il codice sorgente completo per queste classi può essere trovato in questo progetto GitHub.

Nell'applicazione di esempio, i dati transactionId e owner vengono inseriti nello stack NDC, nel thread che sta elaborando una determinata richiesta. Questi dati sono disponibili in ogni messaggio di log in quel thread. In questo modo, ogni singola transazione può essere tracciata e il contesto rilevante di ogni messaggio di log può essere identificato.

4. NDC in Log4j

Log4j fornisce una classe chiamata NDC che fornisce metodi statici per gestire i dati nello stack NDC. Utilizzo di base:

  • Quando si entra in un contesto, utilizzare NDC.push () per aggiungere i dati di contesto nel thread corrente
  • Quando si esce dal contesto, utilizzare NDC.pop () per estrarre i dati di contesto
  • Quando si esce dal thread, chiamare NDC.remove () per rimuovere il contesto diagnostico per il thread e assicurarsi che la memoria venga liberata (a partire da Log4j 1.3, non è più necessario)

Nell'applicazione di esempio, usiamo NDC per aggiungere / rimuovere dati contestuali in punti rilevanti nel codice:

import org.apache.log4j.NDC; @RestController public class Log4JController { @Autowired @Qualifier("Log4JInvestmentService") private InvestmentService log4jBusinessService; @RequestMapping( value = "/ndc/log4j", method = RequestMethod.POST) public ResponseEntity postPayment( @RequestBody Investment investment) { NDC.push("tx.id=" + investment.getTransactionId()); NDC.push("tx.owner=" + investment.getOwner()); log4jBusinessService.transfer(investment.getAmount()); NDC.pop(); NDC.pop(); NDC.remove(); return new ResponseEntity(investment, HttpStatus.OK); } }

Il contenuto di NDC può essere visualizzato nei messaggi di registro utilizzando l' opzione % x in ConversionPattern utilizzato dall'appender in log4j.properties :

log4j.appender.consoleAppender.layout.ConversionPattern = %-4r [%t] %5p %c{1} - %m - [%x]%n

Distribuiamo l'API REST su tomcat. Esempio di richiesta:

POST /logging-service/ndc/log4j { "transactionId": "4", "owner": "Marc", "amount": 2000 }

Possiamo vedere le informazioni sul contesto diagnostico nell'output del log:

48569 [http-nio-8080-exec-3] INFO Log4JInvestmentService - Preparing to transfer 2000$. - [tx.id=4 tx.owner=Marc] 49231 [http-nio-8080-exec-4] INFO Log4JInvestmentService - Preparing to transfer 1500$. - [tx.id=6 tx.owner=Samantha] 49334 [http-nio-8080-exec-3] INFO Log4JInvestmentService - Has transfer of 2000$ completed successfully ? true. - [tx.id=4 tx.owner=Marc] 50023 [http-nio-8080-exec-4] INFO Log4JInvestmentService - Has transfer of 1500$ completed successfully ? true. - [tx.id=6 tx.owner=Samantha] ...

5. NDC in Log4j 2

NDC in Log4j 2 è chiamato come Thread Context Stack:

import org.apache.logging.log4j.ThreadContext; @RestController public class Log4J2Controller { @Autowired @Qualifier("Log4J2InvestmentService") private InvestmentService log4j2BusinessService; @RequestMapping( value = "/ndc/log4j2", method = RequestMethod.POST) public ResponseEntity postPayment( @RequestBody Investment investment) { ThreadContext.push("tx.id=" + investment.getTransactionId()); ThreadContext.push("tx.owner=" + investment.getOwner()); log4j2BusinessService.transfer(investment.getAmount()); ThreadContext.pop(); ThreadContext.pop(); ThreadContext.clearAll(); return new ResponseEntity(investment, HttpStatus.OK); } }

Proprio come con Log4j, usiamo l' opzione % x nel file di configurazione di Log4j 2 log4j2.xml :

Uscita registro:

204724 [http-nio-8080-exec-1] INFO Log4J2InvestmentService - Preparing to transfer 1500$. - [tx.id=6, tx.owner=Samantha] 205455 [http-nio-8080-exec-2] INFO Log4J2InvestmentService - Preparing to transfer 2000$. - [tx.id=4, tx.owner=Marc] 205525 [http-nio-8080-exec-1] INFO Log4J2InvestmentService - Has transfer of 1500$ completed successfully ? false. - [tx.id=6, tx.owner=Samantha] 206064 [http-nio-8080-exec-2] INFO Log4J2InvestmentService - Has transfer of 2000$ completed successfully ? true. - [tx.id=4, tx.owner=Marc] ...

6. NDC nelle facciate di registrazione (registrazione JBoss)

Le facciate di registrazione come SLF4J forniscono l'integrazione con vari framework di registrazione. NDC non è supportato in SLF4J (ma incluso nel modulo slf4j-ext). JBoss Logging è un ponte di registrazione, proprio come SLF4J. NDC è supportato in JBoss Logging.

Per impostazione predefinita, JBoss Logging cercherà in ClassLoader la disponibilità di back-end / provider nel seguente ordine di precedenza: JBoss LogManager, Log4j 2, Log4j, SLF4J e JDK Logging.

JBoss LogManager come provider di registrazione viene generalmente utilizzato nel server delle applicazioni WildFly. Nel nostro caso, il bridge di registrazione JBoss sceglierà il successivo in ordine di precedenza (che è Log4j 2) come provider di registrazione.

Cominciamo aggiungendo la dipendenza richiesta in pom.xml :

 org.jboss.logging jboss-logging 3.3.0.Final 

L'ultima versione della dipendenza può essere verificata qui.

Aggiungiamo informazioni contestuali allo stack NDC:

import org.jboss.logging.NDC; @RestController public class JBossLoggingController { @Autowired @Qualifier("JBossLoggingInvestmentService") private InvestmentService jbossLoggingBusinessService; @RequestMapping( value = "/ndc/jboss-logging", method = RequestMethod.POST) public ResponseEntity postPayment( @RequestBody Investment investment) { NDC.push("tx.id=" + investment.getTransactionId()); NDC.push("tx.owner=" + investment.getOwner()); jbossLoggingBusinessService.transfer(investment.getAmount()); NDC.pop(); NDC.pop(); NDC.clear(); return new ResponseEntity(investment, HttpStatus.OK); } }

Uscita registro:

17045 [http-nio-8080-exec-1] INFO JBossLoggingInvestmentService - Preparing to transfer 1,500$. - [tx.id=6, tx.owner=Samantha] 17725 [http-nio-8080-exec-1] INFO JBossLoggingInvestmentService - Has transfer of 1,500$ completed successfully ? true. - [tx.id=6, tx.owner=Samantha] 18257 [http-nio-8080-exec-2] INFO JBossLoggingInvestmentService - Preparing to transfer 2,000$. - [tx.id=4, tx.owner=Marc] 18904 [http-nio-8080-exec-2] INFO JBossLoggingInvestmentService - Has transfer of 2,000$ completed successfully ? true. - [tx.id=4, tx.owner=Marc] ...

7. Conclusione

Abbiamo visto come il contesto diagnostico aiuti a correlare i registri in modo significativo, dal punto di vista aziendale e per scopi di debug. È una tecnica inestimabile per arricchire la registrazione, specialmente nelle applicazioni multi-thread.

L'esempio utilizzato in questo articolo può essere trovato nel progetto Github.