MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Java使用Log4j实现日志管理

2022-02-167.6k 阅读

一、Log4j简介

Log4j 是 Apache 的一个开源日志记录工具,它被设计用于在 Java 应用程序中记录日志信息。Log4j 提供了一个灵活且强大的机制,允许开发者控制日志的生成、记录级别、输出目的地等。它具有高度的可配置性,能够适应不同规模和复杂度的项目需求。

Log4j 由三个主要组件构成:Logger(记录器)、Appender(输出端)和 Layout(布局)。Logger 负责产生日志记录,它是应用程序代码中用于调用日志记录的入口。Appender 决定了日志输出的位置,例如控制台、文件等。Layout 则负责定义日志信息的格式,如时间、线程名、日志级别和具体消息等如何呈现。

二、引入Log4j到Java项目

在开始使用 Log4j 之前,需要将其引入到 Java 项目中。如果使用 Maven 进行项目管理,在 pom.xml 文件中添加以下依赖:

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

如果使用 Gradle,在 build.gradle 文件中添加:

implementation 'org.apache.logging.log4j:log4j-api:2.14.1'
implementation 'org.apache.logging.log4j:log4j-core:2.14.1'

三、配置Log4j

  1. 默认配置
    • Log4j 在启动时会尝试查找默认的配置文件。默认情况下,它会查找 log4j2.xmllog4j2.jsonlog4j2.properties 文件。如果没有找到任何配置文件,Log4j 会使用其内置的默认配置,该默认配置会将日志输出到控制台,日志级别为 WARN
  2. XML 配置示例
    • 创建一个 log4j2.xml 文件,通常放在 src/main/resources 目录下。以下是一个基本的 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>
    </Appenders>
    <Loggers>
        <Root level="info">
            <AppenderRef ref="Console"/>
        </Root>
    </Loggers>
</Configuration>

在这个配置中:

  • <Configuration status="WARN">status 属性用于设置 Log4j 内部配置过程中的日志级别,WARN 表示只输出警告及以上级别的配置日志。
  • <Appenders> 部分定义了一个名为 ConsoleAppender,它将日志输出到系统标准输出(控制台)。PatternLayout 定义了日志的格式,%d{yyyy-MM-dd HH:mm:ss.SSS} 表示日期和时间,[%t] 是线程名,%-5level 是日志级别,%logger{36} 是记录器名称,%msg%n 是日志消息和换行符。
  • <Loggers> 部分定义了一个 Root 记录器,它的级别设置为 info,并引用了前面定义的 Console Appender。这意味着所有 info 级别及以上的日志都会输出到控制台。
  1. JSON 配置示例
    • 创建一个 log4j2.json 文件,同样放在 src/main/resources 目录下。
{
    "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"
                }
            }
        },
        "Loggers": {
            "Root": {
                "level": "info",
                "AppenderRef": {
                    "ref": "Console"
                }
            }
        }
    }
}

JSON 配置与 XML 配置在结构上类似,只是语法不同。它同样定义了一个控制台输出的 Appender 和一个 Root 记录器。

  1. Properties 配置示例
    • 创建一个 log4j2.properties 文件,放在 src/main/resources 目录下。
status = WARN
name = PropertiesConfig

appender.console.type = Console
appender.console.name = Console
appender.console.layout.type = PatternLayout
appender.console.layout.pattern = %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n

rootLogger.level = info
rootLogger.appenderRef.console.ref = Console

Properties 配置以键值对的形式定义了 Log4j 的配置。这里同样设置了控制台输出的 AppenderRoot 记录器的级别。

四、使用Logger记录日志

  1. 获取Logger实例 在 Java 代码中,首先需要获取 Logger 实例来记录日志。可以通过 LogManager 类来获取。例如:
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class Log4jExample {
    private static final Logger logger = LogManager.getLogger(Log4jExample.class);

    public static void main(String[] args) {
        logger.trace("Trace level log message");
        logger.debug("Debug level log message");
        logger.info("Info level log message");
        logger.warn("Warn level log message");
        logger.error("Error level log message");
        logger.fatal("Fatal level log message");
    }
}

在上述代码中,LogManager.getLogger(Log4jExample.class) 获取了与 Log4jExample 类相关联的 Logger 实例。不同级别的日志记录方法,如 tracedebuginfowarnerrorfatal,用于输出不同级别的日志消息。

  1. 日志级别

    • TRACE:是最低级别,用于记录非常详细的信息,通常在调试复杂问题时使用。例如,在追踪程序执行的详细流程时,每一步的变量值等信息可以使用 TRACE 级别日志记录。
    • DEBUG:用于记录调试信息,帮助开发者理解程序在运行过程中的状态。例如,记录方法的输入参数、中间计算结果等。
    • INFO:用于记录一般的信息,如程序启动、关闭,重要业务流程的开始和结束等。它提供了程序运行的基本信息。
    • WARN:用于记录潜在的问题或不推荐的操作,但这些问题不一定会导致程序失败。例如,使用了已过时的方法,或者配置可能存在问题时,可以记录 WARN 级别日志。
    • ERROR:用于记录程序运行过程中发生的错误,这些错误可能会导致程序部分功能失败,但程序可能仍然可以继续运行。例如,数据库查询失败、文件读取错误等。
    • FATAL:是最高级别,用于记录严重的错误,这些错误会导致程序无法继续运行,如系统崩溃、关键资源不可用等情况。
  2. 占位符和参数化日志

    • 当日志消息中包含变量时,可以使用占位符和参数化日志来提高性能和代码可读性。例如:
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class ParameterizedLoggingExample {
    private static final Logger logger = LogManager.getLogger(ParameterizedLoggingExample.class);

    public static void main(String[] args) {
        String username = "JohnDoe";
        int age = 30;
        logger.info("User {} is {} years old", username, age);
    }
}

在这个例子中,{} 是占位符,usernameage 作为参数传递给 info 方法。这种方式避免了不必要的字符串拼接,特别是在日志级别较低(如 TRACEDEBUG)且日志可能不会被输出的情况下,提高了性能。

五、高级配置

  1. 多个Appender
    • 可以配置多个 Appender 来将日志输出到不同的目的地。例如,除了控制台输出,还可以将日志输出到文件。以下是 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>
        <File name="File" fileName="logs/app.log">
            <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
        </File>
    </Appenders>
    <Loggers>
        <Root level="info">
            <AppenderRef ref="Console"/>
            <AppenderRef ref="File"/>
        </Root>
    </Loggers>
</Configuration>

在这个配置中,除了 Console Appender,还定义了一个 File Appender,它将日志输出到 logs/app.log 文件。Root 记录器同时引用了这两个 Appender,所以 info 级别及以上的日志会同时输出到控制台和文件。

  1. 按日志级别输出到不同Appender
    • 可以根据日志级别将日志输出到不同的 Appender。例如,将 DEBUG 级别及以下的日志输出到一个文件用于调试,将 INFO 级别及以上的日志输出到另一个文件用于生产环境监控。以下是 log4j2.xml 配置示例:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
    <Appenders>
        <File name="DebugFile" fileName="logs/debug.log">
            <ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY"/>
            <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
        </File>
        <File name="InfoFile" fileName="logs/info.log">
            <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
            <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
        </File>
    </Appenders>
    <Loggers>
        <Root level="trace">
            <AppenderRef ref="DebugFile"/>
            <AppenderRef ref="InfoFile"/>
        </Root>
    </Loggers>
</Configuration>

在这个配置中,DebugFile Appender 使用 ThresholdFilter 来只接受 DEBUG 级别及以下的日志,InfoFile Appender 只接受 INFO 级别及以上的日志。

  1. 异步日志记录
    • 在高并发应用程序中,同步日志记录可能会成为性能瓶颈。Log4j 支持异步日志记录来提高性能。可以通过 AsyncAppender 来实现。以下是 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>
        <Async name="AsyncConsole">
            <AppenderRef ref="Console"/>
        </Async>
    </Appenders>
    <Loggers>
        <Root level="info">
            <AppenderRef ref="AsyncConsole"/>
        </Root>
    </Loggers>
</Configuration>

在这个配置中,AsyncAppender 名为 AsyncConsole,它包装了 Console Appender。这样,日志记录操作将在一个单独的线程中执行,从而减少对主应用程序线程的影响。

六、自定义布局和Appender

  1. 自定义布局
    • 如果默认的布局不能满足需求,可以自定义布局。首先创建一个类继承自 AbstractStringLayout 并实现其抽象方法。例如:
import org.apache.logging.log4j.core.Layout;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
import org.apache.logging.log4j.core.config.plugins.PluginFactory;
import org.apache.logging.log4j.core.layout.AbstractStringLayout;

import java.nio.charset.Charset;

@Plugin(name = "CustomLayout", category = "Layout")
public class CustomLayout extends AbstractStringLayout {
    private final String prefix;

    protected CustomLayout(Charset charset, String prefix) {
        super(charset);
        this.prefix = prefix;
    }

    @Override
    public String toSerializable(LogEvent event) {
        return prefix + event.getMessage().getFormattedMessage() + "\n";
    }

    @PluginFactory
    public static CustomLayout createLayout(
            @PluginAttribute("charset") String charsetName,
            @PluginAttribute("prefix") String prefix) {
        Charset charset = charsetName == null? Charset.defaultCharset() : Charset.forName(charsetName);
        return new CustomLayout(charset, prefix);
    }
}

然后在 log4j2.xml 中使用自定义布局:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <CustomLayout charset="UTF-8" prefix="[Custom] "/>
        </Console>
    </Appenders>
    <Loggers>
        <Root level="info">
            <AppenderRef ref="Console"/>
        </Root>
    </Loggers>
</Configuration>

在这个例子中,自定义布局 CustomLayout 在每条日志消息前添加了一个前缀 [Custom]

  1. 自定义Appender
    • 自定义 Appender 需要创建一个类继承自 AbstractAppender 并实现其抽象方法。例如:
import org.apache.logging.log4j.core.Appender;
import org.apache.logging.log4j.core.Core;
import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.Layout;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.appender.AbstractAppender;
import org.apache.logging.log4j.core.config.Property;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
import org.apache.logging.log4j.core.config.plugins.PluginElement;
import org.apache.logging.log4j.core.config.plugins.PluginFactory;

@Plugin(name = "CustomAppender", category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE)
public class CustomAppender extends AbstractAppender {
    protected CustomAppender(String name, Filter filter, Layout<? extends LogEvent> layout, boolean ignoreExceptions) {
        super(name, filter, layout, ignoreExceptions);
    }

    @Override
    public void append(LogEvent event) {
        String message = getLayout().toSerializable(event);
        System.out.println("Custom Appender: " + message);
    }

    @PluginFactory
    public static CustomAppender createAppender(
            @PluginAttribute("name") String name,
            @PluginElement("Filter") Filter filter,
            @PluginElement("Layout") Layout<? extends LogEvent> layout,
            @PluginAttribute("ignoreExceptions") boolean ignoreExceptions) {
        if (name == null) {
            LOGGER.error("No name provided for CustomAppender");
            return null;
        }
        return new CustomAppender(name, filter, layout, ignoreExceptions);
    }
}

log4j2.xml 中使用自定义 Appender

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
    <Appenders>
        <CustomAppender name="CustomAppender">
            <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
        </CustomAppender>
    </Appenders>
    <Loggers>
        <Root level="info">
            <AppenderRef ref="CustomAppender"/>
        </Root>
    </Loggers>
</Configuration>

在这个例子中,自定义 Appender CustomAppender 将日志消息输出到控制台,并在前面添加了 Custom Appender: 前缀。

七、Log4j与Web应用

  1. 在Servlet应用中使用Log4j
    • 在基于 Servlet 的 Web 应用中,首先确保 Log4j 的依赖已经添加到项目的 WEB - INF/lib 目录或通过构建工具管理。可以在 Servlet 中获取 Logger 实例并记录日志。例如:
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/example")
public class Log4jServlet extends HttpServlet {
    private static final Logger logger = LogManager.getLogger(Log4jServlet.class);

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        logger.info("Handling GET request");
        response.getWriter().println("Log4j example in Servlet");
    }
}

同时,在 web.xml 中配置 Servlet:

<web - app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema - instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
                             http://xmlns.jcp.org/xml/ns/javaee/web - app_3_1.xsd"
         version="3.1">
    <servlet>
        <servlet - name>Log4jServlet</servlet - name>
        <servlet - class>Log4jServlet</servlet - class>
    </servlet>
    <servlet - mapping>
        <servlet - name>Log4jServlet</servlet - name>
        <url - pattern>/example</url - pattern>
    </servlet - mapping>
</web - app>
  1. 在Spring Boot应用中使用Log4j
    • Spring Boot 默认使用 Logback 作为日志框架,但可以很容易地切换到 Log4j2。首先在 pom.xml 中排除 Logback 依赖,添加 Log4j2 依赖:
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring - boot - starter - web</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring - boot - starter - logging</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring - boot - starter - log4j2</artifactId>
</dependency>

然后在 src/main/resources 目录下创建 log4j2.xml 或其他 Log4j2 配置文件进行配置。在 Spring Boot 应用中,可以像在普通 Java 类中一样获取 Logger 实例记录日志。例如:

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class Log4jSpringBootController {
    private static final Logger logger = LogManager.getLogger(Log4jSpringBootController.class);

    @GetMapping("/")
    public String home() {
        logger.info("Handling home request");
        return "Log4j example in Spring Boot";
    }
}

通过这些步骤,就可以在 Web 应用中有效地使用 Log4j 进行日志管理,无论是传统的 Servlet 应用还是基于 Spring Boot 的现代 Web 应用。

通过以上详细的介绍和示例代码,相信开发者能够全面掌握在 Java 中使用 Log4j 实现日志管理的方法,从基本的配置和使用到高级的自定义和在不同类型应用中的集成。合理使用 Log4j 可以帮助开发者更好地调试程序、监控应用运行状态以及排查问题。