当前位置:网站首页>2022-06-07 VI. log implementation

2022-06-07 VI. log implementation

2022-07-28 06:35:00 Don't like milkshakes

Preface

Logging is an essential function in a project , Developing 、 test 、 Deploy 、 Online and other environments , Logs can help developers execute programs 、 Troubleshooting helps .

And the log is divided into system log and operation log :

  • The system log is to understand the execution process of the application , Like request logs 、 stay if…else… Wait for which branch 、 Print out the information in a certain line of code , Log information helps developers understand the implementation of the system
  • Operation logs are generally for users , For example, the intermediate steps of order creation , Where is the current progress 、 Or some information has been added or modified ; This requires the readability of the operation log

So how to implement it in the system ? The simplest operation log is to use log.info(" user {} Shipping address ‘{}’ It is amended as follows ‘{}’ ", userId, oldAddr, newAddr); Output the modification record . Here, the modification information is collected , But for the code, it increases the burden of code readability , Or intrusive . When it comes to log output, you need to call log.xxx(xxxx);, What can be done about it ?

Java Log introduction and implementation in the project

Open log

More commonly used logs Log4j、Log4j2、Logbak, stay SpringBoot Use facade mode to support different logs

SpringBoot Import... In project starter When you depend on , By default Logback journal . It uses facade mode Slf4j To develop the log function interface , The specific log only needs to realize the function , You can switch seamlessly

maven rely on :

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

springboot By default logback, Here we use logback Explain , The first is the configuration file

#  The default log is info Level ,  You can specify the log level for a specific path 
logging:
  level: 
    com.zsl.service.dao: debug

log Profile name :

Logging SystemCustomization
Logbacklogback-spring.xml, logback-spring.groovy, logback.xml, or logback.groovy
Log4j2log4j2-spring.xml or log4j2.xml
JDK (Java Util Logging)logging.properties

Custom configuration ‘logback-spring.xml’:

<?xml version="1.0" encoding="UTF-8"?>
<!--  The log level is from low to high TRACE < DEBUG < INFO < WARN < ERROR < FATAL, such as :  If set to WARN, Less than WARN None of our messages will output  -->
<!-- scan: When this property is set to true when , If the configuration document changes , Will be reloaded , The default value is true -->
<!-- scanPeriod: Set the time interval between changes in the monitoring configuration document , If no time unit is given , The default unit is milliseconds . When scan by true when , This property takes effect . The default time interval is 1 minute . -->
<!-- debug: When this property is set to true when , Will print out logback Internal log information , Real-time view logback Running state . The default value is false. -->
<configuration scan="true" scanPeriod="10 seconds">
    <contextName>logback</contextName>

    <!-- name The value of is the name of the variable ,value The value of is the value defined by the variable . The value defined by will be inserted into logger In the context of . After the definition , You can make “${}” To use variables . -->
    <property name="log.path" value="E:/logs"/>

    <!--0.  Log format and color rendering  -->
    <!--  Color log depends on rendering class  -->
    <conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter" />
    <conversionRule conversionWord="wex" converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter" />
    <conversionRule conversionWord="wEx" converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter" />
    <!--  Color log format  -->
    <property name="CONSOLE_LOG_PATTERN" value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>

    <!--1.  Output to console -->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <!-- This log appender It's for development , Configure only the lowest level , The console output log level is greater than or equal to this level of log information -->
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>debug</level>
        </filter>
        <encoder>
            <Pattern>${CONSOLE_LOG_PATTERN}</Pattern>
            <!--  Set character set  -->
            <charset>UTF-8</charset>
        </encoder>
    </appender>

    <!--2.  Output to document -->
    <!-- 2.1 level by  DEBUG  journal , Time scrolling output  -->
    <appender name="DEBUG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!--  The path and document name of the log document being recorded  -->
        <file>${log.path}/debug.log</file>
        <!-- Log document output format -->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset> <!--  Set character set  -->
        </encoder>
        <!--  Rolling policy for loggers , By date , Record by size  -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!--  Log filing  -->
            <fileNamePattern>${log.path}/debug-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>1000MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!-- Log document retention days -->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <!--  This log document only records debug Grade  -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>debug</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!-- 2.2 level by  INFO  journal , Time scrolling output  -->
    <appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!--  The path and document name of the log document being recorded  -->
        <file>${log.path}/info.log</file>
        <!-- Log document output format -->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
        <!--  Rolling policy for loggers , By date , Record by size  -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!--  Daily log archive path and format  -->
            <fileNamePattern>${log.path}/info-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>1000MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!-- Log document retention days -->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <!--  This log document only records info Grade  -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>info</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!-- 2.3 level by  WARN  journal , Time scrolling output  -->
    <appender name="WARN_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!--  The path and document name of the log document being recorded  -->
        <file>${log.path}/warn.log</file>
        <!-- Log document output format -->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset> <!--  Set character set here  -->
        </encoder>
        <!--  Rolling policy for loggers , By date , Record by size  -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${log.path}/warn-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>1000MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!-- Log document retention days -->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <!--  This log document only records warn Grade  -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>warn</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!-- 2.4 level by  ERROR  journal , Time scrolling output  -->
    <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!--  The path and document name of the log document being recorded  -->
        <file>${log.path}/error.log</file>
        <!-- Log document output format -->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset> <!--  Set character set here  -->
        </encoder>
        <!--  Rolling policy for loggers , By date , Record by size  -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${log.path}/error-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!-- Log document retention days -->
            <maxHistory>1</maxHistory>
        </rollingPolicy>
        <!--  This log document only records ERROR Grade  -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!-- 2.5  all   except DEBUG Other levels are higher than DEBUG Of   journal , Record to a file  -->
    <appender name="ALL_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!--  The path and document name of the log document being recorded  -->
        <file>${log.path}/all.log</file>
        <!-- Log document output format -->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset> <!--  Set character set here  -->
        </encoder>
        <!--  Rolling policy for loggers , By date , Record by size  -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${log.path}/all-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>1000MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!-- Log document retention days -->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <!--  This log document records except DEBUG Other levels are higher than DEBUG Of  -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>DEBUG</level>
            <onMatch>DENY</onMatch>
            <onMismatch>ACCEPT</onMismatch>
        </filter>
    </appender>

    <!-- <logger> Used to set the log printing level of a package or a specific class 、  And the designation <appender>.<logger> There is only one name attribute ,  An optional one level And an optional addtivity attribute . name: Used to designate the recipient logger A package of constraints or a specific class . level: Used to set the print level , Case is irrelevant :TRACE, DEBUG, INFO, WARN, ERROR, ALL  and  OFF,  There is also a special value INHERITED Or synonyms NULL, Represents the level of enforcement superior .  If this property is not set , Then the current logger Will inherit the level of the superior . addtivity: Whether to report to the superior logger Transfer printed information . The default is true. <logger name="org.springframework.web" level="info"/> <logger name="org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor" level="INFO"/> -->

    <!--  Use mybatis When ,sql The sentence is debug Next will print , And here we only configure info, So I want to see sql In words , There are two operations :  The first one <root level="info"> Change to <root level="DEBUG"> This will print sql, But there will be a lot of other news in the log   The second is to give it alone dao Directory configuration debug Pattern , The code is as follows , This configuration sql The statement will print , Others are normal info Level : 【logging.level.org.mybatis=debug logging.level.dao=debug】 -->

    <!-- root Nodes are required , Used to specify the most basic log output level , only one level attribute  level: Used to set the print level , Case is irrelevant :TRACE, DEBUG, INFO, WARN, ERROR, ALL  and  OFF,  Cannot be set to INHERITED Or synonyms NULL. The default is DEBUG  Can contain zero or more elements , Identify this appender Will be added to this logger. -->

    <!-- 4  The ultimate strategy :  Basic strategy (root level ) +  according to profile When it starts , logger Customized in the label package The level of logging ( Priority is higher than the above root level )-->
    <springProfile name="dev">
        <root level="info">
            <appender-ref ref="CONSOLE" />
            <appender-ref ref="DEBUG_FILE" />
            <appender-ref ref="INFO_FILE" />
            <appender-ref ref="WARN_FILE" />
            <appender-ref ref="ERROR_FILE" />
            <appender-ref ref="ALL_FILE" />
        </root>
        <logger name="com.zsl" level="debug"/> <!--  development environment ,  Specify a package log as debug level  -->
    </springProfile>

    <springProfile name="zsl_test">
        <root level="info">
            <appender-ref ref="CONSOLE" />
            <appender-ref ref="DEBUG_FILE" />
            <appender-ref ref="INFO_FILE" />
            <appender-ref ref="WARN_FILE" />
            <appender-ref ref="ERROR_FILE" />
            <appender-ref ref="ALL_FILE" />
        </root>
        <logger name="com.zsl" level="debug"/> <!--  Test environment ,  Specify a package log as info level  -->
