springboot 前后端处理日志

news/2024/10/11 5:46:30/

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

日志记录策略概述
前端控制:
前端在发起请求时,可以通过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)

http://www.ppmy.cn/news/1537291.html

相关文章

谷歌AI大模型Gemini API快速入门及LangChain调用视频教程

1. 谷歌Gemini API KEY获取及AI Studio使用 要使用谷歌Gemini API&#xff0c;首先需要获取API密钥。以下是获取API密钥的步骤&#xff1a; 访问Google AI Studio&#xff1a; 打开浏览器&#xff0c;访问Google AI Studio。使用Google账号登录&#xff0c;若没有账号&#xf…

充电宝租赁管理系统网站毕业设计SpringBootSSM框架开发

目录 1. 概述 2. 技术选择与介绍 3. 系统设计 4. 功能实现 5. 需求分析 1. 概述 充电宝租赁管理系统网站是一个既实用又具有挑战性的项目。 随着移动设备的普及和人们日常生活对电力的持续依赖&#xff0c;充电宝租赁服务已成为现代都市生活中的一项重要便利设施。它不仅为…

机器学习与神经网络:开启物理学的新篇章

近日&#xff0c;2024年诺贝尔物理学奖的颁发引发了全球热议&#xff0c;尤其是首次将这项传统上授予物理学研究者的奖项颁给了机器学习与神经网络领域的科学家。这一举动标志着人工智能技术&#xff0c;尤其是深度学习技术&#xff0c;正在深入影响科学的各个领域&#xff0c;…

GC1277和灿瑞的OCH477优势分析 可以用于电脑散热风扇,视频监控和图像处理的图像信号处理器中

GC1277和灿瑞的OCH477是两款用于视频监控和图像处理的图像信号处理器&#xff08;ISP&#xff09;。在对比这两款产品时&#xff0c;可以从以下几个方面考虑它们的优势和特点&#xff1a; 1. 图像处理能力 GC1277&#xff1a;通常具有更强的图像处理算法&#xff0c;支持多种…

npm依赖版本锁定详解

npm中有一个package-lock.json的文件&#xff0c;即npm依赖锁文件&#xff0c;用来描述npm依赖生成的确切树&#xff0c;这样不管你的依赖有何种更新&#xff0c;都会按照这个确切树来安装使用。 不同的包管理工具对应不同的锁文件&#xff1a; ● npm > package-lock.json…

使用 Docker 部署前端项目:Vue 和 React 结合 Nginx 实现静态文件托管

使用 Docker 部署前端项目&#xff1a;Vue 和 React 结合 Nginx 实现静态文件托管 Web 开发中&#xff0c;将前端项目&#xff08;例如 Vue 或 React 应用&#xff09;打包后通过 Docker 容器和 Nginx 部署是非常常见的方式。它不仅简化了部署流程&#xff0c;还能确保在不同环…

科研绘图系列:R语言绘制SCI文章图2

文章目录 介绍加载R包导入数据图a图b图d系统信息介绍 文章提供了绘制图a,图b和图d的数据和代码 加载R包 library(ggplot2) library(dplyr) library(readxl) library(ggpmisc)导入数据 数据可从以下链接下载(画图所需要的所有数据): 百度网盘下载链接: https://pan.baid…

scau:面向对象java实验作业1-2 猜数字游戏

题目名称实验1-2 猜数字游戏题目关键字数据类型 基本输入输出 控制语句 方法题目录入时间2022/10/10 11:01:37题目内容 使用Java程序&#xff0c;项目名称&#xff1a;GuessNumberGame&#xff0c;类根据自己需要定义。 程序开始运行后&#xff0c;允许玩家进行多次猜数字的游…