轩辕李的博客 轩辕李的博客
首页
  • Java
  • Spring
  • 其他语言
  • 工具
  • HTML&CSS
  • JavaScript
  • 分布式
  • 代码质量管理
  • 基础
  • 操作系统
  • 计算机网络
  • 编程范式
  • 安全
  • 中间件
  • 心得
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

轩辕李

勇猛精进,星辰大海
首页
  • Java
  • Spring
  • 其他语言
  • 工具
  • HTML&CSS
  • JavaScript
  • 分布式
  • 代码质量管理
  • 基础
  • 操作系统
  • 计算机网络
  • 编程范式
  • 安全
  • 中间件
  • 心得
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • Java

    • 核心

    • 并发

    • 经验

    • JVM

    • 企业应用

      • Freemarker模板
      • Servlet与JSP指南
      • Java日志系统
        • 引言
          • 1. 日志系统的重要性
          • 2. 日志系统的基本功能
        • Java内置日志工具:Java Util Logging(JUL)
          • 1. 日志记录器
          • 2. 日志级别
          • 3. 日志处理器
          • 4. 日志格式化器
          • 5. 日志管理器
          • 6. JUL的优点和缺点
        • Apache Common Logging(JCL)
        • 简单日志门面:SLF4J
          • 1. SLF4J的概念和基本架构
          • 2. SLF4J的主要特性
          • 3. SLF4J的配置和使用
          • 4. SLF4J的特点
        • Apache Log4j
        • Logback
          • 1. 不同的Appender与日志滚动
          • 2. 滚动策略
          • 3. MDC
        • Apache Log4j2
        • 主流日志库的性能对比
        • 桥接与适配
        • 总结
      • Java JSON处理
      • Java XML处理
      • Java对象池技术
      • Java程序的单元测试(Junit)
      • Thymeleaf官方文档(中文版)
      • Mockito应用
      • Java中的空安全
  • Spring

  • 其他语言

  • 工具

  • 后端
  • Java
  • 企业应用
轩辕李
2023-05-28
目录

Java日志系统

# 引言

# 1. 日志系统的重要性

日志系统是软件开发中至关重要的组成部分之一。

它记录了软件运行时的关键信息,包括错误、警告、调试信息和其他有用的运行时数据。

日志系统可以帮助开发人员在软件开发、测试和维护过程中进行故障排除和问题诊断,提供关键的上下文信息,从而加快问题解决的速度。

通过日志系统,开发人员可以跟踪软件的运行情况,监控系统的性能,了解软件在不同环境下的行为,以及收集用户的反馈和行为数据。

此外,日志系统还可以用于合规性要求,例如记录用户操作日志和安全审计。

# 2. 日志系统的基本功能

日志系统通常提供以下基本功能:

  1. 记录日志:将关键信息记录到日志文件或其他持久化存储介质中,包括错误信息、警告、调试信息等。
  2. 日志级别控制:通过设置日志级别,可以控制记录哪些级别的日志信息。常见的日志级别包括:错误(error)、警告(warn)、信息(info)、调试(debug)和跟踪(trace)等。
  3. 日志格式化:将记录的日志信息按照一定的格式进行展示,包括时间戳、日志级别、线程信息、类名和方法名等。
  4. 日志过滤:根据指定的条件过滤日志信息,以便在大量日志中筛选出特定的记录。
  5. 日志输出:将日志信息输出到不同的目标,如控制台、文件、数据库等。
  6. 日志滚动:自动管理日志文件的大小和数量,避免日志文件过大或过多导致的存储问题。
  7. 日志归档:将旧的日志文件进行归档,以便长期存储和检索。

# Java内置日志工具:Java Util Logging(JUL)

Java Util Logging(JUL)是Java平台自带的日志工具,在1.4版本中引入,作为Java的默认日志系统,无需引入额外的依赖。JUL基于一套简单的日志API,提供了基本的日志记录功能。

