01-Java-Log framework

1 Overview of logging technology

1.1 What is logging technology

Logging technology is a technology that records and stores information when an application is running. It captures information such as application status, events, errors, and warnings and saves it to a log file or other storage medium. Logging technology can help developers and operation and maintenance teams understand the running status of applications and perform troubleshooting, performance optimization, security monitoring, and operational analysis.

Here are some key concepts and components of logging technology:

  • Log level: The log level is used to identify the importance and severity of the log. Common log levels include DEBUG, INFO, WARN, ERROR, FATAL, etc. Developers can choose appropriate log levels to record different types of information based on their needs.
  • Log format: The log format defines the structure and content of the log records. It usually includes timestamp, log level, log message and other relevant information. Developers can customize the log format according to their needs to better understand and analyze the logs.
  • Log output: Log output specifies the destination for logging. Common log output includes console output, file output, remote log server, etc. Developers can configure multiple log outputs as needed for logging and analysis in different environments.
  • Logging framework: A logging framework is a software library or tool for recording and managing logs. It provides a set of API and configuration options that enable developers to generate and process logs conveniently. Common logging frameworks include Log4j, Logback, java.util.logging, etc.
  • Log analysis: Log analysis refers to the processing and analysis of large amounts of log data to extract useful information and insights. Log analysis tools can help developers and operation and maintenance teams discover potential problems, optimize performance, monitor security, and understand user behavior.

1.2 Logs we have access to

We used to use the output statement (System.out.println) to print logs. What's the problem?

  • The log is printed on the console. When we close the console, the log disappears.
  • It is not convenient to record logs to other locations (such as files, databases, etc.)
  • If you want to cancel or modify the log, you need to modify the source code

1.3 The role of introducing log technology

Logging technology was introduced to better manage and track the operation of applications. Here are some benefits of using logging technology:

  • Troubleshooting and debugging : When an application has a problem, logs can provide detailed information about errors and exceptions, helping developers quickly locate and solve the problem. By viewing logs, you can understand the status, execution path, and input and output data of the application at a specific point in time.
  • Monitoring and performance optimization : Logs can record application performance metrics, resource usage, and system status. By analyzing logs, you can identify performance bottlenecks and optimize code and resource allocation to improve application performance and scalability.
  • Security and Compliance : Logs can record application security events, user actions, and sensitive data access. These logs can be used to monitor potential security threats, track user activity and meet compliance requirements.
  • Operations and maintenance : Logs can provide application health, error, and warning information. By monitoring logs, you can discover and solve potential problems in time to ensure the stable operation of the application.
  • Analysis and Insights : By analyzing logs, you can understand application usage, user behavior, and business trends. These insights can help you make better business decisions, improve product functionality, and deliver a better user experience.

The introduction of logging technology can help developers and operation and maintenance teams better understand and manage the running status of applications, improve development efficiency, reduce fault repair time, and provide a better user experience.

1.4 System of logging technology

Architecture of logging technology:

  • Log facade (interface, rules): JCL (Jakarta Commons Logging provided by Apache Jakarta), SLF4J (Simple Logging Facade for Java), Log4j2 API
  • Log implementation (framework): JUL (java util logging), Logback, Log4j, Log4j2-core

The log facade is a set of standards for designing log frameworks. All log frameworks need to implement these interfaces.

Log4j implements JCL

Logbook implements SLF4j

Log4J2-core implements the Log4j2 API

illustrate:

Because they were dissatisfied with the JCL interface, someone came up with SLF4J; because they were dissatisfied with the performance of Log4j, so Logback came out

​Logback is a logging framework based on the SLF4J log specification.

The historical order of log frame appearance

Log4j --> JUL --> JCL -> Slf4j --> Loback --> Log4j2

2 JUL

JUL (Java Util Logging) is the Java platform’s own (native) logging framework. It is part of the Java standard library and requires no additional dependencies. JUL provides a simple logging API that can be used to record log information in applications.

2.1 JUL main components

The main components of JUL include:

  • Logger : The main component used to record log information. Logger instances can be obtained through the Logger.getLogger() method . Logger is usually the entry program for applications to access the logging system.
  • Handler : Also known as Appenders, used to process the output of logging. Each Loigger will be associated with a group of Handlers. The Logger will hand over the log to the associated Handlers for processing, and the Handler will be responsible for recording the log. Logging can be sent to different destinations such as console, file, database, etc. (Handler is an abstraction, and specific implementation is required to determine where the log needs to be sent)
  • Formatter : Also known as Layout, it is used to format the output of logging records. You can define the log record format, including date, time, log level, class name and other information.
  • Level : used to control the level of logging. Different log levels can be set, such as INFO, DEBUG, WARNING, ERROR, etc. Only log records that reach the specified level will be recorded.
  • Filters: Customize which messages will be recorded and which messages will be ignored as needed

JUL's configuration file is a text file, usually named logging.properties. You can configure the logging level, output destination, format, etc. by modifying the configuration file.

2.2 Use of JUL

1) Get the Logger instance : Use the Logger.getLogger() method to get the Logger instance, passing in a unique name as a parameter. Usually the fully qualified name of the class is used as the name.

public static final Logger LOGGER = Logger.getLogger(类名);

2) Configure log level : You can configure the log level by modifying the configuration file logging.properties. In the configuration file, you can set different log levels for different packages or classes.

Configuration files can be placed in one of the following locations:

  • in the application's root directory.
  • under src/main/resourcesthe directory (for Maven or Gradle projects).
  • Under WEB-INF/classesthe directory (for web applications)

logging.properties

# 设置根日志级别为INFO
.level=INFO

# 设置com.clear包的日志级别为DEBUG
com.clear.level=DEBUG

3) Use different methods of the Logger instance to record different levels of log information.

Demo:

import java.util.logging.Logger;

public class JULTest {
    
    
    // 获取Logger实例
    public static final Logger LOGGER = Logger.getLogger("com.clear.JULTest");
    @Test
    public void test1(){
    
    
        LOGGER.severe("server");  // 严重
        LOGGER.warning("warning");  // 警告
        LOGGER.info("info");  // 信息
    }
}

2.3 Log level

Record log information: Use different methods of the Logger instance to record different levels of log information.

Commonly used log levels include:

  • SEVERE: The highest level log, indicating serious errors.
  • WARNING: Warning level logs indicating potential problems.
  • INFO: Information level log , indicating general operating information. (default)
  • CONFIG: Configuration level log, indicating configuration information.
  • FINE: Fine-grained debugging information.
  • FINER: More fine-grained debugging information.
  • FINEST: The most fine-grained debugging information.
LOGGER.severe("This is a severe message");
LOGGER.warning("This is a warning message");
LOGGER.info("This is an info message");
LOGGER.config("This is a config message");
LOGGER.fine("This is a fine message");
LOGGER.finer("This is a finer message");
LOGGER.finest("This is a finest message");

Additionally, there are two special levels:

  • ALL: Enables logging of all messages
  • OFF: can be used to turn off logging

2.4 Log configuration

// 获取Logger实例
public static final Logger LOGGER = Logger.getLogger("com.clear.JULTest");

@Test
public void test2() throws IOException {
    
    
    // 禁用父级处理器。日志消息将不会传递给父级处理器进行处理。
    LOGGER.setUseParentHandlers(false);
    // 创建了一ConsoleHandler实例
    ConsoleHandler consoleHandler = new ConsoleHandler();
    // 设置其日志格式为SimpleFormatter。ConsoleHandler用于将日志消息输出到控制台。
    consoleHandler.setFormatter(new SimpleFormatter());
    // 设置日志级别,所有级别的日志消息都将被记录。
    consoleHandler.setLevel(Level.ALL);

    // 创建一个FileHandler实例
    // 为了简单,我们将异常抛出去
    FileHandler fileHandler = new FileHandler("D:/test/jul.log",true);
    fileHandler.setFormatter(new SimpleFormatter());
    fileHandler.setLevel(Level.WARNING);

    LOGGER.setLevel(Level.ALL);
    // 将ConsoleHandler添加到LOGGER的处理器列表中,以便将日志消息发送到控制台。
    LOGGER.addHandler(consoleHandler);
    // 将FileHandler添加到LOGGER的处理器列表中,以便将日志消息发送到文件中
    LOGGER.addHandler(fileHandler);
    // 日志输出
    LOGGER.severe("severe");
    LOGGER.warning("warning");
    LOGGER.info("info");
    LOGGER.config("config");
    LOGGER.fine("fine");
    LOGGER.finer("finer");
    LOGGER.finest("finest");
}

2.5 The parent-child relationship between Loggers

​ In JUL (Java Util Logging), a parent-child relationship can be established between loggers (Loggers). This parent-child relationship is determined by the name of the logger (that is, a tree structure is established to store the parent-child relationship).

The establishment of the parent-child . When creating a new logger, you can specify the name of its parent logger. If the name of the parent logger is not specified, it defaults to the root logger (RootLogger) .

By default, the child Logger will inherit the properties of the parent Logger.

  • Any logger is a singleton, and there is only one with the same name.
// 获取Logger实例
public static final Logger LOGGER = Logger.getLogger("com.clear.JULTest");

@Test
public void test3() {
    
    
    // 任何一个logger都是单例的,名字相同的只有一个
    Logger logger = Logger.getLogger(JULTest.class.getName());
    System.out.println(LOGGER.hashCode());  // 4959864
    System.out.println(logger.hashCode());  // 4959864
    System.out.println(LOGGER == logger);  // true
}

All Logger instances are managed uniformly by LoggerManager

@Test
public void test4(){
    
    
    Logger logger = Logger.getLogger("com.clear.JULTest");
    // todo 如果没有指定父级日志记录器的名称,则默认为根日志记录器(RootLogger)
    System.out.println("默认父Logger:"+logger.getParent()); 
    // 默认父Logger:java.util.logging.LogManager$RootLogger@4bae78
    System.out.println("默认父Logger名字:"+logger.getParent().getName());  
    // 默认父Logger名字:

    // 父Logger
    // todo 父子关系的建立是通过日志记录器的名称来实现的
    Logger pLogger = Logger.getLogger("com.clear");
    System.out.println("建立父子关系以后:");
    System.out.println("父Logger:"+logger.getParent());  
    // 父Logger:java.util.logging.Logger@1764bce
    System.out.println("父Logger名字:"+logger.getParent().getName()); 
    // 父Logger名字:com.clear
    System.out.println("父Logger的父Logger:"+pLogger.getParent());  
    // 父Logger的父Logger:java.util.logging.LogManager$RootLogger@4bae78
    System.out.println("父Logger的父Logger名称:"+pLogger.getParent().getName());  
    // 父Logger的父Logger名称:
}
  • By default, the child Logger inherits the properties of the parent Logger.
// 默认情况下,子Logger会继承父Logger的属性。
@Test
public void test5(){
    
    
    Logger logger = Logger.getLogger("com.clear.JULTest");
    // 父Logger
    Logger pLogger = Logger.getLogger("com.clear");
    // 父Logger的默认日志打印级别为info
    pLogger.info("info");
    pLogger.fine("fine");
    logger.setLevel(Level.ALL);
    // todo 设置父类Logger
    // 禁用父级处理器
    pLogger.setUseParentHandlers(false);
    ConsoleHandler consoleHandler = new ConsoleHandler();
    consoleHandler.setFormatter(new SimpleFormatter());
    consoleHandler.setLevel(Level.ALL);
    pLogger.addHandler(consoleHandler);

    // 父Logger可以打印fine级别的信息了
    pLogger.info("info");
    pLogger.fine("fine");
    // todo 子Logger继承了父Logger的属性,也可以打印fine级别的信息了
    logger.info("info");
    logger.fine("fine");
}

2.6 Log formatting

​ In JUL (Java Util Logging), you can use a formatter (Formatter) to define the output format of log messages. The formatter can combine various parts of the log message (such as date, level, class name, method name, message content, etc.) into a string and specify the format of its output.

