Tracing is a simple way how to visualize the flow of a request between components. A unique trace ID is generated for every new request. As some component receives the request a new span is assigned for that component and the span is added to the trace. The traces are then sent to jaeger collector. You can find more information about the tracing here.
How to implement tracing into your application
Dependencies
First, we need to add these dependencies to our pom.xml file.
org.springframework.cloud
spring-cloud-starter-sleuth
3.0.2
org.springframework.cloud
spring-cloud-sleuth-zipkin
3.0.2
Configuration
We also need to change the configuration file (application.yaml) for our application.
- Set the name of the application, for example, demo-app-spring-bl.
spring:
application:
name: demo-app-spring-bl
Add Zipkin and sleuth properties.- CodeNOW jaeger collector (deploy).
spring:
zipkin:
enabled: true
baseUrl: http://tracing-jaeger-collector.tracing-system:9411
sleuth:
propagation:
type: B3
tag:
enabled: true
- Localhost jaeger collector (running locally).
- To run jaeger in your local environment check this.
spring:
zipkin:
enabled: true
baseUrl: http://localhost:9411
sleuth:
propagation:
type: B3
tag:
enabled: true
- Tracing with APACHE Kafka.
spring:
sleuth:
propagation:
type: B3
tag:
enabled: true
messaging:
kafka:
enabled: true
Tracing and database queries
- You can visualize any call to the DB by adding aspects.
- This aspect automatically creates a new span for every query to the DB.
First, we need to add a TracingHelper class. This class can create new spans.
package org.example.service.tracing;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.Span.Kind;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.stereotype.Component;
@Component
public class TracingHelper {
private static final String CLASS = "class";
private static final String TYPE = "type";
private final Tracer tracer;
/**
* Constructor.
*
* @param tracer sleuth tracer
*/
public TracingHelper(Tracer tracer) {
this.tracer = tracer;
}
/**
* Create sleuth client span.
*
* @param remoteServiceName executed service name
* @param type tag 'type'
* @param className tag 'class'
* @return new tracing span
*/
public Span createClientSpan(String remoteServiceName, String type, String className) {
return tracer.nextSpan().tag(TYPE, type)
.tag(CLASS, className)
.name(remoteServiceName);
}
/**
* Start sleuth client span.
*
* @param span span
* @return new Tracer.spanInScope
*/
public Tracer.SpanInScope spanInScope(Span span) {
return tracer.withSpan(span.start());
}
}
Now, we need to add an aspect to our repository. Adding this aspect will cause a new span to be automatically created with every DB query and it will take care of the whole lifecycle of the created span.
package org.example.service.tracing;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.sleuth.Span;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class RepositoryTracingAspect {
private static final String TRACING_TYPE = "repository";
private TracingHelper tracingHelper;
@Autowired
public RepositoryTracingAspect(final TracingHelper tracingHelper) {
this.tracingHelper = tracingHelper;
}
@Around("within(org.springframework.data.repository.CrudRepository+)")
public Object traceRepositoryCalls(ProceedingJoinPoint joinPoint) throws Throwable {
String className = joinPoint.getSignature().getDeclaringTypeName();
String targetMethod = joinPoint.getSignature().getName();
Object proceed;
Span span = tracingHelper.createClientSpan(targetMethod, TRACING_TYPE, className);
try (Tracer.SpanInScope ws = tracingHelper.spanInScope(span)) {
proceed = joinPoint.proceed();
} finally {
span.end();
}
return proceed;
}
}
Manually created spans
- We can even create new spans manually by using the createClientSpan function from TracingHelper class. If we want to create spans manually, we have to take care of the lifecycle of the created span on our own. Once we have the span created, we need to start it with the spanInScope function from TracingHelper class. Every span needs to be ended and because our code could produce some exceptions we need to warp it into a try(){} finally{} block as is shown below.
Code can look like this:
Span span = tracingHelper.createClientSpan("Legacy App 1",
"service",
this.getClass().getName())
try(Tracer.SpanInScope ws = tracingHelper.spanInScope(span)){
// Enter your code here
} finally {
span.end();
}