java+springboot 做日志链路追踪

news2024/12/25 13:40:05

一、 为什么要做日志链路追踪

  • 日志链路追踪(Log Path Tracing)是Spring Boot项目的一项关键功能,它通过将日志消息的源头与其对应的请求或响应路径相关联,实现对日志数据的可视化跟踪。
  • 随着项目规模的扩大和复杂性的增加,追踪和管理日志数据变得越来越重要。通过实现日志链路追踪,我们可以更好地理解项目中发生的各种事件,识别瓶颈并快速定位问题。这不仅可以提高开发效率,还能保证项目质量。

二、实现方法

  1. 本文使用 MDC 方案实现日志链路追踪
  2. DC(Mapped Diagnostic Context,映射调试上下文)是 log4j 和 logback 提供的一种方便在多线程条件下记录日志的功能。
  3. 效果展示:
    在这里插入图片描述

三、 实现步骤

  1. 准备依赖 (可根据自己的项目修改springboot 的版本)
<dependency>
	<groupId>org.apache.skywalking</groupId>
    <artifactId>apm-toolkit-logback-1.x</artifactId>
    <version>8.15.0</version>
</dependency>
<dependency>
	<groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-dependencies</artifactId>
    <version>2.7.10</version>
	<type>pom</type>
	<scope>import</scope>
</dependency>
<dependency>
	<groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
	<groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-actuator</artifactId>
</dependency>
<dependency>
	<groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-actuator-autoconfigure</artifactId>
