2025-03-26
工具运维
0

目录

TLog
快速接入指南
Javaagent 接入方式
字节码注入方式
日志框架适配器方式(官方推荐)
高级功能
自定义日志标签模板
业务自定义标签
自定义TraceId
MDC 模式下logback的配置
Logback框架适配器
异步线程支持
一般异步线程
线程池支持
任务框架的支持
JDK TIMER 任务
Quartz 框架的支持
spring-scheduled 的支持
XXL-JOB 框架的支持
实战演示
基础使用
性能分析
最佳实践
总结

随着微服务架构的盛行,很多公司都把系统按照业务边界拆分成多个微服务节点。在排查问题查看日志时,由于业务链路贯穿多个微服务节点,定位某个请求的完整日志链 变得极其困难。传统的分布式追踪系统如 SkyWalking、Pinpoint 等虽然功能强大,但 搭建和维护成本较高,对于中小型项目来说过于沉重。

正是在这样的背景下,TLog 应运而生。它是一款轻量级的分布式日志标记追踪神器,通过对日志自动打标签完成微服务链路追踪,几乎零性能损耗,10分钟即可快速接入。

TLog

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

核心特性

  • 无侵入式设计:对业务代码零侵入,使用简单,10分钟即可接入

  • 多日志框架支持:支持log4j、log4j2、logback三大日志框架,自动检测适配

  • 多RPC框架支持:支持dubbo、dubbox、springcloud三大RPC框架

  • 灵活的接入方式:提供javaagent完全无侵入接入、字节码一行代码接入、基于配置文件的接入三种方式

  • 异步线程追踪:支持线程池、多级异步线程等复杂场景的追踪

  • 自定义标签模板:支持配置自定义日志标签模板,提供多个系统级埋点标签

  • 高性能:几乎无性能损耗,快速稳定

快速接入指南

TLog提供三种接入方式,满足不同场景的需求。

Javaagent 接入方式

这种方式完全不侵入项目,利用 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 中自定义日志标签的生成样式:

properties
tlog.pattern=[$tlogTraceId][$tlogSpanId][$currIp][$tl]

支持的变量有:

  • tlogTraceId:链路ID
  • tlogSpanId:链路spanId
  • currIp:当前服务IP
  • preIvkApp:上游微服务节点名称
  • preIvkHost:上游微服务的Host Name
  • preIp:上游微服务的IP地址
  • NONE:自定义
  • tl:MDC标识,包含业务标签数据
  • exchange: 交换

业务自定义标签

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 这是测试方法

自定义TraceId

可以通过 tlog.id-generator 自定义 TraceId 生成器

yaml
tlog: id-generator: com.yomahub.tlog.example.id.TestIdGenerator

继承 TLogIdGenerator 接口即可,其默认实现类 TLogDefaultIdGenerator

java
public class TLogDefaultIdGenerator extends TLogIdGenerator{ @Override public String generateTraceId() { return UniqueIdGenerator.generateStringId(); } }

MDC 模式下logback的配置

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"]

Logback框架适配器

添加上此 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:

java
new Thread(() -> { logger.info("异步执行"); }).start();

线程池支持

对于线程池,需要使用TLogInheritableTask包装任务:

java
ThreadPoolExecutor pool = new ThreadPoolExecutor(1, 2, 1, TimeUnit.SECONDS, new LinkedBlockingQueue<>(10)); pool.execute(new TLogInheritableTask() { @Override public void runTask() { logger.info("异步执行"); } });

也可以自定义线程池简化操作:

java
ThreadPoolExecutor pool = new TLogThreadPoolExecutor(1, 2, 1, TimeUnit.SECONDS, new LinkedBlockingQueue<>(10)); pool.execute(() -> logger.info("异步执行"));

任务框架的支持

JDK TIMER 任务

从1.3.0版本开始,TLog对JDK Timer的定时任务作了支持。

只要把TimerTask替换成TLogTimerTask就可以了

示例:

java
Timer timer = new Timer(); timer.scheduleAtFixedRate(new TLogTimerTask() { @Override public void runTask() { log.info("hello,world"); } }, 100, 1000);

Quartz 框架的支持

从1.3.0版本开始,TLog对Spring Quartz框架作了支持,只需要把QuartzJobBean替换成TLogQuartzJobBean就可以了。

示例:

java
public 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); } }

spring-scheduled 的支持

从1.3.4版本开始,TLog对Spring中的@Scheduled作了支持。

在springboot环境下,你无需作任何改动。只需引入依赖包即可生效。

而在spring native下,你需要额外配置一行

xml
<bean class="com.yomahub.tlog.task.spring.SpringScheduledTaskAop"/>

XXL-JOB 框架的支持

从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("异步方法结束"); } }

结果输出

text
2024-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 许可协议。转载请注明出处!