当前位置: 首页 > news >正文

springboot 前后端处理日志

为了实现一个高效且合理的日志记录方案,我们需要在系统架构层面进行细致规划。在某些情况下,一个前端页面可能会调用多个辅助接口来完成整个业务流程,而并非所有这些接口的交互都需要被记录到日志中。为了避免不必要的日志开销,并确保日志系统的可读性和有效性,我们可以采用以下策略:

日志记录策略概述
前端控制:
前端在发起请求时,可以通过HTTP头(Header)传递一个标志,指示当前请求是否需要记录日志。例如,可以使用一个自定义的Header,如operation-log,其值可以是true或false。
后端响应:
后端服务接收到请求后,首先检查该Header的存在及值。如果该Header存在且其值为true,则后端将记录此次请求的相关信息;反之,则忽略日志记录。
下面贴出核心代码
注解

import java.lang.annotation.*;@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Loggable {// 接口信息String value() default "";// 是否记录响应结果,默认为trueboolean logResponse() default true;// 模块String module() default "";// 类型 insert 新增 delete 删除  select 查看 update 修改String type() default "";}

配置信息

import cn.com.nmd.base.constant.Constants;
import cn.hutool.core.util.IdUtil;
import org.slf4j.MDC;
import org.springframework.web.filter.OncePerRequestFilter;import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;public class TraceIdFilter extends OncePerRequestFilter {@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)throws ServletException, IOException {String traceId = IdUtil.objectId();MDC.put(Constants.TRACE_ID, traceId);try {// 响应头中添加 traceId 参数,方便排查问题response.setHeader(Constants.TRACE_ID, traceId);filterChain.doFilter(request, response);}finally {MDC.remove(Constants.TRACE_ID);}}}
import cn.com.nmd.auth.log.mdc.TraceIdFilter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;@Configuration
@ConditionalOnWebApplication
public class TraceIdAutoConfiguration {@Beanpublic FilterRegistrationBean<TraceIdFilter> traceIdFilterRegistrationBean() {FilterRegistrationBean<TraceIdFilter> registrationBean = new FilterRegistrationBean<>(new TraceIdFilter());registrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE);return registrationBean;}}

工具类

import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Objects;public class WebUtils extends org.springframework.web.util.WebUtils {/*** 获取 ServletRequestAttributes* @return {ServletRequestAttributes}*/public static ServletRequestAttributes getServletRequestAttributes() {return (ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes());}/*** 获取 HttpServletRequest* @return {HttpServletRequest}*/public static HttpServletRequest getRequest() {return getServletRequestAttributes().getRequest();}/*** 获取 HttpServletResponse* @return {HttpServletResponse}*/public static HttpServletResponse getResponse() {return getServletRequestAttributes().getResponse();}}

拦截器

import cn.com.nmd.auth.annotation.Loggable;
import cn.com.nmd.auth.utils.SecurityUtils;
import cn.com.nmd.auth.utils.WebUtils;
import cn.com.nmd.base.constant.Constants;
import cn.com.nmd.base.model.SysOperationLog;
import cn.com.nmd.base.utils.IpUtils;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.URLUtil;
import com.alibaba.fastjson.JSON;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.multipart.MultipartFile;import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.*;@Aspect
@Component
public class LogAspect {private static final Logger logger = LoggerFactory.getLogger(LogAspect.class);private final List<Class<?>> ignoredParamClasses = ListUtil.toList(ServletRequest.class, ServletResponse.class,MultipartFile.class);// 定义切入点,匹配带有 @Loggable 注解的方法@Pointcut("@annotation(cn.com.nmd.auth.annotation.Loggable)")public void loggableMethod() {}@Around("loggableMethod()")public Object aroundLog(ProceedingJoinPoint joinPoint) throws Throwable {// 获取 RequestHttpServletRequest request = WebUtils.getRequest();String operateLog = request.getHeader("operation-log");// 开始时间long startTime = System.currentTimeMillis();SysOperationLog sysOperationLog = null;boolean b = false;if ("true".equals(operateLog)) {// 获取目标方法MethodSignature signature = (MethodSignature) joinPoint.getSignature();Method method = signature.getMethod();Loggable loggable = method.getAnnotation(Loggable.class);b = loggable.logResponse();sysOperationLog = this.buildLog(loggable, joinPoint);}Throwable throwable = null;Object result = null;try {result = joinPoint.proceed();return result;} catch (Throwable e) {throwable = e;throw e;} finally {if ("true".equals(operateLog)) {// 操作日志记录处理this.handleLog(startTime, sysOperationLog, throwable, b, result);}}}private void handleLog(long startTime, SysOperationLog sysOperationLog, Throwable throwable, boolean b, Object result) {try {// 结束时间long executionTime = System.currentTimeMillis() - startTime;// 执行时长sysOperationLog.setOperateTime(executionTime);// 执行状态String logStatus = throwable == null ? Constants.SUCCESS : Constants.FAIL;sysOperationLog.setStatus(logStatus);// 执行结果if (b) {Optional.ofNullable(result).ifPresent(x -> sysOperationLog.setResult(JSON.toJSONString(x)));}else {sysOperationLog.setResult(("{\"code\": \"200\",\"message\": \"\",\"data\": \"\"}"));}// 保存操作日志logger.info("保存的日志数据:{}", JSON.toJSONString(sysOperationLog));} catch (Exception e) {logger.error("记录操作日志异常:{}", JSON.toJSONString(sysOperationLog));}}private SysOperationLog buildLog(Loggable loggable, ProceedingJoinPoint joinPoint) {HttpServletRequest request = WebUtils.getRequest();SysOperationLog operationLog = new SysOperationLog();operationLog.setIp(IpUtils.getIpAddr(request));operationLog.setMethod(request.getMethod());operationLog.setUserAgent(request.getHeader(HttpHeaders.USER_AGENT));operationLog.setUri(URLUtil.getPath(request.getRequestURI()));operationLog.setType(loggable.type());operationLog.setModule(loggable.module());operationLog.setTraceId(MDC.get(Constants.TRACE_ID));operationLog.setCreateTime(new Date());operationLog.setParams(getParams(joinPoint));operationLog.setCreateUserId(SecurityUtils.getUserId());return operationLog;}/*** 获取方法参数** @param joinPoint 切点* @return 当前方法入参的Json Str*/public String getParams(ProceedingJoinPoint joinPoint) {// 获取方法签名Signature signature = joinPoint.getSignature();String strClassName = joinPoint.getTarget().getClass().getName();String strMethodName = signature.getName();MethodSignature methodSignature = (MethodSignature) signature;logger.debug("[getParams],获取方法参数[类名]:{},[方法]:{}", strClassName, strMethodName);String[] parameterNames = methodSignature.getParameterNames();Object[] args = joinPoint.getArgs();Method method = ((MethodSignature) signature).getMethod();if (ArrayUtil.isEmpty(parameterNames)) {return null;}Map<String, Object> paramsMap = new HashMap<>();for (int i = 0; i < parameterNames.length; i++) {Object arg = args[i];if (arg == null) {paramsMap.put(parameterNames[i], null);continue;}Class<?> argClass = arg.getClass();// 忽略部分类型的参数记录for (Class<?> ignoredParamClass : ignoredParamClasses) {if (ignoredParamClass.isAssignableFrom(argClass)) {arg = "ignored param type: " + argClass;break;}}paramsMap.put(parameterNames[i], arg);}// 特别处理 @RequestBody 参数for (int i = 0; i < parameterNames.length; i++) {Object arg = args[i];if (arg != null && method.getParameterAnnotations()[i].length > 0) {RequestBody requestBodyAnnotation = method.getParameterAnnotations()[i][0].annotationType().getAnnotation(RequestBody.class);if (requestBodyAnnotation != null) {paramsMap.put(parameterNames[i], arg);}}}String params = "";try {// 入参类中的属性可以通过注解进行数据落库脱敏以及忽略等操作params = JSON.toJSONString(paramsMap);} catch (Exception e) {logger.error("[getParams],获取方法参数异常,[类名]:{},[方法]:{}", strClassName, strMethodName, e);}return params;}
}

示例

 	@Loggable(value = "保存采集信息", type = "insert", module = "业务管理-场站管理", logResponse = true)

相关文章:

springboot 前后端处理日志

为了实现一个高效且合理的日志记录方案&#xff0c;我们需要在系统架构层面进行细致规划。在某些情况下&#xff0c;一个前端页面可能会调用多个辅助接口来完成整个业务流程&#xff0c;而并非所有这些接口的交互都需要被记录到日志中。为了避免不必要的日志开销&#xff0c;并…...

C++11 简单手撕多线程编程

如何使用线程库 std::thread 创建线程 thread1.join(); 阻塞主线程 thread1.detach(); 线程分离 #include<iostream> #include<thread>void helloworld(std::string msg) {for (int i 0; i < 10000; i){std::cout << i << std::endl;}//std::cou…...

刷c语言练习题7(牛客网)

1、函数fun的声明为int fun(int *p[4]),以下哪个变量可以作为fun的合法参数&#xff08;&#xff09; A、int a[4][4]; B、int **a; C、int **a[4] D、int (*a)[4]; 答案&#xff1a;B 解析&#xff1a;如果是fun的合法参数&#xff0c;那么其类型应该与定义函数fun中的参数类型…...

Web Worker和WebSocket

Web Worker和WebSocket协议都是Web开发中用于处理多线程和实时通信的技术&#xff0c;但它们的应用场景和工作原理有所不同。 Web Worker Web Worker是HTML5引入的一项技术&#xff0c;它允许JavaScript代码在后台线程中运行&#xff0c;从而实现真正的多线程处理。Web Worke…...

【LeetCode】动态规划—712. 两个字符串的最小ASCII删除和(附完整Python/C++代码)

动态规划—712. 两个字符串的最小ASCII删除和 前言题目描述基本思路1. 问题定义2. 理解问题和递推关系3. 解决方法3.1 动态规划方法3.2 空间优化的动态规划 4. 进一步优化5. 小总结 代码实现PythonPython3代码实现Python 代码解释 CC代码实现C 代码解释 总结: 前言 在字符串处…...

wordpress Contact Form 7插件提交留言时发生错误可能的原因

WordPress Contact Form 7 插件提交留言时发生错误可能有以下几种原因&#xff0c;并提供相应的解决方案&#xff1a; 1. 表单字段验证失败 原因&#xff1a; 用户输入的数据未通过表单字段的验证规则。 解决方案&#xff1a; – 检查表单字段的验证规则是否设置正确。 –…...

uibot发送邮件:自动化邮件发送教程详解!

uibot发送邮件的操作指南&#xff1f;uibot发送邮件的两种方式&#xff1f; 在现代办公环境中&#xff0c;自动化流程的引入极大地提高了工作效率。uibot发送邮件功能成为了许多企业和个人实现邮件自动化发送的首选工具。AokSend将详细介绍如何使用uibot发送邮件。 uibot发送…...

【PostgreSQL】PG数据库表“膨胀”粗浅学习

文章目录 1 为什么需要关注表膨胀&#xff1f;2 如何确定是否发生了表膨胀&#xff1f;2.1 通过查询表的死亡元组占比情况来判断膨胀率2.1.1 指定数据库和表名2.1.2 查询数据库里面所有表的膨胀情况 3 膨胀的原理3.1 什么是膨胀&#xff1f;膨胀率&#xff1f;3.2 哪些数据库元…...

力扣(leetcode)每日一题 871 最低加油次数 | 贪心

871. 最低加油次数 题干 汽车从起点出发驶向目的地&#xff0c;该目的地位于出发位置东面 target 英里处。 沿途有加油站&#xff0c;用数组 stations 表示。其中 stations[i] [positioni, fueli] 表示第 i 个加油站位于出发位置东面 positioni 英里处&#xff0c;并且有 f…...

ppt压缩文件怎么压缩?压缩PPT文件的多种压缩方法

ppt压缩文件怎么压缩&#xff1f;当文件体积过大时&#xff0c;分享和传输就会变得困难。许多电子邮件服务对附件的大小有限制&#xff0c;而在网络环境不佳时&#xff0c;上传和下载大文件可能耗时较长。此外&#xff0c;在不同设备上播放时&#xff0c;较大的PPT文件还可能导…...

2024.10月11日--- SpringMVC拦截器

拦截器 1 回顾过滤器&#xff1a; Servlet规范中的三大接口&#xff1a;Servlet接口&#xff0c;Filter接口、Listener接口。 过滤器接口&#xff0c;是Servlet2.3版本以来&#xff0c;定义的一种小型的&#xff0c;可插拔的Web组件&#xff0c;可以用来拦截和处理Servlet容…...

uniapp 锁屏显示插件 Ba-LockShow(可让vue直接具备锁屏显示能力)

简介 Ba-LockShow 是一款可以直接使uniapp的vue界面在锁屏页展示的插件。 支持使vue直接具备锁屏显示能力支持设置锁屏显示和不显示支持唤醒屏幕 截图展示&#xff08;仅参考&#xff09; 支持定制、本地包、源码等&#xff0c;有建议和需要&#xff0c;请点击文章结尾“Unia…...

CSS计数器

CSS 中的计数器类似于变量&#xff0c;可以实现简单的计数功能&#xff0c;并将结果显示在页面上&#xff0c;在早期的网站上应用比较广泛。要实现计数器需要用到以下几个属性&#xff1a; counter-reset&#xff1a;创建或者重置计数器&#xff1b;counter-increment&#xf…...

嵌入式Linux:信号集

目录 1、信号集初始化 2、向信号集中添加或删除信号 3、测试信号是否在信号集中 在 Linux 系统中&#xff0c;处理多个信号时常用到一种数据结构&#xff1a;信号集&#xff08;sigset_t&#xff09;。信号集允许我们将多个信号组织在一起&#xff0c;以便在系统调用中传递和…...

Linux 外设驱动 应用 1 IO口输出

从这里开始外设驱动介绍&#xff0c;这里使用的IMX8的芯片作为驱动介绍 开发流程&#xff1a; 修改设备树&#xff0c;配置 GPIO1_IO07 为 GPIO 输出。使用 sysfs 接口或编写驱动程序控制 GPIO 引脚。编译并测试。 这里假设设备树&#xff0c;已经配置好了。不在论述这个问题…...

基于SpringBoot+Vue+MySQL的留守儿童爱心网站

系统展示 用户前台界面 管理员后台界面 系统背景 随着现代社会的发展&#xff0c;留守儿童问题日益受到关注。传统的纸质管理方式已经无法满足现代人们对留守儿童爱心信息的需求。为了提高留守儿童爱心信息的管理效率&#xff0c;增加用户信息的安全性&#xff0c;并方便及时反…...

调用第三方接口

目录 一、分析给出的接口文档 二、请求体格式之间的区别 三、示例代码 一、分析给出的接口文档 一般的接口文档包括以下几大部分&#xff1a; 1、请求URL&#xff1a;http://{ip}:{port}/api/ec/dev/message/sendCustomMessageSingle 2、请求方式&#xff1a;POST、GET等 3、…...

JAVA 多线程入门例子:CountDownLatch

首先确定线程数量。如果数据集合的大小小于50&#xff0c;就只使用一个线程&#xff1b;否则使用5个线程。计算每个线程平均处理的数据数量sizePerThread以及余数remainder。在划分数据子集合时&#xff0c;对于每个线程的处理范围进行计算。如果有余数&#xff0c;就将余数依次…...

k8s jenkins 动态创建slave

k8s jenkins 动态创建slave 简述使用jenkins动态slave的优势&#xff1a;配置jenkins动态slave配置 Pod Template配置容器模板挂载卷 测试 简述 持续构建与发布是我们日常工作中必不可少的一个步骤&#xff0c;目前大多公司都采用 Jenkins 集群来搭建符合需求的 CI/CD 流程&am…...

MVS海康工业相机达不到标称最大帧率

文章目录 一、相机参数设置1、取消相机帧率限制2、修改相机图像格式3、调整相机曝光时间4、检查相机数据包大小&#xff08;网口相机特有参数&#xff09;5、 恢复相机默认参数6、 相机 ADC 输出位深调整 二、系统环境设置1、 网口相机设置2、 USB 相机设置 一、相机参数设置 …...

Spark 之 入门讲解详细版(1)

1、简介 1.1 Spark简介 Spark是加州大学伯克利分校AMP实验室&#xff08;Algorithms, Machines, and People Lab&#xff09;开发通用内存并行计算框架。Spark在2013年6月进入Apache成为孵化项目&#xff0c;8个月后成为Apache顶级项目&#xff0c;速度之快足见过人之处&…...

进程地址空间(比特课总结)

一、进程地址空间 1. 环境变量 1 &#xff09;⽤户级环境变量与系统级环境变量 全局属性&#xff1a;环境变量具有全局属性&#xff0c;会被⼦进程继承。例如当bash启动⼦进程时&#xff0c;环 境变量会⾃动传递给⼦进程。 本地变量限制&#xff1a;本地变量只在当前进程(ba…...

抖音增长新引擎:品融电商,一站式全案代运营领跑者

抖音增长新引擎&#xff1a;品融电商&#xff0c;一站式全案代运营领跑者 在抖音这个日活超7亿的流量汪洋中&#xff0c;品牌如何破浪前行&#xff1f;自建团队成本高、效果难控&#xff1b;碎片化运营又难成合力——这正是许多企业面临的增长困局。品融电商以「抖音全案代运营…...

MMaDA: Multimodal Large Diffusion Language Models

CODE &#xff1a; https://github.com/Gen-Verse/MMaDA Abstract 我们介绍了一种新型的多模态扩散基础模型MMaDA&#xff0c;它被设计用于在文本推理、多模态理解和文本到图像生成等不同领域实现卓越的性能。该方法的特点是三个关键创新:(i) MMaDA采用统一的扩散架构&#xf…...

Vue2 第一节_Vue2上手_插值表达式{{}}_访问数据和修改数据_Vue开发者工具

文章目录 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染2. 插值表达式{{}}3. 访问数据和修改数据4. vue响应式5. Vue开发者工具--方便调试 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染 准备容器引包创建Vue实例 new Vue()指定配置项 ->渲染数据 准备一个容器,例如: …...

第一篇:Agent2Agent (A2A) 协议——协作式人工智能的黎明

AI 领域的快速发展正在催生一个新时代&#xff0c;智能代理&#xff08;agents&#xff09;不再是孤立的个体&#xff0c;而是能够像一个数字团队一样协作。然而&#xff0c;当前 AI 生态系统的碎片化阻碍了这一愿景的实现&#xff0c;导致了“AI 巴别塔问题”——不同代理之间…...

WordPress插件:AI多语言写作与智能配图、免费AI模型、SEO文章生成

厌倦手动写WordPress文章&#xff1f;AI自动生成&#xff0c;效率提升10倍&#xff01; 支持多语言、自动配图、定时发布&#xff0c;让内容创作更轻松&#xff01; AI内容生成 → 不想每天写文章&#xff1f;AI一键生成高质量内容&#xff01;多语言支持 → 跨境电商必备&am…...

Python 包管理器 uv 介绍

Python 包管理器 uv 全面介绍 uv 是由 Astral&#xff08;热门工具 Ruff 的开发者&#xff09;推出的下一代高性能 Python 包管理器和构建工具&#xff0c;用 Rust 编写。它旨在解决传统工具&#xff08;如 pip、virtualenv、pip-tools&#xff09;的性能瓶颈&#xff0c;同时…...

免费数学几何作图web平台

光锐软件免费数学工具&#xff0c;maths,数学制图&#xff0c;数学作图&#xff0c;几何作图&#xff0c;几何&#xff0c;AR开发,AR教育,增强现实,软件公司,XR,MR,VR,虚拟仿真,虚拟现实,混合现实,教育科技产品,职业模拟培训,高保真VR场景,结构互动课件,元宇宙http://xaglare.c…...

比较数据迁移后MySQL数据库和OceanBase数据仓库中的表

设计一个MySQL数据库和OceanBase数据仓库的表数据比较的详细程序流程,两张表是相同的结构,都有整型主键id字段,需要每次从数据库分批取得2000条数据,用于比较,比较操作的同时可以再取2000条数据,等上一次比较完成之后,开始比较,直到比较完所有的数据。比较操作需要比较…...