Java 日志框架详解

Java 日志

1. JUL学习

JUL全称Java util Logging是java原生的日志框架,使用时不需要另外引用第三方类库,相对其他日志框 架使用方便,学习简单,能够在小型应用中灵活使用。

1.1 架构介绍

image-20211222111107875

  • Loggers:被称为记录器,应用程序通过获取Logger对象,调用其API来来发布日志信息。Logger通常时应用程序访问日志系统的入口程序。

  • Appenders:也被称为Handlers,每个Logger都会关联一组Handlers,Logger会将日志交给关联Handlers处理,由Handlers负责将日志做记录。Handlers在此是一个抽象,其具体的实现决定了日志记录的位置可以是控制台、文件、网络上的其他日志服务或操作系统日志等。

  • Layouts:也被称为Formatters,它负责对日志事件中的数据进行转换和格式化。Layouts决定了数据在一条日志记录中的最终形式。

  • Level:每条日志消息都有一个关联的日志级别。该级别粗略指导了日志消息的重要性和紧迫,我可以将Level和Loggers,Appenders做关联以便于我们过滤消息。

  • Filters:过滤器,根据需要定制哪些信息会被记录,哪些信息会被放过。

总结

用户使用Logger来进行日志记录,Logger持有若干个Handler,日志的输出操作是由Handler完成的。在Handler在输出日志前,会经过Filter的过滤,判断哪些日志级别过滤放行哪些拦截,Handler会将日志内容输出到指定位置(日志文件、控制台等)。Handler在输出日志时会使用Layout,将输出内容进行排版。

@Test
public void test01(){
    Logger logger = Logger.getLogger("cn.quguai.JULTest");
    logger.info("Hello JUL");

    logger.log(Level.INFO, "info msg");

    logger.log(Level.INFO, "message:{0}:{1}", new Object[]{"quguai", 1});
}

1.2 日志级别

java.util.logging.Level中定义了日志的级别:

  • SEVERE(最高值)
  • WARNING
  • INFO (默认级别)
  • CONFIG
  • FINE
  • FINER
  • FINEST(最低值)

还有两个特殊的级别:

  • OFF,可用来关闭日志记录。
  • ALL,启用所有消息的日志记录。
@Test
public void testLevel(){
    Logger logger = Logger.getLogger("cn.quguai.JULTest");
    logger.severe("severe");
    logger.warning("warning");
    logger.info("info");
    logger.config("config");
    logger.fine("fine");
    logger.finer("finer");
    logger.finest("finest");
}

1.3 自定义日志级别配置

@Test
public void testSetLevel() throws IOException {
    Logger logger = Logger.getLogger("cn.quguai.JULTest");

    // 关闭原有处理器
    logger.setUseParentHandlers(false);

    // 创建ConsoleHandle 和 SimpleFormatter 格式化器
    ConsoleHandler consoleHandler = new ConsoleHandler();
    SimpleFormatter simpleFormatter = new SimpleFormatter();

    // 进行关联
    logger.addHandler(consoleHandler);
    consoleHandler.setFormatter(simpleFormatter); // 默认就是SimpleFormatter

    // 创建FileHandle
    FileHandler fileHandler = new FileHandler("./jul.log");

    // 进行关联
    logger.addHandler(fileHandler);
    fileHandler.setFormatter(simpleFormatter);

    // 设置默认级别
    consoleHandler.setLevel(Level.ALL);
    logger.setLevel(Level.ALL);
    // fileHandler.setLevel(Level.ALL); 默认沿用logger的配置

    logger.severe("severe");
    logger.warning("warning");
    logger.info("info");
    logger.config("config");
    logger.fine("fine");
    logger.finer("finer");
    logger.finest("finest");
}

1.4 Logger 父子关系

@Test
public void testLogParent(){
    Logger logger1 = Logger.getLogger("cn.quguai");
    Logger logger2 = Logger.getLogger("cn");
    // 默认顶级Logger对象是 java.util.logging.LogManager$RootLogger
    System.out.println(logger2.getParent());

    // 修改logger2的配置
    logger2.setUseParentHandlers(false);
    ConsoleHandler consoleHandler = new ConsoleHandler();
    logger2.addHandler(consoleHandler);

    logger2.setLevel(Level.ALL);
    consoleHandler.setLevel(Level.ALL);

    logger1.severe("severe");
    logger1.warning("warning");
    logger1.info("info");
    logger1.config("config");
    logger1.fine("fine");
    logger1.finer("finer");
    logger1.finest("finest");
}

1.5 Logger 默认配置文件

文件路径:D:\jdk-11.0.10\conf\logging.properties

java.util.logging.Logger#getLogger() ->
java.util.logging.Logger#demandLogger ->
java.util.logging.LogManager#getLogManager ->
java.util.logging.LogManager#ensureLogManagerInitialized->
java.util.logging.LogManager#readPrimordialConfiguration ->
java.util.logging.LogManager#readConfiguration() ->
java.util.logging.LogManager#getConfigurationFileName

############################################################
#  	Global properties
############################################################

# "handlers" specifies a comma separated list of log Handler 
# classes.  These handlers will be installed during VM startup.
# Note that these classes must be on the system classpath.
# By default we only configure a ConsoleHandler, which will only
# show messages at the INFO and above levels.
handlers= java.util.logging.ConsoleHandler

# To also add the FileHandler, use the following line instead.
#handlers= java.util.logging.FileHandler, java.util.logging.ConsoleHandler

# Default global logging level.
# This specifies which kinds of events are logged across
# all loggers.  For any given facility this global level
# can be overriden by a facility specific level
# Note that the ConsoleHandler also has a separate level
# setting to limit messages printed to the console.
.level= INFO

############################################################
# Handler specific properties.
# Describes specific configuration info for Handlers.
############################################################

# default file output is in user's home directory.
java.util.logging.FileHandler.pattern = %h/java%u.log   # 当前用户目录下 java01.log
java.util.logging.FileHandler.limit = 50000
java.util.logging.FileHandler.count = 1
# Default number of locks FileHandler can obtain synchronously.
# This specifies maximum number of attempts to obtain lock file by FileHandler
# implemented by incrementing the unique field %u as per FileHandler API documentation.
java.util.logging.FileHandler.maxLocks = 100
java.util.logging.FileHandler.formatter = java.util.logging.XMLFormatter