# 1. 日志记录器

java.util.logging.Logger是Java日志记录API中用于记录应用程序消息的类。

我们可以使用非常简单的一行代码创建Java Logger,如下所示:

Logger logger = Logger.getLogger(MyClass.class.getName());

# 2. 日志级别

java.util.logging.Level定义了Java日志的不同级别。

Java中有七个日志级别:

  1. SEVERE(最高级别):SEVERE是最高级别的日志级别。它表示严重的错误事件,表示应用程序遇到了一个严重的问题或故障,可能导致应用程序无法继续正常运行。SEVERE级别的日志通常用于记录致命错误、系统崩溃或无法恢复的异常情况。
  2. WARNING:WARNING级别用于记录可能导致潜在问题或错误的警告信息。它表示应用程序遇到了一些不寻常的情况或可能导致问题的情况,但不会影响应用程序的正常运行。警告级别的日志通常用于记录潜在的风险或需要关注的事件。
  3. INFO:INFO级别用于记录应用程序的一般信息。它表示应用程序的正常运行状态,用于记录重要的操作、关键事件或关键数据的信息。INFO级别的日志通常用于跟踪应用程序的正常执行流程,以及提供有关应用程序运行状态的相关信息。
  4. CONFIG:CONFIG级别用于记录应用程序的配置信息。它表示应用程序的配置细节、设置参数或其他与应用程序配置相关的信息。CONFIG级别的日志通常用于记录应用程序的配置更改、配置加载或其他与应用程序配置相关的事件。
  5. FINE:FINE级别是一个相对较低的详细级别,用于记录更细微的调试信息。它表示应用程序的详细操作、方法调用或其他详细信息的记录。FINE级别的日志通常用于调试和排查问题,提供更详细的日志输出。
  6. FINER:FINER级别比FINE更详细,用于记录更详细的调试信息。它表示比FINE级别更细粒度的操作、方法调用或其他详细信息的记录。FINER级别的日志通常用于需要更详细的跟踪和排查问题的情况。
  7. FINEST:FINEST级别是最详细的调试级别。它表示非常详细的操作、方法调用或其他详细信息的记录。FINEST级别的日志通常用于需要非常详细的跟踪和排查问题的情况。在生产环境中,通常不会使用FINEST级别的日志。

这些日志级别按照严重性递增,SEVERE级别最高,FINEST级别最低。

还有两个日志级别:OFF表示关闭所有日志记录,ALL表示记录所有消息。

我们可以使用以下代码设置日志记录器的级别:

logger.setLevel(Level.FINE);

日志将生成具有与日志记录器级别相等或高于该级别的所有日志。例如,如果日志记录器级别设置为INFO,则将为INFO、WARNING和SEVERE级别的日志消息生成日志。

# 3. 日志处理器

我们可以向Java日志记录器添加多个处理器,每个处理器将根据需求处理日志消息。Java日志记录API提供了两个默认处理器。

  • ConsoleHandler:此处理器将所有日志消息写入控制台。
  • FileHandler:此处理器将所有日志消息以XML格式写入文件。

我们也可以创建自定义处理器以执行特定任务。

要创建自定义处理器类,我们需要扩展java.util.logging.Handler类或其子类,例如StreamHandler、SocketHandler等。以下是自定义Java日志处理器的示例:

package com.journaldev.log;

import java.util.logging.LogRecord;
import java.util.logging.StreamHandler;

public class MyHandler extends StreamHandler {

    @Override
    public void publish(LogRecord record) {
        // 添加自定义逻辑以发布日志记录
        super.publish(record);
    }


    @Override
    public void flush() {
        super.flush();
    }


    @Override
    public void close() throws SecurityException {
        super.close();
    }

}

# 4. 日志格式化器

