[SpringAop + Logback +MDC] 现网必备全链路日志追踪

2024-01-14 20:12

本文主要是介绍[SpringAop + Logback +MDC] 现网必备全链路日志追踪,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

缘起:前几天有个粉丝私信,想了解现网环境如果出现问题,怎么快速定位。可能有些小伙伴这时候就会脱口而出,直接去看log 呗,有什么好说的。

但是,众所周知,后端服务面向的前端应用是多种多样的,比如有web , app ,小程序等等。也就是说后端服务往往是在一个并发环境下工作,如下图所示,所以日志文件并不一定是按顺序持久化打印的,当然这只是一个极其简陋的模型,实际上在互联网公司工作遇到的日志比这复杂很多,日志的采集也特别讲究 …

所以在一个混乱的日志文件中,有个标识可以将跳跃日志信息,关联起来往往可以决定你是否可以正常下班,这也是本篇文章也要达到的目的~

话不多说,进入正文

文章目录

    • 什么是全链路日志追踪?
    • 怎么用?
      • 1. 配置Logback
      • 2. 编写日志追踪切面
      • 3. 定义任务装饰器
      • 4. 定义线程池
      • 5. 使用线程池
      • 6. 完整的`pom.xml`
    • 为什么是必备的?
    • 总结


本文将介绍什么是全链路日志追踪,如何使用Spring AOPLogback 来实现,以及为什么全链路日志追踪在现网系统中是必备的。

环境: jdk17 + springboot 3

在这里插入图片描述

在分布式系统中,追踪请求的全链路日志对于故障排查和性能优化非常重要。Spring AOP 是常用的Java 技术,它可以结合其他技术(比如logback)灵活实现各种功能。比如本次结合使用来实现全链路日志追踪。

什么是全链路日志追踪?

全链路日志追踪是指在一个分布式系统中追踪请求的完整路径,并记录每个请求在系统中的处理过程中所产生的日志信息。它可以帮助我们了解请求在系统中的每个环节的执行情况,从而更好地排查问题和优化系统性能。

在一个典型的分布式系统中,一个请求可能会经过多个服务组件的处理,每个组件都会产生日志。全链路日志追踪可以将这些日志串联起来,并为每个请求生成一个唯一的标识符,以便在需要时能够方便地查找和分析。

其实说白了,就是在日志文件中加入一个可以标识每个请求的东西,如下所示,可以看到a请求的日志中有一串字符串bb946fc6-7b4f-41eb-800c-7ac7b727ab76 , b请求也有一串字符串6f4a64e1-6fce-4701-ad9f-21ff992b5987 在现网的日志文件,我们就可以通过搜索这个字符串将跳跃的日志文件串联成一个完整的请求上下文,而这个字符串就是后端程序员常说的trace_id 分布式追踪id

在这里插入图片描述

怎么用?

下面将介绍如何使用Spring AOPLogback + MDC来实现全链路日志追踪。

首先简单展示一下,demo 项目结构

在这里插入图片描述

1. 配置Logback

首先,我们需要在应用程序中配置Logback作为日志框架。可以通过在resources下创建logback.xml文件来进行配置。

<!--每隔15s 重新加载日志配置文件-->
<configuration scan="true" scanPeriod="15 seconds"><appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"><encoder>
<!--            %X{var} 注入var变量的内容--><pattern>%d{HH:mm:ss} [%thread] %-5level %logger{36} [%X{logId}] - %msg%n</pattern></encoder></appender><!--    定义变量--><property name="LOG_DIR" value="logs" /><property name="LOG_FILE" value="detail.log" /><!--    注意不要用错class --><appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"><file>${LOG_DIR}/${LOG_FILE}</file>
<!--        压缩归档策略--><rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"><!-- daily rollover --><!-- 按分钟归档 --><fileNamePattern>${LOG_DIR}/${LOG_FILE}.%d{yyyy-MM-dd-HH-mm}.gz</fileNamePattern><!-- keep 30 days' worth of history capped at 3GB total size --><maxHistory>30</maxHistory><totalSizeCap>1GB</totalSizeCap></rollingPolicy><append>true</append><encoder><pattern>%d{HH:mm:ss} [%thread] %-5level %logger{36} [%X{logId}] - %msg%n</pattern></encoder></appender><root level="info"><appender-ref ref="STDOUT" /><appender-ref ref="FILE" /></root>
</configuration>

在这个配置文件中,我们定义了日志输出格式、日志文件路径以及日志的归档方式。你可以根据自己的需求进行配置,具体可以简单谷歌一下。

2. 编写日志追踪切面

接下来,我们需要使用Spring AOP 编写一个切面,来实现日志追踪功能。切面可以拦截请求,并在每个请求的上下文注入追踪id。

