springboot使用切面记录接口访问日志

前言

当我们开发和维护一个复杂的应用程序时,了解应用程序的运行情况变得至关重要。特别是在生产环境中,我们需要追踪应用程序的各个方面,以确保它正常运行并能够及时发现潜在的问题。其中之一关键的方面是记录应用程序的接口访问日志。

Spring Boot是一个流行的Java框架,它使得构建强大的、可伸缩的应用程序变得更加容易。在Spring Boot中,我们可以使用切面(Aspect)来轻松地记录接口访问日志,这将帮助我们跟踪应用程序的运行状况,及时发现问题并提供必要的信息,以便更好地监控和调试我们的应用程序。

本篇博客将深入探讨如何使用Spring Boot的切面功能来记录接口访问日志。我们将介绍什么是切面以及它们在应用程序中的作用,然后逐步展示如何创建一个自定义切面来捕获接口请求和响应的信息,最终将这些信息记录到日志中。通过这个过程,我们将能够实现更好的应用程序监控和故障排除,提高开发和维护的效率。

让我们开始探索如何在Spring Boot中利用切面记录接口访问日志,为我们的应用程序增加更多的可观察性和可维护性。

实现方式

1.准备maven依赖

sql 复制代码
   		<!--切面-->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>1.8.9</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.9</version>
        </dependency>

2.日志实体类

我会通过这个实体类,在数据库中创建一张表,来存储每次访问的记录。

java 复制代码
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;

@Data
@EqualsAndHashCode(callSuper = false)
@ApiModel(value="SLogOperatorall对象", description="")
public class SLogOperatorall implements Serializable {

    private static final long serialVersionUID = 1L;

    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;

    private String module;

    private String type;

    private String description;

    private String uri;

    private String method;

    private String sessionId;

    private String requestId;

    private String params;

    private String createBy;

    @JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
    private String createDate;

    private String beanName;

    private String beanMethod;

    @JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
    private String beginTime;

    @JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
    private String endTime;

    private String exceptionCode;

    private String exceptionDetail;

    private long requestTime;

    private String result;

    private String url;

    private String osInfo;

    private String browserInfo;

