ZetCode

Java Servlet Log4j

最后修改于 2020 年 7 月 13 日

Java Servlet Log4j 教程展示了如何在 Java Servlet 中使用 Log4j 进行日志记录。本教程涵盖 Log4j 2 版本。

Java Servlet

Servlet 是一个 Java 类,它响应特定类型的网络请求——最常见的是 HTTP 请求。Java Servlet 用于创建 Web 应用程序。它们运行在 Servlet 容器中,如 Tomcat 或 Jetty。现代 Java Web 开发使用构建在 Servlet 之上的框架。

Log4j

Apache Log4j 是一个基于 Java 的日志记录实用程序。它是 Apache 软件基金会的项目。Log4j 可以通过 Java 代码或配置文件进行配置。配置文件可以使用 XML、JSON、YAML 或属性文件格式编写。

Log4j 有三个主要组件:日志记录器 (loggers)、附加器 (appenders) 和布局 (layouts)。日志记录器是捕获日志消息并将其发送到附加器的命名目的地。附加器将日志消息传递到它们的目的地,例如文件或控制台。布局用于定义日志消息的格式。

Java Servlet 日志记录示例

以下 Web 应用程序使用 Log4j 进行日志记录。在 Servlet 3.0+ 应用程序中,Log4j 可以直接使用。它会在应用程序部署时自动启动,并在应用程序取消部署时关闭。

$ tree
.
├── pom.xml
└── src
    └── main
        ├── java
        │   └── com
        │       └── zetcode
        │           ├── service
        │           │   └── MyService.java
        │           └── web
        │               └── MyServlet.java
        ├── resources
        │   └── log4j2.xml
        └── webapp
            ├── index.html
            ├── META-INF
            │   └── context.xml
            └── WEB-INF

这是项目结构。Log4j 配置文件位于 src/main/resources/ 目录中。

pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" 
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xsd/maven-4.0.0.xsd">
    
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.zetcode</groupId>
    <artifactId>JavaServletLog4j</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <name>JavaServletLog4j</name>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>
    
    <dependencies>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <scope>provided</scope>
        </dependency>
        
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-web</artifactId>
            <version>2.8.2</version>
        </dependency>
        
    </dependencies>
    
    <build>
        <plugins>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>2.3</version>
                <configuration>
                    <failOnMissingWebXml>false</failOnMissingWebXml>
                </configuration>
            </plugin>
 
        </plugins>
    </build>    

</project>

这是 Maven POM 文件。我们有两个工件:用于 Servlet 的 javax.servlet-api 和用于在 Web 应用程序中使用 Log4j 进行日志记录的 log4j-webmaven-war-plugin 负责收集 Web 应用程序的所有工件依赖项、类和资源,并将它们打包成 Web 应用程序归档 (WAR)。

context.xml
<?xml version="1.0" encoding="UTF-8"?>
<Context path="/JavaServletLog4j"/>

在 Tomcat 的 context.xml 文件中,我们定义了上下文路径。它是 Web 应用程序的名称。

log4j2.xml
<?xml version="1.0" encoding="utf-8"?>
<Configuration status="info">

  <Properties>
    <Property name="logdir">/home/janbodnar/tmp</Property>
    <Property name="layout">%d [%t] %-5p %c- %m%n</Property>
  </Properties>
  
  <Appenders>

    <RollingFile name="LOCALHOST"
        fileName="${logdir}/localhost.log"
        filePattern="${logdir}/localhost.%d{yyyy-MM-dd}-%i.log">
      <PatternLayout pattern="${layout}"/>
      <Policies>
        <TimeBasedTriggeringPolicy />
        <SizeBasedTriggeringPolicy size="1 MB" />
      </Policies>
      <DefaultRolloverStrategy max="10" />
    </RollingFile>

  </Appenders>
  
  <Loggers>

    <Logger name="com.zetcode"
        level="info" additivity="false">
      <AppenderRef ref="LOCALHOST" />
    </Logger>
    
    <Root level="error">
    </Root>    

  </Loggers>
</Configuration>

Log4j 在 log4j2.xml 中进行配置。在我们的示例中,我们选择了 XML 文件格式。

<Properties>
  <Property name="logdir">/home/janbodnar/tmp</Property>
  <Property name="layout">%d [%t] %-5p %c - %m%n</Property>
</Properties>

Properties 标签中,我们设置了日志记录目录和布局。布局定义了日志的格式。

模式布局由转换说明符组成。每个说明符都以百分号开头,后面跟着可选的格式修饰符和必需的转换字符。%d 输出日志事件的日期。%t 输出生成日志事件的线程名称。%-5p 输出日志事件的级别,其中级别名称至少有五个字符,并且字符左对齐。%c 输出发布日志事件的日志记录器的名称。%m 打印与日志事件关联的应用程序消息,而 %n 是平台相关的换行符字符或字符。

<Appenders>
...
</Appenders>

附加器是定义日志消息保存位置的对象。有几种可能的目的地,包括控制台、文件、数据库表或套接字。

<RollingFile name="LOCALHOST"
    fileName="${logdir}/localhost.log"
    filePattern="${logdir}/localhost.%d{yyyy-MM-dd}-%i.log">
  <PatternLayout pattern="${layout}" />
...
  <DefaultRolloverStrategy max="10" />
</RollingFile>

我们设置了日志文件的位置。我们使用了一个滚动文件附加器 (rolling file appender),它会自动滚动或归档当前日志文件,并在新文件中恢复日志记录。PatternLayout 设置日志消息的布局。DefaultRolloverStrategy 在归档文件数量达到十个时删除较旧的归档文件。

<Policies>
  <TimeBasedTriggeringPolicy />
  <SizeBasedTriggeringPolicy size="1 MB" />
</Policies>

触发策略定义在 Policies 标签中。它们控制发生滚动的条件。这里我们使用了两个策略:TimeBasedTriggeringPolicySizeBasedTriggeringPolicyTimeBasedTriggeringPolicy 根据最具体的日期和时间模式开始滚动;在我们的例子中,每小时滚动一次。SizeBasedTriggeringPolicy 在日志文件大小达到 1 MB 时开始滚动。

<Loggers>

  <Logger name="com.zetcode"
      level="info" additivity="false">
    <AppenderRef ref="LOCALHOST" />
  </Logger>
    
  <Root level="error">
  </Root>    

</Loggers>

Loggers 标签中,我们定义了日志记录器。它们是命名的日志消息目的地。每个日志记录器都可以配置不同的日志级别。我们定义了一个具有 info 日志级别的日志记录器。我们将之前定义的滚动文件附加器附加到此日志记录器。将 additivity 设置为 false,日志消息不会传播到它们的祖先。

com/zetcode/MyServlet.java
package com.zetcode.web;

import com.zetcode.service.MyService;
import java.io.IOException;
import java.io.PrintWriter;
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 org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

@WebServlet(name = "MyServlet", urlPatterns = {"/MyServlet"})
public class MyServlet extends HttpServlet {

    final static Logger logger = LogManager.getLogger(MyService.class);

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        logger.info("MyServlet's doGet() called");

        MyService service = new MyService();
        service.doWork();

        response.setContentType("text/plain;charset=UTF-8");

        PrintWriter out = response.getWriter();
        out.print("MyServlet called");
    }
}

这是 MyServlet Servlet。它调用一个服务方法并将文本数据发送回客户端。

final static Logger logger = LogManager.getLogger(MyService.class);

我们从 LogManager 获取日志记录器。

logger.info("MyServlet's doGet() called");

我们记录一个信息级别的消息。

MyService service = new MyService();
service.doWork();

我们调用一个虚拟的服务方法。

PrintWriter out = response.getWriter();
out.print("MyServlet called");

我们将文本数据发送给客户端。

com/zetcode/MyService.java
package com.zetcode.service;

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

public class MyService {
    
    final static Logger logger = LogManager.getLogger(MyService.class);
    
    public void doWork() {
    
        logger.info("MyService's doWork() called");
    }
}

MyServicedoWork 方法记录一个信息级别的消息。

index.html
<!DOCTYPE html>
<html>
    <head>
        <title>Home Page</title>
        <meta charset="UTF-8">
    </head>
    <body>
        <a href="MyServlet">Call Servlet</a>
    </body>
</html>

主页包含一个调用 MyServlet 的链接。

$ cat localhost.log 
2017-11-14 16:50:30,157 [http-nio-8084-exec-5] INFO  com.zetcode.service.MyService- MyServlet's doGet() called
2017-11-14 16:50:31,044 [http-nio-8084-exec-5] INFO  com.zetcode.service.MyService- MyService's doWork() called

这是日志消息的示例输出。

在本教程中,我们在 Java Web 应用程序中使用 Log4j 进行了一些日志记录。