用拦截器解决xxljob适配sleuth

在XXLJob项目中集成Sleuth实现任务调度日志追踪的问题与解决方案
文章描述了作者在使用XXLJob作为任务调度中心时遇到的问题,即调度任务的代码日志缺乏TraceId和SpanId。为便于问题追踪,作者尝试使用Sleuth的日志增强类并自定义了一个AOP拦截器添加这些ID。然而,当执行异步方法时,发现新的traceId和spanId会替换旧值,而非仅更新spanId,导致链路跟踪不清晰。
部署运行你感兴趣的模型镜像

项目中使用了xxljob做任务调度中心,使用过程中发现一个问题,xxljob调度的任务代码日志没有traceId和spanId等信息,为了放方便问题跟踪,所以要做适配。

网上调研,sleuth的日志增强类

org.springframework.cloud.sleuth.autoconfig.TraceEnvironmentPostProcessor
 
    @Override
    public void postProcessEnvironment(ConfigurableEnvironment environment,
            SpringApplication application) {
        Map<String, Object> map = new HashMap<String, Object>();
        // This doesn't work with all logging systems but it's a useful default so you see
        // traces in logs without having to configure it.
        if (Boolean.parseBoolean(environment.getProperty("spring.sleuth.enabled", "true"))) {
            map.put("logging.pattern.level",
                    "%5p [${spring.zipkin.service.name:${spring.application.name:-}},%X{X-B3-TraceId:-},%X{X-B3-SpanId:-},%X{X-Span-Export:-}]");
        }
        addOrReplace(environment.getPropertySources(), map);
    }

所以按照这个思路我写了个拦截器来为使用@XxlJob注解的方法手动添加traceId和spanId

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;

import java.util.UUID;

@Aspect
@Component
@Slf4j
public class XxlJobInterceptor{
    private static final String TRACE_ID = "X-B3-TraceId";
    private static final String SPAN_ID = "X-B3-SpanId";
    private static final String SPAN_EXPORT = "X-Span-Export";

    @Around(value = "@annotation(com.xxl.job.core.handler.annotation.XxlJob)")
    public Object traceId(final ProceedingJoinPoint pjp) throws Throwable {
        try {
            String id = UUID.randomUUID().toString().replace("-","");
            MDC.put(TRACE_ID, id);
            MDC.put(SPAN_ID, id);
            MDC.put(SPAN_EXPORT, "true");
            return pjp.proceed();
        }finally {
            MDC.remove(TRACE_ID);
            MDC.remove(SPAN_ID);
            MDC.remove(SPAN_EXPORT);
        }
    }
}

测试了下,traceId和spanId都成功添加,然后我又测试了异步方法

    @XxlJob("test")
    public void testLog(){
        log.info("===================任务开始=====================");
        testService.test();
        log.info("===================任务结束=====================");
    }


@Service
@Slf4j
public class TestService {

    @Async
    public void test(){
        log.info("-----------------------------test-----------------------------------");
    }
}

发现一个问题,traceId和spanId虽然都还在,但都是一个新值,老的traceId无法保留。正常的情况应该是traceId仍然是老值,只有spanId变化,这样说明是一个链路中线程发生了转换,但是这里traceId和spanId都变化了,此问题尚未解决。

资料:SpringCloud Logback日志配置输出 sleuth 中的 span和traceid_logback如何打印spanid-优快云博客

您可能感兴趣的与本文相关的镜像

ACE-Step

ACE-Step

音乐合成
ACE-Step

ACE-Step是由中国团队阶跃星辰(StepFun)与ACE Studio联手打造的开源音乐生成模型。 它拥有3.5B参数量,支持快速高质量生成、强可控性和易于拓展的特点。 最厉害的是,它可以生成多种语言的歌曲,包括但不限于中文、英文、日文等19种语言