</dependency>
  1. 配置logback日志打印
    • 在项目resources目录下创建logback-spring.xml
    • 配置如下
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <contextName>${APP_NAME}</contextName>
    <springProperty name="APP_NAME" scope="context" source="spring.application.name"/>
    <springProperty name="LOG_FILE" scope="context" source="logging.file" defaultValue="../logs/application/${APP_NAME}"/>
    <springProperty name="LOG_MAXFILESIZE" scope="context" source="logback.filesize" defaultValue="50MB"/>
    <springProperty name="LOG_FILEMAXDAY" scope="context" source="logback.filemaxday" defaultValue="30"/>
    <springProperty name="ServerIP" scope="context" source="spring.cloud.client.ip-address" defaultValue="0.0.0.0"/>
    <springProperty name="ServerPort" scope="context" source="server.port" defaultValue="0000"/>
    <springProperty name="LOG_POINT_FILE" scope="context" source="logging.file" defaultValue="../logs/point"/>
    <springProperty name="LOG_AUDIT_FILE" scope="context" source="logging.file" defaultValue="../logs/audit"/>

    <!-- 彩色日志 -->
    <!-- 彩色日志依赖的渲染类 -->
    <conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter" />
    <conversionRule conversionWord="wex" converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter" />
    <conversionRule conversionWord="wEx" converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter" />

    <!-- 彩色日志格式 -->
    <property name="CONSOLE_LOG_PATTERN"
              value="[${APP_NAME}:${ServerIP}:${ServerPort}] %clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(%level){blue} %clr(${PID}){magenta} %clr([%X{traceId}]){yellow} %clr([%thread]){orange} %clr(%logger){cyan} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}" />
    <property name="CONSOLE_LOG_PATTERN_NO_COLOR" value="[${APP_NAME}:${ServerIP}:${ServerPort}] %d{yyyy-MM-dd HH:mm:ss.SSS} %level ${PID} [%X{traceId}] [%thread] %logger %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}" />

    <!-- 控制台日志 -->
    <appender name="StdoutAppender" class="ch.qos.logback.core.ConsoleAppender">
        <withJansi>true</withJansi>
        <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
            <layout class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.TraceIdPatternLogbackLayout">
                <Pattern>${CONSOLE_LOG_PATTERN}</Pattern>
            </layout>
        </encoder>
    </appender>
    <!-- **************************按照每天生成常规日志文件********************** -->
    <appender name="FileAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_FILE}/${APP_NAME}.log</file>
        <encoder>
            <pattern>${CONSOLE_LOG_PATTERN_NO_COLOR}</pattern>
            <charset>UTF-8</charset>
        </encoder>
        <!--基于时间的分包策略 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>${LOG_FILE}/${APP_NAME}.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <!--保留时间,单位:天-->
            <MaxHistory>${LOG_FILEMAXDAY}</MaxHistory>
            <MaxFileSize>${LOG_MAXFILESIZE}</MaxFileSize>
        </rollingPolicy>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>DEBUG</level>
        </filter>
    </appender>
    <appender name="file_async" class="ch.qos.logback.classic.AsyncAppender">
        <discardingThreshold>0</discardingThreshold>
        <appender-ref ref="FileAppender"/>
    </appender>
    <!-- **************************按照每天生成常规日志文件 end********************** -->

    <!-- *************************************** 埋点日志 start *************************************** -->
    <appender name="point_log" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_POINT_FILE}/point.${APP_NAME}.log</file>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS}|${APP_NAME}|%msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
        <!--基于时间的分包策略 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <FileNamePattern>${LOG_POINT_FILE}/point.${APP_NAME}.%d{yyyy-MM-dd}.%i.log</FileNamePattern>
            <!--保留时间,单位:天-->
            <MaxHistory>${LOG_FILEMAXDAY}</MaxHistory>
            <MaxFileSize>${LOG_MAXFILESIZE}</MaxFileSize>
        </rollingPolicy>
    </appender>
    <appender name="point_log_async" class="ch.qos.logback.classic.AsyncAppender">
        <discardingThreshold>0</discardingThreshold>
        <appender-ref ref="point_log"/>
    </appender>
    <!-- *************************************** 埋点日志  end  *************************************** -->

    <appender name="audit_log" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_AUDIT_FILE}/audit.log</file>
        <encoder>
            <pattern>%msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
        <!-- 基于时间的分包策略 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_AUDIT_FILE}/audit.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <!--保留时间,单位:天-->
            <maxHistory>${LOG_FILEMAXDAY}</maxHistory>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>${LOG_MAXFILESIZE}</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
        </rollingPolicy>
    </appender>

    <appender name="audit_log_async" class="ch.qos.logback.classic.AsyncAppender">
        <discardingThreshold>0</discardingThreshold>
        <appender-ref ref="audit_log"/>
    </appender>

    <!-- *************************************** logger配置 start *************************************** -->
    <logger name="cn.onehome.dubbocore.log.monitor" level="INFO" addtivity="false">
        <appender-ref ref="point_log_async" />
    </logger>
    <logger name="cn.onehome.dubbocore.log.service.impl.LoggerAuditServiceImpl" level="INFO" addtivity="false">
        <appender-ref ref="audit_log_async" />
    </logger>

    <!-- 多环境定制logger配置(根据spring.profiles.active值对应激活某个配置) -->
    <springProfile name="dev">
        <root level="INFO">
            <appender-ref ref="StdoutAppender"/>
            <appender-ref ref="file_async"/>
        </root>
    </springProfile>
    <springProfile name="test">
        <root level="INFO">
            <appender-ref ref="StdoutAppender"/>
            <appender-ref ref="file_async"/>
        </root>
    </springProfile>
    <springProfile name="pre">
        <root level="INFO">
            <appender-ref ref="StdoutAppender"/>
            <appender-ref ref="file_async"/>
        </root>
    </springProfile>
    <springProfile name="prod">
        <root level="INFO">
            <appender-ref ref="StdoutAppender"/>
            <appender-ref ref="file_async"/>
        </root>
    </springProfile>
    <!-- *************************************** logger配置  end  *************************************** -->
</configuration>

  1. 配置拦截器添加追踪日志参数
package cn.wje.common.filter;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.MDC;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.validation.constraints.NotNull;
import java.io.IOException;
import java.util.UUID;

/**
 * @author luoqifeng
 * @date 2023/05/18 10:13
 */
