个人博客开发之blog-api 项目全局日志拦截记录
前言
大型完善项目中肯定是需要一个全局日志拦截,记录每次接口访问相关信息,包括:
访问ip,访问设备,请求参数,响应结果,响应时间,开始请求时间,访问接口描述,访问的用户,接口地址,请求类型,便于项目的调试追踪
整合日志
SpringBoot已经帮我们做了日志整合,在它的父pom项中
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.7</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
我们点进去看到还有父pom项
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.4.7</version>
</parent>
spring-boot-dependencies帮我自动整合所有jar包版本,和内置很多启动项start,这是SpringBoot使用起来不需要配置那么多jar包依赖关系的本质,点进去我们看到了,所有spring,springmvc所需要依赖,和jar版本


最终我们找到了日志依赖所需jar包,所以我们直接使用就行,不需要额外引入
配置日志
######日志配置######
# 日志文件
logging.config=classpath:logback-spring.xml
#日志包级别
#logging.level.root=info
logging.level.cn.soboys.blogapi=info
#日志存储路径
logging.file.path=/Users/xiangyong/selfProject/project/blog-api/log
日志文件配置
这里日志名字logback-spring.xml是SpringBoot独有,这么写spring boot可以为它添加一些spring boot特有的配置项
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds" debug="false">
<contextName>shop-api</contextName>
<!--定义日志文件的存储地址 从springboot配置文件中获取路径-->
<springProperty scope="context" name="LOG_PATH" source="logging.file.path"/>
<!--springboot配置文件中获取日志级别-->
<springProperty scope="context" name="LOG_LEVEL" source="logging.level.root"/>
<!-- <property name="log.path" value="log" />-->
<property name="log.maxHistory" value="15" />
<property name="log.colorPattern" value="%magenta(%d{yyyy-MM-dd HH:mm:ss}) %highlight(%-5level) %yellow(%thread) %green(%logger) %msg%n"/>
<property name="log.pattern" value="%d{yyyy-MM-dd HH:mm:ss} %-5level %thread %logger %msg%n"/>
<!--输出到控制台-->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${log.colorPattern}</pattern>
</encoder>
</appender>
<!--输出到文件-->
<appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/info/info.%d{yyyy-MM-dd}.log</fileNamePattern>
<MaxHistory>${log.maxHistory}</MaxHistory>
</rollingPolicy>
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFO</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/error/error.%d{yyyy-MM-dd}.log</fileNamePattern>
</rollingPolicy>
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<root level="debug">
<appender-ref ref="console" />
</root>
<root level="info">
<appender-ref ref="file_info" />
<appender-ref ref="file_error" />
</root>
</configuration>
这里我把日志分成两个日志文件,一个错误日志,一个信息日志,按照明天一个日志文件方式

