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

从零开始搭建Springboot项目脚手架4:保存操作日志

目的:通过AOP切面,统一记录接口的访问日志

1、加maven依赖

2、 增加日志类RequestLog

3、 配置AOP切面,把请求前的request、返回的response一起记录

package com.template.common.config;import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.template.common.domain.model.RequestLog;
import eu.bitwalker.useragentutils.UserAgent;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
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.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;/*** 使用AOP切面记录请求日志*/
@Aspect
@Component
@Slf4j
public class RequestLogger {/*** 切入点*/@Pointcut("execution(public * com.template.api.controller.*.*Controller.*(..))")public void controllerPointcut() {// 本方法不会被执行,只是作为一个标记,与被注解的服务方法进行关联// 切入点为controller的public方法,也就是各个请求路径}/*** 环绕操作** @param joinPoint 切入点* @return 原方法返回值* @throws Throwable 异常信息*/@Around("controllerPointcut()")public Object controllerLogging(ProceedingJoinPoint joinPoint) throws Throwable {// 开始打印请求日志ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();HttpServletRequest request = Objects.requireNonNull(attributes).getRequest();// 计算各个日志参数long startTime = System.currentTimeMillis();Thread currentThread = Thread.currentThread();String userHost = extractUserHost(request);String userAgentString = request.getHeader("User-Agent");UserAgent userAgent = UserAgent.parseUserAgentString(userAgentString);Map<String, Object> requestParams = extractRequestParams(joinPoint);Object responseResult = joinPoint.proceed();Signature controllerMethod = joinPoint.getSignature();String classMethod = controllerMethod.getDeclaringTypeName() + "." + controllerMethod.getName();final RequestLog requestLog = RequestLog.builder().userHost(userHost).userOs(userAgent.getOperatingSystem().getName()).userBrowser(userAgent.getBrowser().getName()).userAgent(userAgentString).requestUrl(request.getRequestURL().toString()).requestMethod(request.getMethod()).requestParams(requestParams).responseResult(responseResult).classMethod(classMethod).threadId(Long.toString(currentThread.getId())).threadName(currentThread.getName()).costMillisecond(System.currentTimeMillis() - startTime).build();// 打印日志log.info("Request Log Info : {}", JSONUtil.toJsonStr(requestLog));return responseResult;}/*** 获取用户Host地址** @param request 请求对象* @return 用户Host*/private static String extractUserHost(HttpServletRequest request) {String xRealIp = request.getHeader("X-Real-IP");if (StrUtil.isNotEmpty(xRealIp) && !"unknown".equalsIgnoreCase(xRealIp)) {return xRealIp;}String xForwardedFor = request.getHeader("x-forwarded-for");if (StrUtil.isNotEmpty(xForwardedFor) && !"unknown".equalsIgnoreCase(xForwardedFor)) {return xForwardedFor;}String proxyClientIp = request.getHeader("Proxy-Client-IP");if (StrUtil.isNotEmpty(proxyClientIp) && !"unknown".equalsIgnoreCase(proxyClientIp)) {return proxyClientIp;}String wlProxyClientIp = request.getHeader("WL-Proxy-Client-IP");if (StrUtil.isNotEmpty(wlProxyClientIp) && !"unknown".equalsIgnoreCase(wlProxyClientIp)) {return wlProxyClientIp;}String httpClientIp = request.getHeader("HTTP_CLIENT_IP");if (StrUtil.isNotEmpty(httpClientIp) && !"unknown".equalsIgnoreCase(httpClientIp)) {return httpClientIp;}String httpxForwardedFor = request.getHeader("HTTP_X_FORWARDED_FOR");if (StrUtil.isNotEmpty(httpxForwardedFor) && !"unknown".equalsIgnoreCase(httpxForwardedFor)) {return httpxForwardedFor;}String remoteAddr = request.getRemoteAddr();if (!"127.0.0.1".equals(remoteAddr) && !"0:0:0:0:0:0:0:1".equals(remoteAddr)) {return remoteAddr;}//根据网卡取本机IP地址try {return InetAddress.getLocalHost().getHostAddress();} catch (UnknownHostException e) {log.error("getIpAddress exception:", e);}return xRealIp;}/*** 获取请求参数** @param joinPoint 切入点* @return 请求参数*/private Map<String, Object> extractRequestParams(ProceedingJoinPoint joinPoint) {MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();final String[] parameterNames = methodSignature.getParameterNames();final Object[] parameterValues = joinPoint.getArgs();if (ArrayUtil.isEmpty(parameterNames) || ArrayUtil.isEmpty(parameterValues)) {return Collections.emptyMap();}if (parameterNames.length != parameterValues.length) {log.warn("{}方法参数名和参数值数量不一致", methodSignature.getName());return Collections.emptyMap();}Map<String, Object> requestParams = new HashMap<>();for (int i = 0; i < parameterNames.length; i++) {requestParams.put(parameterNames[i], parameterValues[i]);}return requestParams;}}

4、查看效果

 

