Java agent API: Instrumenting example app for external datastore calls and CAT

This document demonstrates using the New Relic Java agent API to instrument a simple client and server application. The instrumentation has these goals:

See the Java agent API Javadoc for full descriptions of the available API classes and methods.

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.

Client-side example

Here is an example of the client-side code for a simple client-server application:

package com.newrelic.example;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;

// New Relic API imports
import com.newrelic.api.agent.ExternalParameters;
import com.newrelic.api.agent.HeaderType;
import com.newrelic.api.agent.HttpParameters;
import com.newrelic.api.agent.InboundHeaders;
import com.newrelic.api.agent.NewRelic;
import com.newrelic.api.agent.OutboundHeaders;
import com.newrelic.api.agent.Trace;
import com.newrelic.api.agent.TracedMethod;

import fi.iki.elonen.NanoHTTPD;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.methods.RequestBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;

public class NewRelicApiExample extends NanoHTTPD {

    public NewRelicApiExample() 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");

        start(NanoHTTPD.SOCKET_READ_TIMEOUT, false);
        System.out.println("Running at: http://localhost:8080/");
    }

    public static void main(String[] args) throws URISyntaxException {
        try {
            new NewRelicApiExample();
        } catch (IOException ioe) {
            System.err.println("Unable to start the server:\n" + ioe);
        }
    }

    @Trace(dispatcher = true)
    @Override
    public Response serve(IHTTPSession session) {
        URI uri = null;
        int status = 0;

        try {
            createDB();
            Thread.sleep(1000);
            uri = new URI("http://localhost:8081");
            status = makeExternalCall(uri);
        } catch (URISyntaxException | InterruptedException | IOException e) {
            e.printStackTrace();
        }

        if (status == 200) {
            return newFixedLengthResponse("<html><body><h1>Successful Response</h1>\n</body></html>\n");
        } else {
            return newFixedLengthResponse("<html><body><h1>Error\n" + status + "</h1>\n</body></html>\n");
        }
    }

    @Trace
    public int makeExternalCall(URI uri) throws IOException {
        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("HttpClient")
            .uri(uri)
            .procedure("execute")
            .inboundHeaders(inboundHeaders)
            .build();

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

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

    // Implement OutboundHeaders interface to create a wrapper for the outgoing Request headers
    class OutboundWrapper implements OutboundHeaders {
        private final HttpUriRequest delegate;

        public OutboundWrapper(HttpUriRequest request) {
            this.delegate = request;
        }

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

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

    // Implement InboundHeaders interface to create a wrapper for the incoming Response headers
    class InboundWrapper implements InboundHeaders {
        private final CloseableHttpResponse responseHeaders;

        public InboundWrapper(CloseableHttpResponse requestHeaders) {
            this.responseHeaders = requestHeaders;
        }

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

        @Override
        public String getHeader(String name) {
            return responseHeaders.getFirstHeader(name).getValue();
        }
    }

    @Trace
    public void createDB() {
        Connection c = null;
        Statement stmt = null;

        try {
            Class.forName("org.sqlite.JDBC");
            c = DriverManager.getConnection("jdbc:sqlite:/tmp/test.db");
            System.out.println("Opened database successfully");

            stmt = c.createStatement();

            String dropSql = "DROP TABLE IF EXISTS COMPANY;";
            stmt.executeUpdate(dropSql);

            String sql = "CREATE TABLE COMPANY " +
                    "(ID INT PRIMARY KEY     NOT NULL," +
                    " NAME           TEXT    NOT NULL, " +
                    " AGE            INT     NOT NULL, " +
                    " ADDRESS        CHAR(50), " +
                    " SALARY         REAL)";
            stmt.executeUpdate(sql);

            sql = "INSERT INTO COMPANY (ID,NAME,AGE,ADDRESS,SALARY) " +
                   "VALUES (1, 'Paul', 32, 'California', 20000.00 );";
            stmt.executeUpdate(sql);

            sql = "INSERT INTO COMPANY (ID,NAME,AGE,ADDRESS,SALARY) " +
                    "VALUES (2, 'Allen', 25, 'Texas', 15000.00 );";
            stmt.executeUpdate(sql);

            sql = "INSERT INTO COMPANY (ID,NAME,AGE,ADDRESS,SALARY) " +
            "VALUES (3, 'Teddy', 23, 'Norway', 20000.00 );";
            stmt.executeUpdate(sql);

            sql = "INSERT INTO COMPANY (ID,NAME,AGE,ADDRESS,SALARY) " +
                    "VALUES (4, 'Mark', 25, 'Rich-Mond ', 65000.00 );";
            stmt.executeUpdate(sql);

            stmt.close();
            c.close();
        } catch (Exception e) {
            System.err.println(e.getClass().getName() + ": " + e.getMessage());
            System.exit(0);
        }
    }
}

Here is the same client app code divided into sections that describe how the API is used:

This section calls the Java agent API imports used to add cross application tracing to the client application later in the example code.

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;

// New Relic API imports
import com.newrelic.api.agent.ExternalParameters;
import com.newrelic.api.agent.HeaderType;
import com.newrelic.api.agent.InboundHeaders;
import com.newrelic.api.agent.NewRelic;
import com.newrelic.api.agent.OutboundHeaders;
import com.newrelic.api.agent.Trace;
import com.newrelic.api.agent.TracedMethod;

import fi.iki.elonen.NanoHTTPD;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.methods.RequestBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;

This section starts up the client server on port 8080 and uses the NewRelic class from the API to call the methods setServerInfo, setAppServerPort, and setInstanceName. These API calls affect what is shown in the New Relic UI.

    public NewRelicApiExample() 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");

        start(NanoHTTPD.SOCKET_READ_TIMEOUT, false);
        System.out.println("Running at: http://localhost:8080/");
    }

    public static void main(String[] args) throws URISyntaxException {
        try {
            new NewRelicApiExample();
        } catch (IOException ioe) {
            System.err.println("Unable to start the server:\n" + ioe);
        }
    }
    