JUL provides two built-in formatters:

  • SimpleFormatter: This is the default formatter, which formats log messages into a line of text. The default format is [日期时间] [级别] [类名] [方法名] - [消息内容].
  • XMLFormatter: This is a formatter that formats log messages into XML format. It treats various parts of the log message as XML elements and uses attributes to represent their values.

The logger's formatter can be configured in the following ways:

  1. Use methods in code Handler.setFormatter(Formatter formatter)to set the formatter. For example, handler.setFormatter(new SimpleFormatter())set handlerthe formatter to SimpleFormatter.
  2. Use properties in the configuration file java.util.logging.ConsoleHandler.formatterto set the console processor's formatter. For example, java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormattermeans setting the console processor's formatter to SimpleFormatter.
  3. Use properties in the configuration file java.util.logging.FileHandler.formatterto set the file processor's formatter. For example, java.util.logging.FileHandler.formatter=java.util.logging.XMLFormattermeans to set the file processor's formatter to XMLFormatter.

In addition to using the built-in formatter, you can also customize the formatter to meet specific needs. A custom formatter needs to implement java.util.logging.Formatterthe interface and implement its format(LogRecord record)methods to define the output format of the log message.

By configuring the logger's formatter, you can flexibly control the output format of log messages to meet different needs and standards.

2.6.1 String format method

The format method of the String class is used to create formatted strings and connect multiple string objects.

public static String format(String format, Object... args) {
    
    
    return new Formatter().format(format, args).toString();
}
    
public static String format(Locale l, String format, Object... args) {
    
    
    return new Formatter(l).format(format, args).toString();
}

In this method, we can define string templates, for example:

String str = String.format("hello %s","world");

2.6.2 Commonly used conversion characters

conversion character illustrate Example
%s string type “hello”
%c Character type ‘a’
%b Boolean type true
%d Integer type (decimal) 88
%x Integer type (hex) FF
%o Integer type (octal) 77
%f floating point type 8.888
%a Hexadecimal floating point type FF.35AE
%e Exponential type 9.38e+5
%n newline character
%tx Date and time types (x represents different date and time conversion characters)

2.6.3 Special symbols

logo illustrate Example result
+ Add symbols for positive or negative numbers, because integers are generally not automatically signed (“+%d”, 15) +15
0 Add 0 in front of the number for alignment (“%04d”, 99) 0099
space Adds a specified number of spaces before an integer (“%4d”, 99) 99
, Group numbers with "," (commonly used to display amounts) (“%,f”, 9999.99) 9,999,990000
( Use parentheses to include negative numbers (“%(f”, -99.99) (99.990000)

By default, our variable parameters are replaced in order, but if we want to reuse the variable parameters:

We can add in the conversion character 数字$to complete the matching, for example:

System.out.printf("%1$s %1$s %1$s","小明");
// 其中,1$就代表第一个参数,那么2$就代表第二个参数

2.6.4 Date processing

In the previous %tx, x represents the date conversion character

logo illustrate Example
c Includes all date and time information Thursday, October 21 14:30:30 GMT+08:00 2023
F "Year-Month-Day" format 2023-7-18
D "Year/Month/Day" format 2023/7/18
r "HH:MM:SS PM" format (12-hour format) 8:20:30 PM
T "HH:MM:SS" format (12-hour format) 20:20:30
R "HH:MM" format (24-hour format) 20:20
b Month localization July
y two years 23
Y year of four 2023
m moon 7
d Day 18
H 24 hour clock 20
l 12 hour clock 8
M point 55
S Second 55
s timestamp in seconds 23144232
p AM or PM afternoon

We can use the following three classes for formatting, which may not be supported. For example, LocalDateTime does not support c:

System.out.println("tc", new Date());
System.out.println("tc", ZonedDateTime.now());
System.out.println("tF", LocalDateTime.now();

2.7 Configuration file

The source code of JUL (Java Util Logging) loading configuration file is interpreted as follows:

  • First, JUL uses LogManagerclasses to manage loggers and configuration information. LogManagerIt is a singleton class that obtains instances through LogManager.getLogManager()methods.

  • When a method is called LogManager.getLogManager().readConfiguration(inputStream), the following steps are performed:

    • a. The method LogManagerof the class readConfigurationwill create a new Propertiesobject to store configuration information.
    • b. Use Propertiesthe object loadmethod to load the contents of the configuration file into Propertiesthe object. inputStreamThe parameters here are getClass().getClassLoader().getResourceAsStream("logging.properties")obtained by reading logging.propertiesthe contents of the file from the classpath.
    • c. After loading the configuration file, LogManagerthe logger configuration will be updated based on the configuration information. Specific configuration information includes log level, log output target, etc.
  • If no configuration file is specified or loading of the configuration file fails, JUL will use the default configuration. The default configuration includes logging to the console and using the default log level.

​ 总结:JUL加载配置文件的源码主要涉及LogManager类的readConfiguration方法,该方法通过读取配置文件的内容,并根据配置信息更新日志记录器的配置。

2.7.1 读取自定义配置文件

在JUL(Java Util Logging)中,加载配置文件的常规步骤如下:

1)创建一个名为logging.properties的文本文件,该文件包含了JUL的配置信息。

2)将logging.properties文件放置在类路径下,通常是放在src/main/resources目录下。

3)在代码中使用以下代码片段来加载配置文件:

InputStream inputStream = getClass().getClassLoader().getResourceAsStream("logging.properties");
if (inputStream != null) {
    
    
    LogManager.getLogManager().readConfiguration(inputStream);
}

演示:

1)在src/main/resources目录下创建 logging.properties 文件,内容如下:

# 设置根日志记录器的级别为FINE
.level=FINE

handlers=java.util.logging.ConsoleHandler,java.util.logging.FileHandler
# 配置控制台处理器
java.util.logging.ConsoleHandler.level=FINE
java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter
# 配置文件处理器
java.util.logging.FileHandler.level=FINE
# 指定日志文件的路径和名称模式
java.util.logging.FileHandler.pattern=%h/java%u.log
# 指定日志文件的大小限制
java.util.logging.FileHandler.limit=50000
# 指定日志文件的数量限制
java.util.logging.FileHandler.count=1
# 指定日志消息的格式化器
java.util.logging.FileHandler.formatter=java.util.logging.XMLFormatter

2)编写代码读取配置文件

// 读取配置文件
@Test
public void readConfig() {
    
    
    LogManager manager = LogManager.getLogManager();
    // 获取配置文件
    InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("logging.properties");
    //        InputStream inputStream = getClass().getClassLoader().getResourceAsStream("logging.properties");

    try {
    
    
        manager.readConfiguration(inputStream);
    } catch (IOException e) {
    
    
        throw new RuntimeException(e);
    }
    Logger logger = Logger.getLogger(JULTest.class.getName());
    // 默认的日志打印级别为INFO,加载我们自己的配置文件后级别为FINE
    // 如果能打印fine级别的日志,证明加载成功
    logger.fine("fine");
}
}

2.7.2 配置文件解读

# 设置根日志记录器的级别为INFO
.level=INFO
# 配置控制台处理器、文件处理器
handlers=java.util.logging.ConsoleHandler,java.util.logging.FileHandler
# 为RootLogger根日志记录器配置handlers,指定处理器的列表,多个处理器之间使用逗号分隔
# 配置文件处理器的相关属性
# 设置日志输出级别
java.util.logging.FileHandler.level=INFO
# 指定日志文件的路径和名称模式    %h 表示用户家目录
java.util.logging.FileHandler.pattern=%h/java%u.log
# 指定日志文件的大小限制
java.util.logging.FileHandler.limit=50000
# 指定日志文件的数量限制(不能小于1)
java.util.logging.FileHandler.count=1
# 指定日志文件是追加写
java.util.logging.FileHandler.append
# 指定日志消息的格式化器
java.util.logging.FileHandler.formatter=java.util.logging.XMLFormatter
# 配置控制台处理器的相关属性
# 设置日志输出级别
java.util.logging.ConsoleHandler.level=INFO
# 指定日志消息的格式化器
java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter
# 指定日志消息的格式
java.util.logging.SimpleFormatter.format=%4$s: %5$s [%1$tc]%n

2.7.3 独立日志的配置

logging.properites

在配置文件中加入如下配置

# 设置 com.clear 日志记录器
com.clear.handlers=java.util.logging.ConsoleHandler
com.clear.level=WARNING
# 禁用父类的Handler
com.clear.useParentHandlers=false

为了让我们自定义的日志记录器生效,必须在获取Logger实例之前加载配置文件

我们可以采用静态代码块的方式实现

public class JULTest {
    
    
    static {
    
    
        LogManager manager = LogManager.getLogManager();
        // 获取配置文件
        InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("logging.properties");
        try {
    
    
            manager.readConfiguration(inputStream);
        } catch (IOException e) {
    
    
            throw new RuntimeException(e);
        }
    }
    // 读取配置文件
    @Test
    public void readConfig() {
    
    
        // 此时,com.clear包下,只会打印WARNING级别及以上的日志
        LOGGER.warning("warning");
        LOGGER.fine("fine");
    }
}

2.8 过滤器

​ 在JUL(Java Util Logging)中,过滤器用于过滤日志消息,只有符合特定条件的日志消息才会被记录或处理。JUL提供了两种类型的过滤器:日志记录器过滤器(Logger Filter)和处理器过滤器(Handler Filter)。

  • 日志记录器过滤器(Logger Filter)

    • java.util.logging.Filter接口定义了日志记录器过滤器的方法boolean isLoggable(LogRecord record),该方法接收一个LogRecord对象作为参数,并返回一个布尔值,表示是否允许记录该日志消息。
    • 可以通过实现Filter接口来自定义日志记录器过滤器,然后将其设置给特定的日志记录器。
  • 处理器过滤器(Handler Filter)

    • java.util.logging.Handler接口定义了处理器过滤器的方法boolean isLoggable(LogRecord record),该方法接收一个LogRecord对象作为参数,并返回一个布尔值,表示是否允许处理该日志消息。
    • 可以通过实现Handler接口来自定义处理器过滤器,然后将其设置给特定的处理器。

演示:

public class FilterTest {
    
    
    public static final Logger LOGGER = Logger.getLogger(FilterTest.class.getName());

    // 日志记录器过滤器测试
    @Test
    public void loggerFilterTest() {
    
    
        // 给日志记录器设置过滤器
        LOGGER.setFilter(logRecord ->  // 只打印级别>= INFO的日志
                logRecord.getLevel().intValue() >= Level.INFO.intValue());
        LOGGER.info("hello");
        LOGGER.fine("world");
        LOGGER.finer("apache");
    }

    // 处理器过滤器测试
    @Test
    public void handlerFilterTest() {
    
    
        // 禁用父级处理器
        LOGGER.setUseParentHandlers(false);

        ConsoleHandler consoleHandler = new ConsoleHandler();
        consoleHandler.setFormatter(new SimpleFormatter());
        consoleHandler.setLevel(Level.ALL);
        // 给处理器设置过滤器
        consoleHandler.setFilter(logRecord ->  // 只打印a开头的日志
                logRecord.getMessage().startsWith("a"));
        // 将处理器Handler加入到日志记录器Logger中
        LOGGER.addHandler(consoleHandler);

        LOGGER.info("hello");
        LOGGER.info("world");
        LOGGER.info("apache");
    }
}

2.9 打印异常堆栈

在JUL(Java Util Logging)中,可以通过配置日志记录器的级别和处理器来打印异常堆栈信息。

  • 配置日志记录器的级别:

    • 使用Logger.setLevel(Level level)方法来设置日志记录器的级别。
    • 通常,将日志记录器的级别设置为Level.SEVERE或更低的级别,以便记录所有的异常信息。
  • 配置处理器:

    • 使用Handler.setFormatter(Formatter formatter)方法来设置处理器的格式化器。
    • 使用SimpleFormatter类作为格式化器,它会将日志消息格式化为包含时间戳、日志级别和消息内容的字符串。

