手写链路追踪优化-自定义用户信息异步追踪

1. 前文回顾和优化需求分析

前面五篇文章已经让《手写链路追踪》更进了一步
前文1:手写链路追踪
前文2:手写链路追踪优化-自动全局追踪代替局部手动追踪
前文3:手写链路追踪优化-多线程追踪
前文4:手写链路追踪优化-MDC改造
前文5:手写链路追踪优化-自定义用户信息追踪
前文想做用户信息的全链路追踪,但是只有阻塞线程生效了,请看日志详情

2025-09-10 22:19:26.856 [main]  INFO  o.s.boot.web.embedded.tomcat.TomcatWebServer - Tomcat started on port 8080 (http) with context path '/'
2025-09-10 22:19:26.866 [main]  INFO  com.sandwich.logtracing.LogTracingApplication - Started LogTracingApplication in 3.117 seconds (process running for 4.259)
2025-09-15 22:20:05.442 [http-nio-8080-exec-1]  INFO  o.a.c.core.ContainerBase.[Tomcat].[localhost].[/] - Initializing Spring DispatcherServlet 'dispatcherServlet'
2025-09-15 22:20:05.442 [http-nio-8080-exec-1]  INFO  org.springframework.web.servlet.DispatcherServlet - Initializing Servlet 'dispatcherServlet'
2025-09-15 22:20:05.443 [http-nio-8080-exec-1]  INFO  org.springframework.web.servlet.DispatcherServlet - Completed initialization in 1 ms
2025-09-15 22:20:05.545 [http-nio-8080-exec-1] 1HBE4VReh3Vz1TQ INFO  [userId: 123456] [username: Sandwich] [clientIp: 0:0:0:0:0:0:0:1] c.s.logtracing.service.impl.LoginServiceImpl - processing login for user Sandwich, login step 1 done
2025-09-15 22:20:05.551 [async-thread-1] 1HBE4VReh3Vz1TQ INFO  c.s.logtracing.service.impl.LoginServiceImpl - processing async login for user Sandwich, login step 1 done
2025-09-15 22:20:05.608 [async-thread-1] 1HBE4VReh3Vz1TQ INFO  c.s.logtracing.service.impl.LoginServiceImpl - processing async login for user Sandwich, login step 2 done
2025-09-15 22:20:05.608 [http-nio-8080-exec-1] 1HBE4VReh3Vz1TQ INFO  [userId: 123456] [username: Sandwich] [clientIp: 0:0:0:0:0:0:0:1] c.s.logtracing.service.impl.LoginServiceImpl - processing login for user Sandwich, login step 2 done
2025-09-15 22:20:05.669 [async-thread-1] 1HBE4VReh3Vz1TQ INFO  c.s.logtracing.service.impl.LoginServiceImpl - processing async login for user Sandwich, login step 3 done
2025-09-15 22:20:05.669 [http-nio-8080-exec-1] 1HBE4VReh3Vz1TQ INFO  [userId: 123456] [username: Sandwich] [clientIp: 0:0:0:0:0:0:0:1] c.s.logtracing.service.impl.LoginServiceImpl - processing login for user Sandwich, login step 3 done
2025-09-15 22:20:05.733 [async-thread-1] 1HBE4VReh3Vz1TQ INFO  c.s.logtracing.service.impl.LoginServiceImpl - processing async login for user Sandwich, login step 4 done
2025-09-15 22:20:05.733 [http-nio-8080-exec-1] 1HBE4VReh3Vz1TQ INFO  [userId: 123456] [username: Sandwich] [clientIp: 0:0:0:0:0:0:0:1] c.s.logtracing.service.impl.LoginServiceImpl - processing login for user Sandwich, login step 4 done
2025-09-15 22:20:05.796 [async-thread-1] 1HBE4VReh3Vz1TQ INFO  c.s.logtracing.service.impl.LoginServiceImpl - processing async login for user Sandwich, login step 5 done
2025-09-15 22:20:05.796 [http-nio-8080-exec-1] 1HBE4VReh3Vz1TQ INFO  [userId: 123456] [username: Sandwich] [clientIp: 0:0:0:0:0:0:0:1] c.s.logtracing.service.impl.LoginServiceImpl - processing login for user Sandwich, login step 5 done
2025-09-15 22:20:05.858 [async-thread-1] 1HBE4VReh3Vz1TQ INFO  c.s.logtracing.service.impl.LoginServiceImpl - processing async login for user Sandwich, login step 6 done
2025-09-15 22:20:05.858 [http-nio-8080-exec-1] 1HBE4VReh3Vz1TQ INFO  [userId: 123456] [username: Sandwich] [clientIp: 0:0:0:0:0:0:0:1] c.s.logtracing.service.impl.LoginServiceImpl - processing login for user Sandwich, login step 6 done
2025-09-15 22:20:05.920 [async-thread-1] 1HBE4VReh3Vz1TQ INFO  c.s.logtracing.service.impl.LoginServiceImpl - processing async login for user Sandwich, login step 7 done
2025-09-15 22:20:05.920 [http-nio-8080-exec-1] 1HBE4VReh3Vz1TQ INFO  [userId: 123456] [username: Sandwich] [clientIp: 0:0:0:0:0:0:0:1] c.s.logtracing.service.impl.LoginServiceImpl - processing login for user Sandwich, login step 7 done
2025-09-15 22:20:05.984 [async-thread-1] 1HBE4VReh3Vz1TQ INFO  c.s.logtracing.service.impl.LoginServiceImpl - processing async login for user Sandwich, login step 8 done
2025-09-15 22:20:05.984 [http-nio-8080-exec-1] 1HBE4VReh3Vz1TQ INFO  [userId: 123456] [username: Sandwich] [clientIp: 0:0:0:0:0:0:0:1] c.s.logtracing.service.impl.LoginServiceImpl - processing login for user Sandwich, login step 8 done
2025-09-15 22:20:06.047 [async-thread-1] 1HBE4VReh3Vz1TQ INFO  c.s.logtracing.service.impl.LoginServiceImpl - processing async login for user Sandwich, login step 9 done
2025-09-15 22:20:06.047 [http-nio-8080-exec-1] 1HBE4VReh3Vz1TQ INFO  [userId: 123456] [username: Sandwich] [clientIp: 0:0:0:0:0:0:0:1] c.s.logtracing.service.impl.LoginServiceImpl - processing login for user Sandwich, login step 9 done
2025-09-15 22:20:06.109 [async-thread-1] 1HBE4VReh3Vz1TQ INFO  c.s.logtracing.service.impl.LoginServiceImpl - processing async login for user Sandwich, login step 10 done
2025-09-15 22:20:06.109 [http-nio-8080-exec-1] 1HBE4VReh3Vz1TQ INFO  [userId: 123456] [username: Sandwich] [clientIp: 0:0:0:0:0:0:0:1] c.s.logtracing.service.impl.LoginServiceImpl - processing login for user Sandwich, login step 10 done
2025-09-15 22:20:06.172 [async-thread-1] 1HBE4VReh3Vz1TQ INFO  com.sandwich.logtracing.util.ThreadLocalContext - traceId removed from thread local
2025-09-15 22:20:06.172 [http-nio-8080-exec-1] 1HBE4VReh3Vz1TQ INFO  [userId: 123456] [username: Sandwich] [clientIp: 0:0:0:0:0:0:0:1] c.sandwich.logtracing.controller.LoginController - user Sandwich login success
2025-09-15 22:20:11.421 [http-nio-8080-exec-1]  INFO  com.sandwich.logtracing.util.ThreadLocalContext - traceId removed from thread local