# Limit the message that are printed on the console to INFO and above.
java.util.logging.ConsoleHandler.level = INFO
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter

# Example to customize the SimpleFormatter output format 
# to print one-line log message like this:
#     <level>: <log message> [<date/time>]
#
# java.util.logging.SimpleFormatter.format=%4$s: %5$s [%1$tc]%n

############################################################
# Facility specific properties.
# Provides extra control for each logger.
############################################################

# For example, set the com.xyz.foo logger to only log SEVERE
# messages:
com.xyz.foo.level = SEVERE

1.6 自定义配置文件

# 顶级父元素的默认的处理器
handlers= java.util.logging.ConsoleHandler,java.util.logging.FileHandler   // 追加文件管理器

# 顶级父元素RootLogger的默认输出级别
.level= ALL  // 修改默认级别
# 文件配置路径
java.util.logging.FileHandler.pattern = ./java%u.log
# 指定日志文件内容大小
java.util.logging.FileHandler.limit = 50000
# 指定日志文件数量
java.util.logging.FileHandler.count = 1

java.util.logging.FileHandler.maxLocks = 100
java.util.logging.FileHandler.formatter = java.util.logging.XMLFormatter  #XML格式化器
java.util.logging.FileHandler.append = true

java.util.logging.ConsoleHandler.level = ALL  # 修改默认级别
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
java.util.logging.ConsoleHandler.encoding = UTF-8
@Test
public void testProperties() throws IOException {
    InputStream ins = JULTest.class.getClassLoader().getResourceAsStream("logging.properties");
    
    // LogManager 是一个单例对象
    LogManager logManager = LogManager.getLogManager();
    logManager.readConfiguration(ins);

    Logger logger = Logger.getLogger("cn.quguai.JULTest");
    logger.severe("severe");
    logger.warning("warning");
    logger.info("info");
    logger.config("config");
    logger.fine("fine");
    logger.finer("finer");
    logger.finest("finest");
    ins.close();
}

1.7 JUL配置文件详情

1.7.1 ConsoleHandler配置形式

java.util.logging.ConsoleHandler.encoding = UTF-8

image-20211222155356134

1.7.2 配置文件格式化方式

java.util.logging.SimpleFormatter.format = %4$s: %5$s [%1$tc]%n

SurrogateLogger.getSimpleFormat 获取配置文件中的key值对应的格式化方式

image-20211222160043652

1.7.3 追加文件

java.util.logging.FileHandler.append = true

1.7.4 自定义Logger

# 自定义Logger
cn.quguai.handlers = java.util.logging.FileHandler
cn.quguai.level = CONFIG
cn.quguai.useParentHandlers=false

1.8 日志原理解析

  1. 初始化LogManager
    1. LogManager加载logging.properties配置
    2. 添加Logger到LogManager
  2. 从单例LogManager获取Logger
  3. 设置级别Level,并指定日志记录LogRecord
  4. Filter提供了日志级别之外更细粒度的控制
  5. Handler是用来处理日志输出位置
  6. Formatter是用来格式化LogRecord的

image-20211222164607347

2. Log4j

官网地址 | 官方文档

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

2.1 日志级别

级别含义
fatal指出每个严重的错误事件将会导致应用程序的退出。
error虽然发生错误事件,但仍然不影响系统的继续运行。
warn表明会出现潜在的错误情形。
info一般和在粗粒度级别上,强调应用程序的运行全程。
debug一般用于细粒度级别上,对调试应用程序非常有帮助。默认级别
trace是程序追踪,可以用于输出程序运行中的变量,显示执行的流程。

推荐:实际开发中只会使用到最中间的四个步骤。

@Test
public void testQuick(){
    Logger logger = Logger.getLogger(Log4jTest.class);
    BasicConfigurator.configure();
    // logger.setLevel(Level.INFO);
    logger.fatal("fatal");
    logger.error("error");
    logger.warn("warn");
    logger.info("info");
    logger.debug("debug");  // 默认级别
    logger.trace("trace");
}

2.2 Log4j 组件

Log4J 主要由 Loggers (日志记录器)、Appenders(输出端)和 Layout(日志格式化器)组成。

其中 Loggers 控制日志的输出级别与日志是否输出;Appenders 指定日志的输出方式(输出到控制台、文件 等);Layout 控制日志信息的输出格式。

2.2.1 Loggers

日志记录器,负责收集处理日志记录,实例的命名就是类“XX”的full quailied name(类的全限定名), Logger的名字大小写敏感,其命名有继承机制:例如:name为org.apache.commons的logger会继承 name为org.apache的logger。

Log4J中有一个特殊的logger叫做“root”,他是所有logger的根,也就意味着其他所有的logger都会直接 或者间接地继承自root。root logger可以用Logger.getRootLogger()方法获取。

但是,自log4j 1.2版以来, Logger 类已经取代了 Category 类。对于熟悉早期版本的log4j的人来说, Logger 类可以被视为 Category 类的别名。

img

2.2.2 Appenders

Appender 用来指定日志输出到哪个地方,可以同时指定日志的输出目的地。Log4j 常用的输出目的地 有以下几种:

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

2.2.3 Layouts

布局器 Layouts用于控制日志输出内容的格式,让我们可以使用各种需要的格式输出日志。Log4j常用 的Layouts:

格式化器类型作用
HTMLLayout格式化日志输出为HTML表格形式
SimpleLayout简单的日志输出格式化,打印的日志格式为(info - message)
PatternLayout最强大的格式化期,可以根据自定义格式输出日志,如果没有指定转换格式, 就是用默认的转换格式

2.3 配置文件详情

配置文件的后缀名称全部来源于对应的set方法注入,例如conversionPattern 使用SetConversionPattern方法完成

log4j.rootLogger = trace,console

# 指定appender
log4j.appender.console = org.apache.log4j.ConsoleAppender
# 指定layout
log4j.appender.console.layout = org.apache.log4j.PatternLayout
# 指定格式化信息
log4j.appender.console.layout.conversionPattern = %r [%5t] %p %l - %m%n

2.4 配置文件源码

2.4.1 支持的配置文件

