Springboot +AOP logging system

1. Foreword

A good arrangement and division of log information can enable us to better locate the wrong place. If we use the debugger breakpoint mechanism to reproduce the problem, locate the problem and solve the problem. Most of the time, we do this when we encounter a bug that is difficult to locate. If it is an ordinary bug, we believe that System.out.println() can solve most of the difficult and miscellaneous diseases (dog head). However, when our service goes online, a large number of log logs cannot be directly printed on the console to locate the problem. Moreover, you don't keep your Shell open all the time. If you turn it off, there will be no problem. So the key to successful debugging, monitoring, and error reporting is logging.

2. Code part

Introduce dependency

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

However, in actual development, we do not need to add this dependency. The default logging framework of Spring Boot is Logback+SLF4J. Spring Boot starter web has been included, so we only need to introduce web components

Transmission document address----

https://www.scalyr.com/blog/started-quickly-spring-boot-logging/#:~:text=Spring%20Boot%20comes%20with%20a%20preconfigured%20default%20logger,dependency%20already.%20That%E2%80%99s%20what%20we%20call%20zero-configuration%20logging.
<!--web-->
<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-web</artifactId>
</dependency>

Next is the configuration of the corresponding logback

logback-spring.xml

<?xml version="1.0" encoding="UTF-8"?>

<configuration scan="true" scanPeriod="10 seconds">
    <!-- The log level is from low to high TRACE < DEBUG < INFO < WARN < ERROR < FATAL,If set to WARN,Is lower than WARN No information will be output -->
    <!-- scan:When this property is set to true If the configuration file changes, it will be reloaded. The default value is true -->
    <!-- scanPeriod:Set the time interval for monitoring whether the configuration file is modified. If no time unit is given, the default unit is milliseconds. When scan by true This property takes effect when. The default interval is 1 minute. -->
    <!-- debug:When this property is set to true When, it will be printed out logback Internal log information, real-time viewing logback Running status. The default value is false.  -->
    <contextName>logback</contextName>
    <!-- name The value of is the name of the variable, value The value defined by the variable. Values defined by are inserted into the logger In context. After defining variables, you can make“ ${}"To use variables. -->
    <property name="log.path" value="---The path where you want to output the log---"/>
    <!-- Color log -->
    <!-- Configure format variables: CONSOLE_LOG_PATTERN Color log format -->
    <!-- magenta:Magenta -->
    <!-- boldMagenta:Coarse red-->
    <!-- cyan:Cyan -->
    <!-- white:white -->
    <!-- magenta:Magenta -->
    <property name="CONSOLE_LOG_PATTERN"
              value="%yellow(%date{yyyy-MM-dd HH:mm:ss}) |%highlight(%-5level) |%blue(%thread) |%blue(%file:%line) |%green(%logger) |%cyan(%msg%n)"/>
    <!--Output to console-->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <!--This log appender It is used for development. Only the lowest level is configured. The log level output from the console is log information greater than or equal to this level-->
        <!-- For example, if you configure INFO Level, even if other locations are configured DEBUG Level logs will not be output -->
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>INFO</level>
        </filter>
        <encoder>
            <Pattern>${CONSOLE_LOG_PATTERN}</Pattern>
            <!-- Set character set -->
            <charset>UTF-8</charset>
        </encoder>
    </appender>
    <!--output to a file-->
    <!-- Time scrolling output level by INFO journal -->
    <appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- The path and file name of the log file being recorded -->
        <file>${log.path}/log_info.log</file>
        <!--Log file output format-->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
        <!-- The rolling strategy of the logger, recording by date and by size -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- Daily log archive path and format -->
            <fileNamePattern>${log.path}/info/log-info-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--Log file retention days-->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <!-- This log file only records info Rank -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>INFO</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>
    <!-- Time scrolling output level by WARN journal -->
    <appender name="WARN_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- The path and file name of the log file being recorded -->
        <file>${log.path}/log_warn.log</file>
        <!--Log file 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>
        <!-- The rolling strategy of the logger, recording by date and by size -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${log.path}/warn/log-warn-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--Log file retention days-->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <!-- This log file only records warn Rank -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>WARN</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>
    <!-- Time scrolling output level by ERROR journal -->
    <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- The path and file name of the log file being recorded -->
        <file>${log.path}/log_error.log</file>
        <!--Log file 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>
        <!-- The rolling strategy of the logger, recording by date and by size -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${log.path}/error/log-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--Log file retention days-->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <!-- This log file only records ERROR Rank -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>
    <!--
      <logger>Used to set the log printing level of a package or a specific class, and specify<appender>. 
      <logger>Only one name Properties,
      An optional level And an optional addtivity Properties.
      name:Used to specify that this logger A package or a specific class of constraints.
      level:Used to set the print level, regardless of case: TRACE, DEBUG, INFO, WARN, ERROR, ALL and OFF,
         If this property is not set, the current logger Will inherit the level of the superior.
    -->
    <!--
      use mybatis When I was young, sql Statement is debug It will not print until next, and here we only configure it info,So I want to see sql Statement, there are two operations:
      First handle<root level="INFO">Change to<root level="DEBUG">This will print sql,But there will be a lot of other messages in the log
      The second is to give it alone mapper Directory configuration under DEBUG Mode, the code is as follows, which is configured in this way sql The statement will print, and others are normal DEBUG Level:
     -->
    <!--development environment :Print Console -->
    <springProfile name="dev">
        <!--You can output data in a project debug Logs, including mybatis of sql journal-->
        <logger name="com.*.*.mapper" level="INFO"/>
        <!--
          root Node is a required node. It is used to specify the most basic log output level. There is only one node level attribute
          level:Used to set the print level, regardless of case: TRACE, DEBUG, INFO, WARN, ERROR, ALL and OFF,The default is DEBUG
          Can contain zero or more appender Element.
        -->
        <root level="INFO">
            <appender-ref ref="CONSOLE"/>
            <appender-ref ref="INFO_FILE"/>
            <appender-ref ref="WARN_FILE"/>
            <appender-ref ref="ERROR_FILE"/>
        </root>
    </springProfile>
    <!--production environment :output to a file-->
    <springProfile name="prod">
        <root level="INFO">
            <appender-ref ref="INFO_FILE"/>
            <appender-ref ref="WARN_FILE"/>
            <appender-ref ref="ERROR_FILE"/>
        </root>
    </springProfile>
