Java agent API: Overview of instrumenting external calls, CAT, messaging, datastore, web frameworks

New Relic's Java agent collects and reports information on web browser transactions and background tasks. The agent instruments supported frameworks automatically, without any need to modify your application code. However, if New Relic does not support your framework, you may need to do some custom instrumentation.

This document describes how to use the Java agent API to instrument external calls, messaging frameworks, cross application tracing (CAT), datastores, and web frameworks.

You can find detailed API documentation in the New Relic Java agent API Javadoc. To see the API in action, see Java agent API: Example application using custom instrumentation for cross application tracing (CAT) and external datastore calls. Additionally, check out the Java agent API guide for a goal-driven guide to using the Java agent API.

For best results when using the API, ensure that you have the latest Java agent release. Several APIs used in the examples require Java agent 3.36.0 or higher.

See Instrumentation by annotation to learn how to use annotations to instrument your application code.

External API

The External API enables applications to report external service calls to New Relic. This information shows up on the External services page in New Relic APM. To report HTTP external activity, simply create an instance of ExternalParameters using the HttpParameters builder, and call reportAsExternal(ExternalParameters parameters) on the traced method you wish to report.

This is illustrated in this example code:

External API
String library = "HttpClient"; // a user-recognizable name for the library that is being used
URI uri = request.getURI(); // the URI that is being requested
String operation = "execute"; // these are typically named after the method in the library that's being instrumented

// construct external parameters
ExternalParameters params = HttpParameters
    .library(library)
    .uri(uri)
    .operation(operation)
    .inboundHeaders(inboundHeaders)
    .build();

// report the current method as doing external activity
NewRelic.getAgent().getTracedMethod().reportAsExternal(params);

External parameters builders

There are several builders to create ExternalParameters:

These builders create the input parameter object for TracedMethod's reportAsExternal API call. These parameter objects are used for things like linking HTTP external calls via cross application tracing, tracing external calls to a datastore, tracing external calls to a datastore with additional slow query processing, as well as tracing calls between message producers and consumers.

All of the methods of this class have the potential to expose sensitive private information. Use caution when creating the arguments, paying particular attention to URIs and string values.

CAT API

The CAT (cross application tracing) API allows the New Relic Java agent to link transactions across applications monitored by New Relic. The API uses client and server wrappers that allow the agent to read headers from requests, and add headers to responses.

Client wrappers

For the agent to write outbound request headers in the client initiating the request, use the OutboundHeaders interface. For example:

OutbounderHeaders implementation
    class OutboundWrapper implements OutboundHeaders {
        private final HttpUriRequest delegate;

        // OutboundHeaders is implemented by delegating to the library's request object
        public OutboundWrapper(HttpUriRequest request) { 
            this.delegate = request; 
        }

        // This allows the agent to add the correct headers to the HTTP request
        @Override
        public void setHeader(String name, String value) {
            delegate.addHeader(name, value);
        }

        // New Relic CAT specifies different header names for HTTP and MESSAGE
        @Override
        public HeaderType getHeaderType() {
            return HeaderType.HTTP;
        }
    }

For the agent to read inbound response headers in the client receiving the response, implement the InboundHeaders. For example:

InboundHeaders implementation
    class InboundWrapper implements InboundHeaders {
        private final CloseableHttpResponse responseHeaders;
        // OutboundHeaders is implemented by delegating to the library's response object
        public InboundWrapper(CloseableHttpResponse responseHeaders) {
            this.responseHeaders = responseHeaders;        
        }

        // New Relic CAT specifies different header names for HTTP and MESSAGE
        @Override
        public HeaderType getHeaderType() {
            return HeaderType.HTTP;
        }

        // this allows the agent to read the correct headers from the HTTP response
       @Override
       public String getHeader(String name) {
           return responseHeaders.getFirstHeader(name).getValue();
       }
    }

Server wrappers

For the agent to get web request headers, you must extend the ExtendedRequest class:

Extend ExtendedRequest class
    // Extend ExtendedRequest class to create a wrapper for the Request object
    class RequestWrapper extends ExtendedRequest {
        private IHTTPSession session;

        public RequestWrapper(IHTTPSession session) {
            super();
            this.session = session;
        }

        @Override
        public String getRequestURI() {
            return session.getUri();
        }

        @Override
        public String getHeader(String name) {
            return session.getHeaders().get(name);
        }

        @Override
        public String getRemoteUser() {
            return null;
        }

        @SuppressWarnings("rawtypes")
        @Override
        public Enumeration getParameterNames() {
            return Collections.enumeration(session.getParms().keySet());
        }

        @Override
        public String[] getParameterValues(String name) {
            return new String[]{session.getParms().get(name)};
        }

        @Override
        public Object getAttribute(String name) {
            return null;
        }

        @Override
        public String getCookieValue(String name) {
            return null;
        }

        @Override
        public HeaderType getHeaderType() {
            return HeaderType.HTTP;
        }

        @Override
        public String getMethod() {
            return session.getMethod().toString();
        }
    }