public class LogManager {
    /** @deprecated */
    public static final String DEFAULT_CONFIGURATION_FILE = "log4j.properties";
    static final String DEFAULT_XML_CONFIGURATION_FILE = "log4j.xml";
    /** @deprecated */
    public static final String DEFAULT_CONFIGURATION_KEY = "log4j.configuration";
    /** @deprecated */
    public static final String CONFIGURATOR_CLASS_KEY = "log4j.configuratorClass";
    /** @deprecated */
    public static final String DEFAULT_INIT_OVERRIDE_KEY = "log4j.defaultInitOverride";
}

org.apache.log4j.LogManager ->
org.apache.log4j.helpers.OptionConverter#selectAndConfigure ->
org.apache.log4j.PropertyConfigurator ->

public class PropertyConfigurator implements Configurator {
    protected Hashtable registry = new Hashtable(11);
    protected LoggerFactory loggerFactory = new DefaultCategoryFactory();
    static final String CATEGORY_PREFIX = "log4j.category.";
    static final String LOGGER_PREFIX = "log4j.logger.";
    static final String FACTORY_PREFIX = "log4j.factory";
    static final String ADDITIVITY_PREFIX = "log4j.additivity.";
    static final String ROOT_CATEGORY_PREFIX = "log4j.rootCategory";
    static final String ROOT_LOGGER_PREFIX = "log4j.rootLogger";
    static final String APPENDER_PREFIX = "log4j.appender.";
    static final String RENDERER_PREFIX = "log4j.renderer.";
    static final String THRESHOLD_PREFIX = "log4j.threshold";
    public static final String LOGGER_FACTORY_KEY = "log4j.loggerFactory";
    private static final String INTERNAL_ROOT_NAME = "root";
}

2.4.2 rootLogger 解析原理

rootLogger 的配置源码 org.apache.log4j.PropertyConfigurator#parseCategory

void parseCategory(Properties props, Logger logger, String optionKey, String loggerName, String value) {
    LogLog.debug("Parsing for [" + loggerName + "] with value=[" + value + "].");
    // 按照逗号进行分割
    StringTokenizer st = new StringTokenizer(value, ",");
    if (!value.startsWith(",") && !value.equals("")) {
        if (!st.hasMoreTokens()) {
            return;
        }
		// 第一个参数作为 level
        String levelStr = st.nextToken();
        LogLog.debug("Level token is [" + levelStr + "].");
        if (!"inherited".equalsIgnoreCase(levelStr) && !"null".equalsIgnoreCase(levelStr)) {
            logger.setLevel(OptionConverter.toLevel(levelStr, Level.DEBUG));
        } else if (loggerName.equals("root")) {
            LogLog.warn("The root logger cannot be set to null.");
        } else {
            logger.setLevel((Level)null);
        }

        LogLog.debug("Category " + loggerName + " set to " + logger.getLevel());
    }

    logger.removeAllAppenders();
	// 后续的参数作为 appender
    while(st.hasMoreTokens()) {
        String appenderName = st.nextToken().trim();
        if (appenderName != null && !appenderName.equals(",")) {
            LogLog.debug("Parsing appender named \"" + appenderName + "\".");
            Appender appender = this.parseAppender(props, appenderName);
            if (appender != null) {
                logger.addAppender(appender);
            }
        }
    }
}

2.5 内部日志记录

public class LogLog {
    public static void debug(String msg) {
        if (debugEnabled && !quietMode) {
            System.out.println("log4j: " + msg);
        }
    }
}

可以使用 LogLog.setInternalDebugging(true) 打开开关,这是LogLog的内部静态方法。

2.6 Layouts 格式

在 log4j.properties 配置文件中,我们定义了日志输出级别与输出端,在输出端中分别配置日志的输出 格式。

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输出代码中的行号,【不推荐】会影响性能
%%输出一个 "%" 字符

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

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

2.7 日志文件配置

log4j.rootLogger = trace,console

# 指定appender
log4j.appender.console = org.apache.log4j.ConsoleAppender
# 指定layout
log4j.appender.console.layout = org.apache.log4j.PatternLayout
# 指定格式化信息
log4j.appender.console.layout.conversionPattern = %r [%5t] %p %l - %m%n

log4j.appender.file = org.apache.log4j.FileAppender
log4j.appender.file.layout = org.apache.log4j.PatternLayout
log4j.appender.file.layout.conversionPattern = %r [%5t] %p %l - %m%n
# 来源于SetFile方法
log4j.appender.file.file = ./log4j.log
log4j.appender.file.encoding = UTF-8

2.7.1 RollingFileAppender

log4j.rootLogger = trace,rollingFile

log4j.appender.rollingFile = org.apache.log4j.RollingFileAppender
log4j.appender.rollingFile.layout = org.apache.log4j.PatternLayout
log4j.appender.rollingFile.layout.conversionPattern = %r [%5t] %p %l - %m%n
log4j.appender.rollingFile.file = ./log4j.log
log4j.appender.rollingFile.encoding = UTF-8
log4j.appender.rollingFile.maxFileSize = 10KB
log4j.appender.rollingFile.maxBackupIndex = 1

2.7.2 DailyRollingFileAppender

log4j.rootLogger = trace,dailyRollingFile

# 按照文件的大小进行拆分
log4j.appender.dailyRollingFile = org.apache.log4j.DailyRollingFileAppender
log4j.appender.dailyRollingFile.layout = org.apache.log4j.PatternLayout
log4j.appender.dailyRollingFile.layout.conversionPattern = %r [%5t] %p %l - %m%n
log4j.appender.dailyRollingFile.file = ./log4j.log
log4j.appender.dailyRollingFile.encoding = UTF-8
# 指定日期拆分规则
log4j.appender.dailyRollingFile.datePattern = '.'yyyy-MM-dd-HH-mm-ss

2.7.3 JDBCAppender

log4j.rootLogger = trace,sql

