Teach you step by step how to use SpringBoot logs

1. Are logs important?

Are logs in programs important? Before answering this question, let me first tell you an example:

The author is particularly impressed by a colleague who received urgent bug feedback from customers last year. Although the log file was requested, many key steps did not print logs, resulting in a very slow troubleshooting process. The problem could not be found for several hours, and no solution could be provided. As a result, the client program has been blocked, ultimately resulting in a lot of losses. Afterwards, after careful consideration, the bug was successfully reproduced. It was actually caused by a very inconspicuous data conversion. However, due to the lack of log content, it is very difficult to investigate. In fact, as long as the log output is performed before and after data conversion, this problem is a matter of glance. But unfortunately it didn’t happen. At the end of the story, the development department still received complaints from customers, which affected the department’s performance.

For students who have just learned programming, many people don't care about logs. When we do code reviews, we often find that some new students like to write a long method, and there are very few comments and logs in the middle.

Frankly speaking, this is a very bad habit. It means that if there is a bug in the method in the future, or it needs to be iterated, it will take a lot of time to clarify the idea of ​​​​the method. Don't be superstitious about "you don't need to write comments and logs if you know the meaning of the method names and field names." That's because their business scenarios are not complex enough. Taking the author as an example, complex scenarios involve many formulas and strange regulations. Without writing notes and logs, no one can maintain it in the future.

So it’s important to remember that logging is very important during development. It helps developers understand what is happening in a program and why errors or exceptions occur in certain situations. By viewing logs, developers can easily locate and resolve issues, and can better monitor and adjust application performance, troubleshooting and security checks when necessary.

picture

2. Log classification

The original log classification was proposed by Eric Allman, the developer of Syslog, in 1981. Since then, this level classification system has been widely used in logging and information processing in various fields. Next we will introduce the commonly used log levels

  • TRACE

Is the lowest level of logging, used to output the most detailed debugging information, usually used for development and debugging purposes. In a production environment, TRACE level logging should be turned off to avoid outputting too much useless information.

  • DEBUG

It is used to output some debugging information in the program, usually used in the development process. Like TRACE, DEBUG level logging should be turned off in production environments.

  • INFO

Used to output some key information when the program is running normally, such as program startup, running logs, etc. Typically INFO level logging is turned on in a production environment.

  • WARN

It is used to output some warning messages to indicate that some exceptions or errors may occur in the program. In applications, WARN level logging is usually used to record some non-fatal exception information so that these problems can be discovered and dealt with in time.

  • ERROR

It is used to output some error information when the program is running, usually indicating that some unpredictable errors have occurred in the program. In applications, ERROR level logging is usually used to record some fatal exception information so that these problems can be discovered and dealt with in time.

Of course, in addition to these five levels, there are some logging frameworks that define other levels, such as CRITICAL in Python, FATAL in PHP, etc. CRITICAL and FATAL are both used to indicate that a fatal error or exception has occurred in the program, that is, an unrecoverable error. Of course, for what we are going to talk about today, it is enough to know the above five log levels.

3. Commonly used log plug-ins

  1. Log4j (born in 1999)

Log4j is one of the earliest popular logging frameworks in the Java field. It was developed by Ceki Gülcü and later taken over by the Apache Software Foundation. Log4j provides flexible configuration options, multiple output destinations, log levels, and hierarchical log systems. Although Log4j 1 was a huge success in its time, it had limitations in terms of performance and certain features, so it later evolved into Log4j 2.

  1. SLF4J (born in 2004)

Strictly speaking, SLF4J (Simple Logging Facade for Java) is not a plug-in, but a log facade interface developed by Ceki Gülcü. It provides a unified logging abstraction for Java applications, allowing developers to use a consistent API for logging without relying directly on a specific logging implementation. SLF4J can be used in conjunction with a variety of underlying logging frameworks (such as Logback, Log4j 2, java.util.logging, etc.).

  1. Logback (born in 2009)

Logback is a logging framework developed by Ceki Gülcü, who is also the author of Log4j. Logback is the successor to Log4j 1 and aims to provide higher performance, more flexible configuration and modern logging solutions. Logback supports asynchronous logging, multiple output formats, flexible configuration, and tight integration with SLF4J.

  1. Log4j 2 (born in 2014)

Log4j 2 is the next generation version of Log4j developed by the Apache Software Foundation. It introduces many new features such as asynchronous logging, plugin support, rich filters, etc., aiming to provide better performance and flexibility. Log4j 2 is designed with the limitations of Log4j 1 in mind and supports multiple configuration methods.

  1. short story

