How to Send Traces from Spring Boot to Jaeger
A guide on enhancing Spring Boot applications to enable sending request traces to Jaeger
Introduction
If you want to observe the precise flow of interactions between your microservices, you require a tool that captures the traces of requests as they traverse through the various microservices, as well as the time taken at each step.
There are numerous methods for sending traces from an application to Jaeger. In the case of a Java application, you have different options, such as Micrometer
, Spring Cloud Sleuth
and OpenTelemetry Java Agent
. In this article, I will explain the differences between them and provide guidance on how to send traces from a Java application to a Jaeger server.
Jaeger VS Zipkin
Jaeger is currently the most popular trace analytics tool, but there are alternative options available, such as Zipkin. In comparison to Zipkin, Jaeger boasts modularity, better performance, and a larger community. Additionally, while Zipkin is an older project (initiated in 2012 at Twitter) written in Java, Jaeger is a newer project (commenced in 2017 at Uber) developed in Go.
Micrometer VS Sleuth VS OpenTelemetry agent
Spring Cloud Sleuth
has been discontinued in Spring Boot 3.x, and it can only be used until the end of Spring Boot 2. To send your traces to Jaeger, you need to migrate to the Micrometer
library.
Alternatively, you can use the OpenTelemetry Java Agent Instrumentor
, but I do not recommend it due to potential security problems that a Java Agent may introduce to your code. Additionally, it may cause performance issues as it can inject interceptors before and after each method call within your application.
Therefore, I recommend using Micrometer
as a superior alternative to Sleuth
or the OpenTelemetry Java Agent
.
The architecture
In the above image, the client initiates a request to microservice 1, which is a Java Spring Boot application. Microservice 1 then forwards the request to microservice 2, transparently from the user’s perspective. Once microservice 2 responds, microservice 1 relays the response back to the client.
Both of these microservices send their traces, following the OpenTelemetry Standard Protocol, to the OpenTelemetry Collector
. This module is part of the Jaeger family and can currently accept OpenTelemetry traces. Consequently, there is no need to set up a separate instance solely for collecting OpenTelemetry traces. It is important to note that the deprecated OpenTracing
protocol has been replaced by OpenTelemetry.
1. Add Micrometer to the project
First, you need to add it to the build.gradle
as a dependency like this:
dependencies {
implementation "io.micrometer:micrometer-tracing-bridge-otel"
implementation "io.opentelemetry:opentelemetry-exporter-otlp"
}
2. Configure the Span Exporter
for configuring the span exporter, you need to add a bean like this:
@Bean
public OtlpGrpcSpanExporter otlpHttpSpanExporter(@Value("${tracing.url}") String url) {
return OtlpGrpcSpanExporter.builder().setEndpoint(url).build();
}
add these configurations to application.properties
file:
management.tracing.sampling.probability=1.0
tracing.url=http://localhost:4317
The port 4317
is for gRPC
, which is lighter than the HTTP protocol.
3. add a sample controller
you can add a sample controller to see what will happen:
@RestController
@RequestMapping("/hello")
public class Controller {
private static final Logger logger = LoggerFactory.getLogger(Controller.class);
Random random = new Random();
private RestTemplate restTemplate;
@Value("${spring.application.name}")
private String applicationName;
public Controller(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
@GetMapping("/path1")
public ResponseEntity<String> path1() {
logger.info("Incoming request at {} for request /path1 ", applicationName);
doNothingButSleepForSomeTime();
String response = restTemplate.getForObject("http://localhost:8090/hello/path2", String.class);
doNothingButSleepForSomeTime();
return ResponseEntity.ok("response from /path1 + " + response);
}
@GetMapping("/path2")
public ResponseEntity<String> path2() {
logger.info("Incoming request at {} at /path2", applicationName);
doNothingButSleepForSomeTime();
return ResponseEntity.ok("response from /path2 ");
}
public void doNothingButSleepForSomeTime() {
try {
int sleepTime = random.nextInt(2, 5);
logger.info("sleeping for " + sleepTime + " seconds");
Thread.sleep(sleepTime * 1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
the directory structure of this project is something like this:
.
├── docker-compose.yaml
├── pom.xml
├── src
│ ├── main
│ │ ├── java
│ │ │ └── com
│ │ │ └── example
│ │ │ └── demo
│ │ │ ├── Controller.java
│ │ │ ├── DemoApplication.java
│ │ └── resources
│ │ └── application.properties
│ └── test
│ └── java
│ └── com
│ └── example
│ └── demo
│ └── DemoApplicationTests.java
4. Run the project
To test our code, you need to run the application twice using different ports, such as 8080
and 8090
, in order to send a request from the first instance to the second instance. You can achieve this by executing the following commands:
$ java -jar target/demo-0.0.1-SNAPSHOT.jar \
--spring.application.name=Service-1 --server.port=8080
$ java -jar target/demo-0.0.1-SNAPSHOT.jar \
--spring.application.name=Service-2 --server.port=8090
5. Bring up Jaeger AllInOne
The Jaeger All-In-One can be easily set up with all the necessary components to test your Java application and ensure that it correctly sends traces. It also includes an OpenTelemetry Collector
module for collecting OpenTelemetry traces. You can bring it up simply by using a docker-compose.yaml
file like the following:
version: '3.7'
services:
jaeger:
image: jaegertracing/all-in-one:latest
ports:
- "16686:16686" # the jaeger UI
- "4317:4317" # the OpenTelemetry collector grpc
environment:
- COLLECTOR_OTLP_ENABLED=true
run the following command to bring up the docker-compose file:
$ docker-compose up -d
6. Send sample requests
Now you can send a request to microservice 1, and it will internally send a request to microservice 2 in the background:
$ curl http://localhost:8080/hello/path1
7. See traces on the Jaeger dashboard
now you can browse the Jaeger dashboard at the http://localhost:16686
address:
Github repository
You can find all the code used in this document at the following GitHub repository:
Feedback
If you have any feedback or suggestions for improving my code, please leave a comment on this post or send me an email at mallakimahdi@gmail.com. I would greatly appreciate your contributions to help make this article better. If you enjoyed this post, be sure to follow me to stay updated on my latest articles.