Tracing Remote EJBs

Since Payara Server 5.2020.4

Introduced in Payara Server 5.2020.4, RMI calls to an EJB from a Java SE client will now have the active Span Context automatically propagated to the server, with the server-side span being created as a child of this client call.

Java SE Client Tracing

No additional setup is required on the client side over what is to be expected for making regular un-traced remote EJB calls - the requirements are the same: usage of payara-embedded-all or the appclient of a Payara Server instance.

No additional properties need to be specified when performing the Initial Context lookup either:

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import java.util.Properties;

...

Properties contextProperties = new Properties();
contextProperties.setProperty(Context.INITIAL_CONTEXT_FACTORY, "com.sun.enterprise.naming.SerialInitContextFactory");

try {
    Context context = new InitialContext(contextProperties);
    EjbRemote ejb = (EjbRemote) context.lookup("java:global/myRemoteEjb/Ejb");
} catch (NamingException ne) {
    logger.warning("Failed performing lookup:\n" + ne.getMessage());
}

Getting a Tracer Instance

Injection of an OpenTracing tracer is not supported on Java SE clients, so you must create an instance yourself and register it to the OpenTracing.io GlobalTracer. Manual registration of a tracer instance is only required if using a third-party tracer such as Zipkin or Jaeger - if using the built-in Payara Request Tracing service the registration will automatically happen during creation of the initial context.

The built-in Payara Request Tracing service does not support tracing of Java SE clients (though will still propagate the active span context to the server) - if you wish to trace the client itself you must set up and use a third-party tracer such as ZipKin or Jaeger. Details on setting these up can be found here.
import io.opentracing.Tracer;
import io.opentracing.util.GlobalTracer;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import java.util.Properties;

...

Properties contextProperties = new Properties();
contextProperties.setProperty(Context.INITIAL_CONTEXT_FACTORY, "com.sun.enterprise.naming.SerialInitContextFactory");

try {
    Context context = new InitialContext(contextProperties);
    EjbRemote ejb = (EjbRemote) context.lookup("java:global/myRemoteEjb/Ejb");

    Tracer tracer = GlobalTracer.get();
} catch (NamingException ne) {
    logger.warning("Failed performing lookup:\n" + ne.getMessage());
}

Extra information on creating a Tracer instance and registering it to the GlobalTracer can be found here, but in short it can be done like so (replacing CustomTracer with your desired implementation):

Tracer tracerImpl = new CustomTracer(...);
GlobalTracer.register(tracerImpl);

Starting a Span

The @Traced annotation is not supported on Java SE client methods, so spans must be started and finished manually. Note that the Span Context of the active span will be propagated to the server, so it is recommended that you use a try-with-resources to help ensure your Span Scope is the one you expect.

import io.opentracing.Scope;
import io.opentracing.Span;
import io.opentracing.Tracer;
import io.opentracing.util.GlobalTracer;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import java.util.Properties;

...

Properties contextProperties = new Properties();
contextProperties.setProperty(Context.INITIAL_CONTEXT_FACTORY, "com.sun.enterprise.naming.SerialInitContextFactory");

try {
    Context context = new InitialContext(contextProperties);
    EjbRemote ejb = (EjbRemote) context.lookup("java:global/myRemoteEjb/Ejb");

    Tracer tracer = GlobalTracer.get();

    try (Scope scope = tracer.buildSpan("ExecuteEjb").startActive(true)) {
        ejb.doTheThing();
    }
} catch (NamingException ne) {
    logger.warning("Failed performing lookup:\n" + ne.getMessage());
}

Once your span has been started, you can attach any desired baggage items ( String key:value pairs), and these will be propagated to the server along with the Span Context. These can then be retrieved within the EJB implementation hosted on Payara Server like so:

import io.opentracing.Span;
import io.opentracing.Tracer;

import javax.ejb.Stateless;
import javax.inject.Inject;

@Stateless
public class Ejb implements EjbRemote {

    @Inject
    Tracer tracer;

...

    Span activeSpan = tracer.activeSpan();
    if (activeSpan != null) {
        String myBaggageItem = activeSpan.getBaggageItem("myBaggageItem");
    }
Baggage items are attached to the Span you invoke setBaggageItem on, as well as any child spans, so you are not prevented from starting additional child spans on your EJB methods manually or via the @Traced annotation.