Generate traces and metrics with Java

This document describes how to modify a Java app to collect trace and metric data using the open source OpenTelemetry framework, and how to write structured JSON logs to standard out. This document also provides information about a sample Java Spring Boot app that you can install and run. The app is configured to generate metrics, traces, and logs. The steps are the same whether or not you are using the Spring Boot Framework.

About manual and automatic instrumentation

The instrumentation described in this document relies on OpenTelemetry automatic instrumentation to send telemetry to your Google Cloud project. For Java, automatic instrumentation refers to the practice of dynamically injecting bytecode into libraries and frameworks to capture telemetry. Automatic instrumentation can collect telemetry for things like inbound and outbound HTTP calls. For more information, see Automatic Instrumentation for Java.

OpenTelemetry also provides an API for adding custom instrumentation to your own code. OpenTelemetry refers to this as manual instrumentation. This document doesn't describe manual instrumentation. For examples and information about that topic, see Manual instrumentation.

Before you begin

Enable the Cloud Logging API, Cloud Monitoring API, and Cloud Trace API APIs.

Enable the APIs

Instrument your app to collect traces, metrics, and logs

To instrument your app to collect trace and metric data, and to write structured JSON to standard out, perform the following steps as described in subsequent sections of this document:

  1. Configure your app to use the OpenTelemetry Java Agent
  2. Configure OpenTelemetry
  3. Configure structured logging
  4. Write structured logs

Configure your app to use the OpenTelemetry Java Agent

To configure the app to write structured logs and to collect metrics and trace data by using OpenTelemetry, update the invocation of your app to use the OpenTelemetry Java Agent. This method of instrumenting your app is known as automatic instrumentation because it doesn't require modifying your app code.

The following code sample illustrates a Dockerfile that downloads the OpenTelemetry Java Agent JAR file and updates the command line invocation to pass the -javaagent flag.

RUN wget -O /opentelemetry-javaagent.jar https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/download/v1.31.0/opentelemetry-javaagent.jar
CMD sh -c "java -javaagent:/opentelemetry-javaagent.jar -cp app:app/lib/* com.example.demo.DemoApplication \
	2>&1 | tee /var/log/app.log"

Alternatively, you can also set the -javaagent flag in the JAVA_TOOL_OPTIONS environment variable:

export JAVA_TOOL_OPTIONS="-javaagent:PATH/TO/opentelemetry-javaagent.jar"

Configure OpenTelemetry

The default configuration for the OpenTelemetry Java Agent exports traces and metrics by using the OTLP protocol. It also configures OpenTelemetry to use the W3C Trace Context format for propagating trace context. This configuration ensures that spans have the correct parent-child relationship within a trace.

For more information and configuration options, see OpenTelemetry Java automatic instrumentation.

Configure structured logging

To include the trace information as part of the JSON-formatted logs written to standard output, configure your app to output structured logs in JSON format. We recommend using Log4j2 as your logging implementation. The following code sample illustrates a log4j2.xml file configured to output JSON structured logs using the JSON Template Layout:

<!-- Format JSON logs for the Cloud Logging agent
https://cloud.google.com/logging/docs/structured-logging#special-payload-fields -->

<!-- Log4j2's JsonTemplateLayout includes a template for Cloud Logging's special JSON fields
https://logging.apache.org/log4j/2.x/manual/json-template-layout.html#event-templates -->
<JsonTemplateLayout eventTemplateUri="classpath:GcpLayout.json">
  <!-- Extend the included GcpLayout to include the trace and span IDs from Mapped
  Diagnostic Context (MDC) so that Cloud Logging can correlate Logs and Spans -->
  <EventTemplateAdditionalField
    key="logging.googleapis.com/trace"
    format="JSON"
    value='{"$resolver": "mdc", "key": "trace_id"}'
  />
  <EventTemplateAdditionalField
    key="logging.googleapis.com/spanId"
    format="JSON"
    value='{"$resolver": "mdc", "key": "span_id"}'
  />
  <EventTemplateAdditionalField
    key="logging.googleapis.com/trace_sampled"
    format="JSON"
    value="true"
  />
</JsonTemplateLayout>

The previous configuration extracts information about the active span from SLF4J's Mapped Diagnostic Context and adds that information as attributes to the log. These attributes can then be used to correlate a log with a trace:

  • logging.googleapis.com/trace: Resource name of the trace associated with the log entry.
  • logging.googleapis.com/spanId: The span ID with the trace that is associated with the log entry.
  • logging.googleapis.com/trace_sampled: The value of this field must be true or false.

For more information about these fields, see the LogEntry structure.

Write structured logs

To write structured logs that link to a trace, use the SLF4J logging API. For example, the following statement shows how to call the Logger.info() method:

logger.info("handle /multi request with subRequests={}", subRequests);

The OpenTelemetry Java Agent automatically populates SLF4J's Mapped Diagnostic Context with the span context of the current active span in the OpenTelemetry Context. The Mapped Diagnostic Context is then included in the JSON logs as described in Configure structured logging.

Run a sample app configured to collect telemetry

