Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using Byte Buddy for Java Agent

I wish to create an agent to attach to our live Tomcat & Weblogic servers which will intercept all method calls to all classes declared in my companies package and do some logging of metrics such as execution time.

I came across the Byte Buddy library which seems to cater for this. However, I am not 100% clear on the approach to creating an agent using Byte Buddy:

  • The following article suggests that one creates an own agent and makes no mention of the byte-buddy-agent: http://mydailyjava.blogspot.ie/2015/01/make-agents-not-frameworks.html
  • However, I do see someone has created his/her own byte-buddy-agent so I am not sure if I am meant to use this. https://github.com/raphw/byte-buddy/tree/master/byte-buddy-agent

I went with the approach of creating my own agent and packaged it up using Maven to include Byte Buddy as a fat jar (so that the Byte Buddy code is on the class path) which I reference from my catalina.bat.

Edit: I have since downloaded the source and figured out that the AgentBuilder relies on the byte-buddy-agent package so the above question is irrelevant.

Tomcat starts up fine and I can see that the agent is called as I see the "Entered premain" System.out.

However I never see the "Intercepted" System.out when I execute the code on a separate war file deployed to Tomcat. Edit: Code below updated based on Rafael's response and this is now working.

Can somebody tell me what I might be doing wrong here? I've included the agent code below.

Also, can someone tell me which ElementMatchers is best for package matching? I tried nameStartsWith but it had no effect and the API documentation does not state if it is the fully qualified class name.

*Edit: The nameStartsWith does check the package. *

Anyway, thanks in advance for any help!

package com.mycompany.agent;

import java.lang.instrument.Instrumentation;
import java.util.concurrent.Callable;

import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.implementation.bind.annotation.RuntimeType;
import net.bytebuddy.implementation.bind.annotation.SuperCall;
import net.bytebuddy.matcher.ElementMatchers;

public class MyAgent {

public static void premain(String agentArgument, Instrumentation instrumentation) {
    System.out.println("Entered premain");
    try{
        new AgentBuilder.Default()
                .withListener( new AgentBuilder.Listener() {

                    public void onComplete(String arg0) {
                        System.out.println("Completed - " + arg0);
                    }

                    public void onError(String arg0, Throwable arg1) {
                        System.out.println("Error - " + arg0+", "+arg1.getMessage());
                        arg1.printStackTrace();
                    }

                    public void onIgnored(String arg0) {
                        System.out.println("Ignored - " + arg0);
                    }

                    public void onTransformation(TypeDescription arg0, DynamicType arg1) {
                        System.out.println("Transformed - " + arg0+", type = "+arg1);
                    }

                })
                .rebase(ElementMatchers.nameStartsWith("com.mycompany"))
                .transform(new AgentBuilder.Transformer() {
                    public DynamicType.Builder transform(DynamicType.Builder builder, TypeDescription typeDescription) {
                        return builder.method(ElementMatchers.any()).intercept(MethodDelegation.to(new Interceptor()));
                    }
                }).installOn(instrumentation);
    }
    catch (RuntimeException e) {
        System.out.println("Exception instrumenting code : "+e);
        e.printStackTrace();
    }

}



package com.mycompany.agent;

import java.lang.reflect.Method;
import java.util.concurrent.Callable;

import net.bytebuddy.implementation.bind.annotation.AllArguments;
import net.bytebuddy.implementation.bind.annotation.Origin;
import net.bytebuddy.implementation.bind.annotation.RuntimeType;
import net.bytebuddy.implementation.bind.annotation.SuperCall;

@SuppressWarnings("rawtypes")
public class Interceptor {

@RuntimeType
public Object intercept( @SuperCall Callable<?> callable, @AllArguments Object[] allArguments, @Origin Method method, @Origin Class clazz) throws Exception {
    long startTime = System.currentTimeMillis();
    Object response;
    try{
        response = callable.call();
    }
    catch(Exception e) {
        System.out.println("Exception occurred in method call: " + methodName(clazz, method, allArguments) + " Exception = " + e);
        throw e;
    }
    finally{
        System.out.println("Method " + methodName(clazz, method) + " completed in " + (System.currentTimeMillis() - startTime) + " miliseconds");
    }
    return response;
}

private String methodName(Class clazz, Method method){
    return methodName(clazz, method, null);
}

private String methodName(Class clazz, Method method, Object[] allArguments){
    StringBuilder builder = new StringBuilder();
    builder.append(clazz.getName());
    builder.append(".");
    builder.append(method.getName());
    builder.append("(");
    for(int i = 0; i < method.getParameters().length; i++) {

        builder.append(method.getParameters()[i].getName());
        if(allArguments != null) {
            Object arg = allArguments[i];
            builder.append("=");
            builder.append(arg != null ? arg.toString() : "null");              
        }

        if(i < method.getParameters().length - 1) {
            builder.append(", ");
        }
    }
    builder.append(")");
    return builder.toString();
}
like image 813
user1563817 Avatar asked Oct 26 '15 13:10

user1563817


1 Answers

Everything seems to be right. You should always try registering an AgentBuider.Listener which will expose stack traces of unsuccessful instrumentations if Byte Buddy causes an exception for signaling an illegal instrumentation attempt.

I assume that your class's package-private definition of your Interceptor is the cause of this exception. Your interceptor must be visible to all instrumented code. Otherwise, the class is not invokable.

like image 190
Rafael Winterhalter Avatar answered Sep 18 '22 09:09

Rafael Winterhalter