Docs » µAPM Instrumentation Guide » Java Instrumentation

Java Instrumentation

Important

Before you start instrumenting your applications, review the information in Instrumentation Overview.

If you are new to tracing in Java, we recommend starting out with our Java agent that will automatically instrument many common libraries in your Java application with no code changes or recompilation required (only a change to how the application is invoked) – hence we refer to that as auto-instrumentation.

Java agents are jar files that implement a premain method and generally are used to modify classes as they are loaded. They use the standard java.lang.instrument package to do this and require no prior modification of the target application’s source code.

Our Java agent for tracing works by injecting span start/finish calls into specific, commonly used Java classes dynamically as they are loaded by the JVM. The end result is much the same as if you had manually placed those span start/finish calls directly in the code, with some slight overhead upon application startup to do the class bytecode alterations.

To use our Java agent, simply download it to the local filesystem of the target application and then invoke your application with the -javaagent flag. Assuming that you have the Smart Agent setup on the same host already, the logical steps are as follows:

$ sudo curl -sSL -o /opt/signalfx-tracing.jar 'https://search.maven.org/remote_content?g=com.signalfx.public&a=signalfx-java-agent&v=LATEST&c=unbundled'

$ # Change this to the common name of your app
$ export SIGNALFX_SERVICE_NAME=my-app

$ java -javaagent:/opt/signalfx-tracing.jar ...other Java flags for your app...

Your app invocation will probably look different from this, but those are the logical steps.

Container/Kubernetes Deployment

When deploying the Java agent in a Kubernetes-run app, you need to configure the Java agent to send traces to an instance of the Smart Agent running on the same Kubernetes node. To do so, refer to the sample Kubernetes Deployment at Deploying on Kubernetes. The Java agent looks for the environment variable SIGNALFX_AGENT_HOST (which is the envvar used in the example Deployment), which defaults to localhost, but will be overridden in this case to point to the underlying K8s node itself.

