⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 blog.csdn.net/weixin_41541562/article/details/123629617 「大象大象你的鼻子怎么那么长」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注**微信公众号:【芋道源码】**有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言将得到认真回复。甚至不知道如何读源码也可以请教噢
  4. 新的源码解析文章实时收到通知。每周更新一篇左右
  5. 认真的源码交流微信群。

一. TLog简介

TLog提供了一种最简单的方式来解决日志追踪问题,它不收集日志,也不需要另外的存储空间,它只是自动的对你的日志进行打标签,自动生成TraceId贯穿你微服务的一整条链路。并且提供上下游节点信息。适合中小型企业以及想快速解决日志追踪问题的公司项目使用。

支持dubbo,dubbox,spring cloud三大RPC框架。

二. TLog安装

在SpringBoot项目引入全量依赖

<dependency>
<groupId>com.yomahub</groupId>
<artifactId>tlog-all-spring-boot-starter</artifactId>
<version>1.3.6</version>
</dependency>

按需引入依赖

三. 适配方式

支持Log4j、Logback、Log4j2三大日志框架

1. Logback框架适配器

1.1. 同步日志

换掉encoder的实现类或者换掉layout的实现类就可以了

<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
<property name="APP_NAME" value="logtest"/>
<property name="LOG_HOME" value="./logs" />
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!--这里替换成AspectLogbackEncoder-->
<encoder class="com.yomahub.tlog.core.enhance.logback.AspectLogbackEncoder">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
</encoder>
</appender>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<File>${LOG_HOME}/${APP_NAME}.log</File>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<FileNamePattern>${LOG_HOME}/${APP_NAME}.log.%d{yyyy-MM-dd}.%i.log</FileNamePattern>
<MaxHistory>30</MaxHistory>
<maxFileSize>1000MB</maxFileSize>
</rollingPolicy>
<!--这里替换成AspectLogbackEncoder-->
<encoder class="com.yomahub.tlog.core.enhance.logback.AspectLogbackEncoder">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
</encoder>
</appender>

<root level="INFO">
<appender-ref ref="STDOUT" />
<appender-ref ref="FILE" />
</root>
</configuration>

1.2. 异步日志

替换掉appender的实现类就可以了

<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
<property name="APP_NAME" value="logback-dubbo-provider"/>
<property name="LOG_HOME" value="./logs" />
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
</encoder>
</appender>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<File>${LOG_HOME}/${APP_NAME}.log</File>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<FileNamePattern>${LOG_HOME}/${APP_NAME}.log.%d{yyyy-MM-dd}.%i.log</FileNamePattern>
<MaxHistory>30</MaxHistory>
<maxFileSize>1000MB</maxFileSize>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
</encoder>
</appender>

<!-- 这里替换成AspectLogbackAsyncAppender -->
<appender name="ASYNC_FILE" class="com.yomahub.tlog.core.enhance.logback.async.AspectLogbackAsyncAppender">
<discardingThreshold>0</discardingThreshold>
<queueSize>2048</queueSize>
<includeCallerData>true</includeCallerData>
<appender-ref ref="FILE"/>
</appender>

<root level="INFO">
<appender-ref ref="STDOUT" />
<appender-ref ref="ASYNC_FILE" />
</root>
</configuration>

四. 日志标签模板自定义

TLog默认只打出spanId和traceId,以<$spanId><$traceId>这种模板打出,当然你能自定义其模板。还能加入其它的标签头

你只需要在springboot的application.properties里如下定义:

tlog.pattern=[$preApp][$preIp][$spanId][$traceId]

  • $preApp:上游微服务节点名称
  • $preHost: 上游微服务的Host Name
  • $preIp:上游微服务的IP地址
  • $spanId:链路spanId
  • $traceId:全局唯一跟踪ID

这样日志的打印就能按照你定义模板进行打印

五. 标签位置自定义

TLog默认的标签都是跟在具体日志信息体的前面的,但是如果想自定义位置怎么办,比如放在[INFO]的前面,有没有办法?

TLog从1.1.5开始,适配了slf4j的mdc,从而能够让你的标签在一行日志的任意位置打印出。但是只支持日志框架配置方式接入。

TLog中log4j和logback的占位符为%X{tl},而在log4j2中的占位符为%TX{tl},请注意不要写错

你可以在任意日志框架的pattern里定义。例如在logback里,你可以这样定义pattern:

