SpringBoot+AOP实现记录操作日志和异常日志,并保存到数据库

1. 简介

 

  项目中对日志的收集往往是非常重要的,不仅方便开发人员快速定位问题,而且越来越多的客户需要查询用户行为日志、用户审计日志等。因此,在收集日志时,不仅要考虑功能实现,而且要考虑可靠性、稳定性和不耦合性。
  在每个操作和每个方法都加上日志处理肯定时不现实的,因此使用Spring提供的AOP原理就变得非常方便。定义好切面以及切点之后,可以非常方便的打印、收集或保存日志,不影响业务性能。

2. 初始化数据库

  创建数据库aop,并初始化表结构:

DROP TABLE IF EXISTS `sys_log`;
CREATE TABLE `sys_log` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT 'ID',
  `log_type` varchar(50) NOT NULL COMMENT '日志类型',
  `create_user_code` varchar(64) NOT NULL COMMENT '创建用户编码',
  `create_user_name` varchar(100) NOT NULL COMMENT '创建用户名称',
  `create_date` datetime NOT NULL COMMENT '创建时间',
  `request_uri` varchar(500) DEFAULT NULL COMMENT '请求URI',
  `request_method` varchar(10) DEFAULT NULL COMMENT '请求方式',
  `request_params` text COMMENT '请求参数',
  `request_ip` varchar(20) NOT NULL COMMENT '请求IP',
  `server_address` varchar(50) NOT NULL COMMENT '请求服务器地址',
  `is_exception` char(1) DEFAULT NULL COMMENT '是否异常',
  `exception_info` text COMMENT '异常信息',
  `start_time` datetime NOT NULL COMMENT '开始时间',
  `end_time` datetime NOT NULL COMMENT '结束时间',
  `execute_time` int DEFAULT NULL COMMENT '执行时间',
  `user_agent` varchar(500) DEFAULT NULL COMMENT '用户代理',
  `device_name` varchar(100) DEFAULT NULL COMMENT '操作系统',
  `browser_name` varchar(100) DEFAULT NULL COMMENT '浏览器名称',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `idx_sys_log_lt` (`log_type`) USING BTREE,
  KEY `idx_sys_log_cub` (`create_user_code`) USING BTREE,
  KEY `idx_sys_log_ie` (`is_exception`) USING BTREE,
  KEY `idx_sys_log_cd` (`create_date`) USING BTREE
) COMMENT='系统日志表';

3. 示例代码

  • 创建项目
    SpringBoot+AOP实现记录操作日志和异常日志,并保存到数据库_第1张图片
  • 修改pom.xml

	4.0.0
	com.c3stones
	spring-aop-log-demo
	0.0.1-SNAPSHOT
	spring-aop-log-demo
	Spring Aop Log Demo

	
		org.springframework.boot
		spring-boot-starter-parent
		2.3.4.RELEASE
	

	
		
			mysql
			mysql-connector-java
		
		
			com.baomidou
			mybatis-plus-boot-starter
			3.3.2
		
		
			org.projectlombok
			lombok
			true
		
		
			cn.hutool
			hutool-all
			5.5.1
		
		
			org.springframework.boot
			spring-boot-starter-aop
		
		
			org.springframework.boot
			spring-boot-starter-web
		
		
			org.springframework.boot
			spring-boot-starter-test
			test
		
	

	
		
			
				org.springframework.boot
				spring-boot-maven-plugin
			
		
	
  • 创建配置文件application.yml
server:
   port: 8080
   
spring:
   datasource:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://127.0.0.1:3306/aop?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull
      username: root
      password: 123456
      
# Mybatis-plus配置
mybatis-plus:
   mapper-locations: classpath:mapper/*.xml
   global-config:
      db-config:
         id-type: AUTO
#   configuration:
#      # 打印sql
#      log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  • 创建公共参数类
/**
 * 公共常量类
 * 
 * @author CL
 */
public interface Global {

	/**
	 * 成功标识
	 */
	Boolean TRUE = true;

	/**
	 * 失败标识
	 */
	Boolean FALSE = false;

	/**
	 * 是标识
	 */
	String YES = "1";

	/**
	 * 否标识
	 */
	String NO = "0";

	/**
	 * 日志级别-INFO
	 */
	String LOG_INGO = "INFO";

	/**
	 * 日志级别-DEBUG
	 */
	String LOG_DEBUG = "DEBUG";

	/**
	 * 日志级别-ERROR
	 */
	String LOG_ERROR = "ERROR";

}
  • 创建工具类
import java.io.Serializable;

import com.c3stones.constants.Global;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import lombok.experimental.Accessors;

/**
 * 响应工具类
 *
 * @param 
 * @author CL
 */
@ToString
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class R implements Serializable {

	private static final long serialVersionUID = 1L;

	@Getter
	@Setter
	private Boolean code;

	@Getter
	@Setter
	private String msg;

	@Getter
	@Setter
	private T data;

	public static  R ok() {
		return restResult(null, Global.TRUE, null);
	}

	public static  R ok(T data) {
		return restResult(data, Global.TRUE, null);
	}

	public static  R ok(T data, String msg) {
		return restResult(data, Global.TRUE, msg);
	}

	public static  R failed() {
		return restResult(null, Global.FALSE, null);
	}

	public static  R failed(String msg) {
		return restResult(null, Global.FALSE, msg);
	}

	public static  R failed(T data) {
		return restResult(data, Global.FALSE, null);
	}

	public static  R failed(T data, String msg) {
		return restResult(data, Global.FALSE, msg);
	}

	private static  R restResult(T data, Boolean code, String msg) {
		R apiResult = new R<>();
		apiResult.setCode(code);
		apiResult.setData(data);
		apiResult.setMsg(msg);
		return apiResult;
	}

}
/**
 * 字节转换工具类
 * 
 * @author CL
 */
public class ByteUtils {

	private static final int UNIT = 1024;

	/**
	 * 格式化字节大小
	 * 
	 * @param byteSize 字节大小
	 * @return
	 */
	public static String formatByteSize(long byteSize) {

		if (byteSize <= -1) {
			return String.valueOf(byteSize);
		}

		double size = 1.0 * byteSize;

		String type = "B";
		if ((int) Math.floor(size / UNIT) <= 0) { // 不足1KB
			type = "B";
			return format(size, type);
		}

		size = size / UNIT;
		if ((int) Math.floor(size / UNIT) <= 0) { // 不足1MB
			type = "KB";
			return format(size, type);
		}

		size = size / UNIT;
		if ((int) Math.floor(size / UNIT) <= 0) { // 不足1GB
			type = "MB";
			return format(size, type);
		}

		size = size / UNIT;
		if ((int) Math.floor(size / UNIT) <= 0) { // 不足1TB
			type = "GB";
			return format(size, type);
		}

		size = size / UNIT;
		if ((int) Math.floor(size / UNIT) <= 0) { // 不足1PB
			type = "TB";
			return format(size, type);
		}

		size = size / UNIT;
		if ((int) Math.floor(size / UNIT) <= 0) {
			type = "PB";
			return format(size, type);
		}
		return ">PB";
	}

	/**
	 * 格式化字节大小为指定单位
	 * 
	 * @param size 字节大小
	 * @param type 单位类型
	 * @return
	 */
	private static String format(double size, String type) {
		int precision = 0;

		if (size * 1000 % 10 > 0) {
			precision = 3;
		} else if (size * 100 % 10 > 0) {
			precision = 2;
		} else if (size * 10 % 10 > 0) {
			precision = 1;
		} else {
			precision = 0;
		}

		String formatStr = "%." + precision + "f";

		if ("KB".equals(type)) {
			return String.format(formatStr, (size)) + "KB";
		} else if ("MB".equals(type)) {
			return String.format(formatStr, (size)) + "MB";
		} else if ("GB".equals(type)) {
			return String.format(formatStr, (size)) + "GB";
		} else if ("TB".equals(type)) {
			return String.format(formatStr, (size)) + "TB";
		} else if ("PB".equals(type)) {
			return String.format(formatStr, (size)) + "PB";
		}
		return String.format(formatStr, (size)) + "B";
	}

}
  • 创建线程池配置类  
    ​
    import java.util.concurrent.Executor;
    import java.util.concurrent.ThreadPoolExecutor;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
    
    /**
     * 线程池配置类
     * 
     * @author CL
     *
     */
    @Configuration
    public class ThreadPoolTaskExecutorConfig {
    
    	@Bean
    	public Executor customThreadPoolTaskExecutor() {
    		ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    		// Java虚拟机可用的处理器数
    		int corePoolSize = Runtime.getRuntime().availableProcessors();
    		// 配置核心线程数
    		executor.setCorePoolSize(corePoolSize);
    		// 配置最大线程数
    		executor.setMaxPoolSize(corePoolSize * 2 + 1);
    		// 配置队列大小
    		executor.setQueueCapacity(100);
    		// 空闲的多余线程最大存活时间
    		executor.setKeepAliveSeconds(3);
    		// 配置线程池中的线程的名称前缀
    		executor.setThreadNamePrefix("thread-execute-");
    		// 当线程池达到最大大小时,在调用者的线程中执行任务
    		executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
    		// 执行初始化
    		executor.initialize();
    		return executor;
    	}
    }
    
    ​
  • 创建实体类
import java.io.Serializable;
import java.time.LocalDateTime;

import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;

import lombok.Data;

/**
 * 系统用户
 * 
 * @author CL
 *
 */
@Data
@TableName("sys_log")
public class SysLog implements Serializable {
	private static final long serialVersionUID = 1L;

	/**
	 * 用户ID
	 */
	@TableId
	private Long id;

	/**
	 * 日志类型
	 */
	private String logType;

	/**
	 * 创建用户编码
	 */
	private String createUserCode;

	/**
	 * 创建用户名称
	 */
	private String createUserName;

	/**
	 * 创建时间
	 */
	private LocalDateTime createDate;

	/**
	 * 请求URI
	 */
	private String requestUri;

	/**
	 * 请求方式
	 */
	private String requestMethod;

	/**
	 * 请求参数
	 */
	private String requestParams;

	/**
	 * 请求IP
	 */
	private String requestIp;

	/**
	 * 请求服务器地址
	 */
	private String serverAddress;

	/**
	 * 是否异常
	 */
	private String isException;

	/**
	 * 异常信息
	 */
	private String exceptionInfo;

	/**
	 * 开始时间
	 */
	private LocalDateTime startTime;

	/**
	 * 结束时间
	 */
	private LocalDateTime endTime;

	/**
	 * 执行时间
	 */
	private Long executeTime;

	/**
	 * 用户代理
	 */
	private String userAgent;

	/**
	 * 操作系统
	 */
	private String deviceName;

	/**
	 * 浏览器名称
	 */
	private String browserName;

}
  • 创建Mapper
import org.apache.ibatis.annotations.Mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.c3stones.entity.SysLog;

/**
 * 系统日志Dao
 * 
 * @author CL
 *
 */
@Mapper
public interface SysLogDao extends BaseMapper {

}
  • 创建Service
import com.baomidou.mybatisplus.extension.service.IService;
import com.c3stones.entity.SysLog;

/**
 * 系统日志Service
 * 
 * @author CL
 *
 */
public interface SysLogService extends IService {

}
  • 创建Service实现
import org.springframework.stereotype.Service;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.c3stones.dao.SysLogDao;
import com.c3stones.entity.SysLog;
import com.c3stones.service.SysLogService;

/**
 * 系统日志Service实现
 * 
 * @author CL
 *
 */
@Service
public class SysLogServiceImpl extends ServiceImpl implements SysLogService {

}
  • 创建日志访问Controller
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.c3stones.entity.SysLog;
import com.c3stones.service.SysLogService;
import com.c3stones.utils.R;

/**
 * 系统日志Controller
 * 
 * @author CL
 *
 */
@RestController
@RequestMapping(value = "/sys/log")
public class SysLogController {

	@Autowired
	private SysLogService sysLogService;

	/**
	 * 日志分页查询
	 * 
	 * @param start  起始页码
	 * @param limit  每页数量
	 * @param sysLog 系统日志
	 * @return
	 */
	@RequestMapping(value = "page")
	public R> page(int start, int limit, SysLog sysLog) {
		QueryWrapper queryWrapper = new QueryWrapper<>(sysLog);
		queryWrapper.orderByDesc("create_date");
		Page page = sysLogService.page(new Page<>(start, limit), queryWrapper);
		return R.ok(page);
	}

}
  • 创建日志切面
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.Executor;

import javax.servlet.http.HttpServletRequest;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import com.c3stones.constants.Global;
import com.c3stones.entity.SysLog;
import com.c3stones.service.SysLogService;
import com.c3stones.utils.ByteUtils;
import com.c3stones.utils.R;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import cn.hutool.extra.servlet.ServletUtil;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import lombok.extern.log4j.Log4j2;

/**
 * 系统日志切面
 * 
 * @author CL
 *
 */
@Log4j2
@Aspect
@Component
public class SysLogAspect {

	private ThreadLocal sysLogThreadLocal = new ThreadLocal<>();

	@Autowired
	private Executor customThreadPoolTaskExecutor;

	@Autowired
	private SysLogService sysLogService;

	/**
	 * 日志切点
	 */
	@Pointcut("execution(public * com.c3stones.controller.*.*(..))")
	public void sysLogAspect() {
	}

	/**
	 * 前置通知
	 * 
	 * @param joinPoint
	 * @throws Throwable
	 */
	@Before(value = "sysLogAspect()")
	public void doBefore(JoinPoint joinPoint) throws Throwable {
		HttpServletRequest request = ((ServletRequestAttributes) Objects
				.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();

		SysLog sysLog = new SysLog();
		// 创建人信息请根据实际项目获取方式获取
		sysLog.setCreateUserCode("");
		sysLog.setCreateUserName("");
		sysLog.setStartTime(LocalDateTime.now());
		sysLog.setRequestUri(URLUtil.getPath(request.getRequestURI()));
		sysLog.setRequestParams(formatParams(request.getParameterMap()));
		sysLog.setRequestMethod(request.getMethod());
		sysLog.setRequestIp(ServletUtil.getClientIP(request));
		sysLog.setServerAddress(request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort());
		String userAgentStr = request.getHeader("User-Agent");
		sysLog.setUserAgent(userAgentStr);
		UserAgent userAgent = UserAgentUtil.parse(userAgentStr);
		sysLog.setDeviceName(userAgent.getOs().getName());
		sysLog.setBrowserName(userAgent.getBrowser().getName());

		sysLogThreadLocal.set(sysLog);

		log.info("开始计时: {}  URI: {}  IP: {}", sysLog.getStartTime(), sysLog.getRequestUri(), sysLog.getRequestIp());
	}

	/**
	 * 返回通知
	 * 
	 * @param ret
	 */
	@AfterReturning(pointcut = "sysLogAspect()", returning = "ret")
	public void doAfterReturning(Object ret) {
		SysLog sysLog = sysLogThreadLocal.get();
		sysLog.setLogType(Global.LOG_INGO);
		sysLog.setEndTime(LocalDateTime.now());
		sysLog.setExecuteTime(Long.valueOf(ChronoUnit.MILLIS.between(sysLog.getStartTime(), sysLog.getEndTime())));
		R r = Convert.convert(R.class, ret);
		if (r.getCode() == Global.TRUE) {
			sysLog.setIsException(Global.NO);
		} else {
			sysLog.setIsException(Global.YES);
			sysLog.setExceptionInfo(r.getMsg());
		}
		customThreadPoolTaskExecutor.execute(new SaveLogThread(sysLog, sysLogService));
		sysLogThreadLocal.remove();

		Runtime runtime = Runtime.getRuntime();
		log.info("计时结束: {}  用时: {}ms  URI: {}  总内存: {}  已用内存: {}", sysLog.getEndTime(), sysLog.getExecuteTime(),
				sysLog.getRequestUri(), ByteUtils.formatByteSize(runtime.totalMemory()),
				ByteUtils.formatByteSize(runtime.totalMemory() - runtime.freeMemory()));
	}

	/**
	 * 异常通知
	 * 
	 * @param e
	 */
	@AfterThrowing(pointcut = "sysLogAspect()", throwing = "e")
	public void doAfterThrowable(Throwable e) {
		SysLog sysLog = sysLogThreadLocal.get();
		sysLog.setLogType(Global.LOG_ERROR);
		sysLog.setEndTime(LocalDateTime.now());
		sysLog.setExecuteTime(Long.valueOf(ChronoUnit.MINUTES.between(sysLog.getStartTime(), sysLog.getEndTime())));
		sysLog.setIsException(Global.YES);
		sysLog.setExceptionInfo(e.getMessage());
		customThreadPoolTaskExecutor.execute(new SaveLogThread(sysLog, sysLogService));
		sysLogThreadLocal.remove();
		
		Runtime runtime = Runtime.getRuntime();
		log.info("计时结束: {}  用时: {}ms  URI: {}  总内存: {}  已用内存: {}", sysLog.getEndTime(), sysLog.getExecuteTime(),
				sysLog.getRequestUri(), ByteUtils.formatByteSize(runtime.totalMemory()),
				ByteUtils.formatByteSize(runtime.totalMemory() - runtime.freeMemory()));
	}

	/**
	 * 格式化参数
	 * 
	 * @param parameterMap
	 * @return
	 */
	private String formatParams(Map parameterMap) {
		if (parameterMap == null) {
			return null;
		}
		StringBuilder params = new StringBuilder();
		for (Map.Entry param : (parameterMap).entrySet()) {
			if (params.length() != 0) {
				params.append("&");
			}
			params.append(param.getKey() + "=");
			if (StrUtil.endWithIgnoreCase(param.getKey(), "password")) {
				params.append("*");
			} else if (param.getValue() != null) {
				params.append(ArrayUtil.join(param.getValue(), ","));
			}
		}
		return params.toString();
	}

	/**
	 * 保存日志线程
	 * 
	 * @author CL
	 *
	 */
	private static class SaveLogThread extends Thread {
		private SysLog sysLog;
		private SysLogService sysLogService;

		public SaveLogThread(SysLog sysLog, SysLogService sysLogService) {
			this.sysLog = sysLog;
			this.sysLogService = sysLogService;
		}

		@Override
		public void run() {
			sysLog.setCreateDate(LocalDateTime.now());
			sysLogService.save(sysLog);
		}
	}
}
  • 创建示例Controller
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.c3stones.utils.R;

/**
 * 示例Controller
 * 
 * @author CL
 *
 */
@RestController
public class DemoController {

