Before in the log? Slf4j slF4J is discussed in this article. This article also learns about the log configuration of logBack from practical examples.

Logack profile

Logback website: https://logback.qos.ch/

At present, I have not read the source code of the logging framework, only how to use it. So no more “hot air”. The most intuitive perception is:

  • logbackandlog4jIt was written by one person
  • springbootThe default logging framework used islogback.
  • Three modules
    • logback-core
    • logback-classic
    • logback-access
  • For more information about performance, memory usage, testing, and documentation, see the source code and official website

Logback-core is the infrastructure on which other modules are built, and obviously logback-core provides some key common mechanisms. Logback-classic has the same status and functions as Log4J. It is also considered an improved version of Log4J, and it implements simple logging facade SLF4J. Logback-access is primarily a module that interacts with Servlet containers, such as Tomcat or Jetty, and provides some HTTP access-related functionality.

Configuration file details

This section describes the configuration items of the Logback configuration file.

configuration

This is the structure of the logback.xml configuration file.

<configuration scan="true" scanPeriod="60 seconds" debug="false">  
    <property name="glmapper-name" value="glmapper-demo" /> 
    <contextName>${glmapper-name}</contextName> 
    
    
    <appender>
        //xxxx
    </appender>   
    
    <logger>
        //xxxx
    </logger>
    
    <root>             
       //xxxx
    </root>  
</configuration>  
Copy the code

If you want to use spring to extend profile support, you need to name it logback-spring. XML

  • Scan: When this property is set to true, the configuration file will be reloaded if it changes. The default value is true.
  • ScanPeriod: indicates the interval for checking whether the configuration file has been modified. If no time unit is given, the default unit is milliseconds. This attribute takes effect when scan is true. The default interval is 1 minute.
  • Debug: When this attribute is set to true, internal logback logs are displayed to view the running status of logback in real time. The default value is false.

contextName

Each logger is associated with a Logger context, and the default context name is “default.” However, it can be set to a different name using the contextName tag to distinguish between records from different applications

property

The tag that defines the value of a variable. The property tag has two attributes, name and value; The value of name is the name of the variable, and the value of value is the value defined by the variable. Values defined by property are inserted into the Logger context. After you define variables, you can make “${name}” to use them. As shown in the XML above.

logger

Sets the log printing level for a package or class and specifies an appender.

root

The root logger, which is also a logger, has only one level attribute

appender

The component responsible for logging, described below

filter

A filter is actually a child of an appender. It exists as a filter, and executing a filter returns one of three enumerated values DENY, NEUTRAL, and ACCEPT.

  • DENY: Logs are immediately discarded and will not pass through other filters
  • NEUTRAL: The log is processed by the next filter in the sequence table
  • ACCEPT: Logs are processed immediately and will not pass through remaining filters

Case analysis

Start by configuring a very simple file. For example, I’m using logback-spring.xml. There is a slight difference in properties from logback.xml. Everything else is the same.

Project: springboot + web

Let’s take a look at the project catalog

Properties specifies the log level and the log output location:

# Set the application log level
logging.level.com.glmapper.spring.boot=INFO
# path
logging.path=./logs
Copy the code

Log output through the console

The logback-spring. XML configuration is as follows:

<configuration>
    <! -- The default console log output, usually in production environment is started in the background, this is not very useful -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <Pattern>%d{HH:mm:ss.SSS} %-5level %logger{80} - %msg%n</Pattern>
        </encoder>
    </appender>
    
    <root level="info">
        <appender-ref ref="STDOUT"/>
    </root>
</configuration>
Copy the code

Controller that prints logs

private static final Logger LOGGER =
LoggerFactory.getLogger(HelloController.class);
@Autowired
private TestLogService testLogService;

@GetMapping("/hello")
public String hello(a){
    LOGGER.info("GLMAPPER-SERVICE:info");
    LOGGER.error("GLMAPPER-SERVICE:error");
    testLogService.printLogToSpecialPackage();
    return "hello spring boot";
}
Copy the code

Verification results:

01:50:39. 633 INFO com. Glmapper. Spring. The boot. Controller. HelloController - glmapper - SERVICE: INFO 01:50:39. ERROR 633 com.glmapper.spring.boot.controller.HelloController - GLMAPPER-SERVICE:errorCopy the code

The above is printed through the console, at this time because we did not specify the output of the log file, because the logs folder will not be produced under the project directory.

The console does not print; it prints directly to a log file

Let’s take a look at the configuration file:

<configuration>
    <! Properties file: Find the corresponding configuration item in the Properties file -->
    <springProperty scope="context" name="logging.path"  source="logging.path"/>
    <springProperty scope="context" name="logging.level" source="logging.level.com.glmapper.spring.boot"/>
    <! -- The default console log output, usually in production environment is started in the background, this is not very useful -->
    <appender name="STDOUT"
        class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <Pattern>%d{HH:mm:ss.SSS} %-5level %logger{80} - %msg%n</Pattern>
        </encoder>
    </appender>
    
    <appender name="GLMAPPER-LOGGERONE"
    class="ch.qos.logback.core.rolling.RollingFileAppender">
        <append>true</append>
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>${logging.level}</level>
        </filter>
        <file>
            ${logging.path}/glmapper-spring-boot/glmapper-loggerone.log
        </file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <FileNamePattern>${logging.path}/glmapper-spring-boot/glmapper-loggerone.log.%d{yyyy-MM-dd}</FileNamePattern>
            <MaxHistory>30</MaxHistory>
        </rollingPolicy>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>
    
    <root level="info">
        <appender-ref ref="GLMAPPER-LOGGERONE"/>
    </root>
</configuration>
Copy the code

The appender specified by appender-ref is glmapper-LoggerOne, because there was no appender named glmapper-LoggerOne. Add an appender with the name glmapper-Loggerone.

Note that we directly assigned root’s appender-ref to our GLmapper-Loggerone appender. So the console will print only Bannar and then nothing, and all startup information will be printed in the log file glmapper-Loggerone.log.

But actually we don’t want my business logs to include this startup information. So at this point we need to do something with the Logger tag. Make simple changes to the configuration file above:

<logger name="com.glmapper.spring.boot.controller" level="${logging.level}"
        additivity="false">
    <appender-ref ref="GLMAPPER-LOGGERONE" />
</logger>

<root level="${logging.level}">
    <appender-ref ref="STDOUT"/>
</root>
Copy the code

Have root output to the console; Logger is responsible for the printing package com. Glmapper. Spring. The boot. The controller of the log.

The verification results

For example, we will print the log through our test controller, but there will be no log information on the console. The desired log file is in./logs/glmapper-spring-boot/glmapper-loggerone.log.

Relationship between logger and appender

Logger /appender/root logger/appender/root logger/ root

As mentioned earlier, root is the root logger, so they are the same thing; Root does not have a name and additivity attribute, but a level.

Appender is a log printing component that defines filter conditions, print output methods, scroll policies, encoding methods, print formats, and so on. But it’s just a print component, and it doesn’t mean much if we don’t use a Logger or root’s appender-ref to specify a specific appender.

So an appender lets our app know how to type, where to print, and what to print; Logger tells the application what to do. For example, logs for a class can be printed using this appender or logs for a package can be printed this way.

Appender configuration details

Here’s an appender named glmapper-LoggerOne in the example above:

<appender name="GLMAPPER-LOGGERONE"
    class="ch.qos.logback.core.rolling.RollingFileAppender">
    <append>true</append>
    <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
        <level>${logging.level}</level>
    </filter>
    <file>
        ${logging.path}/glmapper-spring-boot/glmapper-loggerone.log
    </file>
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
        <FileNamePattern>${logging.path}/glmapper-spring-boot/glmapper-loggerone.log.%d{yyyy-MM-dd}</FileNamePattern>
        <MaxHistory>30</MaxHistory>
    </rollingPolicy>
    <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
        <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
        <charset>UTF-8</charset>
    </encoder>
</appender>
Copy the code

Appender has two properties name and class; Name specifies the name of the appender, and class specifies the fully qualified name of the appender. The above statement is called GLMAPPER – LOGGERONE, class for ch. Qos. Logback. Core. Rolling. RollingFileAppender an appender.

The types of appender

  • ConsoleAppender: Adds logs to the console
  • FileAppender: Adds logs to a file
  • RollingFileAppender: Scrolls logs to a file, first to a specified file, and then to another file when a condition is met. It is a subclass of FileAppender

Append child tags

<append>true</append>
Copy the code

If true, the log is appended to the end of the file. If false, the existing file is emptied. The default is true.

The filter child tags

The filter was mentioned in the introduction; That’s what it does. One or more filters can be added to the appender, and logs can be filtered with any criteria. When appenders have multiple filters, they are executed in the order configured.

ThresholdFilter