</configuration>

Now the logback has been configured, and every corresponding place can be as detailed as possible. To write the yml file (the main part), in fact, it is to specify the file location and the current environment, whether to test or produce

spring:
  profiles:
    active: dev
logging:
  config: classpath:logback-spring.xml

Write AOP

Custom write annotation

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyLog {
    String value() default "";
}

Important: implementation method of custom annotation

@Aspect
@Component
public class SysLogAspect {
    
    @Value("${spring.application.name}")//The projectName here is configured in the yml defined by itself
    private String projectName;

    @Resource
    private SysLogService sysLogService;

    @Pointcut("@annotation(com.*.*.annotation.MyLog)")//Here you specify the specific location of your own annotation
    public void logPointCut() {
    }

    @AfterReturning("logPointCut()")
    public void saveSysLog(JoinPoint joinPoint) {
        try {
            //The entity class here is customized, which is more determined by its own business
            SysLog sysLog = new SysLog();
            //Acquisition method
            MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
            Method method = methodSignature.getMethod();
      
            //Method type
            MyLog myLog = method.getAnnotation(MyLog.class);
            String operation = myLog.value();
            sysLog.setOperation(operation);

            //Method name
            String className = joinPoint.getTarget().getClass().getName();
            String methodName = method.getName();
            sysLog.setMethod(className + "." + methodName);

            //Method parameters
            Object[] args = joinPoint.getArgs();
            String params = Arrays.toString(args);
            sysLog.setParam(params);

            //entry name
            sysLog.setProjectName(projectName);

		   ...........Various parameters

            //ip
            String ipAddress = HttpContextUtil.getIpAddress();
            sysLog.setIp(ipAddress);

            //Add logging
            //The method here can be implemented by yourself. MySQL's insertion method
            sysLogService.save(sysLog);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

Here are the database fields. Other fields can be done with their own business

Paste tools

public class HttpContextUtil {
    public static final String UNKNOWN = "unknown";

    public static HttpServletRequest getRequest() {
        return ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes()))
                .getRequest();
    }

    public static String getIpAddress() {
        HttpServletRequest request = getRequest();
        String ip = request.getHeader("x-forwarded-for");
        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.getRemoteAddr();
        }
        return ip;
    }
}

Write a controller to test it

 	@MyLog("test method")
    @ApiOperation("Test it")
    @PostMapping(value = "/test")
    public R<Object> test(@RequestBody String info) {
        return RUtils.success(info);
    }	

Open the test document and enter the parameters

Look at the database

Let's see if there is log output in the log output path of logback

[the external chain image transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-knw0aotw-1631582728451) (C: \ users \ xxxlomg \ appdata \ roaming \ typora user images \ image-2021091320122289. PNG)]

Click info and output according to the daily time

[the external chain image transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-nyyyw6kj-1631582728455) (C: \ users \ xxxlomg \ appdata \ roaming \ typora user images \ image-20210913201309451. PNG)]

The above is a primary log system. If there is any improper writing, please correct it~

Keywords: Java Spring Boot

Added by jck on Fri, 19 Nov 2021 09:10:49 +0200