基于logback扩展的traceId方案

序言

害~ 年底不幸没躲过公司裁员,断了一个月粮后来到一家新公司,公司的技术基建相对上家几乎可以说还是0,项目也大多数单体为主,架构比较混乱。
为了方便后续微服务化的展开,我花了2天时间写了一个简单的traceId实现,以供业务观测实现

思路

由于公司是使用的日志框架是slf4j+logback,那么就直接从logback本身提供的扩展上做改造,很快,我就想到了一个2个方案
1. 基于MDC做存储
2. 基于TTL做存储

MDC本质上是日志框架提供的一个ThreadLocal访问接口,只要变量存在于MDC中,那么通过在logback配置文件中加上参数占位符,就能直接取到需要的数据。但是MDC本身有一个致命的缺点:它不是跨线程的。其仅仅是使用了ThreadLocal而不是InheritableThreadLocal或者TransmittableThreadLocal这样具有跨线程性质的threadLocal对象。
当然这个问题也不是不可以解决,只要自己在代码里同样写一个ch.qos.logback.classic.util.LogbackMDCAdapter,并使用TransmittableThreadLocal对其进行改造。这样利用了jvm的类加载机制(同样的类只有第一次加载的才会生效),使我们自己实现的LogbackMDCAdapter替换掉logback原有的。但是这种方法会带来不稳定因素,我们没法保证我们自己写的类一定会先被jvm加载。

基于上述种种原因,我决定还是脱离MDC,选择方案2.

具体实现

基于方案2的思路,我们主要解决的是3个问题

  1. 日志解析时怎么通过logback配置文件直接取到traceId
  2. 若是使用了AsyncAppender引用做异步日志,那AsyncAppender的work线程怎么才能获取traceId。
  3. 线程池中的traceId怎么获取

以及一个扩展性的问题

  1. 能否不单单是存traceId, 维度是否能更宽广些,方便列式数据库的索引

日志解析时如何取到traceId

当使用PatternLayout时,logback会通过ch.qos.logback.core.pattern.Converter的实现类去解析pattern
在这里插入图片描述
logback会对pattern进行分词,对每一个词,都会有一个converter进行解析转换成需要的字符串,比如

${appName} %date [%traceId] [%thread] %-5level [%logger{50}] %file:%line - %msg%n

它会被分成

  • ${appName}
  • %date
  • [%traceId]
  • [%thread]
  • %-5level
  • [%logger{50}]
  • %file:%line
    • %msg%n

每一个词都由一个Converter进行解析。因此我们要做的就是两点

  1. 让logback知道当我们在logback配置中的pattern写了 [%traceId]时,它要用哪个Converter获取参数

  2. converter如何获取参数

判断使用哪个Converter

converter和关键词的映射关系存在PatternLayout的static代码块中,具体数据结构是
Map<关键词字符串, converter的类名>
在这里插入图片描述
因此,我们只需要继承PatternLayout并在其static块中加入映射即可,最后在logback中指定该TraceIdPatternLayout
在这里插入图片描述
在这里插入图片描述
当TraceIdPatternLayout被jvm加载时,static代码块就会被执行,映射关系建立

converter如何获取traceId

在使用的同步日志的情况下可以直接从TTL取出即可,若是用AsyAppender进行异步化了呢?那就需要做点手脚了。
在这里插入图片描述

异步日志如何获取traceId

使用异步日志一般是用AsyncAppender去引用真实输出日志的appender,生产日志的线程和输出日志的线程并不是同一个线程
在这里插入图片描述
上图中,绿色的块是产生日志的线程,红色的块是logback的work线程,显然work线程没法拿到属于绿色线程的traceId

我的解决思路是将event放入blockQueue之前,将traceId放入event。为此我们需要对event做一层代理,并且能够在某个地方将生成的event对象变成我们自定义的带traceId的event代理对象。

1. 定义代理对象
实现ILoggingEvent接口,组合ILoggingEvent对象,增加traceId字段

在这里插入图片描述
2. 实现代理对象的转换
自己写一个自定义的AsyncAppender,在将event放入阻塞队列之前,变成event代理对象。我在这里直接复制了AsyncAppender的代码,重写了append方法,并增加了代理转换部分

在这里插入图片描述
这样在取traceId时就能直接通过get方法拿到,不需要到ttl里去拿(本身也没有)
在这里插入图片描述
最后记得在日志配置中使用自定义的TraceAsyncAppender去ref实际输出日志的appender

线程池中的日志,怎么取到traceId

因为使用了阿里的TransmittableThreadLocal,所以方法有2种

  1. 使用阿里提供的工具类TtlExecutors对线程池包装一下,获得代理线程池。这种方式有一定的代码侵入性

  2. 使用阿里官方提供的java-agent,即通过插桩代理完成,该方式在main方法执行之前修改了类的字节码,改变了类的行为,需要在启动时添加-javaagent 参数

两种方式建议看官方文档,个人建议是agent方式,毕竟对代码没有侵入性~

总结

这种实现方式还是比较灵活的,除了traceId,还可以向代理event对象塞入更多的信息,比如一般观测平台的日志列存储需要索引,我们可以依据业务写日志解析器,将解析后的索引也放入ttl中,并通过event代理对象给到日志输出线程。

  • 10
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
logback中写入traceid可以通过使用MDC(Mapped Diagnostic Context)实现。MDC是一个线程相关的map,可以用来存储一些上下文信息。 例如,我们可以在请求进来时生成一个唯一的traceid,并将其放入MDC中,然后在后续的日志记录中,通过占位符`${mdcKey}`来引用这个traceid。 具体实现可以参考以下示例代码: 1. 在请求进来时生成traceid并放入MDC中 ``` import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.MDC; import java.util.UUID; public class TraceInterceptor extends HandlerInterceptorAdapter { private static final Logger logger = LoggerFactory.getLogger(TraceInterceptor.class); @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String traceid = UUID.randomUUID().toString(); MDC.put("traceid", traceid); logger.info("TraceInterceptor preHandle, traceid={}", traceid); return true; } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { MDC.remove("traceid"); } } ``` 2. 在logback配置文件中使用`${mdcKey}`占位符来引用traceid ``` <?xml version="1.0" encoding="UTF-8"?> <configuration> <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg %n</pattern> </encoder> </appender> <logger name="com.example" level="DEBUG"/> <root level="INFO"> <appender-ref ref="CONSOLE"/> </root> </configuration> ``` 在以上的logback配置中,我们可以使用`${mdcKey}`占位符来引用traceid,例如:`[%X{traceid}]`。 ``` %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%X{traceid}] %-5level %logger{36} - %msg %n ``` 这样,在日志中就可以看到类似于`[1a2b3c4d-5e6f-7g8h-9i10j11k12l]`这样的traceid了。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值