You can instrument a Java application to collect trace and metric data with the
[OpenTelemetry](https://opentelemetry.io/docs/what-is-opentelemetry/) SDK and an OpenTelemetry collector and to write correlated,
structured JSON logs to standard output. To experiment with this
instrumentation, you can download and run the sample [Spring Boot](https://spring.io/projects/spring-boot/)
application, which generates log, metric, and trace data and exports it to your
Google Cloud project.

## About the instrumentation approach

When you use an OpenTelemetry collector, you instrument your application with
the SDK and the SDK's OTLP in-process exporter. This instrumentation is
vendor neutral. You also deploy an OpenTelemetry collector that receives telemetry
from the in-process exporter and then exports that
telemetry to your Google Cloud project. To learn more about collectors, see
[Google-Built OpenTelemetry Collector](https://docs.cloud.google.com/stackdriver/docs/instrumentation/google-built-otel.md.txt).

We recommend that you use an OpenTelemetry collector to export your telemetry data
when your environment supports the use of a collector. For some environments,
you must use an in-process exporter that directly sends data to your
Google Cloud project. To learn about in-process instrumentation, see
[Migrate from the Trace exporter to the OTLP endpoint](https://docs.cloud.google.com/trace/docs/migrate-to-otlp-endpoints.md.txt).

To learn more about instrumentation, see the following documents:

- [Observability for application developers](https://docs.cloud.google.com/stackdriver/docs/opentelemetry/application-developers.md.txt)
- [Choose an instrumentation approach](https://docs.cloud.google.com/stackdriver/docs/instrumentation/choose-approach.md.txt)

## About manual and zero-code instrumentation

The instrumentation described in this document relies on OpenTelemetry
zero-code instrumentation to send telemetry to your Google Cloud project.
For Java, *zero-code instrumentation* refers to the practice of
dynamically injecting bytecode into libraries and frameworks to capture
telemetry. Zero-code instrumentation can collect telemetry for things like
inbound and outbound HTTP calls. For more information, see
[Java Agent](https://opentelemetry.io/docs/zero-code/java/agent/).

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](https://opentelemetry.io/docs/languages/java/instrumentation).

## Before you begin

1.

   To get the permissions that
   you need to have the sample application write log, metric, and trace data,

   ask your administrator to grant you the
   following IAM roles:

   - [Logs Writer](https://docs.cloud.google.com/iam/docs/roles-permissions/logging.md.txt#logging.logWriter) (`roles/logging.logWriter`) on your project
   - [Monitoring Metric Writer](https://docs.cloud.google.com/iam/docs/roles-permissions/monitoring.md.txt#monitoring.metricWriter) (`roles/monitoring.metricWriter`) on your project
   - [Cloud Telemetry Traces Writer](https://docs.cloud.google.com/iam/docs/roles-permissions/telemetry.md.txt#telemetry.tracesWriter) (`roles/telemetry.tracesWriter`) on your project
   - [Service Usage Consumer](https://docs.cloud.google.com/iam/docs/roles-permissions/serviceusage.md.txt#serviceusage.serviceUsageConsumer) (`roles/serviceusage.serviceUsageConsumer`) on your quota project


   These permissions are sufficient if you run the sample in the
   Cloud Shell, on Google Cloud resources, or on a local
   development environment. To learn how to configure a quota project, see
   [Set the quota project](https://docs.cloud.google.com/docs/quotas/set-quota-project.md.txt).

   > [!NOTE]
   > **Important**: Production applications typically rely on a service account to provide the credentials required to write log, metric, and trace data. In those scenarios, grant the previous roles to the service account.


   To get the permissions that
   you need to view your log, metric, and trace data,

   ask your administrator to grant you the
   following IAM roles on your project:

   - [Logs Viewer](https://docs.cloud.google.com/iam/docs/roles-permissions/logging.md.txt#logging.viewer) (`roles/logging.viewer`)
   - [Monitoring Viewer](https://docs.cloud.google.com/iam/docs/roles-permissions/monitoring.md.txt#monitoring.viewer) (`roles/monitoring.viewer`)
   - [Cloud Trace User](https://docs.cloud.google.com/iam/docs/roles-permissions/cloudtrace.md.txt#cloudtrace.user) (`roles/cloudtrace.user`)


   For more information about granting roles, see [Manage access to projects, folders, and organizations](https://docs.cloud.google.com/iam/docs/granting-changing-revoking-access.md.txt).


   You might also be able to get
   the required permissions through [custom
   roles](https://docs.cloud.google.com/iam/docs/creating-custom-roles) or other [predefined
   roles](https://docs.cloud.google.com/iam/docs/roles-overview#predefined).

## Instrument your app to collect traces, metrics, and logs

> [!NOTE]
> **Note:** This document displays only selected portions of a working application. For example, the sample doesn't display the list of imported packages. However, the full application is available on GitHub. To view the full sample, in the sample's toolbar, select the GitHub logo.

To instrument your app to collect trace and metric data and to write
structured JSON logs to standard output, perform the following steps:

1. [Configure your app to use the OpenTelemetry Java Agent](https://docs.cloud.google.com/trace/docs/setup/java-ot.md.txt#config-agent)
2. [Configure OpenTelemetry](https://docs.cloud.google.com/trace/docs/setup/java-ot.md.txt#config-otel)
3. [Configure structured logging](https://docs.cloud.google.com/trace/docs/setup/java-ot.md.txt#config-structured-logging)
4. [Write structured logs](https://docs.cloud.google.com/trace/docs/setup/java-ot.md.txt#write-structured-logging)

### 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](https://opentelemetry.io/docs/zero-code/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.

To view the full sample, in the sample's toolbar,
select the GitHub logo.

```java
RUN wget -O /opentelemetry-javaagent.jar https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/download/v2.32.0/opentelemetry-javaagent.jar
CMD sh -c "java -javaagent:/opentelemetry-javaagent.jar -jar app.jar \
	2>&1 | tee /var/log/app.log"
```

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

```bash
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](https://opentelemetry.io/docs/specs/otlp/). It also configures OpenTelemetry to use the
[W3C Trace Context](https://www.w3.org/TR/trace-context/) format for [propagating trace
context](https://opentelemetry.io/docs/concepts/context-propagation/#propagation). 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](https://opentelemetry.io/docs/instrumentation/java/automatic/agent-config/).

### 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](https://logging.apache.org/log4j/2.x/) as your logging implementation.
The following code sample illustrates a `log4j2.xml` file configured to output
JSON structured logs using the [JSON Template Layout](https://logging.apache.org/log4j/2.x/manual/json-template-layout.html):

```xml
<!-- 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
(including trace and span IDs from Mapped Diagnostic Context (MDC) so that Cloud Logging can
correlate Logs and Spans)
https://logging.apache.org/log4j/2.x/manual/json-template-layout.html#event-templates -->
<JsonTemplateLayout eventTemplateUri="classpath:GcpLayout.json"/>
```

The previous configuration extracts information about the active span from
SLF4J's [Mapped Diagnostic Context](https://www.slf4j.org/api/org/slf4j/MDC.html) 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 within 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`](https://docs.cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry.md.txt)
structure.

### Write structured logs

To write structured logs that link to a trace, use the [SLF4J](https://www.slf4j.org/) logging
API. For example, the following statement shows how to call the `Logger.info()`
method:

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

The OpenTelemetry Java Agent automatically populates SLF4J's Mapped Diagnostic Context with
the [span context](https://opentelemetry.io/docs/concepts/signals/traces/#span-context) of the current active span in the
[OpenTelemetry Context](https://opentelemetry.io/docs/specs/otel/context/). The Mapped Diagnostic Context is then included
in the JSON logs as described in [Configure structured
logging](https://docs.cloud.google.com/trace/docs/setup/java-ot#config-structured-logging).

## Run a sample app configured to collect telemetry

The instrumentation in the sample app uses vendor-neutral formats, like JSON
for log data and OTLP for metric and trace data. The app also uses the
[Spring Boot Framework](https://spring.io/projects/spring-boot/). The OpenTelemetry `Collector` sends
log, metric, and trace data to your project by using the Telemetry API,
which uses OTLP.

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.

  ```java
  /**
   * 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.

  ```java
  /**
   * 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

> [!NOTE]
> **Note:** We recommend running the sample app by using Cloud Shell. However, if you want to run the sample app locally on Linux or Mac, then skip the first step in the following instructions.

To run the sample, do the following:

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

   [Activate Cloud Shell](https://console.cloud.google.com/?cloudshell=true)


   At the bottom of the Google Cloud console, a
   [Cloud Shell](https://docs.cloud.google.com/shell/docs/how-cloud-shell-works.md.txt)
   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:

   ```bash
   git clone https://github.com/GoogleCloudPlatform/opentelemetry-samples
   ```
3. Go to the sample directory:

   ```bash
   cd opentelemetry-samples/java/instrumentation-quickstart
   ```
4. Build and run the sample:

   ```bash
   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](https://docs.cloud.google.com/docs/authentication/application-default-credentials#personal)
   provides a credentials file at
   `$HOME/.config/gcloud/application_default_credentials.json`.

   ```bash
   # 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](https://docs.cloud.google.com/monitoring/charts/metrics-explorer.md.txt):

- `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.

<br />

To view the metrics generated by the sample app, do the following:

1. In the Google Cloud console, go to the
   **Metrics explorer** page:

   [Go to **Metrics explorer**](https://console.cloud.google.com/monitoring/metrics-explorer)

   <br />

   If you use the search bar to find this page, then select the result whose subheading is
   **Monitoring**.
2. In the toolbar of the Google Cloud console, select your Google Cloud project. For [App Hub](https://docs.cloud.google.com/app-hub/docs/overview.md.txt) configurations, select the App Hub host project or the app-enabled folder's management project.
3. 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**.
4. To add filters, which remove time series from the query results, use the
   [**Filter** element](https://docs.cloud.google.com/monitoring/charts/metrics-selector.md.txt#filter-option).

5. 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 results in the chart displaying a rate. For
   more information, see [Kinds, types, and conversions](https://docs.cloud.google.com/monitoring/api/v3/aggregation.md.txt#ts-conversion).

   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.](https://docs.cloud.google.com/monitoring/charts/metrics-selector.md.txt)

### View your traces

It might take several minutes before your trace data is available. For example,
when trace data is received by your project, Google Cloud Observability might need to create
a database to store that data. The creation of the database can take a few
minutes, and during that period no trace data is available to view.

To view your trace data, do the following:

1. In the Google Cloud console, go to the
   ![](https://docs.cloud.google.com/static/stackdriver/images/trace-explorer-icon.png)
   **Trace explorer** page:

   [Go to **Trace explorer**](https://console.cloud.google.com/traces/explorer)

   <br />

   You can also find this page by using the search bar.
2. In the table section of the page, select a row with the span name `/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 **Trace Explorer** page, see
[Find and explore traces](https://docs.cloud.google.com/trace/docs/finding-traces.md.txt).

### 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 Google Cloud console, go to the

   **Logs Explorer** page:

   [Go to **Logs Explorer**](https://console.cloud.google.com/logs/query)

   <br />

   If you use the search bar to find this page, then select the result whose subheading is
   **Logging**.
2. Locate a log with the description of `handle /multi request`.

   To view the details of the log, expand the log entry.
3. Click ![](https://docs.cloud.google.com/static/stackdriver/images/trace-explorer-icon.png) **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.

   Your log data might be available several minutes before your trace
   data is available. If you encounter an error when viewing trace data
   either by searching for a trace by ID or by following the steps in
   this task, then wait a minute or two and retry the action.

For more information about using the Logs Explorer, see
[View logs by using the Logs Explorer](https://docs.cloud.google.com/logging/docs/view/logs-explorer-interface.md.txt).

## What's next

- [OpenTelemetry](https://opentelemetry.io/docs/what-is-opentelemetry/)
- [OTLP specification](https://opentelemetry.io/docs/specs/otlp/)
- [Structured logging](https://docs.cloud.google.com/logging/docs/structured-logging.md.txt)
- [Troubleshooting Managed Service for Prometheus](https://docs.cloud.google.com/stackdriver/docs/managed-prometheus/troubleshooting.md.txt)
- [Troubleshoot Cloud Trace](https://docs.cloud.google.com/trace/docs/troubleshooting.md.txt)