springboot 集成tlog 实现分布式日志标记追踪

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: springboot 集成tlog 实现分布式日志标记追踪

一. 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>
    #application.yml中配置
    tlog: 
    # 自动打印调用参数和时间 
    enable-invoke-time-print: true
    # 打印上层服务名称、ip
    pattern: '[$preApp][$preIp][$spanId][$traceId]'


    三. 适配方式

    支持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]

    1

    $preApp:上游微服务节点名称


    $preHost:上游微服务的Host Name


    $preIp:上游微服务的IP地址


    $spanId:链路spanId

    1

    $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();

                1

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


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

                1

                九. 对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);

                  1

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

                  //从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-starter和tlog-gateway-spring-boot-starter模块即可


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


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


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

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


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

                  1

                  当你进行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>


                          相关实践学习
                          日志服务之使用Nginx模式采集日志
                          本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
                          目录
                          相关文章
                          |
                          4天前
                          |
                          存储 安全 Java
                          Spring Boot 3 集成Spring AOP实现系统日志记录
                          本文介绍了如何在Spring Boot 3中集成Spring AOP实现系统日志记录功能。通过定义`SysLog`注解和配置相应的AOP切面,可以在方法执行前后自动记录日志信息,包括操作的开始时间、结束时间、请求参数、返回结果、异常信息等,并将这些信息保存到数据库中。此外,还使用了`ThreadLocal`变量来存储每个线程独立的日志数据,确保线程安全。文中还展示了项目实战中的部分代码片段,以及基于Spring Boot 3 + Vue 3构建的快速开发框架的简介与内置功能列表。此框架结合了当前主流技术栈,提供了用户管理、权限控制、接口文档自动生成等多项实用特性。
                          30 8
                          |
                          1月前
                          |
                          XML Java API
                          Spring Boot集成MinIO
                          本文介绍了如何在Spring Boot项目中集成MinIO,一个高性能的分布式对象存储服务。主要步骤包括:引入MinIO依赖、配置MinIO属性、创建MinIO配置类和服务类、使用服务类实现文件上传和下载功能,以及运行应用进行测试。通过这些步骤,可以轻松地在项目中使用MinIO的对象存储功能。
                          |
                          1月前
                          |
                          缓存 NoSQL Java
                          Spring Boot中的分布式缓存方案
                          Spring Boot提供了简便的方式来集成和使用分布式缓存。通过Redis和Memcached等缓存方案,可以显著提升应用的性能和扩展性。合理配置和优化缓存策略,可以有效避免常见的缓存问题,保证系统的稳定性和高效运行。
                          51 3
                          |
                          2月前
                          |
                          消息中间件 Java Kafka
                          什么是Apache Kafka?如何将其与Spring Boot集成?
                          什么是Apache Kafka?如何将其与Spring Boot集成?
                          79 5
                          |
                          1月前
                          |
                          存储 运维 数据可视化
                          如何为微服务实现分布式日志记录
                          如何为微服务实现分布式日志记录
                          71 1
                          |
                          2月前
                          |
                          存储 Java 关系型数据库
                          在Spring Boot中整合Seata框架实现分布式事务
                          可以在 Spring Boot 中成功整合 Seata 框架,实现分布式事务的管理和处理。在实际应用中,还需要根据具体的业务需求和技术架构进行进一步的优化和调整。同时,要注意处理各种可能出现的问题,以保障分布式事务的顺利执行。
                          109 6
                          |
                          2月前
                          |
                          消息中间件 Java Kafka
                          Spring Boot 与 Apache Kafka 集成详解:构建高效消息驱动应用
                          Spring Boot 与 Apache Kafka 集成详解:构建高效消息驱动应用
                          63 1
                          |
                          2月前
                          |
                          Java 中间件
                          SpringBoot入门(6)- 添加Logback日志
                          SpringBoot入门(6)- 添加Logback日志
                          57 1
                          |
                          2月前
                          |
                          消息中间件 监控 Java
                          您是否已集成 Spring Boot 与 ActiveMQ?
                          您是否已集成 Spring Boot 与 ActiveMQ?
                          61 0
                          |
                          3月前
                          |
                          JavaScript 安全 Java
                          如何使用 Spring Boot 和 Ant Design Pro Vue 实现动态路由和菜单功能,快速搭建前后端分离的应用框架
                          本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 实现动态路由和菜单功能,快速搭建前后端分离的应用框架。首先,确保开发环境已安装必要的工具,然后创建并配置 Spring Boot 项目,包括添加依赖和配置 Spring Security。接着,创建后端 API 和前端项目,配置动态路由和菜单。最后,运行项目并分享实践心得,包括版本兼容性、安全性、性能调优等方面。
                          210 1