在管理日志的时候我们需要查看生成日志都是那些人干了那些事,那么怎么在日志上查看这些事情呢,首先呢可以直接使用Slf4j,然后再配置文件里配置一下
#日志文件最大上限
logging.file.max-size=100MB
#日志文件存储位置
logging.file.path=./logs
#日志文件输出格式 (5level:线程等级,traceID:线程名,PIDL:当前线程ID, logger{50}:日志名称最多50字符 ,%m:日志内容 ,%n:换行)
logging.pattern.console= "%d{yyyy-MM-dd HH:mm:ss.SSS} %clr(%-5level) %clr([%X{traceId}]) %clr(${PID:-}) --- %clr(%logger{50}) - %m%n"
这里我们的线程名可以使用用户信息里具有唯一标识的来标记,我们直接在过滤器里获取用户信息
package com.sunflower.filter;import cn.hutool.core.util.IdUtil;
import org.slf4j.MDC;
import org.springframework.core.annotation.Order;
import org.springframework.util.StringUtils;import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;@Order(1)
@WebFilter(urlPatterns = "/*",filterName = "traceIdFilter")
public class ThreadFilter implements Filter {public final static String MDC_TRACE_ID = "traceId";@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {HttpServletRequest httpRequest = (HttpServletRequest) request;String traceId = httpRequest.getHeader(MDC_TRACE_ID);if (StringUtils.isEmpty(traceId)) {traceId = IdUtil.fastSimpleUUID();;}MDC.put(MDC_TRACE_ID, traceId);chain.doFilter(request, response);}
}
配置完成后输出端日志就是这样的
但是这样只能在同一个日志里操作,如果开启一个子线程那么子线程就没法获取到thradId 因为Slf4j默认只用的是Threadlocal线程,普通 ThreadLocal 类允许每个线程拥有自己的独立变量副本,这些变量只对该线程可见,且不会影响其他线程。然而,当使用 InheritableThreadLocal 时,如果一个线程创建了另一个线程(例如,在调用 Thread.start() 方法时),父线程中 InheritableThreadLocal 绑定的值会被自动复制(或通过 childValue 方法进行转换)给新创建的子线程。
这意味着,如果你在一个线程中设置了一个 InheritableThreadLocal 的值,那么这个值不仅在这个线程中可用,而且在任何由这个线程派生出来的子线程中也将是初始的有效值。这对于需要跨线程层级传递特定上下文信息的场景非常有用。
在内部实现上,InheritableThreadLocal 同样依赖于 ThreadLocalMap 结构来为每个线程存储本地变量的值,不过在子线程初始化时,会额外处理父线程的 InheritableThreadLocal 值的传递。
但是当你new一个新线程后还是一样子线程没有内容,那怎么办呢
这时我们就用到了阿里提供的TTL(TransmittableThreadLoca)
引入依赖
<dependency><groupId>com.alibaba</groupId><artifactId>transmittable-thread-local</artifactId><version>2.11.5</version><scope>compile</scope>
</dependency>
重写(LogbackMDCAdapter)类(必须写在org.slf4j包下)
package org.slf4j;import ch.qos.logback.classic.util.LogbackMDCAdapter;
import org.slf4j.spi.MDCAdapter;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;/*** 描述:* 重写{@link LogbackMDCAdapter}类,搭配TransmittableThreadLocal实现父子线程之间的数据传递* 内容直接从{@link LogbackMDCAdapter}类中copy。把copyOnThreadLocal实例化对象更改为TransmittableThreadLocal即可*/
public class TtlMDCAdapter implements MDCAdapter {final ThreadLocal<Map<String, String>> copyOnThreadLocal = new InheritableThreadLocal();private static final int WRITE_OPERATION = 1;private static final int MAP_COPY_OPERATION = 2;final ThreadLocal<Integer> lastOperation = new ThreadLocal();private static TtlMDCAdapter mtcMDCAdapter;static {mtcMDCAdapter = new TtlMDCAdapter();MDC.mdcAdapter = mtcMDCAdapter;}public TtlMDCAdapter() {}public static MDCAdapter getInstance() {return mtcMDCAdapter;}private Integer getAndSetLastOperation(int op) {Integer lastOp = (Integer)this.lastOperation.get();this.lastOperation.set(op);return lastOp;}private boolean wasLastOpReadOrNull(Integer lastOp) {return lastOp == null || lastOp == MAP_COPY_OPERATION;}private Map<String, String> duplicateAndInsertNewMap(Map<String, String> oldMap) {Map<String, String> newMap = Collections.synchronizedMap(new HashMap());if (oldMap != null) {synchronized(oldMap) {newMap.putAll(oldMap);}}this.copyOnThreadLocal.set(newMap);return newMap;}public void put(String key, String val) throws IllegalArgumentException {if (key == null) {throw new IllegalArgumentException("key cannot be null");} else {Map<String, String> oldMap = (Map)this.copyOnThreadLocal.get();Integer lastOp = this.getAndSetLastOperation(WRITE_OPERATION);if (!this.wasLastOpReadOrNull(lastOp) && oldMap != null) {oldMap.put(key, val);} else {Map<String, String> newMap = this.duplicateAndInsertNewMap(oldMap);newMap.put(key, val);}}}public void remove(String key) {if (key != null) {Map<String, String> oldMap = (Map)this.copyOnThreadLocal.get();if (oldMap != null) {Integer lastOp = this.getAndSetLastOperation(WRITE_OPERATION);if (this.wasLastOpReadOrNull(lastOp)) {Map<String, String> newMap = this.duplicateAndInsertNewMap(oldMap);newMap.remove(key);} else {oldMap.remove(key);}}}}public void clear() {this.lastOperation.set(WRITE_OPERATION);this.copyOnThreadLocal.remove();}public String get(String key) {Map<String, String> map = (Map)this.copyOnThreadLocal.get();return map != null && key != null ? (String)map.get(key) : null;}public Map<String, String> getPropertyMap() {this.lastOperation.set(MAP_COPY_OPERATION);return (Map)this.copyOnThreadLocal.get();}public Set<String> getKeys() {Map<String, String> map = this.getPropertyMap();return map != null ? map.keySet() : null;}public Map<String, String> getCopyOfContextMap() {Map<String, String> hashMap = copyOnThreadLocal.get();return hashMap == null ? null : new HashMap(hashMap);}public void setContextMap(Map<String, String> contextMap) {this.lastOperation.set(WRITE_OPERATION);Map<String, String> newMap = Collections.synchronizedMap(new HashMap());newMap.putAll(contextMap);this.copyOnThreadLocal.set(newMap);}
}
更改spring.factories
在resource/META-INF/spring.factories中进行配置。
org.springframework.context.ApplicationContextInitializer=\
com.jarvan.demo.config.TtlMDCAdapterInitializer
TtlMDCAdapterInitializer类如下
package com.by.config;import org.slf4j.TtlMDCAdapter;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;public class TtlMDCAdapterInitializer implements ApplicationContextInitializer {@Overridepublic void initialize(ConfigurableApplicationContext configurableApplicationContext) {TtlMDCAdapter.getInstance();}
}
结果展示