For the agent to set the web response headers, implement the Response interface:

Response interface implementation
    // Implement Response interface to create a wrapper for the outgoing Response object
    public class ResponseWrapper implements com.newrelic.api.agent.Response {

        private final Response httpResponse;

        public ResponseWrapper(Response httpResponse) {
            this.httpResponse = httpResponse;
        }

        @Override
        public int getStatus() throws Exception {
            return 200;
        }

        @Override
        public String getStatusMessage() throws Exception {
            return null;
        }

        @Override
        public void setHeader(String name, String value) {
            httpResponse.addHeader(name, value);
        }

        @Override
        public String getContentType() {
            return "";
        }

        @Override
        public HeaderType getHeaderType() {
            return HeaderType.HTTP;
        }
    }

CAT implementation using wrappers

Using the wrapper objects described in the previous sections, you can enable the Java agent to do cross application tracing (CAT) on the client and server side. For example:

Cross application tracing: Client-side
     @Trace
     public int makeExternalCall(URI uri) throws IOException {
         String library = "HTTPClient";
         String operation = "Execute";
         HttpUriRequest request = RequestBuilder.get().setUri(uri).build();

         // Wrap the outbound Request object
         OutboundWrapper outboundHeaders = new OutboundWrapper(request);
         // Obtain a reference to the method currently being traced
         TracedMethod tracedMethod = NewRelic.getAgent().getTracedMethod();
         // Add headers for outbound external request
         tracedMethod.addOutboundRequestHeaders(outboundHeaders);

         CloseableHttpClient connection = HttpClientBuilder.create().build();
         CloseableHttpResponse response = connection.execute(request);

         // Wrap the incoming Response object
         InboundWrapper inboundHeaders = new InboundWrapper(response);
         // Create an input parameter object for a call to an external HTTP service
         ExternalParameters params = HttpParameters
             .library(library)
             .uri(uri)
             .operation(operation)
             .inboundHeaders(inboundHeaders)
             .build();

         // Report a call to an external HTTP service
         tracedMethod.reportAsExternal(params);

         return response.getStatusLine().getStatusCode();
    }

In this sample code, the agent is configured to report an external call using CAT on the client that is initiating the request. These steps can be summarized as follows:

  1. Implement OutboundHeaders and InboundHeaders using framework classes on the client.
  2. Use addOutboundRequestHeaders(OutboundHeaders outboundHeaders) to have the agent add appropriate headers to the outbound request.
  3. Create ExternalParameters object using HttpParameters builder and provide inbound response headers.

  4. Report as an external request using reportAsExternal(ExternalParameters params).
Cross application tracing: Server-side
    @Trace(dispatcher = true)
    @Override
    public Response serve(IHTTPSession session) {
        // Obtain a reference to the current Transaction
        Transaction tx = NewRelic.getAgent().getTransaction();
        // Set the name of the current transaction
        NewRelic.setTransactionName("Custom", "ExternalHTTPServer");

        // Wrap the Request object
        ExtendedRequest req = new RequestWrapper(session);

        // Set the request for the current transaction and convert it into a web transaction
        tx.setWebRequest(req);

        queryDB();
        Response res = newFixedLengthResponse("<html><body><h1>SuccessfulResponse</h1>\n</body></html>\n");

        // Set the response for the current transaction and convert it into a web transaction
        tx.setWebResponse(new ResponseWrapper(res));

        // Instruct the transaction to write the outbound response headers
        tx.addOutboundResponseHeaders();
        
        // Mark the time when the response left the server
        tx.markResponseSent();
        

        return res;
    }

In this sample code, the agent is configured to report an external call using CAT on the server that is responding to the request. These steps can be summarized as follows:

  1. Implement Response and extend the ExtendedRequest class​ using framework classes on the server.
  2. Use setWebRequest(ExtendedRequest request) and setWebResponse(Response response) to convert the transaction into a web transaction and together provide the agent with the inbound request headers and a place to record the outbound headers.
  3. Use addOutboundResponseHeaders() to have the agent add appropriate headers to the outbound response.
  4. Mark the end of the response with a call to markResponseSent().

Messaging API

The messaging API allows applications to report interactions with message queue brokers. It builds on top of the External API by providing the MessageConsumerParametersMessage and MessageConsumerParameters.

This API generates the necessary metrics to identify message broker interactions. The UI will use these metrics to display messaging data including segments in transactions with the appropriate action and count (message put, or message take), a dedicated messages tab in transaction traces, and more. Providing inbound and outbound headers to the API also allows the agent to add CAT headers, and record CAT metrics, which enables the UI to draw Service maps that show connections between applications.

The following example demonstrates how to instrument a fictional JMS library.

