Our goal is to implement the mechanism to log every execution of selected methods. We also want to log all passed parameters and returned results.
Assumptions:
- Should support any number of methods in the project
- Methods may return any result (any type/class)
- Methods may receive any set of parameters
- Methods might be placed in any class in the project
This is just an example
public SomeResult methodThatShouldBeLogged(.. set of parameters ..) {
...
}
public AnotherResult anotherMethodThatShouldBeLogged(.. another set of parameters ..) {
...
}
What are we actually going to log? Log messages should include
- all parameters passed to the method
- returned result
- execution time
Logging with AspectJ
Now we need to create an aspect Logger
(or what ever you want to call it) with @Around advice
.
@Around("execution({.. pointcut for your methods ..})")
The problem is that we have to find a correct pointcut for each method that we want to log because all methods have different signatures and placed in different packages. After that we will need to create a separate advice for each method. Sometimes that’s not so easy.
Annotations for methods that should be logged
I’m going to show the trick that I use on all my projects.
First, we create the annotation @LogThat
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface LogThat {
}
Log method with parameters and results. Logger aspect
Now we create our aspect Logger that contains only one @Around advice
for all method that should be logged. In the code snippet I use Reporter.log(..) for actual logging messages because usually I use that in TestNG tests. You can use any other logger to log (Log4j
, Logback
, SLF4J
…) on your project.
@Aspect
public class Logger {
private Gson gson = new GsonBuilder().setPrettyPrinting().create();
@Around("execution(* *(..)) && @annotation(io.lenar.examples.spring.log.LogThat)")
public Object logMethods(ProceedingJoinPoint jp) throws Throwable {
String methodName = jp.getSignature().getName();
logMethodInvocationAndParameters(jp);
long startTime = new Date().getTime();
Object result = jp.proceed(jp.getArgs());
long endTime = new Date().getTime();
Reporter.log("\nExecution time: " + (endTime - startTime) + "ms", true);
Reporter.log("<- " + methodName + " returns \n" + gson.toJson(result) + "\n", true);
return result;
}
private void logMethodInvocationAndParameters(ProceedingJoinPoint jp) {
String[] argNames = ((MethodSignature) jp.getSignature()).getParameterNames();
Object[] values = jp.getArgs();
Map<String, Object> params = new HashMap<>();
if (argNames.length != 0) {
for (int i = 0; i < argNames.length; i++) {
params.put(argNames[i], values[i]);
}
}
Reporter.log("-> method " + jp.getSignature().getName() + " invocation", true);
if (!params.isEmpty()) Reporter.log(gson.toJson(params), true);
}
}
Note: If this is a Spring project add @Component
annotation to the Logger class.
The most interesting part here is logMethodInvocationAndParameters
where we log passed parameters as a map in JSON format.
How to log method with parameters using annotation
Now we can annotate all methods that we want to log with @LogThat
annotation. Logger
will intercept all invocations of methods annotated with @LogThat
and log
@LogThat
public SomeResult methodThatShouldBeLogged(.. set of parameters ..) {
...
}
@LogThat
public AnotherResult anotherMethodThatShouldBeLogged(.. another set of parameters ..) {
...
}
public Result methodThatShouldntBeLogged(.. another set of parameters ..) {
...
}
Aspect weaving
The last thing we need to setup is pom.xml
to properly weave our aspects.
There are some differences between Spring and non-Spring projects so carefully read this - Aspect weaving in Spring and non-Spring projects
EasyLog (Site, Source code) is an open source library that allows you to log any method or all methods of a class with one annotations. Also it supports a lot of other useful features like masking fields to pevent sensitive data from logging and many others.
EasyLog uses a similar approach to the described in the post
You may also find these posts interesting: