前言

大型完善项目中肯定是需要一个全局日志拦截,记录每次接口访问相关信息,包括:

访问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日志使用请参考我这两篇文章

SpringBoot日志详细使用和配置

JAVA 中日志的记录于使用

关注公众号猿小叔获取更多干货分享

个人博客开发之blog-api 项目全局日志拦截记录的更多相关文章

  1. 个人博客开发之xadmin 布局和后台样式

    项目源码下载:http://download.vhosts.cn 一. xadmin 后台配置注册信息 1. 在apps 的blogs 和 users 两个app中添加adminx.py文件 vim ...

  2. 个人博客开发之blog-api项目统一结果集api封装

    前言 由于返回json api 格式接口,所以我们需要通过java bean封装一个统一数据返回格式,便于和前端约定交互, 状态码枚举ResultCode package cn.soboys.core ...

  3. 个人博客开发之blog-api 项目整合JWT实现token登录认证

    前言 现在前后端分离,基于session设计到跨越问题,而且session在多台服器之前同步问题,肯能会丢失,所以倾向于使用jwt作为token认证 json web token 导入java-jwt ...

  4. SpringBoot博客开发之AOP日志处理

    日志处理: 需求分析 日志处理需要记录的是: 请求的URL 访问者IP 调用的方法 传入的参数 返回的内容 上面的内容要求在控制台和日志中输出. 在学习这部分知识的时候,真的感觉收获很多,在之前Spr ...

  5. 个人博客开发之xadmin与ueditor集成

    项目源码下载:http://download.vhosts.cn 1. xadmin 添加ueditor 插件 vim extra_apps\xadmin\plugins\ueditor.py #没有 ...

  6. 个人博客开发之 xadmin 安装

    项目源码下载:http://download.vhosts.cn xadmin 下载地址:https://github.com/sshwsfc/xadmin或 https://github.com/s ...

  7. 个人博客开发之 ueditor 安装

  8. iOS开发之MVVM在项目中的应用

    今天写这篇博客是想达到抛砖引玉的作用,想与大家交流一下思想,相互学习,博文中有不足之处还望大家批评指正.本篇博客的内容沿袭以往博客的风格,也是以干货为主,偶尔扯扯咸蛋(哈哈~不好好工作又开始发表博客啦 ...

  9. 文顶顶iOS开发博客链接整理及部分项目源代码下载

    文顶顶iOS开发博客链接整理及部分项目源代码下载   网上的iOS开发的教程很多,但是像cnblogs博主文顶顶的博客这样内容图文并茂,代码齐全,示例经典,原理也有阐述,覆盖面宽广,自成系统的系列教程 ...

随机推荐

  1. 性能调优命令之jstack

    jstack是java虚拟机自带的一种线程堆栈跟踪工具. /opt/java8/bin/jstack Usage: jstack [-l] <pid> (to connect to run ...

  2. 日常Bug排查-抛异常不回滚

    日常Bug排查-抛异常不回滚 前言 日常Bug排查系列都是一些简单Bug排查,笔者将在这里介绍一些排查Bug的简单技巧,同时顺便积累素材_. Bug现场 最近有人反映java应用操作数据库的时候,抛异 ...

  3. Python+Selenium - 键盘操作

    导包代码: from selenium.webdriver.common.keys import Keys 用法: driver.find_element("id","k ...

  4. ADAS测试

    ADAS测试 1.  ADAS​和​自动​驾驶​测试 AD​和​高级​驾驶​辅助​系统​(ADAS)​正在​不断​增加​新的​雷达.​摄像​头.​激光​雷达​和​GNSS​传感器,​甚至​也在​改变​ ...

  5. 编译器架构Compiler Architecture(下)

    编译器架构Compiler Architecture(下) Combining Scanning and Parsing 实际上没有必要将扫描(词法分析/标记化)与解析(语法分析/树生成)分开.基于P ...

  6. 一文教会你认识Vuex状态机

    摘要:简单来说,Vuex就是实现组件全局状态(数据)管理的一种机制,可以方便的实现组件之间数据的共享. 本文分享自华为云社区<Vuex状态机快速了解与应用>,原文作者:北极光之夜. 一. ...

  7. 《精通 ASP.NET Core MVC (第七版)》开始发售

    学习 Web 开发技术很难吗?没有适合的学习资料,确实很枯燥,很难.如果有一本如同良师益友的优秀图书辅助,就很轻松,一点也不难! 对于优秀的技术图书来说,必须从读者的角度来编写,而不是从作者的角度来编 ...

  8. UF_VIEW 视图操作

    Open C   uc6400uc6401uc6402uc6403uc6404uc6405uc6406uc6408uc6409uc6430uc6431uc6432uc6433  获得视图3×3矩阵uc ...

  9. Django基础之视图层

    内容概要 小白必会三板斧 request对象方法初识 form表单上传文件 Jsonresponse FBV与CBV 内容详细 1 小白必会三板斧 HttpResponse render redire ...

  10. SpringBoot数据访问(二) SpringBoot整合JPA

    JPA简介 Spring Data JPA是Spring Data大家族的一部分,它可以轻松实现基于JPA的存储库.该模块用于增强支持基于JPA的数据访问层,它使我们可以更加容易地构建使用数据访问技术 ...