SpringBoot项目打印接口请求日志,CommonsRequestLoggingFilter实现方式

news2024/11/29 12:56:01

文章目录

  • 需求背景
  • 效果图
  • 实现思路
    • 其他方案对比
    • 优缺点分析
  • 具体实现

需求背景

  1. 线上项目出现bug时,可以通过接口的请求参数来排查定位问题。
  2. 和业务方battle时,能够回怼他是自己操作的问题。

效果图

在这里插入图片描述

在这里插入图片描述

实现思路

  • Spring提供了CommonsRequestLoggingFilter过滤器,该过滤器可以在接口请求前和请求后分别打印日志;
  • 通过继承CommonsRequestLoggingFilter过滤器,实现部分自己的逻辑;

其他方案对比

  1. aop的方式:必须要到controller层,才会打印, 有些接口在 过滤器、拦截器层被拦截。
  2. 拦截器的方式:request.getInputStream()只能调用一次,解决的方法为copy一份流,个人感觉在代码逻辑层面不太清晰

优缺点分析

缺点

  1. 无法获取返回值,适合一些不关心返回值的场景。
  2. 对于Payload参数,内部使用的是ContentCachingRequestWrapper读取的request.getInputStream()流,所以必须要先调用@RequstBody,才能取到值(可以去看下ContentCachingRequestWrapper的具体实现)

优点

  1. 实现简单,代码层次逻辑清晰,与业务逻辑解耦。

具体实现

继承CommonsRequestLoggingFilter过滤器,实现部分自己的逻辑

package com.yp.basic.log.filter;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.StrUtil;
import com.yp.store.base.biz.common.constant.CommonConstant;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.web.filter.CommonsRequestLoggingFilter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

/**
 * 自定义的请求日志打印过滤器,打印所有接口请求参数、耗时等日志 <br/>
 *
 * 对比其他两种方式:<br/>
 * <ul>
 *     <li>1、aop的方式:必须要到controller层,才会打印, 有些接口在 过滤器、拦截器层被拦截</li>
 *     <li>2、拦截器的方式:request.getInputStream()只能调用一次,解决的方法为copy一份流,个人感觉在代码逻辑层面不太清晰</li>
 * </ul>
 *
 *  缺点:<br/>
 *  <ul>
 *      <li>1、无法获取返回值,适合一些不关心返回值的场景。</li>
 *      <li>2. 内部使用的是ContentCachingRequestWrapper读取的request.getInputStream(),必须要先调用@RequstBody,才能取到值。</li>
 *  </ul>
 *
 *  优点:<br/>
 *  <ul>
 *      <li>1、 实现简单,代码层次逻辑清晰。</li>
 *  </ul>
 *
 * @author: wcong
 * @date: 2022/11/25 15:17
 */
@Slf4j
public class RequestLogPrintFilter extends CommonsRequestLoggingFilter {

    /**
     * 接口日志 开始 请求前缀,建议放在公共的工具类中
     */
    public final static String BEFORE_REQUEST_PREFIX = "### Before request[";
    /**
     * 接口 结束 请求前缀,建议放在公共的工具类中
     */
    public final static String AFTER_REQUEST_PREFIX = "### After request[";

    /**
     * 重写父类方法:封装打印消息的格式
     */
    @Override
    protected String createMessage(HttpServletRequest request, String prefix, String suffix) {
        final StringBuilder messageInfo = getMessageInfo(request, prefix, suffix);
        // 请求开始还是结束
        if (BEFORE_REQUEST_PREFIX.equals(prefix)) {
            // 请求开始
            MDC.put("logStartTime", String.valueOf(System.currentTimeMillis()));
        } else {
            // 请求结束,记录耗时
            final Long logStartTime = Convert.toLong(MDC.get("logStartTime"), 0L);
            messageInfo.append("\r\n接口耗时:").append(System.currentTimeMillis() - logStartTime).append("ms");
        }
        return messageInfo.toString();
    }

    /**
     * 重写父类方法:请求前调用逻辑
     */
    @Override
    protected void beforeRequest(HttpServletRequest request, String message) {
        doPrintLog(message);
    }

