Java は完全なリンク ログ追跡の一意の ID を実現します
ログの問題点:
Spring-Aop アスペクトを使用すると、コントロール層またはサービス層の開始位置と終了位置のデータ (つまり、リクエストの入出力パラメーター) のみを切り取ることができ、論理ログを見つけることができません。そして追跡された
ログを印刷するとこんな感じ
1.パラメータにseqが渡されない場合
LOGGER.error("xxx不能为空" );
2.パラメータにseqが渡されています
LOGGER.error("【" + seq + "】,xxx不能为空" );
1 つ目はより簡潔ですが、2 つ目はビジネス ロジックに侵入するため、毎回ステッチする必要があります。
解決策:
1. 単純な構成 (非同期スレッドにはいくつかの問題が発生します。log4j ログ)
1) これらの構成はプリインターセプターに配置でき、制御層は到着するとすぐに割り当てられます。
//前置拦截器
String logUid = UUID.randomUUID().toString();
//org.apache.logging.log4j.ThreadContext
ThreadContext.put("logId", logId);
//后置拦截器
//在请求结束时需要清理logId
ThreadContext.clearMap();
2) ログ印刷キー [logId::%X{logId}]
XML 設定バージョン
<console name="Console" target="SYSTEM_OUT">
<!--输出日志的格式-->
<PatternLayout pattern="[logId::%X{logId}][%d{yyyy-MM-dd HH:mm:ss.SSS}] [%p] - %l - %m%n"/>
<ThresholdFilter level="trace" onMatch="ACCEPT" onMismatch="DENY" />
</console>
スプリングブートバージョン
logging:
pattern:
#配置日志全链路跟踪 logId
console: "[logId::%X{logId}] [%d{yyyy-MM-dd HH:mm:ss.SSS}] [%p] - %l - %m%n"
2. 非同期スレッド (ログバック ログ) のキーポイントを含むリンク全体を追跡します
1). MDC (org.slf4j.MDC)
2). インターセプター (主に logId を挿入)
3). スレッド処理
一意の logId を生成するインターセプター コード
@Component
public class LogInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//如果有上层调用就用上层的ID
String traceId = request.getHeader(LogConstant.TRACE_ID);
if (traceId == null) {
traceId = TraceIdUtil.getTraceId();
}
MDC.put(LogConstant.TRACE_ID, traceId);
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
//调用结束后删除
MDC.remove(LogConstant.TRACE_ID);
}
}
ログリンク ID ツールクラスの取得 (UUID を使用してシリアル番号を生成)
public class TraceIdUtil {
private TraceIdUtil() {
throw new UnsupportedOperationException("Utility class");
}
/**
* 获取traceId
* @return
*/
public static String getTraceId() {
return UUID.randomUUID().toString().replace("-", "").toUpperCase();
}
}
ログ定数 (定数であり、新しいものではありません。実際には、インターフェイス クラスによって定義できます)
public class LogConstant {
private LogConstant(){
throw new UnsupportedOperationException();
}
/**
* 日志追踪ID
*/
public static final String TRACE_ID = "traceId";
}
mdcスレッドプロセッサ
public class ThreadMdcUtil {
public static void setTraceIdIfAbsent() {
if (MDC.get(LogConstant.TRACE_ID) == null) {
//插入唯一日志ID
MDC.put(LogConstant.TRACE_ID, TraceIdUtil.getTraceId());
}
}
public static <T> Callable<T> wrap(final Callable<T> callable, final Map<String, String> context) {
return () -> {
if (context == null) {
MDC.clear();
} else {
MDC.setContextMap(context);
}
setTraceIdIfAbsent();
try {
return callable.call();
} finally {
MDC.clear();
}
};
}
public static Runnable wrap(final Runnable runnable, final Map<String, String> context) {
return () -> {
if (context == null) {
MDC.clear();
} else {
MDC.setContextMap(context);
}
setTraceIdIfAbsent();
try {
runnable.run();
} finally {
MDC.clear();
}
};
}
}
スレッド構成クラス
@Slf4j
@Configuration
public class ExecutorConfig {
@Bean
@Primary
public Executor asyncServiceExecutor() {
log.info("start asyncServiceExecutor");
ThreadPoolExecutorMdcWrapper executor = new ThreadPoolExecutorMdcWrapper();
//配置核心线程数
executor.setCorePoolSize(10);
//配置最大线程数
executor.setMaxPoolSize(200);
//配置队列大小
executor.setQueueCapacity(99999);
//配置线程池中的线程的名称前缀
executor.setThreadNamePrefix("async-service-");
// 设置拒绝策略:当pool已经达到max size的时候,如何处理新任务
// CALLER_RUNS:不在新线程中执行任务,而是有调用者所在的线程来执行
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
//执行初始化
executor.initialize();
return executor;
}
}
スレッドプールの構成
@Slf4j
public class ThreadPoolExecutorMdcWrapper extends ThreadPoolTaskExecutor {
private void showThreadPoolInfo(String prefix){
ThreadPoolExecutor threadPoolExecutor = getThreadPoolExecutor();
if(null==threadPoolExecutor){
return;
}
log.info("{}, {},taskCount [{}], completedTaskCount [{}], activeCount [{}], queueSize [{}]",
this.getThreadNamePrefix(),
prefix,
threadPoolExecutor.getTaskCount(),
threadPoolExecutor.getCompletedTaskCount(),
threadPoolExecutor.getActiveCount(),
threadPoolExecutor.getQueue().size());
}
@Override
public void execute(Runnable task) {
showThreadPoolInfo("1. do execute");
super.execute(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()));
}
@Override
public void execute(Runnable task, long startTimeout) {
showThreadPoolInfo("2. do execute");
super.execute(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()), startTimeout);
}
@Override
public Future<?> submit(Runnable task) {
showThreadPoolInfo("1. do submit");
return super.submit(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()));
}
@Override
public <T> Future<T> submit(Callable<T> task) {
showThreadPoolInfo("2. do submit");
return super.submit(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()));
}
@Override
public ListenableFuture<?> submitListenable(Runnable task) {
showThreadPoolInfo("1. do submitListenable");
return super.submitListenable(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()));
}
@Override
public <T> ListenableFuture<T> submitListenable(Callable<T> task) {
showThreadPoolInfo("2. do submitListenable");
return super.submitListenable(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()));
}
}
インターセプター
@Configuration
@EnableWebMvc
public class WebMvcConfig implements WebMvcConfigurer {
@Autowired
private PreventRepeatSubmitInterceptor preventRepeatSubmitInterceptor;
@Autowired
private LogInterceptor logInterceptor;
@Override
public void addCorsMappings(CorsRegistry registry) {
//设置允许跨域的路径
registry.addMapping("/**") //映射地址
.allowedOrigins("*")//允许跨域地址
.allowedHeaders("*")
.allowCredentials(true)
.allowedMethods("GET", "POST")
.maxAge(3600);
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
//.excludePathPatterns("/wechatwork/**") .addPathPatterns("/order/**")
//防重复提交拦截器
registry.addInterceptor(preventRepeatSubmitInterceptor);
//日志拦截器
registry.addInterceptor(logInterceptor);//.addPathPatterns("/**");
}
}
最終効果