<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
<!--定义日志文件的存储地址 勿在 LogBack 的配置中使用相对路径-->
<property name="LOG_HOME" value="./logs" />
<!--控制台日志-->
<appender name="Console" class="ch.qos.logback.core.ConsoleAppender">
<!--这里替换成AspectLogbackEncoder-->
<encoder class="com.yomahub.tlog.core.enhance.logback.AspectLogbackEncoder">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %X{tl} [%thread] %-5level %logger{50} - %msg%n</pattern>
</encoder>
</appender>

<!--文件日志-->
<!--同步日志-->
<appender name="SyncLogFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
<File>${LOG_HOME}/logback-sync-rolling-mdc.log</File>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!--日志文件输出的文件名-->
<FileNamePattern>${LOG_HOME}/logback-rolling-mdc.log.%d{yyyy-MM-dd}.%i.log</FileNamePattern>
<!--日志文件保留天数-->
<MaxHistory>30</MaxHistory>
<!--日志文件大小-->
<maxFileSize>1000MB</maxFileSize>
</rollingPolicy>
<encoder class="com.yomahub.tlog.core.enhance.logback.AspectLogbackEncoder">
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %X{tl} [%thread] %-5level %logger{50} - %msg%n</pattern>
</encoder>
</appender>

<!-- 日志输出级别 -->
<root level="INFO">
<appender-ref ref="Console" />
<appender-ref ref="SyncLogFile" />
</root>
</configuration>

那么出来的日志范例为:

2020-11-07 17:01:26.647 <0.1><7455616963803840> [Thread-8] INFO  c.y.tlog.example.dubbox.service.impl.AsynDomain - 这是测试日志哦

在log4j和log4j2中同理。

六. SpanId的生成规则

TLog默认的标签打印模板是<$spanId><$traceId>

TLog 中的 SpanId 代表本次调用在整个调用链路树中的位置,假设一个 Web 系统 A 接收了一次用户请求,那么在这个系统的日志中,记录下的 SpanId 是 0,代表是整个调用的根节点,如果 A 系统处理这次请求,需要通过 RPC 依次调用 B,C,D 三个系统,那么在 A 系统的客户端日志中,SpanId 分别是 0.1,0.2 和 0.3,在 B,C,D 三个系统的服务端日志中,SpanId 也分别是 0.1,0.2 和 0.3;

如果 C 系统在处理请求的时候又调用了 E,F 两个系统,那么 C 系统中对应的客户端日志是 0.2.1 和 0.2.2,E,F 两个系统对应的服务端日志也是 0.2.1 和 0.2.2。根据上面的描述,我们可以知道,如果把一次调用中所有的 SpanId 收集起来,可以组成一棵完整的链路树。

我们假设一次分布式调用中产生的 TraceId 是 0a1234(实际不会这么短),那么根据上文 SpanId 的产生过程,有下图:

七. 业务自定义标签

很多公司的系统在打日志的时候,每打一个日志里都会带入一些业务信息,比如记录ID,会员CODE,方便业务日志的定位。现在有了TLog,不仅能做分布式链路标签追加,还能自动帮你做业务标签的添加。这样在定位日志的时候可以更加方便的搜索。

Tlog支持方法级别的自定义业务标签。你可以在方法上定义简单的标注,来实现在某一个方法的日志里,统一加入业务的指标标签,用于更加细致的定位。

使用@TLogAspect

示例一 打印入参

@TLogAspect(value = {"id","name"},pattern = "<-{}->",joint = "_")
public void demo(String id,String name){
log.info("加了patter和joint的示例");
}

日志打出来的样子如下,其中前面为框架spanId+traceId

2020-02-08 22:09:40.103 [main] INFO  Demo - <0.2><7205781616706048><-NO1234_jenny-> 加了patter和joint的示例

示例二 常量标签

@TLogAspect(str = "XYZ")
public void demo1(String name){
log.info("这是第一条日志");
log.info("这是第二条日志");
log.info("这是第三条日志");
new Thread(() -> log.info("这是异步日志")).start();
}
2020-02-08 20:22:33.945 [main] INFO Demo - <0.2><7205781616706048>[XYZ] 这是第一条日志
2020-02-08 20:22:33.945 [main] INFO Demo - <0.2><7205781616706048>[XYZ] 这是第二条日志
2020-02-08 20:22:33.945 [main] INFO Demo - <0.2><7205781616706048>[XYZ] 这是第三条日志
2020-02-08 20:22:33.948 [Thread-3] INFO Demo - <0.2><7205781616706048>[XYZ] 这是异步日志

示例三 点操作符

支持类型:

  • Bean对象
  • Map对象
  • Json格式的字符串
  • Fastjson的JSONObject对象