It is not difficult to notice that an interesting little story is that the first three log plug-ins were all developed by Ceki Gülcü, but Log4j 2 was not. Although many people now think that log4j2 was also written by him, we can see it on github His personal description is "Unaffiliated with log4j 2.x." (has nothing to do with log4j 2.x), so log4j2 and logback both claim to be the follow-up version of log4j. Which one is the orthodox sequel? This is left to the readers to ponder.

picture

4. Appearance mode and SLF4J

Before explaining more plug-in details, let’s take a look at the most commonly used SLF4J. We mentioned earlier that SLF4J (Simple Logging Facade for Java) is a log facade interface developed by Ceki Gülcü, so obviously the facade mode is used here ( That is Facade or appearance mode), the author is more accustomed to calling it facade mode, and it will be called facade mode later.

picture

1. Appearance mode

Definition: Appearance pattern is a structural design pattern that provides a simple interface that encapsulates the underlying complex subsystem, making it easier for clients to use this subsystem.

Purpose: The purpose of appearance mode is to hide the complexity of the underlying system and reduce access costs.

If the definition is a bit abstract, then we can take examples from life. We all know that smart homes are becoming more and more popular now, that is, there are many smart appliances installed in the home, from TVs, air conditioners, to porch lights and even curtains. Intelligent. This kind of family often has a control center. We don't need to turn on the TV manually. We only need to say to the control center: "Little A, little A, help me turn on the TV and adjust the volume to 30%." The TV will turn on and turn on. volume adjustment

In this case, we don't need to know how to turn on the TV or adjust the volume. Everything can be adjusted with the simplest words. In the same way, the current camera functions on mobile phones: sensitivity, focus, white balance and other details are automatically completed for you, so you don't have to worry about these complicated contents now, you only need to press the camera button.

picture

This is the meaning of appearance mode. Appearance mode is designed to hide the complexity of the system, so that the client only has access to one appearance class without being exposed to the complex logic inside the system.

2. The birth of SLF4J

In the early days of using logging frameworks, applications usually needed to be directly coupled with the specific logging framework, which led to the following problems:

  • code dependencies

The application needs to directly reference the specific log framework, which leads to strong coupling between the code and the log framework, making it difficult to meet the flexible configuration of the log framework by the application.

  • The logging framework is not unified

When using different logging frameworks, the application needs to write code according to the specific logging framework, which not only increases the difficulty of development, but also requires a lot of code changes when switching between multiple logging frameworks.

  • Performance issues

In the case of frequent log output, performance problems may occur due to different implementation methods and API designs of the log framework.

In order to solve these problems, SLF4J provides a set of general logging facade interfaces, allowing applications to record log information through these interfaces without directly referencing a specific logging framework. In this way, applications can flexibly configure and switch between different logging frameworks while also achieving better performance. Therefore, I strongly recommend that you use SLF4J instead of directly connecting to a specific logging framework.

3. Use of SLF4J

First, we need to introduce the package into the project, but if you use springboot, various spring-boot-starter starters have already been referenced, so it is best to confirm before referencing:

<dependency>
  <groupId>org.slf4j</groupId>
  <artifactId>slf4j-api</artifactId>
  <version>1.7.32</version>
</dependency>

Then add a line to the class where we want to print the log; private static final Logger logger = LoggerFactory.getLogger(XXXX.class); and it can be used, as follows:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MyClass {
    
    
    private static final Logger log = LoggerFactory.getLogger(MyClass.class);
    //...
    public static void main(String[] args) {
    
    
        log.info("This is an info message.");
    }
}

If we quote lombok, we can also use lombok's annotation @Slf4j instead of the above sentence to use SLF4J, as follows:

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class MyClass {
    
    
    public static void main(String[] args) {
    
    
        log.info("This is an info message.");
    }
}

However, we all know that SLF4J is just a facade. In other words, it only has an interface but no implementation. If we run it directly at this moment, printing logs is useless.

picture

Therefore, if we want to run it, we must arrange an implementation for SLF4J, and the most commonly used ones currently are logback and log4j2. Let us continue to look down.

5. The battle between the two heroes

In fact, there are many reviews on the Internet about Logback and Log4j 2, so there is no need to go into details. They mainly focus on performance. Judging from the current feedback, Log4j 2 appeared several years later, but it still has the advantage of being a latecomer, and its performance will be better than Logback OK. Of course, the performance of Logback itself is also very strong. It is sufficient for most scenarios, and the configuration is relatively intuitive. It is the default log plug-in used by spring-boot.

