如何在Spring Boot中读取Tomcat的访问日志?
问题背景
运行在Tomcat上的Spring Boot应用生成了access_log日志,日志样本如下:
IP - - [17/Feb/2023:17:27:20 +0400] "GET /web-services/112233.jpg HTTP/1.1" IP - - [17/Feb/2023:17:27:29 +0400] "GET /web-services/ HTTP/1.1" 200 189 IP - - [17/Feb/2023:17:27:29 +0400] "GET /web-services/112233.jpg HTTP/1.1" 304
当前application.properties配置:
spring.mvc.view.prefix= /WEB-INF/jsp/ spring.mvc.view.suffix= .jsp server.tomcat.accesslog.enabled= true
尝试过注入AccessLogValve但始终为null,添加额外Tomcat日志配置也未解决问题,需要在Java中读取这些日志内容。
解决方案
方法一:直接读取日志文件
Tomcat的access_log是本地文件,可通过Java文件IO直接读取,需注意日志文件的路径和滚动规则。
代码实现
import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; import java.util.List; @RestController public class AccessLogController { // 从配置文件读取日志目录,默认取Tomcat logs目录 @Value("${server.tomcat.accesslog.directory:${catalina.base}/logs}") private String accessLogDir; // 读取日志前缀,默认是localhost_access_log @Value("${server.tomcat.accesslog.prefix:localhost_access_log}") private String accessLogPrefix; @GetMapping("/access-logs") public List<String> getAccessLogs() throws IOException { // 若配置了file-date-format,需拼接日期(如localhost_access_log.2023-02-17.txt) // 此处示例取基础日志文件名,实际可遍历目录按修改时间获取最新日志 String logFilePath = accessLogDir + "/" + accessLogPrefix + ".txt"; return Files.readAllLines(Paths.get(logFilePath)); } }
注意事项
- 若开启了日志滚动(按日期/大小切割),需遍历日志目录,筛选出目标日期或最新的日志文件
- 确保应用程序对日志目录有读取权限
方法二:自定义AccessLogValve捕获日志
默认的AccessLogValve由Tomcat初始化,不属于Spring容器管理,无法直接注入。可自定义Valve继承AccessLogValve,在日志生成时直接捕获内容。
1. 自定义AccessLogValve
import org.apache.catalina.valves.AccessLogValve; import org.apache.catalina.connector.Request; import org.apache.catalina.connector.Response; import java.util.concurrent.CopyOnWriteArrayList; public class CustomAccessLogValve extends AccessLogValve { // 线程安全集合存储日志,实际可替换为数据库、消息队列等持久化方式 private static final CopyOnWriteArrayList<String> accessLogs = new CopyOnWriteArrayList<>(); @Override public void log(Request request, Response response, long time) { // 调用父类方法生成标准日志字符串 String logEntry = super.getLogEntry(request, response, time); if (logEntry != null) { accessLogs.add(logEntry); } // 保留原有的日志写入文件逻辑 super.log(request, response, time); } public static CopyOnWriteArrayList<String> getAccessLogs() { return accessLogs; } }
2. 注册自定义Valve到Tomcat
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; import org.springframework.boot.web.server.WebServerFactoryCustomizer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class TomcatConfig { @Bean public WebServerFactoryCustomizer<TomcatServletWebServerFactory> customTomcatAccessLog() { return factory -> { CustomAccessLogValve customValve = new CustomAccessLogValve(); // 配置日志格式,与application.properties保持一致 customValve.setPattern("%h %l %u %t \"%r\" %s %b"); // 配置日志存储目录、前缀等,可从配置文件读取 customValve.setDirectory("logs"); customValve.setPrefix("localhost_access_log"); customValve.setSuffix(".txt"); customValve.setEnabled(true); // 将自定义Valve添加到Tomcat引擎 factory.addEngineValves(customValve); }; } }
3. 在Controller中获取日志
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import java.util.List; @RestController public class AccessLogController { @GetMapping("/access-logs") public List<String> getAccessLogs() { return CustomAccessLogValve.getAccessLogs(); } }
注意事项
- 内存存储的日志仅在当前应用节点有效,集群环境下需将日志持久化到共享存储或数据库
- 自定义Valve的配置需与
application.properties中的Tomcat日志配置保持一致,避免重复或冲突
内容的提问来源于stack exchange,提问作者YourDaddyProgrammer
相关产品推荐
相关产品推荐