演示:

将异常的堆栈信息打印至控制台和文件中

package com.clear;

import org.junit.Test;

import java.io.IOException;
import java.util.logging.*;

public class ExceptionTest {
    
    
    public static final Logger LOGGER = Logger.getLogger(ExceptionTest.class.getName());

    @Test
    public void printStackTest() throws IOException {
    
    
        // 设置日志记录器的级别为FINER
        LOGGER.setLevel(Level.FINER);
        // 创建处理器
        Handler consoleHandler = new ConsoleHandler();
        consoleHandler.setLevel(Level.FINER);
        // 设置处理器的格式化器
        consoleHandler.setFormatter(new SimpleFormatter());
        Handler fileHandler = new FileHandler();
        fileHandler.setLevel(Level.FINER);
        fileHandler.setFormatter(new SimpleFormatter());
        // 将处理器添加到日志记录器
        LOGGER.addHandler(consoleHandler);
        LOGGER.addHandler(fileHandler);

        try {
    
    
            // 抛出异常
            int a = 10 / 0;
        } catch (ArithmeticException e) {
    
    
            // 打印异常堆栈信息
            LOGGER.throwing(ExceptionTest.class.getName(), "printStackTest", e);
        }
    }
}

3 LOG4J

​ LOG4J是一个流行的Java日志框架,它提供了强大的日志记录和管理功能。LOG4J可以帮助开发人员在应用程序中实现灵活的日志记录,并提供了多种配置选项和输出格式。

LOG4J的一些主要特点和用法:

  • 配置文件:LOG4J使用一个配置文件来定义日志记录器、日志级别、输出目标等配置信息。默认的配置文件名为log4j.properties,也可以使用XML格式的配置文件log4j.xml
  • 日志级别:LOG4J提供了多个日志级别,包括DEBUGINFOWARNERRORFATAL。可以根据需要设置不同的日志级别,以控制日志记录的详细程度。
  • 日志记录器:LOG4J使用日志记录器(Logger)来记录日志消息。每个日志记录器都有一个唯一的名称,可以根据需要创建多个日志记录器。
  • 输出目标:LOG4J支持将日志消息输出到不同的目标,如控制台、文件、数据库等。可以根据需要配置不同的输出目标。
  • 格式化:LOG4J允许自定义日志消息的格式。可以使用预定义的格式化器,如PatternLayout,也可以自定义格式化器。
  • 运行时配置:LOG4J支持在运行时动态修改日志配置。可以通过修改配置文件或使用API来改变日志记录器的级别、输出目标等配置。

3.1 LOG4J的使用

1)添加POM依赖

​ 添加LOG4J依赖:在项目的构建文件(如Maven的pom.xml)中添加LOG4J的依赖项

<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>

2)创建LOG4J配置文件

​ 创建LOG4J配置文件:在项目的资源目录下创建一个LOG4J的配置文件,命名为log4j.propertieslog4j.xml。配置文件中定义了日志记录器、日志级别、输出目标等配置信息

参考如下:

# 设置根日志级别为INFO
log4j.rootLogger=INFO, console

# 定义控制台输出目标
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n

3)在代码中使用

在代码中使用LOG4J:在需要记录日志的类中,通过获取LOG4J的Logger对象来进行日志记录

import org.apache.log4j.Logger;
import org.junit.Test;

public class Log4jTest {
    
    
    // 获取Logger实例
    public static final Logger logger = Logger.getLogger(Log4jTest.class);

    @Test
    public void test1() {
    
    
        // 日志输出
        logger.fatal("fatal");
        logger.error("error");
        logger.warn("warn");
        logger.info("info");
        logger.debug("debug");
        logger.trace("trace");
    }
}

如果我们不添加配置文件,会报如下错误:

D:\software\jdk1.8.0_131\bin\java.exe ...
log4j:WARN No appenders could be found for logger (com.clear.Log4jTest).
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.

JUL没有添加配置文件,不会报错,是因为他是Java原生的日志框架,拥有默认的配置文件

如果我们没有添加配置文件,加上如下方法也是可以的,他会初始化一个默认的配置文件

BasicConfigurator.configure();

4)运行和查看日志

​ 运行项目时,LOG4J会根据配置文件中的设置进行日志记录。日志消息将根据配置的输出目标(如控制台、文件)进行输出。可以根据配置文件中的日志级别设置,过滤和查看不同级别的日志消息。

3.2 默认配置解读

BasicConfigurator.configure();

当我们在代码中加上如上一行时,可以在使用log4j时不需要添加配置文件

演示:

// 获取Logger实例
public static final Logger logger = Logger.getLogger(Log4jTest.class);

@Test
public void test1() {
    
    
    BasicConfigurator.configure();
    // 日志输出
    logger.fatal("fatal");
    logger.error("error");
    logger.warn("warn");
    logger.info("info");
    logger.debug("debug");
    logger.trace("trace");
}

结果如下:

0 [main] FATAL com.clear.Log4jTest  - fatal
1 [main] ERROR com.clear.Log4jTest  - error
1 [main] WARN com.clear.Log4jTest  - warn
1 [main] INFO com.clear.Log4jTest  - info
1 [main] DEBUG com.clear.Log4jTest  - debug

源码解读:

BasicConfigurator.configure(); 这一行代码会给RootLogger加上一个控制台的输出源,类似于JUL的JHandler

在LOG4J中,BasicConfigurator.configure()是一个静态方法,用于简单配置日志记录器。它会自动创建一个默认的日志记录器,并将其配置为使用控制台输出日志消息。

BasicConfigurator.configure()方法的主要作用是:

1)创建一个默认的日志记录器:该方法会创建一个名为"root"的日志记录器,并将其设置为根日志记录器。

2)配置日志记录器:默认的配置会将日志级别设置为DEBUG,并将一个ConsoleAppender添加到日志记录器中。

3)设置日志输出格式:默认的配置会使用PatternLayout来定义日志消息的输出格式。

使用BasicConfigurator.configure()方法可以快速启动LOG4J的基本配置,方便进行简单的日志记录。但是,它的配置选项有限,如果需要更复杂的配置,可以使用自定义的配置文件或编程方式进行配置

public static  void configure() {
    
    
    Logger root = Logger.getRootLogger();
    root.addAppender(new ConsoleAppender(
        new PatternLayout(PatternLayout.TTCC_CONVERSION_PATTERN)));
}

// 其中 PatternLayout.TTCC_CONVERSION_PATTERN)
public final static String TTCC_CONVERSION_PATTERN = "%r [%t] %p %c %x - %m%n"

3.3 日志级别

在Log4j中,日志级别从低到高分别为(与JUL略有不同)

  • TRACE:最低级别的日志,用于追踪程序的详细执行过程。
  • DEBUG:用于调试程序,输出一些调试信息。(默认)
  • INFO:用于输出程序的运行信息,如启动、关闭等。
  • WARN:用于输出警告信息,表示可能出现的问题。
  • ERROR:用于输出错误信息,表示程序出现了错误但不影响程序的继续运行。
  • FATAL:最高级别的日志,用于输出严重的错误信息,表示程序无法继续运行。

和JUL一样,也有两个特殊的级别:

  • OFF 用于关闭日志记录
  • ALL 启用所有消息的日志记录

可以根据需要选择适当的日志级别来记录日志,通常建议在生产环境中使用INFO级别或更高级别,而在开发和调试阶段使用DEBUG级别。

3.4 核心组件

3.4.1 Loggers

​ Logger(日志记录器):用于记录日志消息的主要组件。每个Logger对象都与一个特定的日志记录器名称相关联,可以通过该名称来标识不同的日志记录器。

​ 实例的命名就是类"XX"的 full quailied name(类的全限定名),Logger的名字对大小写敏感,其命名有继承机制:例如:名称为 com.clear.service 的logger 会继承自名称为 com.clear 的logger,与JUL一致。

​ Log4j有一个特殊的Logger叫做"root",它是所有Logger的根,所有的Logger都会直接或间接继承自它。root logger可以用Logger.getRootLogger()方法获取。在JUL中也有一个 名为.的根

3.4.2 Appenders

​ Appender(日志输出器):与JUL的Handler类似,用于将日志消息输出到不同的目标,如控制台、文件、数据库等。Log4j提供了多种类型的Appender,可以根据需求选择合适的输出目标。

输出端类型 作用
ConsoleAppender 将日志输出到控制台
FileAppender 将日志输出到文件中
DailyRollingFileAppender 将日志输出到日志文件中,并且每天输出到一个新的文件
RollingFileAppender 将日志输出到日志文件中,并指定文件的尺寸,当文件大小到达指定尺寸时,会自动把文件改名,同时产生一个新的文件
JDBCAppender 将日志输出到数据集中

例如:

// 配置一个控制台输出源
ConsoleAppender consoleAppender = new ConsoleAppender();
consoleAppender.setName("clear");
consoleAppender.setWriter(new PrintWriter(System.out));
logger.addAppender(consoleAppender);

3.4.3 Layouts

Layout(日志布局器):用于定义日志消息的格式。Layout将日志消息转换为特定的字符串格式,以便输出到Appender指定的目标。

3.4.4 Lever

​ Level(日志级别):用于控制日志消息的输出级别。可以根据需要设置不同的日志级别,只有达到指定级别及以上的日志消息才会被记录和输出。

3.4.5 Configuration

​ Configuration(配置):用于配置Log4j的各个组件。可以通过配置文件或编程方式来配置Log4j,包括设置日志级别、指定Appender和Layout等。

总结

​ 这些核心组件共同工作,实现了灵活、可配置的日志记录功能。通过合理配置和使用这些组件,可以实现对日志消息的记录、输出和管理。

3.5 编程配置

在Log4j中,可以通过编程方式进行配置

// 获取Logger实例
public static final Logger logger = Logger.getLogger(Log4jTest.class);

@Test
public void test2(){
    
    
    // 获取根Logger实例
    Logger rootLogger = Logger.getRootLogger();
    // 创建一个ConsoleAppender对象
    ConsoleAppender consoleAppender = new ConsoleAppender();
    // 设置输出流为标准输出流
    consoleAppender.setWriter(new PrintWriter(System.out));
    Layout layout = new PatternLayout(PatternLayout.TTCC_CONVERSION_PATTERN);
    // 将consoleAppender添加到根Logger实例中,以便将日志输出到控制台
    consoleAppender.setLayout(layout);
    rootLogger.addAppender(consoleAppender);

    logger.warn("warn");
    logger.info("info");
    logger.debug("debug");

}

3.6 配置文件

​ Log4j的配置文件是一个用于配置日志记录器的文本文件,它定义了日志记录器的行为和输出方式。Log4j支持多种配置文件格式,包括XML、JSON和属性文件

​ 配置文件的命名通常为log4j.properties或log4j.xml,可以放置在类路径下或指定的位置。在应用程序启动时,Log4j会自动加载并解析配置文件,根据配置文件的定义进行日志记录

例如:

log4j.properties

# 设置根日志级别为INFO,并将日志输出到Console和File(其实这里的名字可以随意起,影响不大)
log4j.rootLogger=INFO, Console, File

# 定义控制台输出
log4j.appender.Console=org.apache.log4j.ConsoleAppender
# 指定控制台输出的Layout为 PatternLayout
log4j.appender.Console.layout=org.apache.log4j.PatternLayout
# 设置控制台输出的日志消息格式
log4j.appender.Console.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5p %c{1} - %m%n

