OpenCensus is being archived! Read the blog post to learn more

Java

Introduction

The Java Database Connectivity(JDBC) API provides universal data access from the Java programming language as documented at https://docs.oracle.com/javase/8/docs/technotes/guides/jdbc/

Using OpenCensus, we’ve combined the best of both worlds: observability with distributed tracing and metrics to empower Java developers that use any sort of database/data source with a JDBC driver.

ocjdbc is a type-4 JDBC wrapper for the Java language. We’ve instrumented it with OpenCensus to provide observability with tracing and metrics. It works by wrapping your already obtained JDBC Connection using the class ocjdbc.OcWrapConnection. It wraps any JDBC driver. It is hosted on our integrations page on Github at https://github.com/opencensus-integrations/ocjdbc but also distributed as Maven, Gradle, Ivy and Builder artifacts as you’ll shortly see below.

Installing it

<dependency>
    <groupId>io.orijtech.integrations</groupId>
    <artifactId>ocjdbc</artifactId>
    <version>0.0.6</version>
</dependency>
// https://mvnrepository.com/artifact/io.orijtech.integrations/ocjdbc
compile group: 'io.orijtech.integrations', name: 'ocjdbc', version: '0.0.6'
<!-- https://mvnrepository.com/artifact/io.orijtech.integrations/ocjdbc -->
<dependency org="io.orijtech.integrations" name="ocjdbc" rev="0.0.6"/>
# https://mvnrepository.com/artifact/io.orijtech.integrations/ocjdbc
'io.orijtech.integrations:ocjdbc:jar:0.0.6'

Using it

Using it simply requires you to just wrap your already created JDBC connection and it wraps every method to provide observability by metrics and tracing. For example

import io.orijtech.integrations.ocjdbc.OcWrapConnection;
import io.orijtech.integrations.ocjdbc.Observability;

public static void main(String ...args) {
    // Load and use the MySQL Connector/J driver.
    Class.forName("com.mysql.cj.jdbc.Driver").newInstance();

    java.sql.Connection originalConn =
        java.sql.DriverManager.getConnection(
            "jdbc:mysql://localhost/test?useSSL=false&serverTimezone=UTC");

    // Then create/wrap it with the instrumented Connection from "io.orijtech.integrations.ocjdbc".
    java.sql.Connection conn =
          new OcWrapConnection(originalConn, EnumSet.of(Observability.TraceOption.NONE));

    // Use conn like you would normally below as per your original program
}

// Enabling observability by subscribing to the views
// and then creating the exporters is necessary too.
public static void enableObservability() {
    // Enable metrics with OpenCensus.
    Observability.registerAllViews();

    // Then create the trace and metrics exporters here
}

Annotating traces with the various SQL statements

We also provide an option for your spans to be annotated with the SQL that accompanies an exec*.

However, please note that this is optional and could be a security concern due to Personally Identifiable Information(PII) being used in the SQL query.

This option is available via TraceOption of Observability.ANNOTATE_TRACES_WITH_SQL which is passed into the constructors for: * OcWrapConnection * OcWrapCallableStatement * OcWrapPreparedStatement * OcWrapStatement

thus when used to create the wrapped java.sql.Connection:

      java.sql.Connection conn =
          new OcWrapConnection(
              originalConn,
              // And passing this option to allow the spans
              // to be annotated with the SQL queries.
              // Please note that this could be a security concern
              // since it could reveal personally identifying information.
              EnumSet.of(Observability.TraceOption.ANNOTATE_TRACES_WITH_SQL));

Enabling OpenCensus

To enable observability with OpenCensus, you need to have enabled trace and metrics exporters e.g.

import io.opencensus.exporter.trace.jaeger.JaegerTraceExporter;
import io.opencensus.exporter.stats.prometheus.PrometheusStatsCollector;
import io.prometheus.client.exporter.HTTPServer;
import io.opencensus.trace.samplers.Samplers;
import io.opencensus.common.Scope;
import io.opencensus.trace.Tracer;
import io.opencensus.trace.Tracing;
import io.opencensus.trace.config.TraceConfig;