格式化器用于格式化日志消息。Java日志记录API提供了两种可用的格式化器。

  • SimpleFormatter:此格式化器生成带有基本信息的文本消息。ConsoleHandler使用此格式化器类将日志消息打印到控制台。
  • XMLFormatter:此格式化器为日志生成XML消息,FileHandler使用XMLFormatter作为默认格式化器。

我们可以通过扩展java.util.logging.Formatter类创建自定义的格式化器类,并将其附加到任何处理器上。以下是简单自定义格式化器类的示例。

package com.journaldev.log;

import java.util.Date;
import java.util.logging.Formatter;
import java.util.logging.LogRecord;

public class MyFormatter extends Formatter {

    @Override
    public String format(LogRecord record) {
        return record.getThreadID() + "::" + record.getSourceClassName() + "::"
                + record.getSourceMethodName() + "::"
                + new Date(record.getMillis()) + "::"
                + record.getMessage() + "\n";
    }

}

# 5. 日志管理器

java.util.logging.LogManager是读取日志配置、创建和维护日志记录器实例的类。我们可以使用此类设置我们自己的应用程序特定配置。

LogManager.getLogManager().readConfiguration(new FileInputStream("mylogging.properties"));

以下是Java日志记录API配置文件的示例。如果我们没有指定任何配置,它将从JRE Home lib/logging.properties文件中读取。

mylogging.properties

handlers= java.util.logging.ConsoleHandler

.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

# 将打印在控制台上的消息限制为INFO及以上级别。
java.util.logging.ConsoleHandler.level = INFO
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter

com.journaldev.files = SEVERE

以下是一个显示Java中Logger用法的简单Java程序。

package com.journaldev.log;

import java.io.FileInputStream;
import java.io.IOException;
import java.util.logging.ConsoleHandler;
import java.util.logging.FileHandler;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.Logger;

public class LoggingExample {

    static Logger logger = Logger.getLogger(LoggingExample.class.getName());

    public static void main(String[] args) {
        try {
            LogManager.getLogManager().readConfiguration(new FileInputStream("mylogging.properties"));
        } catch (SecurityException | IOException e1) {
            e1.printStackTrace();
        }
        logger.setLevel(Level.FINE);
        logger.addHandler(new ConsoleHandler());
        // 添加自定义处理器
        logger.addHandler(new MyHandler());
        try {
            // FileHandler文件名具有最大大小和日志文件限制数
            Handler fileHandler = new FileHandler("/Users/pankaj/tmp/logger.log", 2000, 5);
            fileHandler.setFormatter(new MyFormatter());
            // 为FileHandler设置自定义过滤器
            fileHandler.setFilter(new MyFilter());
            logger.addHandler(fileHandler);

            for (int i = 0; i < 1000; i++) {
                // 记录日志消息
                logger.log(Level.INFO, "Msg" + i);
            }
            logger.log(Level.CONFIG, "Config data");
        } catch (SecurityException | IOException e) {
            e.printStackTrace();
        }
    }

}

当您运行上述Java日志记录器示例程序时,您会注意到CONFIG日志不会打印到文件中,这是因为MyFilter类的设置。

package com.journaldev.log;

import java.util.logging.Filter;
import java.util.logging.Level;
import java.util.logging.LogRecord;

public class MyFilter implements Filter {

    @Override
    public boolean isLoggable(LogRecord log) {
        // 不记录CONFIG级别的日志到文件中
        if (log.getLevel() == Level.CONFIG)
            return false;
        return true;
    }

}

此外,输出格式将与MyFormatter类定义的格式相同。

1::com.journaldev.log.LoggingExample::main::Sat Dec 15 01:42:43 PST 2012::Msg977
1::com.journaldev.log.LoggingExample::main::Sat Dec 15 01:42:43 PST 2012::Msg978
1::com.journaldev.log.LoggingExample::main::Sat Dec 15 01:42:43 PST 2012::Msg979
1::com.journaldev.log.LoggingExample::main::Sat Dec 15 01:42:43 PST 2012::Msg980