    private String requestParams;


}
sql 复制代码
DROP TABLE IF EXISTS `s_log_operatorall`;
CREATE TABLE `s_log_operatorall`  (
  `id` int(32) NOT NULL AUTO_INCREMENT,
  `module` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `type` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `description` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `uri` varchar(1024) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `method` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `session_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `request_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `params` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `create_by` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `create_date` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `bean_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `bean_method` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `begin_time` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `end_time` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `exception_code` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `exception_detail` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `request_time` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `result` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL,
  `url` varchar(1024) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `os_info` varchar(1024) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `browser_info` varchar(1024) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `request_params` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 230 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;

SET FOREIGN_KEY_CHECKS = 1;

3.注解

sql 复制代码
import java.lang.annotation.*;


@Target({ElementType.PARAMETER, ElementType.METHOD})    
@Retention(RetentionPolicy.RUNTIME)    
@Documented    
public  @interface SystemControllerNoLog {    
}  

4.ignoringUrls

sql 复制代码
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

@Configuration
@ConfigurationProperties(prefix = "applogs")
public class AppLogsConfiguration {
	
	private String ignoringUrls = "/;/sitemids;/sitemesh;";
	
	public String getIgnoringUrls() {
		return ignoringUrls;
	}
	public void setIgnoringUrls(String ignoringUrls) {
		this.ignoringUrls = ignoringUrls;
	}
}

5.日志实现类

java 复制代码
import com.alibaba.fastjson.JSON;
import com.zl.sys.controller.config.jwt.JwtConfig;
import com.zl.sys.entity.SLogOperatorall;
import com.zl.sys.service.SLogOperatorallService;
import com.zl.utils.DateUtils;
import com.zl.utils.RequestUtil;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.ui.Model;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.ModelAndView;

import javax.annotation.Resource;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;
import java.util.Map;

@SuppressWarnings("all")
@Aspect
@Component
public class ControllerLogAspect {
	private static final Logger logger = LoggerFactory.getLogger(ControllerLogAspect.class);
	@Resource
	protected SLogOperatorallService operatorLogAllService;
	@Resource
	protected AppLogsConfiguration appLogsConfiguration;
	@Resource
	private JwtConfig jwtConfig;

	private static ThreadLocal<SLogOperatorall> tlocal = new ThreadLocal<SLogOperatorall>();

	@Pointcut("(@annotation(org.springframework.web.bind.annotation.RequestMapping) || @annotation(org.springframework.web.bind.annotation.GetMapping) || @annotation(org.springframework.web.bind.annotation.PostMapping)) && !@annotation(com.zl.sys.controller.config.log.SystemControllerNoLog)")
	public void controllerAspect() {
	}

	/**
	 * 前置通知 用于拦截Controller层记录用户的操作
	 * 
	 * @param joinPoint
	 *            切点
	 */
	@Before("controllerAspect()")
	public void doBefore(JoinPoint joinPoint) {
		HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
				.getRequest();
		String uri = request.getRequestURI().replaceFirst(request.getContextPath(), "");
		if (appLogsConfiguration.getIgnoringUrls().indexOf(uri + ";") >= 0) {
			tlocal.set(null);
			return;
		}
		if (StringUtils.isNotEmpty(request.getRequestURL())){
			String url = request.getRequestURL().toString();
			String userName = jwtConfig.getUserName(request);
			String sessionId = request.getSession().getId();

			String ip = getIpAddr(request);
			String method = request.getMethod();
			// 请求的IP
			String params = "";
			if ("POST".equals(method)) {
				Object[] paramsArray = joinPoint.getArgs();
				params = argsArrayToString(paramsArray);
			} else {
				Map<?, ?> paramsMap = (Map<?, ?>) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
				params = paramsMap.toString();
			}
			try {
				SLogOperatorall log = new SLogOperatorall();
				log.setUrl(url);
				log.setUri(uri);
				log.setMethod(method);
				log.setBeanName(joinPoint.getTarget().getClass().getName());
				log.setBeanMethod(joinPoint.getSignature().getName() + "");
				log.setRequestId(ip);
				log.setExceptionCode(null);
				log.setExceptionDetail(null);
				log.setParams(params);
				log.setBeginTime(DateUtils.getISODateTime(new Date()));
				log.setRequestTime(System.currentTimeMillis());
				log.setSessionId(sessionId);
				log.setCreateBy(userName);
				log.setCreateDate(DateUtils.getDateTime());
				log.setOsInfo(RequestUtil.getOsInfo(request));
				log.setBrowserInfo(RequestUtil.getBrowserInfo(request));
				log.setRequestParams(JSON.toJSON(request.getParameterMap()).toString());
				tlocal.set(log);
			} catch (Exception e) {
				logger.error("==前置通知异常==");
				logger.error("异常信息:{}", e.getMessage());
			}
		}

	}

	@AfterReturning(returning = "result", pointcut = "controllerAspect()")
	public void doAfterReturning(Object result) {
		try {
			// 处理完请求,返回内容
			SLogOperatorall optLog = tlocal.get();
			if (optLog != null) {
				String resultMsg = ObjectUtils.toString(result, "");
				if (StringUtils.isNotEmpty(resultMsg)){
					String resultString = getResultString(result);
					if (resultString.length()>6000){
						optLog.setResult("结果集长度过大");
					}else {
						optLog.setResult(resultString);
					}
					long beginTime = optLog.getRequestTime();
					long requestTime = (System.currentTimeMillis() - beginTime);
					optLog.setRequestTime(requestTime);
					optLog.setEndTime(DateUtils.getISODateTime(new Date()));
					logger.info("Uri: " + optLog.getUri() + "请求耗时:" + optLog.getRequestTime());
					operatorLogAllService.save(optLog);
				}
			}

		} catch (Exception e) {
			logger.error("***操作请求日志记录失败doAfterReturning()***", e);
		}
	}

	@AfterThrowing(throwing = "ex", pointcut = "controllerAspect()")
	public void doAfterThrowing(Throwable ex) {
		try {
			// 处理完请求,返回内容
			SLogOperatorall optLog = tlocal.get();
			if (optLog!=null){
				long beginTime = optLog.getRequestTime();
				long requestTime = (System.currentTimeMillis() - beginTime);
				optLog.setRequestTime(requestTime);
				optLog.setExceptionCode("");
				optLog.setExceptionDetail(ex.getMessage());
				optLog.setEndTime(DateUtils.getISODateTime(new Date()));
				logger.info("Uri: " + optLog.getUri() + "请求异常------------耗时:" + optLog.getRequestTime());
				operatorLogAllService.save(optLog);
			}
		} catch (Exception e) {
			logger.error("***操作请求日志记录失败doAfterReturning()***", e);
		}
	}

	/**
	 * 获取登录用户远程主机ip地址
	 * 
	 * @param request
	 * @return
	 */
	private String getIpAddr(HttpServletRequest request) {
		return RequestUtil.getIp(request);
	}

	/**
	 * 请求参数拼装
	 * 
	 * @param paramsArray
	 * @return
	 */
	private String argsArrayToString(Object[] paramsArray) {
		String params = "";
		if (paramsArray != null && paramsArray.length > 0) {
			for (int i = 0; i < paramsArray.length; i++) {
				Object obj = paramsArray[i];

				if (obj instanceof ServletRequest) {
					
				} else if (obj instanceof HttpServletResponse) {

				} else if (obj instanceof MultipartFile) {

				} else if (obj instanceof Model) {

				} else if (obj instanceof ModelAndView) {

				} else {
					try {
						if (obj != null && !"".equals(obj)) {
							Object jsonObj = JSON.toJSON(obj);
							params += jsonObj.toString() + ";";
						}
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
			}
		}
		return params.trim();
	}

	private String getResultString(Object result) {
		if (result == null) {

		}
		if (result instanceof String) {
			return (String) result;
		} else {
			try {
				Object jsonObj = JSON.toJSON(result);
				return jsonObj.toString();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		return null;
	}
}

6.效果展示

6.1每个接口的请求耗时

6.2数据展示

相关推荐
Zilliz Planet5 分钟前
Milvus×EasyAi:如何用java从零搭建人脸识别应用
java·开发语言·milvus
天天进步201512 分钟前
Java全栈项目 - 汽车维修服务管理平台
java·开发语言·汽车
虾球xz21 分钟前
游戏引擎学习第61天
java·学习·游戏引擎
CodeClimb21 分钟前
【华为OD-E卷-租车骑绿道 100分(python、java、c++、js、c)】
java·javascript·c++·python·华为od
梓沂23 分钟前
idea配置gitee仓库
java·gitee·intellij-idea
CodeClimb24 分钟前
【华为OD-E卷-MVP争夺战 100分(python、java、c++、js、c)】
java·python·华为od
大大怪将军~~~~33 分钟前
SpringBoot 入门
java·spring boot·后端
编码浪子35 分钟前
Springboot3国际化
java·spring·mybatis
HUNAG-DA-PAO43 分钟前
Spring AOP是什么
java·jvm·spring
带刺的坐椅1 小时前
Solon v3.0.5 发布!(Spring 生态可以退休了吗?)
java·spring·solon