@Component
@Aspect
@Order(Ordered.HIGHEST_PRECEDENCE)
@AllArgsConstructor
public class MdcAspect {public static final String logIdKey = "logId";private static final Logger log = LoggerFactory.getLogger(MdcAspect.class);private final HttpServletRequest httpServletRequest;@Around("@annotation(org.springframework.web.bind.annotation.GetMapping) " +"|| @annotation(org.springframework.web.bind.annotation.PostMapping) " +"|| @annotation(org.springframework.web.bind.annotation.PutMapping) " +"|| @annotation(org.springframework.web.bind.annotation.DeleteMapping) ")public Object injectLogId(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{String logId = httpServletRequest.getHeader(MdcAspect.logIdKey);logId = StringUtils.isNotBlank(logId) ? (logId + "_") : "" + UUID.randomUUID();MDC.put(logIdKey, logId);log.info("inject logId = {}", logId);try {return proceedingJoinPoint.proceed();} finally {MDC.remove(logIdKey);}}}

在这个切面中,我们使用@Around注解来拦截所有的方法调用。这个切面主要是用于拦截请求,然后在每个请求中注入一个唯一字符串作为每个请求的标识。

现在,当应用程序启动时,切面将会拦截所有被注解的方法,然后在请求上下文注入这么一串唯一字符串。

但是有个问题,就是在多线程环境下,这个唯一字符串没办法从父进程传递给子进程,所以我们采用TaskDecorator的线程任务装饰器方式为线程池的线程提供traceId的传递操作。

3. 定义任务装饰器

public class MDCTaskDecorator implements TaskDecorator {@Overridepublic @NonNull Runnable decorate(@NonNull Runnable runnable) {// 此时获取的是父线程的上下文数据Map<String, String> contextMap = MDC.getCopyOfContextMap();return () -> {try {if (!ObjectUtils.isEmpty(contextMap)) {String logId = contextMap.getOrDefault(MdcAspect.logIdKey, UUID.randomUUID().toString());logId += "_" + Thread.currentThread().getName();contextMap.put(MdcAspect.logIdKey, logId);// 内部为子线程的领域范围,所以将父线程的上下文保存到子线程上下文中,而且每次submit/execute调用都会更新为最新的上                     // 下文对象MDC.setContextMap(contextMap);}runnable.run();} finally {// 清除子线程的,避免内存溢出,就和ThreadLocal.remove()一个原因MDC.clear();}};}
}

4. 定义线程池

@Configuration
public class PoolConfig {@Bean("taskExecutor")public Executor taskExecutor() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();//配置核心线程数executor.setCorePoolSize(5);//配置最大线程数executor.setMaxPoolSize(10);//配置队列大小executor.setQueueCapacity(100);//配置线程池中的线程的名称前缀executor.setThreadNamePrefix("mdc-trace-");// 异步MDCexecutor.setTaskDecorator(new MDCTaskDecorator());//执行初始化executor.initialize();return executor;}
}

5. 使用线程池

  @Resource@Qualifier("taskExecutor")private Executor executor;@PostMapping("/pool")public String pool() throws Exception{CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {log.info("线程池里面");try{Thread.sleep(1000);} catch (Exception ignored){}return "";}, executor);future.thenApplyAsync( value ->{log.info("线程池组合操作");try{Thread.sleep(1000);} catch (Exception ignored) {}return value + "1";}, executor);return future.get();}

6. 完整的pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.2.1</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.example</groupId><artifactId>easy_mdc</artifactId><version>0.0.1-SNAPSHOT</version><name>easy_mdc</name><description>easy_mdc</description><properties><java.version>17</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><!-- 引入aop支持 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency><dependency><groupId>commons-lang</groupId><artifactId>commons-lang</artifactId><version>2.6</version></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><configuration><excludes><exclude><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></exclude></excludes></configuration></plugin></plugins></build></project>

为什么是必备的?

全链路日志追踪在现网系统中是必备的,原因如下:

  1. 故障排查:当系统发生故障时,全链路日志追踪可以帮助我们快速定位问题所在。通过追踪请求的完整路径,我们可以找到导致故障的具体组件或方法,从而更有效地进行故障排查。

  2. 性能优化:全链路日志追踪可以帮助我们发现系统中的性能瓶颈。通过分析日志,我们可以了解每个请求在系统中的处理时间和资源消耗,从而找到需要优化的地方,并进行相应的性能优化。

