Proxy Method Logging

11 February 2004 ~ blog java

Here’s an interesting spin on logging. Say you have a service of some kind that loads in other classes dynamically such as modules or plug-ins and you want to provide a seamless debugging mode no matter what kind of logging the module developer has done. You can use a java.lang.reflect.Proxy to do your method call logging. Basically, you build a wrapper around your module that does the logging for any module.

First off, you need an interface to do this, any interface that your modules implement will work.

public interface SomeInterface {

    public void doSomethingA(String p1, int p2);

    public String[] doSomethingB(Map map);
}

Second, you will need an implementation of that interface, your module or plug-in class. This is the actual object doing the work whose method calls will be logged.

public class SomeImplnterfaceImpl implements SomeInterface {

    public SomeImplnterfaceImpl(){}

    public void doSomethingA(String p1, int p2){
        System.out.println("doSomethingA with (" + p1 + " and " + p2 + ")");
    }

    public String[] doSomethingB(Map map){
        System.out.println("doSomethingB with a Map: " + map);
        return( (String[])map.values().toArray(new String[0]) );
    }
}

Both of the above items you should already have in your system. If not, in most cases, they can be easily added. Now we get into the good stuff. The Proxy class is an interesting little creature. It allows you to give it an array of interfaces that it is to "implement" by passing method calls to an instance of the InvocationHandler interface, which determines how the method calls are to be processed. Our proxy will simply log the method call and pass it along to the real implementation. We’ll make a factory class to create our logging proxy. It will have one method with two arguments. The first is the interface that is to be logged, SomeInterface in our example, and the second is the implementation of that interface, SomeInterfaceImpl for our example.

public final class LoggingProxyFactory {

    private LoggingProxyFactory(){}

    public static final Object create(Class interfc,Object impl) throws Exception {
        LoggingHandler handler = new LoggingHandler(impl);
        return(
            Proxy.newProxyInstance(
                impl.getClass().getClassLoader(),
                new Class[]{interfc},
                handler
            )
        );
    }
}

You see that creating a proxy is pretty simple. You create a proxy instance using your interfaces, your class loader and your invocation handler. The invocation handler is where the action happens. Our InvocationHandler implementation is going to be a static inner class of the LoggingProxyFactory class. What it needs to do is logs the method calls as they come in and then pass along the call to the real implementation. Here is the code for the inner class.

private static final class LoggingHandler implements InvocationHandler {

    private Log log;
    private Object impl;

    private LoggingHandler(Object impl){
        this.impl = impl;
        this.log = LogFactory.getLog(impl.getClass());
    }

    public Object invoke(Object obj,Method method,Object[] params) throws Throwable {
        if(log.isInfoEnabled()) log.info("Entering: " + method.getName();

        if(log.isDebugEnabled()){
            for(int p=0; p&tl;params.length;p++){
                log.debug(method.getName() + " Param[" + p + "]: " + params[p].toString());
            }
        }

        Object ret = null;

        try {ret = method.invoke(impl, params);}
        catch(Exception ex){
            if(log.isErrorEnabled()){
                log.error(method.getName() + " Exception: " + ex.getMessage(), ex);
            }
            throw ex;
        }

        if(log.isDebugEnabled()) log.debug(method.getName() + " Returned: " + ret);

        if(log.isInfoEnabled()) log.info("Leaving");

        return(ret);
    }
}

I am using the Jakarta Logging API for the logging in this case; however, you could use any logging API or simple standard out statements as you see fit. The main thing to notice in the code above is the invocation of the method on the implementation object.

ret = method.invoke(impl, params);

It is this line that passes on the method call to the real implementation. The rest of the method is logging of the entering and leaving, parameters and return value. Exceptions are also logged. To test out the proxy and see it in action, you can run the following tester app:

public class Tester {
    public static void main(String[] args){
        try {
            Object obj = LoggingProxyFactory.create(
                SomeInterface.class,
                new SomeImplnterfaceImpl()
            );
            SomeInterface siObj = (SomeInterface)obj;
            siObj.doSomethingA("Hello",31);

            Map map = new HashMap();
            map.put("a", "Some data 1");
            map.put("b", "Some data 2");
            map.put("c", "Some data 3");
            map.put("d", "Some data 4");
            siObj.doSomethingB(map);
        } catch(Exception ex){
            ex.printStackTrace();
        }
    }
}

Which yields the method call results and their logging information. This was an interesting idea I have tossed around for a while now. It does work, though it has not been rigorously tested. If nothing else, it is a description of how to use the proxy class.


Creative Commons License CoffeaElectronica.com content is copyright © 2016 Christopher J. Stehno and available under a Creative Commons Attribution-ShareAlike 4.0 International License.