# 按照数据库级别进行日志记录
log4j.appender.sql = org.apache.log4j.jdbc.JDBCAppender
log4j.appender.sql.layout = org.apache.log4j.PatternLayout
log4j.appender.sql.Driver = com.mysql.jdbc.Driver
log4j.appender.sql.URL = jdbc:mysql://localhost:3306/test
log4j.appender.sql.User = root
log4j.appender.sql.Password = root
log4j.appender.sql.Sql = INSERT INTO log(project_name,create_date,level,category,file_name,thread_name,line,all_category,message) values('itcast','%d{yyyy-MM-ddHH:mm:ss}','%p','%c','%F','%t','%L','%l','%m')
CREATE TABLE `log` (
    `log_id` int(11) NOT NULL AUTO_INCREMENT,
    `project_name` varchar(255) DEFAULT NULL COMMENT '目项名',
    `create_date` varchar(255) DEFAULT NULL COMMENT '创建时间',
    `level` varchar(255) DEFAULT NULL COMMENT '优先级',
    `category` varchar(255) DEFAULT NULL COMMENT '所在类的全名',
    `file_name` varchar(255) DEFAULT NULL COMMENT '输出日志消息产生时所在的文件名称 ',
    `thread_name` varchar(255) DEFAULT NULL COMMENT '日志事件的线程名',
    `line` varchar(255) DEFAULT NULL COMMENT '号行',
    `all_category` varchar(255) DEFAULT NULL COMMENT '日志事件的发生位置',
    `message` varchar(4000) DEFAULT NULL COMMENT '输出代码中指定的消息',
    PRIMARY KEY (`log_id`)
);

2.8 自定义Logger

level会进行覆盖,appender会进行继承,也就是dailyRollingFileconsole会同时起作用。

该**包名**下面的全部类都是使用这个这种类型

log4j.rootLogger = trace,dailyRollingFile

# 自定义logger
log4j.logger.cn.quguai = info,console
log4j.rootLogger = trace,dailyRollingFile

log4j.logger.cn.quguai = info,console

log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.conversionPattern = %r [%5t] %p %l - %m%n

log4j.appender.file = org.apache.log4j.FileAppender
log4j.appender.file.layout = org.apache.log4j.PatternLayout
log4j.appender.file.layout.conversionPattern = %r [%5t] %p %l - %m%n
log4j.appender.file.file = ./log4j.log
log4j.appender.file.encoding = UTF-8

log4j.appender.rollingFile = org.apache.log4j.RollingFileAppender
log4j.appender.rollingFile.layout = org.apache.log4j.PatternLayout
log4j.appender.rollingFile.layout.conversionPattern = %r [%5t] %p %l - %m%n
log4j.appender.rollingFile.file = ./log4j.log
log4j.appender.rollingFile.encoding = UTF-8
log4j.appender.rollingFile.maxFileSize = 1KB
log4j.appender.rollingFile.maxBackupIndex = 5

log4j.appender.dailyRollingFile = org.apache.log4j.DailyRollingFileAppender
log4j.appender.dailyRollingFile.layout = org.apache.log4j.PatternLayout
log4j.appender.dailyRollingFile.layout.conversionPattern = %r [%5t] %p %l - %m%n
log4j.appender.dailyRollingFile.file = ./log4j.log
log4j.appender.dailyRollingFile.encoding = UTF-8
log4j.appender.dailyRollingFile.datePattern = '.'yyyy-MM-dd-HH-mm-ss

log4j.appender.sql = org.apache.log4j.jdbc.JDBCAppender
log4j.appender.sql.layout = org.apache.log4j.PatternLayout
log4j.appender.sql.Driver = com.mysql.jdbc.Driver
log4j.appender.sql.URL = jdbc:mysql://localhost:3306/test
log4j.appender.sql.User = root
log4j.appender.sql.Password = root
log4j.appender.sql.Sql = INSERT INTO log(project_name,create_date,level,category,file_name,thread_name,line,all_category,message) values('itcast','%d{yyyy-MM-ddHH:mm:ss}','%p','%c','%F','%t','%L','%l','%m')

3. JCL日志门面

image-20211222203416866

3.1 JCL入门

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

默认使用JUL作为日志底层实现,当导入log4j的包的时候自动进行转换。

@Test
public void testQuick(){
    Log log = LogFactory.getLog(JCLTest.class);
    BasicConfigurator.configure();
    log.fatal("s");
    log.error("error");
    log.warn("warning");
    log.info("info");
    log.debug("debug");
    log.trace("trace");
}

3.2 JCL原理

Log