@TLogAspect({"person.id","person.age","person.company.department.dptId"})
public void demo(Person person){
log.info("多参数加多层级示例");
}
2020-02-08 22:09:40.110 [main] INFO Demo - <0.2><7205781616706048>[31-25-80013] 多参数加多层级示例

可以用点操作符操作符合Json格式的字符串

@TLogAspect({"person.id","person.age","person.company.department.dptId"})
public void demo(String person){
log.info("多参数加多层级示例");
}

甚至可以用下标[num]去访问Json格式的字符串中的数组

@TLogAspect({"person.id","person.age","person.company.title[1]"})
public void demo(String person){
log.info("多参数加多层级示例");
}

示例四 自定义Convertor

自定义Convert,适用于更复杂的业务场景

@TLogAspect(convert = CustomAspectLogConvert.class)
public void demo(Person person){
log.info("自定义Convert示例");
}
public class CustomAspectLogConvert implements AspectLogConvert {
@Override
public String convert(Object[] args) {
Person person = (Person)args[0];
return "PERSON(" + person.getId() + ")";
}
}
2020-02-20 17:05:12.414 [main] INFO Demo - <0.2><7205781616706048>[PERSON(31] 自定义Convert示例

八. 线程支持

1. 一般异步线程

异步线程的定义为:执行好之后线程会自动销毁,如new Thread(){…}.start这种内部类线程,你自定定义的类继承Thread或实现Runnerable接口的。

对于一般异步线程,不需要你做任何事,TLog天然支持在异步线程中打印标签。

2. 线程池

但是对于使用了线程池的场景,由于线程池中的线程不会被销毁,会被复用。需要你用TLogInheritableTask替换Runnable,否则标签数据会重复:

ExecutorService pool = Executors.newFixedThreadPool(5);
pool.submit(new TLogInheritableTask() {
@Override
public void runTask() {
log.info("我是异步线程日志");
}
});

3. MDC模式的异步线程

如果你用了标签位置自定义功能(这个功能是用slf4j的MDC来实现的),那么你有可能发现,在MDC模式中的异步线程好像都没法正常打印标签。这是因为MDC中使用了ThreadLocal,而ThreadLocal中的信息没法传递给子线程。为此TLog提供了线程的包装类,能帮助你解决这个问题。

还是同样的类TLogInheritableTask,如果你声明一个线程,你需要去继承TLogInheritableTask

public class AsynDomain extends TLogInheritableTask {

private Logger log = LoggerFactory.getLogger(this.getClass());

@Override
public void runTask() {
log.info("这是异步方法哦");
log.info("异步方法开始");
log.info("异步方法结束");
}
}

执行的时候和普通继承了Thread一样:

new AsynDomain().start();

如果你要在线程池中执行,同样以AsynDomain为例,则为:

ExecutorService pool = Executors.newFixedThreadPool(5);pool.submit(new AsynDomain());

九. 对HTTPClient的支持

目前TLog对于Httpclient客户端的支持是有侵入性的,对于请求端需要加入拦截器,把日志标签信息记录用过Http Header传递到服务器。

HttpClient 4.X 添加拦截器TLogHttpClientInterceptor

String url = "http://127.0.0.1:2111/hi?name=2323";
CloseableHttpClient client = HttpClientBuilder.create()
.addInterceptorFirst(new TLogHttpClientInterceptor())
.build();
HttpGet get = new HttpGet(url);
try {
CloseableHttpResponse response = client.execute(get);
HttpEntity entity = response.getEntity();
log.info("http response code:{}", response.getStatusLine().getStatusCode());
log.info("http response result:{}",entity == null ? "" : EntityUtils.toString(entity));
} catch (IOException e) {
e.printStackTrace();
}

HttpClient 5.X 添加拦截器TLogHttpClient5Interceptor

String url = "http://127.0.0.1:2111/hi?name=2323";
CloseableHttpClient client = HttpClientBuilder.create()
.addInterceptorFirst(new TLogHttpClient5Interceptor())
.build();
HttpGet get = new HttpGet(url);
try {
CloseableHttpResponse response = client.execute(get);
HttpEntity entity = response.getEntity();
log.info("http response code:{}", response.getStatusLine().getStatusCode());
log.info("http response result:{}",entity == null ? "" : EntityUtils.toString(entity));
} catch (IOException e) {
e.printStackTrace();
}

十. 对mq中间件的支持

目前Tlog对于消息中间件的支持是有侵入性的,暂时无法做到完全无侵入。

对于客户端,你只需要用TLogMqWrapBean包装你的业务bean就可以了。

TLogMqWrapBean<BizBean> tLogMqWrap = new TLogMqWrapBean(bizBean);mqClient.send(tLogMqWrap);

对于消费者端,你需要这么做:

//从mq里接受到tLogMqWrapBean
TLogMqConsumerProcessor.process(tLogMqWrapBean, new TLogMqRunner<BizBean>() {
@Override
public void mqConsume(BizBean o) {
//业务操作
}
});

十一. 对SpringCloud Gateway的支持

TLog从1.3.0开始,对spring cloud gateway也进行了支持

你只需在gateway的server项目中引入tlog-web-spring-boot-startertlog-gateway-spring-boot-starter模块即可

TLog会在gateway启动时进行自动装载适配。

如果进行了全需依赖导入,则无需再进行按需依赖导入

十二. 自动打印调用参数和时间

对于springboot应用来说,你只需在springboot的配置文件中作如下配置:

# 不配默认为falsetlog.enable-invoke-time-print=true

当你进行RPC调用时,会自动打印出参数信息和调用时间信息:

2020-12-01 19:20:07.768 [DubboServerHandler-127.0.0.1:30900-thread-2] INFO  c.y.tlog.dubbo.filter.TLogDubboInvokeTimeFilter - <0.1><7592057736843136> [TLOG]开始调用接口[DemoService]的方法[sayHello],参数为:["jack"]
2020-12-01 19:20:07.787 [DubboServerHandler-127.0.0.1:30900-thread-2] INFO c.y.t.example.dubbo.service.impl.DemoServiceImpl - <0.1><7592057736843136> logback-dubbox-provider:invoke method sayHello,name=jack
2020-12-01 19:20:07.788 [Thread-14] INFO c.y.tlog.example.dubbo.service.impl.AsynDomain - <0.1><7592057736843136> 这是异步方法哦
2020-12-01 19:20:07.788 [Thread-14] INFO c.y.tlog.example.dubbo.service.impl.AsynDomain - <0.1><7592057736843136> 异步方法开始
2020-12-01 19:20:07.789 [Thread-14] INFO c.y.tlog.example.dubbo.service.impl.AsynDomain - <0.1><7592057736843136> 异步方法结束
2020-12-01 19:20:07.795 [DubboServerHandler-127.0.0.1:30900-thread-2] INFO c.y.tlog.dubbo.filter.TLogDubboInvokeTimeFilter - <0.1><7592057736843136> [TLOG]结束接口[DemoService]中方法[sayHello]的调用,耗时为:90毫秒

十三. 自定义TraceId生成器

TLog默认采用snowflake算法生成traceId,当然你也可以去更换traceId的生成算法。

定义自己的traceId生成类去继承TLogIdGenerator接口:

public class TestIdGenerator extends TLogIdGenerator {
@Override
public String generateTraceId() {
return String.valueOf(System.nanoTime());
}
}

然后在springboot的配置类里声明:

tlog.id-generator=com.yomahub.tlog.example.dubbo.id.TestIdGenerator

十四. SpringCloud的Openfeign

如果你的RPC是spring cloud,需要在spring xml里如下配置

<bean class="com.yomahub.tlog.feign.filter.TLogFeignFilter"/>
<bean class="com.yomahub.tlog.core.aop.AspectLogAop"/>

同时需要在spring mvc的xml里做如下配置

<mvc:interceptors>
<bean class="com.yomahub.tlog.web.interceptor.TLogWebInterceptor" />
</mvc:interceptors>

文章目录
  1. 1. 一. TLog简介
  2. 2. 二. TLog安装
  3. 3. 三. 适配方式
    1. 3.0.1. 1. Logback框架适配器
      1. 3.0.1.0.1. 1.1. 同步日志
      2. 3.0.1.0.2. 1.2. 异步日志
  • 4. 四. 日志标签模板自定义
  • 5. 五. 标签位置自定义
  • 6. 六. SpanId的生成规则
  • 7. 七. 业务自定义标签
    1. 7.0.0.1. 示例一 打印入参
    2. 7.0.0.2. 示例二 常量标签
    3. 7.0.0.3. 示例三 点操作符
    4. 7.0.0.4. 示例四 自定义Convertor
  • 8. 八. 线程支持
    1. 8.0.1. 1. 一般异步线程
    2. 8.0.2. 2. 线程池
    3. 8.0.3. 3. MDC模式的异步线程
  • 9. 九. 对HTTPClient的支持
  • 10. 十. 对mq中间件的支持
  • 11. 十一. 对SpringCloud Gateway的支持
  • 12. 十二. 自动打印调用参数和时间
  • 13. 十三. 自定义TraceId生成器
  • 14. 十四. SpringCloud的Openfeign