AOP在日志中的使用

579 阅读4分钟

What

日志指的是系统运行时进行的一些记录,可以指定输入到控制台也可以输入到文本文件中。

Why

这个东西很重要,体现了职业素养

程序运行起来基本就是一个黑箱,如果程序的行为与预期的不一致,那就是出了Bug, 这时候我们需要快速定位Bug 作为开发人员难免跟Bug打交道, 解决问题是开发的基本技能 但是解决问题的方式,步骤以及反思的程度都更加体现开发者的职业素养

职业素养

职业素养一方面体现了能力与素质; 另一方面它又强调了持续的积累与养成,作为开发人员,基本技能不够熟练,当然谈不上职业素养。但是仅仅能快速地编写代码,却不关心代码背后的意义,不能迅速的判断、解决程序运行中的各种问题,不能对自己的代码产生自信,这也是缺乏职业素养的体现。 缺乏职业素养, 可能导致的后果:

  1. 无法迅速解决问题,浪费过多的时间,导致产出低下
  2. 给加班增加了前置条件

快速定位Bug

1. 单步调试

使用IDE的debug模式, 进行单步调试,一步一步的跟踪,同时观察变量值与逻辑分支 缺点:

  1. 耗时,debug模式一般比较慢
  2. 耗力,debug过程中会经过其他业务, 而这些其他业务也需要开发者关注,它们也有可能是产生Bug的原因

2. 日志

开发者通过在特定的地方打日志 通过日志的输出可以,可以帮助快速定位Bug,尤其是在类似黑盒的生产环境中 目前已经有很多日志框架如Log4j, LogBack等开源框架可以使用

  1. 只能在开发环境使用,单步调试只能由开发者手动进行

How

1. 通过开源日志框架

常见的日志框架Log4j, Log4j2, LogBack等 可以将日志输出到控制台与文件中, 以及对日志文件的维护。 SpringBoot自带LogBack日志框架,我基本上使用这个日志框架

2. LogBack + Lombok

1. 使用LogBack

依赖

    <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.1.2</version>
        </dependency>

        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-core</artifactId>
            <version>1.1.2</version>
        </dependency>

        <dependency>
            <groupId>org.logback-extensions</groupId>
            <artifactId>logback-ext-spring</artifactId>
            <version>0.1.1</version>
        </dependency>

2. 配置文件

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <property name="log.root.level" value="DEBUG"/>
    <property name="log.other.level" value="ERROR"/>
    <property name="log.base"
              value="C:/ideaWorkplace/om-systemV4"/>
    <property name="log.moduleName" value="omSystem"/>
    <property name="log.max.size" value="20MB"/>

    <appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <Pattern>%date{yyyy-MM-dd HHmmss.SSS} %-5level [%thread]%logger{56}.%method:%L -%msg%n</Pattern>
        </encoder>
    </appender>

    <appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <File>${log.base}/${log.moduleName}.log
        </File>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <FileNamePattern>${log.base}/archive/${log.moduleName}_all_%d{yyyy-MM-dd}.%i.log.zip
            </FileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>${log.max.size}</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
        </rollingPolicy>
        <layout class="ch.qos.logback.classic.PatternLayout">
            <pattern>%date{yyyy-MM-dd HHmmss.SSS} %-5level [%thread]%logger{56}.%method:%L -%msg%n</pattern>
        </layout>
    </appender>

    <root level="INFO">
        <appender-ref ref="stdout"/>
        <appender-ref ref="file"/>
    </root>
</configuration>

3. Lombok

  1. Maven依赖
<dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.12</version>
        </dependency>

4. 日志使用

  1. 类上使用注解
@Slf4j
  1. 日志记录 使用以下方法
log.info()
log.error()
log.warn()

2. 使用AOP使用统一日志接口

目的:打印函数的名称,输入参数名,参数类型,参数值

使用AOP的环绕通知 见

类名

可通过joinPoint获得

        String className = joinPoint.getSignature().getDeclaringType().getName();

函数名

可通过joinPoint获得

        String methodName = joinPoint.getSignature().getName();

参数名

     MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        //参数值
        Object[] args = joinPoint.getArgs();
        //参数名
        String[] parameterNames = signature.getParameterNames();
        //参数类型
        Class[] parameterTypes = signature.getParameterTypes();

        StringBuilder stringBuilder = new StringBuilder();
        for(int i = 0; i < parameterNames.length; i++) {
            stringBuilder.append(parameterTypes[i] + " : " + parameterNames[i] + " : " + args[i] +"\n");
        }
        String parameters = stringBuilder.toString();

获得以上元素即可输出到日志文件里

切面类如下

@Pointcut("@annotation(com.atoz.omsystem.annotation.GetParamLog)")
    public void getParam() {

    }


    //获得函数名, 参数名, 参数值,执行时间
    @Around(value = "getParam()")
    public void getParams(ProceedingJoinPoint joinPoint) throws Throwable {
        Date start = new Date();
        String className = joinPoint.getSignature().getDeclaringType().getName();
        String methodName = joinPoint.getSignature().getName();
        String entranceName = className + "." + methodName;
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        //参数值
        Object[] args = joinPoint.getArgs();
        //参数名
        String[] parameterNames = signature.getParameterNames();
        //参数类型
        Class[] parameterTypes = signature.getParameterTypes();

        StringBuilder stringBuilder = new StringBuilder();
        for(int i = 0; i < parameterNames.length; i++) {
            stringBuilder.append(parameterTypes[i] + " : " + parameterNames[i] + " : " + args[i] +"\n");
        }
        String parameters = stringBuilder.toString();
        log.info("入口为 {} \n 参数信息: \n{}", entranceName , stringBuilder.toString());
        Object proceed = joinPoint.proceed();
        Date end = new Date();
        long millSecond = end.getTime() - start.getTime();
        long second = millSecond/1000;
        log.info("执行时间为 {} s", second);
    }