private static final String[] classesToDiscover = new String[{
	"org.apache.commons.logging.impl.Log4JLogger", 
	"org.apache.commons.logging.impl.Jdk14Logger", 
    "org.apache.commons.logging.impl.Jdk13LumberjackLogger", 
    "org.apache.commons.logging.impl.SimpleLog"
};

流程如下:

org.apache.commons.logging.LogFactory#getLog(java.lang.Class)
org.apache.commons.logging.impl.LogFactoryImpl#getInstance(java.lang.String)
org.apache.commons.logging.impl.LogFactoryImpl#newInstance
org.apache.commons.logging.impl.LogFactoryImpl#discoverLogImplementation

for(int i = 0; i < classesToDiscover.length && result == null; ++i) {
    result = this.createLogFromClass(classesToDiscover[i], logCategory, true);
}

4. Slf4J 技术

官网文档 | 官方地址

4.1 Slf4j 入门

使用 slf4j 内置的简单的实现

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.26</version>
</dependency>
<!--slf4j简单日志实现-->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-simple</artifactId>
    <version>1.7.26</version>
</dependency>

slf4j 相比于JCL 只提供了5个日志级别,去除了fatal

public class Slf4jTest {
    private static final Logger logger = LoggerFactory.getLogger(Slf4jTest.class);
    
    @Test
    public void testQuick(){
        logger.error("error");
        logger.warn("warning");
        logger.info("info");  // slf4j-simple 默认日志级别 简单的日志框架的都是这个
        logger.debug("debug"); // 复杂的日志框架都是这个,logback log4j
        logger.trace("trace");
    }
}

输出形式

logger.info("用户:{}{}", "李扬", 1);
try {
    int i = 10 / 0;
} catch (Exception e) {
    logger.error("出现异常", e);
}

4.2 日志绑定

本质上就是依靠slf4j进行日志门面的同意管理

  • 蓝色部分代表默认遵循了slf4j的规范可以直接进行使用
  • 青色部分代表了适配层,需要引入桥接进行使用

  • slf4j-nop 代表了日志的开关,如果引入这个包,代表日志不在进行使用,全部禁用;
  • 适配层的包,默认会将上下两层的包全部导入,例如slf4j-log412.jar,默认依赖了slf4j和log4j;
  • 直接实现的包,默认也直接依赖了slf4j;

推荐直接引入处于第三层的包(红色边框)就可以完全导入所有依赖。

绑定流程

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

4.3 绑定原理流程

org.slf4j.LoggerFactory#getLogger(java.lang.String)

org.slf4j.LoggerFactory#getILoggerFactory()

org.slf4j.LoggerFactory#performInitialization()

org.slf4j.LoggerFactory#bind()

org.slf4j.LoggerFactory#findPossibleStaticLoggerBinderPathSet() 其中的STATIC_LOGGER_BINDER_PATH="org/slf4j/impl/StaticLoggerBinder.class",只要实现这个方法就可以完成绑定操作。

4.4 桥接原理

桥接和绑定不同,绑定适用于前期的选型问题,确定下来就已经定死了,桥接适用于一些老代码的日志切换为Slf4j进行管理。不会改变原有的代码格式。桥接解决的是项目中日志的遗留问题,当系统中存在之前的日志API,可以通过桥接转换到slf4j的实现。

  1. 先去除之前老的日志框架的依赖
  2. 添加SLF4J提供的桥接组件
  3. 为项目添加SLF4J的具体实现

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>log4j-over-slf4j</artifactId>
    <version>1.7.26</version>
</dependency>
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.8</version>
</dependency>

注意问题:

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

5. logback

官网文档 | 中文文档

logback-classic 默认导入了 slf4j 以及 logback-core(都是一个人的嘛)

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

默认日志级别debug (复杂日志框架(log4j logback)都是debug,简单实现日志框架(jul slf4j-sample)都是info)

image-20211223154125775

@Test
public void testQuick() {
    logger.error("error");
    logger.warn("warn");
    logger.info("info");
    logger.debug("debug");  // 默认级别
    logger.trace("trace");
}

5.1 配置文件

可以通过 PropertiesTranslator 将log4j.properties 转化为xml形式

logback会依次读取以下类型配置文件:

  • logback.groovy
  • logback-test.xml
  • logback.xml
  • 如果均不存在会采用默认配置(BasicConfigurator,直接输出到控制台上)

logback组件之间的关系

  • Logger: 日志的记录器,把它关联到应用的对应的context上后,主要用于存放日志对象,也 可以定义日志类型、级别;
  • Appender: 用于指定日志输出的目的地,目的地可以是控制台、文件、数据库等等;
  • Layout: 负责把事件转换成字符串,格式化的日志信息的输出。在logback中Layout对象被封 装在encoder中。

5.1.1 配置文件详情

大写的简化形式都不推荐,影响性能问题

格式化含义备注
%-5level = %p = %le日志级别
%d日期
%c = %logger = %lo类的完整名称后面数字代表简写的形式不代表长度
%thread= %t线程名称
%r = %relative程序启动到创建日志记录的时间单位是毫秒
%m = %msg信息
%n换行
%C = %class全限定类名避免使用
%Mmethod避免使用
%L行号避免使用
<?xml version="1.0" encoding="UTF-8"?>

<configuration>
    
    <!--配置集中管理属性-->
    <property name="pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %c{36} - %msg%n"/>
    
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <!--控制输出流对象,默认为System.out 变为红色字体-->
        <target>System.err</target>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${pattern}</pattern>
        </encoder>
    </appender>

    <root level="ALL">
        <appender-ref ref="console"/>
    </root>
</configuration>

5.1.2 文件输出

一般封装好的类都处于classic包下面,没有封装好的,进行二次开发的都处于core包下面

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

<configuration>
    <property name="pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %c{36} - %msg%n"/>
    <property name="log_dir" value="./logs"/>

    <appender name="file" class="ch.qos.logback.core.FileAppender">
        <file>${log_dir}/logback.log</file>
        <append>true</append>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${pattern}</pattern>
        </encoder>
    </appender>

    <root level="ALL">
        <appender-ref ref="file"/>
    </root>
</configuration>

5.1.3 自定义html输出

一般封装好的类都处于classic包下面,没有封装好的,进行二次开发的都处于core包下面

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

<configuration>
    <!--配置集中管理属性-->
    <property name="pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %c{36} - %msg%n"/>
    <property name="log_dir" value="./logs"/>
    
    <appender name="file" class="ch.qos.logback.core.FileAppender">
        <file>${log_dir}/logback.log</file>
        <append>true</append>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${pattern}</pattern>
        </encoder>
    </appender>

    <appender name="html" class="ch.qos.logback.core.FileAppender">   <==classic包下面
        <file>${log_dir}/logback.html</file>
        <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">   <==core包下面
            <layout class="ch.qos.logback.classic.html.HTMLLayout">        <==classic包下面
                <pattern>%d{HH:mm:ss.SSS} %thread %-5level %c{36} - %msg%n</pattern>
            </layout>
        </encoder>
    </appender>

    <root level="ALL">
        <appender-ref ref="file"/>
        <appender-ref ref="html"/>
    </root>
</configuration>

5.1.4 日志拆分和归档压缩

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

<configuration>
    <property name="pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %c{36} - %msg%n"/>
    <property name="log_dir" value="./logs"/>

    <!--日志拆分和归档压缩-->
    <appender name="rollFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${log_dir}/roll_logback.log</file>
        <append>true</append>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${pattern}</pattern>
        </encoder>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!--按照文件大小进行拆分-->
            <maxFileSize>1mb</maxFileSize>
            <!--按照时间进行拆分和归档压缩-->
            <fileNamePattern>${log_dir}/roll_logback.%d{yyyy-MM-dd-HH-mm-ss}.log%i.zip</fileNamePattern>
        </rollingPolicy>
    </appender>

    <root level="ALL">
        <appender-ref ref="rollFile"/>
    </root>
</configuration>

5.1.5 日志过滤器

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

<configuration>
    <property name="pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %c{36} - %msg%n"/>
    <property name="log_dir" value="./logs"/>

    <appender name="rollFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${log_dir}/roll_logback.log</file>
        <append>true</append>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${pattern}</pattern>
        </encoder>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <maxFileSize>1mb</maxFileSize>
            <fileNamePattern>${log_dir}/roll_logback.%d{yyyy-MM-dd-HH-mm-ss}.log%i.zip</fileNamePattern>
        </rollingPolicy>
        <!--过滤器-->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">   <== 过滤器
            <level>ERROR</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <root level="ALL">
        <appender-ref ref="rollFile"/>
    </root>
</configuration>

5.1.6 异步日志记录

异步日志借助其他的appender完成异步任务,底层使用阻塞队列完成

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

<configuration>
    <property name="pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %c{36} - %msg%n"/>
    <property name="log_dir" value="./logs"/>

    <appender name="rollFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${log_dir}/roll_logback.log</file>
        <append>true</append>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${pattern}</pattern>
        </encoder>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <maxFileSize>1mb</maxFileSize>
            <fileNamePattern>${log_dir}/roll_logback.%d{yyyy-MM-dd-HH-mm-ss}.log%i.zip</fileNamePattern>
        </rollingPolicy>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>
    <!--异步日志记录-->
    <appender name="async" class="ch.qos.logback.classic.AsyncAppender">
        <appender-ref ref="rollFile"/>  <== 借助rollFile完成异步任务
    </appender>

    <root level="ALL">
        <appender-ref ref="async"/>
    </root>
</configuration>

5.1.7 自定义Logger

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

<configuration>
    <property name="pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %c{36} - %msg%n"/>
    <property name="log_dir" value="./logs"/>

    <appender name="rollFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${log_dir}/roll_logback.log</file>
        <append>true</append>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${pattern}</pattern>
        </encoder>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <maxFileSize>1mb</maxFileSize>
            <fileNamePattern>${log_dir}/roll_logback.%d{yyyy-MM-dd-HH-mm-ss}.log%i.zip</fileNamePattern>
        </rollingPolicy>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <root level="ALL">
        <appender-ref ref="rollFile"/>
    </root>
     
     <!--自定义logger additivity=false不存在父子继承关系-->
    <logger name="cn.quguai" level="info" additivity="false">
        <appender-ref ref="console"/>
    </logger>
</configuration>

**注意:**即使会输出root级别的文件,但是大小均为0kb。

5.2 logback-access的使用

logback-access模块与Servlet容器(如Tomcat和Jetty)集成,以提供HTTP访问日志功能。我们可以使 用logback-access模块来替换tomcat的访问日志。

  1. 将logback-access.jar与logback-core.jar复制到$TOMCAT_HOME/lib/目录下

  2. 修改$TOMCAT_HOME/conf/server.xml中的Host元素中添加:

    <Valve className="ch.qos.logback.access.tomcat.LogbackValve" />
    
  3. logback默认会在$TOMCAT_HOME/conf下查找文件 logback-access.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <configuration>
    <!-- always a good activate OnConsoleStatusListener -->
    	<statusListener class="ch.qos.logback.core.status.OnConsoleStatusListener"/>
    	<property name="LOG_DIR" value="${catalina.base}/logs"/>
    	<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
    		<file>${LOG_DIR}/access.log</file>
    		<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
    			<fileNamePattern>access.%d{yyyy-MM-dd}.log.zip</fileNamePattern>
    		</rollingPolicy>
        	<encoder>
    		<!-- 访问日志的格式 -->
    			<pattern>combined</pattern>  <== logback集成的格式信息可以参考下方链接
    		</encoder>
    	</appender>
    	<appender-ref ref="FILE"/>
    </configuration>
    
  4. 官方配置: https://logback.qos.ch/access.html#configuration

6. Log4j2

官方文档 | 配置文件

6.1 快速入门

<!--日志门面-->
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-api</artifactId>
    <version>2.11.1</version>
</dependency>
<!--日志实现-->
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.11.1</version>
</dependency>
public class Log4j2Test {
    private static final Logger logger = LogManager.getLogger(Log4j2Test.class);

    @Test
    public void testQuick(){
        logger.fatal("fatal");
        logger.error("error"); // 默认级别
        logger.warn("warn");
        logger.info("info");
        logger.debug("debug");
        logger.trace("trace");
    }
}

实际开发当中依然使用slf4jlogback的组合方式进行

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

6.2 配置文件

配置文件参数上和log4j形式上十分相同

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn" monitorInterval="5">
    <properties>
        <property name="LOG_HOME">./logs</property>
    </properties>
    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] [%-5level] %c{36}:%L --- %m%n"/>
        </Console>
        <File name="file" fileName="${LOG_HOME}/myfile.log">
            <PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %l%c{36} - %m%n"/>
        </File>
        <RandomAccessFile name="accessFile" fileName="${LOG_HOME}/myAcclog.log">
            <PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %l%c{36} - %m%n"/>
        </RandomAccessFile>
        <RollingFile name="rollingFile" fileName="${LOG_HOME}/myrollog.log"
                     filePattern="D:/logs/$${date:yyyy-MM-dd}/myrollog-%d{yyyyMM-dd-HH-mm}-%i.log">
            <ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY"/>
            <PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %l%c{36} - %msg%n"/>
            <Policies>
                <!--系统启动时,会自动进行触发,生成一个新的日志文件-->
                <OnStartupTriggeringPolicy/>
                <SizeBasedTriggeringPolicy size="10 MB"/>
                <TimeBasedTriggeringPolicy/>
            </Policies>
            <!--同一个文件夹下,最大的文件个数-->
            <DefaultRolloverStrategy max="30"/>
        </RollingFile>
    </Appenders>
    <Loggers>
        <!--默认使用rootLogger-->
        <Root level="trace">
            <AppenderRef ref="Console"/>
        </Root>
    </Loggers>
