- 一、日志框架
- 1. 什么是日志框架?
- 2. 日志框架的作用
- 3. 常见的日志框架
- 3.1. Log4j
- 3.2. Logback
- 3.3. java.util.logging (JUL)
- 3.4. SLF4J(日志门面)(Simple Logging Facade for Java)
- 4.常见的日志级别
- 5.讲讲logback
- 5.1. logback-core
- 5.2. logback-classic
- 5.3. logback-access
- 5.4.学到此处的疑惑?
- 6.举个完整示例
- 6.1. 项目结构 (登录为例)
- 6.2. Maven 依赖
- 6.3. Logback 配置文件
- 6.4.Logback 的 JDBCAppender
一、日志框架
1. 什么是日志框架?
日志框架 是一种软件组件,负责记录应用程序在运行时生成的日志信息。日志信息通常包括错误、警告、调试信息和其他重要的运行状态,帮助开发者和运维人员监控和调试应用。
2. 日志框架的作用
- 记录运行信息:捕获程序执行中的重要事件和状态信息,便于追踪问题和性能瓶颈。
- 故障排查:当应用发生错误时,日志记录提供了上下文信息,有助于快速定位和解决问题。
- 性能监控:记录性能指标,帮助分析系统的运行效率。
- 审计和合规:某些应用需要记录操作历史以满足安全审计和合规要求。
- 可追溯性:通过日志,用户可以追溯到具体的操作和系统状态。
3. 常见的日志框架
3.1. Log4j
最早的 Java 日志框架之一,提供了强大的日志记录功能。
支持多种输出目标(控制台、文件、数据库等),灵活的配置方式,但在性能上相对较低。
目前有 Log4j 1 和 Log4j 2,后者在性能和功能上进行了显著改进。
3.2. Logback
由 Log4j 的创始人开发,是 SLF4J 的原生实现,性能优越(比log4j强)。
支持异步日志记录、自动重载配置文件、灵活的输出格式等。
3.3. java.util.logging (JUL)
Java 官方自带的日志框架,简单易用,但功能和灵活性相对较弱。
默认与 JDK 一起提供,不需要额外依赖,但通常被其他日志框架替代。
java.util.logging 适合小型项目和快速集成,但在复杂应用中可能无法满足需求。
3.4. SLF4J(日志门面)(Simple Logging Facade for Java)
SLF4J 是一个日志门面,提供了统一的日志 API,允许开发者在不同的日志框架之间切换。可以与多种日志框架(如 Logback、Log4j)兼容使用,提供简洁的 API,易于使用。将依赖从具体日志框架转移到统一接口,简化了依赖管理。不处理日志,只是一层抽象,实际的日志处理依赖于其他框架。
4.常见的日志级别
- TRACE:最细粒度的信息,通常用于追踪问题。
- DEBUG:调试信息,通常在开发和测试时使用。
- INFO:正常运行的信息。
- WARN:警告信息,表示可能出现的问题。
- ERROR:错误信息,表示发生了错误。
- FATAL:严重错误,表示应用程序无法继续运行。
5.讲讲logback
Logback 主要由三个核心模块构成:
logback-core:核心模块,提供基本的日志记录功能。
logback-classic:经典模块,提供 SLF4J(Simple Logging Facade for Java)实现,支持多种日志级别和输出格式。
logback-access:用于 Servlet 环境中的访问日志记录。
5.1. logback-core
logback-core 是 Logback 的基础模块,提供了日志记录的核心功能和通用的 API。它包含了 Logback 的一些基本组件,比如 Appender、Layout 和 Filter。它定义了基本的日志记录行为。提供了其他模块(如 logback-classic 和 logback-access)所需的共享功能。
一般情况下,不需要直接与 logback-core 进行交互,因为它是 Logback 其他模块的基础。
5.2. logback-classic
logback-classic 是 Logback 的经典模块,它实现了 SLF4J的接口。这个模块是最常用的,通常用于大多数 Java 应用程序。
提供对 SLF4J API 的实现,支持多种日志级别(如 TRACE、DEBUG、INFO、WARN、ERROR、FATAL)。
允许配置日志输出到不同的目标(控制台、文件等)。
支持日志格式化和过滤器。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory; public class MyApp {private static final Logger logger = LoggerFactory.getLogger(MyApp.class);public static void main(String[] args) {logger.info("应用程序启动");try {int result = 10 / 0;} catch (Exception e) {logger.error("发生错误:{}", e.getMessage());}logger.info("应用程序结束");}
5.3. logback-access
logback-access 是 Logback 的一个扩展模块,专门用于 Servlet 环境中的访问日志记录。它允许你记录 HTTP 请求和响应的相关信息。
提供一个 Servlet 过滤器,可以捕获和记录请求信息。
允许配置请求和响应日志的输出格式。
public class LoginServlet extends HttpServlet {private static final Logger logger = LoggerFactory.getLogger(LoginServlet.class);@Overrideprotected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {String username = request.getParameter("username");String password = request.getParameter("password");// 假设的用户验证逻辑boolean isAuthenticated = authenticateUser(username, password);if (isAuthenticated) {logger.info("用户 {} 登录成功", username);response.getWriter().write("登录成功");} else {logger.warn("用户 {} 登录失败,密码错误", username);response.getWriter().write("登录失败");}}private boolean authenticateUser(String username, String password) {return "admin".equals(username) && "password".equals(password);}
5.4.学到此处的疑惑?
上述在http交互中,好像直接使用logback classic也可以,为什么还多此一举多个logback access?或者说logback access优越在哪里?
举个例子作比较:
1.假设我们在处理用户登录时,使用 Logback Classic 记录日志:
public class LoginServlet extends HttpServlet {private static final Logger logger = LoggerFactory.getLogger(LoginServlet.class);@Overrideprotected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {String username = request.getParameter("username");String password = request.getParameter("password");boolean isAuthenticated = authenticateUser(username, password);if (!isAuthenticated) {// 手动记录登录失败的信息logger.warn("登录失败,用户名: {}, 请求方法: {}, 请求路径: {}", username, request.getMethod(), request.getRequestURI()); ---注意这里response.getWriter().write("登录失败");} else {response.getWriter().write("登录成功");}}private boolean authenticateUser(String username, String password) {return "admin".equals(username) && "password".equals(password);}
}
输出结果:
2024-10-21 10:00:00 WARN [http-nio-8080-exec-1] LoginServlet [LoginServlet.java:25]
登录失败,用户名: user, 请求方法: POST, 请求路径: /login
2.在同样的登录失败场景中,使用 Logback Access:
public class LoginServlet extends HttpServlet {@Overrideprotected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {String username = request.getParameter("username");String password = request.getParameter("password");boolean isAuthenticated = authenticateUser(username, password);if (!isAuthenticated) {// 使用 Logback Access 自动记录 HTTP 请求信息LogbackAccessFilter filter = new LogbackAccessFilter();filter.logAccess(request, response, "登录失败,用户名: " + username); ---和上面比一下response.getWriter().write("登录失败");} else {response.getWriter().write("登录成功");}}private boolean authenticateUser(String username, String password) {return "admin".equals(username) && "password".equals(password);}
}
输出结果
2024-10-21 10:00:00 WARN [http-nio-8080-exec-1] AccessLogger [LogbackAccess.java:123]
请求方法: POST, 请求路径: /login, 用户名: user, 状态码: 401, 响应时间: 150ms
3.比较分析
输出内容:
Logback Classic:记录了用户名、请求方法和请求路径。这些信息帮助理解发生了什么,但缺乏对请求状态的详细描述。
Logback Access:自动记录请求方法、请求路径、状态码和响应时间等多种信息,使得分析请求情况更加全面。
代码简洁性:
Logback Classic:需要手动提取和记录请求相关的信息,代码较冗长且容易出错。
Logback Access:自动处理 HTTP 请求的记录,代码更简洁,专注于业务逻辑。
上下文信息:
Logback Classic:需要自己管理所有的请求上下文信息,容易遗漏关键信息。
Logback Access:全面捕获 HTTP 请求的上下文信息。
性能优化:
Logback Classic:未针对 HTTP 请求进行优化,记录过程相对较慢。
Logback Access:经过优化,专门为 HTTP 请求设计,可以更高效地记录访问信息。
6.举个完整示例
6.1. 项目结构 (登录为例)
my-login-app
│
├── src
│ ├── main
│ │ ├── java
│ │ │ ├── LoginServlet.java
│ │ │ └── MyApp.java
│ │ └── resources
│ │ └── logback.xml
│ └── webapp
│ └── index.html
└── pom.xml
6.2. Maven 依赖
<dependencies><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>1.7.30</version></dependency><dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>1.2.3</version></dependency><dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>4.0.1</version><scope>provided</scope></dependency>
</dependencies>
6.3. Logback 配置文件
在 src/main/resources 目录下创建 logback.xml 文件,内容如下:
<configuration><!-- 配置 ConsoleAppender,用于输出日志到控制台 --><appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"><encoder><pattern>%d{yyyy-MM-dd HH:mm:ss} %-5level [%thread] %logger{36} [%file:%line] - %msg%n</pattern></encoder></appender><!-- 配置 AccessAppender,用于处理 HTTP 访问日志 --><appender name="ACCESS" class="ch.qos.logback.access.Appender"><encoder><pattern>%h %l %u %t "%r" %s %b %D</pattern> <!-- 自定义输出格式 --></encoder></appender><!-- 配置日志级别 --><root level="INFO"><appender-ref ref="STDOUT" /> <!-- 将控制台输出连接到根日志 --></root><logger name="ch.qos.logback.access" level="DEBUG"><appender-ref ref="ACCESS" /> <!-- 将访问日志连接到特定日志级别 --></logger></configuration>
6.4.Logback 的 JDBCAppender
将日志输出到数据库中
1.先数据库建表:
CREATE TABLE logs (id INT AUTO_INCREMENT PRIMARY KEY,timestamp TIMESTAMP NOT NULL,level VARCHAR(10) NOT NULL,thread VARCHAR(50),logger VARCHAR(100),message TEXT
);
2.配置xml:
<configuration><!-- JDBC Appender --><appender name="JDBC" class="ch.qos.logback.classic.db.JDBCAppender"><connectionSource><driverClass>com.mysql.cj.jdbc.Driver</driverClass> <url>jdbc:mysql://localhost:3306/database</url> <user>username</user> <password>password</password> </connectionSource><encoder><pattern>%date %level [%thread] %logger{10} %msg%n</pattern> </encoder></appender><root level="debug"><appender-ref ref="JDBC" /> <!-- 引用 JDBC Appender --></root>
</configuration>
3.数据存储结果:
id timestamp level thread logger message
1 2024-10-22 10:00:00 INFO main com.example.LoginServlet 用户 admin 登录成功
2 2024-10-22 10:01:00 WARN main com.example.LoginServlet 登录失败,用户名: user
3 2024-10-22 10:02:00 ERROR main com.example.LoginServlet 数据库连接失败
已经集成了与数据库操作,很方便