This method creates a sample database, sleeps the thread, and makes an external call to the server app listening on port 8081. The @Trace(dispatcher = true) annotation tells the agent to start a new transaction when the serve method is called, if it is not called as part of an existing transaction (and in this case, it is not). If it were called as part of an existing transaction, it would simply be included as part of that transaction rather than start a new one.

    @Trace(dispatcher = true)
    @Override
    public Response serve(IHTTPSession session) {
        URI uri = null;
        int status = 0;

        try {
            createDB();
            Thread.sleep(1000);
            uri = new URI("http://localhost:8081");
            status = makeExternalCall(uri);
        } catch (URISyntaxException | InterruptedException | IOException e) {
            e.printStackTrace();
        }

        if (status == 200) {
            return newFixedLengthResponse("<html><body><h1>Successful Response</h1>\n</body></html>\n");
        } else {
            return newFixedLengthResponse("<html><body><h1>Error\n" + status + "</h1>\n</body></html>\n");
        }
    }
    

This section contains the code that initiates cross application tracing to the application making the request. The @Trace annotation tells the agent to track this method as part of an existing transaction as started by the serve method.

The request object is wrapped by a class that implements the Java agent API's OutboundHeaders interface, which ensures that the proper HeaderType is set (in this case HTTP). A call to addOutboundRequestHeaders adds the headers to the request and the request is sent to the server.

