Menu
AWS X-Ray
Developer Guide

X-Ray SDK for Java Sample Application

A sample application that shows the use of the SDK to instrument incoming HTTP calls, DynamoDB SDK clients, and HTTP clients is available on GitHub. The app, eb-java-scorekeep, uses AWS Elastic Beanstalk features to create DynamoDB tables, compile Java code on-instance, and run the X-Ray daemon without any additional configuration.

The sample is an instrumented version of the Scorekeep project on AWSLabs. It includes a front-end web app, the API that it calls, and the DynamoDB tables that it uses to store data. All of the components are hosted in an AWS Elastic Beanstalk environment for portability and ease of deployment.

The xray branch of the application shows the use of filters, plugins, instrumented AWS SDK clients, HTTPClient, Annotations, SQL queries, and custom subsegments.

The sample application shows basic instrumentation in these files:

For instructions on using the sample application with X-Ray, see the getting started tutorial. In addition to the basic use of the X-Ray SDK for Java discussed in the tutorial, the sample also shows how to use the following features.

Manually Instrumenting AWS SDK Clients

The X-Ray SDK for Java automatically instruments all AWS SDK clients when you include the AWS SDK Instrumentor submodule in your build dependencies.

You can disable automatic client instrumentation by removing the Instrumentor submodule, which enables you to instrument some clients manually while ignoring others, or use different tracing handlers on different clients.

To illustrate support for instrumenting specific AWS SDK clients, the application passes a tracing handler to AmazonDynamoDBClientBuilder as a request handler in the user, game, and session model. This code change tells the SDK to instrument all calls to DynamoDB using those clients.

Example src/main/java/scorekeep/SessionModel.java – Manual AWS SDK Client Instrumentation

import com.amazonaws.xray.AWSXRay;
import com.amazonaws.xray.handlers.TracingHandler;

public class SessionModel {
  private AmazonDynamoDB client = AmazonDynamoDBClientBuilder.standard()
        .withRegion(Constants.REGION)
        .withRequestHandlers(new TracingHandler(AWSXRay.getGlobalRecorder()))
        .build();
  private DynamoDBMapper mapper = new DynamoDBMapper(client);

If you remove the AWS SDK Instrumentor submodule from project dependencies, only the manually instrumented AWS SDK clients appear in the service map.

Creating Additional Subsegments

In the user model class, the application manually creates subsegments to group all downstream calls made within the saveUser function and adds metadata.

Example src/main/java/scorekeep/UserModel.java - Custom Subsegments

import com.amazonaws.xray.AWSXRay;
import com.amazonaws.xray.entities.Subsegment;
...
    public void saveUser(User user) {
    // wrap in subsegment
    Subsegment subsegment = AWSXRay.beginSubsegment("## UserModel.saveUser");
    try {
      mapper.save(user);
    } catch (Exception e) {
      subsegment.addException(e);
      throw e;
    } finally {
      AWSXRay.endSubsegment();
    }
  }

Recording Annotations, Metadata, and User IDs

In the game model class, the application records Game objects in a metadata block each time it saves a game in DynamoDB. Separately, the application records game IDs in annotations for use with filter expressions.

Example src/main/java/scorekeep/GameModel.java -- Annotations and Metadata

import com.amazonaws.xray.AWSXRay;
import com.amazonaws.xray.entities.Segment;
import com.amazonaws.xray.entities.Subsegment;
...
  public void saveGame(Game game) throws SessionNotFoundException {
    // wrap in subsegment
    Subsegment subsegment = AWSXRay.beginSubsegment("## GameModel.saveGame");
    try {
      // check session
      String sessionId = game.getSession();
      if (sessionModel.loadSession(sessionId) == null ) {
        throw new SessionNotFoundException(sessionId);
      }
      Segment segment = AWSXRay.getCurrentSegment();
      subsegment.putMetadata("resources", "game", game);
      segment.putAnnotation("gameid", game.getId());
      mapper.save(game);
    } catch (Exception e) {
      subsegment.addException(e);
      throw e;
    } finally {
      AWSXRay.endSubsegment();
    }
  }

In the move controller, the application records user IDs with setUser. User IDs are recorded in a separate field on segments and are indexed for use with search.

Example src/main/java/scorekeep/MoveController.java – User ID

import com.amazonaws.xray.AWSXRay;
...
  @RequestMapping(value="/{userId}", method=RequestMethod.POST)
  public Move newMove(@PathVariable String sessionId, @PathVariable String gameId, @PathVariable String userId, @RequestBody String move) throws SessionNotFoundException, GameNotFoundException, StateNotFoundException, RulesException {
    AWSXRay.getCurrentSegment().setUser(userId);
    return moveFactory.newMove(sessionId, gameId, userId, move);
  }

Instrumenting Outgoing HTTP Calls

The user factory class shows how the application uses the X-Ray SDK for Java's version of HTTPClientBuilder to instrument outgoing HTTP calls.

Example src/main/java/scorekeep/UserFactory.java – HTTPClient Instrumentation

import com.amazonaws.xray.proxies.apache.http.HttpClientBuilder;

  public String randomName() throws IOException {
    CloseableHttpClient httpclient = HttpClientBuilder.create().build();
    HttpGet httpGet = new HttpGet("http://uinames.com/api/");
    CloseableHttpResponse response = httpclient.execute(httpGet);
    try {
      HttpEntity entity = response.getEntity();
      InputStream inputStream = entity.getContent();
      ObjectMapper mapper = new ObjectMapper();
      Map<String, String> jsonMap = mapper.readValue(inputStream, Map.class);
      String name = jsonMap.get("name");
      EntityUtils.consume(entity);
      return name;
    } finally {
      response.close();
    }
  }

If you currently use org.apache.http.impl.client.HttpClientBuilder, you can simply swap out the import statement for that class with one for com.amazonaws.xray.proxies.apache.http.HttpClientBuilder.

Instrumenting Calls to a PostgreSQL Database

The application-pgsql.properties file adds the X-Ray PostgreSQL tracing interceptor to the data source created in RdsWebConfig.java.

Example application-pgsql.properties – PostgreSQL Database Instrumentation

spring.datasource.continue-on-error=true
spring.jpa.show-sql=false
spring.jpa.hibernate.ddl-auto=create-drop
spring.datasource.jdbc-interceptors=com.amazonaws.xray.sql.postgres.TracingInterceptor
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQL94Dialect

Note

See Configuring Databases with Elastic Beanstalk in the AWS Elastic Beanstalk Developer Guide for details on how to add a PostgreSQL database to the application environment.

The X-Ray demo page in the xray branch includes a demo that uses the instrumented data source to generate traces that show information about the SQL queries that it generates. Navigate to the /#/xray path in the running application or choose Powered by AWS X-Ray in the navigation bar to see the demo page.

Choose Trace SQL queries to simulate game sessions and store the results in the attached database. Then, choose View traces in AWS X-Ray to see a filtered list of traces that hit the API's /api/history route.

Choose one of the traces from the list to see the timeline, including the SQL query.