日志门面 定义一套日志接口(规范),常见的有slf4j,common-logging.
日志系统(具体的实现),常见的有log4j2,log-back,log4j等
SpringBoot中默认是slf4j+log-back组合,不需要额外引入日志依赖
日志配置
<configuration debug="false">
<!-- 日志的输出策略(例如 日志输出格式,日志时间区间覆盖,文件大小达到阈值新建文件等)控制台输出 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
<pattern>%d{HH:mm:ss} [%thread][%X{traceId}] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- logger定义指定包下日志的输出级别 -->
<logger name="com.gyp.controller" level="TRACE" >
<appender-ref ref="STDOUT"/>
</logger>
<!-- root全局日志输出级别 -->
<root level="INFO">
<appender-ref ref="STDOUT"/>
</root>
</configuration>
可以查看大佬的介绍https://www.cnblogs.com/warking/p/5710303.html
日志输出时如何注入自定义key-value
自定义的日志输出信息。通过MDC这个类存储实现,底层用的是ThreadLocal类。
例如上述的traceId,可以通过MDC.put()方法进行注入。log.info()输出日志时,就是自动注入traceId字段的值。
手动埋点-日志路径追踪
日志追踪,可以理解为在每条日志信息上,附加一条UUID。 UUID相同的日志就是一条调用链上的
在web应用中,一次接口调用可以理解为一条任务执行链
如果是rpc feign调用,在请求头中放入traceId,服务端从请求头中取出即可。
上述方案只适用于同一个线程下(ThreadLocal线程之间隔离了,每个Thread中的ThreadLocalMap保存着ThreadLocal一个副本)。如果执行某个方法需要开启其他线程情况下,此时其他线程的MDC无法获取到traceId值。
可以通过JDK中的InheritableThreadLocal对象存储一份traceId,父线程在创建子线程时会将ThreadLocalMap中的值Copy到子线程中。此时子线程就会得到traceId值,然后注入到MDC中就可以了
InheritableThreadLocal只会在子线程创建时copy,如果线程池中的线程复用就会出现问题了。可以使用TransmittableThreadLocal 解决这个问题,alibaba开发的工具类,其实现了InheritableThreadLocal类。
TransmittableThreadLocal也提供了前置和后置钩子函数。
钩子函数触发时机是在执行Runnable或者Callable接口实现类(还有一步必须通过TtlRunnable,TtlCallable进行二次封装),执行前后触发。 这种方式无需手动把traceId注入到MDC中。
第二种方式是使用TtlExecutors对已有的线程池进行封装
//定义工具类
public class ThreadHolder {
//创建全局对象
private static TransmittableThreadLocal<String> transmittableThreadLocal = new TransmittableThreadLocal() {
@Override
protected void beforeExecute() {
MDC.put("traceId", transmittableThreadLocal.get());
System.err.println("执行beforeExecute--------------");
}
@Override
protected void afterExecute() {
MDC.clear();
System.err.println("执行afterExecute=-------------");
}
};
public static void setTraceId(String value) {
transmittableThreadLocal.set(value);
MDC.put("traceId", value);
}
public static String getTraceId() {
return transmittableThreadLocal.get();
}
public static void remove() {
transmittableThreadLocal.remove();
}
}
@GetMapping("/log1")
public String test1(){
String id = UUID.randomUUID().toString();
ThreadHolder.setTraceId(id);
threadPool.submit(getTTlRunnable(()->log.error("线程池中的日志")));
log.error("父线程日志");
return id;
/**日志打印结果
* 执行beforeExecute--------------
* 15:47:36 [http-nio-8080-exec-1][15cf48d2-40f2-46ff-a0c7-2ca88f0ec4ff] ERROR com.gyp.controller.TestController - 父线程日志
* 15:47:36 [pool-1-thread-1][15cf48d2-40f2-46ff-a0c7-2ca88f0ec4ff] ERROR com.gyp.controller.TestController - 线程池中的日志
* 执行afterExecute=-------------
*/
}
//这种方式无需手动注入到MDC
public TtlRunnable getTTlRunnable(Runnable runnable){
return TtlRunnable.get(runnable);
}
自动埋点-日志路径追踪
无需程序员改动或添加逻辑代码,通过jvm编译时,自动生成字节码注入到代码中。
常见的有SkyWalking,阿里云的sls。
感谢你的浏览,
以上是博主的一些心得,
有错误请指出,谢谢!