This tutorial describes how to add and visualize method level information to ZipKin using Spring Boot, Spring AOP, Spring Sleuth and Spring Cloud.
Introduction to ZipKin
ZipKin is a service tracing tool to identify and debug performance related problems in chain of service calls. It shows the time taken by all service calls in a request trace.
Spring Boot has good support for ZipKin using Spring Cloud ZipKin starter as it automatically instruments external service calls for you. All we need to do is to set up ZipKin and add ZipKin starter to our service pom.xml.
For more details, please refer to Service Tracing with Spring Boot and ZipKin.
Adding Zipkin Starter Dependency
First step is to add following dependency to dependencies section of your project's pom.xml.
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
Version of this starter will be maintained automatically from following Spring Cloud BOM that you need to add to your dependencyManagement/dependencies tag -
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
Please note that your Spring Boot project needs to be running with 2.x.x version.
Add Method Calls to ZipKin Traces
With default instrumentation/integration with Spring Boot, it is easy to find out service taking long time in a request. However, we sometimes may want to see which of internal calls are problematic.
In this tutorial, we will see how we can get method level traces in ZipKin with help of Spring AOP. We will utilize AOP to ensure that we don't need to go and introduce instrumentation logic between method calls.
Spring AOP has the concept of advices(logic that we want to add) and pointcuts(points where logic need to be added). Pointcuts are defined using expressions to target various execution points such as method calls.
For instance, here is the pointcut expression to target all public methods executions in base package com.aksain -
execution(public * com.aksain..*(..))
Next is to use a pointcut expression to apply an advice. Depending on at what point we want to apply our logic, advice can be of many types -
- Around Advice - It wraps actual method call and provides interception point for both before and after execution of this method
- Before Advice - It provides interception point before method call
- After Advice - It provides interception point after method call
- After Throwing Advice - It provides interception point after method call only if an exception is thrown
Since we need to intercept both entry and exit points of method call, we will use Around advice.
In order to enable AspectJ support in your project, add following to your main class having @SpringBootApplication annotation -
@EnableAspectJAutoProxy(proxyTargetClass=true)
Here is the class that will intercept all the public method calls in base package com.aksain.
package com.aksain.msa.author;
import javax.inject.Inject;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import brave.ScopedSpan;
import brave.Tracer;
/**
* Demonstrates adding local method calls to ZipKin traces.
*
*/
@Component
@Aspect
public class ZipKinTracingAspect {
@Value("${spring.application.name}")
private String appName;
@Inject
private Tracer tracer;
private ScopedSpan startNewSpan(String serviceName, String methodSignature) {
// Span name contains class and method name
final StringBuilder spanNameBuilder = new StringBuilder("/").append(methodSignature);
final ScopedSpan span = tracer.startScopedSpan(spanNameBuilder.toString());
if(span != null) {
span.tag("peer.service", serviceName);
}
return span;
}
private void addErrorDetailsToSpan(ScopedSpan span, Throwable throwable) {
if (span == null) {
return;
}
span.error(throwable);
}
private void closeSpan(ScopedSpan span) {
if (span == null) {
return;
}
span.finish();
}
@Around("execution(public * com.aksain..*(..))")
public Object traceAllMethodCalls(ProceedingJoinPoint pJoinPoint) throws Throwable {
final MethodSignature methodSignature = (MethodSignature)pJoinPoint.getSignature();
// We add local-ops after service name to signify that these spans are for internal method calls
final String serviceName = appName + "-" + "local-ops";
final String targetMethodSignature = methodSignature.toShortString();
final ScopedSpan span = startNewSpan(serviceName, targetMethodSignature);
try {
return pJoinPoint.proceed();
} catch (Exception exception) {
addErrorDetailsToSpan(span, exception);
throw exception;
} finally {
closeSpan(span);
}
}
}
Project Path in GitHub
In case you want to give it a try on a sample project, you can import it from GitHub Repo.
Thank you for reading through the tutorial. In case of any feedback/questions/concerns, you can communicate same to us through your comments and we shall get back to you as soon as possible.