	/**
	 * 示例方法1
	 * 
	 * @return
	 */
	@RequestMapping(value = "demo1")
	public R demo1(String str) {
		return R.ok("成功返回 -> " + str);
	}

	/**
	 * 示例方法2
	 * 
	 * @return
	 */
	@RequestMapping(value = "demo2")
	public R demo2(String str, int num) {
		return R.failed("失败返回");
	}

	/**
	 * 示例方法3
	 * 
	 * @return
	 */
	@SuppressWarnings("unused")
	@RequestMapping(value = "demo3")
	public R demo3() {

		int a = 1 / 0;

		return R.ok("模拟异常");
	}

}
  • 创建启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * 启动类
 * 
 * @author CL
 *
 */
@SpringBootApplication
public class Application {

	public static void main(String[] args) {
		SpringApplication.run(Application.class);
	}

}

4. 测试

  • 访问接口demo1:http://127.0.0.1:8080/demo1?str=demo1
# 接口返回:
{
	"code": true,
	"msg": null,
	"data": "成功返回 -> demo1"
}

# 控制台打印
2021-07-04 14:45:12.077  INFO 16032 --- [nio-8080-exec-4] com.c3stones.aspect.SysLogAspect         : 开始计时: 2021-07-04T14:45:12.076  URI: /demo1  IP: 127.0.0.1
2021-07-04 14:45:12.077  INFO 16032 --- [nio-8080-exec-4] com.c3stones.aspect.SysLogAspect         : 计时结束: 2021-07-04T14:45:12.077  用时: 1ms  URI: /demo1  总内存: 323.5MB  已用内存: 70.332MB
  • 访问接口demo2:http://127.0.0.1:8080/demo2?str=demo2&num=10
# 接口返回:
{
	"code": false,
	"msg": "失败返回",
	"data": null
}

# 控制台打印
2021-07-04 14:48:57.795  INFO 16032 --- [nio-8080-exec-6] com.c3stones.aspect.SysLogAspect         : 开始计时: 2021-07-04T14:48:57.794  URI: /demo2  IP: 127.0.0.1
2021-07-04 14:48:57.796  INFO 16032 --- [nio-8080-exec-6] com.c3stones.aspect.SysLogAspect         : 计时结束: 2021-07-04T14:48:57.795  用时: 1ms  URI: /demo2  总内存: 323.5MB  已用内存: 72.496MB
  • 访问接口demo3:http://127.0.0.1:8080/demo3
# 接口返回:
异常无返回

# 控制台打印
2021-07-04 14:49:30.260  INFO 16032 --- [nio-8080-exec-7] com.c3stones.aspect.SysLogAspect         : 开始计时: 2021-07-04T14:49:30.260  URI: /demo3  IP: 127.0.0.1
2021-07-04 14:49:30.261  INFO 16032 --- [nio-8080-exec-7] com.c3stones.aspect.SysLogAspect         : 计时结束: 2021-07-04T14:49:30.261  用时: 0ms  URI: /demo3  总内存: 323.5MB  已用内存: 72.861MB
2021-07-04 14:49:30.272 ERROR 16032 --- [nio-8080-exec-7] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.ArithmeticException: / by zero] with root cause

