Using Open Telemetry Java Agent to Instrument a Java Application with Maven

Ankita LunawatAnkita Lunawat
5 min read

OpenTelemetry is an open-source observability framework that helps you instrument your applications to generate telemetry data (traces, metrics, and logs). By instrumenting your Java application with Open Telemetry, you can gain valuable insights into its performance, behavior, and errors.

Prerequisites

  • AWS Account with EC2 Instance.

  • Java Development Kit (JDK) and Maven must be installed.

Update the package list.

sudo apt update

Install OpenJDK because Spring Boot needs Java.

sudo apt install openjdk-17-jdk -y

Verify that Java is installed.

java -version

Let's install Maven, the build tool used to manage Java projects.

sudo apt install maven -y

Verify the Maven installation by running mvn -version.

mvn -version

Use Maven to create a new Java application.

Maven uses a standard directory layout that simplifies project management, and you can create a new Maven project using the command line.

Run the command below to create a new project.

mvn archetype:generate -DgroupId=com.example -DartifactId=java-simple -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false
  • groupId: A unique identifier for your project (usually the package name).

  • artifactId: The name of your project.

  • archetypeArtifactId: The template for the project; maven-archetype-quickstart creates a simple Java project.

  • interactiveMode: Set to false to skip interactive prompts.

Navigate to the project directory.

cd java-simple

Open the pom.xml file and update it to include Spring Boot dependencies.

The pom.xml file is the main configuration file in a Maven project, detailing dependencies, build settings, and project information.

vi pom.xml

Replace its content with the following.

<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>com.example</groupId>
  <artifactId>helloworld</artifactId>
  <packaging>jar</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>helloworld</name>
  <url>http://maven.apache.org</url>
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.0.0</version>
    <relativePath />
  </parent>
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>
  </dependencies>
  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </build>
</project>

Create the Java Application and Controller

Create a new file named DiceApplication.java and RestController.java in the src/main/java/com/example directory with the specified content.

Go to the directory.

cd src/main/java/com/example

Delete the default App.java file.

sudo rm App.java

Create a file named DiceApplication.java in the specified directory.

nano DiceApplication.java

Add the following code to the file.

package com.example;

import org.springframework.boot.Banner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DiceApplication {
    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(DiceApplication.class);
        app.setBannerMode(Banner.Mode.OFF);
        app.run(args);
    }
}

Explanation of the code:

This is the main application code for a simple Java Spring Boot project, which launches a web application without displaying the Spring Boot banner.

  1. Package Declaration:

    • package com.example;: Defines the package for the DiceApplication class. In Java, packages help organize classes logically and avoid naming conflicts.
  2. Import Statements:

    • Imports Spring Boot classes for app startup (SpringApplication) and configuration (SpringBootApplication), along with Banner for optional customization.
  3. Class Annotation:

    • @SpringBootApplication: This annotation marks the main class of the Spring Boot application, enabling features like:

      • @Configuration: Allows Java-based configuration in place of XML.

      • @EnableAutoConfiguration: Enables automatic configuration of Spring based on dependencies.

      • @ComponentScan: Scans the package for Spring components (like controllers and services), making them available to the application.

  4. Class and main Method:

    • Declares DiceApplication as the main class.

    • SpringApplication app = new SpringApplication(DiceApplication.class); – Initializes the application.

    • app.setBannerMode(Banner.Mode.OFF); – Disables the banner for cleaner console output.

    • app.run(args); – Starts the application, launching the embedded web server.

Create the RollController class to simulate rolling a dice.

vi RollController.java

Add this code.

package com.example;

import java.util.Optional;
import java.util.concurrent.ThreadLocalRandom;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class RollController {
    private static final Logger logger = LoggerFactory.getLogger(RollController.class);

    @GetMapping("/rolldice")
    public String index(@RequestParam("player") Optional<String> player) {
        int result = this.getRandomNumber(1, 6);
        if (player.isPresent()) {
            logger.info("{} is rolling the dice: {}", player.get(), result);
        } else {
            logger.info("Anonymous player is rolling the dice: {}", result);
        }
        return Integer.toString(result);
    }

    public int getRandomNumber(int min, int max) {
        return ThreadLocalRandom.current().nextInt(min, max + 1);
    }
}