如果我们没有将自定义的Formatter类添加到FileHandler中,日志消息将以以下方式打印。

<record>
  <date>2012-12-14T17:03:13</date>
  <millis>1355533393319</millis>
  <sequence>996</sequence>
  <logger>com.journaldev.log.LoggingExample</logger>
  <level>INFO</level>
  <class>com.journaldev.log.LoggingExample</class>
  <method>main</method>
  <thread>1</thread>
  <message>Msg996</message>
</record>

控制台日志消息的格式如下:

Dec 15, 2012 1:42:43 AM com.journaldev.log.LoggingExample main
INFO: Msg997
Dec 15, 2012 1:42:43 AM com.journaldev.log.LoggingExample main
INFO: Msg998
Dec 15, 2012 1:42:43 AM com.journaldev.log.LoggingExample main
INFO: Msg998

下图显示了最终的Java Logger示例项目:
image

您可以从链接下载该项目。

# 6. JUL的优点和缺点

JUL的优点包括:

  • 内置于Java平台,无需额外依赖。
  • 简单易用,适合快速开发和小规模项目。
  • 与Java平台紧密集成,无需额外学习成本。
  • 配置灵活,可以通过配置文件或代码进行配置。

然而,JUL也有一些缺点:

  • 功能相对有限,不支持一些高级特性,如异步日志写入、动态日志级别修改等。
  • 配置相对繁琐,需要通过配置文件或代码进行配置。
  • 缺乏一些常见日志系统的特性和扩展性。

由于JUL的限制和缺点,许多开发人员更倾向于使用第三方的日志系统,如Log4j、Logback,以满足更复杂的日志需求。这些日志系统提供了更多的功能和扩展性,并广泛应用于Java开发领域。

# Apache Common Logging(JCL)

当Apache Common Logging(JCL,原名是Jakarta Commons Logging)是一个Java日志框架,用于在Java应用程序中实现日志记录功能。

它提供了一个统一的日志接口,可以与不同的日志实现(如Log4j、java.util.logging)进行集成,从而实现在不同环境中灵活地切换和配置日志记录。

JCL的继任者是SLF4J(更多功能更强大),所以就不展开讲JCL了。

# 简单日志门面:SLF4J

# 1. SLF4J的概念和基本架构

Simple Logging Facade for Java(SLF4J)是一个简单的日志门面系统,旨在解决Java应用程序中使用不同日志系统的兼容性问题。

SLF4J提供了统一的API,使开发人员可以在应用程序中使用不同的日志实现而无需更改代码。

SLF4J的基本架构由以下几个核心组件组成:

  • Logger(日志记录器):Logger是SLF4J中最主要的组件,用于记录日志。每个Logger与一个特定的类关联,可以通过LoggerFactory.getLogger()方法获取Logger实例。Logger将日志记录请求委托给底层的日志实现。

  • Binding(绑定):Binding用于将SLF4J的API与底层的日志实现框架连接起来。开发人员需要使用特定的绑定(如slf4j-log4j12.jar)将SLF4J与所选的日志实现(如Log4j)绑定在一起。

# 2. SLF4J的主要特性

SLF4J具有以下主要特性:

  • 统一的API:SLF4J提供了一致的API,使开发人员可以在应用程序中使用统一的日志接口,而不依赖于特定的日志实现。

  • 适配性强:SLF4J与多个日志实现框架(如Log4j、Logback)兼容,并且可以无缝切换。

  • 轻量级:SLF4J本身非常轻量级,对应用程序的性能影响较小。

# 3. SLF4J的配置和使用