    /**
     * 重写父类方法:请求后调用逻辑
     */
    @Override
    protected void afterRequest(HttpServletRequest request, String message) {
        doPrintLog(message);
    }
    /**
     * 重写父类方法:是否打印日志
     */
    @Override
    protected boolean shouldLog(HttpServletRequest request) {
        // 父类中的逻辑是:logger.isDebugEnabled()
        return true;
    }

    /**
     * 统一封装打印的日志格式
     *
     * @param request   javax.servlet.http.HttpServletRequest
     * @param prefix    打印前缀
     * @param suffix    打印后缀
     * @return 封装好的日志格式
     */
    private StringBuilder getMessageInfo(HttpServletRequest request, String prefix, String suffix) {
        StringBuilder msg = new StringBuilder();
        msg.append(prefix);
        msg.append(StrUtil.format("method={}; ", request.getMethod().toLowerCase()));
        msg.append("uri=").append(request.getRequestURI());

        if (isIncludeClientInfo()) {
            String client = request.getRemoteAddr();
            if (StrUtil.isNotBlank(client)) {
                msg.append("; client=").append(client);
            }
            HttpSession session = request.getSession(false);
            if (session != null) {
                msg.append("; session=").append(session.getId());
            }
            String user = request.getRemoteUser();
            if (user != null) {
                msg.append("; user=").append(user);
            }
        }

        if (isIncludeQueryString()) {
            String queryString = request.getQueryString();
            if (queryString != null) {
                msg.append("; ").append('?').append(queryString);
            }
        }

        if (isIncludePayload()) {
            String payload = getMessagePayload(request);
            if (payload != null) {
                msg.append("; payload=").append(payload);
            }
        }

        msg.append(suffix);
        return msg;
    }

    /**
     * 具体打印的方法
     *
     * @param message   打印的消息
     */
    private void doPrintLog(String message) {
        // 生产环境打印debug级别
        if (CommonConstant.IS_PRODUCT_ENVIRONMENT) {
            log.debug(message);
        } else {
            log.info(message);
        }
    }

}

注册过滤器

package com.yp.basic.log;


import com.yp.basic.jackson.JsonUtil;
import com.yp.basic.log.aspect.SysLogAspect;
import com.yp.basic.log.event.SysLogListener;
import com.yp.basic.log.filter.RequestLogPrintFilter;
import com.yp.basic.log.monitor.PointUtil;
import com.yp.basic.log.properties.OptLogProperties;
import lombok.AllArgsConstructor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.web.filter.CommonsRequestLoggingFilter;

/**
 * 日志自动配置
 *
 * @author: wcong
 * @date: 2022/11/25 15:17
 */
@EnableAsync
@Configuration
@ConditionalOnWebApplication
@EnableConfigurationProperties(OptLogProperties.class)
public class LogAutoConfiguration {

    @Bean
    @ConditionalOnProperty(prefix = OptLogProperties.PREFIX, name = "enable-http-log", havingValue = "true", matchIfMissing = true)
    public FilterRegistrationBean<CommonsRequestLoggingFilter> logFilterRegistration() {
        CommonsRequestLoggingFilter filter = new RequestLogPrintFilter();
        // 是否打印header中的内容,参数很多
        filter.setIncludeHeaders(false);
        // 是否打印查询字符串内容
        filter.setIncludeQueryString(true);
        // 是否打印 payLoad内容,内部使用的是ContentCachingRequestWrapper读取的request.getInputStream(),必须要先调用@RequstBody,才能取到值。
        filter.setIncludePayload(true);
        // 是否打印客户端信息(ip、session、remoteUser)
        filter.setIncludeClientInfo(true);
        // 1024字节(1kb),超出部分截取
        // 在UTF-8编码方案中,一个英文字符占用一个字节,一个汉字字符占用三个字节的空间。
        filter.setMaxPayloadLength(1024);
        // 设置 before request 日志前缀,默认为:Before request [
        filter.setBeforeMessagePrefix(RequestLogPrintFilter.BEFORE_REQUEST_PREFIX);
        // 设置 before request 日志后缀,默认为:]
        filter.setBeforeMessageSuffix("]");
        // 设置 before request 日志前缀,默认为:After request [
        filter.setAfterMessagePrefix(RequestLogPrintFilter.AFTER_REQUEST_PREFIX);
        // 设置 after request 日志后缀,默认为:]
        filter.setAfterMessageSuffix("]");

        FilterRegistrationBean<CommonsRequestLoggingFilter> registration = new FilterRegistrationBean<>(filter);
        registration.addUrlPatterns("/*");
        registration.setOrder(0);
        registration.setName("commonsRequestLoggingFilter");
        return registration;
    }

}

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

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