public static void enableObservability() throws Exception {
    // Enable metrics with OpenCensus.
    Observability.registerAllViews();

    TraceConfig traceConfig = Tracing.getTraceConfig();
    // For demo purposes, lets always sample.
    traceConfig.updateActiveTraceParams(
            traceConfig.getActiveTraceParams().toBuilder().setSampler(Samplers.alwaysSample()).build());

    // The trace exporter.
    JaegerTraceExporter.createAndRegister("http://127.0.0.1:14268/api/traces", "ocjdbc-demo");

    // The metrics exporter.
    PrometheusStatsCollector.createAndRegister();

    // Run the server as a daeon on address "localhost:8889".
    HTTPServer server = new HTTPServer("localhost", 8889);
}

End to end example

In this example, we’ll just wrap a MySQL Connector/J app as below. Please place the file in src/main/java/io/opencensus/tutorial/ocjdbc/App.java. It uses exporters:

Source code

// Please place the file in: src/main/java/io/opencensus/tutorial/ocjdbc/App.java
package io.opencensus.tutorial.ocjdbc;

import io.opencensus.common.Scope;
import io.opencensus.exporter.stats.prometheus.PrometheusStatsCollector;
import io.opencensus.exporter.trace.jaeger.JaegerTraceExporter;
import io.opencensus.trace.Tracer;
import io.opencensus.trace.Tracing;
import io.opencensus.trace.config.TraceConfig;
import io.opencensus.trace.samplers.Samplers;
import io.orijtech.integrations.ocjdbc.Observability;
import io.orijtech.integrations.ocjdbc.OcWrapConnection;
import io.prometheus.client.exporter.HTTPServer;
import java.util.EnumSet;

public class App {
  private static final Tracer tracer = Tracing.getTracer();

  public static void main(String... args) {
    java.sql.Connection conn = null;

    try {
      enableObservability();

      // Load and use the MySQL Connector/J driver.
      Class.forName("com.mysql.cj.jdbc.Driver").newInstance();

      java.sql.Connection originalConn =
          java.sql.DriverManager.getConnection(
              "jdbc:mysql://localhost/repro?user=root&useSSL=false&serverTimezone=UTC");
      /*
      Optionally enable annotation of spans with the accompanying SQL statements.
      java.sql.Connection conn = new OcWrapConnection(originalConn,
                                 // And passing this option to allow the spans
                                 // to be annotated with the SQL queries.
                                 // Please note that this could be a security concern
                                 // since it could reveal personally identifying information.
                                 EnumSet.of(Observability.TraceOption.ANNOTATE_TRACES_WITH_SQL));
      */

      // Then create/wrap it with the instrumented Connection from
      // "io.orijtech.integrations.ocjdbc".
      conn = new OcWrapConnection(originalConn, EnumSet.of(Observability.TraceOption.NONE));
      doWork(conn);
    } catch (Exception e) {
      e.printStackTrace();
      System.err.println(String.format("Failed to create SQLDriver: %s", e));
      return;
    } finally {
      // Finally close the connection when done.
      if (conn != null) {
        try {
          conn.close();
        } catch (Exception e) {
          e.printStackTrace();
          System.err.println(String.format("Failed to close conn: %s", e));
        }
      }
    }
  }

  public static void doWork(java.sql.Connection conn) throws Exception {
    System.out.println("Hello OCJDBC!");

    for (int i = 0; i < 200; i++) {
      Scope ss = tracer.spanBuilder(String.format("DoWork-%d", i)).startScopedSpan();
      try {
        java.sql.Statement stmt = conn.createStatement();
        java.sql.ResultSet rs = stmt.executeQuery("SELECT * from repro");
        rs.close();
        System.out.println("Iteration #" + i);
      } finally {
        ss.close();
      }

      Thread.sleep(4000);
    }
  }

