个人博客开发之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博主文顶顶的博客这样内容图文并茂,代码齐全,示例经典,原理也有阐述,覆盖面宽广,自成系统的系列教程 ...
随机推荐
- Python保留指定位数的小数
Python保留指定位数的小数 1 '%.2f' %f 方法(推荐) f = 1.23456 print('%.4f' % f) print('%.3f' % f) print('%.2f' % f) ...
- .NET平台系列17 .NET5中的ARM64性能
系列目录 [已更新最新开发文章,点击查看详细] .NET团队使.NET 5大大提高了常规性能和ARM64性能.在<.NET5中的性能改进>博客中可以查看总体改进情况.在这篇文章中, ...
- TensorFlow创建DeepDream网络
TensorFlow创建DeepDream网络 Google 于 2014 年在 ImageNet 大型视觉识别竞赛(ILSVRC)训练了一个神经网络,并于 2015 年 7 月开放源代码. 该网络学 ...
- 【NX二次开发】NX内部函数,libuifw.dll文件中的内部函数
本文分为两部分:"带参数的函数"和 "带修饰的函数". 浏览这篇博客前请先阅读: [NX二次开发]NX内部函数,查找内部函数的方法 带参数的函数: void U ...
- 彻底搞懂彻底搞懂事件驱动模型 - Reactor
在高性能网络技术中,大家应该经常会看到Reactor模型.并且很多开源软件中都使用了这个模型,如:Redis.Nginx.Memcache.Netty等. 刚开始接触时可能一头雾水,这到底是个什么东东 ...
- 有了Java8的“+”真的可以不要StringBuilder了吗
最近在头条上看到一篇帖子,说Java8开始,字符串拼接时,"+"会被编译成StringBuilder,所以,字符串的连接操作不用再考虑效率问题了,事实真的是这样吗?要搞明白,还是要 ...
- 「模拟8.29」chinese(性质)·physics·chemistry(概率期望)
T1 chinese 根据他的问题i*f[i]我们容易联想到,答案其实是每种方案中每个点的贡献为1的加和 我们可以转变问题,每个点在所有方案的贡献 进而其实询问就是1-k的取值,有多少中方案再取个和 ...
- 连接过的WiFi改了密码之后再次连接不让输入新密码还是用旧密码一直显示连接失败
设置---网络和Internet---WLAN----管理已知网络----忘记 根据这个步骤就能忘记密码,重新输入新密码了.
- C#winform的Richtextbox控件实现自动滚动到最后一行功能
这里有两种情况 如果是采用的是richtextbox的AppendText的方法添加的内容,则只需 设置HideSelection为false 如果采用的是其他添加内容的方法,则需要添加TextCha ...
- ANDROID开发之OOM:一张图片(BitMap)占用内存的计算 图片内存优化
Android中一张图片(BitMap)占用的内存主要和以下几个因数有关:图片长度,图片宽度,单位像素占用的字节数. 一张图片(BitMap)占用的内存=图片长度*图片宽度*单位像素占用的字节数 注: ...