从零开始搭建Springboot项目脚手架4:保存操作日志

目的:通过AOP切面,统一记录接口的访问日志

1、加maven依赖

2、 增加日志类RequestLog

3、 配置AOP切面,把请求前的request、返回的response一起记录

package com.template.common.config;

import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.template.common.domain.model.RequestLog;
import eu.bitwalker.useragentutils.UserAgent;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
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.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * 使用AOP切面记录请求日志
 */
@Aspect
@Component
@Slf4j
public class RequestLogger {
  /**
   * 切入点
   */
  @Pointcut("execution(public * com.template.api.controller.*.*Controller.*(..))")
  public void controllerPointcut() {
    // 本方法不会被执行,只是作为一个标记,与被注解的服务方法进行关联
    // 切入点为controller的public方法,也就是各个请求路径
  }

  /**
   * 环绕操作
   *
   * @param joinPoint 切入点
   * @return 原方法返回值
   * @throws Throwable 异常信息
   */
  @Around("controllerPointcut()")
  public Object controllerLogging(ProceedingJoinPoint joinPoint) throws Throwable {
    // 开始打印请求日志
    ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
    HttpServletRequest request = Objects.requireNonNull(attributes).getRequest();

    // 计算各个日志参数
    long startTime = System.currentTimeMillis();
    Thread currentThread = Thread.currentThread();
    String userHost = extractUserHost(request);
    String userAgentString = request.getHeader("User-Agent");
    UserAgent userAgent = UserAgent.parseUserAgentString(userAgentString);
    Map<String, Object> requestParams = extractRequestParams(joinPoint);
    Object responseResult = joinPoint.proceed();
    Signature controllerMethod = joinPoint.getSignature();
    String classMethod = controllerMethod.getDeclaringTypeName() + "." + controllerMethod.getName();

    final RequestLog requestLog = RequestLog
      .builder()
      .userHost(userHost)
      .userOs(userAgent.getOperatingSystem().getName())
      .userBrowser(userAgent.getBrowser().getName())
      .userAgent(userAgentString)
      .requestUrl(request.getRequestURL().toString())
      .requestMethod(request.getMethod())
      .requestParams(requestParams)
      .responseResult(responseResult)
      .classMethod(classMethod)
      .threadId(Long.toString(currentThread.getId()))
      .threadName(currentThread.getName())
      .costMillisecond(System.currentTimeMillis() - startTime)
      .build();

    // 打印日志
    log.info("Request Log Info : {}", JSONUtil.toJsonStr(requestLog));

    return responseResult;
  }

  /**
   * 获取用户Host地址
   *
   * @param request 请求对象
   * @return 用户Host
   */
  private static String extractUserHost(HttpServletRequest request) {
    String xRealIp = request.getHeader("X-Real-IP");
    if (StrUtil.isNotEmpty(xRealIp) && !"unknown".equalsIgnoreCase(xRealIp)) {
      return xRealIp;
    }

    String xForwardedFor = request.getHeader("x-forwarded-for");
    if (StrUtil.isNotEmpty(xForwardedFor) && !"unknown".equalsIgnoreCase(xForwardedFor)) {
      return xForwardedFor;
    }

    String proxyClientIp = request.getHeader("Proxy-Client-IP");
    if (StrUtil.isNotEmpty(proxyClientIp) && !"unknown".equalsIgnoreCase(proxyClientIp)) {
      return proxyClientIp;
    }

    String wlProxyClientIp = request.getHeader("WL-Proxy-Client-IP");
    if (StrUtil.isNotEmpty(wlProxyClientIp) && !"unknown".equalsIgnoreCase(wlProxyClientIp)) {
      return wlProxyClientIp;
    }

    String httpClientIp = request.getHeader("HTTP_CLIENT_IP");
    if (StrUtil.isNotEmpty(httpClientIp) && !"unknown".equalsIgnoreCase(httpClientIp)) {
      return httpClientIp;
    }

    String httpxForwardedFor = request.getHeader("HTTP_X_FORWARDED_FOR");
    if (StrUtil.isNotEmpty(httpxForwardedFor) && !"unknown".equalsIgnoreCase(httpxForwardedFor)) {
      return httpxForwardedFor;
    }

    String remoteAddr = request.getRemoteAddr();
    if (!"127.0.0.1".equals(remoteAddr) && !"0:0:0:0:0:0:0:1".equals(remoteAddr)) {
      return remoteAddr;
    }

    //根据网卡取本机IP地址
    try {
      return InetAddress.getLocalHost().getHostAddress();
    } catch (UnknownHostException e) {
      log.error("getIpAddress exception:", e);
    }

    return xRealIp;
  }

  /**
   * 获取请求参数
   *
   * @param joinPoint 切入点
   * @return 请求参数
   */
  private Map<String, Object> extractRequestParams(ProceedingJoinPoint joinPoint) {
    MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
    final String[] parameterNames = methodSignature.getParameterNames();
    final Object[] parameterValues = joinPoint.getArgs();

    if (ArrayUtil.isEmpty(parameterNames) || ArrayUtil.isEmpty(parameterValues)) {
      return Collections.emptyMap();
    }

    if (parameterNames.length != parameterValues.length) {
      log.warn("{}方法参数名和参数值数量不一致", methodSignature.getName());
      return Collections.emptyMap();
    }

    Map<String, Object> requestParams = new HashMap<>();
    for (int i = 0; i < parameterNames.length; i++) {
      requestParams.put(parameterNames[i], parameterValues[i]);
    }

    return requestParams;
  }

}

4、查看效果

 

 也可以把日志写入到数据库里面,自由发挥。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值