由以上日志看出,异步线程并没有生效,如果我们想做好全链路用户信息可视化,在多线程项目中对异步线程的支持也是必不可少的。
多线程项目中有很多种异步线程,我们先来梳理一下常见的异步线程
根据代码和常见的Java应用架构,异步线程主要有以下几种类型:

  1. @Async注解异步方法

    • Spring框架提供的异步执行方式
    • 通过在方法上添加@Async注解实现异步调用
  2. CompletableFuture异步任务

    • Java 8引入的异步编程工具
    • 支持链式调用和组合多个异步操作
    • 可以使用自定义线程池执行任务
  3. ThreadPoolTaskExecutor线程池

    • Spring封装的线程池实现
    • 可以通过注入 [ThreadPoolTaskExecutor] bean来执行异步任务
  4. 原生Thread线程

    • Java原生的线程创建方式
    • 直接继承Thread类或实现Runnable接口
  5. ScheduledExecutorService定时任务

    • Java提供的定时任务执行器
    • 支持延迟执行和周期性执行任务
  6. @Scheduled定时任务

    • Spring提供的定时任务注解
    • 在方法上添加 [@Scheduled]注解实现定时执行

对于上述异步线程类型,如果需要传递上下文信息(如traceId、用户信息等),都需要相应的处理机制。不过其原理都差不多,这里还是只用@Async注解实现异步调用作实例,其他异步线程不再一一阐述。

2.代码实现

2.1 修改MDC工具类

package com.sandwich.logtracing.util;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.MDC;

/**
 * @Author 公众号: IT三明治
 * @Date 2025/8/31
 * @Description: MDC工具类
 */
public class MdcUtils {
    public static final String TRACE_ID = "traceId";
    public static final String USER_ID = "userId";
    public static final String USER_NAME = "username";
    public static final String CLIENT_IP = "clientIp";

    public static void setTraceId(String traceId) {
        if (StringUtils.isBlank(traceId)) {
            setTraceId();
            return;
        }
        MDC.put(TRACE_ID, traceId);
    }

    public static void setTraceId() {
        String traceId = RandomStrUtils.generateRandomString(15);
        MDC.put(TRACE_ID, traceId);
    }

    public static String getTraceId() {
        return MDC.get(TRACE_ID);
    }

    public static void removeTraceId() {
        MDC.remove(TRACE_ID);
    }

    public static void setUserId(String userId) {
        if (StringUtils.isBlank(userId)) {
            return;
        }
        MDC.put(USER_ID, userId);
    }

    public static String getUserId() {
        return MDC.get(USER_ID);
    }

    public static void removeUserId() {
        MDC.remove(USER_ID);
    }

    public static void setUserName(String userName) {
        if (StringUtils.isBlank(userName)) {
            return;
        }
        MDC.put(USER_NAME, userName);
    }

    public static String getUserName(String username) {
        return MDC.get(USER_NAME);
    }

    public static void removeUserName() {
        MDC.remove(USER_NAME);
    }

    public static void setClientIp(String clientIp) {
        if (StringUtils.isBlank(clientIp)) {
            return;
        }
        MDC.put(CLIENT_IP, clientIp);
    }

    public static String getClientIp() {
        return MDC.get(CLIENT_IP);
    }

    public static void removeClientIp() {
        MDC.remove(CLIENT_IP);
    }

    public static void clearAll() {
        removeTraceId();
        removeUserId();
        removeUserName();
        removeClientIp();
    }
}

2.2 修改thread local 上下文工具类

package com.sandwich.logtracing.util;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

/**
 * @Author 公众号: IT三明治
 * @Date 2025/8/30
 * @Description: 这是一个用于在线程间存储和传递追踪ID(traceId)的上下文工具类,主要目的是实现请求链路追踪。
 */
@Slf4j
public class ThreadLocalContext {
    /**
     * 线程本地存储,用于存储追踪ID
     * 使用 InheritableThreadLocal 而不是普通的 ThreadLocal
     * InheritableThreadLocal 允许子线程继承父线程的值,这对于异步处理场景非常重要
     */
    private static final InheritableThreadLocal<String> TRACE_ID = new InheritableThreadLocal<>();
    private static final InheritableThreadLocal<String> USER_ID = new InheritableThreadLocal<>();
    private static final InheritableThreadLocal<String> USER_NAME = new InheritableThreadLocal<>();
    private static final InheritableThreadLocal<String> CLIENT_IP = new InheritableThreadLocal<>();

    public static String getTraceId() {
        return TRACE_ID.get();
    }

    public static void setTraceId(String traceId) {
        TRACE_ID.set(traceId);
    }

    /**
     * 清除当前线程的traceId,防止内存泄漏
     */
    public static void clearTraceId() {
        if (StringUtils.isBlank(TRACE_ID.get())) {
            log.info("traceId is empty from thread local already");
            return;
        }
        TRACE_ID.remove();
        log.info("traceId removed from thread local");
    }

    public static String getUserId() {
        return USER_ID.get();
    }

    public static void setUserId(String userId) {
        USER_ID.set(userId);
    }

    public static void clearUserId() {
        if (StringUtils.isBlank(USER_ID.get())) {
            log.info("user id is empty from thread local already");
            return;
        }
        USER_ID.remove();
        log.info("user id removed from thread local");
    }

    public static String getUserName() {
        return USER_NAME.get();
    }

