0

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.

Teddy Tsai
  • 414
  • 1
  • 13

2 Answers2

0

You can use Slf4j annotation from lombok. It helps you define a default log instance that you can use in very annotated classes.

Edit: You can also use an interceptor

But if you still want to use reflection with your custom annotation, it is always possible.

@Slf4j
public class MyClass {

    public void myMethod() {
        log.error("Something else is wrong here");
    }
}
Harry Coder
  • 2,429
  • 2
  • 28
  • 32
  • Yes that is what's being done now, what I need to do additionally is that whenever something is logged, I'd like to save it to a database by calling a custom save method. – Teddy Tsai Apr 24 '22 at 05:12
  • 1
    You need an interceptor. This link could help you: https://stackoverflow.com/questions/13956262/intercept-log-messages-slf4j – Harry Coder Apr 24 '22 at 05:18
0

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>

Teddy Tsai
  • 414
  • 1
  • 13