My problem is that a lot of methods in my project now require to have their logs stored, AOP isn't very viable since there isn't an appropriate point to cut, so I'm thinking about making a custom annotation and putting it wherever it's needed.
Annotated methods would call a custom method to store the log message whenever something is logged inside it.
I have never made annotations and I'm not really familiar with reflection, so I would like to know if such a thing would be doable, or is there some kind of approach you would suggest.
Thank you very much.
In the end I used the logback filter to filter all logging events, then used the stacktrace from the ILoggingEent to find out whether an annotation is present in the stacktrace of the logging event.
Annotation:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface StoreLog {
//business logic hidden
}
Here's the implementation of the filter:
public class SaveLogFilter extends Filter<ILoggingEvent> {
@Override
public FilterReply decide(ILoggingEvent event) {
if (event.getLevel() == Level.DEBUG) {
return FilterReply.DENY;
}
StackTraceElement[] callerData = event.getCallerData();
if (callerData != null && callerData.length > 0) {
for (StackTraceElement stackTraceElement : callerData) {
StoreLog annotation;
try {
Class clazz = Class.forName(stackTraceElement.getClassName());
annotation = (StoreLog) clazz.getAnnotation(StoreLog.class);
if (annotation == null) {
Method method = ReflectionUtils.getMethod(stackTraceElement);
if (method.isAnnotationPresent(StoreLog.class)) {
annotation = method.getAnnotation(StoreLog.class);
}
}
//business logic to save the log
return FilterReply.ACCEPT;
}catch (Exception ignored){
//no action needed
}
}
}
return FilterReply.ACCEPT;
}
To find an annotated method:
import aj.org.objectweb.asm.Opcodes;
import org.objectweb.asm.*;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.concurrent.atomic.AtomicReference;
public class ReflectionUtils {
private ReflectionUtils() {
}
public static Method getMethod(final StackTraceElement stackTraceElement) throws ClassNotFoundException, IOException, NoSuchMethodException, NoSuchLineException {
final String stackTraceClassName = stackTraceElement.getClassName();
final String stackTraceMethodName = stackTraceElement.getMethodName();
final int stackTraceLineNumber = stackTraceElement.getLineNumber();
Class<?> stackTraceClass = Class.forName(stackTraceClassName);
final AtomicReference<String> methodDescriptorReference = new AtomicReference<>();
InputStream classFileStream = stackTraceClass.getResourceAsStream(stackTraceClassName.split("\\.")[stackTraceClassName.split("\\.").length - 1] + ".class");
if (classFileStream == null) {
throw new ClassNotFoundException("Could not acquire the class file containing for the calling class");
}
try {
ClassReader classReader = new ClassReader(classFileStream);
classReader.accept(
new ClassVisitor(Opcodes.ASM5) {
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
if (!name.equals(stackTraceMethodName)) {
return null;
}
return new MethodVisitor(Opcodes.ASM5) {
@Override
public void visitLineNumber(int line, Label start) {
if (line == stackTraceLineNumber) {
methodDescriptorReference.set(desc);
}
}
};
}
},
0
);
} finally {
classFileStream.close();
}
String methodDescriptor = methodDescriptorReference.get();
if (methodDescriptor == null) {
throw new NoSuchLineException("Could not find line " + stackTraceLineNumber);
}
for (Method method : stackTraceClass.getMethods()) {
if (stackTraceMethodName.equals(method.getName()) && methodDescriptor.equals(Type.getMethodDescriptor(method))) {
return method;
}
}
throw new NoSuchMethodException("Could not find the calling method");
}
}
Logback.xml:
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<filter class="#{filter.path}"/>
<encoder>
<pattern>
%-4relative [%thread] %-5level %logger - %msg%n
</pattern>
</encoder>
</appender>
<root>
<appender-ref ref="STDOUT"/>
</root>
</configuration>