项目实战第二篇:一文搞懂全链路日志实现(一)

本文介绍了如何在高并发的微服务项目中使用MDC技术结合Logback实现全链路日志管理,包括如何定义切点、在请求中注入logId以及如何配置logback以包含MDC上下文。后续将探讨异步和不同通信协议的日志解决方案。
摘要由CSDN通过智能技术生成

一个讨厌八股的博主:关注我每周学习项目实战调优小技巧,拒绝CV,拒绝屎山


前言

看到标题以后,是不有人会联想到前两年出现的log4j的漏洞,很多人把日志理解为一个简单的日志输出,甚至很多新手没有记录日志的习惯。其实一个好的日志记录习惯,可以更方便我们来调试程序,甚至是排查线上bug。那么问题来了,在我们的实际生产项目中,如果并发很高的情况下,会有以下几个问题:

  • 日志输出太快了,往往都找不到自己想找的
  • 哪怕是根据关键词找到了日志,该怎么知道这条日志是不是我想要的呢
  • 在我们的微服务架构当中,有没有一种方案能够从流量入口到流量完结都能够找到日志呢,比如说A调用B,通过A服务输出的日志,也可以知道B服务输出的日志

今天主角:在微服务架构下,全链路日志处理方案要登场了。

一、基础理论知识

1.1 MDC 技术

MDC(Mapped Diagnostic Context)是一种日志记录技术,常用于Java应用程序中,特别是在基于日志框架如Logback、Log4j等的应用中。MDC 技术允许在应用程序的不同执行线程中共享上下文信息,使得在日志输出时能够包含更多的上下文信息,方便日志的追踪、调试和分析。

MDC 技术的核心思想是在每个线程中维护一个上下文信息的映射表(通常是一个类似于Map的数据结构),在需要的时候将关键信息存储在这个映射表中。当线程执行日志输出操作时,日志框架会自动将当前线程的 MDC 上下文信息包含到日志中,从而使得日志中能够包含更多的上下文信息。

使用 MDC 技术,可以在日志中添加诸如用户ID、会话ID、请求ID等关键信息,使得在分布式系统中更容易追踪某个请求或者操作的完整执行路径。

这里需要注意两个点:1个是每个线程,一个是上下文,也就是说MDC是一个线程安全的记录技术,每个线程都有独享的信息。

二、具体实现方案

现在市面上实现全链路日志的方案太多太多了,但是万变不离其宗(学好底层,走遍天下不用怕)
可以利用AOP来做,可以用开源组件来做,也可以通过logback。本文主要介绍一下最常用的logback+MDC。

1.定义切点

切点怎么定义?
首先我们所有微服务的流量入口无非就这么几个:

  • HTTP接口
  • RPC
  • 定时任务
  • MQ
    最大的流量入口是HTTP(本文先介绍单体应用该怎么实现全链路日志,下篇会介绍在微服务场景下该怎么实现日志的闭环)
    代码如下(示例):
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.MDC;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;

@Component
@Aspect
@Order(Ordered.HIGHEST_PRECEDENCE)
@AllArgsConstructor
@Slf4j
public class MdcAspect {
    public static final String logIdKey = "logId";



    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 = StrUtil.isNotBlank(logId) ? (logId + "_") : "" + IdUtil.fastUUID();
        MDC.put(logIdKey, logId);
        try {
            return proceedingJoinPoint.proceed();
        } finally {
            MDC.remove(logIdKey);
        }
    }

从上面代码可以看出,我拦截了所有的get,post,put,delete请求,然后生成了一个uuid,放到了MDC中。MDC就可以理解是一个线程独享的map。

2.修改logback配置文件

代码如下(示例):

<!--每隔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>


该配置文件当中

 <pattern>%d{HH:mm:ss} [%thread] %-5level %logger{36} [%X{logId}] - %msg%n</pattern>

中的logId就是我们的全链路日志(一个http请求一个独有的日志id)


总结

非常简单,两步就做好一个单体的链路日志,但是还有很多问题,比如说:

  • 异步问题怎么获取到这个logId?
  • 微服务架构怎么做到这个logId的传递?
  • 以上方案只是解决了http接口的,那我的RPC接口,MQ里的日志输出中的logId从哪来?

下一篇博客继续…
一个讨厌八股的博主:关注我每周学习项目实战调优小技巧,拒绝CV,拒绝屎山

  • 29
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值