Search code examples
spring-data-jpahikaricpaws-xray

How do I set up AWS X-Ray SQL Instrumentation for Hikari based projects without using Tomcat JDBC?


The AWS Documentation for AWS X-Ray currently doesn't give any solutions for Java projects that do not use Tomcat JDBC.

In order to instrument database queries using spring-boot-data-jpa, you need to also include Tomcat JDBC as a dependency, and set up a Tomcat DataSource object along with your Hikari one, and include the XRay interceptor as a JDBC interceptor by either:

  • Adding it in your config using:

dataSource.setJdbcInterceptors("com.amazonaws.xray.sql.postgres.TracingInterceptor;");

  • As a property:

spring.datasource.jdbc-interceptors=com.amazonaws.xray.sql.postgres.TracingInterceptor

Gradle:

dependencies {
    ...
    implementation 'org.springframework.boot:spring-boot-starter'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'

    implementation "com.amazonaws:aws-java-sdk-core"
    implementation "com.amazonaws:aws-xray-recorder-sdk-core" // Required for core xray features
    implementation "com.amazonaws:aws-xray-recorder-sdk-spring" // Required for spring annotations
    implementation "com.amazonaws:aws-xray-recorder-sdk-sql-postgres" // required for db callouts

    implementation 'org.apache.tomcat:tomcat-jdbc:9.0.31'
    ...
}

Database Configuration (Spring):

    @Bean(name = "dataSource")
    @Primary
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource dataSource() {
        final org.apache.tomcat.jdbc.pool.DataSource dataSource = new org.apache.tomcat.jdbc.pool.DataSource();
        dataSource.setUsername(getUsername());
        dataSource.setPassword(getPassword());
        dataSource.setUrl(POSTGRES_URL_PREFIX
                + getHost()
                + ":" + getPort()
                + "/" + getName()
                + "?stringtype=unspecified");
        dataSource.setDriverClassName(getDriver());
        dataSource.setJdbcInterceptors("com.amazonaws.xray.sql.postgres.TracingInterceptor;");

        final HikariDataSource hikariDataSource = new HikariDataSource();
        hikariDataSource.setDataSource(dataSource);

        return hikariDataSource;
    }

I find this quite clunky, and I would rather not have to have Tomcat JDBC as an additional dependency if possible.

Is there no way around this without using Tomcat?

Other notes:

  • Spring Boot 2.1.7

  • Gradle 6.0.1

  • AWS SDK for Java 2.4.0


Solution

  • It turns out that someone else had this question. The AWS developers have been working on this in a feature branch that got merged in Novemeber 2019 for SDK version 2.3.0.

    I am yet to find any documentation for this new feature, so after some digging into the PR code, I managed to find that it's even more simple than the previous implementation.

    Simply add the following dependency instead of the postgres specific one to your build.gradle file (or similar):

        implementation "com.amazonaws:aws-java-sdk-core:2.4.0"
        implementation "com.amazonaws:aws-xray-recorder-sdk-core:2.4.0"
        implementation "com.amazonaws:aws-xray-recorder-sdk-sql:2.4.0"
    

    Then simply create a TracingDataSource object in your DataSource configuration, and pass it your original javax.sql.DataSource object. This should be done in whatever @Configuration annotated class you use to create your DataSource bean.

        @Bean(name = "dataSource")
        @Primary
        @ConfigurationProperties(prefix = "spring.datasource")
        public DataSource dataSource() {
            final DataSource dataSource = DataSourceBuilder
                    .create()
                    .username(getUsername())
                    .password(getPassword())
                    .url(POSTGRES_URL_PREFIX
                            + getHost()
                            + ":" + getPort()
                            + "/" + getName()
                            + "?stringtype=unspecified")
                    .driverClassName(getDriver())
                    .build();
    
            final TracingDataSource tracingDataSource = new TracingDataSource(dataSource);
            return tracingDataSource;
        }
    

    That's all there is to it. I really hope this helps someone as I spent many hours trying to get this to work without Tomcat, plus the AWS documentation doesn't help here either.