When the response returns it is wrapped by a class implementing the Java agent API's InboundHeaders interface. The inboundHeaders, along with the "library", URI, and "procedure" arguments, are used to build an HttpParameters object. The params object is then passed as an argument to the reportAsExternal method, which reports the TracedMethod as an external HTTP call.

    @Trace
    public int makeExternalCall(URI uri) throws IOException {
        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("HttpClient")
            .uri(uri)
            .procedure("execute")
            .inboundHeaders(inboundHeaders)
            .build();

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

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

An implementation of the Java agent API's OutboundHeaders Interface is used to wrap the request object of the client server, which in this example is of type HttpUriRequest. The request is passed into the constructor of the OutboundWrapper class and implementations of the setHeader and getHeaderType methods are provided.

The getHeaderType method returns a HeaderType enum that can either be HeaderType.HTTP or HeaderType.MESSAGE, as defined by the Java agent API. In this example the external call protocol is HTTP, so HeaderType.HTTP is returned.

    // Implement OutboundHeaders interface to create a wrapper for the outgoing Request headers
    class OutboundWrapper implements OutboundHeaders {
        private final HttpUriRequest delegate;

        public OutboundWrapper(HttpUriRequest request) {
            this.delegate = request;
        }

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

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

An implementation of the Java agent API's InboundHeaders Interface is used to wrap the response object returned to the client server, which in this example is of type CloseableHttpResponse. The response is passed into the constructor of the InboundWrapper class, where implementations of the getHeaderType and getHeader methods are provided.

The getHeaderType method returns a HeaderType enum that can either be HeaderType.HTTP or HeaderType.MESSAGE, as defined by the Java agent API. In this example the external call protocol is HTTP so HeaderType.HTTP is returned.

    // Implement InboundHeaders interface to create a wrapper for the incoming Response headers
    class InboundWrapper implements InboundHeaders {
        private final CloseableHttpResponse responseHeaders;

        public InboundWrapper(CloseableHttpResponse requestHeaders) {
            this.responseHeaders = requestHeaders;
        }

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

        @Override
        public String getHeader(String name) {
            return responseHeaders.getFirstHeader(name).getValue();
        }
    }
    

This method simply creates an example SQLite database. The @Trace annotation tells the agent to track this method as part of an existing transaction as started by the serve method.

    @Trace
    public void createDB() {
        Connection c = null;
        Statement stmt = null;

        try {
            Class.forName("org.sqlite.JDBC");
            c = DriverManager.getConnection("jdbc:sqlite:/tmp/test.db");
            System.out.println("Opened database successfully");

            stmt = c.createStatement();

            String dropSql = "DROP TABLE IF EXISTS COMPANY;";
            stmt.executeUpdate(dropSql);

            String sql = "CREATE TABLE COMPANY " +
                    "(ID INT PRIMARY KEY     NOT NULL," +
                    " NAME           TEXT    NOT NULL, " +
                    " AGE            INT     NOT NULL, " +
                    " ADDRESS        CHAR(50), " +
                    " SALARY         REAL)";
            stmt.executeUpdate(sql);

            sql = "INSERT INTO COMPANY (ID,NAME,AGE,ADDRESS,SALARY) " +
                   "VALUES (1, 'Paul', 32, 'California', 20000.00 );";
            stmt.executeUpdate(sql);

            sql = "INSERT INTO COMPANY (ID,NAME,AGE,ADDRESS,SALARY) " +
                    "VALUES (2, 'Allen', 25, 'Texas', 15000.00 );";
            stmt.executeUpdate(sql);

            sql = "INSERT INTO COMPANY (ID,NAME,AGE,ADDRESS,SALARY) " +
            "VALUES (3, 'Teddy', 23, 'Norway', 20000.00 );";
            stmt.executeUpdate(sql);

            sql = "INSERT INTO COMPANY (ID,NAME,AGE,ADDRESS,SALARY) " +
                    "VALUES (4, 'Mark', 25, 'Rich-Mond ', 65000.00 );";
            stmt.executeUpdate(sql);

            stmt.close();
            c.close();
        } catch (Exception e) {
            System.err.println(e.getClass().getName() + ": " + e.getMessage());
            System.exit(0);
        }
    }
    

Server-side example

Here is the server-side code for this example application:

package com.newrelic.example;

import java.io.IOException;
import java.net.URISyntaxException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Collections;
import java.util.Enumeration;

// New Relic API imports
import com.newrelic.api.agent.DatastoreParameters;
import com.newrelic.api.agent.ExtendedRequest;
import com.newrelic.api.agent.HeaderType;
import com.newrelic.api.agent.NewRelic;
import com.newrelic.api.agent.Trace;
import com.newrelic.api.agent.TracedMethod;
import com.newrelic.api.agent.Transaction;

import fi.iki.elonen.NanoHTTPD;

public class NewRelicApiServer extends NanoHTTPD {

    public NewRelicApiServer() throws IOException, URISyntaxException {
        super(8081);

        // Set Dispatcher name and version
        NewRelic.setServerInfo("NanoHttp", "2.3.1");
        // Set Appserver port for jvm identification
        NewRelic.setAppServerPort(8081);
        // Set JVM instance name
        NewRelic.setInstanceName("Server");

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

    public static void main(String[] args) throws URISyntaxException {
        try {
            new NewRelicApiServer();
        } catch (IOException ioe) {
            System.err.println("Unable to start the server:\n" + ioe);
        }
    }

    @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;
    }

    // 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;
        }
    }

    // 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.toLowerCase());
        }

        @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();
        }
    }

    @Trace
    public void queryDB() {
        Connection c = null;
        Statement stmt = null;
        try {
            Class.forName("org.sqlite.JDBC");
            c = DriverManager.getConnection("jdbc:sqlite:/tmp/test.db");
            c.setAutoCommit(false);
            System.out.println("Opened database successfully");

            stmt = c.createStatement();
            ResultSet rs = stmt.executeQuery("SELECT * FROM COMPANY;");
            while (rs.next()) {
                int id = rs.getInt("id");
                String name = rs.getString("name");
                int age = rs.getInt("age");
                String address = rs.getString("address");
                float salary = rs.getFloat("salary");
                System.out.println("ID = " + id);
                System.out.println("NAME = " + name);
                System.out.println("AGE = " + age);
                System.out.println("ADDRESS = " + address);
                System.out.println("SALARY = " + salary);
                System.out.println();
            }
            rs.close();
            stmt.close();
            c.close();
        } catch (Exception e) {
            System.err.println(e.getClass().getName() + ": " + e.getMessage());
            System.exit(0);
        }
        // Obtain a reference to the method currently being traced
        TracedMethod method = NewRelic.getAgent().getTracedMethod();

        // Create a DatastoreParameters object and report a call to an external datastore service
        method.reportAsExternal(
                DatastoreParameters
                        .product("sqlite")
                        .collection("test.db")
                        .operation("select")
                        .instance("localhost", 8080)
                        .databaseName("test.db")
                        .build());
    }
}