# 定义文件输出
log4j.appender.File=org.apache.log4j.RollingFileAppender
# 指定日志文件的路径和文件名(这里定义的是相对路径,可根据实际需要进行修改)
log4j.appender.File.File=logs/app.log
# 设置日志文件的最大大小为10MB
log4j.appender.File.MaxFileSize=10MB
# 设置保留的日志文件备份数量为10个
log4j.appender.File.MaxBackupIndex=10
# 指定文件输出的 Layout为 PatternLayout
log4j.appender.File.layout=org.apache.log4j.PatternLayout
# 设置文件输出的日志消息格式
log4j.appender.File.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5p %c{1} - %m%n

说明

​ 控制台输出使用PatternLayout进行格式化,文件输出使用RollingFileAppender进行日志文件的滚动。日志文件将按照文件大小进行切割,每个文件最大为10MB,最多保留10个备份文件。

如果需要分别控制输出到控制台和文件中的日志级别,则如下:

# 输出到控制台的日志级别
log4j.rootLogger=INFO, Console

# 输出到文件的日志级别(自定义Logger)
log4j.logger.myapp=WARN, File
# 禁止将myapp Logger的日志事件传递给父Logger(在自定义Logger时需要配置,否则会重复打印日志)
log4j.additivity.myapp=false

# 控制台输出的配置
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.layout=org.apache.log4j.PatternLayout
log4j.appender.Console.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n

# 文件输出的配置
log4j.appender.File=org.apache.log4j.RollingFileAppender
log4j.appender.File.File=/path/to/log/file.log
log4j.appender.File.MaxFileSize=10MB
log4j.appender.File.MaxBackupIndex=10
log4j.appender.File.layout=org.apache.log4j.PatternLayout
log4j.appender.File.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n

log4j2.xml

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
    <Appenders>
        <!-- 定义控制台输出 -->
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
        </Console>

        <!-- 定义文件输出(文件路径可根据实际进行修改) -->
        <RollingFile name="File" fileName="logs/app.log"
            filePattern="logs/app-%d{yyyy-MM-dd}.log">
            <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
            <Policies>
                <SizeBasedTriggeringPolicy size="10MB" />
            </Policies>
            <DefaultRolloverStrategy max="10" />
        </RollingFile>
    </Appenders>

    <Loggers>
        <!-- 设置根日志级别为INFO -->
        <Root level="INFO">
            <AppenderRef ref="Console" />
            <AppenderRef ref="File" />
        </Root>
    </Loggers>
</Configuration>
  • <?xml version="1.0" encoding="UTF-8"?>:指定XML文件的版本和编码。
  • <Configuration status="WARN">:设置Log4j的全局配置,其中status属性设置为WARN表示只输出警告级别以上的日志信息。
  • <Appenders>:定义日志输出的Appenders。
  • <Console name="Console" target="SYSTEM_OUT">:定义控制台输出的Appender,其中name属性为Appender的名称,target属性指定输出目标为系统标准输出。
  • <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />:设置控制台输出的日志消息格式。
  • <RollingFile name="File" fileName="logs/app.log" filePattern="logs/app-%d{yyyy-MM-dd}.log">:定义文件输出的Appender,其中name属性为Appender的名称,fileName属性指定日志文件的路径和文件名,filePattern属性指定日志文件的滚动模式。
  • <Policies>:定义日志文件滚动的策略。
  • <SizeBasedTriggeringPolicy size="10MB" />:设置日志文件的滚动触发策略为基于文件大小,当日志文件达到10MB时触发滚动。
  • <DefaultRolloverStrategy max="10" />:设置日志文件的滚动策略为默认策略,最多保留10个备份文件。
  • <Loggers>:定义日志记录器。
  • <Root level="INFO">:设置根日志记录器的级别为INFO。
  • <AppenderRef ref="Console" /><AppenderRef ref="File" />:将控制台输出和文件输出的Appender引用添加到根日志记录器中。

输出格式

log4j 采用类似 C 语言的 printf 函数的打印格式格式化日志信息,具体的占位符及其含义如下:

转换符 说明
%m 输出代码中指定的日志信息
%p 输出优先级,如 DEBUG、INFO 等
%n 换行符(Windows平台的换行符为 “\n”,Unix 平台为 “\n”)
%r 输出自应用启动到输出该 log 信息耗费的毫秒数
%c 输出打印语句所属的类的全限定名
%t 输出产生该日志的线程全名
%d 输出服务器当前时间,默认格式为 ISO8601,也可以在后面指定格式。
如:%d{yyyy-MM-dd HH:mm:ss}
%l 输出日志时间发生的位置,包括类名、发生的线程,以及在代码中的行数,如:Test.main(Test.java:10)
%F 输出日志消息产生时所在的文件名称
%L 输出代码中的行号
%x 输出和当前线程相关的 NDC(嵌套诊断环境)
%% 输出一个 “%” 字符

可以在 % 与字符之间加上修饰符来控制最小宽度、最大宽度和文本的对其方式。如:

  • %5c 输出category名称,最小宽度是5,category<5,默认的情况下右对齐
  • %-5c 输出category名称,最小宽度是5,category<5,"-"号指定左对齐,会有空格
  • %.5c 输出category名称,最大宽度是5,category>5,就会将左边多出的字符截掉,<5不会有空格
  • %20.30c category名称<20补空格,并且右对齐,>30字符,就从左边交远销出的字符截掉

3.7 PatternLayout源码

PatternLayout这个实现类,可以根据特定的占位符进行转换,类似于JUL,但是又不一样,如下是他的构造器:

public PatternLayout(String pattern) {
    
    
    this.pattern = pattern;
    head = createPatternParser((pattern == null) ? DEFAULT_CONVERSION_PATTERN :
                               pattern).parse();
}

传入一个pattern字符,他会根据pattern字符串创建一个链表

接着查看解析器:

protected PatternParser createPatternParser(String pattern) {
    
    
    return new PatternParser(pattern);
}

查看parse()方法:

// 如下是这个方法的简略版
public
    PatternConverter parse() {
    
    
    char c;
    i = 0;
    while(i < patternLength) {
    
    
        c = pattern.charAt(i++);
        switch(state) {
    
    
         .....
                            finalizeConverter(c);  // 这个方法最重要的就是这个
         ......
    return head;
}

下面是finalizeConverter()方法:

protected
  void finalizeConverter(char c) {
    
    
    PatternConverter pc = null;
    switch(c) {
    
    
    case 'c':
      pc = new CategoryPatternConverter(formattingInfo,
					extractPrecisionOption());
      //LogLog.debug("CATEGORY converter.");
      //formattingInfo.dump();
      currentLiteral.setLength(0);
      break;
    // 处理类名的转换器
    case 'C':
      pc = new ClassNamePatternConverter(formattingInfo,
					 extractPrecisionOption());
      //LogLog.debug("CLASS_NAME converter.");
      //formattingInfo.dump();
      currentLiteral.setLength(0);
      break;
    // 处理时间的转换器        
    case 'd':
      String dateFormatStr = AbsoluteTimeDateFormat.ISO8601_DATE_FORMAT;
      DateFormat df;
      String dOpt = extractOption();
      if(dOpt != null)
	dateFormatStr = dOpt;

      if(dateFormatStr.equalsIgnoreCase(
                                    AbsoluteTimeDateFormat.ISO8601_DATE_FORMAT))
	df = new  ISO8601DateFormat();
      else if(dateFormatStr.equalsIgnoreCase(
                                   AbsoluteTimeDateFormat.ABS_TIME_DATE_FORMAT))
	df = new AbsoluteTimeDateFormat();
      else if(dateFormatStr.equalsIgnoreCase(
                              AbsoluteTimeDateFormat.DATE_AND_TIME_DATE_FORMAT))
	df = new DateTimeDateFormat();
      else {
    
    
	try {
    
    
	  df = new SimpleDateFormat(dateFormatStr);
	}
	catch (IllegalArgumentException e) {
    
    
	  LogLog.error("Could not instantiate SimpleDateFormat with " +
		       dateFormatStr, e);
	  df = (DateFormat) OptionConverter.instantiateByClassName(
			           "org.apache.log4j.helpers.ISO8601DateFormat",
				   DateFormat.class, null);
	}
      }
      pc = new DatePatternConverter(formattingInfo, df);
      //LogLog.debug("DATE converter {"+dateFormatStr+"}.");
      //formattingInfo.dump();
      currentLiteral.setLength(0);
      break;
    // 输出日志时间发生的位置,包括类名、线程、及代码所在行数        
    case 'F':
      pc = new LocationPatternConverter(formattingInfo,
					FILE_LOCATION_CONVERTER);
      //LogLog.debug("File name converter.");
      //formattingInfo.dump();
      currentLiteral.setLength(0);
      break;
    case 'l':
      pc = new LocationPatternConverter(formattingInfo,
					FULL_LOCATION_CONVERTER);
      //LogLog.debug("Location converter.");
      //formattingInfo.dump();
      currentLiteral.setLength(0);
      break;
    case 'L':
      pc = new LocationPatternConverter(formattingInfo,
					LINE_LOCATION_CONVERTER);
      //LogLog.debug("LINE NUMBER converter.");
      //formattingInfo.dump();
      currentLiteral.setLength(0);
      break;
    case 'm':
      pc = new BasicPatternConverter(formattingInfo, MESSAGE_CONVERTER);
      //LogLog.debug("MESSAGE converter.");
      //formattingInfo.dump();
      currentLiteral.setLength(0);
      break;
    case 'M':
      pc = new LocationPatternConverter(formattingInfo,
					METHOD_LOCATION_CONVERTER);
      //LogLog.debug("METHOD converter.");
      //formattingInfo.dump();
      currentLiteral.setLength(0);
      break;
    case 'p':
      pc = new BasicPatternConverter(formattingInfo, LEVEL_CONVERTER);
      //LogLog.debug("LEVEL converter.");
      //formattingInfo.dump();
      currentLiteral.setLength(0);
      break;
    case 'r':
      pc = new BasicPatternConverter(formattingInfo,
					 RELATIVE_TIME_CONVERTER);
      //LogLog.debug("RELATIVE time converter.");
      //formattingInfo.dump();
      currentLiteral.setLength(0);
      break;
    case 't':
      pc = new BasicPatternConverter(formattingInfo, THREAD_CONVERTER);
      //LogLog.debug("THREAD converter.");
      //formattingInfo.dump();
      currentLiteral.setLength(0);
      break;
      /*case 'u':
      if(i < patternLength) {
	char cNext = pattern.charAt(i);
	if(cNext >= '0' && cNext <= '9') {
	  pc = new UserFieldPatternConverter(formattingInfo, cNext - '0');
	  LogLog.debug("USER converter ["+cNext+"].");
	  formattingInfo.dump();
	  currentLiteral.setLength(0);
	  i++;
	}
	else
	  LogLog.error("Unexpected char" +cNext+" at position "+i);
      }
      break;*/
    case 'x':
      pc = new BasicPatternConverter(formattingInfo, NDC_CONVERTER);
      //LogLog.debug("NDC converter.");
      currentLiteral.setLength(0);
      break;
    case 'X':
      String xOpt = extractOption();
      pc = new MDCPatternConverter(formattingInfo, xOpt);
      currentLiteral.setLength(0);
      break;
    default:
      LogLog.error("Unexpected char [" +c+"] at position "+i
		   +" in conversion patterrn.");
      pc = new LiteralPatternConverter(currentLiteral.toString());
      currentLiteral.setLength(0);
    }

    addConverter(pc);
  }

下面就是一个典型的链表结构的创建:

protected void addConverter(PatternConverter pc) {
    
    
    currentLiteral.setLength(0);
    // Add the pattern converter to the list.
    addToList(pc);
    // Next pattern is assumed to be a literal.
    state = LITERAL_STATE;
    // Reset formatting info
    formattingInfo.reset();
}
private void  addToList(PatternConverter pc) {
    
    
    if(head == null) {
    
    
        head = tail = pc;
    } else {
    
    
        tail.next = pc;
        tail = pc;
    }
}

构建完转换器链表之后,就是循环这个链表,依次处理对应的占位符,他的核心格式化的方法也是format方法,在format方法中通过一个转换器链来完成转化:

public static format(LoggingEvent event){
    
    
    // 在format方法中是通过转换器链来完成的
    PatternConverter c = head;
    
    while(c != null){
    
    
        // 这一句是核心,第一个参数是StringBuilder,第二个参数是LoggingEvent
        c.format(sbuf, event);
        c = c.next;
    }
    return sbuf.toString;
} 

这里就是通过一个pattern字符串,这个字符串可能张这个样子(%-d{yyyy-MM-dd HH:mm:ss​} [%t:%r] -[%p] %m%n),使用createPatternParser().parse()构建第一个处理器的链表,这个每个处理器处理一个占位符,例如:%d

进入 c.format()方法,我们会进入一个抽象类 PatternConverter 中的format方法,该方法的核心代码如下:

public void format(StringBuilder sbuf, LoggingEvent e){
    
    
	// 核心语句如下
	String s = convert(e);
}

3.10 日志分割

在Log4j中,可以通过配置来实现日志的分割,即将日志按照一定的规则拆分成多个文件,以便于管理和查看。

Log4j提供了多种方式来实现日志的分割,其中常用的方式包括:

  • 按时间分割:可以按照日期、小时、分钟等时间单位来拆分日志文件。例如,每天生成一个新的日志文件,或者每小时生成一个新的日志文件。
  • 按文件大小分割:可以按照文件大小来拆分日志文件。例如,当日志文件达到一定大小时,自动创建一个新的日志文件。
  • 按日志级别分割:可以按照日志级别来拆分日志文件。例如,将不同级别的日志分别记录到不同的文件中

源码中,FileAppender类有几个子类,用于实现日志文件的滚动、分割:

  • DailyRollingFileAppender:按照日期滚动的Appender。它可以按照一定的时间间隔(如每天、每周、每月)生成新的日志文件
  • RollingFileAppender:按照文件大小滚动的Appender。它可以在日志文件达到一定大小时生成新的日志文件
    • ExternallyRolledFileAppende:外部滚动的Appender。它允许外部程序控制日志文件的滚动。可以通过外部程序的信号或命令来触发日志文件的滚动。

如下是使用DailyRollingFileAppenderRollingFileAppender来实现日志文件的分割和滚动:

# 设置根日志级别为INFO
log4j.rootLogger=INFO, dailyRollingFile, rollingFile

# 配置DailyRollingFileAppender
log4j.appender.dailyRollingFile=org.apache.log4j.DailyRollingFileAppender
log4j.appender.dailyRollingFile.File=logs/application.log
# 指定按照日期滚动的规则
log4j.appender.dailyRollingFile.DatePattern='.'yyyy-MM-dd
log4j.appender.dailyRollingFile.layout=org.apache.log4j.PatternLayout
log4j.appender.dailyRollingFile.layout.ConversionPattern=%d{HH:mm:ss.SSS} [%t] %-5p %c{1} - %m%n

# 配置RollingFileAppender
log4j.appender.rollingFile=org.apache.log4j.RollingFileAppender
log4j.appender.rollingFile.File=logs/application.log
# 日志文件到达10MB时生成新文件
log4j.appender.rollingFile.MaxFileSize=10MB
# 保留最多5个备份文件
log4j.appender.rollingFile.MaxBackupIndex=5
log4j.appender.rollingFile.layout=org.apache.log4j.PatternLayout
log4j.appender.rollingFile.layout.ConversionPattern=%d{HH:mm:ss.SSS} [%t] %-5p %c{1} - %m%n

# 设置日志输出格式
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=%d{HH:mm:ss.SSS} [%t] %-5p %c{1} - %m%n

# 配置日志级别
log4j.logger.com.example=DEBUG

# 关闭Log4j内部日志输出
log4j.logger.org.apache.log4j=OFF

​ 在Java源代码中,Log4j的日志分割功能由RollingFileAppender类实现。它通过检查日志文件的大小或时间来决定是否需要生成新的日志文件。

例如:

import org.apache.log4j.RollingFileAppender;
import org.apache.log4j.PatternLayout;

RollingFileAppender appender = new RollingFileAppender();
appender.setFile("logs/application.log");
appender.setLayout(new PatternLayout("%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"));
appender.setMaxFileSize("10MB");
appender.setMaxBackupIndex(5);
appender.activateOptions();

Logger logger = Logger.getLogger("com.clear.MyClass");
logger.addAppender(appender);

3.6 自定义Logger

log4j.properties

# 设置根Logger的日志级别和输出目标
log4j.rootLogger=INFO, console

# 设置自定义Logger的日志级别和输出目标
log4j.logger.com.clear=DEBUG, myappFile
# 禁止将myapp Logger的日志事件传递给父Logger(在自定义Logger时需要配置,否则会重复打印日志)
log4j.additivity.com.clear=false

# 设置输出目标为控制台
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=%d [%t] %-5p %c - %m%n

# 设置输出目标为文件
log4j.appender.myappFile=org.apache.log4j.FileAppender
log4j.appender.myappFile.File=D:/test/myapp.log
log4j.appender.myappFile.layout=org.apache.log4j.PatternLayout
log4j.appender.myappFile.layout.ConversionPattern=%d [%t] %-5p %c - %m%n

测试

public static final Logger LOGGER = Logger.getLogger(DIY.class);
@Test
public void test(){
    
    
    // 测试自定义Logger
    // 我们配置文件中设置了DEBUG级别以及上的才输出到日志文件
    LOGGER.trace("trace");
    LOGGER.debug("debug");
    LOGGER.info("info");
    LOGGER.warn("warn");
    LOGGER.error("error");
    LOGGER.fatal("fatal");
}

结果

D:\test\myapp.log 内容如下,确实只打印了DEBUG及以上的日志

2023-07-21 21:33:00,514 [main] DEBUG com.clear.DIY - debug
2023-07-21 21:33:00,515 [main] INFO  com.clear.DIY - info
2023-07-21 21:33:00,515 [main] WARN  com.clear.DIY - warn
2023-07-21 21:33:00,515 [main] ERROR com.clear.DIY - error
2023-07-21 21:33:00,515 [main] FATAL com.clear.DIY - fatal

4 日志门面

日志门面(Logging Facade)是一种设计模式,用于在应用程序中实现日志记录的抽象层。它的作用是提供一个统一的接口,使应用程序可以使用不同的日志库进行日志记录,而不需要直接依赖于特定的日志库。

日志门面的作用有以下几个方面:

  • 解耦应用程序和具体的日志库:通过使用日志门面,应用程序可以将日志记录的实现细节与具体的日志库解耦。这意味着应用程序可以在不修改代码的情况下切换或替换不同的日志库,以满足不同的需求或适应不同的环境。
  • 统一日志记录接口:日志门面提供了一个统一的接口,使应用程序可以使用相同的日志记录方法和语法,无论使用哪个具体的日志库。这简化了日志记录的代码编写和维护,并提高了代码的可读性和可维护性。
  • 支持多个日志级别和日志输出目标:日志门面通常支持多个日志级别(如调试、信息、警告、错误等)和多个日志输出目标(如控制台、文件、数据库等)。这使得应用程序可以根据需要选择适当的日志级别和输出目标,以满足不同的调试、监控和故障排查需求。
  • 提供日志记录的扩展和定制能力:通过日志门面,应用程序可以使用日志库提供的扩展和定制功能,如日志过滤、格式化、异步记录等。这使得应用程序可以根据具体需求对日志记录进行定制和优化,以满足特定的业务需求和性能要求。

总之,日志门面的作用是提供一个抽象层,使应用程序可以灵活地使用不同的日志库,并提供统一的日志记录接口和功能,以提高代码的可维护性、可读性和灵活性

4.1 阿里日志规约

  • 【强制】应用中不可直接使用日志系统(Log4、Logback)中的API ,而应依赖使用日志框架(SLF4)、JCL–Jakarta Commons Logging)中的API,使用门面模式的日志框架 ,有利于维护和各个类的日志处理方式统一。

    • 说明:日志框架 (SLF4)JCL–Jakarta Commons Logging)的使用方式(推荐使用 SLF4J)

    • // 使用 SLF4J:
      import org.slf4j Logger;
      import org slf4j. LoggerFactory;
      private static final Logger logger = LoggerFactory getLogger(Test.class);
      
      // 使用 JCL:
      import org.apache.commons.logging.Log;
      import org.apache.commons.logging. LogFactory;
      private static final Log log = LogFactory getLog(Test.class);
      
  • 【强制】所有日志文件至少保存 15 天,因为有些异常具备以“周” 为频次发生的特点。对于当天日志,以“应用名.log〞 来保存,保存在/home/admin/应用名/logs/目录下,过往日志格式为:{logname.log}.{保存日期},日期格式:yyyy-MM-dd

  • 正例:以aap 应用为例,日志保存在/home/admin/aapserver/logs/aap.log,历史日志名称为aap.log.2016-08-01

  • 【强制】根据国家法律,网络运行状态、网络安全事件、个人敏感信息操作等相关记录,留存的日志不少于六个月,并且进行网络多机备份。

  • 【强制】应用中的扩展日志(如打点、临时监控、访问日志等) 命名方式:

    • appName log Type Iog Name.log。 logType:日志类型

    • 如stats/monitor/access 等; logName:日志描述。这种命名的好处:通过文件名就可知道日志文件属于什么应用,什么类型,什么目的,也有利于归类查找。

    • 说明:推荐对日志进行分类,如将错误日志和业务日志分开存放,便于开发人员查看,也便于通过日志对系统进行及时监控。

    • 正例:mppserver 应用中单独监控时区转换异常,如:mppserver_monitor_timeZoneConvert.log

  • 【强制】在日志输出时 ,字符串变量之间的拼接使用占位符的方式

    • 说明:因为String 字符串的拼接会使用 StringBuilder 的append0方式,有一定的性能损耗。使用占位符仅是替换动作,可以有效提升性能。

    • 正例:

    ```java
    logger.debug("Processing trade with id: ( and symbol: (", id, symbol);
    ```
    
  • 【强制】对于 trace/debue/info 级别的日志输出,必须进行日志级别的开关判断

    • 说明:虽然在 debug(参数的方法体内第一行代码 isDisabled(Level.DEBUGLINT)为真时( S1f4j的常见实现Log4j 和Logback),就直接return ,但是参数可能会进行字符串拼接运算。此外,如果 debug(getNameo这种参数内有getName0方法调用,无谓浪费方法调用的开销。

    • 正例:

      // 如果判断为真,那么可以输出 trace 和 debug 级别的日志
      if (logger.isDebugEnabled0) {
              
              
          logger.debug(Current ID is: & and name is: , id, getNamel);
      }
      
  • 【强制】避免重复打印日志,浪费磁密空间 ,务必在日志配置文件中设置 additivity =false

    • 正例:

      <logger name=”com.taobao.dubbo.config” additivity="false>
      
  • 【强制】生产环境禁止直接使用 System.out 或System.err 输出日志或使用e.printStackTrace(打印昇常堆桟。

    • 说明:标准日志输出与标准错误输出文件每次 Jboss 重启时才滚动,如果大量输出送往这两个文件,容易造成文件大小超过操作系统大小限制。
  • 【强制】异常信息应该包括两类信息 :案发现场信息和异常堆栈信息。如果不处理,那么通过关键字 throws 往上抛出。

    • 正例:

      logger.error("inputParams:{
              
              } and errorMessage:{
              
              }”,各类参数或者对系 toString(), e.getMessage(), e);
      
  • 【强制】日志打印时禁止直接用 JSON 工具将对象转换成 String

    • 说明:如果对象里某些 get 方法被覆写,存在抛出异常的情况,则可能会因为打印日志而影响正常业务流程的执行。
    • 正例:打印日志时仅打印出业务相关属性值或者调用其对象的 toString() 方法。
  • 【推荐】递慎地记录日志。生产环境禁止输出 debug 日志:有选择地输出 info 日志:如果使用 warn 来记录刚上线时的业务行为信息,一定要注意日志输出量的问题,避免把服务器磁盘撑爆,并记得及时删除这些观察日志。

    • 说明:大量地输出无效日志 ,不利于系统性能提升 ,也不利于快速定位错误点。记录日志时请思考:这些日志真的有人看吗?看到这条日志你能做什么?能不能给问题排查带来好处?
  • 【推荐】可以使用 warn 日志级别来记录用户输入参数错误的情况,避免用户投诉时,无所适从。如非必要,请不要在此场景打出 error级别,避免频繁报警。

    • 说明:注意日志输出的级别,error 级别只记录系统逻辑出错、异常或者重要的错误信息
  • 【推荐】尽量用英文来描述日志错误信息,如果日志中的错误信息用英文描述不清楚的话使用中文描述即可,否则容易产生歧义。

    • 说明:国际化团队或海外部署的服务器由于字符集问题,使用全英文来注释和描述日志错误信息。

为什么要使用日志规约:

  • 面向接口开发,不再依赖具体的实现类,减少代码的耦合
  • 项目通过导入不同的日志实现类,可以灵活的切换日志框架
  • 统一API、方便开发者学习、使用
  • 统一配置便于项目日志的管理

4.2 SLF4J

​ 简单日志门面(Simple Logging Facade For Java)SLF4J主要是为了给Java日志访问提供一套标准、规范的API框架,其意义主要是提供接口,具体的实现交由日志框架,例如log4j、logback等。当然slf4j 自己也提供了功能较为简单的日志实现,但是我们一般不使用。

对于一般的Java项目而言,我们会选择 slf4j-api 作为门面,再配上具体的日志实现(log4j、logback等),中间使用桥接器完成桥接。

SLF4J主要提供了两大功能:

  • 日志框架的绑定
  • 日志框架的桥接

4.2.1 使用SLF4J入门(slf4j与simple整合)

​ SLF4J(Simple Logging Facade for Java)是一个日志门面框架,它提供了一组简单的接口,用于在Java应用程序中进行日志记录。SLF4J本身提供了一个简单的日志实现,称为SimpleLogger。SimpleLogger是SLF4J的默认日志实现,它不需要额外的依赖,可以直接在项目中使用

要使用SimpleLogger,只需按照以下步骤进行配置:

1)添加SLF4J依赖:在项目的构建文件(如pom.xml)中添加SLF4J的依赖。

pom.xml

<dependencies>
    <!-- SLF4J API -->
    <!-- SLF4J是一个抽象层,它提供了统一的日志接口,可以与不同的日志实现框架(如Logback、Log4j等)进行交互 -->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.32</version>
    </dependency>
	<!-- SLF4J 自带的简单日志实现 -->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-simple</artifactId>
        <version>1.7.32</version>
    </dependency>
</dependencies>

2)配置日志级别:在项目的根目录下创建一个名为simplelogger.properties的文件,并添加以下内容:

这一步可以不配置

org.slf4j.simpleLogger.defaultLogLevel=debug

上述配置将日志级别设置为DEBUG,你可以根据需要将其调整为其他级别,如INFO、WARN、ERROR等。

3)在代码中使用SLF4J接口进行日志记录:在需要记录日志的地方,使用SLF4J提供的接口进行日志记录。

例如:

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

public class SLF4JTest {
    
    
    // 获取Logger实例
    public static final Logger logger = LoggerFactory.getLogger(SLF4JTest.class);
    public static void main(String[] args) {
    
    
        logger.debug("Debug message");
        logger.info("Info message");
        logger.warn("Warning message");
        logger.error("Error message");
    }
}

​ 通过以上步骤,就可以在Java应用程序中使用SLF4J的SimpleLogger进行日志记录。SimpleLogger的实现非常简单,适用于简单的应用程序或测试环境。如果需要更高级的日志功能,可以考虑使用其他日志实现框架,如Logback或Log4j2

4.2.2 绑定其他日志的实现(Binding)

SLF4J支持各种日志框架。SLF4J发行版附带了几个称为“SLF4J绑定”的jar文件,每个绑定对应一个受支持的框架。

使用SLF4J的日志绑定流程:

  • 添加 slf4j-api 的依赖
  • 使用slf4j的API在项目中进行统一的日志记录
  • 绑定具体的日志实现框架
    • 绑定已经实现了slf4j的日志框架,直接添加对应的依赖
    • 绑定没有实现了slf4j的日志框架,先添加日志的适配器,再添加实现类的依赖
  • slf4j有且仅有一个日志实现框架的绑定(如果出现多个默认使用第一个依赖日志实现)

绑定jul的实现(jul是JCL门面的实现),及与jdk14整合

<dependencies>
    <!-- SLF4J API -->
    <!-- SLF4J是一个抽象层,它提供了统一的日志接口,可以与不同的日志实现框架(如Logback、Log4j等)进行交互 -->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.32</version>
    </dependency>

    <dependency>
        <groupId>org.slf4j</groupId>
        <!--jdk14指的是JDK1.4开始支持slf4j绑定JUL-->
        <artifactId>slf4j-jdk14</artifactId>
        <version>1.7.32</version>
    </dependency>
</dependencies>

绑定log4j的实现(slf4j与log4j整合)

<dependencies>
    <!-- SLF4J API -->
    <!-- SLF4J是一个抽象层,它提供了统一的日志接口,可以与不同的日志实现框架(如Logback、Log4j等)进行交互 -->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.32</version>
    </dependency>

    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
        <version>1.7.32</version>
    </dependency>
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.17</version>
    </dependency>
</dependencies>

说明

​ 要切换日志框架,只需要替换类路径上的slf4j绑定。例如,要从java.util.logging(jul)切换到log4j,只需要将 slf4j-jdk14-1.7.32.jar 替换为 slf4j-log4j12-1.7.32.jar 即可。

​ SLF4J不依赖于任何特殊的类加载。实际上,每个SLF4J绑定在编译时都是硬连线的,以使用一个且一个特定的日志记录框架。例如, slf4j-log4j12-1.7.32.jar绑定在编译时绑定以使用log4j

4.2.3 桥接旧的日志框架(Birdging)

​ SLF4J提供了日志桥接(Logging Bridges)的功能,用于将其他日志框架的日志记录转发到SLF4J接口。这样可以在项目中使用SLF4J进行日志记录,同时仍然能够使用其他日志框架的功能

​ 通常,您依赖的某些组件依赖于SLF4J以外的日志记录APl。您也可以假设这些組件在不久的将来不会切换到SLF4J。为了解决这种情况,SLF4J附带了几个桥接模块,这些模共将对log4j,JCL和 java.util.logging API 的调用重定向,就好像它们是对SLF4J API一样。
​ 就是你还用log4j的api写代码,但是具体的实现给你抽离了,我们依赖了一个中间层,这个层其实是用旧的的api操作slf4j,而不是操作具体的实现。

桥接解決的是项目中日志的遗留问题,当系统中存在之前的日志API,可以通过桥接转换到slf4j的实现

  • 1)先去除之前老的日志框架的依赖,必须去掉。
  • 2)添加SLF4J提供的桥接组件,这个组件就是模仿之前老的日志与了一套相同的api,只不过这个api是在调用slf4j的api。
  • 3)为项目添加SLF4J的具体实现。

SLF4J提供的桥接组件

<!-- 桥接的组件-->

<!-- log4j转slf4j -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>log4j-over-slf4j</artifactId>
    <version>1.7.32</version>
</dependency>

<!-- jul转slf4j -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>jul-to-slf4j</artifactId>
    <version>1.7.32</version>
</dependency>

<!-- jcl转slf4j -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>jcl-over-slf4j</artifactId>
    <version>1.7.32</version>
</dependency>

注意:

  • jcl-over-slf4j.jar 和 slf4j-jcl.jar 不能同时部署。前一个jar文件将导致JCL将日志系统的选择委托给 SLF4J,后一个jar文件将导致SLF4J将日志系统的选择委托给 JCL,从而导致无限循环。
  • log4j-over-slf4j.jar 和 slf4j-log4j12.jar 不能同时出现
  • jul-to-slf4j.jar 和 slf4j-jdk14.jar 不能同时出现
  • 所有的桥接都只对Logger日志记录器有效,如果程序中调用了内部的配置类或者是 Appender、Filter等对象,将无法产生效果

4.2.3 SLF4J原理剖析

4.3 JCL

全称为Jakarta Conmons Logging,是Apache提供的一个通用日志API。该日志门面的使用并不是很广泛。

它是为“所有的Java日志实现"提供一个统一的接口,它自身也提供一个日志的实现,但是功能非常的弱 (Simplelog)。所以一般不会单独使用它。他允许开发人员使用不同的具体日志实现工具:Log4j、Jdk自带的日志 (JUL)
JCL有两个基本的抽象类:Log(基本记录器)LogFactory(负责创建Log实例)

简单使用

1)添加依赖

pom.xml

<dependencies>
    <dependency>
        <groupId>commons-logging</groupId>
        <artifactId>commons-logging</artifactId>
        <version>1.2</version>
    </dependency>
</dependencies>

2)编写代码

public class JCLTest {
    
    
    // 创建Logger实例
    public static final Log log = LogFactory.getLog(JCLTest.class);

    public static void main(String[] args) {
    
    
        log.fatal("fatal");
        log.error("error");
        log.warn("warn");
        log.info("info");
        log.debug("debug");
    }

4.4 日志发展历史

Java日志系统历史从入门到崩溃 - 个人文章 - SegmentFault 思否

5 Logback

​ Logback是一个功能强大的Java日志框架,是Log4j框架的继任者(Logback是由Log4j创始人编写的)。它提供了灵活的配置选项和高性能的日志记录功能,被广泛用于Java应用程序的日志管理。

以下是一些Logback的特点和功能:

  1. 灵活的配置:Logback使用XML或Groovy配置文件来定义日志记录器、日志级别、输出格式等。它支持继承和覆盖配置,可以根据不同的环境和需求进行灵活的配置。
  2. 多种日志级别:Logback支持多种日志级别,包括TRACE、DEBUG、INFO、WARN、ERROR等。开发人员可以根据需要选择适当的日志级别来记录不同类型的信息。
  3. 多种输出方式:Logback支持多种输出方式,包括控制台输出、文件输出、远程日志服务器等。开发人员可以根据需求配置多个输出目标,以便在不同环境下进行日志记录和分析。
  4. 异步日志记录:Logback支持异步日志记录,可以提高日志记录的性能。它使用多线程机制将日志事件异步写入目标输出,避免了阻塞应用程序的性能问题。
  5. 运行时配置更新:Logback支持在运行时动态更新配置,无需重启应用程序。这使得开发人员可以在不停止应用程序的情况下修改日志配置,方便调试和故障排查。
  6. 插件支持:Logback提供了丰富的插件支持,可以扩展其功能。例如,可以使用Logstash插件将日志数据发送到Elasticsearch进行分析和可视化。

5.1 三大模块

Logback框架包含三个主要的模块:logback-core、logback-classic 和 logback-access。

  • logback-core:logback-core是Logback框架的核心模块,提供了日志记录和输出的基本功能。

    • 它定义了Logger、Appender、Layout等核心组件的接口和实现,并提供了日志事件的处理和传递机制。
    • logback-core模块是其他两个模块的基础,它不依赖于任何特定的日志实现。
  • logback-classic:logback-classic是Logback框架的经典模块,是logback-core模块的扩展。

    • 它是Log4j的改良版,实现了完整的SLF4J(Simple Logging Facade for Java)的API,并提供了与SLF4J的适配器,使得应用程序可以无缝地迁移和使用Logback作为日志实现。
    • logback-classic模块还提供了一些额外的功能,如MDC(Mapped Diagnostic Context)和LoggerContext等。
  • logback-access:logback-access是Logback框架的访问模块,用于记录和访问HTTP请求的日志。