</Configuration>

6.3 异步日志(额外依赖)

image-20211223193012334

  • 异步日志形式

image-20211223193037614

Log4j2提供了两种实现日志的方式,一个是通过AsyncAppender,一个是通过AsyncLogger,分别对应 前面我们说的Appender组件和Logger组件。
注意:配置异步日志需要添加依赖

<dependency>
    <groupId>com.lmax</groupId>
    <artifactId>disruptor</artifactId>
    <version>3.3.4</version>
</dependency>

6.3.1 AsyncAppender方式

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn" monitorInterval="5">
    <properties>
        <property name="LOG_HOME">./logs</property>
    </properties>
    <Appenders>
        <File name="file" fileName="${LOG_HOME}/myfile.log">
            <PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %l%c{36} - %m%n"/>
        </File>
        <Async name="Async">  <==异步形式
            <AppenderRef ref="file"/>
        </Async>
    </Appenders>
    <Loggers>
        <!--默认使用rootLogger-->
        <Root level="error">
            <AppenderRef ref="Async"/> <==异步形式
        </Root>
    </Loggers>
</Configuration>

6.3.2 AsyncLogger方式

AsyncLogger才是log4j2 的重头戏,也是官方推荐的异步方式。它可以使得调用Logger.log返回的 更快。你可以有两种选择:全局异步和混合异步。

  • 全局异步就是,所有的日志都异步的记录,在配置文件上不用做任何改动,只需要添加一个 log4j2.component.properties 配置;

    Log4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector
    
  • 混合异步就是,你可以在应用中同时使用同步日志和异步日志,这使得日志的配置方式更加灵活。

    <?xml version="1.0" encoding="UTF-8"?>
    <Configuration status="warn" monitorInterval="5">
        <properties>
            <property name="LOG_HOME">./logs</property>
        </properties>
        <Appenders>
            <Console name="Console" target="SYSTEM_OUT">
                <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] [%-5level] %c{36}:%L --- %m%n"/>
            </Console>
            <File name="file" fileName="${LOG_HOME}/myfile.log">
                <PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %l%c{36} - %m%n"/>
            </File>
        </Appenders>
        <Loggers>
            <!--默认使用rootLogger-->
            <Root level="error">
                <AppenderRef ref="Console"/>
            </Root>
    
            <!--自定义异步Logger对象 includeLocation:关闭日志行号信息 addivitity:是否进行继承-->
            <AsyncLogger name="cn.quguai" level="info" includeLocation="false" addivitity="false">
                <AppenderRef ref="file"/>
            </AsyncLogger>
        </Loggers>
    </Configuration>
    