Explanation of the code:

This RollController class handles HTTP requests for rolling a dice. It provides an endpoint (/rolldice) that simulates rolling a dice and logs the result, optionally associating it with a player name.

  1. Package Declaration:

    • package com.example; – Organizes the class under the com.example package.
  2. Imports:

    • java.util.Optional, java.util.concurrent.ThreadLocalRandom: For handling optional player input and generating random dice numbers.

    • org.slf4j.Logger, org.slf4j.LoggerFactory: For logging the dice roll results.

    • org.springframework.web.bind.annotation.GetMapping, org.springframework.web.bind.annotation.RequestParam, org.springframework.web.bind.annotation.RestController: For defining the RESTful controller and handling GET requests with optional query parameters.

  3. @RestController Annotation:

    • Indicates that the class is a Spring REST controller, capable of handling HTTP requests and returning HTTP responses.
  4. Logger:

    • private static final Logger logger = LoggerFactory.getLogger(RollController.class); – Initializes a logger for the class to log dice roll information.
  5. index Method:

    • @GetMapping("/rolldice"): Maps the /rolldice URL to this method, allowing HTTP GET requests to trigger the dice roll logic.

    • @RequestParam("player") Optional<String> player: Captures the optional player query parameter from the URL (e.g., /rolldice?player=John).

    • int result = this.getRandomNumber(1, 6);: Rolls the dice by generating a random number between 1 and 6.

    • Logs the dice roll result, either associating it with a player if the name is provided or logging it as an anonymous roll.

    • Returns the dice roll result as a string.

  6. getRandomNumber Method:

    • Generates a random number between the specified min and max values using ThreadLocalRandom.current().nextInt(min, max + 1).

Compile and execute the Java application

Return to the project's root directory.

cd ../../../../..

Build the project by compiling and executing the Java application.

mvn package

The output JAR file is in the target directory.

Execute the application.

java -jar ./target/helloworld-1.0-SNAPSHOT.jar

The application is running and can be accessed at http://<EC2-Instance-IP>:8080/rolldice.

Add the OpenTelemetry Java Agent to your application.

Download the OpenTelemetry Java Agent.

curl -L -O https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/latest/download/opentelemetry-javaagent.jar

Set the environment variables.

export JAVA_TOOL_OPTIONS="-javaagent:/home/ubuntu/java-simple/opentelemetry-javaagent.jar" \
OTEL_TRACES_EXPORTER=logging \
OTEL_METRICS_EXPORTER=logging \
OTEL_LOGS_EXPORTER=logging \
OTEL_METRIC_EXPORT_INTERVAL=15000

Run the application using OpenTelemetry.

java -jar ./target/helloworld-1.0-SNAPSHOT.jar

The application is now running; let's access it again at http://<EC2-Instance-IP>:8080/rolldice.

These are the steps and best practices, you can use Open Telemetry to effectively monitor your Java application's performance and behavior.

0
Subscribe to my newsletter

Read articles from Ankita Lunawat directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Ankita Lunawat
Ankita Lunawat

Hi there! I'm a passionate AWS DevOps Engineer with 2+ years of experience in building and managing scalable, reliable, and secure cloud infrastructure. I'm excited to share my knowledge and insights through this blog. Here, you'll find articles on: AWS Services: Deep dives into core AWS services like EC2, S3, Lambda, and more. DevOps Practices: Best practices for CI/CD, infrastructure as code, and automation. Security: Tips and tricks for securing your AWS environments. Serverless Computing: Building and deploying serverless applications. Troubleshooting: Common issues and solutions in AWS. I'm always eager to learn and grow, and I hope this blog can be a valuable resource for fellow DevOps enthusiasts. Feel free to connect with me on [LinkedIn/Twitter] or leave a comment below!