    • 它提供了与Servlet容器集成的功能,可以记录HTTP请求的详细信息,如请求URL、请求参数、响应状态等。logback-access模块可以与logback-classic模块一起使用,实现全面的日志记录和访问功能。

这三个模块共同构成了Logback框架的核心功能,开发人员可以根据需要选择和配置相应的模块,以满足应用程序的日志管理需求。

5.2 Logbook的使用

1)要想使用Logbook框架,至少需要在pom中导入如下三个依赖:

  • slf4j-api:日志门面
  • logbook-core
  • logback-classic

pom.xml

<dependencies>
    <!-- SLF4J API -->
    <!-- SLF4J是一个抽象层,它提供了统一的日志接口,可以与不同的日志实现框架(如Logback、Log4j等)进行交互 -->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.32</version>
    </dependency>

    <!-- Logback Core -->
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-core</artifactId>
        <version>1.2.3</version>
    </dependency>

    <!-- Logback Classic -->
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.2.3</version>
    </dependency>
</dependencies>

2)在模块的src目录下创建核心配置文件 logback.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <!--
        CONSOLE :表示当前的日志信息是可以输出到控制台的。
    -->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <!--输出流对象 默认 System.out 改为 System.err-->
        <target>System.out</target>
        <encoder>
            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度
                %msg:日志消息,%n是换行符-->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%-5level]  %c [%thread] : %msg%n</pattern>
        </encoder>
    </appender>

    <!-- File是输出的方向通向文件的 -->
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
            <charset>utf-8</charset>
        </encoder>
        <!--日志输出路径-->
        <file>C:/code/data.log</file>
        <!--该目录需要提前存在->
        <!-指定日志文件拆分和压缩规则-->
        <rollingPolicy
                class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!--通过指定压缩文件名称,来确定分割文件方式-->
            <fileNamePattern>C:/code/data-%d{yyyy-MMdd}.log%i.gz</fileNamePattern>
            <!--文件拆分大小-->
            <maxFileSize>1MB</maxFileSize>
        </rollingPolicy>
    </appender>

    <!--

    level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF
   , 默认debug
    <root>可以包含零个或多个<appender-ref>元素,标识这个输出位置将会被本日志级别控制。
    -->
    <root level="ALL">
        <appender-ref ref="CONSOLE"/>
        <appender-ref ref="FILE" />
    </root>
</configuration>

3)创建Logback框架提供的Logger对象,然后用Logger对象调用其提供的方法就可以记录系统的日志信息。

public static final Logger LOGGER = LoggerFactory.getLogger("类名");

简单演示:

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

public class LogBackTest {
    
    
    // 创建Logger日志对象
    public static final Logger LOGGER = LoggerFactory.getLogger("LogBackTest");
    public static void main(String[] args) {
    
    
        try {
    
    
            LOGGER.info("div方法开始执行~~~");
            div(10, 0);
            LOGGER.info("div方法执行成功~~~");
        } catch (ArithmeticException e) {
    
    
            LOGGER.error("div方法执行失败!!!");
        }
    }

    public static void div(int a, int b) {
    
    
        LOGGER.debug("参数a: " + a);
        LOGGER.debug("参数b: " + b);
        System.out.println(a + " / " + b + " = " + a / b);
        LOGGER.info(a + " / " + b + " = " + a / b);
    }
}

结果

16:08:44.962 [main] INFO LogBackTest - div方法开始执行~~~
16:08:44.964 [main] DEBUG LogBackTest - 参数a: 10
16:08:44.964 [main] DEBUG LogBackTest - 参数b: 0
16:08:44.964 [main] ERROR LogBackTest - div方法执行失败!!!

5.3 核心配置文件logback.xml

  • 对LogBack日志框架进行控制

日志的输出位置、输出格式的设置

  • 通常可以设置2个输出日志的位置:一个是控制台、一个是系统文件中
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
  • 开启(ALL)、关闭日志(OFF)
<root level="ALL">
	<appender-ref ref="CONSOLE"/> <!-- 如果不想将日志输出到控制台,注释该行即可-->
    <appender-ref ref="FILE" />
</root>

如下是一个logback.xml的模板

<configuration>

    <!-- 定义日志输出的格式 -->
    <property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"/>

    <!-- 定义日志输出的目录 -->
    <property name="LOG_DIR" value="/path/to/log/directory"/>

    <!-- 定义根日志级别 -->
    <root level="INFO">
        <appender-ref ref="CONSOLE"/>
        <appender-ref ref="FILE"/>
    </root>

    <!-- 定义控制台输出的日志 -->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>${LOG_PATTERN}</pattern>
        </encoder>
    </appender>

    <!-- 定义文件输出的日志 -->
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_DIR}/application.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_DIR}/application.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <maxHistory>30</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>${LOG_PATTERN}</pattern>
        </encoder>
    </appender>

</configuration>
  • 定义日志输出的格式:通过<property>元素定义一个名为LOG_PATTERN的属性,可以在后面的配置中引用该属性。
  • 定义日志输出的目录:通过<property>元素定义一个名为LOG_DIR的属性,可以在后面的配置中引用该属性。
  • 定义根日志级别:通过<root>元素定义根日志的级别,以及要使用的日志输出器(appender)。
  • 定义控制台输出的日志:通过<appender>元素定义一个名为CONSOLE的控制台输出器,使用ConsoleAppender类,并配置日志格式。
  • 定义文件输出的日志:通过<appender>元素定义一个名为FILE的文件输出器,使用RollingFileAppender类,并配置日志文件路径、滚动策略、日志格式等。

打印日志到控制台

<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
    <!--输出流对象 默认 System.out 改为 System.err-->
    <target>System.out</target>
    <encoder>
        <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度
                %msg:日志消息,%n是换行符-->
        <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%-5level]  %c [%thread] : %msg%n</pattern>
    </encoder>
</appender>
  • ConsoleAppender类表示日志信息输出到控制台

  • <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%-5level] %c [%thread] : %msg%n中:

    • 表示输出格式
    • %d{yyyy-MM-dd HH:mm:ss.SSS} 表示日期年月日时分秒
    • %-5level 表示日志输出的级别
    • %c 表示当前所在类(打印日志的类)
    • %thread 表示当前线程
    • %msg 表示日志信息
    • %n 表示换行

打印日志到文件

<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <encoder>
        <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
        <charset>utf-8</charset>
    </encoder>
    <!--日志输出路径-->
    <file>C:/code/data.log</file>
    <!--指定日志文件拆分和压缩规则-->
    <rollingPolicy
                   class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
        <!--通过指定压缩文件名称,来确定分割文件方式-->
        <fileNamePattern>C:/code/data-%d{yyyy-MMdd}.log%i.gz</fileNamePattern>
        <!--文件拆分大小-->
        <maxFileSize>1MB</maxFileSize>
    </rollingPolicy>
</appender>
  • RollingFileAppender类表示日志信息输出到文件

  • <charset>utf-8</charset> 记录写入到文件中的字符集编码为UTF-8

  • <file>C:/code/data.log</file> 控制文件输出的路径

5.4 Logback日志级别的设置

以下是Logback中定义的日志级别:(从小到大)

  • TRACE:最低的日志级别,用于输出最详细的日志信息,通常用于调试目的。(追踪,指明程序运行轨迹)
  • DEBUG:用于输出调试信息,比TRACE级别更高,通常用于开发和调试阶段。
  • INFO:用于输出一般的信息性消息,表示程序正常运行的状态。
  • WARN:用于输出警告信息,表示潜在的问题或不符合预期的情况,但不会影响程序的正常运行。
  • ERROR:用于输出错误信息,表示严重的问题或错误,可能会导致程序的异常终止或不可恢复的错误。

除了以上五个标准的日志级别,Logback还支持自定义的日志级别。可以根据具体的需求定义自己的日志级别,并在配置文件中进行配置。

​ 在Logback中,可以通过配置文件(通常是logback.xmllogback.groovy)来设置日志级别。可以为不同的日志记录器(Logger)设置不同的日志级别,以控制不同部分的日志输出。

例如,可以使用以下配置将日志级别设置为DEBUG

  • 只有日志级别是大于或等于核心配置文件配置的日志级别,才会别记录,否则不会被记录
<root level="DEBUG">
    <appender-ref ref="CONSOLE"/>
    <appender-ref ref="FILE" />
</root>

5.5 日志拆分

在 Logback 中,可以通过配置来实现日志的拆分。拆分日志可以按照时间、文件大小或者其他条件进行

  • 时间拆分:使用 TimeBasedRollingPolicy 可以按照时间来拆分日志文件。可以通过 <fileNamePattern> 指定拆分后的日志文件名格式,例如 logfile-%d{yyyy-MM-dd}.%i.log,其中 %d{yyyy-MM-dd} 表示按照日期进行拆分,%i 表示拆分后的文件索引。可以通过 <maxHistory> 指定保留的历史日志文件的数量。
  • 大小拆分:使用 SizeBasedTriggeringPolicy 可以按照文件大小来拆分日志文件。可以通过 <maxFileSize> 指定每个日志文件的最大大小,例如 10MB。当日志文件达到指定大小时,会自动拆分为新的日志文件。
  • 条件拆分:使用 OnMarkerEvaluator 可以根据自定义的条件来拆分日志文件。可以通过 <evaluator> 配置一个自定义的 OnMarkerEvaluator,并在代码中使用 Marker 来触发日志拆分。

演示:

将日志按照时间和文件大小进行拆分

<configuration>
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>/path/to/logfile.log</file>
        <!-- 按照时间来拆分日志文件 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 指定拆分后的日志文件名格式 -->
            <fileNamePattern>/path/to/logfile-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <!-- 指定保留的历史日志文件的数量 -->
            <maxHistory>30</maxHistory>
        </rollingPolicy>
        <!-- 按照文件大小来拆分日志文件 -->
        <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
            <!-- 指定每个日志文件的最大大小 -->
            <maxFileSize>10MB</maxFileSize>
        </triggeringPolicy>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

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

上面配置中,日志文件将按照日期进行拆分,每天生成一个新的日志文件。同时,当日志文件达到 10MB 时,也会触发拆分为新的日志文件

5.6 Logback过滤器

在 Logback 中,可以使用过滤器来控制日志的输出。过滤器可以根据不同的条件来决定是否输出某条日志。

Logback 提供了多种类型的过滤器,常用的过滤器包括:

  • LevelFilter根据日志级别进行过滤。可以通过设置 level 属性来指定过滤的日志级别,只有达到指定级别的日志才会被输出。
  • ThresholdFilter:根据日志级别的阈值进行过滤。可以通过设置 level 属性来指定阈值,只有达到或超过指定级别的日志才会被输出。
  • EvaluatorFilter:根据自定义的评估器进行过滤。可以通过设置 evaluator 属性来指定一个自定义的评估器,根据评估器的结果来决定是否输出日志。
  • MarkerFilter:根据 Marker 进行过滤。可以通过设置 marker 属性来指定一个 Marker,只有包含指定 Marker 的日志才会被输出。
  • TurboFilter:自定义的高性能过滤器。可以通过继承 ch.qos.logback.core.spi.TurboFilter 类来实现自定义的过滤器。

演示:

使用 LevelFilter 过滤器只输出 INFO 级别的日志

<configuration>
  <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
      <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
    </encoder>
  </appender>
  
  <root level="DEBUG">
    <appender-ref ref="CONSOLE" />
  </root>
  
  <logger name="com.clear" level="DEBUG" />
  
  <filter class="ch.qos.logback.classic.filter.LevelFilter">
    <!-- 只输出 INFO 级别的日志 -->
    <level>INFO</level>
    <onMatch>ACCEPT</onMatch>
    <onMismatch>DENY</onMismatch>
  </filter>
