buguge - Keep it simple,stupid

知识就是力量,但更重要的,是运用知识的能力why buguge?

导航

异步线程里的日志难以追踪?小支一招,轻松搞定!

HTML源码编辑器自动换行

众所周知,通过唯一的链路追踪id(TraceId)来追踪一次请求的所有日志,对于排查生产问题来说,会是非常给力的。
实现方案是重写工作线程的线程名(java.lang.Thread#getName),让它的值在整个java服务生命周期(或其中一段时间比如某一天)中是唯一的。我之前的博客也有多次提及 ▄︻┻┳═一 https://www.cnblogs.com/buguge/tag/日志链路追踪/
那么,如果涉及到异步线程处理的话,我们知道,由于异步线程与工作线程是两个不同的线程,因此,这时的线程名会发生变化。一次请求的完整日志就无法通过唯一的标识来过滤了。

 

有没有办法呢?
问题即答案。当然是有的。

 

线程是用来执行任务的,任务是一段程序代码的封装。在java中,任务通过 java.lang.Runnable 来表示。使用方面,我们可以自己定义一个实现Runnable的任务类,也可以用lambda表达式的方式直接使用Runnable,来作为线程或线程池的参数。

 

自定义实现了Runnable的类来使用异步线程,先贴demo代码。

实现了Runnable接口的类--MyTask:

package com.emaxcard.test;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class MyTask implements Runnable {
    String flag;

    public MyTask(String flag) {
        this.flag = flag;
    }

    @Override
    public void run() {
        log.info("in 异步线程.入参={}", flag);
    }
}
View Code

主线程测试类:

package com.emaxcard.test;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class RunnableTest {
    public static void main(String[] args) {
        log.info("主线程begin");
        new Thread(new MyTask("mytest")).start();
        log.info("主线程end");
    }

}
View Code

运行测试类,打印出来的log如下:

[main] buguge.test.RunnableTest - 主线程begin
[main] buguge.test.RunnableTest - 主线程end
[Thread-0] buguge.test.MyTask - in 异步线程.入参=mytest

我们希望看到的日志是:
[main] buguge.test.RunnableTest - 主线程begin
[main] buguge.test.RunnableTest - 主线程end
[main] buguge.test.MyTask - in 异步线程.入参=mytest

 

小支一招,轻松搞定。给 MyTask 加点料,代码如下。这样就能实现?是的,因为构造MyTask对象的操作发生在当前工作线程,也就是说,MyTask构造器的代码是在工作线程里执行的。没错,正是基于这一点。

@Slf4j
public class MyTask implements Runnable {
    String flag;
    final String threadName;

    public MyTask(String flag) {
        this.flag = flag;
        this.threadName = Thread.currentThread().getName();
    }

    @Override
    public void run() {
        Thread.currentThread().setName(threadName);
        log.info("in 异步线程.入参={}", flag);
    }
}

 

同样,用lambda表达式使用Runnable来使用异步线程,传递线程名,也很简单。上面的 RunnableTest 的代码改造如下。

@Slf4j
public class RunnableTest {
    public static void main(String[] args) {
        log.info("主线程begin");
        final String name = Thread.currentThread().getName();
        new Thread(() -> {
            Thread.currentThread().setName(name);
            log.info("这是异步线程里的日志");
        }).start();
        log.info("主线程end");
    }
}

lambda表达式里的代码即是Runnable#run方法的代码,因为Runnable是函数式接口(FunctionalInterface)。即,上述代码与下述代码是等效的。

@Slf4j
public class RunnableTest {
    public static void main(String[] args) {
        log.info("主线程begin");
        final String name = Thread.currentThread().getName();
        new Thread(new Runnable() {
            @Override
            public void run() {
                Thread.currentThread().setName(name);
                log.info("这是异步线程里的日志");
            }
        }).start();
        log.info("主线程end");
    }

}

 

posted on 2022-08-23 16:53  buguge  阅读(638)  评论(0)    收藏  举报