picture

Therefore, you can choose anyone. If you don’t want to bother, you can directly use the Logback that comes with spring-boot. If you have high requirements for log performance, it is safer to use log4j2. We will introduce the two separately next.

1. Logback

1. Quote

Since Logback is the default log framework of spring-boot, there is no need to reference it. However, for non-spring-boot projects, you can make the following reference

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

The core module of Logback is logback-classic, which provides an implementation of SLF4J, is compatible with Log4j API, and can seamlessly replace Log4j. It already contains the logback-core module, and logback-core, as the name suggests, is the core function of logback, including logger, Appender, Layout, etc. Other logback modules depend on this module

2. Configuration

Logback can be configured via XML or Groovy. The following takes XML configuration as an example. The name of logback's XML configuration file is usually logback.xml or logback-spring.xml (in Spring Boot), which needs to be placed in the root directory of the classpath.

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <!--定义日志文件的存储地址,使用Spring的属性文件配置方式-->
    <springProperty scope="context" name="log.home" source="log.home" defaultValue="logs"/>

    <!--定义日志文件的路径-->
    <property name="LOG_PATH" value="${log.home}"/>

    <!--定义控制台输出-->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%-5relative [%thread] %-5level %logger{35} - %msg%n</pattern>
        </encoder>
    </appender>

    <!--定义 INFO 及以上级别信息输出到控制台-->
    <root level="INFO">
        <appender-ref ref="console"/>
    </root>

    <!--定义所有组件的日志级别,如所有 DEBUG-->
    <logger name="com.example" level="DEBUG"/>

    <!-- date 格式定义 -->
    <property name="LOG_DATEFORMAT" value="yyyy-MM-dd"/>

    <!-- 定义日志归档文件名称格式,每天生成一个日志文件 -->
    <property name="ARCHIVE_PATTERN" value="${LOG_PATH}/%d{${LOG_DATEFORMAT}}/app-%d{${LOG_DATEFORMAT}}-%i.log.gz"/>

    <!--定义文件输出,会根据定义的阈值进行切割,支持自动归档压缩过期日志-->
    <appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_PATH}/app.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!--定义日志文件切割的阈值,本例是 50MB-->
            <maxFileSize>50MB</maxFileSize>
            <!--定义日志文件保留时间,本例是每天生成一个日志文件-->
            <fileNamePattern>${ARCHIVE_PATTERN}</fileNamePattern>
            <maxHistory>30</maxHistory>
            <!-- zip 压缩生成的归档文件 -->
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>50MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!-- 删除过期文件 -->
            <cleanHistoryOnStart>true</cleanHistoryOnStart>
        </rollingPolicy>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{35} - %msg%n</pattern>
        </encoder>
    </appender>

    <!--定义 ERROR 级别以上信息输出到文件-->
    <logger name="com.example.demo" level="ERROR" additivity="false">
        <appender-ref ref="file"/>
    </logger>

    <!--异步输出日志信息-->
    <appender name="asyncFile" class="ch.qos.logback.classic.AsyncAppender">
        <discardingThreshold>0</discardingThreshold>
        <queueSize>256</queueSize>
        <appender-ref ref="file"/>
    </appender>

    <!--定义INFO及以上级别信息异步输出到文件-->
    <logger name="com.example" level="INFO" additivity="false">
        <appender-ref ref="asyncFile"/>
    </logger>

</configuration>

Among them, it mainly includes the following configurations:

  • springProperty defines the storage path of the log file, which can be set through Spring's property file configuration. If not configured, it will be stored in the logs directory by default.
  • The appender defines the target of log output, which includes console output and file output, and can be configured according to needs.
  • Root defines the default log level and output target. By default, log information above the INFO level will be output to the console, which can be modified according to actual needs.
  • The logger defines the log levels and output targets of different components. For example, the log level of the com.example component is defined here as DEBUG, and the log level of the com.example.demo component is ERROR, and is output to a file.
  • rollingPolicy defines the cutting rules and archiving policy of log files. It is defined here that log files are cut into 50MB each, one log file is generated every day, expired files are compressed and deleted, and log files are retained for up to 30 days.
  • The encoder defines the output format of log information, and the specific format can be defined by yourself.
  • asyncAppender defines a way to output logs asynchronously. For high concurrency, asynchronous output can be used to improve system performance.
  • discardingThreshold defines the threshold of the asynchronous output queue. When the amount of data in the queue exceeds this value, the earliest data put in will be discarded. Setting it to 0 here means that the queue will not discard any data.
  • queueSize defines the size of the asynchronous output queue. When the queue is full, it will wait for the data in the queue to be consumed before putting the data into the queue. It is set to 256 here.