The example app uses vendor-neutral formats, including JSON for logs and OTLP for metrics and traces, and the Spring Boot Framework. To route the telemetry to Google Cloud, this sample uses the OpenTelemetry Collector configured with Google exporters. The app has two endpoints:

  • The /multi endpoint is handled by the handleMulti function. The load generator in the app issues requests to the /multi endpoint. When this endpoint receives a request, it sends between three and seven requests to the /single endpoint on the local server.

    /**
     * handleMulti handles an http request by making 3-7 http requests to the /single endpoint.
     *
     * <p>OpenTelemetry instrumentation requires no changes here. It will automatically generate a
     * span for the controller body.
     */
    @GetMapping("/multi")
    public Mono<String> handleMulti() throws Exception {
      int subRequests = ThreadLocalRandom.current().nextInt(3, 8);
    
      // Write a structured log with the request context, which allows the log to
      // be linked with the trace for this request.
      logger.info("handle /multi request with subRequests={}", subRequests);
    
      // Make 3-7 http requests to the /single endpoint.
      return Flux.range(0, subRequests)
          .concatMap(
              i -> client.get().uri("http://localhost:8080/single").retrieve().bodyToMono(Void.class))
          .then(Mono.just("ok"));
    }
  • The /single endpoint is handled by the handleSingle function. When this endpoint receives a request, it sleeps for a short delay and then responds with a string.

    /**
     * handleSingle handles an http request by sleeping for 100-200 ms. It writes the number of
     * milliseconds slept as its response.
     *
     * <p>OpenTelemetry instrumentation requires no changes here. It will automatically generate a
     * span for the controller body.
     */
    @GetMapping("/single")
    public String handleSingle() throws InterruptedException {
      int sleepMillis = ThreadLocalRandom.current().nextInt(100, 200);
      logger.info("Going to sleep for {}", sleepMillis);
      Thread.sleep(sleepMillis);
      logger.info("Finishing the request");
      return String.format("slept %s\n", sleepMillis);
    }

Download and deploy the app

To run the sample, do the following:

  1. In the Google Cloud console, activate Cloud Shell.

    Activate Cloud Shell

    At the bottom of the Google Cloud console, a Cloud Shell session starts and displays a command-line prompt. Cloud Shell is a shell environment with the Google Cloud CLI already installed and with values already set for your current project. It can take a few seconds for the session to initialize.

  2. Clone the repository:

    git clone https://github.com/GoogleCloudPlatform/opentelemetry-operations-java
    
  3. Go to the sample directory:

    cd opentelemetry-operations-java/examples/instrumentation-quickstart
    
  4. Build and run the sample:

    docker compose up --abort-on-container-exit
    

    If you aren't running on Cloud Shell, then run the application with the GOOGLE_APPLICATION_CREDENTIALS environment variable pointing to a credentials file. Application Default Credentials provides a credentials file at $HOME/.config/gcloud/application_default_credentials.json.

    # Set environment variables
    export GOOGLE_CLOUD_PROJECT="PROJECT_ID"
    export GOOGLE_APPLICATION_CREDENTIALS="$HOME/.config/gcloud/application_default_credentials.json"
    export USERID="$(id -u)"
    
    # Run
    docker compose -f docker-compose.yaml -f docker-compose.creds.yaml up --abort-on-container-exit
    

View your metrics

The OpenTelemetry instrumentation in the sample app generates Prometheus metrics that you can view by using the Metrics Explorer:

  • Prometheus/http_server_duration_milliseconds/histogram records the duration of server requests and stores the results in a histogram.

  • Prometheus/http_client_duration_milliseconds/histogram records the duration of client requests and stores the results in a histogram.

To view the metrics generated by the sample app, do the following:
  1. In the navigation panel of the Google Cloud console, select Monitoring, and then select  Metrics explorer:

    Go to Metrics explorer

  2. In the Metric element, expand the Select a metric menu, enter http_server in the filter bar, and then use the submenus to select a specific resource type and metric:
    1. In the Active resources menu, select Prometheus Target.
    2. In the Active metric categories menu, select Http.
    3. In the Active metrics menu, select a metric.
    4. Click Apply.
  3. Configure how the data is viewed.

    When the measurements for a metric are cumulative, Metrics Explorer automatically normalizes the measured data by the alignment period, which which results in the chart displaying a rate. For more information, see Kinds, types, and conversions.

    When integer or double values are measured, such as with the two counter metrics, Metrics Explorer automatically sums all time series. To view the data for the /multi and /single HTTP routes, set the first menu of the Aggregation entry to None.

    For more information about configuring a chart, see Select metrics when using Metrics Explorer.

View your traces

To view your trace data, do the following:

  1. In the navigation panel of the Google Cloud console, select Trace, and then select Trace explorer:

    Go to Trace explorer

  2. In the scatter plot, select a trace with the URI of /multi.
  3. In the Gantt chart on the Trace details panel, select the span labeled /multi.

    A panel opens that displays information about the HTTP request. These details include the method, status code, number of bytes, and the user agent of the caller.

  4. To view the logs associated with this trace, select the Logs & Events tab.

    The tab shows individual logs. To view the details of the log entry, expand the log entry. You can also click View Logs and view the log by using the Logs Explorer.

For more information about using the Cloud Trace explorer, see Find and explore traces.

View your logs

From the Logs Explorer, you can inspect your logs, and you can also view associated traces, when they exist.

  1. In the navigation panel of the Google Cloud console, select Logging, and then select Logs Explorer:

    Go to Logs Explorer

  2. Locate a log with the description of handle /multi request.

    To view the details of the log, expand the log entry.

  3. Click Traces on a log entry with the "handle /multi request" message, and then select View trace details.

    A Trace details panel opens and displays the selected trace.

For more information about using the Logs Explorer, see View logs by using the Logs Explorer.

What's next