java.lang.ArithmeticException: / by zero
	......
  • 访问日志分页接口:http://127.0.0.1:8080/sys/log/page?start=0&limit=10
# 接口返回:
{
	"code": true,
	"msg": null,
	"data": {
		"records": [
			{
				"id": 3,
				"logType": "ERROR",
				"createUserCode": "",
				"createUserName": "",
				"createDate": "2021-07-04T14:49:30",
				"requestUri": "/demo3",
				"requestMethod": "GET",
				"requestParams": "",
				"requestIp": "127.0.0.1",
				"serverAddress": "http://127.0.0.1:8080",
				"isException": "1",
				"exceptionInfo": "/ by zero",
				"startTime": "2021-07-04T14:49:30",
				"endTime": "2021-07-04T14:49:30",
				"executeTime": 0,
				"userAgent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36",
				"deviceName": "Windows 10 or Windows Server 2016",
				"browserName": "Chrome"
			},
			
			{
				"id": 2,
				"logType": "INFO",
				"createUserCode": "",
				"createUserName": "",
				"createDate": "2021-07-04T14:48:58",
				"requestUri": "/demo2",
				"requestMethod": "GET",
				"requestParams": "str=demo2&num=10",
				"requestIp": "127.0.0.1",
				"serverAddress": "http://127.0.0.1:8080",
				"isException": "1",
				"exceptionInfo": "失败返回",
				"startTime": "2021-07-04T14:48:58",
				"endTime": "2021-07-04T14:48:58",
				"executeTime": 1,
				"userAgent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36",
				"deviceName": "Windows 10 or Windows Server 2016",
				"browserName": "Chrome"
			},
			{
				"id": 1,
				"logType": "INFO",
				"createUserCode": "",
				"createUserName": "",
				"createDate": "2021-07-04T14:45:12",
				"requestUri": "/demo1",
				"requestMethod": "GET",
				"requestParams": "str=demo1",
				"requestIp": "127.0.0.1",
				"serverAddress": "http://127.0.0.1:8080",
				"isException": "0",
				"exceptionInfo": null,
				"startTime": "2021-07-04T14:45:12",
				"endTime": "2021-07-04T14:45:12",
				"executeTime": 1,
				"userAgent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36",
				"deviceName": "Windows 10 or Windows Server 2016",
				"browserName": "Chrome"
			}
		],
		"total": 0,
		"size": 10,
		"current": 1,
		"orders": [],
		"optimizeCountSql": true,
		"hitCount": false,
		"searchCount": true,
		"pages": 0
	}
}

5. 项目地址

  spring-aop-log-demo

你可能感兴趣的:(java,spring,分布式,spring,boot,mysql)