  public static void enableObservability() throws Exception {
    // Enable metrics with OpenCensus.
    Observability.registerAllViews();

    TraceConfig traceConfig = Tracing.getTraceConfig();
    // For demo purposes, lets always sample.
    traceConfig.updateActiveTraceParams(
        traceConfig.getActiveTraceParams().toBuilder().setSampler(Samplers.alwaysSample()).build());

    // The trace exporter.
    JaegerTraceExporter.createAndRegister("http://127.0.0.1:14268/api/traces", "ocjdbc-demo");

    // The metrics exporter.
    PrometheusStatsCollector.createAndRegister();

    // Run the server as a daeon on address "localhost:8889".
    HTTPServer server = new HTTPServer("localhost", 8889);
  }
}
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>io.opencensus</groupId>
    <artifactId>ocjdbc-app</artifactId>
    <packaging>jar</packaging>
    <version>0.0.1</version>
    <name>ocjdbc-app</name>
    <url>http://maven.apache.org</url>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <opencensus.version>0.24.0</opencensus.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>io.opencensus</groupId>
            <artifactId>opencensus-api</artifactId>
            <version>${opencensus.version}</version>
        </dependency>

        <dependency>
            <groupId>io.opencensus</groupId>
            <artifactId>opencensus-impl</artifactId>
            <version>${opencensus.version}</version>
        </dependency>

        <dependency>
            <groupId>io.orijtech.integrations</groupId>
            <artifactId>ocjdbc</artifactId>
            <version>0.0.6</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.12</version>
        </dependency>

        <dependency>
            <groupId>io.opencensus</groupId>
            <artifactId>opencensus-exporter-stats-prometheus</artifactId>
            <version>${opencensus.version}</version>
        </dependency>

        <dependency>
            <groupId>io.prometheus</groupId>
            <artifactId>simpleclient_httpserver</artifactId>
            <version>0.4.0</version>
        </dependency>

        <dependency>
            <groupId>io.opencensus</groupId>
            <artifactId>opencensus-exporter-trace-jaeger</artifactId>
            <version>${opencensus.version}</version>
        </dependency>
    </dependencies>

    <build>
        <extensions>
            <extension>
                <groupId>kr.motd.maven</groupId>
                <artifactId>os-maven-plugin</artifactId>
                <version>1.5.0.Final</version>
            </extension>
        </extensions>

        <pluginManagement>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.7.0</version>
                    <configuration>
                        <source>1.8</source>
                        <target>1.8</target>
                    </configuration>
                </plugin>
            </plugins>
        </pluginManagement>

        <plugins>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>appassembler-maven-plugin</artifactId>
                <version>1.10</version>
                <configuration>
                    <programs>
                        <program>
                            <id>SQLApp</id>
                            <mainClass>io.opencensus.tutorials.ocjdbc.App</mainClass>
                        </program>
                    </programs>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

but also the Prometheus config.yaml file saved in config.yaml

scrape_configs:
  - job_name: 'ocjavametricstutorial'

    scrape_interval: 10s

    static_configs:
      - targets: ['localhost:8889']

Running it

With the Java source code properly placed in src/main/java/io/opencensus/tutorial/ocjdbc/App.java and the pom.xml file, we can now do

mvn install && mvn exec:java -Dexec.mainClass=io.opencensus.tutorial.ocjdbc.App

and then start up Jaeger and Prometheus as per

Starter URL
Jaeger https://opencensus.io/codelabs/jaeger
Prometheus https://opencensus.io/codelabs/prometheus

in another terminal, please run Prometheus as per

prometheus --config.file=config.yaml

which should then produce such output

Hello OCJDBC!
Iteration #0
Iteration #1
Iteration #2
Iteration #3
Iteration #4
Iteration #5
Iteration #6
Iteration #7
Iteration #8
Iteration #9
Iteration #10
Iteration #11
Iteration #12
Iteration #13
Iteration #14
Iteration #15
Iteration #16

Examining the traces

With Jaeger running, we can navigate to the Jaeger UI at http://localhost:16686/search you should be able to see such visuals

    java.sql.Connection conn = new Connection(originalConn,
                                        // And passing this option to allow the spans
                                        // to be annotated with the SQL queries.
                                        // Please note that this could be a security concern
                                        // since it could reveal personally identifying information.
                                        EnumSet.of(Observability.ANNOTATE_TRACES_WITH_SQL));

giving

Examining the metrics

With Prometheus running, we can navigate to the Prometheus UI at http://localhost:9090/graph you should be able to see such visuals

References

Resource URL
JDBC information page https://docs.oracle.com/javase/8/docs/technotes/guides/jdbc/
OCJDBC Github repository https://github.com/opencensus-integrations/ocjdbc
JDBC JavaDoc https://docs.oracle.com/javase/8/docs/api/index.html?java/sql/package-summary.html