相关文章

opengl,opengl es,egl,glfw,glew

OpenGL ES之GLFW窗口搭建 - Plato - 博客园概述 本章节主要总结如何使用GLFW来创建Opengl窗口。主要包括如下内容&#xff1a; OpenGl窗口创建介绍 GLFW Window版编译介绍 GLFW简单工程源码介绍 OpenGL窗口创建介绍 能用于Ohttps://www.cnblogs.com/feng-sc/p/5093262.htmlOp…

事务-P26,P27

事务&#xff1a;要么都成功&#xff0c;要么都失败。例子&#xff1a;支付宝交易。 acid原则。 11直接移植10的代码 spring-11-transaction&#xff1a; UserMapper&#xff08;增加两个接口&#xff09; package com.Li.mapper;import com.Li.pojo.User;import java.util.…

C# 消息 界面卡顿 界面进程 工作进程

一 消息 消息与消息循环&#xff0c;是所有的GUI开发里共同的概念&#xff1a; 消息Message,有的地方也叫事件&#xff1b; ① 鼠标消息&#xff1b; ② 键盘消息&#xff1b; ③ 绘制事件&#xff1b; ④ 窗口最大化、最小化&#xff1b; 1 消息循环 消息循环&#xff0c;M…

10道不得不会的缓存面试题【缓存】

博主介绍&#xff1a; &#x1f680;自媒体 JavaPub 独立维护人&#xff0c;全网粉丝15w&#xff0c;csdn博客专家、java领域优质创作者&#xff0c;51ctoTOP10博主&#xff0c;知乎/掘金/华为云/阿里云/InfoQ等平台优质作者、专注于 Java、Go 技术领域和副业。&#x1f680; 最…

使用Conda

0. Anaconda Prompt 命令提示符 0.1 验证conda是否被安装 conda --version0.2 conda管理环境 可以用命令复制和删除环境 参考. 1. Conda管理包 1.1 常用包管理功能 查找包查看包安装包 查找分为精确查找和模糊查找&#xff0c;如下图所示 卸载包更新包 1.2 conda管理环…

【实习之velocity】

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录前言一、图示二、应用场景1.Web应用程序&#xff1a;作为为应用程序的视图&#xff0c;显示数据2.源代码生成&#xff1a;Velocity可用于基于模板生成java源代码3.自…

[c++基础]-vector类

前言 作者&#xff1a;小蜗牛向前冲 名言&#xff1a;我可以接受失败&#xff0c;但我不能接受放弃 如果觉的博主的文章还不错的话&#xff0c;还请点赞&#xff0c;收藏&#xff0c;关注&#x1f440;支持博主。如果发现有问题的地方欢迎❀大家在评论区指正。 目录 一、 vect…

scanf导致程序运行时出现stack smashing

一 栈溢出stack smashing 程序在运行期间破坏了已在操作系统里定义好的栈边界&#xff0c;这种行为具有破坏性&#xff0c;操作系统使用stack smashing detect机制来检测栈溢出。 二 栈溢出简单例子 实例一&#xff1a; #include <stdio.h> #include <stdlib.h>…

基于智能优化算法实现的机械臂避障路径规划(Matlab代码实现)

目录 &#x1f4a5;1 概述 &#x1f4da;2 运行结果 &#x1f389;3 参考文献 &#x1f468;‍&#x1f4bb;4 Matlab代码 &#x1f4a5;1 概述 针对目前空间机械臂避障路径规划算法计算量大难以达到在线实时规划的缺点,对空间机械臂的在线实时避障路径规划问题进行了研究和…

计算机四级网络-网络技术-第六章 网络管理与网络安全

6.1 网络管理技术 CMIP 采用委托监控机制。 CMIP协议是由IS0组织制定的一种管理协议。管理进程根据事件发生时对网络服务影响的大小来划分事件的严重等级&#xff0c;然后再产生相应的故障处理方案。CMIP的所有功能都要映射到应用层的相关协议上实现。操作和事件报告是通过远…