<!-- <logger name="com.zsl" level="info"/>--> <!--  Test environment ,  Specify a package log as info level  -->
    </springProfile>

    <springProfile name="prod">
        <root level="info">
            <!--  The production environment should not be configured console Writing documents  -->
            <appender-ref ref="DEBUG_FILE" />
            <appender-ref ref="INFO_FILE" />
            <appender-ref ref="WARN_FILE" />
            <appender-ref ref="ERROR_FILE" />
            <appender-ref ref="ALL_FILE" />
        </root>
        <logger name="com.zsl" level="warn"/> <!--  Production environment ,  Specify a package log as warn level  -->
        <logger name="com.zsl.MyApplication" level="info"/> <!--  Print a specific class info journal ,  such as application Prompt after successful startup  -->
    </springProfile>

</configuration>

Log implementation

system log

system log , In order to understand the execution process , use Interceptor Log collection of requests in the form of , For the execution process of configuration , have access to log.info() Log output .

At present, only the prototype has been realized, and the follow-up will be continuously improved every day

Package structure :

 Insert picture description here

Use here interceptor Realization :

package com.zsl.custombox.log.core.interceptor;

import com.zsl.custombox.common.util.SecurityContextHolder;
import com.zsl.custombox.common.util.ServletUtil;
import com.zsl.custombox.common.model.log.SystemLogContext;
import com.zsl.custombox.common.util.SystemLogContextHolder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/** *  Access records ( system log ) *  Realization @LogRecord Annotation implements operation log  * * @Author zsl * @Date 2022/5/22 14:55 * @Email [email protected] */
public class AccessLogInterceptor implements HandlerInterceptor {
    
    private final Logger log = LoggerFactory.getLogger(this.getClass());

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    
        //  Create a global logging context  todo  get data ( Implement tool class )

        SystemLogContext systemLogContext = new SystemLogContext()
                .setUserId(SecurityContextHolder.getAuth().getUserId())
                .setRequestNo(0L)//  You can use the snowflake algorithm to get 64 Only one id
                .setIp(ServletUtil.getIp())
                .setUri(request.getRequestURI())
                .setMethod(request.getMethod())
                .setStartTime(new Date(System.currentTimeMillis()));
        //  Store global logging context 
        SystemLogContextHolder.set(systemLogContext);
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    
        SystemLogContext systemLogContext = SystemLogContextHolder.get();
        systemLogContext.setRespTime(System.currentTimeMillis() - systemLogContext.getStartTime().getTime());

        // format log
        StringBuilder requestStr = new StringBuilder();
        List<Object> requestArgs = new ArrayList<>();
        requestStr.append("\n=========================== AccessLog ===========================\n");
        requestStr.append(String.format(" %-10s: {}\n", "userId"));
        requestArgs.add(systemLogContext.getUserId());
        requestStr.append(String.format(" %-10s: {}\n", "requestNo"));
        requestArgs.add(systemLogContext.getRequestNo());
        requestStr.append(String.format(" %-10s: {}\n", "ip"));
        requestArgs.add(systemLogContext.getIp());
        requestStr.append(String.format(" %-10s: {}\n", "uri"));
        requestArgs.add(systemLogContext.getUri());
        requestStr.append(String.format(" %-10s: {}\n", "method"));
        requestArgs.add(systemLogContext.getMethod());
        requestStr.append(String.format(" %-10s: {}\n", "startTime"));
        requestArgs.add(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(systemLogContext.getStartTime()));
        requestStr.append(String.format(" %-10s: {} ms\n", "respTime"));
        requestArgs.add(systemLogContext.getRespTime());
        requestStr.append(String.format(" %-10s: {}\n", "respCode"));
        requestArgs.add(systemLogContext.getRespCode());
        requestStr.append(String.format(" %-10s: {}\n", "respMsg"));
        requestArgs.add(systemLogContext.getRespMsg());
        requestStr.append("=================================================================\n");

