Journalisation Java avec contexte de diagnostic imbriqué (NDC)

1. Vue d'ensemble

Le contexte de diagnostic imbriqué (NDC) est un mécanisme permettant de distinguer les messages de journal entrelacés de différentes sources. NDC le fait en offrant la possibilité d'ajouter des informations contextuelles distinctives à chaque entrée de journal.

Dans cet article, nous explorerons l'utilisation de NDC et son utilisation / prise en charge dans divers frameworks de journalisation Java.

2. Contextes de diagnostic

Dans une application multi-thread typique telle qu'une application Web ou des API REST, chaque demande client est servie par un thread différent. Les journaux générés à partir d'une telle application seront un mélange de toutes les demandes et sources des clients. Cela rend difficile de donner un sens commercial aux journaux ou de déboguer.

Le contexte de diagnostic imbriqué (NDC) gère une pile d'informations contextuelles, par thread. Les données dans NDC sont disponibles pour chaque demande de journal dans le code et peuvent être configurées pour se connecter avec chaque message de journal, même aux endroits où les données ne sont pas dans la portée. Ces informations contextuelles dans chaque message de journal permettent de distinguer les journaux en fonction de leur source et de leur contexte.

Le contexte de diagnostic mappé (MDC) gère également les informations sur une base par thread, mais comme une carte.

3. La pile NDC dans un exemple d'application

Pour démontrer l'utilisation d'une pile NDC, prenons un exemple d'API REST qui envoie de l'argent à un compte d'investissement.

Les informations requises en entrée sont représentées dans une classe d' investissement :

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 }

Le transfert vers le compte d'investissement est effectué à l'aide d' InvestmentService . Le code source complet de ces classes se trouve dans ce projet github.

Dans l'exemple d'application, les données transactionId et owner sont placés dans la pile NDC, dans le thread qui traite une demande donnée. Ces données sont disponibles dans chaque message de journal de ce thread. De cette manière, chaque transaction unique peut être tracée et le contexte pertinent de chaque message de journal peut être identifié.

4. NDC dans Log4j

Log4j fournit une classe appelée NDC qui fournit des méthodes statiques pour gérer les données dans la pile NDC. Utilisation de base:

  • Lors de la saisie d'un contexte, utilisez NDC.push () pour ajouter des données de contexte dans le thread actuel
  • Lorsque vous quittez le contexte, utilisez NDC.pop () pour retirer les données de contexte
  • Lorsque vous quittez le thread, appelez NDC.remove () pour supprimer le contexte de diagnostic du thread et vous assurer que la mémoire est libérée (à partir de Log4j 1.3, plus nécessaire)

Dans l'exemple d'application, utilisons NDC pour ajouter / supprimer des données contextuelles aux endroits appropriés dans le code:

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

Le contenu de NDC peut être affiché dans les messages du journal en utilisant l' option % x dans le ConversionPattern utilisé par l'appender dans log4j.properties :

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

Déployons l'API REST sur tomcat. Demande d'échantillon:

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

Nous pouvons voir les informations de contexte de diagnostic dans la sortie du journal:

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 dans Log4j 2

NDC dans Log4j 2 est appelé comme pile de contexte de thread:

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

Tout comme avec Log4j, utilisons l' option % x dans le fichier de configuration Log4j 2 log4j2.xml :

Sortie du journal:

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 dans les façades de journalisation (JBoss Logging)

Les façades de journalisation telles que SLF4J permettent une intégration avec divers cadres de journalisation. NDC n'est pas pris en charge dans SLF4J (mais inclus dans le module slf4j-ext). JBoss Logging est un pont de journalisation, tout comme SLF4J. NDC est pris en charge dans JBoss Logging.

Par défaut, JBoss Logging recherchera dans le ClassLoader la disponibilité des back-ends / fournisseurs dans l'ordre de priorité suivant: JBoss LogManager, Log4j 2, Log4j, SLF4J et JDK Logging.

JBoss LogManager en tant que fournisseur de journalisation est généralement utilisé dans le serveur d'applications WildFly. Dans notre cas, le pont de journalisation JBoss choisira le suivant par ordre de priorité (qui est Log4j 2) en tant que fournisseur de journalisation.

Commençons par ajouter la dépendance requise dans pom.xml :

 org.jboss.logging jboss-logging 3.3.0.Final 

La dernière version de la dépendance peut être vérifiée ici.

Ajoutons des informations contextuelles à la pile 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); } }

Sortie du journal:

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

Nous avons vu comment le contexte de diagnostic aide à corréler les journaux de manière significative - d'un point de vue commercial ainsi qu'à des fins de débogage. C'est une technique inestimable pour enrichir la journalisation, en particulier dans les applications multithreads.

L'exemple utilisé dans cet article se trouve dans le projet Github.