如何使用腾讯会议录屏?腾讯会议不允许录屏怎么办?这个方法教你解决

​近年来&#xff0c;线上活动越来越活跃&#xff0c;很多人都开始使用腾讯会议进行开会、网课教学等活动。很多人希望使用腾讯会议进行录屏。那么如何使用腾讯会议录屏&#xff1f;腾讯会议不允许录屏怎么办&#xff1f;这个方法教你解决&#xff01; 一、如何使用腾讯会议录屏…

计算机组成原理习题课第三章-5(唐朔飞)

计算机组成原理习题课第三章-5&#xff08;唐朔飞&#xff09; ✨欢迎关注&#x1f5b1;点赞&#x1f380;收藏⭐留言✒ &#x1f52e;本文由京与旧铺原创&#xff0c;csdn首发&#xff01; &#x1f618;系列专栏&#xff1a;java学习 &#x1f4bb;首发时间&#xff1a;&…

[ElasticSearch]-初识Elastic Stack

[ElasticSearch]-初识Elastic Stack 森格 | 2022年11月 本文是第一次接触Elasticsearch的一些个人总结&#xff0c;介绍了Elastic Stack&#xff0c;如有错误之处&#xff0c;敬请指正。 一、ELK 到 Elastic Stack 注&#xff1a;上图来源于Elasticsearch中文博客 ELK分别是Ela…

PyTorch深度学习笔记之五(使用神经网络拟合数据)

使用神经网络拟合数据 1. 人工神经网络 1.1 神经网络和神经元 神经网络&#xff1a;一种通过简单函数的组合来表示复杂函数的数学实体。 人工神经网络和生理神经网络似乎都使用模糊相似的数学策略来逼近复杂的函数&#xff0c;因为这类策略非常有效。 这些复杂函数的基本构…

多线程轮流打印

一、背景 面试的时候&#xff0c;有一个高频的笔试题&#xff1a; 让2个线程轮流打印&#xff0c;a线程是打印ABCDEFGHIJ&#xff0c;b线程是打印1、2、3、4、5、6、7、8、9、10 二、原理 这种类型的面试题&#xff0c;主要是考察object的wait()方法和notify()方法的使用 …

spring整合Mybatis-P23,24,25

复习Mybatis&#xff08;都是之前的内容&#xff0c;不再解释&#xff09; 6个需要修改或创建的文件 UserMapper package com.Li.mapper;import com.Li.pojo.User;import java.util.List;public interface UserMapper {public List<User> selectUser(); }UserMapper.xm…

如何全面提升架构设计的质量

低成本 低成本本质上是对架构的一种约束&#xff0c;与高性能等架构是冲突的 手段和应用 先设计架构方案&#xff0c;再看如何降低成本 优化 引入缓存虚拟化、容器化性能调优采用高性能硬件采用开源方案 创新 NoSQL vs SQLSQL vs 倒排索引Hadoop vs MySQL 安全性 复杂…

《码出高效:Java开发手册》 四-走进JVM

前言 JVM是java中底层的知识&#xff0c;这里的内容比较复杂&#xff0c;对于一些软件编程&#xff0c;会经常使用&#xff0c;但很多业务其实碰不到这里的知识&#xff0c;下图为目录 介绍 JVM&#xff0c;java虚拟机&#xff0c;它的前身是99年的hotspot java虚拟机&…

vue 计算属性未重新计算 / computed 未触发 / computed 原理源码分析

点击可打开demo 这里在一秒后改了数组里value属性的值 虽然数据有更新&#xff0c;但打开控制台&#xff0c;可以发现computed函数只在初始化时执行了一次 按理说一秒后改变了value值&#xff0c;应该执行两次才对呀&#xff1f; 但如果computed属性这样写&#xff0c;明确写…

数据分析之大数据分析

一 什么是大数据分析 大数据是指无法在一定时间范围内用常规软件工具进行捕捉、管理和处理的数据集合&#xff0c;是需要新处理模式才能具有更强的决策力、洞察发现力和流程优化能力的海量、高增长率和多样化的信息资产。大数据的特点可以概括为5个V&#xff1a;数据量大&…