优雅实现AOP 切面统一处理请求日志

一、背景:

1.打印请求和响应结果

2.计算出请求耗时

3.不打印流

4.指定切面

二、代码


import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.time.LocalDateTime;
import java.util.Enumeration;

/**
 * 请求日志切面
 *
 * @author
 */
@Aspect
@Component
public class WebLogAspect {

    private final static Logger logger = LoggerFactory.getLogger(WebLogAspect.class);

    /**
     * 以 controller 包下定义的所有请求为切入点
     */
    @Pointcut("(execution(public * com.jiesz.mall.api..*.*(..)))&& (@within(org.springframework.web.bind.annotation.RestController))")
    public void webLog() {
    }

    /**
     * 环绕
     *
     * @param proceedingJoinPoint
     * @return
     * @throws Throwable
     */
    @Around("webLog()")
    public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        // 开始打印请求日志
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        Signature signature = proceedingJoinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        //2.最关键的一步:通过这获取到方法的所有参数名称的字符串数组
        String[] parameterNames = methodSignature.getParameterNames();
        Object[] oArr = proceedingJoinPoint.getArgs();
        JSONObject param = new JSONObject();
        if (oArr != null && oArr.length > 0) {
            for (int i = 0; i < oArr.length; i++) {
                if (oArr[i] instanceof MultipartFile || oArr[i] instanceof File) {
                    oArr[i] = "上传文件不打印";
                }
                if (oArr[i] instanceof HttpServletResponse) {
                    oArr[i] = "响应头不打印";
                }
                if (oArr[i] instanceof HttpServletRequest) {
                    oArr[i] = "请求头不打印";
                }
                if (oArr[i] != null) {
                    param.put(parameterNames[i], oArr[i]);
                }
            }
        }
        // 记录下请求内容
        StringBuilder sb = new StringBuilder();
        sb.append("\n========================================== Start ==========================================\n")
                .append("Request Time   :").append(LocalDateTime.now().toString()).append("\n")
                .append("URL            :").append(request.getMethod()).append("   ").append(request.getRequestURL()).append("\n");
        try {
            sb.append("Request Args   : ").append(param.toJSONString()).append("\n");
        } catch (Exception e) {
            logger.error("打印请求日志报错,存在无法json化的对象", e);
        }

        if (logger.isDebugEnabled()) {
            sb.append("Request Header   :").append(getHeader(request)).append("\n")
                    .append("Class Method   : ").append(proceedingJoinPoint.getSignature().getDeclaringTypeName()).append(".").append(proceedingJoinPoint.getSignature().getName()).append("\n")
                    .append("IP             : ").append(getIpAddress(request)).append("\n");
        }

        long startTime = System.currentTimeMillis();
        Object result = proceedingJoinPoint.proceed();
        String resultJson = "";
        if (result instanceof byte[]) {
            resultJson = "方法的结果是文件流,不输出";
        } else if (result != null) {
            try {
                resultJson = JSON.toJSONString(result);
                if (resultJson.length() > 2000) {
                    resultJson = resultJson.substring(0, 2000) + "...结果过长...";
                }
            } catch (Exception e) {
                logger.error("打印结果日志报错,存在无法json化的对象", e);
            }
        }
        sb.append("Response   :  ").append(resultJson).append("\n")
                .append("Time-Consuming : ").append(System.currentTimeMillis() - startTime).append("ms\n")
                .append("=========================================== End ===========================================\n");
        // 执行耗时
        if (logger.isDebugEnabled()) {
            logger.debug(sb.toString());
        } else {
            logger.info(sb.toString());
        }
        return result;
    }

    private String getHeader(HttpServletRequest request) {
        JSONObject header = new JSONObject();
        Enumeration<String> names = request.getHeaderNames();
        while (names.hasMoreElements()) {
            String name = names.nextElement();
            header.put(name, request.getHeader(name));
        }
        return header.toJSONString();
    }

    /**
     * 获取用户真实IP地址,不使用request.getRemoteAddr()的原因是有可能用户使用了代理软件方式避免真实IP地址,
     * 可是,如果通过了多级反向代理的话,X-Forwarded-For的值并不止一个,而是一串IP值
     *
     * @return ip
     */
    public static String getIpAddress(HttpServletRequest request) {
        String ip = request.getHeader("x-forwarded-for");
        if (ip != null && ip.length() != 0 && !"unknown".equalsIgnoreCase(ip)) {
            // 多次反向代理后会有多个ip值,第一个ip才是真实ip
            if (ip.indexOf(",") != -1) {
                ip = ip.split(",")[0];
            }
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_CLIENT_IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("X-Real-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        return ip;

    }
}

三、打印调用第三方接口得请求参数和返回结果

@Component
public class LogInterceptor implements ClientHttpRequestInterceptor {
    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
        traceRequest(request, body);
        ClientHttpResponse response = execution.execute(request, body);
        traceResponse(response);
        return response;
    }

    /**
     * 打印请求信息
     *
     * @param request
     * @param body
     * @throws IOException
     */
    private void traceRequest(HttpRequest request, byte[] body) throws IOException {
        logger.info("》》URI         : {}", request.getMethod().toString() + " " + request.getURI().toString());
        request.getHeaders().keySet().stream().forEach((key) -> {
            logger.debug("》》Header      : {}", key + ":" + request.getHeaders().get(key).toString());
        });
        logger.info("》》Request body: {}", new String(body, StandardCharsets.UTF_8));
    }

    /**
     * 响应数据
     *
     * @param response
     * @throws IOException
     */
    private void traceResponse(ClientHttpResponse response) throws IOException {
        logger.info("《《 Status       : {}", response.getStatusCode());
        if (logger.isDebugEnabled()) {
            response.getHeaders().keySet().stream().forEach((key) -> {
                logger.debug("《《 Header      : {}", key + ":" + response.getHeaders().get(key).toString());
            });
            StringBuilder inputStringBuilder = new StringBuilder();
            try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(response.getBody(), StandardCharsets.UTF_8))) {
                String line = bufferedReader.readLine();
                while (line != null) {
                    inputStringBuilder.append(line);
                    inputStringBuilder.append('\n');
                    line = bufferedReader.readLine();
                }
            }
            String resp = inputStringBuilder.length() > 1000 ? inputStringBuilder.substring(0, 1000) : inputStringBuilder.toString();
            logger.debug("《《 Response body: {}", resp);
        }
    }

    public Logger getLogger() {
        return logger;
    }
}
@Configuration
public class RestTemplateConfig {
    @Bean
    public RestTemplate restTemplate(LogInterceptor logInterceptor) {
        SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
        requestFactory.setConnectTimeout(30000);
        requestFactory.setReadTimeout(30000);
        RestTemplate restTemplate;
        if (logInterceptor.getLogger().isDebugEnabled()) {
            // BufferingClientHttpRequestFactory 多次读取 response body
            ClientHttpRequestFactory factory = new BufferingClientHttpRequestFactory(requestFactory);
            restTemplate = new RestTemplate(factory);
        } else {
            restTemplate = new RestTemplate(requestFactory);
        }
        restTemplate.setInterceptors(Collections.singletonList(logInterceptor));
        return restTemplate;
    }

}

参考资料:

 https://www.cnblogs.com/quanxiaoha/p/10414681.html AOP 切面统一处理请求日志

https://blog.csdn.net/sky_jiangcheng/article/details/81564149 使用aop写一个简单日志切片

https://blog.csdn.net/qq525099302/article/details/53996344   Spring AOP 中pointcut expression表达式解析及配置

 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值