This tutorial demonstrates how to instrument your Java applications to capture [OpenTelemetry](https://coralogix.com/docs/opentelemetry/getting-started/index.md) traces and send them to Coralogix.

OpenTelemetry-Java automatic instrumentation is the most efficient method for adding instrumentation to Java applications. Requiring minimal modifications to the code, it uses a Java agent that can be attached to any **Java 8+** application and dynamically injects bytecode to capture telemetry from several popular libraries and frameworks.

Send JVM runtime metrics

With the OpenTelemetry Java agent attached, the same setup can also report JVM runtime metrics — heap, garbage collection, threads, CPU, and class loading. See [Send JVM metrics](https://coralogix.com/docs/user-guides/apm/features/runtime-metrics/send-jvm-metrics/index.md) to enable the metrics exporter, then view the data in the [Runtime metrics](https://coralogix.com/docs/user-guides/apm/features/runtime-metrics/index.md) tab in Service Catalog.

## Installation

The instructions below conform to the [latest](https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/latest) OpenTelemetry Java Auto Instrumentation, currently Java agent version **`1.22.1`**.

**STEP 1**. Download and distribute the agent JAR.

- Download the [latest OpenTelemetry Java agent](https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/latest/download/opentelemetry-javaagent.jar).
- Apply the agent to each service host or container that requires access to it.
- The JVM will require access to the agent to function properly.

**STEP 2**. Update the JVM configuration.

Either of the following options may be used as the template, with the following changes:

- `JAVA_TOOL_OPTIONS`: Replace the path to the java agent JAR and the `coralogix-opentelemetry` JAR files with the file's location downloaded and distributed in **STEP 1** above.

- `OTEL_EXPORTER_OTLP_ENDPOINT`: Choose the ingress.\[[DOMAIN_VALUE]\]:443 endpoint that corresponds to your Coralogix [domain](https://coralogix.com/docs/user-guides/account-management/account-settings/coralogix-domain/index.md) using the domain selector at the top of the page.

- `OTEL_RESOURCE_ATTRIBUTES`: Specify your Service.Name.

- `OTEL_RESOURCE_ATTRIBUTES`: Specify Your Coralogix [Send-Your-Data API key](https://coralogix.com/docs/user-guides/account-management/api-keys/send-your-data-api-key/index.md), [application](https://coralogix.com/docs/user-guides/account-management/account-settings/application-and-subsystem-names/index.md), and [subsystem name](https://coralogix.com/docs/user-guides/account-management/account-settings/application-and-subsystem-names/index.md).

- Specify 4 RESOURCE_ATTRIBUTES for application and subsystem:

  - `application.name`: Replace with the name used for the identification of your Coralogix application name
  - `api.name`: Replace with the name used for the identification of your Coralogix subsystem name
  - `cx.application.name`: Replace with the name used for the identification of your Coralogix application name
  - `cx.subsystem.name`: Replace with the name used for the identification of your Coralogix subsystem name

**Option 1** (**recommended**): Leveraging environment variables

- Pass the Configuration parameters as system properties environment variables in the JVM:

```bash
export JAVA_TOOL_OPTIONS="-javaagent:path/to/opentelemetry-javaagent.jar —Dotel.javaagent.extensions=build/libs/opentelemetry-java-instrumentation-extension-demo-1.0-all.jar"

export OTEL_TRACES_EXPORTER="otlp"
export OTEL_METRICS_EXPORTER="none"
export OTEL_LOGS_EXPORTER="none"
export OTEL_EXPORTER_OTLP_TRACES_PROTOCOL="grpc"
export OTEL_EXPORTER_OTLP_ENDPOINT="ingress.[[DOMAIN_VALUE]]:443"
export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer <CXPrivateKey>"
export OTEL_RESOURCE_ATTRIBUTES=service.name=<ServiceName>,application.name=<CXApplicationName>,api.name=<CXSubsystemName>,cx.application.name=<CXApplicationName>,cx.subsystem.name=<CXSubsystemName>

java -javaagent:</path/to/>opentelemetry-javaagent.jar -jar myapp.jar
```

- View the full range of configuration options [here](https://opentelemetry.io/docs/instrumentation/java/automatic/agent-config/).

**Option 2**: Changing the Java command line

- Enable the instrumentation agent using the `-javaagent` flag to the JVM and pass the Configuration parameters as Java system properties (`-D` flags):

```bash
java -javaagent:path/to/opentelemetry-javaagent.jar \
    —Dotel.javaagent.extensions=build/libs/opentelemetry-java-instrumentation-extension-demo-1.0-all.jar" \
    -Dotel.traces.exporter=otlp \
    -Dotel.metrics.exporter=none \
    -Dotel.logs.exporter=none \
    -Dotel.exporter.otlp.traces.protocol=grpc \
    -Dotel.exporter.otlp.traces.endpoint="ingress.[[DOMAIN_VALUE]]:443" \
    -Dotel.exporter.otlp.traces.headers=Authorization=Bearer "<CXPrivateKey>" \
    -Dotel.resource.attributes=service.name=<ServiceName>,application.name=<CXApplicationName>,api.name=<CXSubsystemName>,cx.application.name=<CXApplicationName>,cx.subsystem.name=<CXSubsystemName> \
    -jar myapp.jar
```

## Troubleshooting

Confirm that the instrumentation was installed by looking for the following log lines in your console:

```text
[otel.javaagent 2023-02-14 10:40:00:811 +0000] [main] INFO io.opentelemetry.javaagent.tooling.VersionLogger - opentelemetry-javaagent - version: 1.21.1
```

## Example

[JPetStore](https://github.com/mybatis/jpetstore-6) 6 is a full web application built on top of MyBatis 3, Spring 5, and Stripes.

**STEP 1**. Install a version of the Java Development Kit. It will not deploy when using only the Java Runtime Environment.

**STEP 2**. [Download](https://www.oracle.com/uk/java/technologies/downloads/#java19) the JDK for your platform.

**STEP 3**. Clone the [jpetstore](https://github.com/kazuki43zoo/mybatis-spring-boot-jpetstore.git) git repo:

```bash
git clone <https://github.com/kazuki43zoo/mybatis-spring-boot-jpetstore.git>
```

**STEP 4**. Run the following:

```bash
./mvnw clean package -DskipTests=true
```

**STEP 5**. [Download](https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/latest/download/opentelemetry-javaagent.jar) the OpenTelemetry agent.

**STEP 6**. Apply the environment variables:

```bash
export OTEL_TRACES_EXPORTER="otlp"
export OTEL_EXPORTER_OTLP_TRACES_PROTOCOL="grpc"
export OTEL_EXPORTER_OTLP_ENDPOINT="ingress.[[DOMAIN_VALUE]]:443"
export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer <CXPrivateKey>"
export OTEL_RESOURCE_ATTRIBUTES=service.name=<ServiceName>,application.name=<CXApplicationName>,api.name=<CXSubsystemName>,cx.application.name=<CXApplicationName>,cx.subsystem.name=<CXSubsystemName>
```

**STEP 7**. Run the project with the agent:

```bash
java -javaagent:</path/to/>opentelemetry-javaagent.jar -jar mybatis-spring-boot-jpetstore-2.0.0-SNAPSHOT.jar
```

**STEP 8**. Navigate to <http://localhost:8080/>.

**STEP 9**. View the traces in your Coralogix dashboard.

## Additional instrumentation using annotations

**STEP 1**. Open the project with your favorite IDE/text editor.

**STEP 2**. Edit the `pom.xml` file and add the following maven dependency:

```xml
<dependency>
    <groupId>io.opentelemetry.instrumentation</groupId>
    <artifactId>opentelemetry-instrumentation-annotations</artifactId>
    <version>1.22.1</version>
</dependency>
```

**STEP 3**. Edit file src/main/java/com/kazuki43zoo/jpetstore/ui/Cart.java.

**STEP 4**. Add this import to the Java file where the methods you want to trace are present.

```java
import io.opentelemetry.instrumentation.annotations.WithSpan;
```

**STEP 5**. Before the function you wish to trace, add the annotation "WithSpan".

**Example**: `cart.java`

**STEP 6.** Rebuild your springboot application applying the changes (using either your IDE or command line).

**STEP 7**. Run your application again with the OTel agent:

```bash
java -javaagent:</path/to/>opentelemetry-javaagent.jar -jar mybatis-spring-boot-jpetstore-2.0.0-SNAPSHOT
```

## Validation

Access **Explore > Tracing** to view the traces generated by your application.

Your traces should now contain additional spans on annotated methods.

## Exceptions

Java exception logging is available through the OpenTelemetry Logs API and through the Log4j appender instrumentation. When an exception is attached to a log record, the exported log follows the OpenTelemetry semantic conventions for exceptions in logs.

### OpenTelemetry Java API

The stable `LogRecordBuilder.setException()` API is available in `io.opentelemetry:opentelemetry-api` starting in version `1.60.0`. If you are using the incubator logs API, `ExtendedLogRecordBuilder.setException()` is available starting in `1.50.0-alpha`.

**STEP 1**. Add the required dependencies:

```xml
<dependency>
  <groupId>io.opentelemetry</groupId>
  <artifactId>opentelemetry-api</artifactId>
  <version>1.60.0</version>
</dependency>
<dependency>
  <groupId>io.opentelemetry</groupId>
  <artifactId>opentelemetry-sdk</artifactId>
  <version>1.60.0</version>
</dependency>
<dependency>
  <groupId>io.opentelemetry</groupId>
  <artifactId>opentelemetry-sdk-logs</artifactId>
  <version>1.60.0</version>
</dependency>
<dependency>
  <groupId>io.opentelemetry</groupId>
  <artifactId>opentelemetry-exporter-logging</artifactId>
  <version>1.60.0</version>
</dependency>
```

**STEP 2**. Configure a `LoggerProvider` and emit a log with an exception:

```java
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.logs.Logger;
import io.opentelemetry.api.logs.Severity;
import io.opentelemetry.exporter.logging.SystemOutLogRecordExporter;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.sdk.logs.SdkLoggerProvider;
import io.opentelemetry.sdk.logs.export.SimpleLogRecordProcessor;

SdkLoggerProvider loggerProvider =
    SdkLoggerProvider.builder()
        .addLogRecordProcessor(
            SimpleLogRecordProcessor.create(SystemOutLogRecordExporter.create()))
        .build();

OpenTelemetry openTelemetry =
    OpenTelemetrySdk.builder().setLoggerProvider(loggerProvider).build();

Logger logger =
    openTelemetry.getLogsBridge().loggerBuilder("java-exceptions-example").build();

IllegalStateException error =
    new IllegalStateException("database connection failed");

logger
    .logRecordBuilder()
    .setSeverity(Severity.ERROR)
    .setBody("request failed")
    .setException(error)
    .emit();
```

### Log4j 2

The `io.opentelemetry.instrumentation:opentelemetry-log4j-appender-2.17` artifact supports Log4j 2.17 and higher and is published as an alpha artifact. Exception mapping is available in this artifact starting in `1.14.0-alpha`.

**STEP 1**. Add the required dependencies:

```xml
<dependency>
  <groupId>io.opentelemetry</groupId>
  <artifactId>opentelemetry-sdk</artifactId>
  <version>1.60.0</version>
</dependency>
<dependency>
  <groupId>io.opentelemetry</groupId>
  <artifactId>opentelemetry-sdk-logs</artifactId>
  <version>1.60.0</version>
</dependency>
<dependency>
  <groupId>io.opentelemetry</groupId>
  <artifactId>opentelemetry-exporter-logging</artifactId>
  <version>1.60.0</version>
</dependency>
<dependency>
  <groupId>io.opentelemetry.instrumentation</groupId>
  <artifactId>opentelemetry-log4j-appender-2.17</artifactId>
  <version>2.26.1-alpha</version>
</dependency>
```

**STEP 2**. Configure the Log4j appender in `log4j2.xml`:

```xml
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
  <Appenders>
    <OpenTelemetry name="OpenTelemetryAppender"/>
  </Appenders>
  <Loggers>
    <Root level="all">
      <AppenderRef ref="OpenTelemetryAppender"/>
    </Root>
  </Loggers>
</Configuration>
```

**STEP 3**. Install the appender during application startup:

```java
import io.opentelemetry.exporter.logging.SystemOutLogRecordExporter;
import io.opentelemetry.instrumentation.log4j.appender.v2_17.OpenTelemetryAppender;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.sdk.logs.SdkLoggerProvider;
import io.opentelemetry.sdk.logs.export.SimpleLogRecordProcessor;

SdkLoggerProvider loggerProvider =
    SdkLoggerProvider.builder()
        .addLogRecordProcessor(
            SimpleLogRecordProcessor.create(SystemOutLogRecordExporter.create()))
        .build();

OpenTelemetrySdk openTelemetry =
    OpenTelemetrySdk.builder().setLoggerProvider(loggerProvider).build();

OpenTelemetryAppender.install(openTelemetry);
```

**STEP 4**. Log the exception:

```java
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

Logger logger = LogManager.getLogger("java-log4j-exceptions-example");
IllegalStateException error =
    new IllegalStateException("database connection failed");

logger.error("request failed", error);
```

### SLF4J with Log4j 2

When SLF4J is configured to use the Log4j 2 backend, the same Log4j appender setup is used. SLF4J to Log4j 2 support is available starting in `1.23.0-alpha`.

**STEP 1**. Add the SLF4J API and the Log4j 2 binding:

```xml
<dependency>
  <groupId>org.slf4j</groupId>
  <artifactId>slf4j-api</artifactId>
  <version>2.0.17</version>
</dependency>
<dependency>
  <groupId>org.apache.logging.log4j</groupId>
  <artifactId>log4j-slf4j2-impl</artifactId>
  <version>2.25.2</version>
</dependency>
```

**STEP 2**. Reuse the `log4j2.xml` and `OpenTelemetryAppender.install(...)` configuration from the Log4j 2 section, and log the exception through SLF4J:

```java
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Logger logger = LoggerFactory.getLogger("java-slf4j-exceptions-example");
IllegalStateException error =
    new IllegalStateException("database connection failed");

logger.error("request failed", error);
```