使用SLF4J的步骤如下:

  1. 引入SLF4J依赖:在项目的构建文件中引入SLF4J的依赖(如Maven或Gradle)。

  2. 选择日志实现:根据项目需求选择具体的日志实现,如Log4j或Logback。

  3. 配置日志实现:根据选择的日志实现,配置相应的日志实现框架(如log4j.properties或logback.xml)。

  4. 获取Logger实例:使用LoggerFactory.getLogger()方法获取Logger实例。可以传入一个标识符来命名Logger,通常使用类的全名作为标识符。

  5. 记录日志:使用Logger的不同方法,如error()、warn()、info()等记录相应级别的日志信息。

SLF4J定义了以下日志级别,按照从高到低的顺序:

  1. ERROR(错误):指示发生了严重错误,可能导致应用程序无法继续执行的情况。

  2. WARN(警告):指示潜在的问题或异常情况,可能会影响应用程序的正常行为。

  3. INFO(信息):指示应用程序运行的关键信息,如启动、停止、配置信息等。

  4. DEBUG(调试):指示详细的调试信息,用于定位问题、追踪代码执行流程等。

  5. TRACE(跟踪):指示更详细的调试信息,通常用于追踪代码中的细微变化或特定方法的调用。

在SLF4J中,使用不同的日志级别来记录相应级别及以上的日志信息。例如,如果Logger的日志级别设置为INFO,则会记录INFO、WARN和ERROR级别的日志,而DEBUG和TRACE级别的日志则不会被记录。

# 4. SLF4J的特点

SLF4J的特点包括:

  • 提供了统一的日志API,方便开发人员在应用程序中使用不同的日志实现。
  • 轻量级,对应用程序的性能影响较小。
  • 易于配置和使用,与多个日志实现框架兼容。

注意:SLF4J本身并不是一个日志实现框架,而是一个日志门面系统,需要与具体的日志实现框架(如Log4j2、Logback)配合使用。

# Apache Log4j

Log4j是Apache的一个开源项目,是一个功能强大的日志组件,可以灵活地进行日志配置,支持多种日志级别,支持多种输出目的地,支持自定义日志格式,支持多种配置方式,支持多种过滤器等。

Log4j可以作为JCL和SLF4J的实现,但本身性能较差,已经不再维护,推荐使用Logback和Log4j2。

# Logback

Logback是一个强大的日志框架,被认为是Log4j的继任者。它提供了高度可配置的日志功能,并具有出色的性能。

logback-classic是SLF4J的一个标准实现,可以轻松地记录和管理日志信息。

下面是一个简单的Logback使用示例:

  1. 引入依赖:在项目的构建文件中引入Logback的依赖(如Maven或Gradle)。示例中使用Maven:
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.3</version>
</dependency>
  1. 配置Logback:创建一个Logback的配置文件,通常命名为logback.xml,并放置在类路径下。示例配置文件如下:
<configuration>
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>
    
    <root level="INFO">
        <appender-ref ref="CONSOLE" />
    </root>
</configuration>

上述配置创建了一个名为CONSOLE的ConsoleAppender,定义了日志输出的格式。然后,将CONSOLE Appender添加到根Logger中,并设置日志级别为INFO。

  1. 在代码中使用Logback:在需要记录日志的类中,通过SLF4J获取Logger实例,并使用不同级别的方法记录日志。示例代码如下:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MyClass {
    private static final Logger logger = LoggerFactory.getLogger(MyClass.class);
    
    public void doSomething() {
        logger.info("Doing something...");
        logger.error("An error occurred!");
    }
}

在示例中,使用LoggerFactory.getLogger()方法获取Logger实例,并传入当前类的Class作为参数。然后,使用logger的info()和error()方法记录相应级别的日志信息。

# 1. 不同的Appender与日志滚动

