Automatically instruments 3rd-party libraries in Java applications
Java SpecialAgent automatically instruments Java applications to produce trace events via the OpenTracing API. This file contains the operational instructions for the use of Java SpecialAgent.
When Java SpecialAgent attaches to an application, either statically or dynamically, it will automatically load the OpenTracing instrumentation plugins explicitly specified as dependencies in its POM.
Any exception that occurs during the execution of the bootstrap process will not adversely affect the stability of the target application. It is, however, possible that the instrumentation plugin code may result in exceptions that are not properly handled, and would destabilize the target application.
- The Java SpecialAgent must allow any Java instrumentation plugin available in
opentracing-contrib
to be automatically installable in applications that utilize a 3rd-party library for which an instrumentation plugin exists. - The Java SpecialAgent must automatically install the instrumentation plugin for each 3rd-party library for which a plugin exists, regardless in which
ClassLoader
the 3rd-party library is loaded. - The Java SpecialAgent must not adversely affect the runtime stability of the application on which it is intended to be used. This goal applies only to the code in the Java SpecialAgent, and cannot apply to the code of the instrumentation plugins made available in
opentracing-contrib
. - The Java SpecialAgent must support static and dynamic attach to applications running on JVM versions 1.7 to latest.
- The Java SpecialAgent must implement a lightweight test methodology that can be easily applied to a module that implements instrumentation for a 3rd-party plugin. This test must simulate:
- Launch the test in a process with the
-javaagent
vm argument that points to the Java SpecialAgent (in order to test automatic instrumentation functionality of theotarules.btm
file). - Elevate the test code to be executed from a custom
ClassLoader
that is disconnected from the systemClassLoader
(in order to test bytecode injection into an isolatedClassLoader
that cannot resolve classes on the system classpath). - Initialize a
MockTracer
asGlobalTracer
, and provide a reference to theTracer
instance in the test method for assertions with JUnit. The Java SpecialAgent must provide a means by which instrumentation plugins can be configured before use on a target application.
- Launch the test in a process with the
- The Java SpecialAgent is not designed to modify application code, beyond the installation of OpenTracing instrumentation plugins. For example, there is no facility for dynamically tracing arbitrary code.
The Java SpecialAgent is built with Maven, and produces 2 artifacts:
-
opentracing-specialagent-<version>.jar
This is the main artifact that contains within it all applicable instrumentation plugins from the
opentracing-contrib
project. This JAR can be specified as the-javaagent
target for static attach to an application. This JAR can also be executed, standalone, with an argument representing the PID of a target process to which it should dynamically attach. -
opentracing-specialagent-<version>-tests.jar
This is the test artifact that contains within it the
AgentRunner
, which is a JUnit runner class provided for testing ofotarules.btm
files in instrumentation plugins. This JAR does not contain within it any instrumentation plugins themselves, and is only intended to be applied to the test phase of the build lifecycle of a single instrumentation plugin implementation. This JAR can be used in the same manner as the main JAR, both for static and dynamic attach.
The Java SpecialAgent uses Java’s Instrumentation interface to transform the behavior of a target application. The entrypoint into the target application is performed via Java’s Agent convention. Java SpecialAgent supports both static and dynamic attach.
Statically attaching to a Java application involves the use of the -javaagent
vm argument at the time of startup of the target Java application. The following command can be used as an example:
java -javaagent:opentracing-specialagent.jar -jar myapp.jar
This command statically attaches Java SpecialAgent into the application in myapp.jar
.
Dynamically attaching to a Java application involves the use of a running application’s PID, after the application’s startup. The following commands can be used as an example:
-
Call this to obtain the
PID
of the target application:jps
-
For jdk1.8
java -Xbootclasspath/a:$JAVA_HOME/lib/tools.jar -jar opentracing-specialagent.jar <PID>
-
For jdk9+
java -jar opentracing-specialagent.jar <PID>
The Java SpecialAgent uses the JUnit Runner API to implement a lightweight test methodology that can be easily applied to modules that implement instrumentation for 3rd-party plugins. This runner is named AgentRunner
, and allows developers to implement tests using vanilla JUnit patterns, transparently providing the following behavior:
- Launch the test in a process with the
-javaagent
vm argument that points to the Java SpecialAgent (in order to test automatic instrumentation functionality of theotarules.btm
file). - Elevate the test code to be executed from a custom
ClassLoader
that is disconnected from the systemClassLoader
(in order to test bytecode injection into an isolatedClassLoader
that cannot resolve classes on the system classpath). - Initialize a
MockTracer
asGlobalTracer
, and provide a reference to theTracer
instance in the test method for assertions with JUnit.
The AgentRunner
is available in the test jar of the Java SpecialAgent module. It can be imported with the following dependency spec:
<dependency>
<groupId>io.opentracing.contrib</groupId>
<artifactId>opentracing-specialagent</artifactId>
<version>${project.version}</version>
<type>test-jar</type>
<scope>test</scope>
</dependency>
To use the AgentRunner
in a JUnit test class, provide the following annotation to the class in question:
@RunWith(AgentRunner.class)
In addition to the @RunWith
annotation, each method annotated with @Test
must declare a parameter of type MockTracer
, as such:
@Test
public void test(MockTracer tracer) {}
Similarly, each method annotated with @Before
, @After
, @BeforeClass
, and @AfterClass
must declare a parameter of type MockTracer
, as such:
@BeforeClass
public static void beforeClass(MockTracer tracer) {}
@AfterClass
public static void afterClass(MockTracer tracer) {}
@Before
public void before(MockTracer tracer) {}
@After
public void after(MockTracer tracer) {}
The MockTracer
class can be referenced by importing the following dependency spec:
<dependency>
<groupId>io.opentracing</groupId>
<artifactId>opentracing-mock</artifactId>
<version>${version.opentracing-mock}</version>
<scope>test</scope>
</dependency>
Upon execution of the test class, in either the IDE or with Maven, the AgentRunner
will execute each test method via the 3 step workflow described above.
The AgentRunner
can be configured via the @AgentRunner.Config(...)
annotation. The annotation supports the following properties:
debug
: If set totrue
,FINEST
level root logging will be enabled. Default:false
.verbose
: If set totrue
, Byteman verbose logging will be enabled. Default:false
.isolateClassLoader
: If set totrue
, tests will be run from aClassLoader
that is isolated from the systemClassLoader
. If set tofalse
, tests will be run from the systemClassLoader
. Default:true
.instrumenter
: The retransformation instrumentation manager (BYTEMAN
orBYTEBUDDY
). Default:BYTEBUDDY
.
The opentracing-contrib
repository contains 40+ OpenTracing instrumentation plugins for Java. Only a handful of these plugins are currently supported by SpecialAgent.
If you are interested in contributing to the SpecialAgent project by integrating support for existing plugins in the opentracing-contrib
repository, or by implementing a new plugin with support for SpecialAgent, the following guide is for you:...
The opentracing-contrib
repository contains instrumentation plugins for a wide variety of 3rd-party libraries, as well as Java standard APIs. The plugins instrument a 3rd-party library of interest by implementing custom library-specific hooks that integrate with the OpenTracing API. To see examples, explore projects named with the prefix java-... in the opentracing-contrib
repository.
The SpecialAgent uses Byteman to perform bytecode injection for the purpose of auto-instrumentation. Instrumentation scripts mustb e named otarules.btm
, and be placed as a resource in the default package. Please refer to the following scripts as examples:
- otarules.btm for OkHttp3
- otarules.btm for
java.util.Concurrent
- otarules.btm for JDBC
- otarules.btm for Java Web Servlet Filter
- otarules.btm for Mongo Driver
- otarules.btm for Apache Camel
The SpecialAgent has specific requirements for packaging of instrumentation plugins:
- If the library being instrumented is 3rd-party (i.e. it does not belong to the standard Java APIs), then the dependency artifacts for the library must be non-transitive (i.e. declared with
<scope>test</scope>
, or with<scope>provided</scope>
).- The dependencies for the 3rd-party libraries are not necessary when the plugin is applied to a target application, as the application must already have these dependencies for the plugin to be used.
- Declaring the 3rd-party libraries as non-transitive dependencies greatly reduces the size of the SpecialAgent package, as all of the instrumentation plugins as contained within it.
- If 3rd-party libraries are not declared as non-transitive, there is a risk that target applications may experience class loading exceptions due to inadvertant loading of incompatibile classes.
- Many of the currently implemented instrumentation plugins do not declare the 3rd-party libraries which they are instrumenting as non-transitive. In this case, an
<exclude>
tag must be specified for each 3rd-party artifact dependency when referring to the instrumentation plugin artifact. An example of this can be seen with the instrumentation plugin for the Mongo Driver here.
- The package must contain a
fingerprint.bin
file. This file provides the SpecialAgent with a fingerprint of the 3rd-party library that the plugin is instrumenting. This fingerprint allows the SpecialAgent to determine if the plugin is compatible with the relevant 3rd-party library in a target application.- To generate the fingerprint, it is first necessary to identify which Maven artifacts are intended to be fingerprinted. To mark an artifact to be fingerprinted, you must add
<optional>true</optional>
to the dependency's spec. Please see the pom.xml for OkHttp3 as an example. - Next, include the following plugin in the project's POM:
<plugin> <groupId>io.opentracing.contrib</groupId> <artifactId>specialagent-maven-plugin</artifactId> <version>0.0.1-SNAPSHOT</version> <executions> <execution> <goals> <goal>fingerprint</goal> </goals> <phase>generate-resources</phase> <configuration> <destFile>${project.build.directory}/generated-resources/fingerprint.bin</destFile> </configuration> </execution> </executions> </plugin>
- To generate the fingerprint, it is first necessary to identify which Maven artifacts are intended to be fingerprinted. To mark an artifact to be fingerprinted, you must add
- The package must contain a
dependencies.tgf
file. This file allows the SpecialAgent to distinguish instrumentation plugin dependency JARs from test JARs and API JARs. To generate this file, include the following plugin in the project's POM:<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-dependency-plugin</artifactId> <executions> <execution> <goals> <goal>tree</goal> </goals> <phase>generate-resources</phase> <configuration> <outputType>tgf</outputType> <outputFile>${project.build.directory}/generated-resources/dependencies.tgf</outputFile> </configuration> </execution> </executions> </plugin>
The SpecialAgent provides a convenient methodolofy for testing of the auto-instrumentation of plugins via AgentRunner
. Please refer to the section on Test Usage for instructions.
Instrumentation plugins must be explicitly packaged into the main JAR of the SpecialAgent. Please refer to the <id>deploy</id>
profile in the POM
for an example of the usage.
The SpecialAgent is built in 2 profiles:
-
The
default
profile is used for development of plugins. It builds and runs tests for each plugin, but does not include the plugins inopentracing-specialagent-<version>.jar
To run this profile:
mvn clean install
-
The
deploy
profile is used for packaging of plugins into theopentracing-specialagent-<version>.jar
. It builds each plugin, but does not run their tests. Once the build is finished, theopentracing-specialagent-<version>.jar
will contain the built plugins inside it.To run this profile:
mvn -Ddeploy clean install
Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.
Please make sure to update tests as appropriate.
This project is licensed under the Apache 2 License - see the LICENSE.txt file for details.