项目场景:
在项目引入xxljob后,希望方法中的log.info等日志可以同步打印一份到xxljob日志文件中,而不需要每个log.info后再打一句xxljobhelper.log。
通过配置日志过滤器成功实现了这一功能后,发现了另一个问题。
问题描述
在xxljob方法中有部分使用了异步自定义线程池,导致异步方法中的日志要么丢失、要么打到其他的日志文件中。
原因分析:
下面是xxljob上下文源码,可以看到存储在InheritableThreadLocal中,而InheritableThreadLocal特点是子线程会继承父线程的上下文,由于自定义线程池中部分线程初始化在项目启动时已经完成,所以这部分线程在项目启动时就已经赋值好了XxlJobContext。
总的来说,线程复用使得xxljob上下文在首次赋值之后就一直不变了,导致各线程日志打印混乱。
package com.xxl.job.core.context;
/**
* xxl-job context
*
* @author xuxueli 2020-05-21
* [Dear hj]
*/
public class XxlJobContext {
public static final int HANDLE_CODE_SUCCESS = 200;
public static final int HANDLE_CODE_FAIL = 500;
public static final int HANDLE_CODE_TIMEOUT = 502;
// ---------------------- base info ----------------------
/**
* job id
*/
private final long jobId;
/**
* job param
*/
private final String jobParam;
// ---------------------- for log ----------------------
/**
* job log filename
*/
private final String jobLogFileName;
// ---------------------- for shard ----------------------
/**
* shard index
*/
private final int shardIndex;
/**
* shard total
*/
private final int shardTotal;
// ---------------------- for handle ----------------------
/**
* handleCode:The result status of job execution
*
* 200 : success
* 500 : fail
* 502 : timeout
*
*/
private int handleCode;
/**
* handleMsg:The simple log msg of job execution
*/
private String handleMsg;
public XxlJobContext(long jobId, String jobParam, String jobLogFileName, int shardIndex, int shardTotal) {
this.jobId = jobId;
this.jobParam = jobParam;
this.jobLogFileName = jobLogFileName;
this.shardIndex = shardIndex;
this.shardTotal = shardTotal;
this.handleCode = HANDLE_CODE_SUCCESS; // default success
}
public long getJobId() {
return jobId;
}
public String getJobParam() {
return jobParam;
}
public String getJobLogFileName() {
return jobLogFileName;
}
public int getShardIndex() {
return shardIndex;
}
public int getShardTotal() {
return shardTotal;
}
public void setHandleCode(int handleCode) {
this.handleCode = handleCode;
}
public int getHandleCode() {
return handleCode;
}
public void setHandleMsg(String handleMsg) {
this.handleMsg = handleMsg;
}
public String getHandleMsg() {
return handleMsg;
}
// ---------------------- tool ----------------------
private static InheritableThreadLocal<XxlJobContext> contextHolder = new InheritableThreadLocal<XxlJobContext>(); // support for child thread of job handler)
public static void setXxlJobContext(XxlJobContext xxlJobContext){
contextHolder.set(xxlJobContext);
}
public static XxlJobContext getXxlJobContext(){
return contextHolder.get();
}
}
解决方案:
项目中依次创建这个路径com.xxl.job.core.context;写一个新的类XxlJobContext覆盖原来的类,类中引入阿里开源ttl:TransmittableThreadLocal
最后验证,问题解决。
另外,xxljob中异步方法报错,xxljob执行还是成功的,这个问题遗留。
package com.xxl.job.core.context;
import com.alibaba.ttl.TransmittableThreadLocal;
/**
* xxl-job context
* 覆盖xxljob源码 重写
* 修复xxljobhelper.log在线程池中日志打印混乱问题
*
* @author zdp 2024-06-14
*/
public class XxlJobContext {
public static final int HANDLE_CODE_SUCCESS = 200;
public static final int HANDLE_CODE_FAIL = 500;
public static final int HANDLE_CODE_TIMEOUT = 502;
// ---------------------- base info ----------------------
/**
* job id
*/
private final long jobId;
/**
* job param
*/
private final String jobParam;
// ---------------------- for log ----------------------
/**
* job log filename
*/
private final String jobLogFileName;
// ---------------------- for shard ----------------------
/**
* shard index
*/
private final int shardIndex;
/**
* shard total
*/
private final int shardTotal;
// ---------------------- for handle ----------------------
/**
* handleCode:The result status of job execution
*
* 200 : success
* 500 : fail
* 502 : timeout
*
*/
private int handleCode;
/**
* handleMsg:The simple log msg of job execution
*/
private String handleMsg;
public XxlJobContext(long jobId, String jobParam, String jobLogFileName, int shardIndex, int shardTotal) {
this.jobId = jobId;
this.jobParam = jobParam;
this.jobLogFileName = jobLogFileName;
this.shardIndex = shardIndex;
this.shardTotal = shardTotal;
this.handleCode = HANDLE_CODE_SUCCESS; // default success
}
public long getJobId() {
return jobId;
}
public String getJobParam() {
return jobParam;
}
public String getJobLogFileName() {
return jobLogFileName;
}
public int getShardIndex() {
return shardIndex;
}
public int getShardTotal() {
return shardTotal;
}
public void setHandleCode(int handleCode) {
this.handleCode = handleCode;
}
public int getHandleCode() {
return handleCode;
}
public void setHandleMsg(String handleMsg) {
this.handleMsg = handleMsg;
}
public String getHandleMsg() {
return handleMsg;
}
// ---------------------- tool ----------------------
private static TransmittableThreadLocal<XxlJobContext> contextHolder = new TransmittableThreadLocal<>(); // support for child thread of job handler)
public static void setXxlJobContext(XxlJobContext xxlJobContext){
contextHolder.set(xxlJobContext);
}
public static XxlJobContext getXxlJobContext(){
return contextHolder.get();
}
}