全局日志记录
这里我们基于aop切面进行请求拦截,记录所有请求相关信息
package cn.soboys.core;
import lombok.Data;
/**
* @author kenx
* @version 1.0
* @date 2021/6/18 18:48
* 日志信息
*/
@Data
public class LogSubject {
/**
* 操作描述
*/
private String description;
/**
* 操作用户
*/
private String username;
/**
* 操作时间
*/
private String startTime;
/**
* 消耗时间
*/
private String spendTime;
/**
* URL
*/
private String url;
/**
* 请求类型
*/
private String method;
/**
* IP地址
*/
private String ip;
/**
* 请求参数
*/
private Object parameter;
/**
* 请求返回的结果
*/
private Object result;
/**
* 城市
*/
private String city;
/**
* 请求设备信息
*/
private String device;
}
这里用到了aop 所以要导入aop相关包
<!--aop 切面-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
编写切面类拦截对应的controller请求
package cn.soboys.core;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import java.lang.reflect.Method;
/**
* @author kenx
* @version 1.0
* @date 2021/6/18 14:52
* 切面
*/
public class BaseAspectSupport {
public Method resolveMethod(ProceedingJoinPoint point) {
MethodSignature signature = (MethodSignature)point.getSignature();
Class<?> targetClass = point.getTarget().getClass();
Method method = getDeclaredMethod(targetClass, signature.getName(),
signature.getMethod().getParameterTypes());
if (method == null) {
throw new IllegalStateException("无法解析目标方法: " + signature.getMethod().getName());
}
return method;
}
private Method getDeclaredMethod(Class<?> clazz, String name, Class<?>... parameterTypes) {
try {
return clazz.getDeclaredMethod(name, parameterTypes);
} catch (NoSuchMethodException e) {
Class<?> superClass = clazz.getSuperclass();
if (superClass != null) {
return getDeclaredMethod(superClass, name, parameterTypes);
}
}
return null;
}
}
日志记录切面GlobalLogAspect
package cn.soboys.core;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.TimeInterval;
import cn.hutool.json.JSONUtil;
import cn.soboys.core.utils.HttpContextUtil;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author kenx
* @version 1.0
* @date 2021/6/18 15:22
* 全局日志记录器
*/
@Slf4j
@Aspect
@Component
public class GlobalLogAspect extends BaseAspectSupport {
/**
* 定义切面Pointcut
*/
@Pointcut("execution(public * cn.soboys.blogapi.controller.*.*(..))")
public void log() {
}
/**
* 环绕通知
*
* @param joinPoint
* @return
*/
@Around("log()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
LogSubject logSubject = new LogSubject();
//记录时间定时器
TimeInterval timer = DateUtil.timer(true);
//执行结果
Object result = joinPoint.proceed();
logSubject.setResult(result);
//执行消耗时间
String endTime = timer.intervalPretty();
logSubject.setSpendTime(endTime);
//执行参数
Method method = resolveMethod(joinPoint);
logSubject.setParameter(getParameter(method, joinPoint.getArgs()));
HttpServletRequest request = HttpContextUtil.getRequest();
// 接口请求时间
logSubject.setStartTime(DateUtil.now());
//请求链接
logSubject.setUrl(request.getRequestURL().toString());
//请求方法GET,POST等
logSubject.setMethod(request.getMethod());
//请求设备信息
logSubject.setDevice(HttpContextUtil.getDevice());
//请求地址
logSubject.setIp(HttpContextUtil.getIpAddr());
//接口描述
if (method.isAnnotationPresent(ApiOperation.class)) {
ApiOperation apiOperation = method.getAnnotation(ApiOperation.class);
logSubject.setDescription(apiOperation.value());
}
String a = JSONUtil.toJsonPrettyStr(logSubject);
log.info(a);
return result;
}
/**
* 根据方法和传入的参数获取请求参数
*/
private Object getParameter(Method method, Object[] args) {
List<Object> argList = new ArrayList<>();
Parameter[] parameters = method.getParameters();
Map<String, Object> map = new HashMap<>();
for (int i = 0; i < parameters.length; i++) {
//将RequestBody注解修饰的参数作为请求参数
RequestBody requestBody = parameters[i].getAnnotation(RequestBody.class);
//将RequestParam注解修饰的参数作为请求参数
RequestParam requestParam = parameters[i].getAnnotation(RequestParam.class);
String key = parameters[i].getName();
if (requestBody != null) {
argList.add(args[i]);
} else if (requestParam != null) {
map.put(key, args[i]);
} else {
map.put(key, args[i]);
}
}
if (map.size() > 0) {
argList.add(map);
}
if (argList.size() == 0) {
return null;
} else if (argList.size() == 1) {
return argList.get(0);
} else {
return argList;
}
}
}
package cn.soboys.core;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.TimeInterval;
import cn.hutool.json.JSONUtil;
import cn.soboys.core.utils.HttpContextUtil;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author kenx
* @version 1.0
* @date 2021/6/18 15:22
* 全局日志记录器
*/
@Slf4j
@Aspect
@Component
public class GlobalLogAspect extends BaseAspectSupport {
/**
* 定义切面Pointcut
*/
@Pointcut("execution(public * cn.soboys.blogapi.controller.*.*(..))")
public void log() {
}
/**
* 环绕通知
*
* @param joinPoint
* @return
*/
@Around("log()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
LogSubject logSubject = new LogSubject();
//记录时间定时器
TimeInterval timer = DateUtil.timer(true);
//执行结果
Object result = joinPoint.proceed();
logSubject.setResult(result);
//执行消耗时间
String endTime = timer.intervalPretty();
logSubject.setSpendTime(endTime);
//执行参数
Method method = resolveMethod(joinPoint);
logSubject.setParameter(getParameter(method, joinPoint.getArgs()));
HttpServletRequest request = HttpContextUtil.getRequest();
// 接口请求时间
logSubject.setStartTime(DateUtil.now());
//请求链接
logSubject.setUrl(request.getRequestURL().toString());
//请求方法GET,POST等
logSubject.setMethod(request.getMethod());
//请求设备信息
logSubject.setDevice(HttpContextUtil.getDevice());
//请求地址
logSubject.setIp(HttpContextUtil.getIpAddr());
//接口描述
if (method.isAnnotationPresent(ApiOperation.class)) {
ApiOperation apiOperation = method.getAnnotation(ApiOperation.class);
logSubject.setDescription(apiOperation.value());
}
String a = JSONUtil.toJsonPrettyStr(logSubject);
log.info(a);
return result;
}
/**
* 根据方法和传入的参数获取请求参数
*/
private Object getParameter(Method method, Object[] args) {
List<Object> argList = new ArrayList<>();
Parameter[] parameters = method.getParameters();
Map<String, Object> map = new HashMap<>();
for (int i = 0; i < parameters.length; i++) {
//将RequestBody注解修饰的参数作为请求参数
RequestBody requestBody = parameters[i].getAnnotation(RequestBody.class);
//将RequestParam注解修饰的参数作为请求参数
RequestParam requestParam = parameters[i].getAnnotation(RequestParam.class);
String key = parameters[i].getName();
if (requestBody != null) {
argList.add(args[i]);
} else if (requestParam != null) {
map.put(key, args[i]);
} else {
map.put(key, args[i]);
}
}
if (map.size() > 0) {
argList.add(map);
}
if (argList.size() == 0) {
return null;
} else if (argList.size() == 1) {
return argList.get(0);
} else {
return argList;
}
}
}
这样我们在每次请求接口的时候都会记录请求的信息:

