Spring Cloud Sleuth & Zipkin – Distributed Logging and Tracing


In standard applications, app logs are implemented into a single file which can be read for debugging purposes. However, apps which follows microservices architecture style comprises multiple small apps and multiple log files are to maintained to have at least one file per microservice. Due to this , identification and correlation of logs to a specific request chain becomes difficult.

For this, distributed logging & tracing mechanism can be implemented using tools like Sleuth, Zipkin, ELK etc

How to use Sleuth?

Sleuth is part of spring cloud libraries. It can be used to generate the traceid, spanid and add this information to the service calls in headers and mapped diagnostic context (MDC). These ids can be used by the tools such as Zipkin, ELK to store, index and process the log file.

To use sleuth in the app, following dependencies needs to be added

<dependency> 
<groupId>org.springframework.cloud</groupId> 
<artifactId>spring-cloud-starter-sleuth</artifactId> 
</dependency>

How to use Zipkin?

Zipkin contains two components

  • Zipkin Client
  • Zipkin Server

Zipkin client contains Sampler which collects data from ms apps with the help of sleuth and provides it the zipkin server.

To use zipkin client following dependency needs to be added in the application

<dependency> 
<groupId>org.springframework.cloud</groupId> 
<artifactId>spring-cloud-starter-zipkin</artifactId> 
</dependency>

To use zipkin server, we need to download and set up the server in our system

zipkin server

Implementation on microservice apps

To see distributed logging implementation, we need to create three services with the same configuration, the only difference has to be the service invocation details where the endpoint changes.

  • Create services as Spring boot applications with WebRest RepositoryZipkin and Sleuth dependencies.
  • Package services inside a single parent project so that three services can be built together. Also, I’ve added useful windows scripts in github repo to start/stop all the services with a single command
  • Below is one rest controller in service1 which exposes one endpoint and also invokes one downstream service using the RestTemplate. Also, we are using Sampler.ALWAYS_SAMPLE that traces each action.

Service 1

package com.mvtechbytes.service1;
 
import brave.sampler.Sampler;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpMethod;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
 
@SpringBootApplication
public class Service1Application {
 
    public static void main(String[] args) {
        SpringApplication.run(Service1Application.class, args);
    }
}
 
@RestController
class Service1Controller {

    private static final Logger LOG = Logger.getLogger(Service1Controller.class.getName());
     
    @Autowired
    RestTemplate restTemplate;
 
    @Bean
    public RestTemplate getRestTemplate() {
        return new RestTemplate();
    }
 
    @Bean
    public Sampler alwaysSampler() {
        return Sampler.ALWAYS_SAMPLE;
    }
     
    @GetMapping(value="/service1")
    public String service1() 
    {
        LOG.info("Inside Service 1..");         
String response = (String)   restTemplate.exchange("http://localhost:8082/service2", HttpMethod.GET, null, new ParameterizedTypeReference<String>() {}).getBody();
        return response;
    }
}

Appication Configuration

As all services will run in a single machine, so we need to run them in different ports. Also to identify in Zipkin, we need to give proper names. so configure the application name and port information in application.properties file under the resources folder.

application.propertiesserver.port = 8081
spring.application.name = zipkin-server1

Similarly, for the other 2 services, we will use ports 8082, 8083 and their name will also be zipkin-server2 and zipkin-server3

Also, we have intentionally introduced a delay in the second service so that we can view that in Zipkin.

Above project is available in below github location

Github repo : https://github.com/mvtechbytes/Zipkin-Sleuth

On running app using bat files

Find Traces
Individual Trace
Trace details

References

Spring Boot vs LoopBack – Node.js for developing Microservices


In this post, we will see comparison between Spring Boot and LoopBack – Node.js for implementing Microservices.

SpringBoot

Spring Boot is an open source Java-based framework used to create Microservices. It is developed by Pivotal Team and is used to build stand-alone and production ready spring applications.

Microservices architecture using Java Spring Boot

LoopBack – Node.js

Events and event-driven programming

Events are actions generated by the user or the system, like a click, a completed file download, or a hardware or software error.

Event-driven programming is a programming paradigm in which the flow of the program is determined by events. An event-driven program performs actions in response to events. When an event occurs it triggers a callback function.

Node.js is a platform that executes server-side JavaScript programs that can communicate with I/O sources like file systems and networks.

LoopBack

LoopBack is a highly extensible, open-source Node.js and TypeScript framework based on Express that enables you to quickly create APIs and microservices composed from backend systems such as databases and SOAP or REST services.

The diagram below demonstrates how LoopBack serves as a composition bridge between incoming requests and outgoing integrations. It also shows the different personas who are interested in various capabilities provided by LoopBack.

Advantages of LoopBack – Node.js and Spring Boot

LoopBack – Node.js Spring Boot
Lightweight, fast – loosely typed Java is statically-typed (type safety)
Javascript Community: growing rapidly Java Community: mature and thriving
Great for I/O tasks. Example: file writing and reading, network calls, Streaming Long-term support and maintainability for memory intensive applications
Single-threaded – low memory utilization Support for multi-threading
npm is constantly growing Many easily usable dependencies using Maven, Gradle

Disadvantages of LoopBack – Node.js and Spring Boot

LoopBack – Node.js

  • Doesn’t support multi-threading
  • Lack of strict type checking can lead to runtime problems
  • Not great for heavy computing – performance bottlenecks

Spring Boot

  • High memory utilization
  • Java is verbose
  • Contains lots of boilerplate code which makes debugging tough
  • May include unused dependencies – huge deployment binary file size.

