Small question regarding MDC with Java please.
At first, I had very straightforward methods, some methods with many parameters (I shorten the list of parameters to keep things short just for this question, but please imagine a lot of parameters)
public String invokeMethodForPerson(int age, String name, boolean isCool, long distanceRun, double weight) {
LOGGER.info("begin to invoke method invokeMethodForPerson");
return methodForPerson(age, name, isCool, distanceRun, weight);
}
public String invokeMethodForCar(String model, boolean isElectric, long price, int numberOfDoors) {
LOGGER.info("begin to invoke method invokeMethodForCar");
return methodForCar(model, isElectric, price, numberOfDoors);
}
public String invokeMethodForFlower(String name, String color) {
LOGGER.info("begin to invoke method invokeMethodForFlower");
return methodForFlower(name, color);
}
(This question is not about how to refactor the long list of parameters)
Then, I wanted to leverage MDC. MDC is very helpful, it allow better search in log aggregator tools etc. Having them as first level, not inside the logger itself is very helpful.
Therefore, in order to leverage MDC, the code went from simple, to something now similar to this, here is what I tried.
public String invokeMethodForPerson(int age, String name, boolean isCool, long distanceRun, double weight) {
MDC.put("age", age);
MDC.put("name", name);
MDC.put("isCool", isCool);
MDC.put("distanceRun", distanceRun);
MDC.put("weight", weight);
LOGGER.info("begin to invoke method invokeMethodForPerson");
return methodForPerson(age, name, isCool, distanceRun, weight);
}
public String invokeMethodForCar(String model, boolean isElectric, long price, int numberOfDoors) {
MDC.put("model", model);
MDC.put("isElectric", isElectric);
MDC.put("price", price);
MDC.put("numberOfDoors", numberOfDoors);
LOGGER.info("begin to invoke method invokeMethodForCar");
return methodForCar(model, isElectric, price, numberOfDoors);
}
public String invokeMethodForFlower(String name, String color) {
MDC.put("name", name);
MDC.put("color", color);
LOGGER.info("begin to invoke method invokeMethodForFlower");
return methodForFlower(name, color);
}
Problem: now looking at the code, there is actually more lines of MDC.put()
than actual business logic code
Question: Is there a cleaner way to leveraging MDC, for many parameters, other than just adding tons of lines of MDC.put()
, if possible, using AOP/aspects/advice/pointcut please?
Thank you
You can create your own utils to work with MDC.
public class MDCUtils {
private MDCUtils() {
}
public static void putAll(Object... params) {
if ((params.length & 1) != 0) {
throw new IllegalArgumentException("Length is odd");
}
for (int i = 0; i < params.length; i += 2) {
String key = Objects.requireNonNull((String) params[i]); //The key parameter cannot be null
Object value = params[i + 1]; //The val parameter can be null only if the underlying implementation supports it.
MDC.put(key, value);
}
}
public static void putAll(Map<String, Object> map) {
map.forEach(MDC::put);
}
public static <K extends String, V> void put(K k1, V v1, K k2, V v2) {
putAll(k1, v1, k2, v2);
}
public static <K extends String, V> void put(K k1, V v1, K k2, V v2, K k3, V v3) {
putAll(k1, v1, k2, v2, k3, v3);
}
}
Example of usage:
public String invokeMethodForPerson(int age, String name, boolean isCool, long distanceRun, double weight) {
MDCUtils.putAll("age",age, "name", name,"isCool", isCool,"distanceRun", distanceRun,"weight", weight);
LOGGER.info("begin to invoke method invokeMethodForPerson");
return methodForPerson(age, name, isCool, distanceRun, weight);
}
More strict example:
public String invokeMethodForFlower(String name, String color) {
MDCUtils.put("name", name, "color", color);
LOGGER.info("begin to invoke method invokeMethodForFlower");
return methodForFlower(name, color);
}
Example using Map.of
, but it does not support nullable values.
public String invokeMethodForPerson(int age, String name, boolean isCool, long distanceRun, double weight) {
MDCUtils.putAll(Map.of( "age",age,"name", name,"isCool", isCool,"distanceRun", distanceRun,"weight", weight));
LOGGER.info("begin to invoke method invokeMethodForPerson");
return methodForPerson(age, name, isCool, distanceRun, weight);
}
UPDATE:
AOP solution via AspectJ
Add annotation @LogMDC
for method and parameter target. The idea is to add the ability to put all method arguments or specific ones to MDC
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface LogMDC {
}
Add aspect which will catch methods marked with @LogMDC
annotation and perform storing arguments to the MDC
.
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
@Aspect
public class MDCAspect {
//match the method marked @LogMDC annotation
@Before("@annotation(LogMDC) && execution(* *(..))")
public void beforeMethodAnnotation(JoinPoint joinPoint) {
String[] argNames = ((MethodSignature) joinPoint.getSignature()).getParameterNames();
Object[] values = joinPoint.getArgs();
if (argNames.length != 0) {
for (int i = 0; i < argNames.length; i++) {
MDC.put(argNames[i], values[i]);
}
}
}
//match the method which has any parameter with @LogMDC annotation
@Before("execution(* *(.., @LogMDC (*), ..))")
public void beforeParamsAnnotation(JoinPoint joinPoint) {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
String[] argNames = methodSignature.getParameterNames();
Object[] values = joinPoint.getArgs();
Method method = methodSignature.getMethod();
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
for (int i = 0; i < parameterAnnotations.length; i++) {
Annotation[] annotations = parameterAnnotations[i];
for (Annotation annotation : annotations) {
if (annotation.annotationType() == LogMDC.class) {
MDC.put(argNames[i], values[i]);
}
}
}
}
}
Example of using. Put all parameters of method to MDC
@LogMDC
public String invokeMethodForPerson(int age, String name, boolean isCool, long distanceRun, double weight) {
LOGGER.info("begin to invoke method invokeMethodForPerson");
return methodForPerson(age, name, isCool, distanceRun, weight);
}
Example of using. Put only specific parameters of the method to MDC, that marked with annotation.
public String invokeMethodForPerson(@LogMDC int age, @LogMDC String name, boolean isCool, long distanceRun, @LogMDC double weight) {
LOGGER.info("begin to invoke method invokeMethodForPerson");
return methodForPerson(age, name, isCool, distanceRun, weight);
}
Please note, AspecJ have a little performace impact
Performance penalty for using AspectJ
Performance impact of using aop
Performance: using AspectJ to log run time of all methods
Depends on your use cases you should decide what to use simple util method call or aop solution.