19

I have a class with database calls, and I generally want to log every method called (with arguments) in this class with log4j:

logger.debug("foo(id="+id+") initiated");

Is it possible to do this automatically? Maybe by using some sort of annotation in start of each method instead of writing every single logger.debug?

Today I have to update my logging.debug every time I change arguments or method name.

Nishant
  • 54,584
  • 13
  • 112
  • 127
Peeer
  • 219
  • 1
  • 2
  • 7

3 Answers3

13

Try @Loggable annotation and an AspectJ aspect from jcabi-aspects (I'm a developer):

@Loggable(Loggable.INFO)
public String load(URL url) {
  return url.openConnection().getContent();
}

All method calls are logged through SLF4J.

This blog post explains it step by step: Java Method Logging with AOP and Annotations

yegor256
  • 102,010
  • 123
  • 446
  • 597
  • Hi, can you please add a full working example of how to set up `jcabi-aspects`, including the `pom.xml`? A URL to a tutorial webpage would be fine. Thanks! – Jonathan Benn Mar 16 '18 at 14:57
  • 1
    You can see a real project, where it all works just fine: https://github.com/yegor256/jare I also added the link to the blog post – yegor256 Mar 18 '18 at 05:42
9

If you have interfaces declaring the methods you want to log calls to, you can use the standard Proxy API to achieve what you want.

The Proxy API would allow you to wrap your actual implementation in a new, proxy class, that would log the call, and the forward the call to implementation. You just have to implement one InvocationHandler that does the logging and the forwarding.

For example,

interface Calculator {
  int add(int a, int b);
}

class CalculatorImpl implements Calculator {
  @Override public int add(int a, int b) { return a+b; }
}

class LoggingInvocationHandler implements InvocationHandler {
  private final Object delegate;
  public LoggingInvocationHandler(final Object delegate) {
    this.delegate = delegate;
  }
  @Override invoke(Object proxy, Method method, Object[] args) throws Throwable {
    System.out.println("method: " + method + ", args: " + args);
    return method.invoke(delegate, args);
  }
}

class X {
  public static void main(String... args) {
    final Calculator calc = new CalculatorImpl();
    final Calculator loggingCalc =
      (Calculator) Proxy.newProxyInstance(X.class.getClassLoader(),
                                          new Class[] {Calculator.class},
                                          new LoggingInvocationHandler (calc));
    loggingCalc.add(2, 3); // shall print to the screen
  }
}

You can also easily log the return values and exceptions thrown by the methods, just by changing the code in the InvocationHandler. Also, you could use any logging framework you like instead of System.out.println as in the example.

To log return values and exceptions, you could do something like:

  @Override invoke(Object proxy, Method method, Object[] args) throws Throwable {
    System.out.println("method: " + method + ", args: " + args);
    try {
      final Object ret = method.invoke(delegate, args);
      System.out.println("return: " + ret);
      return ret;
    } catch (Throwable t) {
      System.out.println("thrown: " + t);
      throw t;
    }
  }
mdev
  • 1,366
  • 17
  • 23
Bruno Reis
  • 37,201
  • 11
  • 119
  • 156
5

One possible solution would be to use aspectj. Idea would be to attach aspect to every method you wish to log, and perform logging is aspect instead of a method. One example of aspectj logging is right here in stackoverflow.

Community
  • 1
  • 1
Ahe
  • 2,124
  • 3
  • 19
  • 29