Threshold filter: Filters out logs whose values are lower than the specified threshold. When the log level is equal to or above the critical value, the filter returns NEUTRAL. When the log level is lower than the threshold, logs are rejected.

<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
    <level>INFO</level>
</filter>
Copy the code

LevelFilter

Level filter: Filters logs by log level. If the log level is equal to the configuration level, the filter receives or rejects logs based on onMath(used to configure operations that match the filtering criteria) and onMismatch(used to configure operations that do not match the filtering criteria).

<filter class="ch.qos.logback.classic.filter.LevelFilter">   
  <level>INFO</level>   
  <onMatch>ACCEPT</onMatch>   
  <onMismatch>DENY</onMismatch>   
</filter> 
Copy the code

NEUTRAL, ACCEPT, DENY refer to the introduction of filter in the introduction above.

The file child tags

The file tag is used to specify the file name to be written to. It can be a relative directory or an absolute directory. If the parent directory does not exist, it is automatically created.

<file>
    ${logging.path}/glmapper-spring-boot/glmapper-loggerone.log
</file>
Copy the code

Path}/glmapper-spring-boot/glmapper-loggerone.log.

RollingPolicy child tags

This subtag is used to describe the scrolling strategy. This is only required if the appender’s class is a RollingFileAppender. This also involves moving and renaming files (A.log.2018.07.22).

TimeBasedRollingPolicy

The most commonly used rolling strategy, it develops the rolling strategy according to the time, is responsible for both rolling and starting rolling. This contains two more attributes:

  • FileNamePattern
  • maxHistory
<rollingPolicy 
    class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
    <! -- Log file output filename: rollback daily -->
    <FileNamePattern>
        ${logging.path}/glmapper-spring-boot/glmapper-loggerone.log.%d{yyyy-MM-dd}
    </FileNamePattern>
    <! -- Log file retention days -->
    <MaxHistory>30</MaxHistory>
</rollingPolicy>
Copy the code

The above configuration indicates that a log file is generated every day and stored for 30 days

FixedWindowRollingPolicy

A scrolling strategy for renaming files according to the fixed window algorithm.

Encoder child tags

Format the recorded events. It does two things:

  • Convert log information into byte arrays
  • Writes an array of bytes to the output stream
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
    <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50}
    - %msg%n</pattern>
    <charset>UTF-8</charset>
</encoder>
Copy the code

There is currently only one type of Encoder, PatternLayoutEncoder.

Define an Appcener that prints only error-level logs

 <! Error log appender: Generate log files daily -->
<appender name="ERROR-APPENDER" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <append>true</append>
    <! -- filter, only record error level logs -->
    <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
        <level>error</level>
    </filter>
    <! -- Log name -->
    <file>${logging.path}/glmapper-spring-boot/glmapper-error.log</file>
    <! Create a log file every day and save the log file for 30 days.
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
        <! -- Log file output filename: rollback daily -->
        <FileNamePattern>${logging.path}/glmapper-spring-boot/glmapper-error.log.%d{yyyy-MM-dd}</FileNamePattern>
        <! -- Log file retention days -->
        <MaxHistory>30</MaxHistory>
    </rollingPolicy>
    <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
        <! Format output: %d indicates the date, %thread indicates the thread name, %-5level indicates the level of 5 characters from the left. % MSG indicates the log message, %n indicates the newline character.
        <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
        <! - - - >
        <charset>UTF-8</charset>
    </encoder>
</appender>
Copy the code

Define an appender for output to the console

<! -- The default console log output, usually in production environment is started in the background, this is not very useful -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
        <Pattern>%d{HH:mm:ss.SSS} %-5level %logger{80} - %msg%n</Pattern>
    </encoder>
</appender>
Copy the code

Logger configuration details

<logger name="com.glmapper.spring.boot.controller"
        level="${logging.level}" additivity="false">
    <appender-ref ref="GLMAPPER-LOGGERONE" />
</logger>
Copy the code

The above described in the configuration file is: com. Glmapper. Spring. The boot. The controller under this package of ${logging. Level} will use glmapper – LOGGERONE level of log to print. Logger has three attributes and a child tag:

  • Name: used to specify thisloggerA package of constraints or a specific class.
  • Level: Used to set the print level (TRACE.DEBUG.INFO.WARN.ERROR.ALLOFF), and a valueINHERITEDOr synonymsNULLTo enforce the superior level. If this property is not set, the currentloggerWill inherit the rank of the superior.
  • Addtivity: Used to describe whether or not to move upwardloggerPass print information. The default istrue.