<think>我们正在处理的问题是:在微服务中使用JPA开发时,表数据莫名其妙被修改,且Controller层没有相关请求日志。用户询问是否可以使用拦截器来添加日志进行排查。 根据之前的讨论,我们已经知道数据修改可能不经过Controller层(如直接调用Service、定时任务、JPA回调等)。因此,使用拦截器(Interceptor)只能拦截Controller的请求,对于非Controller途径的修改无效。 但是,用户特别询问了拦截器,我们需要明确: 1. 拦截器的作用范围:Spring MVC的拦截器作用于Controller层,只能拦截HTTP请求。 2. 如果数据修改是由HTTP请求触发的,但Controller日志没有记录,可能是日志配置问题,此时拦截器可以记录请求信息。 3. 如果数据修改不是由HTTP请求触发的(如定时任务、异步处理、直接数据库操作等),则拦截器无法捕获。 因此,我们可以分两种情况讨论: 情况一:数据修改确实由HTTP请求触发,但Controller日志遗漏(例如日志级别设置过高,或日志输出位置不对)。 - 此时,使用拦截器可以补充记录请求信息,帮助排查。 情况二:数据修改不是由HTTP请求触发(即绕过Controller层)。 - 这种情况使用拦截器无效,我们需要采用其他方法(如之前提到的Service层日志、Repository层日志、JPA回调日志、AOP等)。 接下来,我们详细说明如何使用拦截器来补充记录HTTP请求,并指出其局限性。 步骤1:创建拦截器类,记录请求信息 我们可以创建一个实现`HandlerInterceptor`接口的拦截器,在请求进入Controller之前记录请求的URL、参数等信息。 示例代码: ```java import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @Component public class RequestLoggingInterceptor implements HandlerInterceptor { private static final Logger logger = LoggerFactory.getLogger(RequestLoggingInterceptor.class); @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { // 记录请求的URL、方法、参数等信息 logger.info("Request URL: {}, Method: {}, Parameters: {}", request.getRequestURL(), request.getMethod(), request.getParameterMap()); return true; } } ``` 步骤2:注册拦截器,使其生效 通过配置类将拦截器添加到拦截器注册表中。 ```java import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class WebConfig implements WebMvcConfigurer { @Autowired private RequestLoggingInterceptor requestLoggingInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(requestLoggingInterceptor); } } ``` 这样,每次HTTP请求到达Controller之前,都会在日志中记录请求信息。 但是,请注意: - 拦截器只能记录HTTP请求,对于非HTTP请求触发的数据修改无效。 - 如果Controller层没有日志,但拦截器有日志,说明请求确实到达了Web层,但可能未进入Controller方法(如被全局异常处理器处理)或Controller日志配置有问题。 - 如果拦截器也没有记录,则说明请求根本没有经过Web层,需要从其他途径排查。 因此,拦截器可以作为排查Web层请求的一种补充手段,但不能解决所有问题。我们仍需结合其他方法: 推荐的综合排查策略(包括拦截器): 1. 启用拦截器日志,确认是否有可疑HTTP请求。 2. 在Service层和Repository层添加详细日志(如前所述)。 3. 在JPA实体中使用生命周期回调(如`@PreUpdate`)添加日志。 4. 使用AOP监控所有Service方法或Repository方法的执行。 5. 检查是否有定时任务、消息监听等后台进程。 6. 检查数据库是否有触发器、存储过程等。 7. 启用数据库审计日志(如MySQL的binlog)追踪数据变更来源。 通过以上多层次的日志记录,我们可以逐步定位问题。 最后,关于拦截器的局限性:它只能解决Web请求的日志记录问题,对于非Web请求引起的数据修改,拦截器无能为力。因此,在排查时,我们需要根据实际情况选择合适的方法。 如果用户已经添加了拦截器日志,但仍然没有捕获到导致数据修改的请求,那么基本可以确定修改不是由HTTP请求引起的,需要转向其他排查方向。 相关问题: 1. 在Spring Boot中,如何配置拦截器来记录HTTP请求? 2. 除了拦截器,还有哪些方法可以监控Spring MVC的请求? 3. 如何使用JPA的生命周期回调来追踪实体变更? </think>### 使用拦截器排查JPA数据莫名修改问题 是的,**可以使用拦截器添加日志排查数据莫名修改问题**,但需要针对不同场景选择合适的拦截层级。以下是具体方案: #### 📌 拦截器适用场景分析 | 拦截类型 | 覆盖范围 | 能否解决当前问题 | |----------------|------------------------|------------------------------| | **Controller拦截器** | 仅HTTP请求入口 | ❌ 无法捕获非HTTP请求的修改 | | **Service层拦截器** | 业务逻辑层操作 | ✅ 可捕获Service内部数据修改 | | **Repository拦截器** | 数据库操作层 | ✅ 可直接监控JPA数据变更 | | **JPA生命周期回调** | 实体持久化过程 | ✅ 捕获自动更新机制触发修改 | #### 🔍 推荐解决方案(按优先级排序) 1. **Repository层拦截器(最优解)** 使用Spring AOP拦截所有JPA Repository操作: ```java @Aspect @Component public class RepositoryAuditAspect { private static final Logger logger = LoggerFactory.getLogger(RepositoryAuditAspect.class); // 拦截所有save/update操作 @Pointcut("execution(* org.springframework.data.jpa.repository.JpaRepository.save*(..))") public void repositoryUpdateMethods() {} @Before("repositoryUpdateMethods()") public void logBeforeUpdate(JoinPoint joinPoint) { Object entity = joinPoint.getArgs()[0]; logger.warn("准备更新实体: {}", entityToString(entity)); } @AfterReturning(pointcut = "repositoryUpdateMethods()", returning = "result") public void logAfterUpdate(JoinPoint joinPoint, Object result) { logger.warn("实体更新完成: {}", entityToString(result)); } private String entityToString(Object entity) { return String.format("Class: %s, ID: %s, Hash: %d", entity.getClass().getSimpleName(), ReflectionUtils.getFieldValue(entity, "id"), entity.hashCode()); } } ``` **效果**:监控所有通过JPA执行的修改操作,即使未经过Controller[^1] 2. **JPA实体生命周期拦截** 在实体类中添加回调日志: ```java @Entity public class YourEntity { @Id private Long id; private String data; @PreUpdate public void preUpdate() { // 获取调用栈信息 StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); logger.error("实体自动更新触发! ID: {} | 调用栈: {}", id, Arrays.stream(stackTrace) .limit(5) .map(StackTraceElement::toString) .collect(Collectors.joining("\n"))); } } ``` 3. **Service层方法拦截** 针对Service方法添加切面: ```java @Pointcut("within(@org.springframework.stereotype.Service *)") public void serviceMethods() {} @Around("serviceMethods() && execution(* *..update*(..))") public Object logServiceUpdate(ProceedingJoinPoint joinPoint) throws Throwable { String methodName = joinPoint.getSignature().getName(); Object[] args = joinPoint.getArgs(); logger.info("Service方法调用: {} | 参数: {}", methodName, Arrays.toString(args)); Object result = joinPoint.proceed(); if(result instanceof YourEntity) { logger.info("Service修改结果: {}", ((YourEntity)result).toString()); } return result; } ``` #### ️ 关键注意事项 1. **拦截器局限性** - 无法捕获直接SQL操作(如`EntityManager.createNativeQuery()`) - 不能监控数据库触发器或外部系统修改 - 需配合数据库审计日志(如启用MySQL的binlog)[^2] 2. **增强措施** ```properties # application.properties 添加 spring.jpa.properties.hibernate.session.events.log=true logging.level.org.hibernate.event.internal=DEBUG ``` 3. **生产环境建议** - 临时开启`DEBUG`级别日志,避免性能影响 - 添加分布式追踪ID(如Sleuth的TraceID)关联跨服务操作 - 结合APM工具(如SkyWalking)监控JPA执行链路 > **最佳实践**:建议同时使用**Repository拦截器+实体生命周期回调**,覆盖95%的JPA数据修改场景。若仍无法定位,需启用数据库层面的审计日志[^2]。
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值