        // todo  Log in 
        log.info(requestStr.toString(), requestArgs.toArray());

        //  clear ThreadLocal
        SystemLogContextHolder.clear();
    }
}

Logging global context :

package com.zsl.custombox.common.model.log;

import lombok.Data;
import lombok.experimental.Accessors;

import java.util.Date;
import java.util.Map;

/** *  Logging context  * * @Author zsl * @Date 2022/5/22 16:48 * @Email [email protected] */
@Data
@Accessors(chain = true)
public class SystemLogContext {
    
    // =========================== request data ===========================
    //  user id( Anonymous as 0)
    private Long userId;
    //  Request number , Ensure that the request number does not change when the request is called 
    private Long requestNo;
    // ip Address 
    private String ip;
    //  Unified resource interface uri
    private String uri;
    //  Parameters 
// private String params;
    //  Request method (GET、POST...)
    private String method;
    //  Request time 
    private Date startTime;

    // =========================== response data ===========================
    //  response time 
    private Long respTime;
    //  Response code 
    private Integer respCode;
    //  Response information 
    private String respMsg;
    //  Response body 
// private String respBody;
}

Logging context tool class :

package com.zsl.custombox.log.core.util;


import com.zsl.custombox.log.core.model.LogRecordContext;

/** *  Thread safety logging  * * @Author zsl * @Date 2022/5/22 16:57 * @Email [email protected] */
public class LogRecordContextHolder {
    
    private static final ThreadLocal<LogRecordContext> LOG_RECORD_CONTEXT = new ThreadLocal<>();

    public static LogRecordContext get() {
    
        return LOG_RECORD_CONTEXT.get();
    }

    public static void set(LogRecordContext logRecordContext) {
    
        LOG_RECORD_CONTEXT.set(logRecordContext);
    }

    public static void clear() {
    
        LOG_RECORD_CONTEXT.remove();
    }
}

Automatic configuration class :

package com.zsl.custombox.log.config;

import com.zsl.custombox.log.core.interceptor.AccessLogInterceptor;
import com.zsl.custombox.log.core.model.logrecord.LogRecordOperationSource;
import com.zsl.custombox.log.core.service.record.DefaultLogRecordServiceImpl;
import com.zsl.custombox.log.core.service.record.ILogRecordService;
import com.zsl.custombox.log.core.service.function.DefaultFunctionServiceImpl;
import com.zsl.custombox.log.core.service.function.IFunctionService;
import com.zsl.custombox.log.core.service.function.IParseFunction;
import com.zsl.custombox.log.core.service.function.ParseFunctionFactory;
import com.zsl.custombox.log.core.service.operator.DefaultOperatorGetServiceImpl;
import com.zsl.custombox.log.core.service.operator.IOperatorGetService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.List;

/** *  Log auto configuration ,AccessLogInterceptor( Access log )、LogRecordAspect( The operation log ) * * @Author zsl * @Date 2022/5/22 21:04 * @Email [email protected] */
@Configuration
public class LogAutoConfiguration implements WebMvcConfigurer {
    

    // ==========================  Interceptor  ==========================
    @Bean
    public AccessLogInterceptor accessLogInterceptor() {
    
        return new AccessLogInterceptor();
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
    
        registry.addInterceptor(this.accessLogInterceptor());
    }

    // ==========================  Operation log container  ==========================

    //  Processing annotation information operation source 
    @Bean
    public LogRecordOperationSource logRecordOperationSource () {
    
        return new LogRecordOperationSource();
    }

    //  Custom function 
    @Bean
    @ConditionalOnMissingBean(IFunctionService.class)
    public IFunctionService functionService(ParseFunctionFactory parseFunctionFactory) {
    
        return new DefaultFunctionServiceImpl(parseFunctionFactory);
    }

    @Bean
    public ParseFunctionFactory parseFunctionFactory(@Autowired List<IParseFunction> list) {
    
        return new ParseFunctionFactory(list);
    }

    @Bean
    @ConditionalOnMissingBean(IParseFunction.class)
    public IParseFunction defaultParseFunction() {
    
        return new IParseFunction() {
    
            @Override
            public String functionName() {
    
                return null;
            }

            @Override
            public String apply(String value) {
    
                return null;
            }
        };
    }