详细日志学习SpringBoot日志使用请参考我这两篇文章
关注公众号猿小叔获取更多干货分享

个人博客开发之blog-api 项目全局日志拦截记录的更多相关文章
- 个人博客开发之xadmin 布局和后台样式
项目源码下载:http://download.vhosts.cn 一. xadmin 后台配置注册信息 1. 在apps 的blogs 和 users 两个app中添加adminx.py文件 vim ...
- 个人博客开发之blog-api项目统一结果集api封装
前言 由于返回json api 格式接口,所以我们需要通过java bean封装一个统一数据返回格式,便于和前端约定交互, 状态码枚举ResultCode package cn.soboys.core ...
- 个人博客开发之blog-api 项目整合JWT实现token登录认证
前言 现在前后端分离,基于session设计到跨越问题,而且session在多台服器之前同步问题,肯能会丢失,所以倾向于使用jwt作为token认证 json web token 导入java-jwt ...
- SpringBoot博客开发之AOP日志处理
日志处理: 需求分析 日志处理需要记录的是: 请求的URL 访问者IP 调用的方法 传入的参数 返回的内容 上面的内容要求在控制台和日志中输出. 在学习这部分知识的时候,真的感觉收获很多,在之前Spr ...
- 个人博客开发之xadmin与ueditor集成
项目源码下载:http://download.vhosts.cn 1. xadmin 添加ueditor 插件 vim extra_apps\xadmin\plugins\ueditor.py #没有 ...
- 个人博客开发之 xadmin 安装
项目源码下载:http://download.vhosts.cn xadmin 下载地址:https://github.com/sshwsfc/xadmin或 https://github.com/s ...
- 个人博客开发之 ueditor 安装
- iOS开发之MVVM在项目中的应用
今天写这篇博客是想达到抛砖引玉的作用,想与大家交流一下思想,相互学习,博文中有不足之处还望大家批评指正.本篇博客的内容沿袭以往博客的风格,也是以干货为主,偶尔扯扯咸蛋(哈哈~不好好工作又开始发表博客啦 ...
- 文顶顶iOS开发博客链接整理及部分项目源代码下载
文顶顶iOS开发博客链接整理及部分项目源代码下载 网上的iOS开发的教程很多,但是像cnblogs博主文顶顶的博客这样内容图文并茂,代码齐全,示例经典,原理也有阐述,覆盖面宽广,自成系统的系列教程 ...
随机推荐
- 性能调优命令之jstack
jstack是java虚拟机自带的一种线程堆栈跟踪工具. /opt/java8/bin/jstack Usage: jstack [-l] <pid> (to connect to run ...
- 日常Bug排查-抛异常不回滚
日常Bug排查-抛异常不回滚 前言 日常Bug排查系列都是一些简单Bug排查,笔者将在这里介绍一些排查Bug的简单技巧,同时顺便积累素材_. Bug现场 最近有人反映java应用操作数据库的时候,抛异 ...
- Python+Selenium - 键盘操作
导包代码: from selenium.webdriver.common.keys import Keys 用法: driver.find_element("id","k ...
- ADAS测试
ADAS测试 1. ADAS和自动驾驶测试 AD和高级驾驶辅助系统(ADAS)正在不断增加新的雷达.摄像头.激光雷达和GNSS传感器,甚至也在改变 ...
- 编译器架构Compiler Architecture(下)
编译器架构Compiler Architecture(下) Combining Scanning and Parsing 实际上没有必要将扫描(词法分析/标记化)与解析(语法分析/树生成)分开.基于P ...
- 一文教会你认识Vuex状态机
摘要:简单来说,Vuex就是实现组件全局状态(数据)管理的一种机制,可以方便的实现组件之间数据的共享. 本文分享自华为云社区<Vuex状态机快速了解与应用>,原文作者:北极光之夜. 一. ...
- 《精通 ASP.NET Core MVC (第七版)》开始发售
学习 Web 开发技术很难吗?没有适合的学习资料,确实很枯燥,很难.如果有一本如同良师益友的优秀图书辅助,就很轻松,一点也不难! 对于优秀的技术图书来说,必须从读者的角度来编写,而不是从作者的角度来编 ...
- UF_VIEW 视图操作
Open C uc6400uc6401uc6402uc6403uc6404uc6405uc6406uc6408uc6409uc6430uc6431uc6432uc6433 获得视图3×3矩阵uc ...
- Django基础之视图层
内容概要 小白必会三板斧 request对象方法初识 form表单上传文件 Jsonresponse FBV与CBV 内容详细 1 小白必会三板斧 HttpResponse render redire ...
- SpringBoot数据访问(二) SpringBoot整合JPA
JPA简介 Spring Data JPA是Spring Data大家族的一部分,它可以轻松实现基于JPA的存储库.该模块用于增强支持基于JPA的数据访问层,它使我们可以更加容易地构建使用数据访问技术 ...