Docs » µAPM Instrumentation Guide » Java, Scala, and Kotlin Instrumentation

Java, Scala, and Kotlin Instrumentation 🔗

Important

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

Automatic instrumentation with the SignalFx Java Agent 🔗

The easiest and simplest way to instrument your Java and JVM-based applications is to use our Java JVM agent. The Java agent 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).

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, download it to the local filesystem of the target application and 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...

Supported runtimes and languages 🔗

The SignalFx Java Agent is designed to work with Java runtimes version 8 or above.

It offers full support for the libraries and frameworks listed below in Java. Other JVM-based languages, such as Scala and Kotlin, are also supported but are not explicitly validated for compatibility with all instrumentations.

Supported libraries and frameworks 🔗

Our Java agent comes with several instrumentation libraries built in. 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.

The following libraries and frameworks are provided with auto-instrumentation:

Library Supported Versions Instrumentation name Notes
Akka HTTP 10.0.0+ akka-http, akka-http-server, akka-http-client  
Apache HTTP Client 4.0+ httpclient  
AWS SDK Client 1.11.0+ aws-sdk  
Cassandra (DataStax client) 3.0+ cassandra  
Couchbase Client 2.0.0+ couchbase  
DropWizard Views All dropwizard, dropwizard-view  
ElasticSearch Client 2+ elasticsearch  
gRPC (Client and Server) 1.5.0+ grpc  
java.net.HttpURLConnection All supported Java versions httpurlconnection  
Hibernate 3.5.0+ hibernate  
Hystrix 1.4.0+ hystrix  
JAX-RS Client 2.0.0+ jaxrs  
JDBC API All supported Java versions jdbc  
Jedis (Redis client) 1.4.0 - 2.x jedis  
Jersey 2.1+ jersey In tandem with JAX-RS Annotations
Jetty Server 6.0.0+, 8.0.0+ jetty  
JMS Messaging All supported Java versions jms  
JSP 7+ jsp  
Kafka Client 0.11.0.0+ kafka  
Lettuce (Redis Client) 5.0.0+ lettuce  
Java MDC All Disabled by default; enabled with -Dsignalfx.log.injection=true on invocation Injects signalfx.trace_id and signalfx.span_id into MDC contexts
Memcached (SpyMemcached) 2.10.0+ spymemcached  
Mongo Client 3.1+ mongo  
Mongo Async Client 3.3+ mongo  
Netty Client and Server 4.0+ netty  
OkHttp Client 3.0+ okhttp  
Play Web Framework 2.4+ play  
RabbitMQ Client 2.7.0+ rabbitmq  
Ratpack 1.4+ ratpack In beta; disabled by default
Reactor Core 3.1.0+ reactor-core  
Java Servlet 2+ servlet  
Spark Java 2.3+ sparkjava In beta; disabled by default
Spring Data 1.5.0+ spring-data  
Spring Web 4.0+ spring-web  
Spring WebFlux 5.0.0+ spring-webflux  
Vertx Web 3.5.0+ N/A This works through the Netty instrumentation

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.

Container/Kubernetes Deployment 🔗

When deploying the Java agent in a containerized or Kubernetes-run app, you need to configure the Java agent to send traces to an instance of the Smart Agent running on the same host or 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). This environment variable defaults to localhost, but it will be overridden in this case to point to the underlying Kubernetes 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).

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 examples show 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.28.0-sfx2</version>
   <scope>provided</scope>
</dependency>

Or to your Gradle config:

compileOnly group: 'com.signalfx.public', name: 'signalfx-trace-api', version: '0.28.0-sfx2'

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.