I'm trying to implement a Java Agent that can easily be added to the commandline of some of our legacy services to enable distributed tracing using the OpenTracing API. I'm using ByteBuddy
(1.9.12) and hitting NoClassDefFoundErrors
when the instrumented code runs for the following scenarios so far:
- instrumented a class in the
java.util.concurrent
package: can't find theGlobalTracer
since the Java Agent was only in the app classpath. I got around this temporarily by adding the Java Agent that includes the tracing APIs to the bootstrap classpath on the commandline (Java 8 only), but I'd like to be able to do that programmatically. Looking for the right way to do that. (Edit: this is solved by using theBoot-Class-Path
option in the manifest file of the Java Agent--just put the filename--no path--of the Java Agent JAR itself.) - instrumented a custom network client class: can't find a class in the custom network packages from my Intercept class (during byte code injection) since the network client packages aren't in the Java Agent JAR. I'm looking for the "right way" to structure things at a high level to avoid this.
Code for scenario 2:
public class NettyAgentRule implements AgentRule {
public Iterable<? extends AgentBuilder> buildAgent(AgentBuilder agentBuilder) {
return Arrays.asList(agentBuilder
.type(hasSuperType(named("org.jboss.netty.bootstrap.Bootstrap")))
.transform((builder, typeDescription, classLoader, module) -> {
return builder.visit(Advice.to(NettyAgentRule.class).on(named("setPipelineFactory")));
}));
}
@Advice.OnMethodEnter
public static void enter(final @Advice.Origin String origin,
final @Advice.This Object thiz,
@Advice.Argument(value = 0, readOnly = false, typing = Assigner.Typing.DYNAMIC) Object parameter) {
parameter = NettyAgentIntercept.enter(thiz, parameter);
}
}
where the Error happens in the NettyAgentIntercept class, which references Netty classes.
I've researched how the OpenTracing 'contrib' SpecialAgent handles these scenarios and it has quite a bit of custom classloading going on, which is tightly coupled to their build structure (JARs within the JAR following a naming convention). This would be nice to avoid if possible.
Example exception stack from scenario 2:
java.lang.NoClassDefFoundError: org/jboss/netty/channel/ChannelPipelineFactory
2019-07-05 18:52:21,10613 at com.example.tracing.netty.NettyAgentIntercept.enter(NettyAgentIntercept.java:9)
2019-07-05 18:52:21,10613 at org.jboss.netty.bootstrap.Bootstrap.setPipelineFactory(Bootstrap.java:251)
2019-07-05 18:52:21,10616 at com.example.lib.util.net.NettyCore.<init>(NettyCore.java:176)
2019-07-05 18:52:21,10616 at com.example.lib.util.net.NetClient.getInstance(NetClient.java:125)
2019-07-05 18:52:21,10617 at com.example.lib.util.net.NetClient.getInstance(NetClient.java:147)
2019-07-05 18:52:21,10617 at com.example.lib.util.net.NetClient.getInstance(NetClient.java:55)
2019-07-05 18:52:21,10617 at com.example.component.Component.main(Component.java:155)
2019-07-05 18:52:21,10618 at com.example.lib.util.invoke.ComponentThread.run(ComponentThread.java:21)
I have also tried the following code as recommended in
byte-buddy throws java.lang.ClassNotFoundException: javax.servlet.http.HttpServlet
but it results in the same sort of exception:
public Iterable<? extends AgentBuilder> buildAgent(AgentBuilder agentBuilder) {
return Arrays.asList(agentBuilder
.type(hasSuperType(named("org.jboss.netty.bootstrap.Bootstrap")))
.transform(new AgentBuilder.Transformer.ForAdvice()
.include(getClass().getClassLoader())
.advice(named("setPipelineFactory"),"com.example.tracing.netty.NettyAgentAdvice")
));
}
where the advice class is
package com.example.tracing.netty;
import org.jboss.netty.bootstrap.ClientBootstrap;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.ChannelPipelineFactory;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.implementation.bytecode.assign.Assigner;
public class NettyAgentAdvice {
@Advice.OnMethodEnter
public static void enter(final @Advice.Origin String origin,
final @Advice.This Object thiz,
@Advice.Argument(value = 0, readOnly = false, typing = Assigner.Typing.DYNAMIC) Object parameter) {
parameter = enter(thiz, parameter);
}
public static ChannelPipelineFactory enter(Object thiz, Object returned) {
ChannelPipelineFactory pipelineFactory = (ChannelPipelineFactory) returned;
if (thiz instanceof ClientBootstrap) {
return () -> {
ChannelPipeline pipeline = pipelineFactory.getPipeline();
if (pipeline.get(TracingHandler.class) == null) {
pipeline.addLast("tracing", new TracingHandler());
}
return pipeline;
};
}
return pipelineFactory;
}
}