    public static void setUserName(String userName) {
        USER_NAME.set(userName);
    }

    public static void clearUserName() {
        if (StringUtils.isBlank(USER_NAME.get())) {
            log.info("user name is empty from thread local already");
            return;
        }
        USER_NAME.remove();
        log.info("user name removed from thread local");
    }

    public static String getClientIp() {
        return CLIENT_IP.get();
    }

    public static void setClientIp(String clientIp) {
        CLIENT_IP.set(clientIp);
    }

    public static void clearClientIp() {
        if (StringUtils.isBlank(CLIENT_IP.get())) {
            log.info("client ip is empty from thread local already");
            return;
        }
        CLIENT_IP.remove();
        log.info("client ip removed from thread local");
    }

    /**
     * 清除所有线程本地存储
     */
    public static void clearAll() {
        clearTraceId();
        clearUserId();
        clearUserName();
        clearClientIp();
    }
}

2.3 用MDC工具类定义的常代替拦截器的key的魔法值,同时把filter的内容废除,追踪逻辑全部移动到拦截器处理

package com.sandwich.logtracing.interceptor;

import com.sandwich.logtracing.util.MdcUtils;
import com.sandwich.logtracing.util.RandomStrUtils;
import com.sandwich.logtracing.util.ThreadLocalContext;
import io.micrometer.common.util.StringUtils;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.catalina.connector.RequestFacade;
import org.jetbrains.annotations.NotNull;
import org.slf4j.MDC;
import org.springframework.lang.NonNullApi;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

/**
 * @Author 公众号: IT三明治
 * @Date 2025/9/9
 * @Description:
 */
@Slf4j
@Component
public class MDCInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull Object handler) {
        // 从header中获取用户信息
        String traceId = request.getHeader("x-request-correlation-id");
        //if the request header don't have a trace id,then generate a random one
        if (StringUtils.isBlank(traceId)) {
            traceId = RandomStrUtils.generateRandomString(15);
        }
        MdcUtils.setTraceId(traceId);
        String userId = request.getHeader("X-User-ID");
        if (StringUtils.isNotBlank(userId)) {
            // 添加用户ID到MDC
            MdcUtils.setUserId(userId);
        }
        String username = request.getHeader("X-Username");

        if (StringUtils.isNotBlank(username)) {
            // 添加用户名到MDC
            MdcUtils.setUserName(username);
        }

        // 可以添加其他请求相关信息
        String clientIp = getClientIp( request);
        MdcUtils.setClientIp(clientIp);

        ThreadLocalContext.setTraceId(traceId);
        ThreadLocalContext.setUserId(userId);
        ThreadLocalContext.setUserName(username);
        ThreadLocalContext.setClientIp(clientIp);

        return true;
    }

    @Override
    public void postHandle(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response,
                           @NotNull Object handler, @NotNull ModelAndView modelAndView) {
        // 请求处理完成后可以添加响应信息
    }

    @Override
    public void afterCompletion(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response,
                                @NotNull Object handler, @Nullable Exception ex) {
        // 请求完全结束后清理MDC,防止内存泄漏
        MdcUtils.clearAll();
    }

    private String getClientIp(HttpServletRequest request) {
        String ip = request.getHeader("X-Forwarded-For");
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("X-Real-IP");
        }
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        return ip;
    }
}

注释掉filter

package com.sandwich.logtracing.config;

import com.sandwich.logtracing.filter.LogFilter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;

/**
 * @Author 公众号: IT三明治
 * @Date 2025/8/30
 * @Description:
 */
@Configuration
public class WebConfiguration {

//    @Bean
//    @ConditionalOnMissingBean(LogFilter.class)
//    @Order(Ordered.HIGHEST_PRECEDENCE + 101)
//    public FilterRegistrationBean<LogFilter> logFilterFilterRegistrationBean() {
//        FilterRegistrationBean<LogFilter> bean = new FilterRegistrationBean<>();
//        bean.setFilter(new LogFilter());
//        bean.addUrlPatterns("/*");
//        return bean;
//    }
}

2.4 创建统一的上下文传输对象

package com.sandwich.logtracing.dto;

import com.sandwich.logtracing.util.MdcUtils;
import com.sandwich.logtracing.util.ThreadLocalContext;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;

/**
 * @Author 公众号: IT三明治
 * @Date 2025/9/15
 * @Description:
 */
