随着微服务架构的盛行,很多公司都把系统按照业务边界拆分成多个微服务节点。在排查问题查看日志时,由于业务链路贯穿多个微服务节点,定位某个请求的完整日志链 变得极其困难。传统的分布式追踪系统如 SkyWalking、Pinpoint 等虽然功能强大,但 搭建和维护成本较高,对于中小型项目来说过于沉重。
正是在这样的背景下,TLog 应运而生。它是一款轻量级的分布式日志标记追踪神器,通过对日志自动打标签完成微服务链路追踪,几乎零性能损耗,10分钟即可快速接入。
TLog 提供了一种最简单的方式来解决日志追踪问题,它不收集日志,也不需要另外的存储空间,只是自动地对你的日志进行打标签,生成 TraceId 贯穿微服务的一整条链路,并提供上下游节点信息。适合中小型企业以及想快速解决日志追踪问题的公司项目使用。
核心特性
无侵入式设计:对业务代码零侵入,使用简单,10分钟即可接入
多日志框架支持:支持log4j、log4j2、logback三大日志框架,自动检测适配
多RPC框架支持:支持dubbo、dubbox、springcloud三大RPC框架
灵活的接入方式:提供javaagent完全无侵入接入、字节码一行代码接入、基于配置文件的接入三种方式
异步线程追踪:支持线程池、多级异步线程等复杂场景的追踪
自定义标签模板:支持配置自定义日志标签模板,提供多个系统级埋点标签
高性能:几乎无性能损耗,快速稳定
TLog提供三种接入方式,满足不同场景的需求。
这种方式完全不侵入项目,利用 Javaagent 在启动时加入jar包,整个过程1分钟就能搞定。
使用步骤:
下载 tlog-agent.jar,可以在官方发布页面下载。在Java启动参数中加入:
text-javaagent:/path/to/tlog-agent.jar
只需要这一步,就可以把springboot项目快速接入。
这种方式适合springboot项目,需要项目依赖 tlog-all-spring-boot-starter 包。
依赖配置:
xml<dependency>
<groupId>com.yomahub</groupId>
<artifactId>tlog-all-spring-boot-starter</artifactId>
<version>1.5.2</version>
</dependency>
在启动类中加入一行代码:
java@SpringBootApplication
public class Application {
static {AspectLogEnhance.enhance();}//进行日志增强,自动判断日志框架
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
警告
这种方式和javaagent接入差不多,虽然简单,但是不支持MDC和异步日志。而且只支持springboot项目。
TLog 对 springboot 和 spring native 提供了2种不同的依赖,此种方式只需依赖一个包,必须的包会传递依赖进来
springboot 全量依赖
xml<dependency>
<groupId>com.yomahub</groupId>
<artifactId>tlog-all-spring-boot-starter</artifactId>
<version>1.5.2</version>
</dependency>
spring native 依赖
xml<dependency>
<groupId>com.yomahub</groupId>
<artifactId>tlog-all</artifactId>
<version>1.5.2</version>
</dependency>
或者根据模块部分依赖配置:
xml<dependency>
<groupId>com.yomahub</groupId>
<artifactId>tlog-xxx-spring-boot-starter</artifactId>
<version>1.5.2</version>
</dependency>
具体模块和描述如下表
| 模块名 | 描述 |
|---|---|
| tlog-dubbo-spring-boot-starter | 适用于apache dubbo的项目 |
| tlog-dubbox-spring-boot-starter | 适用于当当的dubbox的项目 |
| tlog-feign-spring-boot-starter | 适用于spring cloud中open feign的项目 |
| tlog-gateway-spring-boot-starter | 适用于spring cloud中的gateway网关服务 |
| tlog-soul-spring-boot-starter | 适用于soul网关服务 |
| tlog-web-spring-boot-starter | 适用于有spring web的项目 |
| tlog-xxljob-spring-boot-starter | 适用于xxl-job的项目 |
SpringBoot 中默认的日志框架是 Logback,我们以 Logback 配置示例:
xml<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>
以上为同步日志的处理,如果是异步日志,可以通过以下配置:
xml<!-- 这里替换成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>
提示
TLog 提供了针对每一种日志框架适配的方式,需要你去修改日志的配置文件,替换相应的类 ,配置方式也很简单,下面给出了每一种场景的示例
这种方式支持了全特性,为官方推荐方式
在 application.properties 中自定义日志标签的生成样式:
propertiestlog.pattern=[$tlogTraceId][$tlogSpanId][$currIp][$tl]
支持的变量有:
TLog支持方法级别的自定义业务标签,在方法上添加@TLogAspect注解:
java@TLogAspect({"id","name"})
public String test(String id, String name) {
logger.info("这是测试方法");
return "success";
}
假设id的值为'NO1234',name为'jenny',日志输出:
text<0><7161457983341056> id:NO1234,name:jenny 这是测试方法
可以通过 tlog.id-generator 自定义 TraceId 生成器
yamltlog:
id-generator: com.yomahub.tlog.example.id.TestIdGenerator
继承 TLogIdGenerator 接口即可,其默认实现类 TLogDefaultIdGenerator
javapublic class TLogDefaultIdGenerator extends TLogIdGenerator{
@Override
public String generateTraceId() {
return UniqueIdGenerator.generateStringId();
}
}
TLog 中 logback 的 MDC 占位符为 %X{tl},想要 MDC 生效,需要添加 TLogLogbackTTLMdcListener 监听器
xml<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
<!-- 增加如下的TLog MDC Listener -->
<contextListener class="com.yomahub.tlog.core.enhance.logback.TLogLogbackTTLMdcListener"/>
<!--定义日志文件的存储地址 勿在 LogBack 的配置中使用相对路径-->
<property name="LOG_HOME" value="./logs" />
<!--控制台日志-->
<appender name="Console" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %X{tl} [%thread] %-5level %logger{50} - %msg%n</pattern>
</encoder>
</appender>
</configuration>
其中 %X{tl} 输出格式为 <0><f9fd5b91-41e8-410c-aaf4-9837a74b5073> [name:"123"]
添加上此 AspectLogbackEncoder 适配器,即可无需配置 MDC 的属性 %X{tl}
xml<?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>
</configuration>
输出格式为 <0><f9fd5b91-41e8-410c-aaf4-9837a74b5073> [name:"123"] 这是输出日志
提示
%X{tl} 此格式会出现在自定义好的日志位置中
AspectLogbackEncoder 则会自动匹配到输出的日志内容之前
直接通过new Thread创建异步线程,TLog天然支持携带traceId:
javanew Thread(() -> {
logger.info("异步执行");
}).start();
对于线程池,需要使用TLogInheritableTask包装任务:
javaThreadPoolExecutor pool = new ThreadPoolExecutor(1, 2, 1, TimeUnit.SECONDS, new LinkedBlockingQueue<>(10));
pool.execute(new TLogInheritableTask() {
@Override
public void runTask() {
logger.info("异步执行");
}
});
也可以自定义线程池简化操作:
javaThreadPoolExecutor pool = new TLogThreadPoolExecutor(1, 2, 1, TimeUnit.SECONDS, new LinkedBlockingQueue<>(10));
pool.execute(() -> logger.info("异步执行"));
从1.3.0版本开始,TLog对JDK Timer的定时任务作了支持。
只要把TimerTask替换成TLogTimerTask就可以了
示例:
javaTimer timer = new Timer();
timer.scheduleAtFixedRate(new TLogTimerTask() {
@Override
public void runTask() {
log.info("hello,world");
}
}, 100, 1000);
从1.3.0版本开始,TLog对Spring Quartz框架作了支持,只需要把QuartzJobBean替换成TLogQuartzJobBean就可以了。
示例:
javapublic class DateTimeJob extends TLogQuartzJobBean {
private Logger log = LoggerFactory.getLogger(this.getClass());
@Override
public void executeTask(JobExecutionContext jobExecutionContext) throws JobExecutionException {
//获取JobDetail中关联的数据
String msg = (String) jobExecutionContext.getJobDetail().getJobDataMap().get("msg");
log.info("current time :"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + "---" + msg);
}
}
从1.3.4版本开始,TLog对Spring中的@Scheduled作了支持。
在springboot环境下,你无需作任何改动。只需引入依赖包即可生效。
而在spring native下,你需要额外配置一行
xml<bean class="com.yomahub.tlog.task.spring.SpringScheduledTaskAop"/>
从1.3.0版本开始,TLog对开源框架XXL-JOB作了支持。
在springboot环境下,你无需作任何改动。只需引入依赖包即可生效。
而在spring native环境下,你需要额外配置一行
xml<bean class="com.yomahub.tlog.xxljob.aop.TLogXxlJobAop"/>
spring cloud openfeign 同时需要 tlog-feign-spring-boot-starter 和 tlog-web-spring-boot-starter 包
xml<dependency>
<groupId>com.yomahub</groupId>
<artifactId>tlog-feign-spring-boot-starter</artifactId>
<version>1.5.2</version>
</dependency>
<dependency>
<groupId>com.yomahub</groupId>
<artifactId>tlog-web-spring-boot-starter</artifactId>
<version>1.5.2</version>
</dependency>
日志配置文件
xml<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- 增加如下的TLog MDC Listener -->
<contextListener class="com.yomahub.tlog.core.enhance.logback.TLogLogbackTTLMdcListener"/>
<!-- 属性 -->
<property name="log.path" value="logs"/>
<property name="log.maxHistory" value="15"/>
<property name="log.colorPattern" value="%magenta(%d{yyyy-MM-dd HH:mm:ss}) %highlight(%-5level) %boldCyan(%X{tl}) %yellow(%thread) %green(%logger{50}) %msg%n"/>
<property name="log.pattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %boldCyan(%X{tl}) %thread %logger{50} %msg%n"/>
<!-- 配置环境 -->
<springProfile name="dev">
<!-- 日志文件 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>DEBUG</level>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<FileNamePattern>${log.path}/info.%d{yyyy-MM-dd}.log</FileNamePattern>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${log.colorPattern}</pattern>
</encoder>
</appender>
<!-- 控制台 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>DEBUG</level>
</filter>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${log.colorPattern}</pattern>
</encoder>
</appender>
<!-- DEBUG -->
<logger name="liu" level="DEBUG" additivity="false">
<appender-ref ref="STDOUT"/>
<appender-ref ref="FILE"/>
</logger>
<!-- INFO -->
<root level="INFO">
<appender-ref ref="STDOUT"/>
<appender-ref ref="FILE"/>
</root>
</springProfile>
<springProfile name="prod">
<!-- 日志文件 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>DEBUG</level>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<FileNamePattern>${log.path}/info.%d{yyyy-MM-dd}.log</FileNamePattern>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${log.colorPattern}</pattern>
</encoder>
</appender>
<!-- 控制台 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>DEBUG</level>
</filter>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${log.colorPattern}</pattern>
</encoder>
</appender>
<!-- DEBUG -->
<logger name="liu" level="DEBUG" additivity="false">
<appender-ref ref="STDOUT"/>
<appender-ref ref="FILE"/>
</logger>
<!-- INFO -->
<root level="INFO">
<appender-ref ref="STDOUT"/>
<appender-ref ref="FILE"/>
</root>
</springProfile>
</configuration>
接入TLog后,代码中无需任何改动,直接使用日志API:
java@Slf4j
@RestController
public class DemoController {
@Resource
private AsyncAnnoDomain asyncAnnoDomain;
@TLogAspect("name")
@RequestMapping("/hi")
public String sayHello(@RequestParam String name){
log.info("invoke method sayHello,name={}",name);
asyncAnnoDomain.testAnnotationAsync();
new AsynDomain().start();
return "hello";
}
}
@Component
public class AsyncAnnoDomain {
private Logger log = LoggerFactory.getLogger(this.getClass());
@Async
public void testAnnotationAsync(){
log.info("这是异步标签日志");
}
}
public class AsynDomain extends Thread{
private Logger log = LoggerFactory.getLogger(this.getClass());
@Override
public void run() {
log.info("这是异步方法哦");
log.info("异步方法开始");
log.info("异步方法结束");
}
}
结果输出
text2024-11-05 17:48:33 INFO <0><b0fdba05-6e3b-48ea-b334-784a4f3b0b0c> [name:"123"] http-nio-8051-exec-1 c.y.t.e.RunnerApplication$$EnhancerBySpringCGLIB$$3cbc6fa2 invoke method sayHello,name=123 2024-11-05 17:48:33 INFO <0><b0fdba05-6e3b-48ea-b334-784a4f3b0b0c> [name:"123"] Thread-3 com.yomahub.tlog.example.AsynDomain 这是异步方法哦 2024-11-05 17:48:33 INFO <0><b0fdba05-6e3b-48ea-b334-784a4f3b0b0c> [name:"123"] Thread-3 com.yomahub.tlog.example.AsynDomain 异步方法开始 2024-11-05 17:48:33 INFO <0><b0fdba05-6e3b-48ea-b334-784a4f3b0b0c> [name:"123"] Thread-3 com.yomahub.tlog.example.AsynDomain 异步方法结束 2024-11-05 17:48:33 INFO <0><b0fdba05-6e3b-48ea-b334-784a4f3b0b0c> [name:"123"] task-1 com.yomahub.tlog.example.AsyncAnnoDomain 这是异步标签日志```
TLog在设计上充分考虑了性能因素,具有以下优势:
轻量级设计:不收集日志,只对日志进行标签增强,网络开销极小
异步处理:关键的耗时操作采用异步方式,不影响主业务流程
智能采样:支持采样率配置,在高并发场景下可控制日志输出量
本地变量:基于ThreadLocal的实现,上下文传递效率极高
在实际压测中,TLog的性能损耗通常低于1%,完全满足生产环境要求。
推荐选择 日志框架适配 接入方式,最稳定,功能完整,可扩展性高
虽然TLog本身不收集日志,但在微服务体系中,官方推荐使用TLog+ELK/EFK方案:
TLog负责生成和传递traceId
ELK负责日志的收集、存储和展示
通过traceId在Kibana中快速检索整个调用链路
注意事项
TraceId生成:默认使用Snowflake算法生成,可通过SPI扩展其他算法
传递失败处理:当traceId传递中断时,有完善的恢复机制
ID长度控制:支持traceId长度定制,避免日志膨胀
TLog作为一款轻量级的分布式日志标记追踪工具,在易用性、功能性和性能之间取得了很好的平衡:
简单易用:10分钟快速接入,学习成本低
功能完善:支持多种日志框架、RPC框架和复杂场景
性能优异:几乎零性能损耗,适合高并发场景
灵活扩展:支持多种接入方式和自定义扩展
对于尚未搭建完整分布式追踪系统的中小型项目,或者希望以最小成本解决日志追踪问题的团队,TLog无疑是一个极具性价比的选择。
本文作者:柳始恭
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!