<configuration>
    <!-- Console Appender -->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
        <filter class="ch.qos.logback.core.filter.LevelFilter">
            <level>WARN</level>
            <onMatch>DENY</onMatch>
        </filter>
    </appender>
    
    <!-- File Appender -->
    <appender name="FILE" class="ch.qos.logback.core.FileAppender">
        <file>logs/application.log</file>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>
    
    <!-- Rolling File Appender -->
    <appender name="ROLLING_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>logs/application.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>logs/application.%d{yyyy-MM-dd}.log</fileNamePattern>
        </rollingPolicy>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>
    
    <root level="INFO">
        <!-- Use both Console and File appenders -->
        <appender-ref ref="CONSOLE" />
        <appender-ref ref="FILE" />
    </root>
    
    <logger name="com.example" level="DEBUG">
        <!-- Use Rolling File appender for com.example package -->
        <appender-ref ref="ROLLING_FILE" />
    </logger>
</configuration>

在上述示例中,我们定义了三个不同的Appender:

  1. Console Appender:将日志输出到控制台,这里使用了过滤器来限制输出级别。

  2. File Appender:将日志输出到指定的文件(application.log)。

  3. Rolling File Appender:将日志输出到文件,并支持日志文件的滚动和归档。

在根Logger中,我们使用了<appender-ref>指令将Console和File appenders添加到根Logger中,以便同时输出日志到控制台和文件。

此外,我们还针对com.example包设置了一个专门的Logger,并将Rolling File appender添加到该Logger中。这意味着该Logger下的日志将单独输出到Rolling File appender指定的文件。

# 2. 滚动策略

下面是RollingFileAppender的几种用法示例:

1. 基于时间的滚动(TimeBasedRollingPolicy)示例:

<configuration>
    <appender name="ROLLING_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>/path/to/logs/app-%d{yyyy-MM-dd}.log</fileNamePattern>
            <maxHistory>30</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

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

上述示例中,我们使用TimeBasedRollingPolicy配置了RollingFileAppender。日志文件的命名模式为每天一个文件,使用%d{yyyy-MM-dd}表示日期部分,例如"app-2023-05-30.log"。<maxHistory>指定了保留的日志文件历史天数。

2. 基于文件大小的滚动(SizeBasedTriggeringPolicy)示例:

<configuration>
    <appender name="ROLLING_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
            <fileNamePattern>/path/to/logs/app.%i.log</fileNamePattern>
            <minIndex>1</minIndex>
            <maxIndex>10</maxIndex>
        </rollingPolicy>
        <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
            <maxFileSize>10MB</maxFileSize>
        </triggeringPolicy>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

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

上述示例中,我们使用SizeBasedTriggeringPolicy来触发日志文件的滚动。当日志文件的大小达到指定的大小(10MB)时,将滚动到下一个文件。FixedWindowRollingPolicy用于限制滚动的文件数量,保留10个日志文件。

3. 组合滚动策略(CompositeTriggeringPolicy)示例:

<configuration>
    <appender name="ROLLING_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>/path/to/logs/app-%d{yyyy-MM-dd}.log</fileNamePattern>
            <maxHistory>30</maxHistory>
        </rollingPolicy>
        <triggeringPolicy class="ch.qos.logback.core.rolling.CompositeTriggeringPolicy">
            <policies>
                <sizeBasedTriggeringPolicy>
                    <maxFileSize>10MB</maxFileSize>
                </sizeBasedTriggeringPolicy>
                <timeBasedTriggeringPolicy />
            </policies>
        </triggeringPolicy>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

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

上述示例中,我们使用CompositeTriggeringPolicy来组合多个滚动策略。在这个示例中,我们同时使用了SizeBasedTriggeringPolicy和TimeBasedRollingPolicy。当日志文件的大小达到指定的大小(10MB)或者按照时间规则触发滚动时,都会滚动到下一个文件。

# 3. MDC

当我们在应用程序中记录日志时,有时我们需要关联一些上下文信息,例如请求ID、用户ID、会话ID等。这种关联上下文信息的需求可以通过使用MDC(Mapped Diagnostic Context)来实现。

MDC是SLF4J(Simple Logging Facade for Java)提供的一个功能,它允许我们在应用程序的不同线程中存储和访问上下文信息。MDC使用一个类似于Map的结构来存储键值对,其中键是上下文的名称,值是对应的上下文信息。