@Data
public class RequestContext {
    private String traceId;
    private String userId;
    private String username;
    private String clientIp;


    public static RequestContext capture() {
        RequestContext context = new RequestContext();
        context.setTraceId(ThreadLocalContext.getTraceId());
        context.setUserId(ThreadLocalContext.getUserId());
        context.setUsername(ThreadLocalContext.getUserName());
        context.setClientIp(ThreadLocalContext.getClientIp());
        return context;
    }

    public void restore() {
        if (StringUtils.isNoneBlank(traceId)) MdcUtils.setTraceId(traceId);
        if (StringUtils.isNoneBlank(userId)) MdcUtils.setUserId(userId);
        if (StringUtils.isNoneBlank(username)) MdcUtils.setUserName(username);
        if (StringUtils.isNoneBlank(clientIp)) MdcUtils.setClientIp(clientIp);
    }
}

2.5 修改AOP切面

package com.sandwich.logtracing.aspect;

import com.sandwich.logtracing.dto.RequestContext;
import com.sandwich.logtracing.util.MdcUtils;
import com.sandwich.logtracing.util.ThreadLocalContext;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

/**
 * @Author 公众号: IT三明治
 * @Date 2025/8/30
 * @Description: 这是一个用于在异步线程中保持traceId一致性的AOP切面,主要目的是在使用@Async注解的方法执行时,将主线程中的traceId传递到异步线程中
 * 主线程是父线程,异步线程是子线程, 因此需要使用ThreadLocal来保存traceId, 以便在异步线程中保持一致性
 */
@Slf4j
@Aspect
@Component
public class TraceIdAspect {

    @Around("@annotation(org.springframework.scheduling.annotation.Async)")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        RequestContext context = RequestContext.capture();

        try {
            context.restore();
            // 执行目标方法并返回
            return joinPoint.proceed();
        } finally {
            // clear all fields from thread local
            ThreadLocalContext.clearAll();
            // remove all fields from mdc
            MdcUtils.clearAll();
        }
    }
}

3. 测试验证

  • 启动项目,用shell请求api
    shell原文如下
#!/bin/bash

# Define the API endpoint
API_URL="http://localhost:8080/test/login"

function generate_random_string() {
    # 使用openssl生成随机字符串(如果已安装)
    if command -v openssl &> /dev/null; then
        openssl rand -base64 20 | tr -dc 'a-zA-Z0-9' | fold -w 15 | head -n 1
    else
        # 使用系统方法生成
        local chars="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
        local result=""
        result=$(printf "%s" "${chars:$((RANDOM % ${#chars})):1}"{1..15} | tr -d '\n')
        echo "$result"
    fi
}

function normalLogin() {
    # 生成15位随机字符串作为traceId
    traceId=$(generate_random_string)
    echo "Generated traceId from client side: $traceId"
    response=$(curl -X POST $API_URL \
        -H "Content-Type: application/json" \
        -H "x-request-correlation-id: $traceId" \
        -H "X-User-ID: 123456" \
        -H "X-Username: Sandwich" \
        -d '{"username": "Sandwich", "password": "test"}')
    echo "Response from login API:"
    echo "$response" | python -m json.tool
}

normalLogin

API请求如下