Appender-ref is used to specify a specific appender.

Different log isolation printing cases

In the previous example we had three types of appenders, one for specifying package constraints, one for controlling error levels, and one for the console. Then in this section we will implement printing different logs to different log files.

Log file isolation by package

This example we will com. Glmapper. Spring. The boot. The controller of the log output to glmapper – controller. The log. Will com. Glmapper. Spring. The boot. The service of the log output to glmapper – service. The log.

<! Print log to appender of glmapper-service.log -->
<appender name="GLMAPPER-SERVICE"
          class="ch.qos.logback.core.rolling.RollingFileAppender">
    <append>true</append>
    <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
        <level>${logging.level}</level>
    </filter>
    <file>
        ${logging.path}/glmapper-spring-boot/glmapper-service.log
    </file>
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
        <FileNamePattern>${logging.path}/glmapper-spring-boot/glmapper-service.log.%d{yyyy-MM-dd}</FileNamePattern>
        <MaxHistory>30</MaxHistory>
    </rollingPolicy>
    <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
        <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
        <charset>UTF-8</charset>
    </encoder>
</appender>

<! Glmapper-controller. log appender-->
<appender name="GLMAPPER-CONTROLLER"
          class="ch.qos.logback.core.rolling.RollingFileAppender">
    <append>true</append>
    <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
        <level>${logging.level}</level>
    </filter>
    <file>
        ${logging.path}/glmapper-spring-boot/glmapper-controller.log
    </file>
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
        <FileNamePattern>${logging.path}/glmapper-spring-boot/glmapper-controller.log.%d{yyyy-MM-dd}</FileNamePattern>
        <MaxHistory>30</MaxHistory>
    </rollingPolicy>
    <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
        <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
        <charset>UTF-8</charset>
    </encoder>
</appender>

<! -- This Logger constraint outputs logs from the.controller package to glmapper-Controller and error logs to Geror-appende; Geror-appende see above -->
<logger name="com.glmapper.spring.boot.controller" level="${logging.level}" additivity="false">
    <appender-ref ref="GLMAPPER-CONTROLLER" />
    <appender-ref ref="GERROR-APPENDER" />
</logger>

<! -- This Logger constraint outputs. Service package logs to glmapper-service and error logs to geror-appende; Geror-appende see above -->
<logger name="com.glmapper.spring.boot.service" level="${logging.level}" additivity="false">
    <appender-ref ref="GLMAPPER-SERVICE" />
    <appender-ref ref="GERROR-APPENDER" />
</logger>
Copy the code

Let’s see what the results are

1, glmaper – controller

2, glmapper – service

3, glmapper – error

Met our expectations, but there was a slight problem. An error occurs in the INFO log, which is normal. What if we don’t want errors in info? Appender-service can be used as an example to modify the filter: appender-service

Put the following:

<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
    <level>${logging.level}</level>
</filter>
Copy the code

Is amended as:

<filter class="ch.qos.logback.classic.filter.LevelFilter">
    <level>ERROR</level>
    <! -- Disable this log if it hits -->
    <onMatch>DENY</onMatch>  
    <! Use this rule if there is no hit -->
    <onMismatch>ACCEPT</onMismatch>  
</filter>
Copy the code

Also note here that level needs to be set to info in Logger.

Log file isolation by class

This is actually not the same as the above, but a bit more granular. In general, let’s say we have a timed task class that needs to log its information separately, so we can consider using class-based dimensions to constrain printing.

<! -- Separate appenders for special functions such as scheduling class logging -->
<appender name="SCHEDULERTASKLOCK-APPENDER" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <append>true</append>
    <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
        <level>${logging.level}</level>
    </filter>
    <file>${logging.path}/glmapper-spring-boot/scheduler-task-lock.log</file>
    <! Create a log file every day and save the log file for 30 days.
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
        <! -- Log file output filename: rollback daily -->
        <FileNamePattern>${logging.path}/glmapper-spring-boot/scheduler-task-lock.log.%d{yyyy-MM-dd}</FileNamePattern>
        <! -- Log file retention days -->
        <MaxHistory>30</MaxHistory>
    </rollingPolicy>
    <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
        <! Format output: %d indicates the date, %thread indicates the thread name, %-5level indicates the level of 5 characters from the left. % MSG indicates the log message, %n indicates the newline character.
        <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
        <! - - - >
        <charset>UTF-8</charset>
    </encoder>
</appender>