Industry Usage of these technologies

Companies using Spring Boot

  • Amazon
  • Intuit
  • JP Morgan Chase & Co.
  • Capital One
  • Google
  • Microsoft

Companies using Node.js

  • FlightOffice
  • Symantec
  • Pen Systems
  • GoDaddy.com
  • Sapient

LoopBack vs SpringBoot on various parameters

Criteria / Parameter SpringBoot LoopBack
Performance Long-term support and maintainability for memory intensive applications Great for I/O tasks. Example: file writing and reading, network calls, Streaming
Circuit Breaker Resilience4j Opossum
Hystrix Levee
Soap Client Apache CXF, Camel, Spring WebServiceTemplate loopback-connector-soap
JSON Manipulation/Validation Jackson, Spring Validator payload-validator
Orchestration and Routing support Apache CXF, Camel, Spring WebServiceTemplate, RestTemplate loopback-connector-soap, loopback-connector-rest
Caching support Spring Cache, external cache support Interception – CachingService, external cache support
Open API Contract first, API first both are supported Contract first, API first both are supported
Recommended For Building applications which consists of Memory intensive tasks Building applications which consists of I/O intensive tasks

References:

Spring Boot and Netflix Eureka Integration


In this post we will learn how to integrate applications developed in Spring Boot with Netflix Eureka.

First step is to create two Spring Boot application services

  • customer-service
  • order-service

Go to https://start.spring.io and create order-service app using below config details.

Mainly for our use case, adding Spring Web, Eureka Server dependencies are the required ones

Likewise, create customer-service app with same config details used for order-service by adding Spring Web, Eureka Server dependencies.

Next, create eureka-server app with below config . Adding Eureka Server is main important config for our use case

Next, Import the projects created from the above into Eclipse IDE.

Add @EnableEurekaServer on EurekaServerApplication class as shown Below

package com.venkat;

@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {

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

Add below properties to the application.properties

server.port=8090
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false

Next, Add @EnableEurekaClient on CustomerServiceApplication class in customer-service  app as shown below

@SpringBootApplication
@EnableEurekaClient
public class CustomerServiceApplication {
public static void main(String[] args) throws RestClientException, IOException {
SpringApplication.run(CustomerServiceApplication.class, args);
}

@Bean
public ConsumerControllerClient consumerControllerClient()
{
return new ConsumerControllerClient();
}
}

Likewise, Add @EnableEurekaClient on OrderServiceApplication class in order-service app as shown below

@SpringBootApplication
@EnableEurekaClient
public class OrderSerivceApplication {

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

Next, Add below configuration in customer-service and order-service
application.properties files

customer-service

spring.application.name=customer-service
server.port=8091
eureka.client.serviceUrl.defaultZone=http://localhost:8090/eureka

order-service

spring.application.name=order-service
server.port=8080
eureka.client.serviceUrl.defaultZone=http://localhost:8090/eureka

Next, Create Order.java in model package as shown below with orderId, itemName, itemType, Cost attributes.

package com.venkat.model;

public class Order {
private String orderId;
private String itemName;
private String itemType;
private double cost;

public Order() {
}

public String getOrderId() {
return orderId;
}

public void setOrderId(String orderId) {
this.orderId = orderId;
}

public String getItemName() {
return itemName;
}

public void setItemName(String itemName) {
this.itemName = itemName;
}

public String getItemType() {
return itemType;
}

public void setItemType(String itemType) {
this.itemType = itemType;
}

public double getCost() {
return cost;
}

public void setCost(double cost) {
this.cost = cost;
}
}

Create OrderController with a REST GET mapping for /customerorder to return Order object

package com.venkat.controllers;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import com.venkat.model.Order;

@RestController
public class OrderController {

@RequestMapping(value = "/customerorder", method = RequestMethod.GET)
public Order customerOrder() {
Order order = new Order();
order.setOrderId("TIF567");
order.setItemName("Dosa");
order.setItemType("Tiffin");
order.setCost(40.00);
return order;
}
}

Create a ConsumerControllerClient.java class as shown below

package com.venkat.controllers;

import java.io.IOException;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;

@RestController
public class ConsumerControllerClient {

@Autowired
private DiscoveryClient discoveryClient;

@GetMapping(path="/customerorderinfo")
public String getCustomerOrder() throws RestClientException, IOException {

List<ServiceInstance> instances = discoveryClient.getInstances("ORDER-SERVICE");

// Creating URL for calling order-service
ServiceInstance serviceInstance = instances.get(0);
String baseURL = serviceInstance.getUri().toString();
String completeURL = baseURL + "/customerorder";

// Calling order-service
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> response = null;
try {
response = restTemplate.exchange(completeURL, HttpMethod.GET, getHeaders(), String.class);
} catch (Exception ex) {
System.out.println(ex);
}
System.out.println(response.getBody());
return response.getBody();
}

private static HttpEntity<?> getHeaders() throws IOException {
HttpHeaders headers = new HttpHeaders();
headers.set("Accept", MediaType.APPLICATION_JSON_VALUE);
return new HttpEntity<>(headers);
}

}

Final step is to Run EurekaServer, order-service, customer-service apps

Open http://localhost:8090 (port on which eureka server is running). Here you will see
order-service,customer-service app instances registered with it.

Output on hitting REST Endpoint http://localhost:8091/customerorderinfo is given below

Conclusion

In the above post we have seen how to register two service applications with Netflix Eureka
Server and communicate between the services via Eureka server registry without knowing the
host, port info of the service to which we are communicating with.