</configuration>

在上述配置中,所有的日志都会输出到控制台。但是通过 LevelFilter 过滤器,只有 INFO 级别的日志才会被接受(输出),其他级别的日志会被拒绝(不输出)

5.7 Logback异步日志

打印日志到文件中需要涉及到大量的文件IO,性能比较低,Logback之所以高效就是因为它支持异步日志

在 Logback 中,可以使用异步日志来提高日志的性能和吞吐量。异步日志将日志的写入操作放在一个独立的线程中进行,不会阻塞主线程的执行。

使用方式:

​ 要启用异步日志,可以使用 Logback 提供的 AsyncAppenderAsyncAppender 是一个包装器,可以将其他的 Appender 转换为异步的。

演示:

<configuration>
    <appender name="ASYNC_FILE" class="ch.qos.logback.classic.AsyncAppender">
        <!-- 使用 AsyncAppender 包装了这个 FileAppender,将其转换为异步的 -->
        <appender-ref ref="FILE" />
        <!-- 指定了异步队列的大小 -->
        <queueSize>512</queueSize>
        <!-- 指定了当队列满时是否丢弃日志 -->
        <!-- 0,表示不丢弃任何日志事件。新的日志事件将被阻塞,直到队列中有空闲位置-->
        <!-- 大于0,表示当队列已满时,丢弃最早的<discardingThreshold>个日志事件,然后将新的日志事件添加到队列中 -->
        <!-- 如果对日志的实时性要求较高,可以将 <discardingThreshold> 的值设置为一个较小的数值,以避免队列过大导致的性能问题-->
        <discardingThreshold>0</discardingThreshold>
    </appender>

    <!-- FileAppender,将日志输出到文件 logs/myapp.log -->
    <appender name="FILE" class="ch.qos.logback.core.FileAppender">
        <file>logs/myapp.log</file>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <root level="DEBUG">
        <!-- 用于将一个或多个 appender 引用到根日志中。在这里,根日志引用了名为 ASYNC_FILE 的 appender,表示根日志的输出目标是 ASYNC_FILE appender-->
        <appender-ref ref="ASYNC_FILE" />
    </root>
</configuration>

6 LOG4J2

​ Log4j 2 是一个用于 Java 应用程序的日志框架,它提供了灵活的配置和强大的日志功能。与 Logback 类似,Log4j 2 也支持异步日志,可以提高日志的性能和吞吐量(其实就是参考了Logback而设计出来的)

6.1 与Log4J的关系

LOG4J和LOG4J2都是Java的日志框架,它们都提供了强大的日志记录和管理功能。然而,它们在设计和实现上有一些区别。

  • 设计理念:LOG4J是LOG4J2的前身,它的设计目标是提供一个简单、灵活的日志框架。LOG4J2则是LOG4J的升级版,它在LOG4J的基础上进行了重构和改进,旨在提供更高的性能和更丰富的功能。
  • 性能:LOG4J2相对于LOG4J在性能上有所提升。LOG4J2使用了异步日志记录机制,可以在不阻塞应用程序的情况下进行日志记录,从而提高了应用程序的性能。
  • 配置方式:LOG4J使用基于属性的配置文件(如log4j.properties)或基于XML的配置文件(如log4j.xml)来配置日志记录器和输出目标。LOG4J2则引入了新的配置方式,支持使用XML、JSON、YAML等格式的配置文件,同时也支持使用编程API进行配置。
  • 插件支持:LOG4J2引入了插件机制,可以通过插件扩展日志框架的功能。LOG4J2提供了一些内置的插件,如异步日志记录器、日志事件过滤器等,同时也支持自定义插件。
  • 日志框架迁移:由于LOG4J2在设计和实现上与LOG4J有所不同,因此在迁移项目中使用LOG4J2时,可能需要进行一些代码和配置的调整。

总的来说,LOG4J2是LOG4J的升级版,它在性能、配置方式和功能扩展方面有所改进。如果你正在开始一个新的项目,或者希望提升现有项目的日志性能和功能,那么LOG4J2可能是一个更好的选择。但如果你已经在使用LOG4J,并且没有特别的需求,那么继续使用LOG4J也是可以的。

6.2 默认配置

DefaultConfiguration类提供的默认配置

private volatile Configuration configuration = new DefaultConfiguration();

6.3 Log4j2的使用

6.3.1 使用log4j-api做门面

要使用 Log4j 2,需要在项目中添加 Log4j 2 的依赖,并配置 Log4j 2 的配置文件

1)pom.xml

<dependencies>
    <!-- Log4j2门面API-->
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-api</artifactId>
        <version>2.14.1</version>
    </dependency>
    <!-- Log4j2日志实现-->
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-core</artifactId>
        <version>2.14.1</version>
    </dependency>
</dependencies>

2)配置 Log4j 2 的配置文件,位于类路径的根目录下。

log4j2.xml

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="INFO">
    <Appenders>
        <!-- 定义一个名为Console的Appender,将日志输出到控制台 -->
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
        </Console>
    </Appenders>
    <Loggers>
        <!-- 根日志的级别被设置为 DEBUG-->
        <Root level="DEBUG">
            <AppenderRef ref="Console" />
        </Root>
    </Loggers>
</Configuration>

3)代码中使用

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.junit.Test;

public class Log4j2Test {
    
    
    private static final Logger LOGGER = LogManager.getLogger(Log4j2Test.class);
    @Test
    public void test(){
    
    
        LOGGER.fatal("fatal");
        LOGGER.error("error");
        LOGGER.warn("warn");
        LOGGER.info("info");
        LOGGER.debug("debug");
        LOGGER.trace("trace");
    }
}

结果:

21:24:15.027 [main] FATAL com.clear.Log4j2Test - fatal
21:24:15.027 [main] ERROR com.clear.Log4j2Test - error
21:24:15.027 [main] WARN  com.clear.Log4j2Test - warn
21:24:15.027 [main] INFO  com.clear.Log4j2Test - info
21:24:15.027 [main] DEBUG com.clear.Log4j2Test - debug

6.3.2 使用slf4j做门面

使用slf4j作为日志的门面,使用log4j2作为日志的实现

1)pom.xml

<dependencies>
    <!-- Log4j2门面API-->
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-api</artifactId>
        <version>2.14.1</version>
    </dependency>
    <!-- Log4j2日志实现-->
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-core</artifactId>
        <version>2.14.1</version>
    </dependency>
    <!-- 使用slf4j作为日志的门面,使用log4j2来记录日志-->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.32</version>
    </dependency>
    <!-- 为slf4j绑定日志实现 log4j2的适配器-->
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-slf4j-impl</artifactId>
        <version>2.12.1</version>
    </dependency>
</dependencies>

6.4 log4j2异步日志

要使用 Log4j 2 的异步日志功能,你需要进行以下配置:

1)添加 Log4j 2 的异步日志依赖

pom.xml

<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-async</artifactId>
    <version>2.14.1</version>
</dependency>

2)创建 Log4j 2 的配置文件 log4j2.xml,并将其放置在类路径下

log4j2.xml

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
    <Appenders>
        <!-- 将日志输出到控制台,并使用异步日志记录。 -->
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
        </Console>
    </Appenders>
    <Loggers>
        <Root level="info">
            <AppenderRef ref="Console"/>
        </Root>
    </Loggers>
</Configuration>

3)在代码中使用log4j的API记录日志

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.junit.Test;

public class Log4j2Test {
    
    
    private static final Logger LOGGER = LogManager.getLogger(Log4j2Test.class);
    @Test
    public void test(){
    
    
        LOGGER.fatal("fatal");
        LOGGER.error("error");
        LOGGER.warn("warn");
        LOGGER.info("info");
        LOGGER.debug("debug");
        LOGGER.trace("trace");
    }
}

注意:

​ 异步日志可以提高性能,但也可能会导致日志消息的顺序不一致。如果你需要确保日志消息的顺序,请使用同步日志。

7 日志的打印

基本格式

必须使用参数化信息的方式:

logger.debug("Processing ing trade with id:[{}] and symbol:[{}]",id,symbol);

不要进行字符串拼接,那样会产生多个String对象,占用空间,影响性能。

错误示例:

logger.debug("Processing ing trade with id:“ + id + ” symbol:" + symbol);

ERROR,影响到程序正常运行、当前请求正常运行的异常情况:

  • 打开配置文件失败

  • 所有第三方对接的异常(包括第三方返回异常码)

  • 所有影响功能使用的异常,包括 SQLException 和除了业务异常之外的所有异常(RuntimeException和Exception)

  • 不应该出现的情况,比如使用阿里云传图片,但是未响应

  • 如果有Throwable信息,需要记录完整的堆栈信息

log.error("获取用户[{}]的用户信息时出错", userName, e);

说明:

​ 如果进行了抛出异常操作,请不要记录error日志,由最终处理方进行处理

反例(错误示例):

try{
    
    
	...
}catch(Exception e){
    
    
	String errorMessage = String.format("Error while reading information of user [%s]",userName);
    logger.error(errorMessage, e);
    throw new UserServiceException(errorMessage, e);
}

WARN,不应该出现但是不影响程序、当前请求正常运行的异常情况:

有容错机制的时候出现的错误情况

找不到配置文件,但是系统能自动创建配置文件,比如我们第一次格式化Hadoop时就会出现WARN,并帮我们创建日志

即将接近临界点的时候,比如缓存池占用达到警戒线,业务异常的记录,比如用户锁定异常

INFO,系统运行信息

  • Service方法中对于系统/业务状态的变更
  • 主要逻辑的分步骤:1.初始化什么 2.加载什么
  • 外部接口的部分
  • 客户端请求参数(REST/WS)
  • 调用第三方时的调用参数和调用结果
  • 对于复杂的业务逻辑,需要进入日志打点,以及埋点记录
  • 调用其它第三方服务时,所有的出参和入参是必须要记录的(因为我们很难追溯第三方的错误)

说明

​ 并不是所有的service都进行出入口大点记录,单一、简单service是没有意义的(job除外,job需要记录开始、结束)

反例:

public List listByBaseType(Integer baseTypeId){
    
    
	log.info("开始查询基地");
    BaseExample e = new BaseExample();
    BaseExample.Criteria ctr = e.createCriteria();
    ctr.andIsDeleteEqualTo(IsDelete.USE.getValue());
    Options.doIfPresent(baseTypeId, ctr::andIsDeleteEqualTo);
    log.info("查询基地结束");
    return baseRepository.selectByExample(e);
}

DEBUG,可以填写所有的想知道的信息(但是也需要填写有意义的信息

  • 生成环境需要关闭DEBUG信息

  • 如果在生产环境情况下需要开启DEBUG,需要使用开关进行管理,不能一直开启

说明

​ 如果代码中出现以下代码,可以进行优化:

​ 1.获取用户基本薪资

​ 2.获取用户休假情况

​ 3.计算用户所得薪资

logger.debug("开始获取员工[{}] [{}]年基本薪资", employee, year);	
logger.debug("获取员工[{}] [{}]年基本薪资为[{}]", employee, year, basicSalary);	
logger.debug("开始获取员工[{}] [{}]年[{}]月休假情况", employee, year, month);	
logger.debug("员工[{}] [{}]年[{}]月年假/病假/事假为[{}]/[{}]/[{}]", employee, year, month, annualLeaveDays, sickLeaveDays, noPayLeaveDays);	
logger.debug("开始计算员工[{}] [{}]年[{}]月应得薪资", employee, year, month);	
logger.debug("员工[{}] [{}]年[{}]月应得薪资为[{}]", employee, year, month, actualSaraly);

TRACE,特别详细的系统运行完全信息,业务代码中,不要使用(除非有特殊用意,否则使用DEBUG代替)

Guess you like

Origin blog.csdn.net/weixin_56058578/article/details/132814191