 也可以把日志写入到数据库里面,自由发挥。

相关文章:

  • 基于飞书机器人跨账号消息提醒
  • redis查看一个key占用了多少内存
  • [nextjs]推荐几个很好看的模板网站
  • shell将文件分割成小块文件
  • 场景文本检测识别学习 day10(MMdetection)
  • 预训练模型语义相似性计算(十一) - M3E和BGE
  • P7-P9【分配器】【源文件】【OOPvs.GP】
  • Flutter 中的 AnimatedPadding 小部件:全面指南
  • 跳绳步法汇总
  • go语言初识别(五)
  • 【文末附gpt升级方案】GPT-4级别的AI系统安全性探讨与未来展望
  • 【Linux】Linux的权限_2 + Linux环境基础开发工具_1
  • BIO/NIO学习
  • JAVA面试题大全(十二)
  • 常见的MySQL语句类型及其基础用法
  • 【Linux系统编程】快速查找errno错误码信息
  • co.js - 让异步代码同步化
  • CSS居中完全指南——构建CSS居中决策树
  • Dubbo 整合 Pinpoint 做分布式服务请求跟踪
  • eclipse(luna)创建web工程
  • ES6简单总结(搭配简单的讲解和小案例)
  • Intervention/image 图片处理扩展包的安装和使用
  • JavaScript 基础知识 - 入门篇(一)
  • JavaScript函数式编程(一)
  • laravel with 查询列表限制条数
  • mongo索引构建
  • MySQL用户中的%到底包不包括localhost?
  • node入门
  • Python 使用 Tornado 框架实现 WebHook 自动部署 Git 项目
  • Ruby 2.x 源代码分析:扩展 概述
  • vagrant 添加本地 box 安装 laravel homestead
  • VirtualBox 安装过程中出现 Running VMs found 错误的解决过程
  • Vue 动态创建 component
  • vue2.0一起在懵逼的海洋里越陷越深(四)
  • 关于springcloud Gateway中的限流
  • 前言-如何学习区块链
  • 深入浅出Node.js
  • 手写一个CommonJS打包工具(一)
  • 赢得Docker挑战最佳实践
  • 在GitHub多个账号上使用不同的SSH的配置方法
  • 【运维趟坑回忆录 开篇】初入初创, 一脸懵
  • elasticsearch-head插件安装
  • 哈罗单车融资几十亿元,蚂蚁金服与春华资本加持 ...
  • ​configparser --- 配置文件解析器​
  • # wps必须要登录激活才能使用吗?
  • # 服务治理中间件详解:Spring Cloud与Dubbo
  • ###51单片机学习(2)-----如何通过C语言运用延时函数设计LED流水灯
  • #laravel 通过手动安装依赖PHPExcel#
  • #NOIP 2014# day.1 T2 联合权值
  • #中的引用型是什么意识_Java中四种引用有什么区别以及应用场景
  • $emit传递多个参数_PPC和MIPS指令集下二进制代码中函数参数个数的识别方法
  • (Java)【深基9.例1】选举学生会
  • (Mirage系列之二)VMware Horizon Mirage的经典用户用例及真实案例分析
  • (MonoGame从入门到放弃-1) MonoGame环境搭建
  • (第27天)Oracle 数据泵转换分区表