使用 AOP 在 Spring Boot 中实现跟踪和日志记录
在现代应用程序中,尤其是使用微服务构建的应用程序,跟踪和日志记录在跟踪流经各种服务的请求方面起着至关重要的作用。跟踪可帮助开发人员诊断问题、监控性能并了解用户在多个系统中的旅程。
在此博客中,我们将介绍如何使用traceId
从前端生成的代码在 Spring Boot 应用程序中实现跟踪和日志记录,我们还将探索在微服务环境中进行分布式跟踪的选项。
什么是追踪?
跟踪是指为traceId
请求分配一个唯一标识符(通常称为),并确保该标识符在请求经过应用程序中的不同层或服务时随请求一起移动。这对于了解请求流至关重要,尤其是在排查错误或性能瓶颈时。
traceId
为什么从前端生成并发送?
在微服务架构中,请求通常会流经多个服务和组件。traceId
在前端生成请求具有以下几个优点:
- 端到端可视性:
traceId
从用户交互开始,可以更轻松地跟踪从前端到后端服务的请求。这确保了对整个堆栈中用户操作的完全可视性。 - 一致跟踪:通过在前端生成
traceId
并将其包含在每次请求中,相同的内容traceId
会传播到所有服务。这可以实现跨多个 API 调用和服务的一致跟踪。 - 会话级跟踪:当用户会话开始时,前端可以生成一个会话
traceId
并将其用于该会话中的所有请求。这允许开发人员跟踪用户在单个会话中执行的所有操作。
步骤 1:traceId
从 React生成并发送
让我们首先在 React 前端生成traceId
并将其与每个 API 请求一起发送。
traceId
在 React 中生成
我们将使用该uuid
库为每个会话生成一个唯一的标识符:
npm install uuid
然后,创建一个生成或检索的实用函数traceId
:
import { v4 as uuidv4 } from 'uuid' ;
function getOrCreateTraceId ( ) {
let traceId = localStorage . getItem ( 'traceId' );
if (!traceId) {
traceId = uuidv4 (); // 为 traceId 生成一个新的 UUID
localStorage . setItem ( 'traceId' , traceId); // 存储它以供将来的请求使用
}
return traceId;
}
添加traceId
Axios 拦截器
为了确保traceId
自动包含在每个 API 请求中,我们更新了 Axios 拦截器:
const axios = require('axios');
import { getOrCreateTraceId } from './traceIdUtil';
const instanceUrl = axios.create({
baseURL: 'http://localhost:8080/',
transformRequest: [
function (data, headers) {
let jwt = localStorage.getItem('jwt');
if (jwt) {
headers.Authorization = 'Bearer ' + jwt;
}
// Include traceId in every request header
headers['X-Trace-Id'] = getOrCreateTraceId();
return JSON.stringify(data);
}
],
headers: {
'Content-Type': 'application/json',
'Cache-Control': 'no-cache',
Pragma: 'no-cache'
}
});
现在,从 React 前端发送的每个请求都将在标头traceId
中包含X-Trace-Id
。
第 2 步:traceId
在 Spring Boot 中处理
在 Spring Boot 后端,我们需要捕获此信息traceId
并确保在整个应用程序生命周期中始终使用它进行日志记录。我们使用 SpringMapped Diagnostic Context (MDC)
来存储traceId
和userId
。
设置过滤器以捕获traceId
我们将使用 Spring Boot 中的过滤器来拦截每个传入请求,从请求标头中提取traceId
,并将其存储在 MDC 中:
import org.slf4j.MDC;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.UUID;
@Component
public class TraceAndUserFilter extends HttpFilter {
private static final String TRACE_ID = "traceId";
private static final String USER_ID = "userId";
private static final String HEADER_TRACE_ID = "X-Trace-Id";
@Override
protected void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
// Get traceId from the request header, or generate a new one if missing
String traceId = request.getHeader(HEADER_TRACE_ID);
if (traceId == null || traceId.isEmpty()) {
traceId = UUID.randomUUID().toString();
}
MDC.put(TRACE_ID, traceId);
// Retrieve userId from the security context
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String userId = (authentication != null && authentication.isAuthenticated()) ? authentication.getName() : "ANONYMOUS";
MDC.put(USER_ID, userId);
try {
chain.doFilter(request, response); // Continue with the next filter in the chain
} finally {
// Remove traceId and userId from MDC after the request is processed
MDC.remove(TRACE_ID);
MDC.remove(USER_ID);
}
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
super.init(filterConfig);
}
@Override
public void destroy() {
super.destroy();
}
}
步骤 3:使用 AOP(面向方面编程)进行日志记录
通过将traceId
和userId
存储在 MDC 中,我们可以使用 Spring AOP 来记录整个系统中的方法进入和退出。这种方法允许我们在调用方法时自动记录,而不会使业务逻辑变得混乱。
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;
@Aspect
@Component
@Slf4j
public class LoggingAspect {
private static final String TRACE_ID = "traceId";
private static final String USER_ID = "userId";
@Around("execution(* com.example.application..*(..))")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
String traceId = MDC.get(TRACE_ID);
String userId = MDC.get(USER_ID);
String className = joinPoint.getSignature().getDeclaringTypeName();
String methodName = joinPoint.getSignature().getName();
// Log entering the method
log.info("TraceId: {}, UserId: {}, Class: {}, Entering method {} with parameters {}", traceId, userId, className, methodName, joinPoint.getArgs());
Object result;
try {
result = joinPoint.proceed(); // Proceed with the method execution
} catch (Throwable throwable) {
log.error("TraceId: {}, UserId: {}, Class: {}, Exception in method {}: {}", traceId, userId, className, methodName, throwable.getMessage());
throw throwable;
}
// Log exiting the method
if (result != null) {
log.info("TraceId: {}, UserId: {}, Class: {}, Exiting method {} with return value {}", traceId, userId, className, methodName, result);
} else {
log.info("TraceId: {}, UserId: {}, Class: {}, Exiting method {} with no return value", traceId, userId, className, methodName);
}
return result;
}
}
步骤 4:配置日志记录以包括traceId
和userId
配置您的日志系统以在所有日志条目中包含traceId
和userId
。例如,在 Logback 中,您可以修改logback.xml
以包含以下值:
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg [traceId=%X{traceId}] [userId=%X{userId}]%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="STDOUT" />
</root>
</configuration>
步骤 5:分布式跟踪选项
虽然从前端发送traceId
有助于追踪用户在系统中的旅程,但在更复杂的微服务架构中,通常需要分布式跟踪工具才能获得跨多个服务的整体视图。
以下是微服务环境中分布式跟踪的一些选项:
- Jaeger:Uber 构建的开源端到端分布式跟踪工具。它与 Spring Boot 集成良好,有助于可视化请求如何在服务之间传播。Jaeger
允许您监控服务的延迟和性能,并提供服务交互的详细视图。 - Zipkin:一种流行的开源跟踪系统,可帮助收集解决微服务架构中的延迟问题所需的时间数据。Zipkin 可捕获跟踪并帮助可视化服务之间的依赖关系。Zipkin
设置简单,广泛用于分布式系统的性能监控。 - OpenTelemetry :一个
高度可扩展的框架,用于收集和导出跟踪数据。OpenTelemetry 支持多个后端(Jaeger、Zipkin、Prometheus 等),并且可以轻松集成到 Spring Boot 中。OpenTelemetry正在成为分布式系统中跟踪和指标收集的标准。
结论
通过从前端发送traceId
并使用 Spring Boot 和 AOP 将其传播到整个后端,我们可以实现有效的跟踪以实现端到端可见性。此外,Jaeger、Zipkin 和 OpenTelemetry 等分布式跟踪工具可以帮助您跟踪跨多个服务的请求,从而使微服务中的调试和监控变得更加容易。
通过此设置,您可以跟踪整个系统的请求,无论是单个应用程序还是复杂的微服务网络,确保易于追踪问题并快速识别性能瓶颈。