springBoot集成AOP做请求日志记录

139 阅读1分钟

项目结构

image.png

切面日志记录信息

@Aspect
@Component
@Slf4j
public class AopLog {
   private static final String START_TIME = "request-start";
    private static final Logger log = LoggerFactory.getLogger(AopLog.class);

   /**
    * 切入点
    */
   @Pointcut("execution(public * com.xkcoding.log.aop.controller.*Controller.*(..))")
   public void log() {

   }

   /**
    * 前置操作
    *
    * @param point 切入点
    */
   @Before("log()")
   public void beforeLog(JoinPoint point) {
      ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();

      HttpServletRequest request = Objects.requireNonNull(attributes).getRequest();

      log.info("【请求 URL】:{}", request.getRequestURL());
      log.info("【请求 IP】:{}", request.getRemoteAddr());
      log.info("【请求类名】:{},【请求方法名】:{}", point.getSignature().getDeclaringTypeName(), point.getSignature().getName());

      Map<String, String[]> parameterMap = request.getParameterMap();
      log.info("【请求参数】:{},", JSONUtil.toJsonStr(parameterMap));
      Long start = System.currentTimeMillis();
      request.setAttribute(START_TIME, start);
   }

   /**
    * 环绕操作
    *
    * @param point 切入点
    * @return 原方法返回值
    * @throws Throwable 异常信息
    */
   @Around("log()")
   public Object aroundLog(ProceedingJoinPoint point) throws Throwable {
      Object result = point.proceed();
      log.info("【返回值】:{}", JSONUtil.toJsonStr(result));
      return result;
   }

   /**
    * 后置操作
    */
   @AfterReturning("log()")
   public void afterReturning() {
      ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
      HttpServletRequest request = Objects.requireNonNull(attributes).getRequest();

      Long start = (Long) request.getAttribute(START_TIME);
      Long end = System.currentTimeMillis();
      log.info("【请求耗时】:{}毫秒", end - start);

      String header = request.getHeader("User-Agent");
      UserAgent userAgent = UserAgent.parseUserAgentString(header);
      log.info("【浏览器类型】:{},【操作系统】:{},【原始User-Agent】:{}", userAgent.getBrowser().toString(), userAgent.getOperatingSystem().toString(), header);
   }
}

测试类

@RestController
public class TestController {

   /**
    * 测试方法
    *
    * @param who 测试参数
    * @return {@link Dict}
    */
   @GetMapping("/test")
   public Dict test(String who) {
      return Dict.create().set("who", StrUtil.isBlank(who) ? "me" : who);
   }

}

xml文件配置

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
 <include resource="org/springframework/boot/logging/logback/defaults.xml"/>
 <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
  <filter class="ch.qos.logback.classic.filter.LevelFilter">
   <level>INFO</level>
  </filter>
  <encoder>
   <pattern>%date [%thread] %-5level [%logger{50}] %file:%line - %msg%n</pattern>
   <charset>UTF-8</charset>
  </encoder>
 </appender>

 <appender name="FILE_INFO" class="ch.qos.logback.core.rolling.RollingFileAppender">
  <!--如果只是想要 Info 级别的日志,只是过滤 info 还是会输出 Error 日志,因为 Error 的级别高, 所以我们使用下面的策略,可以避免输出 Error 的日志-->
  <filter class="ch.qos.logback.classic.filter.LevelFilter">
   <!--过滤 Error-->
   <level>ERROR</level>
   <!--匹配到就禁止-->
   <onMatch>DENY</onMatch>
   <!--没有匹配到就允许-->
   <onMismatch>ACCEPT</onMismatch>
  </filter>
  <!--日志名称,如果没有File 属性,那么只会使用FileNamePattern的文件路径规则如果同时有<File>和<FileNamePattern>,那么当天日志是<File>,明天会自动把今天的日志改名为今天的日期。即,<File> 的日志都是当天的。-->
  <!--<File>logs/info.spring-boot-demo-log-aop.log</File>-->
  <!--滚动策略,按照时间滚动 TimeBasedRollingPolicy-->
  <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
   <!--文件路径,定义了日志的切分方式——把每一天的日志归档到一个文件中,以防止日志填满整个磁盘空间-->
   <FileNamePattern>logs/spring-boot-demo-log-aop/info.created_on_%d{yyyy-MM-dd}.part_%i.log</FileNamePattern>
   <!--只保留最近90天的日志-->
   <maxHistory>90</maxHistory>
   <!--用来指定日志文件的上限大小,那么到了这个值,就会删除旧的日志-->
   <!--<totalSizeCap>1GB</totalSizeCap>-->
   <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
    <!-- maxFileSize:这是活动文件的大小,默认值是10MB,本篇设置为1KB,只是为了演示 -->
    <maxFileSize>2MB</maxFileSize>
   </timeBasedFileNamingAndTriggeringPolicy>
  </rollingPolicy>
  <!--<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">-->
  <!--<maxFileSize>1KB</maxFileSize>-->
  <!--</triggeringPolicy>-->
  <encoder>
   <pattern>%date [%thread] %-5level [%logger{50}] %file:%line - %msg%n</pattern>
   <charset>UTF-8</charset> <!-- 此处设置字符集 -->
  </encoder>
 </appender>

 <appender name="FILE_ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
  <!--如果只是想要 Error 级别的日志,那么需要过滤一下,默认是 info 级别的,ThresholdFilter-->
  <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
   <level>Error</level>
  </filter>
  <!--日志名称,如果没有File 属性,那么只会使用FileNamePattern的文件路径规则如果同时有<File>和<FileNamePattern>,那么当天日志是<File>,明天会自动把今天的日志改名为今天的日期。即,<File> 的日志都是当天的。-->
  <!--<File>logs/error.spring-boot-demo-log-aop.log</File>-->
  <!--滚动策略,按照时间滚动 TimeBasedRollingPolicy-->
  <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
   <!--文件路径,定义了日志的切分方式——把每一天的日志归档到一个文件中,以防止日志填满整个磁盘空间-->
   <FileNamePattern>logs/spring-boot-demo-log-aop/error.created_on_%d{yyyy-MM-dd}.part_%i.log</FileNamePattern>
   <!--只保留最近90天的日志-->
   <maxHistory>90</maxHistory>
   <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
    <!-- maxFileSize:这是活动文件的大小,默认值是10MB,本篇设置为1KB,只是为了演示 -->
    <maxFileSize>2MB</maxFileSize>
   </timeBasedFileNamingAndTriggeringPolicy>
  </rollingPolicy>
  <encoder>
   <pattern>%date [%thread] %-5level [%logger{50}] %file:%line - %msg%n</pattern>
   <charset>UTF-8</charset> <!-- 此处设置字符集 -->
  </encoder>
 </appender>

 <root level="info">
  <appender-ref ref="CONSOLE"/>
  <appender-ref ref="FILE_INFO"/>
  <appender-ref ref="FILE_ERROR"/>
 </root>
</configuration>