Journalisation Java avec contexte de diagnostic imbriqué (NDC)

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. Pour ce faire, NDC offre la possibilité d'ajouter des informations contextuelles distinctes à chaque entrée du journal.

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

2. Contextes de diagnostic

Dans une application multithread 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 du client. Cela rend difficile la compréhension des journaux ou le débogage.

Le contexte de diagnostic imbriqué (NDC) gère une pile d'informations contextuelles, par thread. Les données du 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.

LeMapped Diagnostic Context (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 illustrer 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 classeInvestment:

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 deInvestmentService. Le code source complet de ces classes se trouve dansthis github project.

Dans l'exemple d'application, les donnéestransactionId etowner sont placées 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 fil. 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éeNDC 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, utilisezNDC.push() pour ajouter des données de contexte dans le thread actuel

  • Lorsque vous quittez le contexte, utilisezNDC.pop() pour retirer les données de contexte

  • Lorsque vous quittez le thread, appelezNDC.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 lesConversionPattern utilisés par l'appender danslog4j.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é 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 2log4j2.xml:


    
        
            
        
    
    
        
            
            
        
    

Journal de sortie:

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 fournissent 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 dansClassLoader 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 choisit le suivant par ordre de priorité (Log4j 2) en tant que fournisseur de journalisation.

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


    org.jboss.logging
    jboss-logging
    3.3.0.Final

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

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

Journal de sortie:

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 permet de corréler les journaux de manière significative, tant du point de vue commercial que du 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 dansthe Github project.