@Slf4j
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class AbstractTraceIdFilter implements Filter {

    /**
     * 放置traceId
     */
    private void putTraceId(String traceId) {
        // 放入日志请求域
        MDC.put("traceId", traceId);
    }

    /**
     * 清除traceId
     */
    private void clearTraceId() {
        try {
            MDC.remove("traceId");
        } catch (Exception e) {
            log.warn("clear traceId error. " + e.getMessage());
            // ignore
        }
    }

    /**
     * 生成traceId
     *
     * @param request
     *            请求
     * @return  traceId
     */
    @NotNull
    public static String generateTraceId(HttpServletRequest request) {
        String traceId = UUID.randomUUID().toString().replace("-", "");
        try {
            if (request != null) {
                String requestUri = request.getRequestURI();
                if (requestUri != null) {
                    if (requestUri.startsWith("/")) {
                        requestUri = requestUri.substring(1);
                    }
                    if (requestUri.endsWith("/")) {
                        requestUri = requestUri.substring(0, requestUri.length() - 1);
                    }
                    requestUri = requestUri.replace("/", ".");
                }

                // TODO 如果需要加上用户追踪,这儿可以将用户ID放入 traceId 中
                // String accountId = null;
                // TODO 获取用户ID逻辑
                // traceId = String.join("_", traceId, accountId == null ? "%s" : accountId, requestUri);
                traceId = String.join("_", traceId, requestUri);
            }
        } catch (Exception e) {
            // ignore
        }
        return traceId;
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        try {
            HttpServletRequest httpServletRequest = ((HttpServletRequest)request);
            String traceId = httpServletRequest.getHeader("traceId");
            if (StringUtils.isBlank(traceId)) {
                traceId = generateTraceId(httpServletRequest);
                log.debug("generate traceId [{}].", traceId);
            } else {
                log.debug("obtain traceId [{}] from request header. ", traceId);
            }
            // put
            putTraceId(traceId);
            chain.doFilter(httpServletRequest, response);
        } finally {
            // clear
            clearTraceId();
        }
    }
}
  1. 编写测试接口,运行测试
package cn.wjr.towerJJ.controller;

import cn.wje.common.util.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author luoqifeng
 * @date 2023/05/18 10:13
 */
@RestController
@RequestMapping("test")
@Slf4j
public class testController {

    @RequestMapping("test001")
    public Result<String> test001(){
        log.info("test001");
        return Result.success("test001");
    }
}
  1. 查看返回与结果:
    响应结果
    在这里插入图片描述
    在这里插入图片描述

  2. 贴上我自己封装的返回体,可以根据项目情况自行封装返回体

package cn.wje.common.util;

import cn.wje.common.constants.CommonErrorCode;
import org.slf4j.MDC;

import java.io.Serializable;

/**
 * @author luoqifeng
 * @date 2023/05/18 10:13
 */
public class Result<T> implements Serializable {

    private static final long serialVersionUID = -2234571636295254776L;

    /**编码**/
    private String code;
    /**失败时为失败原因**/
    private String msg;
    /**成功时的返回数据**/
    private T data;

    /**
     * traceId
     * */
    private String traceId;

    public Result() {

    }

    /**
     * 返回成功
     *
     * @param data
     * @return
     */
    public static <T> Result<T> success(T data) {
        Result<T> result = new Result<>();
        result.setCode(CommonErrorCode.SUCCESS.getCode());
        if (data != null) {
            result.setData(data);
        }
        result.setTraceId(MDC.get("traceId"));
        return result;
    }

    /**
     * 返回成功
     *
     * @return
     */
    public static <T> Result<T> success() {
        return Result.success(null);
    }

    /**
     * 返回失败
     *
     * @return
     */
    public static <T> Result<T> fail(String code,String msg) {
        Result<T> result = new Result<>();
        result.setCode(code);
        result.setMsg(msg);
        result.setTraceId(MDC.get("traceId"));
        return result;
    }

    /**
     * 返回失败
     *
     * @return
     */
    public static <T> Result<T> fail(CommonErrorCode code) {
        return Result.fail(code.getCode(), code.getErrorMsg());
    }

    /**
     * 构建Result
     *
     * @param code
     * @param msg
     * @param data
     * @return
     */
    public static <T> Result<T> build(String code,String msg,T data) {
        Result<T> result = new Result<>();
        result.setCode(code);
        result.setMsg(msg);
        result.setData(data);
        result.setTraceId("traceId");
        return result;
    }

    /**
     * 构建Result
     *
     * @param code
     * @param data
     * @return
     */
    public static <T> Result<T> build(String code,T data) {
        return Result.build(code, null, data);
    }

    /**
     * 构建Result
     *
     * @param code
     * @param msg
     * @return
     */
    public static <T> Result<T> build(String code,String msg) {
        return Result.build(code, msg, null);
    }

    /**
     * 是否成功
     *
     * @return
     */
    public boolean isOk() {
        return CommonErrorCode.SUCCESS.getCode().equals(code);
    }