Similar configuration applies to any containerized environment where the Smart Agent is not accessible over localhost from the application being traced. Just set SIGNALFX_AGENT_HOST to something besides localhost, or you can set SIGNALFX_ENDPOINT_URL to the full URL of the Smart Agent or Gateway (defaults to http://localhost:9080/v1/trace).

Built-in Instrumentations

Our Java agent comes with several instrumentation libraries built in. An up-to-date list is on the README for the Java agent Github repo.

Some of these instrumentations are disabled by default and must be enabled by specifying the JVM property flag -Dsignalfx.integration.INTEGRATION_NAME.enabled=true where INTEGRATION_NAME is the name of the integration as shown in the library table in the Java agent Github repo.

OpenTracing Contributed Instrumentations

For the most part, our Java agent provides its own instrumentation logic and does not rely on instrumentation contributed to the OpenTracing project. However, you are also free to use standard OpenTracing instrumentations if you want. An up-to-date list of OpenTracing Java libraries is available on Github. All of these instrumentations are compatible with manual instrumentation because they are OpenTracing-compatible, and thus will use the same span scope/context that manual instrumentation uses. Note that the use of these requires code changes to your application and are not installed automatically by the Java agent.

Manual Instrumentation (with the Java Agent)

The Java agent provides an OpenTracing-compatible tracer instance that is automatically configured upon startup. This tracer instance is available to your application code via the GlobalTracer class of the opentracing-utils artifact. Simply add the following to your Maven POM:

<dependency>
  <groupId>io.opentracing</groupId>
  <artifactId>opentracing-util</artifactId>
  <version>0.31.0</version>
  <scope>provided</scope>
</dependency>

Or to your Gradle config:

compileOnly group: 'io.opentracing', name: 'opentracing-util', version: '0.31.0'

The scope is provided in Maven and compileOnly in Gradle because that artifact is included in the Java agent and will be available to your application classes at runtime.

Our Java agent example shows the use of auto-instrumented libraries in conjunction with manually instrumented code.

For many non-trivial applications, it will be necessary to do some manual instrumentation to get a more cohesive picture of what is going on. For example, if your application interally uses a work queue with multiple pre-started worker threads arbitrarily accepting items, there is no way for the Java agent to generically keep track of the relationship between input and output items of the queue – you must link them manually by somehow propagating the span context between the two ends and ensure the span scope is closed and reactivated when work is stopped and started in a new thread. The manually instrumented code will seamlessly interleave with the auto-instrumented code (i.e. if you start a span in a function and within that function an auto-instrumented library is used, the generated spans will be part of the same trace).

Trace Annotation

If you want to automatically trace the execution of a particular method in a Java class, simply add the annotation com.signalfx.tracing.api.Trace to it, along with an optional operationName parameter to make the operation name on the generated span something different from the method name.

First you need the signalfx-trace-api artifact in your project dependency config:

<dependency>
   <groupId>com.signalfx.public</groupId>
   <artifactId>signalfx-trace-api</artifactId>
   <version>0.24.0-sfx0</version>
   <scope>provided</scope>
</dependency>

Or to your Gradle config:

compileOnly group: 'com.signalfx.public', name: 'signalfx-trace-api', version: '0.24.0-sfx0'

The scope is provided in Maven and compileOnly in Gradle because that artifact is included in the Java agent and will be available to your application classes at runtime.

Then simply add the annotation to any method that you want to be automatically wrapped in a span:

package mypackage;

// This will be automatically provided by the Java agent
import com.signalfx.tracing.api.Trace;

class MyClass {
    @Trace(operationName = "doSomething")
    public doSomething(int n) {
        // All logic executed here will be counted under the `doSomething` span, including child spans generated in this method.
    }
}

Cross-thread tracing

The Java agent includes support for keeping track of the span context across thread boundaries. This can be a bit tricky because it is generally undesirable for spans to be automatically tracked across threads because it is generally impossible for the Java agent to know whether a thread is logically intended to be part of the current operation, or whether it is intended for some background asynchronous task that is distinct from the current operation. Therefore, the agent requires an explicit marker on spans to make them automatically propagated when using Java’s standard concurrency tools (e.g. Executor). If you already have access to the current scope (e.g. from an activate() call), you simply set the async propagation flag on the span like this:

import com.signalfx.tracing.context.TraceScope;

// ...

    Span span = GlobalTracer.get().buildSpan("my-operation").start();
    try (Scope sc = GlobalTracer.get().scopeManager().activate(span, true)) {
        // The below cast will always work as long as you haven't set a custom tracer
        ((TraceScope) scope).setAsyncPropagation(true);
        // ... Dispatch work to a Java thread.
        // Any methods calls in the new thread will have their active scope set to the current one.
    }

Or, if you don’t have access to the scope in the code that determines whether the operation should be continued across threads, you can get it from the GlobalTracer:

import com.signalfx.tracing.context.TraceScope;

// ...

   // The below cast will work as long as you haven't set a custom tracer implementation and if there is a currently active span.
   // If the active scope could be null (i.e. no span has been activated in the current scope), you must guard the cast and set.
   ((TraceScope) GlobalTracer.get().scopeManager().active()).setAsyncPropagation(true);
   // ... Dispatch work to a Java thread using an Executor.
   // Any methods calls in the new thread will have their active scope set to the current one.

The com.signalfx.tracing.context.TraceScope class is provided by the agent JAR, but to make it work with your IDE you should add the Maven/Gradle dependency on signalfx-trace-api as described in Trace Annotations

If you do not set the async propagation flag, spans generated in different threads will be considered part of a different trace. You can always pass the Span instance across thread boundaries via parameters or closures and reactivate it manually in the thread using GlobalTracer.get().scopeManager().activate(Span span, boolean closeOnFinish). Just remember that Scope instances are not thread-safe – they should not even be passed between threads, even if externally synchronized.

Manual Instrumentation (without the Java agent)

To instrument your Java application without the Java agent, we recommend using the Jaeger Java tracer. Our backend and Smart Gateway will accept Jaeger’s Thrift-encoded spans over HTTP. We have an example application that shows the use of Jaeger in Java that provides detailed instructions and sample code.