以下是MDC的基本用法:

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

public class MyClass {
    private static final Logger logger = LoggerFactory.getLogger(MyClass.class);

    public void doSomething() {
        MDC.put("username", "john_doe");
        logger.info("Doing something...");
        // 在使用完MDC后,应该及时清除MDC中的值,以防止潜在的内存泄漏。
        MDC.clear();
    }
}

在日志事件中,我们可以通过在日志消息中使用MDC占位符来引用MDC的值。例如,使用%X{key}来引用名为"key"的MDC值。

使用MDC可以帮助我们将上下文信息与特定的日志事件关联起来,从而更好地追踪和分析日志。例如,在多线程环境中,我们可以在请求开始时设置请求ID,并在整个请求处理过程中记录该请求ID。这样,我们就可以根据请求ID轻松地筛选和分析与特定请求相关的日志。

如果我们想要在日志消息中显示MDC的值,需要相应地修改PatternLayout,例如:

<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} [%X{username}] - %msg%n</pattern>

在这种情况下,输出的日志消息将类似于:

2023-05-30 10:30:15 [main] INFO  com.example.MyClass [john_doe] - Doing something...

在这个日志消息中,我们可以看到MDC的值 "john_doe" 在方括号内与日志消息一起显示。这样,我们就可以将上下文信息与日志事件关联起来,并在日志中进行显示。

# Apache Log4j2

Apache Log4j2是Apache软件基金会提供的一种高性能、可扩展的日志框架。它是Log4j的升级版本,提供了更多的功能和性能优化。

下面是一个简单的Log4j2使用示例:

  1. 引入依赖:在项目的构建文件中引入Log4j2的依赖(如Maven或Gradle)。示例中使用Maven:
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.14.1</version>
</dependency>
  1. 配置Log4j2:创建一个Log4j2的配置文件,通常命名为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} [%thread] %-5level %logger{36} - %msg%n" />
        </Console>
    </Appenders>
    <Loggers>
        <Root level="info">
            <AppenderRef ref="Console" />
        </Root>
    </Loggers>
</Configuration>

上述配置创建了一个名为Console的ConsoleAppender,并定义了日志输出的格式。然后,将Console Appender添加到根Logger中,并设置日志级别为INFO。

  1. 在代码中使用Log4j2:在需要记录日志的类中,通过LogManager获取Logger实例,并使用不同级别的方法记录日志。示例代码如下:
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class MyClass {
    private static final Logger logger = LogManager.getLogger(MyClass.class);
    
    public void doSomething() {
        logger.info("Doing something...");
        logger.error("An error occurred!");
    }
}

在示例中,使用LogManager.getLogger()方法获取Logger实例,并传入当前类的Class作为参数。然后,使用logger的info()和error()方法记录相应级别的日志信息。

# 主流日志库的性能对比

image

参考了 同步和异步日志记录的基准测试 (opens new window)。

# 桥接与适配

在Spring Boot官方文档 (opens new window)中,有这么一段话:

Spring Boot将Commons Logging用于所有内部日志记录。为Java Util Logging、Log4j2和Logback提供了默认配置。在每种情况下,记录器都被预先配置为使用控制台输出,可选的文件输出也可用。
默认情况下,使用Logback进行日志记录。还包括适当的Logback路由,以确保使用Java Util Logging、Commons Logging、Log4J或SLF4J的依赖库都能正常工作。

Spring Boot默认使用了Logback作为日志框架,同时保证了对其他日志框架的兼容性。这是如何实现的呢?

查看spring-boot-starter-logging,我们发现它依赖了jul-to-slf4j(org.slf4j)和log4j-to-slf4j(org.apache.logging.log4j)。