使用异步日志需要注意的问题:

  1. 如果使用异步日志,AsyncAppenderAsyncLogger全局日志,不要同时出现。性能会和 AsyncAppender一致,降至最低。
  2. 设置includeLocation=false ,打印位置信息会急剧降低异步日志的性能,比同步日志还要慢。

7. SpringBoot 日志配置

默认使用slf4j + logback 进行实现

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

7.1 快速入门

直接使用spring配置

logging.level.cn.quguai = debug

logging.pattern.console=[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%p] %c -%m%n
logging.pattern.file=[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%p] %c -%m%n

# 两者只能留下一个path默认文件名是spring.log
#logging.file.name=./logs/springboot.log
logging.file.path=./logs 

logging.logback.rollingpolicy.file-name-pattern=${logging.file.path}/roll_logback.%d{yyyy-MM-dd-HH-mm-ss}.log%i.zip
logging.logback.rollingpolicy.max-file-size=1KB
logging.logback.rollingpolicy.max-history=30
@SpringBootTest
class SpringBootLogApplicationTests {

    private static final Logger logger = LoggerFactory.getLogger(SpringBootLogApplicationTests.class);

    @Test
    void contextLoads() {
        logger.info("info");  // 默认级别
        logger.debug("debug");

        // 即使桥接器的形式使用log4j2的门面输出,默认还是会转为slf4j+logback
        org.apache.logging.log4j.Logger logger = LogManager.getLogger(SpringBootLogApplicationTests.class);
        logger.info("info log4j2");
        logger.debug("debug log4j2");
    }
}

7.2 指定配置

给类路径下放上每个日志框架自己的配置文件;SpringBoot就不使用默认配置的了

日志框架配置文件
Logbacklogback-spring.xml, logback.xml
Log4j2log4j2-spring.xml , log4j2.xml
JULlogging.properties

7.3 使用SpringBoot解析日志配置

logback-spring.xml:由SpringBoot解析日志配置

<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
    <springProfile name="default">
        <pattern>${pattern}</pattern>
    </springProfile>
    <springProfile name="test">
        <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %c{36} ==== %msg%n</pattern>
    </springProfile>
</encoder>
spring.profiles.active=test

7.4 日志切换到log4j2

image-20211223210425792

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>   <== 排除logging依赖:logback依赖
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>  <== 引入log4j2的依赖,底层依然使用slf4j
    <artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>

image-20211223211107925

7. 配置文件汇总

7.1 JUL配置文件

handlers= java.util.logging.ConsoleHandler,java.util.logging.FileHandler
.level= ALL

# Self-Logger
cn.quguai.handlers = java.util.logging.FileHandler
cn.quguai.level = CONFIG
cn.quguai.useParentHandlers=false

java.util.logging.FileHandler.pattern = ./java%u.log
java.util.logging.FileHandler.limit = 50000
java.util.logging.FileHandler.count = 1

java.util.logging.FileHandler.maxLocks = 100
java.util.logging.FileHandler.append = true
java.util.logging.FileHandler.formatter = java.util.logging.XMLFormatter

java.util.logging.ConsoleHandler.level = ALL
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
java.util.logging.ConsoleHandler.encoding = UTF-8
java.util.logging.SimpleFormatter.format = %4$s: %5$s [%1$tc]%n

7.2 Log4j 配置文件

log4j.rootLogger = trace,dailyRollingFile

# ???logger
log4j.logger.cn.quguai = info,console

# ??appender
log4j.appender.console = org.apache.log4j.ConsoleAppender
# ??layout
log4j.appender.console.layout = org.apache.log4j.PatternLayout
# ???????
log4j.appender.console.layout.conversionPattern = %r [%5t] %p %l - %m%n

log4j.appender.file = org.apache.log4j.FileAppender
log4j.appender.file.layout = org.apache.log4j.PatternLayout
log4j.appender.file.layout.conversionPattern = %r [%5t] %p %l - %m%n
log4j.appender.file.file = ./log4j.log
log4j.appender.file.encoding = UTF-8

# ???????????
log4j.appender.rollingFile = org.apache.log4j.RollingFileAppender
log4j.appender.rollingFile.layout = org.apache.log4j.PatternLayout
log4j.appender.rollingFile.layout.conversionPattern = %r [%5t] %p %l - %m%n
log4j.appender.rollingFile.file = ./log4j.log
log4j.appender.rollingFile.encoding = UTF-8
# ???????????
log4j.appender.rollingFile.maxFileSize = 1KB
# ?????????
log4j.appender.rollingFile.maxBackupIndex = 5