<! A class is specified -->
<logger name="com.glmapper.spring.boot.task.TestLogTask" level="${logging.level}" additivity="true">
        <appender-ref ref="SCHEDULERTASKLOCK-APPENDER" />
        <appender-ref ref="ERROR-APPENDER" />
    </logger>
Copy the code

Eventually the TestLogTask logs will be printed to their own separate log file. As follows:

Log file isolation based on the name of the custom logger

Logger’s name can also be used with classes, packages, etc.

Before we do the example, here is the code declared by the logger in the previous example for comparison, using the TestLogTask log as an example:

 private static final Logger LOGGER =
 LoggerFactory.getLogger(TestLogTask.class);
Copy the code

In getLogger we take the current object’s class as an argument to get its fully qualified name for printing (see below 3-).

2018-07-21 11:15:42. [003] - thread pool - 1-1. 3-2 - INFO com glmapper. Spring. The boot. Task. TestLogTask - 4-com.glmapper.spring.boot.task:infoCopy the code

Business class definition

We also define a class TestLogNameServiceImpl under the Service package

package com.glmapper.spring.boot.service;

@Service("testLogNameService")
public class TestLogNameServiceImpl implements TestLogNameService {

    private static final Logger LOGGER =
    LoggerFactory.getLogger("GLMAPPER-TEST-LOG");

    @Override
    public void print(a) {
        LOGGER.info("GLMAPPER-TEST-LOG:this is special logger-----info");
        LOGGER.error("GLMAPPER-TEST-LOG:this is special logger-------error"); }}Copy the code

Appender and Logger configuration

<appender name="ROOT-APPENDER" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <append>true</append>
    <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
        <level>${logging.level}</level>
    </filter>
    <file>${logging.path}/glmapper-spring-boot/glmapper-test.log</file>
    <! Create a log file every day and save the log file for 30 days.
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
        <! -- Log file output filename: rollback daily -->
        <FileNamePattern>${logging.path}/glmapper-spring-boot/glmapper-test.log.%d{yyyy-MM-dd}
        </FileNamePattern>
        <! -- Log file retention days -->
        <MaxHistory>30</MaxHistory>
    </rollingPolicy>
    <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
        <! Format output: %d indicates the date, %thread indicates the thread name, %-5level indicates the level of 5 characters from the left. % MSG indicates the log message, %n indicates the newline character.
        <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
        <! - - - >
        <charset>UTF-8</charset>
    </encoder>
</appender>

<! The name is the same as the string in the business class getLogger -->
<logger name="GLMAPPER-TEST-LOG" level="${logging.level}" additivity="true">
        <appender-ref ref="ROOT-APPENDER" />
        <appender-ref ref="ERROR-APPENDER" />
    </logger>
Copy the code

We expect that TestLogNameServiceImpl logs will not be printed to glmapper-service.log, but to glmapper-test.log.

1, the glmapper – test. The log

2, glmapper – service. The log

Meet our expectations.

How to use logback to print mybatis SQL statement

This is still a bit of a pit. Why. Take a look at this:

<settings>
    <setting name="logImpl" value="slf4j" />
</settings>
Copy the code

In mybatis-configration. XML, we associate a specific logging component with such a configuration item. But there is no logback in the logImpl implementation. So what to do? The only way to bridge to logback is slF4J.

Then configure the following in our logback-spring.xml:

 <! Print SQL statements to specific log files -->
<logger name="com.alipay.sofa.cloudplatform.common.dao" level="${logging.sql.level}" additivity="false">
    <appender-ref ref="SQL-APPENDER"/>
</logger>
Copy the code

There are a couple of things to note here. ${logging.sql.level} this must be debug, which is determined by the mybatis implementation itself. And here the name set com.alipay.sofa.cloudplatform.com mon. Dao values is our dao interface package path.

In a typical case, this method can only output to the console, and can not output files to log files; It is based on an internal implementation mechanism to steal a lazy. Mybatis does not display SQL with logback logs.

conclusion

This blog is mainly to sort out some log configuration accumulated in recent work, and summarize every detail for a memo. If you have time will consider looking at the source of a logging framework. In fact, I think it is very necessary, after all, the log component is the need to log file drop disk, this will involve a lot of performance problems, buffer problems, queue problems, of course, there are some lock problems, synchronous printing or asynchronous printing problems. Interested partners can take a look, and then share with us.

Ant Financial SOFABoot and SpringBoot are ready to write some articles, if you are interested in the first wave.

SOFABoot GitHub portal