    //  Persistence 
    @Bean
    @ConditionalOnMissingBean(ILogRecordService.class)
    public ILogRecordService logRecordService() {
    
        return new DefaultLogRecordServiceImpl();
    }

    //  Operator 
    @Bean
    @ConditionalOnMissingBean(IOperatorGetService.class)
    public IOperatorGetService operatorGetService() {
    
        return new DefaultOperatorGetServiceImpl();
    }

}

The operation log

Use AOP+ How to annotate , Conduct operation log :

/** * @Author zsl * @Date 2022/5/23 23:20 * @Email [email protected] */
@Aspect
@Component
public class LogRecordAspect {
    
    private final Logger log = LoggerFactory.getLogger(this.getClass());


    @Autowired
    ILogRecordService logRecordService;

    @Autowired
    IFunctionService functionService;

    LogRecordValueParser logRecordValueParser = new LogRecordValueParser(new LogRecordExpressionEvaluator(), functionService);

    @Around("@annotation(com.zsl.custombox.log.core.annotation.LogRecord)")
    public Object logRecord(ProceedingJoinPoint point) throws Throwable {
    
        return execute(point, point.getTarget().getClass(), ((MethodSignature) point.getSignature()).getMethod(), point.getArgs());
    }

    /** * v0.0.1  edition   Easy to use SpEL analysis ( In later versions, it is provided to parse variables in a functional way ) */
    private Object execute(ProceedingJoinPoint point, Class<?> targetClass, Method method, Object[] args) throws Throwable {
    
        Object ret = null;

        LogRecordContext.putEmptySpan();

        MethodExceptionResult exceptionResult = new MethodExceptionResult(true, null, "");
        //  Split annotation information is stored as List Waiting for execution 

        //  perform List Custom functions in 
        String spEL = getAnnotation(method).content();

        try {
    
            ret = point.proceed();
        } catch (Throwable throwable) {
    
            exceptionResult = new MethodExceptionResult(false, throwable, throwable.getMessage());
        }
        //  analysis List in SpEL
        try {
    
            //  Store logs 
            if (Strings.isNotBlank(spEL)) {
    
                recordExecute(ret, method, args, spEL, targetClass,
                        exceptionResult.isSuccess(), exceptionResult.getErrorMsg());
            }
        } catch (Throwable t) {
    
            log.error(" Failed to record operation log , Does not affect business execution !", t);
        } finally {
    
            //  clear  Context
            LogRecordContext.clear();
        }
        //  If the execution fails, an exception will be thrown 
        if (!exceptionResult.isSuccess()) {
    
            throw exceptionResult.getThrowable();
        }
        return ret;
    }

    private void recordExecute(Object ret, Method method, Object[] args, String spEL, Class<?> targetClass, boolean success, String errorMsg) {
    
        //  Create context 
        EvaluationContext evaluationContext = logRecordValueParser.createEvaluationContext(method, args, ret, errorMsg);
        //  After obtaining the evaluation expressionString
        String expression = logRecordValueParser.getExpression(spEL, new AnnotatedElementKey(method, targetClass), evaluationContext);
        //  Persistent operation log 
        logRecordService.record(new com.zsl.custombox.log.core.model.logrecord.LogRecord(expression));
    }

    /** *  Access method  */
    private Method getMethod(ProceedingJoinPoint point) {
    
        return ((MethodSignature) point.getSignature()).getMethod();
    }

    /** *  Get comments  */
    private LogRecord getAnnotation(Method method) {
    
        LogRecord annotation = method.getAnnotation(LogRecord.class);
        return annotation;
    }
}

example :

public class XxxxServiceImpl {
    
    
    @LogRecord(content = "' Change the user name to ' + #userParam.username")
    public int updateUser(UserParam userParam) {
    
        return mapper.updateUser(userParam);
    }
}

Finally record the operation log : Change the user name to xxx

This is just a simple version , The annotation parsing will be improved later , Expected effect " user xxx Change user name ‘aaa’ by ‘bbb’ "

Later, we will use SpEL Realize the improvement of log items and submit them to Github in , Welcome to your attention ~
https://github.com/zsl0/costom_box

Related articles

actual combat ! The log is printed 15 A good suggestion

How to record operation logs gracefully ?

原网站

版权声明
本文为[Don't like milkshakes]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/209/202207280519357395.html