Registro em Java com NDC (Nested Diagnostic Context)
1. Visão geral
O NDC (Nested Diagnostic Context) é um mecanismo para ajudar a distinguir mensagens de log intercaladas de diferentes origens. O NDC faz isso fornecendo a capacidade de adicionar informações contextuais distintas a cada entrada de log.
Neste artigo, exploraremos o uso do NDC e seu uso / suporte em várias estruturas de log Java.
2. Contextos de Diagnóstico
Em um aplicativo multiencadeado típico, como um aplicativo Web ou APIs REST, cada solicitação do cliente é atendida por um encadeamento diferente. Os logs gerados a partir de tal aplicativo serão uma mistura de todas as solicitações e fontes do cliente. Isso dificulta o entendimento comercial dos logs ou a depuração.
O NDC (Nested Diagnostic Context) gerencia uma pilha de informações contextuais, por thread. Os dados no NDC estão disponíveis para todas as solicitações de log no código e podem ser configurados para registrar com todas as mensagens de log - mesmo em locais onde os dados não estão no escopo. Essas informações contextuais em cada mensagem de log ajudam a distinguir os logs por sua origem e contexto.
OMapped Diagnostic Context (MDC) também gerencia informações por thread, mas como um mapa.
3. A pilha NDC em um aplicativo de amostra
Para demonstrar o uso de uma pilha NDC, vamos dar um exemplo de uma API REST que envia dinheiro para uma conta de investimento.
As informações exigidas como entrada são representadas em uma 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
}
A transferência para a conta de investimento é realizada usandoInvestmentService. O código-fonte completo dessas classes pode ser encontrado emthis github project.
No aplicativo de amostra, os dadostransactionIdeowner são colocados na pilha NDC, no encadeamento que está processando uma determinada solicitação. Esses dados estão disponíveis em todas as mensagens de log desse encadeamento. Dessa forma, cada transação exclusiva pode ser rastreada e o contexto relevante de cada mensagem de log pode ser identificado.
4. NDC em Log4j
Log4j fornece uma classe chamadaNDC que fornece métodos estáticos para gerenciar dados na pilha NDC. Uso básico:
-
Ao entrar em um contexto, useNDC.push() para adicionar dados de contexto no segmento atual
-
Ao sair do contexto, useNDC.pop() para retirar os dados do contexto
-
Ao sair do encadeamento, chameNDC.remove() para remover o contexto de diagnóstico do encadeamento e garantir que a memória seja liberada (a partir do Log4j 1.3, não é mais necessário)
No aplicativo de amostra, vamos usar o NDC para adicionar / remover dados contextuais em locais relevantes no código:
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);
}
}
O conteúdo do NDC pode ser exibido em mensagens de log usando a opção%x noConversionPattern usado pelo appender emlog4j.properties:
log4j.appender.consoleAppender.layout.ConversionPattern
= %-4r [%t] %5p %c{1} - %m - [%x]%n
Vamos implantar a API REST no tomcat. Pedido de amostra:
POST /logging-service/ndc/log4j
{
"transactionId": "4",
"owner": "Marc",
"amount": 2000
}
Podemos ver as informações do contexto de diagnóstico na saída do 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 em Log4j 2
O NDC no Log4j 2 é chamado como pilha de contexto de encadeamento:
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);
}
}
Assim como com Log4j, vamos usar a opção%x no arquivo de configuração Log4j 2log4j2.xml:
Saída de log:
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 em Fachadas de Log (JBoss Logging)
Fachadas de log como o SLF4J fornecem integração com várias estruturas de log. O NDC não é suportado no SLF4J (mas incluído no módulo slf4j-ext). O JBoss Logging é uma ponte de registro, assim como o SLF4J. O NDC é suportado no JBoss Logging.
Por padrão, o JBoss Logging pesquisaráClassLoader quanto à disponibilidade de back-ends / provedores na seguinte ordem de precedência: JBoss LogManager, Log4j 2, Log4j, SLF4J e JDK Logging.
O JBoss LogManager como provedor de registro é normalmente usado no servidor de aplicativos WildFly. No nosso caso, a ponte de registro do JBoss escolherá o próximo em ordem de precedência (que é Log4j 2) como o provedor de registro.
Vamos começar adicionando a dependência necessária empom.xml:
org.jboss.logging
jboss-logging
3.3.0.Final
A versão mais recente da dependência pode ser verificadahere.
Vamos adicionar informações contextuais à pilha 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);
}
}
Saída de log:
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. Conclusão
Vimos como o contexto de diagnóstico ajuda a correlacionar logs de maneira significativa - do ponto de vista comercial e para fins de depuração. É uma técnica inestimável para enriquecer o registro, especialmente em aplicativos multithread.
O exemplo usado neste artigo pode ser encontrado emthe Github project.