Administrator@USER-20230930SH MINGW64 /d/git/java/log-tracing/shell (master)
$ ./login.sh
Generated traceId from client side: PgPzcW32QlOOVNv
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   144    0   100  100    44    118     52 --:--:-- --:--:-- --:--:--   171
Response from login API:
{
    "responseCode": 200,
                                 Dload  Upload   Total   Spent    Left  Speed
100   144    0   100  100    44    125     55 --:--:-- --:--:-- --:--:--   180
Response from login API:
{
    "responseCode": 200,
    "message": "success",
    "data": "Sandwich login success",
    "traceId": "PgPzcW32QlOOVNv"
}
  • 用trace id: PgPzcW32QlOOVNv 查看日志,验证结果
2025-09-17 07:50:30.234 [main]  INFO  o.s.boot.web.embedded.tomcat.TomcatWebServer - Tomcat started on port 8080 (http) with context path '/'
2025-09-17 07:50:30.242 [main]  INFO  com.sandwich.logtracing.LogTracingApplication - Started LogTracingApplication in 1.905 seconds (process running for 2.492)
2025-09-17 07:50:42.002 [http-nio-8080-exec-1]  INFO  o.a.c.core.ContainerBase.[Tomcat].[localhost].[/] - Initializing Spring DispatcherServlet 'dispatcherServlet'
2025-09-17 07:50:42.003 [http-nio-8080-exec-1]  INFO  org.springframework.web.servlet.DispatcherServlet - Initializing Servlet 'dispatcherServlet'
2025-09-17 07:50:42.004 [http-nio-8080-exec-1]  INFO  org.springframework.web.servlet.DispatcherServlet - Completed initialization in 1 ms
2025-09-17 07:50:42.124 [http-nio-8080-exec-1] PgPzcW32QlOOVNv INFO  [userId: 123456] [username: Sandwich] [clientIp: 0:0:0:0:0:0:0:1] c.s.logtracing.service.impl.LoginServiceImpl - processing login for user Sandwich, login step 1 done
2025-09-17 07:50:45.431 [http-nio-8080-exec-1] PgPzcW32QlOOVNv INFO  [userId: 123456] [username: Sandwich] [clientIp: 0:0:0:0:0:0:0:1] c.s.logtracing.service.impl.LoginServiceImpl - processing login for user Sandwich, login step 2 done
2025-09-17 07:50:45.703 [http-nio-8080-exec-1] PgPzcW32QlOOVNv INFO  [userId: 123456] [username: Sandwich] [clientIp: 0:0:0:0:0:0:0:1] c.s.logtracing.service.impl.LoginServiceImpl - processing login for user Sandwich, login step 3 done
2025-09-17 07:50:46.071 [http-nio-8080-exec-1] PgPzcW32QlOOVNv INFO  [userId: 123456] [username: Sandwich] [clientIp: 0:0:0:0:0:0:0:1] c.s.logtracing.service.impl.LoginServiceImpl - processing login for user Sandwich, login step 4 done
2025-09-17 07:50:48.350 [http-nio-8080-exec-1] PgPzcW32QlOOVNv INFO  [userId: 123456] [username: Sandwich] [clientIp: 0:0:0:0:0:0:0:1] c.s.logtracing.service.impl.LoginServiceImpl - processing login for user Sandwich, login step 5 done
2025-09-17 07:50:48.355 [async-thread-1] PgPzcW32QlOOVNv INFO  [userId: 123456] [username: Sandwich] [clientIp: 0:0:0:0:0:0:0:1] c.s.logtracing.service.impl.LoginServiceImpl - processing async login for user Sandwich, login step 1 done
2025-09-17 07:50:48.415 [http-nio-8080-exec-1] PgPzcW32QlOOVNv INFO  [userId: 123456] [username: Sandwich] [clientIp: 0:0:0:0:0:0:0:1] c.s.logtracing.service.impl.LoginServiceImpl - processing login for user Sandwich, login step 6 done
2025-09-17 07:50:48.416 [async-thread-1] PgPzcW32QlOOVNv INFO  [userId: 123456] [username: Sandwich] [clientIp: 0:0:0:0:0:0:0:1] c.s.logtracing.service.impl.LoginServiceImpl - processing async login for user Sandwich, login step 2 done
2025-09-17 07:50:48.478 [http-nio-8080-exec-1] PgPzcW32QlOOVNv INFO  [userId: 123456] [username: Sandwich] [clientIp: 0:0:0:0:0:0:0:1] c.s.logtracing.service.impl.LoginServiceImpl - processing login for user Sandwich, login step 7 done
2025-09-17 07:50:48.478 [async-thread-1] PgPzcW32QlOOVNv INFO  [userId: 123456] [username: Sandwich] [clientIp: 0:0:0:0:0:0:0:1] c.s.logtracing.service.impl.LoginServiceImpl - processing async login for user Sandwich, login step 3 done
2025-09-17 07:50:48.542 [http-nio-8080-exec-1] PgPzcW32QlOOVNv INFO  [userId: 123456] [username: Sandwich] [clientIp: 0:0:0:0:0:0:0:1] c.s.logtracing.service.impl.LoginServiceImpl - processing login for user Sandwich, login step 8 done
2025-09-17 07:50:48.542 [async-thread-1] PgPzcW32QlOOVNv INFO  [userId: 123456] [username: Sandwich] [clientIp: 0:0:0:0:0:0:0:1] c.s.logtracing.service.impl.LoginServiceImpl - processing async login for user Sandwich, login step 4 done
2025-09-17 07:50:48.606 [http-nio-8080-exec-1] PgPzcW32QlOOVNv INFO  [userId: 123456] [username: Sandwich] [clientIp: 0:0:0:0:0:0:0:1] c.s.logtracing.service.impl.LoginServiceImpl - processing login for user Sandwich, login step 9 done
2025-09-17 07:50:48.606 [async-thread-1] PgPzcW32QlOOVNv INFO  [userId: 123456] [username: Sandwich] [clientIp: 0:0:0:0:0:0:0:1] c.s.logtracing.service.impl.LoginServiceImpl - processing async login for user Sandwich, login step 5 done
2025-09-17 07:50:48.669 [http-nio-8080-exec-1] PgPzcW32QlOOVNv INFO  [userId: 123456] [username: Sandwich] [clientIp: 0:0:0:0:0:0:0:1] c.s.logtracing.service.impl.LoginServiceImpl - processing login for user Sandwich, login step 10 done
2025-09-17 07:50:48.669 [async-thread-1] PgPzcW32QlOOVNv INFO  [userId: 123456] [username: Sandwich] [clientIp: 0:0:0:0:0:0:0:1] c.s.logtracing.service.impl.LoginServiceImpl - processing async login for user Sandwich, login step 6 done
2025-09-17 07:50:48.732 [http-nio-8080-exec-1] PgPzcW32QlOOVNv INFO  [userId: 123456] [username: Sandwich] [clientIp: 0:0:0:0:0:0:0:1] c.sandwich.logtracing.controller.LoginController - user Sandwich login success
2025-09-17 07:50:48.732 [async-thread-1] PgPzcW32QlOOVNv INFO  [userId: 123456] [username: Sandwich] [clientIp: 0:0:0:0:0:0:0:1] c.s.logtracing.service.impl.LoginServiceImpl - processing async login for user Sandwich, login step 7 done
2025-09-17 07:50:48.795 [async-thread-1] PgPzcW32QlOOVNv INFO  [userId: 123456] [username: Sandwich] [clientIp: 0:0:0:0:0:0:0:1] c.s.logtracing.service.impl.LoginServiceImpl - processing async login for user Sandwich, login step 8 done
2025-09-17 07:50:48.857 [async-thread-1] PgPzcW32QlOOVNv INFO  [userId: 123456] [username: Sandwich] [clientIp: 0:0:0:0:0:0:0:1] c.s.logtracing.service.impl.LoginServiceImpl - processing async login for user Sandwich, login step 9 done
2025-09-17 07:50:48.921 [async-thread-1] PgPzcW32QlOOVNv INFO  [userId: 123456] [username: Sandwich] [clientIp: 0:0:0:0:0:0:0:1] c.s.logtracing.service.impl.LoginServiceImpl - processing async login for user Sandwich, login step 10 done
2025-09-17 07:50:48.983 [async-thread-1] PgPzcW32QlOOVNv INFO  [userId: 123456] [username: Sandwich] [clientIp: 0:0:0:0:0:0:0:1] com.sandwich.logtracing.util.ThreadLocalContext - traceId removed from thread local
2025-09-17 07:50:48.983 [async-thread-1] PgPzcW32QlOOVNv INFO  [userId: 123456] [username: Sandwich] [clientIp: 0:0:0:0:0:0:0:1] com.sandwich.logtracing.util.ThreadLocalContext - user id removed from thread local
2025-09-17 07:50:48.983 [async-thread-1] PgPzcW32QlOOVNv INFO  [userId: 123456] [username: Sandwich] [clientIp: 0:0:0:0:0:0:0:1] com.sandwich.logtracing.util.ThreadLocalContext - user name removed from thread local
2025-09-17 07:50:48.984 [async-thread-1] PgPzcW32QlOOVNv INFO  [userId: 123456] [username: Sandwich] [clientIp: 0:0:0:0:0:0:0:1] com.sandwich.logtracing.util.ThreadLocalContext - client ip removed from thread local

由上文日志可以看到trace id: PgPzcW32QlOOVNv, 追踪的日志包括了阻塞线程和异步线程。

4.总结

本文主要是完成了用户信息的异步追踪,实现了用户信息在多线程环境下全链路可视化。如果您有什么建议,请在评论区给我留言。请关注我,接下来我们继续探讨,谢谢。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

IT三明治

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值