切面通用父类
这里实现抽象日志记录类
,里面写好了对请求的信息(url、方法、请求头信息、参数)等信息,并对执行的controller的方法名、方法参数值进行记录,无论方法执行完成或者执行异常都会有相应的记录输出。也可以通用继承这个·抽象类·来实现自定义的日志记录。这里主要用了环绕通知及异常通知。里面使用了自定义注解
,用于对当前执行的方法进行说明,方便日志观察。抽象类中的方法已经写好了切面能得到的所有信息,可以根据需要个性化定制。
package com.langangkj.home.cloud.common.aspect;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.langangkj.home.cloud.common.annotation.MethodLogDesc;
import com.langangkj.home.cloud.common.util.RequestUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.Ordered;
import org.springframework.util.MultiValueMap;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import javax.servlet.http.HttpServletRequest;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Enumeration;
import java.util.Map;
/**
* @author ZhouChuGang
* @version 1.0
* @project ImageWeb
* @date 2019/11/30 17:56
* @Description 切面实现日志记录 记录每次请求的信息 抽象类 方便子类重写
*/
@Slf4j
public abstract class AbstractWebLogAspect {
@Autowired
private ObjectMapper objectMapper;
/**
* 切入点
* 子类可以重写
*/
@Pointcut(
"@annotation(org.springframework.web.bind.annotation.RequestMapping) || " +
"@annotation(org.springframework.web.bind.annotation.GetMapping)||" +
"@annotation(com.langangkj.home.cloud.common.annotation.MethodLogDesc) || " +
"@annotation(org.springframework.web.bind.annotation.PostMapping)"
)
protected void webLog() {
}
/**
* 得到当前请求需要记录的信息
*
* @param request
* @return
*/
protected String getHttpServletRequestLogInfoStr(HttpServletRequest request) {
// 记录下请求内容
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(String.format("URL:%s, HTTP_METHOD:%s, IP:%s \n", RequestUtil.getRequestUrl(), request.getMethod(), RequestUtil.getIpAddr(request)));
Enumeration<String> headerNames = request.getHeaderNames();
if (headerNames != null) {
StringBuilder headStr = new StringBuilder();
while (headerNames.hasMoreElements()) {
String name = headerNames.nextElement();
String header = request.getHeader(name);
headStr.append(name).append(":").append(header).append("\n");
}
stringBuilder.append(String.format("header: %s", headStr.toString()));
}
Map<String, String[]> parameterMap = request.getParameterMap();
if (parameterMap != null && !parameterMap.isEmpty()) {
StringBuilder strBuilder = new StringBuilder();
parameterMap.forEach((key, value) -> {
try {
strBuilder.append(key).append("=").append(objectMapper.writeValueAsString(value)).append(";");
} catch (JsonProcessingException e) {
//忽略异常
}
});
if (strBuilder.length() > 0) {
stringBuilder.append("请求参数:----").append(strBuilder.toString());
}
}
//这里得到body里面的数据
if (request instanceof MultipartHttpServletRequest) {
stringBuilder.append("\n可能存在文件上传:");
MultipartHttpServletRequest multipartHttpServletRequest = (MultipartHttpServletRequest) request;
MultiValueMap<String, MultipartFile> multiFileMap = multipartHttpServletRequest.getMultiFileMap();
stringBuilder.append("[");
multiFileMap.forEach((k, v) -> {
stringBuilder.append("文件名:" + k).append(",文件大小:").append(v.size()).append(";");
});
stringBuilder.append("]");
}
return stringBuilder.toString();
}
/**
* 得到当前代理方法 需要记录的信息
* 方便用于子类重写
*
* @param joinPoint
* @return
*/
protected String getJoinPointLogInfoStr(JoinPoint joinPoint) {
if (joinPoint.getSignature() instanceof MethodSignature) {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
//得到调用的方法对象
Method method = methodSignature.getMethod();
//得到此方法具有的注解列表
Annotation[] declaredAnnotations = method.getDeclaredAnnotations();
//得到调用的方法名
String name = method.getName();
//得到方法的参数名
String[] parameterNames = methodSignature.getParameterNames();
//得到方法的参数类型
Class<?>[] parameterTypes = methodSignature.getParameterTypes();
//得到方法中的每个参数的注解列表
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
//得到当前方法的参数值
Object[] args = joinPoint.getArgs();
StringBuilder stringBuilder = new StringBuilder("执行的方法名:" + name);
//得到方法上注解的说明并加入日志中
MethodLogDesc methodLogDesc = method.getAnnotation(MethodLogDesc.class);
if (methodLogDesc != null && StringUtils.isNotEmpty(methodLogDesc.value())) {
stringBuilder.append("(").append(methodLogDesc.value()).append(")");
}
stringBuilder.append("[");
if (parameterNames != null && parameterNames.length > 0) {
//开始遍历拼接
for (int i = 0; i < parameterNames.length; i++) {
String parameterName = parameterNames[i];
stringBuilder.append(parameterName).append("=");
try {
if (args[i] != null) {
stringBuilder.append(objectMapper.writeValueAsString(args[i]));
} else {
stringBuilder.append("null");
}
} catch (JsonProcessingException e) {
//忽略异常
stringBuilder.append("notConvertJsonString");
}
stringBuilder.append(";");
}
}
stringBuilder.append("]");
return stringBuilder.toString();
}
return "";
}
/**
* 环绕通知
*
* @param proceedingJoinPoint
*/
@Around(value = "webLog()")
public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
StringBuilder stringBuilder = new StringBuilder();
// 接收到请求,记录请求内容
HttpServletRequest request = RequestUtil.getHttpServletRequest();
String requestLogInfoStr = getHttpServletRequestLogInfoStr(request);
if (StringUtils.isNotEmpty(requestLogInfoStr)) {
stringBuilder.append(requestLogInfoStr).append("\n");
}
//这里打印执行的方法的参数信息
String signatureLogInfoStr = getJoinPointLogInfoStr(proceedingJoinPoint);
if (StringUtils.isNotEmpty(signatureLogInfoStr)) {
stringBuilder.append(signatureLogInfoStr);
}
log.info(stringBuilder.toString());
long startTime = System.currentTimeMillis();
//执行目标方法
Object proceed = proceedingJoinPoint.proceed();
long runTime = System.currentTimeMillis() - startTime;
stringBuilder = new StringBuilder();
stringBuilder.append("执行完成! ").append(signatureLogInfoStr)
.append("\nURL:").append(RequestUtil.getRequestUrl())
.append("\n返回值类型: ").append(proceed.getClass().getSimpleName())
.append("\n返回值: ").append(objectMapper.writeValueAsString(proceed))
.append("\n执行时间: ").append(runTime).append("ms");
log.info(stringBuilder.toString());
return proceed;
}
/**
* 异常通知 参数 Throwable ex 一定要在配置中指出. throwing指出参数名
* 如果产生异常 此通知是最后执行的
*
* @param joinPoint
* @param ex
*/
@AfterThrowing(pointcut = "webLog()", throwing = "ex")
public void afterThrowing(JoinPoint joinPoint, Throwable ex) {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("执行出异常! ");
//这里打印执行的方法的参数信息
String signatureLogInfoStr = getJoinPointLogInfoStr(joinPoint);
if (StringUtils.isNotEmpty(signatureLogInfoStr)) {
stringBuilder.append(signatureLogInfoStr);
}
stringBuilder.append("\nURL:").append(RequestUtil.getRequestUrl())
.append("\n异常类型: ").append(ex.getClass().getSimpleName())
.append("\n异常消息: ").append(ex.getMessage());
log.error(stringBuilder.toString());
}
}
简单日志记录类
主要是继承了抽象日志类,直接用抽象类的方法来实现日志
@Slf4j
@Aspect
public class SimpleWebLogAspect extends AbstractWebLogAspect {
public SimpleWebLogAspect() {
log.info("------------------注入简单切面日志记录--------------------");
}
}
自定义注解
主要用于对方法进行说明,写入日志,方便观察
/**
* @author ZhouChuGang
* @version 1.0
* @project home-cloud
* @date 2020/4/3 15:09
* @Description 方法功能说明
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MethodLogDesc {
/**
* 当前方法的功能 方便在日志输出时打印
*
* @return
*/
String value() default "";
}
需要的一些工具类
主要是得到当前的请求信息
package com.langangkj.home.cloud.common.util;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.net.InetAddress;
import java.net.UnknownHostException;
/**
* 提供全局的request 对象
*/
@Slf4j
public class RequestUtil {
private RequestUtil() {
}
/**
* 得到当前的请求对象
*
* @return
*/
public static HttpServletRequest getHttpServletRequest() {
return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
}
/**
* 得到当前请求的ip
*
* @return
*/
public static String getRequestIpAddress() {
HttpServletRequest httpServletRequest = getHttpServletRequest();
if (httpServletRequest != null) {
return getIpAddr(httpServletRequest);
}
return "";
}
/**
* 得到ip地址
*
* @param request
* @return
*/
public static String getIpAddr(HttpServletRequest request) {
String ipAddress;
try {
ipAddress = request.getHeader("x-forwarded-for");
if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getHeader("Proxy-Client-IP");
}
if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getHeader("WL-Proxy-Client-IP");
}
if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getRemoteAddr();
if (ipAddress.equals("127.0.0.1")) {
// 根据网卡取本机配置的IP
InetAddress inet = null;
try {
inet = InetAddress.getLocalHost();
} catch (UnknownHostException e) {
log.error(e.getMessage(), e);
}
ipAddress = inet.getHostAddress();
}
}
// 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
if (ipAddress != null && ipAddress.length() > 15) { // "***.***.***.***".length()
// = 15
if (ipAddress.indexOf(",") > 0) {
ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));
}
}
} catch (Exception e) {
ipAddress = "";
}
return ipAddress;
}
/**
* 得到当前请求的URL
*
* @return
*/
public static String getRequestUrl() {
HttpServletRequest httpServletRequest = getHttpServletRequest();
if (httpServletRequest != null) {
return httpServletRequest.getRequestURI();
}
return "";
}
}
使能简单日志记录注解
其功能就只是把SimpleWebLogAspect类放入容器中,实现对执行的controller中请求的方法进行切面日志记录。
/**
* @author ZhouChuGang
* @version 1.0
* @project home-cloud
* @date 2020/4/20 14:09
* @Description 开启默认的日志切面记录
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(SimpleWebLogAspect.class)
public @interface EnableSimpleWebLogAspect {
}
自动注入
在resources/META-INF/spring.factories 文件中加入如下信息
# 自动配置 下面写的是需要spring容器加载放入到容器中的配置类
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.langangkj.home.cloud.common.HomeCommonAutoConfiguration
编写配自动置类
package com.langangkj.home.cloud.common;
@Slf4j
public class HomeCommonAutoConfiguration {
public HomeCommonAutoConfiguration() {
log.info("启动了自定义配置类");
}
@Bean
@ConditionalOnWebApplication
@ConditionalOnMissingBean(AbstractWebLogAspect.class)
public SimpleWebLogAspect simpleWebLogAspect() {
log.info("注入简单切面类");
return new SimpleWebLogAspect();
}
}
业务模块使用
因为会自动把日志切面类注入到容器中,这里直接向写一般的controller代码即可,日志会自动实现
@ApiOperation(value = "得到当前用户的认证信息")
@MethodLogDesc("得到当前用户的认证信息")
@GetMapping(value = "/getAuthentication")
public Result getAuthentication(Authentication authentication) {
if (authentication == null) {
return Result.failed("无效凭证");
}
log.info("authentication class : {} id -- {} ", authentication.getClass(), authentication.hashCode());
return Result.succeed(authentication);
}
执行效果
关于自定义
在抽象父类
里面已经几乎把所有方法能得到的参数都列出来了,可根据自己的实际需要进行改造,完善和标准化这个日志记录
本文章项目开源地址 主要是看dev分支