前言
当我们开发和维护一个复杂的应用程序时,了解应用程序的运行情况变得至关重要。特别是在生产环境中,我们需要追踪应用程序的各个方面,以确保它正常运行并能够及时发现潜在的问题。其中之一关键的方面是记录应用程序的接口访问日志。
Spring Boot是一个流行的Java框架,它使得构建强大的、可伸缩的应用程序变得更加容易。在Spring Boot中,我们可以使用切面(Aspect)来轻松地记录接口访问日志,这将帮助我们跟踪应用程序的运行状况,及时发现问题并提供必要的信息,以便更好地监控和调试我们的应用程序。
本篇博客将深入探讨如何使用Spring Boot的切面功能来记录接口访问日志。我们将介绍什么是切面以及它们在应用程序中的作用,然后逐步展示如何创建一个自定义切面来捕获接口请求和响应的信息,最终将这些信息记录到日志中。通过这个过程,我们将能够实现更好的应用程序监控和故障排除,提高开发和维护的效率。
让我们开始探索如何在Spring Boot中利用切面记录接口访问日志,为我们的应用程序增加更多的可观察性和可维护性。
实现方式
1.准备maven依赖
<!--切面-->
<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.日志实体类
我会通过这个实体类,在数据库中创建一张表,来存储每次访问的记录。
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;
}
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.注解
import java.lang.annotation.*;
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SystemControllerNoLog {
}
4.ignoringUrls
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.日志实现类
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数据展示