3. Demonstration

We create a new ordinary project (non-spring project), and after referencing Logback, copy the above configuration file into logback.xml, and then set the project structure to the following pattern

picture

The code for two of the classes is as follows:

public class Main {
    
    
    private static final Logger log = LoggerFactory.getLogger(Main.class);
    public static void main(String[] args) {
    
    
        log.trace("This is a Main trace message.");
        log.debug("This is a Main debug message.");
        log.info("This is a Main info message.");
        log.warn("This is a Main warn message.");
        log.error("This is a Main error message.");
        Slave.main(args);
    }
}

public class Slave {
    
    
    private static final Logger log = LoggerFactory.getLogger(Slave.class);
    public static void main(String[] args) {
    
    
        log.trace("This is a Slave trace message.");
        log.debug("This is a Slave debug message.");
        log.info("This is a Slave info message.");
        log.warn("This is a Slave warn message.");
        log.error("This is a Slave error message.");
    }
}

We want to achieve this effect. First, the logs must be output to the console and log files at the same time, and different levels of code will output different log levels. Then we can make some adjustments to the above xml:

Because it is a non-Spring project, there is no need to use tags like springProperty. We can just write a log file address directly.

<!--定义日志文件的路径-->
<property name="LOG_PATH" value="D://logs/log"/>

Remove the original root and logger labels, and create two new loggers for two different levels. We want the inner layer to output the debug level and the outer layer to output the info level. We can set it like this. And output to the console and log file at the same time

<logger name="com.zhanfu" level="INFO">
    <appender-ref ref="file" />
    <appender-ref ref="console"/>
</logger>
<logger name="com.zhanfu.child" level="DEBUG" additivity="false">
    <appender-ref ref="file" />
    <appender-ref ref="console"/>
</logger>

When we run Main.main, we can get the following log. Slave can output debug level, and Main can only output info and above levels.

picture

4. Details

In fact, there are two details in our above demonstration that need to be paid attention to. One is ours

 <logger name="com.zhanfu.child" level="DEBUG" additivity="false">

An additivity="false" attribute is used. This is actually because the logger tag may have a hierarchical relationship when locking a certain directory. For example, among our two loggers, one targets the directory com.zhanfu and the other is com.zhanfu.child, and the latter will be included by the former.

When our com.zhanfu.child.Slave prints logs, of course the latter (more precise) settings will be used, but are the former settings still used? It depends on additivity="false". If we remove additivity="false" (the default value of this attribute is true), then print the log

picture

You will find that the Slave log is logged twice, and even the debug level is logged twice. We can understand this logic as inheritance. The subclass executes it once, and the parent class can execute it again, but the level attribute is still Will use subclasses instead of parent classes.

Another point is that we have deleted the root label. Root is actually a top-level logger, and other loggers can be regarded as its subclasses. If those loggers have areas that are not covered, or they do not specify additivity="false", then Finally the root settings will be used. For example, we change the settings to the following:

<logger name="com.zhanfu" level="INFO">
    <appender-ref ref="file" />
    <appender-ref ref="console"/>
</logger>
<logger name="com.zhanfu.child" level="DEBUG">
    <appender-ref ref="file" />
    <appender-ref ref="console"/>
</logger>
<root level="WARN">
    <appender-ref ref="console"/>
</root>

As a result, in the output log of the console, Main will be repeated twice and Slave will be repeated three times, as follows:

picture

But because our root is only configured with console output, the log file will not change.

picture

2. Log4j 2

1. Quote

For spring-boot projects, in addition to referencing Log4j 2, we also need to remove the reference to Logback. For ordinary projects, we only need to reference it directly. But pay attention to our principle, use Log4j2 through SLF4J, so quote the following package

<dependency>
     <groupId>org.apache.logging.log4j</groupId>
     <artifactId>log4j-slf4j-impl</artifactId>
     <version>2.13.3</version>
</dependency>

It contains the implementation of Log4j2 and the API of SLF4J, as follows:

picture

2. Configuration

