DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Last call! Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • A Practical Guide to Creating a Spring Modulith Project
  • Distributed Tracing System (Spring Cloud Sleuth + OpenZipkin)
  • Java, Spring Boot, and MongoDB: Performance Analysis and Improvements
  • Spring Boot Secured By Let's Encrypt

Trending

  • Docker Base Images Demystified: A Practical Guide
  • Medallion Architecture: Why You Need It and How To Implement It With ClickHouse
  • Top Book Picks for Site Reliability Engineers
  • Event-Driven Architectures: Designing Scalable and Resilient Cloud Solutions
  1. DZone
  2. Coding
  3. Frameworks
  4. Setting Up MDC Context With AOP in Spring Boot Applications

Setting Up MDC Context With AOP in Spring Boot Applications

Need help setting up MDC Context with AOP on your Spring Boot application? Check out this tutorial on how to put specific information into the Mapped Diagnostic Context.

By 
Peter Varga user avatar
Peter Varga
·
Sep. 04, 18 · Tutorial
Likes (3)
Comment
Save
Tweet
Share
40.3K Views

Join the DZone community and get the full member experience.

Join For Free

In order to be able to analyze logs of complex processes for your application, it is useful to use Mapped Diagnostic Context (MDC) and put some process specific information into it. Currently, I am implementing a Spring Boot application with several services and lots of calls between them. As there is a huge number of users parallel, when it comes to analyzing a production log, it is not easy to find the subsequent process calls, belonging to a REST call in the log file.

In order to make my life easier, there is still a unique session ID in the log, using MDC, to find all the calls belonging to a specific user and call. But, to make the analysis easier, I decided to add function-specific information to the logs. So, if a REST method gets called, it can set the logical function name in the MDC. Therefore, all later calls to the helper or DAO classes can be logged with the same function name and can be found easily.

Obviously, setting the MDC value could be done at the beginning of in every REST method as well, like this:

@RequestMapping(value = "/logic/delete", method = { RequestMethod.POST })
public ResponseEntity<String> delete(HttpServletRequest httpServletRequest, @Valid @RequestBody DeleteLogicRequest request) {
    MDC.put(BACKEND_FUNCTION_NAME, "[" + functionName + "]");

    // do the subsequent service calls...


Fortunately, using Spring AOP possibilities, it is easy to implement this cross-cutting functionality in a more sophisticated way.

First, I defined an annotation to mark services where function information needs to be set into the MDC context before calling any of its public methods.

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Used to mark classes in which before all method call the function information should be set into the MDC context. All logs of subsequent method
 * calls on any bean will contain the MDC information.
 */
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface ClassWithMdcContext {
 String functionName() default "";
}


I also defined another annotation to make it possible to mark single methods. The function name information will be set by the Spring framework into MDC before calling the marked methods.

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Used to mark methods in which before calling it, the function information should be set into the MDC context. All logs of subsequent method calls
 * on any bean will contain the MDC information.
 */
@Target({ ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface MethodWithMdcContext {
 String functionName() default "";
}


The AOP magic happens in the following class:

import java.lang.reflect.Method;

import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.MDC;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Service;

// Marks the AOP functionality as the first one. Needed while logging happens also with AOP, and you make sure, that MDC information is set before any
// other AOP activities.
@Order(1)
@Service
@Aspect
public class MdcContextInitialiser {
 private static final String BACKEND_FUNCTION_NAME = "backendFunctionName";

 @Around("methodsAnnoatatedWithMethodWithMdcContext()")
 public Object aroundAnnotatedMethods(ProceedingJoinPoint joinPoint) throws Throwable {
  setMdcContextForMethod(joinPoint);
  return joinPoint.proceed();
 }

 @Around("classesAnnotatedWithClassWithMdcContext()")
 public Object aroundAnnotatedClass(ProceedingJoinPoint joinPoint) throws Throwable {
  setMdcContextForClass(joinPoint);
  return joinPoint.proceed();
 }

 @Pointcut(value = "@annotation(MethodWithMdcContext)")
 public void methodsAnnoatatedWithMethodWithMdcContext() {
  // defines pointcut for methods annotated with MethodWithMdcContext
 }

 @Pointcut("@within(ClassWithMdcContext)") // this should work for the annotation service pointcut
 private void classesAnnotatedWithClassWithMdcContext() {
  // defines pointcut for classes annotated with ClassWithMdcContext
 }

 private void setMdcContextForMethod(ProceedingJoinPoint joinPoint) {
  MethodSignature signature = (MethodSignature) joinPoint.getSignature();
  Method method = signature.getMethod();
  MethodWithMdcContext annotation = method.getAnnotation(MethodWithMdcContext.class);

  String functionName = annotation.functionName();

  if (StringUtils.isBlank(functionName)) {
   functionName = getClassName(signature.getDeclaringTypeName()) + "_" + method.getName();
  }

  MDC.put(BACKEND_FUNCTION_NAME, "[" + functionName + "]");
 }

 @SuppressWarnings({ "unchecked", "rawtypes" })
 private void setMdcContextForClass(ProceedingJoinPoint joinPoint) {
  MethodSignature signature = (MethodSignature) joinPoint.getSignature();
  Class clazz = signature.getDeclaringType();
  ClassWithMdcContext annotation = (ClassWithMdcContext) clazz.getAnnotation(ClassWithMdcContext.class);

  String functionName = annotation.functionName();

  if (StringUtils.isBlank(functionName)) {
   functionName = getClassName(signature.getDeclaringTypeName()) + "_" + signature.getMethod().getName();
  }

  MDC.put(BACKEND_FUNCTION_NAME, "[" + functionName + "]");
 }

 private String getClassName(String classFullName) {
  int startIndexOfClassName = StringUtils.lastIndexOf(classFullName, ".") + 1;
  return StringUtils.substring(classFullName, startIndexOfClassName);
 }

}


The MdcContextInitialiser class initializes the MDC context, corresponding to the annotations above. I defined a pointcut for both of the annotations and an around method, in order to call the MDC initialization before calling the actual method.

The  functionName  property in both annotations allows defining any logical function name for the log, like  StartupCall, AddToCart, or Purchase. In case it is not defined, a combination of the class and method name will be used.

In order to get the MDC data printed in the log, you need to add the key backendFunctionName to the log pattern in the logback.xml.

<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
 <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
  <level>INFO</level>
 </filter>
 <encoder>
  <Pattern>%d %-5level %X{genericID} %X{backendFunctionName} %C{0} %L : %msg%n</Pattern>
 </encoder>
</appender>


After marking some classes or methods with the corresponding annotation, your log will contain the function name. Say calling the  LogicRestService.getAll() method, the log looks like this:

 [exec] 2018-07-05 16:00:42,704 INFO  [LogicRestService_getAll] AuthenticationFilter 56 : Processing URL: https://localhost/rest/logic/all
 [exec] 2018-07-05 16:00:42,706 INFO  [LogicRestService_getAll] AuthenticationFilter 183 : URL needs to be processed: https://localhost/rest/logic/all for type crbheader
 [exec] 2018-07-05 16:00:42,706 INFO  [LogicRestService_getAll] AuthenticationFilter 183 : URL needs to be processed: https://localhost/rest/logic/all for type ManualLogin
 [exec] 2018-07-05 16:00:42,706 INFO  [LogicRestService_getAll] AuthenticationFilter 118 : URL https://localhost/rest/logic/all requires manual login: true
 [exec] 2018-07-05 16:00:42,706 INFO  [LogicRestService_getAll] AuthenticationFilter 125 : User logged in with manual login: true
 [exec] 2018-07-05 16:00:42,707 INFO  [LogicRestService_getAll] ExecutionTimeMonitor 119 : Entering GatewayDao.findGatewayIdBySubscriberId(String) parameters: [51125096]
 [exec] 2018-07-05 16:00:42,710 INFO  [LogicRestService_getAll] ExecutionTimeMonitor 124 : Exiting GatewayDao.findGatewayIdBySubscriberId(String) : 3 ms result : Optional.empty
 [exec] 2018-07-05 16:00:42,714 INFO  [LogicRestService_getAll] ExecutionTimeMonitor 119 : Entering LogicRestService.getAll(HttpServletRequest) parameters: [org.springframework.web.util.ContentCachingRequestWrapper@72689384]
 [exec] 2018-07-05 16:00:42,716 INFO  [LogicRestService_getAll] ExecutionTimeMonitor 119 : Entering LogicDao.findByGatewaySubscriberIdAndWorkingState(String) parameters: [53474139]
 [exec] 2018-07-05 16:00:42,718 INFO  [LogicRestService_getAll] ExecutionTimeMonitor 124 : Exiting LogicDao.findByGatewaySubscriberIdAndWorkingState(String) : 2 ms result : []
 [exec] 2018-07-05 16:00:42,718 INFO  [LogicRestService_getAll] ExecutionTimeMonitor 124 : Exiting LogicRestService.getAll(HttpServletRequest) : 4 ms result : <200 OK,[],{}>


As you can see, all further log entry contains the function information. In this way, it is quite easy to identify all the logs belonging to a given REST service, even if they are separated by entries from other processes or sessions.

Spring Framework Spring Boot application

Published at DZone with permission of Peter Varga. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • A Practical Guide to Creating a Spring Modulith Project
  • Distributed Tracing System (Spring Cloud Sleuth + OpenZipkin)
  • Java, Spring Boot, and MongoDB: Performance Analysis and Improvements
  • Spring Boot Secured By Let's Encrypt

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends: