diff --git a/log-mdc/pom.xml b/log-mdc/pom.xml index 4cabce502e..28c8bb820e 100644 --- a/log-mdc/pom.xml +++ b/log-mdc/pom.xml @@ -5,20 +5,37 @@ logmdc 0.0.1-SNAPSHOT logmdc - tutorial on logging with MDC + war + tutorial on logging with MDC and NDC - - org.springframework - spring-context - ${springframework.version} - - - org.springframework - spring-webmvc - ${springframework.version} - + + org.springframework + spring-core + ${springframework.version} + + + org.springframework + spring-web + ${springframework.version} + + + org.springframework + spring-webmvc + ${springframework.version} + + + javax.servlet + javax.servlet-api + ${javax.servlet.version} + provided + + + com.fasterxml.jackson.core + jackson-databind + ${jackson.library} + @@ -31,12 +48,12 @@ org.apache.logging.log4j log4j-api - 2.7 + ${log4j2.version} org.apache.logging.log4j log4j-core - ${log4j-api.version} + ${log4j2.version} @@ -52,6 +69,13 @@ logback-classic ${logback.version} + + + + org.jboss.logging + jboss-logging + ${jbosslogging.version} + junit @@ -59,15 +83,51 @@ ${junit.version} test + + org.springframework + spring-test + ${springframework.version} + test + 4.3.4.RELEASE 1.2.17 - 2.7 + 2.7 3.3.6 1.1.7 + 3.3.0.Final + 3.1.0 + 2.8.5 4.12 - \ No newline at end of file + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.2 + + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-war-plugin + 2.4 + + src/main/webapp + logging-service + false + + + + + logging-service + + diff --git a/log-mdc/src/main/java/com/baeldung/config/AppConfiguration.java b/log-mdc/src/main/java/com/baeldung/config/AppConfiguration.java new file mode 100644 index 0000000000..5e52c5f25e --- /dev/null +++ b/log-mdc/src/main/java/com/baeldung/config/AppConfiguration.java @@ -0,0 +1,19 @@ +package com.baeldung.config; + +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; + +@Configuration +@EnableWebMvc +@ComponentScan(basePackages = "com.baeldung") +public class AppConfiguration extends WebMvcConfigurerAdapter { + + @Override + public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) { + configurer.enable(); + } + +} diff --git a/log-mdc/src/main/java/com/baeldung/config/AppInitializer.java b/log-mdc/src/main/java/com/baeldung/config/AppInitializer.java new file mode 100644 index 0000000000..828c2b2efa --- /dev/null +++ b/log-mdc/src/main/java/com/baeldung/config/AppInitializer.java @@ -0,0 +1,29 @@ +package com.baeldung.config; + +import javax.servlet.ServletContext; +import javax.servlet.ServletException; + +import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer; + +public class AppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { + + @Override + public void onStartup(ServletContext servletContext) throws ServletException { + super.onStartup(servletContext); + } + + @Override + protected Class[] getRootConfigClasses() { + return new Class[] { AppConfiguration.class }; + } + + @Override + protected Class[] getServletConfigClasses() { + return null; + } + + @Override + protected String[] getServletMappings() { + return new String[] { "/" }; + } +} diff --git a/log-mdc/src/main/java/com/baeldung/ndc/Investment.java b/log-mdc/src/main/java/com/baeldung/ndc/Investment.java new file mode 100644 index 0000000000..4275c6ef21 --- /dev/null +++ b/log-mdc/src/main/java/com/baeldung/ndc/Investment.java @@ -0,0 +1,41 @@ +package com.baeldung.ndc; + +public class Investment { + private String transactionId; + private String owner; + private Long amount; + + public Investment() { + } + + public Investment(String transactionId, String owner, Long amount) { + this.transactionId = transactionId; + this.owner = owner; + this.amount = amount; + } + + public String getTransactionId() { + return transactionId; + } + + public void setTransactionId(String transactionId) { + this.transactionId = transactionId; + } + + public String getOwner() { + return owner; + } + + public void setOwner(String owner) { + this.owner = owner; + } + + public Long getAmount() { + return amount; + } + + public void setAmount(Long amount) { + this.amount = amount; + } + +} diff --git a/log-mdc/src/main/java/com/baeldung/ndc/controller/JBossLoggingController.java b/log-mdc/src/main/java/com/baeldung/ndc/controller/JBossLoggingController.java new file mode 100644 index 0000000000..b024f3ec81 --- /dev/null +++ b/log-mdc/src/main/java/com/baeldung/ndc/controller/JBossLoggingController.java @@ -0,0 +1,42 @@ +package com.baeldung.ndc.controller; + +import org.jboss.logging.NDC; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +import com.baeldung.ndc.Investment; +import com.baeldung.ndc.service.InvestmentService; + + +@RestController +public class JBossLoggingController { + @Autowired + @Qualifier("JBossLoggingInvestmentService") + private InvestmentService jbossLoggingBusinessService; + + @RequestMapping(value = "/ndc/jboss-logging", method = RequestMethod.POST) + public ResponseEntity postPayment(@RequestBody Investment investment) { + // Add transactionId and owner to NDC + NDC.push("tx.id=" + investment.getTransactionId()); + NDC.push("tx.owner=" + investment.getOwner()); + + try { + jbossLoggingBusinessService.transfer(investment.getAmount()); + } finally { + // take out owner from the NDC stack + NDC.pop(); + + // take out transactionId from the NDC stack + NDC.pop(); + + NDC.clear(); + } + return new ResponseEntity(investment, HttpStatus.OK); + } +} \ No newline at end of file diff --git a/log-mdc/src/main/java/com/baeldung/ndc/controller/Log4J2Controller.java b/log-mdc/src/main/java/com/baeldung/ndc/controller/Log4J2Controller.java new file mode 100644 index 0000000000..9cc57d9fa7 --- /dev/null +++ b/log-mdc/src/main/java/com/baeldung/ndc/controller/Log4J2Controller.java @@ -0,0 +1,41 @@ +package com.baeldung.ndc.controller; + +import org.apache.logging.log4j.ThreadContext; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +import com.baeldung.ndc.Investment; +import com.baeldung.ndc.service.InvestmentService; + +@RestController +public class Log4J2Controller { + @Autowired + @Qualifier("log4j2InvestmentService") + private InvestmentService log4j2BusinessService; + + @RequestMapping(value = "/ndc/log4j2", method = RequestMethod.POST) + public ResponseEntity postPayment(@RequestBody Investment investment) { + // Add transactionId and owner to NDC + ThreadContext.push("tx.id=" + investment.getTransactionId()); + ThreadContext.push("tx.owner=" + investment.getOwner()); + + try { + log4j2BusinessService.transfer(investment.getAmount()); + } finally { + // take out owner from the NDC stack + ThreadContext.pop(); + + // take out transactionId from the NDC stack + ThreadContext.pop(); + + ThreadContext.clearAll(); + } + return new ResponseEntity(investment, HttpStatus.OK); + } +} \ No newline at end of file diff --git a/log-mdc/src/main/java/com/baeldung/ndc/controller/Log4JController.java b/log-mdc/src/main/java/com/baeldung/ndc/controller/Log4JController.java new file mode 100644 index 0000000000..daf7994a88 --- /dev/null +++ b/log-mdc/src/main/java/com/baeldung/ndc/controller/Log4JController.java @@ -0,0 +1,41 @@ +package com.baeldung.ndc.controller; + +import org.apache.log4j.NDC; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +import com.baeldung.ndc.Investment; +import com.baeldung.ndc.service.InvestmentService; + +@RestController +public class Log4JController { + @Autowired + @Qualifier("log4jInvestmentService") + private InvestmentService log4jBusinessService; + + @RequestMapping(value = "/ndc/log4j", method = RequestMethod.POST) + public ResponseEntity postPayment(@RequestBody Investment investment) { + // Add transactionId and owner to NDC + NDC.push("tx.id=" + investment.getTransactionId()); + NDC.push("tx.owner=" + investment.getOwner()); + + try { + log4jBusinessService.transfer(investment.getAmount()); + } finally { + // take out owner from the NDC stack + NDC.pop(); + + // take out transactionId from the NDC stack + NDC.pop(); + + NDC.remove(); + } + return new ResponseEntity(investment, HttpStatus.OK); + } +} \ No newline at end of file diff --git a/log-mdc/src/main/java/com/baeldung/ndc/service/InvestmentService.java b/log-mdc/src/main/java/com/baeldung/ndc/service/InvestmentService.java new file mode 100644 index 0000000000..13d8e6a71b --- /dev/null +++ b/log-mdc/src/main/java/com/baeldung/ndc/service/InvestmentService.java @@ -0,0 +1,31 @@ +package com.baeldung.ndc.service; + +/** + * A fake investment service. + */ +public interface InvestmentService { + + /** + * Sample service transferring a given amount of money. + * @param amount + * @return {@code true} when the transfer complete successfully, {@code false} otherwise. + */ + default public boolean transfer(long amount) { + beforeTransfer(amount); + // exchange messages with a remote system to transfer the money + try { + // let's pause randomly to properly simulate an actual system. + Thread.sleep((long) (500 + Math.random() * 500)); + } catch (InterruptedException e) { + // should never happen + } + // let's simulate both failing and successful transfers + boolean outcome = Math.random() >= 0.25; + afterTransfer(amount, outcome); + return outcome; + } + + void beforeTransfer(long amount); + + void afterTransfer(long amount, boolean outcome); +} diff --git a/log-mdc/src/main/java/com/baeldung/ndc/service/JBossLoggingInvestmentService.java b/log-mdc/src/main/java/com/baeldung/ndc/service/JBossLoggingInvestmentService.java new file mode 100644 index 0000000000..e1e5e0a083 --- /dev/null +++ b/log-mdc/src/main/java/com/baeldung/ndc/service/JBossLoggingInvestmentService.java @@ -0,0 +1,21 @@ +package com.baeldung.ndc.service; + +import org.jboss.logging.Logger; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Service; + +@Service +@Qualifier("JBossLoggingInvestmentService") +public class JBossLoggingInvestmentService implements InvestmentService { + private static final Logger logger = Logger.getLogger(JBossLoggingInvestmentService.class); + + @Override + public void beforeTransfer(long amount) { + logger.infov("Preparing to transfer {0}$.", amount); + } + + @Override + public void afterTransfer(long amount, boolean outcome) { + logger.infov("Has transfer of {0}$ completed successfully ? {1}.", amount, outcome); + } +} \ No newline at end of file diff --git a/log-mdc/src/main/java/com/baeldung/ndc/service/Log4J2InvestmentService.java b/log-mdc/src/main/java/com/baeldung/ndc/service/Log4J2InvestmentService.java new file mode 100644 index 0000000000..6e55796574 --- /dev/null +++ b/log-mdc/src/main/java/com/baeldung/ndc/service/Log4J2InvestmentService.java @@ -0,0 +1,22 @@ +package com.baeldung.ndc.service; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Service; + +@Service +@Qualifier("log4j2InvestmentService") +public class Log4J2InvestmentService implements InvestmentService { + private static final Logger logger = LogManager.getLogger(); + + @Override + public void beforeTransfer(long amount) { + logger.info("Preparing to transfer {}$.", amount); + } + + @Override + public void afterTransfer(long amount, boolean outcome) { + logger.info("Has transfer of {}$ completed successfully ? {}.", amount, outcome); + } +} \ No newline at end of file diff --git a/log-mdc/src/main/java/com/baeldung/ndc/service/Log4JInvestmentService.java b/log-mdc/src/main/java/com/baeldung/ndc/service/Log4JInvestmentService.java new file mode 100644 index 0000000000..1f581554e5 --- /dev/null +++ b/log-mdc/src/main/java/com/baeldung/ndc/service/Log4JInvestmentService.java @@ -0,0 +1,21 @@ +package com.baeldung.ndc.service; + +import org.apache.log4j.Logger; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Service; + +@Service +@Qualifier("log4jInvestmentService") +public class Log4JInvestmentService implements InvestmentService { + private Logger logger = Logger.getLogger(Log4JInvestmentService.class); + + @Override + public void beforeTransfer(long amount) { + logger.info("Preparing to transfer " + amount + "$."); + } + + @Override + public void afterTransfer(long amount, boolean outcome) { + logger.info("Has transfer of " + amount + "$ completed successfully ? " + outcome + "."); + } +} \ No newline at end of file diff --git a/log-mdc/src/main/resources/log4j.properties b/log-mdc/src/main/resources/log4j.properties index 39be027f3f..575ebcca8d 100644 --- a/log-mdc/src/main/resources/log4j.properties +++ b/log-mdc/src/main/resources/log4j.properties @@ -3,6 +3,10 @@ log4j.appender.consoleAppender.layout=org.apache.log4j.PatternLayout #note the %X{userName} - this is how you fetch data from Mapped Diagnostic Context (MDC) #log4j.appender.consoleAppender.layout.ConversionPattern=%-4r [%t] %5p %c{1} %x - %m%n +# %x is used to fetch data from NDC. So below setting uses both MDC and NDC log4j.appender.consoleAppender.layout.ConversionPattern=%-4r [%t] %5p %c{1} %x - %m - tx.id=%X{transaction.id} tx.owner=%X{transaction.owner}%n -log4j.rootLogger = TRACE, consoleAppender \ No newline at end of file +# NDC only setting - %x is used to fetch data from NDC +#log4j.appender.consoleAppender.layout.ConversionPattern=%-4r [%t] %5p %c{1} - %m - [%x]%n + +log4j.rootLogger = INFO, consoleAppender \ No newline at end of file diff --git a/log-mdc/src/main/resources/log4j2.xml b/log-mdc/src/main/resources/log4j2.xml index 800cfacafe..cbdf02fc51 100644 --- a/log-mdc/src/main/resources/log4j2.xml +++ b/log-mdc/src/main/resources/log4j2.xml @@ -2,8 +2,15 @@ + + pattern="%-4r [%t] %5p %c{1} - %m - %x - tx.id=%X{transaction.id} tx.owner=%X{transaction.owner}%n" /> + + + + diff --git a/log-mdc/src/test/java/com/baeldung/ndc/NDCLogTest.java b/log-mdc/src/test/java/com/baeldung/ndc/NDCLogTest.java new file mode 100644 index 0000000000..8cba176f7e --- /dev/null +++ b/log-mdc/src/test/java/com/baeldung/ndc/NDCLogTest.java @@ -0,0 +1,61 @@ +package com.baeldung.ndc; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +import com.baeldung.config.AppConfiguration; +import com.fasterxml.jackson.databind.ObjectMapper; + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = AppConfiguration.class) +@WebAppConfiguration +public class NDCLogTest { + + private MockMvc mockMvc; + + @Autowired + private WebApplicationContext webApplicationContext; + + private Investment investment; + + @Before + public void setUp() { + mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build(); + + investment = new Investment(); + investment.setTransactionId("123"); + investment.setOwner("Mark"); + investment.setAmount(1000L); + } + + @Test + public void givenLog4jLogger_whenNDCAdded_thenResponseOkAndNDCInLog() throws Exception { + mockMvc.perform(post("/ndc/log4j", investment).contentType(MediaType.APPLICATION_JSON).content(new ObjectMapper().writeValueAsString(investment))).andExpect(status().is2xxSuccessful()); + + } + + @Test + public void givenLog4j2Logger_whenNDCAdded_thenResponseOkAndNDCInLog() throws Exception { + mockMvc.perform(post("/ndc/log4j2", investment).contentType(MediaType.APPLICATION_JSON).content(new ObjectMapper().writeValueAsString(investment))).andExpect(status().is2xxSuccessful()); + + } + + @Test + public void givenJBossLoggerBridge_whenNDCAdded_thenResponseOkAndNDCInLog() throws Exception { + mockMvc.perform(post("/ndc/jboss-logging", investment).contentType(MediaType.APPLICATION_JSON).content(new ObjectMapper().writeValueAsString(investment))).andExpect(status().is2xxSuccessful()); + + } + +}