  3. 监控和统计:全链路日志追踪可以为系统的监控和统计提供数据支持。通过收集和分析日志信息,我们可以了解系统的运行情况、请求的处理情况和各个组件的性能指标,从而对系统进行实时监控和统计分析。

综上所述,全链路日志追踪是现网系统中必备的工具,它可以帮助我们更好地排查问题、优化性能,并提供对系统的监控和统计能力。

总结

本文介绍了全链路日志追踪的概念和重要性,并通过使用Spring AOP和Logback来实现全链路日志追踪的示例。通过配置Logback作为日志框架,在切面中记录请求的开始和结束日志,我们可以实现全链路日志追踪的功能。最后,我们强调了全链路日志追踪在现网系统中的必备性,它可以帮助我们排查问题、优化性能,并提供对系统的监控和统计能力。

当然本文只是一个简单参考,实现方法还有很多,仅抛砖引玉~

希望本文能够帮助你理解和应用全链路日志追踪。如果你有任何问题或建议,请随时留言。

欢迎关注我们的微信公众号,获取更多有关Java和Spring的技术文章和教程。

这篇关于[SpringAop + Logback +MDC] 现网必备全链路日志追踪的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/606383

相关文章

flume系列之:查看flume系统日志、查看统计flume日志类型、查看flume日志

遍历指定目录下多个文件查找指定内容 服务器系统日志会记录flume相关日志 cat /var/log/messages |grep -i oom 查找系统日志中关于flume的指定日志 import osdef search_string_in_files(directory, search_string):count = 0

我在移动打工的日志

客户:给我搞一下录音 我:不会。不在服务范围。 客户:是不想吧 我:笑嘻嘻(气笑) 客户:小姑娘明明会,却欺负老人 我:笑嘻嘻 客户:那我交话费 我:手机号 客户:给我搞录音 我:不会。不懂。没搞过。 客户:那我交话费 我:手机号。这是电信的啊!!我这是中国移动!! 客户:我不管,我要充话费,充话费是你们的 我:可是这是移动!!中国移动!! 客户:我这是手机号 我:那又如何,这是移动!你是电信!!

Detectorn2预训练模型复现:数据准备、训练命令、日志分析与输出目录

Detectorn2预训练模型复现:数据准备、训练命令、日志分析与输出目录 在深度学习项目中,目标检测是一项重要的任务。本文将详细介绍如何使用Detectron2进行目标检测模型的复现训练,涵盖训练数据准备、训练命令、训练日志分析、训练指标以及训练输出目录的各个文件及其作用。特别地,我们将演示在训练过程中出现中断后,如何使用 resume 功能继续训练,并将我们复现的模型与Model Zoo中的

SSM项目使用AOP技术进行日志记录

本步骤只记录完成切面所需的必要代码 本人开发中遇到的问题: 切面一直切不进去,最后发现需要在springMVC的核心配置文件中中开启注解驱动才可以,只在spring的核心配置文件中开启是不会在web项目中生效的。 之后按照下面的代码进行配置,然后前端在访问controller层中的路径时即可观察到日志已经被正常记录到数据库,代码中有部分注释,看不懂的可以参照注释。接下来进入正题 1、导入m

多数据源的事务处理总是打印很多无用的log日志

之前做了一个项目,需要用到多数据源以及事务处理,在使用事务处理,服务器总是打印很多关于事务处理的log日志(com.atomikos.logging.Slf4jLogger),但是我们根本不会用到这些log日志,反而使得查询一些有用的log日志变得困难。那要如何屏蔽这些log日志呢? 之前的项目是提高项目打印log日志的级别,后来觉得这样治标不治本。 现在有一个更好的方法: 我使用的是log

android两种日志获取log4j

android   log4j 加载日志使用方法; 先上图: 有两种方式: 1:直接使用架包 加载(两个都要使用); 架包:android-logging-log4j-1.0.3.jar 、log4j-1.2.15.jar  (说明:也可以使用架包:log4j-1.2.17.jar)  2:对架包输入日志的二次封装使用; 1:直接使用 log4j 日志框架获取日志信息: A:配置 日志 文

Unity Post Process Unity后处理学习日志

Unity Post Process Unity后处理学习日志 在现代游戏开发中,后处理(Post Processing)技术已经成为提升游戏画面质量的关键工具。Unity的后处理栈(Post Processing Stack)是一个强大的插件,它允许开发者为游戏场景添加各种视觉效果,如景深、色彩校正、辉光、模糊等。这些效果不仅能够增强游戏的视觉吸引力,还能帮助传达特定的情感和氛围。 文档

提升PrestaShop外贸电商网站安全的几款行业必备工具

提升PrestaShop外贸电商网站安全的几款行业必备工具 PrestaShop发展历程 PrestaShop是一款优秀且强大的外贸开源电商软件,我们开始使用PrestaShop始于2009年,那时PrestaShop还是0.9版本:界面清新,性能强悍,扩展友好等特性,既没有Magento的笨重,也没有ZenCart的古老,更没有OpenCart的脆弱,因此PrestaShop如雨后春笋,迅速

zblog自定义关键词和描述,zblog做seo优化必备插件

zblog自定义关键词和描述,zblog做seo优化必备插件     首先说下用到的一款插件:CustomMeta自定义数据字段 ,我们这里用到的版本是1.1,1.1+版增加了列表页标签支持!     插件介绍:文章,分类等添加自定义数据字段。1.1+版适用于 Z-Blog 2.0 B2以上版本。     在zblog2.0beta1里面,这个插件是集成到了程序里面,beta2里面默认没有了

logback配置实例

logger和appender的关系 root是根logger,所以他两是一回事;只不过root中不能有name和additivity属性,是有一个level。 appender是一个日志打印的组件,这里组件里面定义了打印过滤的条件、打印输出方式、滚动策略、编码方式、打印格式等等。但是它仅仅是一个打印组件,如果我们不使用一个logger或者root的appender-ref指定某个具体的app