Here is the same example server code broken into sections that describe how the API is used:

This section shows the relevant Java agent API imports needed to add cross application tracing and reporting of external datastore calls to the server application.

import java.io.IOException;
import java.net.URISyntaxException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Collections;
import java.util.Enumeration;

// New Relic API imports
import com.newrelic.api.agent.DatastoreParameters;
import com.newrelic.api.agent.ExtendedRequest;
import com.newrelic.api.agent.HeaderType;
import com.newrelic.api.agent.NewRelic;
import com.newrelic.api.agent.Trace;
import com.newrelic.api.agent.TracedMethod;
import com.newrelic.api.agent.Transaction;

import fi.iki.elonen.NanoHTTPD;
    

This section starts up the server on port 8081 and uses the NewRelic class from the API to call the methods setServerInfo, setAppServerPort, and setInstanceName. These API calls affect what is shown in the New Relic APM UI.

    public NewRelicApiServer() throws IOException, URISyntaxException {
        super(8081);

        // Set Dispatcher name and version
        NewRelic.setServerInfo("NanoHttp", "2.3.1");
        // Set Appserver port for jvm identification
        NewRelic.setAppServerPort(8081);
        // Set JVM instance name
        NewRelic.setInstanceName("Server");

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

    public static void main(String[] args) throws URISyntaxException {
        try {
            new NewRelicApiServer();
        } catch (IOException ioe) {
            System.err.println("Unable to start the server:\n" + ioe);
        }
    }
    

The @Trace(dispatcher = true) annotation tells the agent to start a new transaction when the serve method is called if it is not called as part of an existing transaction (which in this case, it is not). If it were called as part of an existing transaction, it would simply be included as part of that transaction rather than start a new one.

A reference to the current Transaction is obtained via a call to getTransaction and the name of the transaction is set via a call to the setTransactionName method.

The request object, which in this example is of type IHTTPSession, is then wrapped using a class extending the Java agent API's ExtendedRequest class. The current Transaction is then converted to a web transaction via a call to setWebRequest which takes the wrapped ExtendedRequest as an argument.

A call to the database is then made and the response object is generated and wrapped by a class implementing the Java agent API's Response interface. The wrapped response object is passed as an argument to setWebResponse which converts the current Transaction into a web transaction.

Once the web request and response are set, a call to addOutboundResponseHeaders adds the headers to the response. This must be called after setWebRequest and setWebResponse, which together provide the agent with the inbound request headers and a place to record the outbound headers.

A call to markResponseSent marks the time when the last byte of the response left the server as the current timestamp.

    @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;
    }
    

