java项目自定义打印日志,打印请求方式,参数用时等
1.相关依赖
<!-- 私人工具包 --><dependency><groupId>cn.changeforyou</groupId><artifactId>location</artifactId><version>1.13-SNAPSHOT</version></dependency><!-- hutool工具依赖 --><dependency><groupId>cn.hutool</groupId><artifactId>hutool-crypto</artifactId><version>5.5.0</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId></dependency>
2.添加日志过滤类LogFilter.java
package com.abliner.test.common.log;import cn.changeforyou.web.utils.http.ServletUtils;
import cn.changeforyou.web.utils.http.warpper.BufferedHttpResponseWrapper;
import cn.hutool.json.JSONUtil;
import org.apache.commons.collections.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.multipart.MultipartResolver;
import org.springframework.web.multipart.support.StandardServletMultipartResolver;
import com.abliner.test.common.log.LogRecordConfig.InterfaceLogConfig;import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;@Component
public class LogFilter extends OncePerRequestFilter {Logger log = LoggerFactory.getLogger("reqResp");@Autowiredprivate LogRecordConfig logRecordConfig;private final Set<String> urls;private final AntPathMatcher antPathMatcher;private final Map<String, InterfaceLogConfig> url2Config = new ConcurrentHashMap<>();public LogRecordConfig getLogRecordConfig() {return logRecordConfig;}public LogRecordConfig addInterfaceLogConfig(InterfaceLogConfig config) {logRecordConfig.getInterfaceLogConfigs().add(config);initMatcher();return logRecordConfig;}public LogRecordConfig removeInterfaceLogConfig(String url) {if (url2Config.containsKey(url)) {InterfaceLogConfig config = url2Config.remove(url);logRecordConfig.getInterfaceLogConfigs().remove(config);initMatcher();}return logRecordConfig;}public LogRecordConfig updateDefaultInterfaceLogLevel(InterfaceLogConfig config) {logRecordConfig.setDefaultInterfaceLogConfig(config);return logRecordConfig;}public LogFilter() {urls = Collections.synchronizedSet(new HashSet<>());antPathMatcher = new AntPathMatcher();}private InterfaceLogConfig matches(String url) {if (urls.isEmpty()) {return null;}for (String s : urls) {if (antPathMatcher.match(s, url)) {return url2Config.get(s);}}return null;}@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {long requestTime = System.currentTimeMillis();String uri = request.getRequestURI();String contextPath = request.getContextPath();String url = uri.substring(contextPath.length());InterfaceLogConfig thisConfig = matches(url);if (null == thisConfig) {thisConfig = logRecordConfig.getDefaultInterfaceLogConfig();}if (!thisConfig.printLog()) {filterChain.doFilter(request, response);return;}String requestBody = "";String requestContentType = request.getHeader(HttpHeaders.CONTENT_TYPE);if (requestContentType != null) {// xml jsonif ((requestContentType.startsWith(MediaType.APPLICATION_JSON_VALUE) || requestContentType.startsWith(MediaType.APPLICATION_XML_VALUE)) && request.getMethod().equalsIgnoreCase("POST")) {StringBuilder sb = new StringBuilder();request = ServletUtils.getRequestBody(request, sb);requestBody = sb.toString();// 普通表单提交} else if (requestContentType.startsWith(MediaType.APPLICATION_FORM_URLENCODED_VALUE)) {requestBody = toJson(request.getParameterMap());// 文件表单提交} else if (requestContentType.startsWith(MediaType.MULTIPART_FORM_DATA_VALUE)) {requestBody = getFormParam(request);} else {requestBody = toJson(request.getParameterMap());}} else if (request.getMethod().equals(HttpMethod.GET.name())) {requestBody = toJson(request.getParameterMap());}BufferedHttpResponseWrapper responseWrapper = new BufferedHttpResponseWrapper(response);if (thisConfig.printReq()) {if (thisConfig.isDebugEnabled()) {log.debug("URL: {}, requestBody: {}", url, requestBody);} else {log.info("URL: {}, requestBody: {}", url, requestBody);}}filterChain.doFilter(request, responseWrapper);long costTime = System.currentTimeMillis() - requestTime;String responseBody = "";// 暂定只有json 输出响应体String contentType = responseWrapper.getContentType();if (contentType != null && contentType.startsWith(MediaType.APPLICATION_JSON_VALUE)) {responseBody = new String(responseWrapper.getBuffer(), StandardCharsets.UTF_8);}StringBuilder sb = new StringBuilder();sb.append("URL:").append(url).append(", total time:").append(costTime).append(" ms, ");if (thisConfig.printRes()) {sb.append(", responseBody:").append(responseBody);}if (responseWrapper.getStatus() >= 200 && responseWrapper.getStatus() < 1000) {if (thisConfig.isDebugEnabled()) {log.debug(sb.toString());} else {log.info(sb.toString());}} else {log.error(sb.toString());}response.getOutputStream().write(responseWrapper.getBuffer());}private String getFormParam(HttpServletRequest request) {MultipartResolver resolver = new StandardServletMultipartResolver();MultipartHttpServletRequest mRequest = resolver.resolveMultipart(request);Map<String, Object> param = new HashMap<>();Map<String, String[]> parameterMap = mRequest.getParameterMap();if (!parameterMap.isEmpty()) {param.putAll(parameterMap);}Map<String, MultipartFile> fileMap = mRequest.getFileMap();if (!fileMap.isEmpty()) {for (Map.Entry<String, MultipartFile> fileEntry : fileMap.entrySet()) {MultipartFile file = fileEntry.getValue();param.put(fileEntry.getKey(), file.getOriginalFilename() + "(" + file.getSize() + " byte)");}}return toJson(param);}@Overridepublic void afterPropertiesSet() throws ServletException {super.afterPropertiesSet();initMatcher();}private void initMatcher() {List<InterfaceLogConfig> configs = logRecordConfig.getInterfaceLogConfigs();this.urls.clear();if (CollectionUtils.isNotEmpty(configs)) {for (InterfaceLogConfig config : configs) {this.urls.add(config.getUrl());url2Config.put(config.getUrl(), config);}}}private static String toJson(Object object) {return JSONUtil.toJsonStr(object);}}
3.添加日志配置类LogRecordConfig.java
package com.abliner.test.common.log;import com.abliner.test.common.validator.InStrings;
import com.abliner.test.common.validator.ValidatorConstant;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.NestedConfigurationProperty;
import org.springframework.stereotype.Component;import javax.validation.constraints.NotEmpty;
import java.util.List;@ConfigurationProperties(prefix = "log.record")
@Data
@Component
public class LogRecordConfig {private InterfaceLogConfig defaultInterfaceLogConfig;@NestedConfigurationPropertyprivate List<InterfaceLogConfig> interfaceLogConfigs;@Datapublic static class InterfaceLogConfig {@NotEmpty(groups = ValidatorConstant.InsertAndUpdate.class)@NotEmpty(groups = ValidatorConstant.Delete.class)private String url;/**** 1: info* 2: debug*/@NotEmpty(groups = ValidatorConstant.InsertAndUpdate.class)@InStrings(in= {"info", "debug"}, groups = ValidatorConstant.InsertAndUpdate.class)@InStrings(in= {"info", "debug"}, groups = ValidatorConstant.UpdateDefault.class)private String logLevel;/**** res* req* all* none*/@NotEmpty(groups = ValidatorConstant.InsertAndUpdate.class)@InStrings(in= {"res", "req", "all", "none"},groups = ValidatorConstant.InsertAndUpdate.class)@InStrings(in= {"res", "req", "all", "none"},groups = ValidatorConstant.UpdateDefault.class)private String print;public boolean isDebugEnabled() {return "debug".equalsIgnoreCase(logLevel);}public boolean printLog() {return !"none".equalsIgnoreCase(print);}public boolean printRes() {return "res".equalsIgnoreCase(print) || "all".equalsIgnoreCase(print);}public boolean printReq() {return "req".equalsIgnoreCase(print) || "all".equalsIgnoreCase(print);}}}
4.添加使用到的注解类InStrings.java
package com.abliner.test.common.validator;import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.ElementType.TYPE_USE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;@Documented
@Constraint(validatedBy = {InStringsValidator.class})
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Repeatable(InStrings.List.class)
public @interface InStrings {String message() default "字符串不在设定范围内";String[] in();Class<?>[] groups() default {};Class<? extends Payload>[] payload() default {};/*** Defines several {@code @NotEmpty} constraints on the same element.** @see InStrings*/@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })@Retention(RUNTIME)@Documented@interface List {InStrings[] value();}
}
5.添加InStringsValidator.java
package com.abliner.test.common.validator;import cn.changeforyou.utils.string.StringUtils;import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;public class InStringsValidator implements ConstraintValidator<InStrings, String> {private String[] mustIn;@Overridepublic void initialize(InStrings constraintAnnotation) {mustIn = constraintAnnotation.in();}@Overridepublic boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {if (StringUtils.isEmpty(s)) {return false;}return StringUtils.in(s, mustIn);}
}
6.添加常量ValidatorConstant.java
package com.abliner.test.common.validator;public interface ValidatorConstant {interface Insert {}interface Update {}interface Delete {}interface Select {}interface InsertAndUpdate {}interface SelectAndDelete {}interface UpdateDefault {}
}
7.项目结构图

8.在主配置文件application.yml添加代码
log:record:defaultInterfaceLogConfig:logLevel: infoprint: req

9.测试打印

相关文章:
java项目自定义打印日志,打印请求方式,参数用时等
1.相关依赖 <!-- 私人工具包 --><dependency><groupId>cn.changeforyou</groupId><artifactId>location</artifactId><version>1.13-SNAPSHOT</version></dependency><!-- hutool工具依赖 --><dependency>…...
03:EDA的进阶使用
使用EDA设计一个38译码器电路和245放大电路 1、38译码器1.1、查看74HC138芯片数据1.2、电路设计 2、245放大电路2.1、查看数据手册2.2、设计电路 3、绘制PCB3.1、导入3.2、放置3.3、飞线3.4、特殊方式连接GND3.5、泪滴3.6、配置丝印和划分区域3.7、添加typc接口供电 1、38译码器…...
Linux/Unix系统指令:(tar压缩和解压)
tar 是一个在Linux和Unix系统中用于创建和处理归档文件的命令。 下面是tar命令的详细用法,包括它的所有常用选项和一些示例。 基本语法 tar [选项] [归档文件] [文件或目录]常用选项 基本操作 -c:创建一个新的归档文件(create)…...
MySQL 日期和时间函数知识点总结
引言 在数据库管理和开发中,日期查询是一项基础且频繁使用的功能。MySQL提供了丰富的日期和时间处理函数,使得我们能够灵活地进行日期查询和数据处理。本文将详细介绍MySQL中关于日期查询的几个重要知识点,并附上具体的案例。 1. MySQL的日…...
鸿蒙登录页面及页面跳转的设计
目录 任务目标任务分析任务实施1.新建工程项目HMLogin2.设计登录页面Index.visual3.设计第二个页面SecondPage4.修改Index.ets代码5.修改SecondPage.ets代码6.运行工程 任务目标 设计一个简单的登录页面,要求可以将第一页的登录信息,传递到第二个页面&a…...
【居家养老实训室】:看中医保健在养老中的应用
本文以居家养老实训室为视角,深入探讨了中医保健在养老中的应用。通过对中医保健理念、常用方法以及在居家养老中的具体实践进行分析,阐述了其在改善老年人健康状况、提高生活质量方面的重要作用。同时,也指出了目前应用中存在的问题…...
【区块链+基础设施】区块链服务网络 BSN | FISCO BCOS应用案例
BSN(Blockchain-based Service Network,区块链服务网络)是一个跨云服务、跨门户、跨底层框架,用于部 署和运行各类区块链应用的全球性基础设施网络,旨在为开发者提供低成本和技术互通的区块链一站式服务。 2019 年 12…...
六、快速启动框架:SpringBoot3实战-个人版
六、快速启动框架:SpringBoot3实战 文章目录 六、快速启动框架:SpringBoot3实战一、SpringBoot3介绍1.1 SpringBoot3简介1.2 系统要求1.3 快速入门1.4 入门总结回顾复习 二、SpringBoot3配置文件2.1 统一配置管理概述2.2 属性配置文件使用2.3 YAML配置文…...
SA 注册流程
目录 1. UE开机后按照3GPP TS 38.104定义的Synchronization Raster搜索特定频点 2.UE尝试检测PSS/SSS,取得下行时钟同步,并获取小区的PCI;如果失败则转步骤1搜索下一个频点;否则继续后续步骤; 3.解析Mib,…...
图像的灰度直方图
先来认识一下灰度直方图,灰度直方图是图像灰度级的函数,用来描述每个灰度级在图像矩阵中的像素个数或者占有率。接下来使用程序实现直方图: 首先导入所需的程序包: In [ ]: import cv2 import numpy as np import matplotlib…...
软件测试面试题:Redis的五种数据结构,以及使用的场景是什么?
字符串(Strings):简单直接,就像记事本一样,用来存储和快速访问简单的数据,比如缓存网页或者保存用户会话信息。 列表(Lists):有序的数据集合,适合用来存储按…...
Java后端每日面试题(day1)
目录 JavaWeb三大组件依赖注入的方式Autowire和Resurce有什么区别?Spring Boot的优点Spring IoC是什么?说说Spring Aop的优点Component和Bean的区别自定义注解时使用的RetentionPolicy枚举类有哪些值?如何理解Spring的SPI机制?Spr…...
AI与测试相辅相成
AI助力软件测试 1.AI赋能软件测试 使用AI工具来帮助测试人员提高测试效率,提供缺陷分析和缺陷预测。 语法格式 设定角色 具体指示 上下文格式 例: 角色:你是一个测试人员 内容:请帮我生成登录案例的测试用例 1.只有输入正确账号和密码才…...
搜索+动态规划
刷题刷题刷题刷题 Forgery - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 思路: 需要两个数组,一个数组全部初始化为".",另一个数组输入数据,每碰到一个“.”就进行染色操作,将其周围的…...
strcpy,srtcmp,strlen函数漏洞利用
strcpy,srtcmp,strlen函数漏洞利用 strcpy strcpy函数用于将字符串复制到另一个指针指向的空间中,遇到空字符 **b’x\00’**时停止,: 所以可以利用 strcpy不检查缓冲区 的漏洞(构造的字符串要以\0结尾),…...
SketchUp + Enscape+ HTC Focus3 VR
1. 硬件: 设备连接 2. 软件: 安装steam steamVR Vive Business streaming 3. 操作: 双方登录steam 账号,然后带上头盔,用手柄在HTC Focus3 安装 串流软件,选择串流软件,在Enscape中选择 VR 模式即可 4.最终效果: SketchUp Enscape HTC Focus 3 VR 实时预览_哔哩哔哩_bi…...
推荐3款Windows系统的神级软件,免费、轻量、绝对好用!
DiskView DiskView是一款用于管理和查看磁盘空间的工具,它集成了于微软的Windows操作系统资源管理器中,以显示直观的磁盘空间使用情况。该软件通过生成图形化地图,帮助用户组织和管理大量文件和文件夹,从而高效地管理磁盘空间。用…...
-bash: /snap/bin/docker: 没有那个文件或目录
-bash: /snap/bin/docker: 没有那个文件或目录 解决办法 export PATH$PATH:/usr/bin/docker然后,重新加载配置文件 source ~/.bashrc...
[深度学习]卷积理解
单通道卷积 看这个的可视化就很好理解了 https://github.com/vdumoulin/conv_arithmetic/blob/master/README.md 多通道卷积 当输入有多个通道时,卷积核需要拥有相同的通道数. 假设输入有c个通道,那么卷积核的每个通道分别于相应的输入数据通道进行卷积,然后将得到的特征图对…...
基于aardio web.view2库和python playwright包的内嵌浏览器自动化操作
通过cdp协议可以实现playwright操控webview。 新建Python窗口工程 修改pip.aardio 修改pip.aardio,并执行,安装playwright。 //安装模块 import process.python.pip; //process.python.path "python.exe";/* 安装模块。 参数可以用一个字…...
7.4.分块查找
一.分块查找的算法思想: 1.实例: 以上述图片的顺序表为例, 该顺序表的数据元素从整体来看是乱序的,但如果把这些数据元素分成一块一块的小区间, 第一个区间[0,1]索引上的数据元素都是小于等于10的, 第二…...
2024年赣州旅游投资集团社会招聘笔试真
2024年赣州旅游投资集团社会招聘笔试真 题 ( 满 分 1 0 0 分 时 间 1 2 0 分 钟 ) 一、单选题(每题只有一个正确答案,答错、不答或多答均不得分) 1.纪要的特点不包括()。 A.概括重点 B.指导传达 C. 客观纪实 D.有言必录 【答案】: D 2.1864年,()预言了电磁波的存在,并指出…...
抖音增长新引擎:品融电商,一站式全案代运营领跑者
抖音增长新引擎:品融电商,一站式全案代运营领跑者 在抖音这个日活超7亿的流量汪洋中,品牌如何破浪前行?自建团队成本高、效果难控;碎片化运营又难成合力——这正是许多企业面临的增长困局。品融电商以「抖音全案代运营…...
多模态大语言模型arxiv论文略读(108)
CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文标题:CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文作者:Sayna Ebrahimi, Sercan O. Arik, Tejas Nama, Tomas Pfister ➡️ 研究机构: Google Cloud AI Re…...
大学生职业发展与就业创业指导教学评价
这里是引用 作为软工2203/2204班的学生,我们非常感谢您在《大学生职业发展与就业创业指导》课程中的悉心教导。这门课程对我们即将面临实习和就业的工科学生来说至关重要,而您认真负责的教学态度,让课程的每一部分都充满了实用价值。 尤其让我…...
20个超级好用的 CSS 动画库
分享 20 个最佳 CSS 动画库。 它们中的大多数将生成纯 CSS 代码,而不需要任何外部库。 1.Animate.css 一个开箱即用型的跨浏览器动画库,可供你在项目中使用。 2.Magic Animations CSS3 一组简单的动画,可以包含在你的网页或应用项目中。 3.An…...
STM32HAL库USART源代码解析及应用
STM32HAL库USART源代码解析 前言STM32CubeIDE配置串口USART和UART的选择使用模式参数设置GPIO配置DMA配置中断配置硬件流控制使能生成代码解析和使用方法串口初始化__UART_HandleTypeDef结构体浅析HAL库代码实际使用方法使用轮询方式发送使用轮询方式接收使用中断方式发送使用中…...
基于PHP的连锁酒店管理系统
有需要请加文章底部Q哦 可远程调试 基于PHP的连锁酒店管理系统 一 介绍 连锁酒店管理系统基于原生PHP开发,数据库mysql,前端bootstrap。系统角色分为用户和管理员。 技术栈 phpmysqlbootstrapphpstudyvscode 二 功能 用户 1 注册/登录/注销 2 个人中…...
【LeetCode】3309. 连接二进制表示可形成的最大数值(递归|回溯|位运算)
LeetCode 3309. 连接二进制表示可形成的最大数值(中等) 题目描述解题思路Java代码 题目描述 题目链接:LeetCode 3309. 连接二进制表示可形成的最大数值(中等) 给你一个长度为 3 的整数数组 nums。 现以某种顺序 连接…...
Linux系统部署KES
1、安装准备 1.版本说明V008R006C009B0014 V008:是version产品的大版本。 R006:是release产品特性版本。 C009:是通用版 B0014:是build开发过程中的构建版本2.硬件要求 #安全版和企业版 内存:1GB 以上 硬盘…...
