Tracing in Spring Boot

Let's do some tracing. Tracing is an essential part of a microservice architecture. You need to be able to examine an event lifecycle. In a microservice architecture and event will flow through many microservices, transforming, forking, and merging. In a monolith, we can log the events in one log file and see everything that is going on. In a microservice architecture, we need a way to combine the logs from multiple microservices and tie together the log messages generated by a unique event. Enter Spring Cloud Sleuth. Spring Cloud Sleuth is an abstraction over a tracing library. In this example, we will use Brave as the underlying tracing library and Zipkin to record the events. Let's get started.

Start Zipkin

Run this docker command

bash docker-compose up --remove-orphans

Open a browser to http://localhost:9411/

# Create a sample Service and WebApp to generate traces and spans.

## Sample Service
Head over to https://start.spring.io/ and add the dependency **Spring Web**, **Spring Boot DevTools**, **Lombok**, **Slueth**, and **Zipkin Client**. Choose a Maven project type and use the Spring Boot version 2.3.3 and call the artifact 'service'. Import the project into SpringToolSuite or your favorite IDE.

Edit the application properties.

> src/main/resources/application.properties

```properties
server.port=8081
spring.application.name=sample-service

Add a simple controller and use annotations to create a span.

src/main/java/com/example/service/RootController.java

package com.example.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.sleuth.annotation.NewSpan;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Controller
public class RootController {
	@Autowired
	HelloService helloService;

	@GetMapping("/")
	@NewSpan("RootController#helloWorld")
	@ResponseBody
	public String helloWorld() {
		log.info("Handling home");
		return helloService.createMessage();
	}
}

Add a simple service and use both annotations and explicit calls to create spans.

src/main/java/com/example/service/HelloService.java

package com.example.service;

import java.text.SimpleDateFormat;
import java.util.Date;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.sleuth.annotation.NewSpan;
import org.springframework.stereotype.Service;

import brave.ScopedSpan;
import brave.Tracer;

@Service
public class HelloService {
	@Autowired
	private Tracer tracer;

	SimpleDateFormat timeFormater = new SimpleDateFormat("hh:mm:ss");

	@NewSpan("HelloService#createMessage with explicit span")
	public String createMessage() {
		ScopedSpan span = tracer.startScopedSpan("Add date to hello world");
		String message = "Hello World at " + timeFormater.format(new Date());
		span.finish();
		return message;
	}
}

Sample WebService

Head over to https://start.spring.io/ and add the dependency Spring Web, Spring Boot DevTools, Slueth, and Zipkin Client. Choose a Maven project type and use the Spring Boot version 2.3.3 and call the artifact 'webapp'. Import the project into SpringToolSuite or your favorite IDE.

Edit the application properties.

src/main/resources/application.properties

server.port=8080
spring.application.name=sample-webapp
service.base.url=http://localhost:8081

Edit the application main to include a RestTemplate bean.

src/main/java/com/example/service/WebappApplication.java

package com.example.webapp;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
public class WebappApplication {

	public static void main(String[] args) {
		SpringApplication.run(WebappApplication.class, args);
	}

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

Add a simple controller and use annotations to create a span.

src/main/java/com/example/service/RootController.java

package com.example.webapp;


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.sleuth.annotation.NewSpan;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.client.RestTemplate;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Controller
public class RootController {
	@Autowired
	RestTemplate restTemplate;

	@Value( "${service.base.url}" )
	private String serviceBaseUrl;

	@GetMapping("/")
	@NewSpan("RootController#helloWorld")
	@ResponseBody
	public String helloWorld() {
		log.info("Handling home");
		return restTemplate.getForEntity(serviceBaseUrl+"/", String.class).getBody();
	}
}

Test it

We can start our sample service and web app now in your IDE or at the command line. Note the -pl flag is required because I am using a multimodule pom

mvn spring-boot:run -pl service
mvn spring-boot:run -pl webapp

Open a browser to http://localhost:8080/ will generate a trace. Now go to http://localhost:9411/ and run a query for the generated trace.

Dockerize it

Build the images. You should replace jameskolean whith your docker hub ID.

mvn -pl webapp spring-boot:build-image -Dspring-boot.build-image.imageName=jameskolean/tracing-spring-boot-webapp-sample
mvn -pl service spring-boot:build-image -Dspring-boot.build-image.imageName=jameskolean/tracing-spring-boot-service-sample

Create a docker-compose to start all the services.

docker-compose.yml

version: '3'
services:
  zipkin:
    image: openzipkin/zipkin
    container_name: zipkin
    ports:
      - '9411:9411'
  service-sample:
    image: jameskolean/tracing-spring-boot-service-sample
    container_name: service-sample-1
    ports:
      - '8081:8081'
    environment:
      - spring.zipkin.base-url=http://zipkin:9411
      - spring.application.instance_id=sample-1
  webapp-sample:
    image: jameskolean/tracing-spring-boot-webapp-sample
    container_name: webapp-sample-1
    ports:
      - '8080:8080'
    environment:
      - service.base.url=http://service-sample:8081
      - spring.zipkin.base-url=http://zipkin:9411
      - spring.application.instance_id=webapp-1

Make sure that all docker images are stopped. Start the services.

docker-compose up

Opening a browser to http://localhost:8080/ will generate a trace.

Now go to http://localhost:9411/ and run a query for the generated trace.


Copyright © 2020 Code Green LLC