    public String getCode() {
        return code;
    }
    public void setCode(String code) {
        this.code = code;
    }
    public String getMsg() {
        return msg;
    }
    public void setMsg(String msg) {
        this.msg = msg;
    }
    public T getData() {
        return data;
    }
    public void setData(T data) {
        this.data = data;
    }

    public String getTraceId() {
        return traceId;
    }

    public void setTraceId(String traceId) {
        this.traceId = traceId;
    }
}

  1. 如果不生效,请断点调试一下拦截器(AbstractTraceIdFilter)是否成功拦截

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/544908.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Padding, Spacer, Initializer 的使用

1. Padding 的使用 1.1 样式一 1) 实现 func testText1()-> some View{Text("Hello, World!").background(Color.yellow) // 背景颜色//.padding() // 默认间距.padding(.all, 10) // 所有的间距.padding(.leading, 20) // 开始的间距.ba…

真题详解(数字签名算法)-软件设计(七十八)

真题详解(有限自动机)-软件设计&#xff08;七十七)https://blog.csdn.net/ke1ying/article/details/130748759 可用于数字签名算法的是_____。 答案&#xff1a;非对称RSA 移植性&#xff1a;易安装、易替换、适应性。 UML状态图转换不正确的是______。 活动可以在转换时执…

药包材国家标准ybb2020-电子版在线阅读

国家药包材标准对于药品的质量和安全至关重要&#xff0c;因此需要查阅国家药包材标准来确保药品的质量和安全。 对于一些医药生产企业、药品检验机构、药品注册申请人、医疗机构来说他们查阅相关国家药包材标准可以说是轻车熟路&#xff0c;但对于部分新入行或普通人群想要了…

要想工作流程更简便,试试开源web表单设计器

繁杂的工作流程&#xff0c;让您头疼不已&#xff1f;传统的表单制作效率低&#xff1f;内部数据迟迟得不到有效管理&#xff1f;…作为职场人的你&#xff0c;是否经常遇到上述问题。别着急&#xff0c;在如今的快节奏发展时代&#xff0c;传统的表单制作已经满足不了行业和市…

python3 爬虫相关学习5: python相关工具:anaconda,sublime_text等等

前言 1 作为一个中国人坚决不用notepad 2 sublimeText 3 anaconda 1 sublime Text 下载地址 Sublime Text - Text Editing, Done Righthttp://www.sublimetext.com/ 下载是个绿色包解压缩即可用快捷方式需要自己剪切 2 导航器/浏览器 /平台 Anaconda 下载地址 Anaconda…

【学习日记】在不可联网电脑上按照Python和深度学习环境

测试环境 Hyer-V上开了个虚拟机&#xff0c;win7-64位企业版&#xff0c;全新未安装任何环境的最基本的操作系统。 因为不联网安装&#xff0c;而且是win7这种古老的操作系统&#xff0c;确实会遇到很多问题。做个记录。 安装Python 打开python-3.7.8.exe 安装程序 此时可能…

Unity之新版InputSystem如何自定义InputActions

前言 上一篇文章&#xff0c;我们介绍了如何使用新版本的InputSystem&#xff0c;我们知道了InputActionsAsset给我们提供了更多的灵活性&#xff0c;扩展性和复用性。那么这篇文章我们就来介绍一下如何创建自定义InputActionAsset 创建InputActionAssets Input Action Asse…

YOLOv5/v7 改进实战 | 目录 | 使用教程

YOLOv5/v7 改进实战 | 目录 | 使用教程 本专栏包含超多YOLO算法改进使用教程&#xff1b;全专栏阅读量50 w&#xff0c;改进YOLO必看教程&#xff0c;所有改进都提供详细手把手教程&#xff0c;欢迎大家订阅~ 专栏地址&#xff1a;点击跳转 整体目录如下&#xff1a; 使用教程系…

React脚手架搭建

介绍 react提供了一个用于创建 react项目的脚手架库: create-react-app 全局安装react脚手架 npm i -g create-react-app 创建项目 create-react-app 项目包名 静等一会儿 创建成功 切换到创建的目录 输入 npm start或yarn start启动项目 项目基本结构介绍 自定义简单组件 p…

数影周报:三星核心技术遭泄露,阿里宣布多业务启动融资上市计划

本周看点&#xff1a;三星再次发生核心技术信息泄露事件&#xff1b;领英职场将停止服务&#xff1b;阿里宣布多业务启动融资上市计划&#xff1b;Bolttech获得2亿美元B轮融资...... 数据安全那些事 三星再次发生核心技术信息泄露事件 2023年5月17日消息&#xff0c;据Business…

智能二狗机器人使用攻略,微信群活跃助手

群里聊天的人来来去去就那几个&#xff1f;聊来聊去都是一样的话题&#xff1f; 要怎么才能丰富社群的玩法体验&#xff0c;提高成语的积极性&#xff0c;打造99社群呢&#xff1f; 别慌&#xff0c;让二狗机器人来拯救你的无聊&#xff01; 数十款互动玩法&#xff0c;轻松…

Token 常用的加解密算法

常用的 Token 加密算法包括对称加密算法和非对称加密算法。对称加密算法使用相同的密钥来进行加密和解密&#xff0c;加密速度快&#xff0c;但是密钥传输和管理较为复杂。非对称加密算法使用一对公钥和私钥来进行加密和解密&#xff0c;加密速度慢&#xff0c;但是密钥传输和管…

基于html+css的图展示79

准备项目 项目开发工具 Visual Studio Code 1.44.2 版本: 1.44.2 提交: ff915844119ce9485abfe8aa9076ec76b5300ddd 日期: 2020-04-16T16:36:23.138Z Electron: 7.1.11 Chrome: 78.0.3904.130 Node.js: 12.8.1 V8: 7.8.279.23-electron.0 OS: Windows_NT x64 10.0.19044 项目…

西安石油大学上机作业2023.5.19

上机的一次作业 上机作业&#xff1a; 编写一个基本帐户类。成员变量包括&#xff1a;帐号、储户姓名和存款余额&#xff0c;成员函数包括&#xff1a;存款和取款。编写一个测试程序对该类功能进行测试。 要求&#xff1a;上传源码及运行结果截图。 分析思路&#xff1a; 针…

面试造航母,入职拧螺丝,工资离了个大谱...

有粉丝跟我吐槽说&#xff1a;金三银四去面试软件测试岗&#xff0c;真的是面试造航母&#xff0c;入职拧螺丝&#xff0c;工资还低 这种现象很正常&#xff0c;因为找一个测试员&#xff0c;当然希望他能做的业务越多越好&#xff0c;最好像机器猫一样&#xff0c;啥事儿都能…

数据结构与算法(四)

一、链表 线性表&#xff1a;0个或者是多个数据元素有限序列 物理的存储结构&#xff1a; 顺序存储&#xff1a;用一段连续的存储单元依次存储线性表的数据元素。链式存储&#xff1a;内存地址可以是连续的&#xff0c;也可以是不连续的。把数据元素存放在任意的存储单元里&…

寻找人工智能工具?警惕散布红线恶意软件的流氓网站

针对OpenAI ChatGPT和Midjourney等生成式人工智能服务的恶意谷歌搜索广告被用来将用户引导到一些粗糙的网站&#xff0c;这是BATLOADER活动的一部分&#xff0c;旨在传播红线窃取软件。 eSentire在一份分析报告中表示:“这两种人工智能服务都非常受欢迎&#xff0c;但缺乏第一…

Windows环境下pcl点云库 安装配置教程

本文为Windows配置点云库pcl步骤&#xff0c;具体win10、visual studio 2017、pcl1.11.1。 【1】下载安装包 Releases PointCloudLibrary/pcl GitHub 其中&#xff0c;AllInOne是一个包含了PCL库所有模块的单独下载包&#xff0c;方便快速获取整个PCL库&#xff0c;而pdb则…

【腾讯云 Finops Crane 开发者集训营】浅谈Crane的核心概念和原理

一、Crane是什么&#xff1f; FinOps&#xff08;Financial Operations&#xff09;是一种管理云计算成本的方法&#xff0c;它强调将云计算资源的成本与使用情况及业务需求相匹配&#xff0c;从而提高企业的效率和效益。在当前云计算环境下&#xff0c;FinOps已经成为了越来越…

如何理解工业互联网与智能制造,怎么共建智慧工厂?

第六届数字中国建设峰会26日在福州开幕&#xff0c;在这个数字化新技术的变革风口&#xff0c;企业如何把握机遇&#xff0c;借工业互联网和智能制造实现智慧工厂建设&#xff1f; 探讨三个问题&#xff1a; 什么是工业互联网、智能制造、智慧工厂&#xff1b;它们三者之间的…