An implementation of the Java agent API's Response Interface is used to wrap the response object of the server, which in this example is of type fi.iki.elonen.NanoHTTPD.Response. The response is passed into the constructor of the ResponseWrapper class and implementations of the getStatus, getStatusMessage, setHeader, getContentType and getHeaderType methods are provided.

The getHeaderType method returns a HeaderType enum that can either be HeaderType.HTTP or HeaderType.MESSAGE, as defined by the Java agent API. In this example the external call protocol is HTTP, so HeaderType.HTTP is returned.

    // 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;
        }
    }
    

A subclass of the Java agent API's ExtendedRequest class is used to wrap the request object of the server, which in this example is of type IHTTPSession. The request is passed into the constructor of the RequestWrapper class, which provides implementations of the getRequestURI, getHeader, getRemoteUser, getParameterNames, getParameterValues, getAttribute, getCookieValue, getHeaderType, and getMethod methods.

The getHeaderType method returns a HeaderType enum that can either be HeaderType.HTTP or HeaderType.MESSAGE, as defined by the Java agent API. In this example the external call protocol is HTTP so HeaderType.HTTP is returned.

    // 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.toLowerCase());
        }

        @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();
        }
    }
    

This method makes an external call to the SQLite database that is created by the client. The @Trace annotation tells the agent to track this method as part of an existing transaction as started by the serve method.

A reference to the current TracedMethod is obtained via a call to getTracedMethod. A DatastoreParameters object is then created using the builder pattern. The ExternalParameters object is then passed as an argument to the reportAsExternal method, which has the effect of reporting the TracedMethod as an external datastore call.

    @Trace
    public void queryDB() {
        Connection c = null;
        Statement stmt = null;
        try {
            Class.forName("org.sqlite.JDBC");
            c = DriverManager.getConnection("jdbc:sqlite:/tmp/test.db");
            c.setAutoCommit(false);
            System.out.println("Opened database successfully");

            stmt = c.createStatement();
            ResultSet rs = stmt.executeQuery("SELECT * FROM COMPANY;");
            while (rs.next()) {
                int id = rs.getInt("id");
                String name = rs.getString("name");
                int age = rs.getInt("age");
                String address = rs.getString("address");
                float salary = rs.getFloat("salary");
                System.out.println("ID = " + id);
                System.out.println("NAME = " + name);
                System.out.println("AGE = " + age);
                System.out.println("ADDRESS = " + address);
                System.out.println("SALARY = " + salary);
                System.out.println();
            }
            rs.close();
            stmt.close();
            c.close();
        } catch (Exception e) {
            System.err.println(e.getClass().getName() + ": " + e.getMessage());
            System.exit(0);
        }
        // Obtain a reference to the method currently being traced
        TracedMethod method = NewRelic.getAgent().getTracedMethod();

        // Create a DatastoreParameters object and report a call to an external datastore service
        method.reportAsExternal(
                DatastoreParameters
                        .product("sqlite")
                        .collection("test.db")
                        .operation("select")
                        .instance("localhost", 8080)
                        .databaseName("test.db")
                        .build());
    }
    

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.