Messaging API implementation
public class MessageProducer {
     // instrument the method that puts messages on a queue
     @Trace
     public void sendMessageToQueue(Message message) {
          ExternalParameters messageProduceParameters = 
              MessageProduceParameters.library("JMS)"
                  .destinationType(DestinationType.NAMED_QUEUE)
                  .destinationName(message.getJMSDestination())
                  .outboundHeaders(new OutboundWrapper(message))
                  .build();

          NewRelic.getAgent().getTracedMethod().reportAsExternal(messageProduceParameters);
      }
}

To simplify things, the agent assumes that sendMessageToQueue always puts a message in a named queue. In reality, a message can be sent to different destination types, including named queues, temporary queues, topics, and temporary topics. The API provides an enum to report messages to different destination types: NAMED_QUEUE, TEMP_QUEUE, NAMED_TOPIC, TEMP_TOPIC. It's important to specify the appropriate destination type because the UI will display the names of named queues and named topics and will omit the names of temporary queues and temporary topics.

If the library is capable of transmitting CAT headers, an OutboundHeaders object will be provided to the API so that the agent can add CAT headers.

Message with CAT headers implementation
public class MessageConsumer {
    @Trace
    public Message messageReceive() {
        ExternalParameters messageConsumeParameters = 
            MessageConsumeParameters.library("JMS")
                .destinationType(DestinationType.NAMED_QUEUE)
                .destinationName(message.getJMSDestination())
                .inboundHeaders(new InboundWrapper(message))
                .build();
        NewRelic.getAgent().getTracedMethod().reportAsExternal(messageConsumeParameters);
        return message;
    }
}

Datastore API

When a traced method is reported as an external datastore call, the call is shown in the APM Databases page. Because datastores are external to the running application, the method is reported as datastore activity using the reportAsExternal(ExternalParameters params) method. The only difference is that a different builder, DatastoreParameters, is used to create the appropriate ExternalParameters object.

Datastore API implementation
    TracedMethod tracedMethod = NewRelic.getAgent().getTracedMethod();
    tracedMethod.reportAsExternal(
        DatastoreParameters
        .product("sqlite") // the datastore vendor
        .collection("test.db") // the name of the collection (or table for SQL databases)
        .operation("select") // the operation being performed, e.g. "SELECT" or "UPDATE" for SQL databases
        .instance("localhost", 8080) // the datastore instance information - generally can be found as part of the connection
        .databaseName("test.db") // may be null, indicating no keyspace for the command
        .build());

Datastore API: Slow query

This API call provides the same behavior as the Datastore API call and extends it to allow slow query information to be tracked. The same reportAsExternal(ExternalParameters params) method and builder are used, but with an additional builder method.

Datastore with slow query implementation

Creating the appropriate ExternalParameters object is illustrated below:

    //Reporting a method as doing datastore activity
    TracedMethod tracedMethod = NewRelic.getAgent().getTracedMethod();
    tracedMethod.reportAsExternal(
        DatastoreParameters
        .product("sqlite") // the datastore vendor
        .collection("test.db") // the name of the collection (or table for SQL databases)
        .operation("select") // the operation being performed, e.g. "SELECT" or "UPDATE" for SQL databases
        .instance("localhost", 8080) // the datastore instance information - generally can be found as part of the connection
        .databaseName("test.db") // may be null, indicating no keyspace for the command
        .slowQuery(rawQuery,QUERY_CONVERTER) 
        .build());

    private static QueryConverter<String> QUERY_CONVERTER = new QueryConverter<String>() {

        @Override
        public String toRawQueryString(String statement) {
            // Do work to transform raw query object to string
            return statement;
        }

        @Override
        public String toObfuscatedQueryString(String statement) {
            // Do work to remove any sensitive information here
            return obfuscateQuery(statement);
        }
    };

WebFrameworks API

The WebFrameworks API allows the agent to report additional identifying information about the application.

// Set the dispatcher name and version which is reported to APM.
// The dispatcherName is intended to represent the type of server that this
// application is running on such as: Tomcat, Jetty, Netty, etc.
NewRelic.setServerInfo(String dispatcherName, String version)

// Set the app server port which is reported to APM.
NewRelic.setAppServerPort(int port)

// Set the instance name in the environment. 
// A single host:port may support multiple JVM instances. 
// The instance name is intended to help identify a specific JVM instance.
NewRelic.setInstanceName(String instanceName)

These values can be set only once. Subsequent calls will have no effect.

WebFrameworks API implementation
  public NewRelicApiClient() throws IOException, URISyntaxException {
      super(8080);

      // Set Dispatcher name and version.
      NewRelic.setServerInfo("NanoHttp", "2.3.1");

      // Set Appserver port for jvm identification
      NewRelic.setAppServerPort(8080);

      // Set JVM instance name
      NewRelic.setInstanceName("Client:8080");

      start(NanoHTTPD.SOCKET_READ_TIMEOUT, false);
      System.out.println("\nRunning on http://localhost:8080/ \n");
  }

For more help

Join the discussion about Java monitoring in the New Relic Online Technical Community! The Technical Community is a public platform to discuss and troubleshoot your New Relic toolset.

If you need additional help, get support at support.newrelic.com.