切面实现对controller层进行统一日志记录

本文章项目开源地址

切面通用父类

这里实现抽象日志记录类,里面写好了对请求的信息(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分支

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值