Distributed Tracing with Spring Cloud Jaeger

In this article, we will explore how to implement distributed tracing using Jaeger and visualize the traces using Jaeger UI.

Introduction

I have explained some key concepts of tracing in my previous article “Distributed Tracing With Spring Cloud Sleuth”.

With this, let’s look at some code.

Implementing Jaeger Tracing

Once you generate and download the code, we will add the following Jaeger dependency to the pom file which will help to generate and propagate the traces between the services.

<dependency>
<groupId>io.opentracing.contrib</groupId>
<artifactId>opentracing-spring-jaeger-cloud-starter</artifactId>
<version>3.3.1</version>
</dependency>

With this, let's add a controller with some paths.

@RestController
@RequestMapping("/service")
public class Controller {

private static final Logger logger = LoggerFactory.getLogger(Controller.class);
private RestTemplate restTemplate;

@Value("${spring.application.name}")
private String applicationName;

public Controller(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}

@GetMapping("/path1")
public ResponseEntity path1() {

logger.info("Incoming request at {} for request /path1 ", applicationName);
String response = restTemplate.getForObject("http://localhost:8090/service/path2", String.class);
return ResponseEntity.ok("response from /path1 + " + response);
}

@GetMapping("/path2")
public ResponseEntity path2() {
logger.info("Incoming request at {} at /path2", applicationName);
return ResponseEntity.ok("response from /path2 ");
}
}

Here, we have two endpoints /path1 and /path2 . The idea here is to use two instances of the same application such that/path1 calls /path2 of another service at a fixed port 8090.

For the spans to get connected to the same trace id, We need to create a RestTemplate bean to allow Jaeger to include an interceptor. This then helps to add traces to the outgoing request which will help to trace the entire request.

@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder.build();
}

With this done, Let’s start a Jaeger Server locally using docker. For this, I have created a docker-compose file with the port mappings.

version: "3.3"
services:
jaeger-allinone:
image: jaegertracing/all-in-one:1.25
ports:
- 6831:6831/udp
- 6832:6832/udp
- 16686:16686
- 14268:14268

We can communicate with Jaeger using either via UDP or TCP. After starting the docker image using docker-compose up , we can access the UI using the URL http://localhost:16686/

Now, let's add some properties to allow the application to send the traces to the Jaeger server. We will communicate via TCP, so make sure that we send the traces to the other TCP port. i.e 14268

opentracing:
jaeger:
http-sender:
url: http://localhost:14268/api/traces

Let’s start “Server 1” with the below command.

java -jar \
target/Distributed-Service-0.0.1-SNAPSHOT.jar \
--spring.application.name=Service-1 \
--server.port=8080

Then on a different terminal, run a new instance of the same application as “Service 2” as follows

java -jar \
target/Distributed-Service-0.0.1-SNAPSHOT.jar \
--spring.application.name=Service-2 \
--server.port=8090

Once the application starts, call “Service 1” at /path1 as follows

curl -i http://localhost:8080/service/path1

Let’s look at the logs of “Service 1”.

INFO 69938 --- [nio-8080-exec-1] i.j.internal.reporters.LoggingReporter   : Span reported: ed70bbaa2bd5b42f:c7c94163fc95fc1e:ed70bbaa2bd5b42f:1 - GET

The tracing is of the format [Root Span Id, Current Span Id, Parent Span Id]. In this case, since “Service 1” is the originating service, the parent span Id “ed70bbaa2bd5b42f” is also the root span id.

Now, let’s look at the logs of “Service 2”.

INFO 69885 --- [nio-8090-exec-1] i.j.internal.reporters.LoggingReporter   : Span reported: ed70bbaa2bd5b42f:e9060cb1d5336c55:c7c94163fc95fc1e:1 - path2

Here we see that the middle value is the current span id and the parent span id (ie. the third value “c7c94163fc95fc1e”) is the span id of “Service 1”.

Now, If you open the UI you will see the following.

When we dig deeper, we see more details on each of the spans.

Here, the root span id “ed70bbaa2bd5b42f” spans across the entire request. The other two span ids refer to the individual services.

Conclusion

I have uploaded the code on GitHub.

You can read about Distributed tracing using Zipkin my previous article here.

I keep exploring and learning new things. If you want to know the latest trends and improve your software development skills, then subscribe to my newsletter on https://refactorfirst.com and also follow me on Twitter.

Enjoy!!

Software Craftsman, Tech Enthusiast. I run https://refactorfirst.com to post all my articles