The configuration logic of Log4j2 is similar to logback, only some details are different, such as the capitalization of the first letter of Logger, etc. Finally, we write such a log4j2.xml

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="INFO" monitorInterval="30">
  <Properties>
    <Property name="logPath">logs</Property>
  </Properties>
  <Appenders>
    <Console name="Console" target="SYSTEM_OUT">
      <PatternLayout pattern="%d{ISO8601} [%t] %-5level %logger{36} - %msg%n" />
    </Console>
    <RollingFile name="File" fileName="${logPath}/example.log"
                 filePattern="${logPath}/example-%d{yyyy-MM-dd}-%i.log">
      <PatternLayout pattern="%d{ISO8601} [%t] %-5level %logger{36} - %msg%n"/>
      <Policies>
        <SizeBasedTriggeringPolicy size="10 MB"/>
      </Policies>
      <DefaultRolloverStrategy max="4"/>
    </RollingFile>
  </Appenders>
  <Loggers>
    <Logger name="com.zhanfu.child" level="DEBUG">
      <AppenderRef ref="File"/>
      <AppenderRef ref="Console"/>
    </Logger>
    <Logger name="com.zhanfu" level="INFO">
      <AppenderRef ref="File"/>
      <AppenderRef ref="Console"/>
    </Logger>
    <Root level="WARN">
      <AppenderRef ref="Console" />
    </Root>
  </Loggers>
</Configuration>
  • Properties

Partially defines a logPath attribute for easy reference in other places.

  • Appenders

Two Appenders are defined: Console and RollingFile, which output logs to the console and files respectively. RollingFile uses RollingFileAppender and sets the log rolling policy and default number of backup files.

  • Loggers

Three Loggers are defined: the log level of com.zhanfu.child is DEBUG, the log level of com.zhanfu is INFO, and the log level of Root Logger is WARN. And two Appenders are specified: Console and File.

3. Demonstration

Since our configuration logic has not changed, the log results are still the same:

picture

picture

3. Comparison

Both Log4j2 and Logback are among the most popular logging frameworks for Java applications. They are both highly configurable and flexible in use, and provide a range of useful features such as asynchronous logging and log filtering. The following is a comparison and summary from the aspects of configuration traversal, functionality, performance, etc.

Configure traversability

Logback's configuration file format is relatively simple and easy to read and modify. It supports symbols to reference variables, properties, environment variables, etc. In addition, it supports conditional logging (based on log level, logger name or time, etc.), as well as rolling file size or date, etc.

Log4j2's configuration file format is more complex, but it provides a large number of options in the configuration file to control logging. It supports directly declaring context parameters, filters, outputters, Appenders, etc. in the configuration file, which makes its configuration more flexible. In addition, Log4j2 also supports asynchronous logging, log event serialization and performance optimization.

Overall, both support configuration traversal well, but Log4j2 offers more options and greater flexibility.

Feature

Logback provides a series of basic logging functions, such as asynchronous Appender, rolling files, GZIP compression, etc. It also supports use with SLF4J and can be easily integrated with other logging frameworks.

Log4j2 provides more advanced features, such as asynchronous logging, performance optimization, and log event serialization. It also supports Lambda expressions, which can make the logger more concise and readable. In addition, Log4j2 also supports big data processing frameworks such as Flume and Kafka, and can easily send log records to these frameworks.

Overall, Log4j2 provides more advanced features and can be better integrated with big data processing frameworks.

performance

Logback performs well and can handle high-throughput logging. It uses an asynchronous logger and takes advantage of multi-threading to improve performance.

Log4j2 is more powerful in terms of performance. It uses asynchronous loggers and multi-threading, and also introduces the RingBuffer data structure and Disruptor library to speed up the delivery and processing of log events. This gives it higher throughput and lower latency than Logback.

To sum up, Log4j2 is more powerful than Logback in terms of configuration flexibility, functionality and performance. But if you need a lightweight logging framework or only basic logging functions, Logback is also a good choice.

But if we reference both at the same time, will an error be reported? Or will you use one of them?

 <dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.12</version>
</dependency>
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-slf4j-impl</artifactId>
    <version>2.13.3</version>
</dependency>

As you can see, SLF4J discovered that two plug-in frameworks exist in the system at the same time, and finally chose to use Logback

picture

Summarize

After studying this article, you should have an understanding of these commonly used frameworks and be able to apply them based on them. This time we did not talk about the source code, nor did we talk about its configuration and advanced use in depth. We will learn these slowly later. But now what I want you to know is this. You must write a good log, you must write a good log, you must write a good log. Say important things three times! This is an important basis for distinguishing newcomers from veterans, and it is also the only way to make troubleshooting easier!

In addition, many middleware now reference log plug-ins. When using middleware as a whole project, we must promptly discover and resolve plug-in conflicts to avoid invalidation of our own log configuration. This is also a point that programmers should pay attention to.

Guess you like

Origin blog.csdn.net/qq_43842093/article/details/132953586