这些包都是为了实现不同日志框架之间的互操作性而存在的。它们是SLF4J(Simple Logging Facade for Java)提供的桥接器,用于连接不同日志框架的API和SLF4J的API,以便统一使用SLF4J作为日志门面接口。

SLF4J提供了以下桥接器:

  1. jcl-over-slf4j:将Apache Common Logging(JCL)的API转发到SLF4J,通过使用jcl-over-slf4j桥接器,开发人员可以在应用程序中继续使用JCL的API,但实际日志将由SLF4J进行记录。
  2. jul-to-slf4j:将Java Util Logging(JUL)的API转发到SLF4J。它允许开发人员使用JUL的API进行日志记录,但实际日志将由SLF4J进行输出。
  3. log4j-over-slf4j:将Log4j的API转发到SLF4J。开发人员可以使用Log4j的API进行日志记录,但实际日志将由SLF4J进行输出。这个桥接器允许在现有的Log4j应用程序中逐步迁移到SLF4J。

此外,还提供了一些适配器:

  1. slf4j-jcl:提供了JCL的SLF4J适配器,使得通过SLF4J的API使用JCL进行日志记录成为可能。开发人员可以使用SLF4J的API,但实际日志将由JCL进行记录。
  2. slf4j-jdk14:提供了JDK 1.4 Logger的SLF4J适配器,使得通过SLF4J的API使用JUL进行日志记录成为可能。开发人员可以使用SLF4J的API,但实际日志将由JUL进行记录。
  3. slf4j-log4j12:提供了Log4j 1.x的SLF4J适配器,使得通过SLF4J的API使用Log4j进行日志记录成为可能。开发人员可以使用SLF4J的API,但实际日志将由Log4j进行输出。

这些适配器或桥接器的存在使得开发人员可以使用统一的SLF4J API,并且可以在应用程序中使用不同的日志框架,而无需修改现有的日志记录代码。这为日志系统的切换、升级和统一提供了方便。

# 总结

Java日志系统提供了多种选择,每个日志系统都有其优点和适用场景。在选择和使用Java日志系统时,可以考虑以下几点建议:

  1. 根据项目需求选择合适的日志系统:根据项目的规模、复杂性和性能需求,选择适合的日志系统。常见的选择包括Java Util Logging(JUL)、SLF4J、Apache Log4j2等。
  2. 合理配置日志级别:根据项目的运行环境和需求,合理配置日志级别。在开发阶段使用较低的级别(如DEBUG或TRACE)以获取更详细的调试信息,而在生产环境中使用较高的级别(如INFO或WARN)以减少日志量和性能开销。
  3. 注意日志系统的性能影响:日志记录对应用程序的性能有一定的影响。在关键路径上使用适当的日志级别和日志输出方式,以减少性能开销。
  4. 灵活使用日志系统的特性:不同的日志系统提供了丰富的特性和配置选项,如异步日志写入、日志滚动策略、自定义日志格式等。根据项目需求,灵活使用这些特性以满足日志记录和管理的需求。
  5. 日志分析和监控:日志不仅用于故障排查和调试,还可以用于系统监控和性能分析。考虑使用专业的日志分析工具或服务,以便对日志进行集中管理、分析和可视化。

综上所述,选择合适的日志系统并合理配置和使用,能够帮助开发人员更好地进行日志记录和管理,提升系统的可维护性和调试能力。同时,关注日志系统的发展趋势,及时采纳新的技术和工具,能够更好地适应日志记录的不断变化的需求。

祝你变得更强!

编辑 (opens new window)
#Java日志#Java Loggin
上次更新: 2025/02/07
Servlet与JSP指南
Java JSON处理

← Servlet与JSP指南 Java JSON处理→

最近更新
01
Spring Boot版本新特性
09-15
02
Spring框架版本新特性
09-01
03
Spring Boot开发初体验
08-15
更多文章>
Theme by Vdoing | Copyright © 2018-2025 京ICP备2021021832号-2 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式