# ???????????
log4j.appender.dailyRollingFile = org.apache.log4j.DailyRollingFileAppender
log4j.appender.dailyRollingFile.layout = org.apache.log4j.PatternLayout
log4j.appender.dailyRollingFile.layout.conversionPattern = %r [%5t] %p %l - %m%n
log4j.appender.dailyRollingFile.file = ./log4j.log
log4j.appender.dailyRollingFile.encoding = UTF-8
# ????????
log4j.appender.dailyRollingFile.datePattern = '.'yyyy-MM-dd-HH-mm-ss

# ?????????????
log4j.appender.sql = org.apache.log4j.jdbc.JDBCAppender
log4j.appender.sql.layout = org.apache.log4j.PatternLayout
log4j.appender.sql.Driver = com.mysql.jdbc.Driver
log4j.appender.sql.URL = jdbc:mysql://localhost:3306/test
log4j.appender.sql.User = root
log4j.appender.sql.Password = root
log4j.appender.sql.Sql = INSERT INTO log(project_name,create_date,level,category,file_name,thread_name,line,all_category,message) values('itcast','%d{yyyy-MM-ddHH:mm:ss}','%p','%c','%F','%t','%L','%l','%m')

7.3 Logback 配置文件

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

<configuration>
    <!--配置集中管理属性-->
    <!-- 日志输出格式:
        %-5level = %p = %le
        %d{yyyy-MM-dd HH:mm:ss.SSS}日期
        %c{length}类的完整名称 = %logger = %lo :后面数字代表简写的形式不代表长度
        %C = %class :避免使用
        %M为method   :避免使用
        %L为行号     :避免使用
        %thread线程名称 = %t
        %m或者%msg为信息
        %n换行 -->
    <property name="pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %c{36} - %msg%n"/>
    <property name="log_dir" value="./logs"/>

    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <!--控制输出流对象,默认为System.out 变为红色字体-->
        <target>System.err</target>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${pattern}</pattern>
        </encoder>
    </appender>

    <!--文件输出-->
    <appender name="file" class="ch.qos.logback.core.FileAppender">
        <file>${log_dir}/logback.log</file>
        <append>true</append>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${pattern}</pattern>
        </encoder>
    </appender>

    <!--html 格式输出-->
    <appender name="html" class="ch.qos.logback.core.FileAppender">
        <file>${log_dir}/logback.html</file>
        <!--自定义 encoder -->
        <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
            <layout class="ch.qos.logback.classic.html.HTMLLayout">
                <pattern>%d{HH:mm:ss.SSS} %thread %-5level %c{36} - %msg%n</pattern>
            </layout>
        </encoder>
    </appender>

    <!--日志拆分和归档压缩-->
    <appender name="rollFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${log_dir}/roll_logback.log</file>
        <append>true</append>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${pattern}</pattern>
        </encoder>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!--按照文件大小进行拆分-->
            <maxFileSize>1mb</maxFileSize>
            <!--按照时间进行拆分和归档压缩-->
            <fileNamePattern>${log_dir}/roll_logback.%d{yyyy-MM-dd-HH-mm-ss}.log%i.zip</fileNamePattern>
            <!--只保留最近30天的-->
            <maxHistory>30</maxHistory>
        </rollingPolicy>
        <!--过滤器-->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>
    <!--异步日志记录-->
    <appender name="async" class="ch.qos.logback.classic.AsyncAppender">
        <appender-ref ref="rollFile"/>
    </appender>

    <root level="ALL">
        <appender-ref ref="console"/>
        <appender-ref ref="async"/>
        <appender-ref ref="html"/>
    </root>

    <!--自定义logger additivity是否存在父子继承关系-->
    <logger name="cn.quguai" level="info" additivity="false">
        <appender-ref ref="console"/>
    </logger>
</configuration>

7.4 Log4j2 配置文件

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn" monitorInterval="5">
    <properties>
        <property name="LOG_HOME">./logs</property>
    </properties>
    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] [%-5level] %c{36}:%L --- %m%n"/>
        </Console>
        <File name="file" fileName="${LOG_HOME}/myfile.log">
            <PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %l%c{36} - %m%n"/>
        </File>
        <RandomAccessFile name="accessFile" fileName="${LOG_HOME}/myAcclog.log">
            <PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %l%c{36} - %m%n"/>
        </RandomAccessFile>
        <RollingFile name="rollingFile" fileName="${LOG_HOME}/myrollog.log"
                     filePattern="D:/logs/$${date:yyyy-MM-dd}/myrollog-%d{yyyyMM-dd-HH-mm}-%i.log">
            <ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY"/>
            <PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %l%c{36} - %msg%n"/>
            <Policies>
                <!--系统启动时,会自动进行触发,生成一个新的日志文件-->
                <OnStartupTriggeringPolicy/>
                <SizeBasedTriggeringPolicy size="10 MB"/>
                <TimeBasedTriggeringPolicy/>
            </Policies>
            <!--同一个文件夹下,最大的文件个数-->
            <DefaultRolloverStrategy max="30"/>
        </RollingFile>
        <!--使用异步Appender 等同于Logback-->
        <!--<Async name="Async">
            <AppenderRef ref="file"/>
        </Async>-->
    </Appenders>
    <Loggers>
        <!--默认使用rootLogger-->
        <Root level="error">
            <AppenderRef ref="Console"/>
        </Root>

        <!--自定义异步Logger对象 includeLocation:关闭日志行号信息 addivitity:是否进行继承-->
        <AsyncLogger name="cn.quguai" level="info" includeLocation="false" addivitity="false">
            <AppenderRef ref="file"/>
        </AsyncLogger>
    </Loggers>
</Configuration>

使用异步日志需要注意的问题:

  1. 如果使用异步日志,AsyncAppenderAsyncLogger全局日志,不要同时出现。性能会和 AsyncAppender一致,降至最低。
  2. 设置includeLocation=false ,打印位置信息会急剧降低异步日志的性能,比同步日志还要慢。

Copyright: 采用 知识共享署名4.0 国际许可协议进行许可

Links: https://quguai.cn/archives/javari-zhi-kuang-jia-xiang-jie