功能目标:
实现 Log 模块,支持日志记录和日志级别管理。
实现内容:
Log 模块:实现一个日志组件,用于记录请求日志、错误日志和系统日志。
日志级别:支持不同的日志级别(INFO、DEBUG、ERROR 等),以便控制日志的详细程度。
实现方式:设计一个简单的
Logger
类,提供不同级别的日志输出,并配置输出格式和文件路径。
14.1 日志记录的重要性
在 Web 应用开发中,日志记录是非常重要的。日志帮助我们跟踪系统的运行状态,诊断问题,并提供可用的监控信息。常见的日志类型包括:
请求日志:记录每个 HTTP 请求的相关信息。
错误日志:记录程序异常和错误信息。
系统日志:记录系统级别的信息,如服务启动、停止等事件。
为了更高效地记录日志,我们需要将日志分为不同的级别,并根据日志级别来输出不同的日志内容。
14.2 日志级别的设计
常见的日志级别如下:
DEBUG:最详细的日志,用于调试阶段,记录系统的详细信息。
INFO:常规信息,记录系统的正常操作,如请求处理过程等。
WARN:警告信息,用于记录可能出现问题的地方,但不一定会导致错误。
ERROR:错误信息,用于记录异常或错误,系统无法继续运行的情况。
我们可以通过控制日志级别来决定输出多少日志信息,避免在生产环境中输出过多的调试信息。
14.3 实现 Logger 类
我们将实现一个 Logger
类,支持不同级别的日志输出。每个日志级别都对应一个方法:debug()
、info()
、warn()
和 error()
。
14.3.1 Logger 类的设计
Logger
类将支持日志级别的控制,并能将日志输出到控制台和文件。
package server;
import java.io.FileWriter;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class Logger {
// 日志级别
public enum Level {
DEBUG, INFO, WARN, ERROR
}
private Level currentLevel;
private String logFilePath;
// 构造函数,设置日志级别和日志文件路径
public Logger(Level level, String logFilePath) {
this.currentLevel = level;
this.logFilePath = logFilePath;
}
// 获取当前时间的字符串
private String getCurrentTime() {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
return LocalDateTime.now().format(formatter);
}
// 输出日志到控制台和文件
private void log(Level level, String message) {
if (level.ordinal() >= currentLevel.ordinal()) {
String logMessage = String.format("[%s] [%s] %s", getCurrentTime(), level, message);
// 输出到控制台
System.out.println(logMessage);
// 输出到文件
try (FileWriter writer = new FileWriter(logFilePath, true)) {
writer.write(logMessage + "\n");
} catch (IOException e) {
e.printStackTrace();
}
}
}
// debug级别日志
public void debug(String message) {
log(Level.DEBUG, message);
}
// info级别日志
public void info(String message) {
log(Level.INFO, message);
}
// warn级别日志
public void warn(String message) {
log(Level.WARN, message);
}
// error级别日志
public void error(String message) {
log(Level.ERROR, message);
}
}
14.3.2 主要方法解析
构造函数:接受日志级别(
Level
)和日志文件路径(logFilePath
),用于初始化日志对象。getCurrentTime:用于获取当前的时间,并格式化为字符串,以便在日志中输出时间。
log:根据日志级别输出日志信息。如果日志级别满足当前日志设置的级别,则将日志输出到控制台和文件。
debug、info、warn、error:分别对应不同级别的日志输出方法。
14.4 配置和使用 Logger
在实际使用中,我们可以通过 Logger
类记录 Web 容器中的各种日志信息。
14.4.1 配置和使用示例
package com.daicy.minitomcat;
public class LogManager {
private static FileLogger logger = new FileLogger(FileLogger.Level.INFO, "server.log");
// 创建 Logger 实例,设置日志级别为 INFO,并指定日志文件路径
public static FileLogger getLogger() {
return logger;
}
public static void setLogger(FileLogger newLogger) {
logger = newLogger;
}
}
package com.daicy.minitomcat;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Date;
public class LoggingFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// 初始化操作,如果有需要可以在这里读取配置参数等
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
long startTime = System.currentTimeMillis();
HttpServletRequest request = (HttpServletRequest) servletRequest;
LogManager.getLogger().info("Request started at: " + startTime + " for path: " + request.getRequestURI());
filterChain.doFilter(servletRequest, servletResponse);
long endTime = System.currentTimeMillis();
LogManager.getLogger().info("Request completed at: " + endTime + " for path: " + request.getRequestURI() + " Took: " + (endTime - startTime) + "ms");
}
@Override
public void destroy() {
// 清理资源操作
}
}
在这个示例中,我们创建了一个 Logger
实例,日志级别为 INFO
。这意味着 DEBUG
级别的日志将不会输出,而 INFO
、WARN
和 ERROR
级别的日志会被输出。
输出结果(控制台和 server.log
文件中)如下:
[2024-11-23 15:39:00] [INFO] Servlet context initialized.
[2024-11-23 15:39:00] [INFO] HelloServlet initialized.
[2024-11-23 15:39:00] [INFO] HTTP Connector is running on port 8080
[2024-11-23 15:39:07] [INFO] Accepted connection from /0:0:0:0:0:0:0:1
[2024-11-23 15:39:07] [INFO] Accepted connection from /0:0:0:0:0:0:0:1
[2024-11-23 15:39:07] [INFO] Session created with ID: afeb595f-354d-4b6a-a136-f11ba15f4bb2
[2024-11-23 15:39:07] [INFO] LogValve: Logging request /hello
[2024-11-23 15:39:07] [INFO] Request started at: 1732347547912 for path: /hello
[2024-11-23 15:39:07] [INFO] Request completed at: 1732347547912 for path: /hello Took: 0ms
[2024-11-23 15:39:40] [INFO] Server stopping...
[2024-11-23 15:39:40] [INFO] HelloServlet destroyed.
[2024-11-23 15:39:40] [INFO] Servlet context destroyed.
14.5 日志管理和扩展
14.5.1 日志级别管理
在生产环境中,我们通常会将日志级别设置为 INFO
或更高的级别,这样可以避免过多的调试信息输出。而在开发和调试过程中,DEBUG
级别的日志有助于我们进行问题排查。
14.5.2 日志文件管理
为了防止日志文件过大,我们可以定期轮换日志文件。例如,使用时间戳或文件大小来分割日志文件。日志文件的管理可以通过日志框架(如 Log4j 或 SLF4J)来实现。
14.5.3 日志的多线程安全
如果系统是多线程的,日志输出可能会受到线程竞争的影响。可以通过同步方法或使用线程安全的日志框架来确保日志输出的正确性。
14.6 学习收获
通过实现日志模块,我们学习了以下内容:
日志级别控制:我们掌握了日志级别控制的原理,理解了如何根据不同的日志级别输出不同的日志信息。
日志输出到文件和控制台:我们实现了日志的双重输出,既能在控制台显示,也能记录到日志文件中,方便后期分析。
日志模块的可扩展性:通过
Logger
类的设计,我们可以灵活地扩展日志模块,支持更多功能,如日志轮转、异步日志等。
通过日志模块的实现,我们为 Web 容器的调试、监控和运维提供了基础支持。
项目源代码地址:
https://github.com/daichangya/MiniTomcat/tree/chapter14/mini-tomcat