主流中间件与语言框架的默认日志实测梳理
做应急响应,日志是还原攻击链的第一手材料。可真正上手翻的时候会发现,”日志到底有没有、在哪、什么格式、怎么开”,各家中间件和框架的答案差得很远,有的开箱就记而且落盘,有的默认根本不记访问日志,还有的在容器里只往 docker logs 写、文件里一片空白。
这篇把常见的 Web 中间件和语言框架挨个过了一遍。结论都是在一台香港的云主机上用 vulhub 把环境真实拉起来、发一批请求、再回头看日志得出的。有一条规律贯穿始终,先说在前面:记”谁访问了哪个 URL”是中间件/Web 服务器的活,框架大多不掺和访问日志,只记自己应用层的错误和调试。
中间件
| 中间件 | 访问日志默认 | 运行/错误日志默认 | 默认日志路径 | 访问日志格式 |
|---|---|---|---|---|
| Nginx | 开 | 开 | /var/log/nginx/{access,error}.log | 内置 main(≈combined) |
| Apache httpd | 开 | 开 | /var/log/apache2/(Debian)、/var/log/httpd/(RHEL) | combined |
| Tomcat | 开(Valve) | 开(JULI) | $CATALINA_HOME/logs/ | common(不含 UA/Referer) |
| WebLogic | 开 | 开 | domains/<域>/servers/<服务>/logs/ | common(CLF) |
| JBoss (AS/EAP) | 默认关 | 开 | server/<profile>/log/ 或 standalone/log/ | 需手动开 Valve |
| Jetty | 默认关 | 仅 stdout | $JETTY_BASE/logs/ | 需 –add-module=requestlog |
| GlassFish | 默认关 | 开(server.log) | domains/domain1/logs/ | 需 asadmin 开启 |
| IIS(Windows) | 开 | 开(HTTPERR) | %SystemDrive%\inetpub\logs\LogFiles\ | W3C Extended |
JBoss、Jetty、GlassFish 默认都不记 HTTP 访问日志,等出了事才发现回溯不了攻击者的请求;容器里 Nginx、Apache 的日志文件常常是软链到 /dev/stdout、/dev/stderr 的,只进 docker logs,容器一删就没;Tomcat 默认的访问日志用 common 格式,不带 User-Agent 和 Referer,溯源时缺一大块指纹。这几点后面各自展开。
Nginx
Nginx 的日志基本不用操心,访问日志和错误日志默认都开着。用 nginx -T 把生效的配置导出来,能看到这两行:
error_log /var/log/nginx/error.log warn;
access_log /var/log/nginx/access.log main;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
main 是 Nginx 内置的默认格式,长得跟 Apache 的 combined 很像,区别是结尾多了个 $http_x_forwarded_for。过反向代理的时候,真实客户端 IP 就藏在这个字段里,别只盯着最前面的 $remote_addr。实测一行是这样的:
172.18.0.1 - - [09/Jul/2026:04:03:07 +0000] "GET / HTTP/1.1" 302 161 "-" "curl/7.81.0" "-"
字段顺序:客户端 IP、用户、时间、请求行、状态码、响应字节、Referer、User-Agent、X-Forwarded-For。下面这张是同一批请求在 CVM 上跑出来的真实访问日志,注意 wp-login.php 那行结尾的 XFF 字段带出了伪造的来源 IP:

apt/yum 装的落在 /var/log/nginx/,源码编译的默认在 /usr/local/nginx/logs/。
值得注意的是容器。官方 Nginx 镜像把日志文件软链到了标准输出:
access.log -> /dev/stdout error.log -> /dev/stderr
也就是说进容器 cat /var/log/nginx/access.log,看到的是空的,日志全跑 docker logs 里去了,而且不落盘、容器删了就没。排查时如果发现 /var/log/nginx/ 空空如也,先 readlink -f 确认下是不是软链,是的话就转去 docker logs,或者宿主机的 /var/lib/docker/containers/<id>/<id>-json.log。
想让日志回到文件也简单,在 nginx.conf 的 http/server/location 块里把 access_log 显式指向真实路径就行:
access_log /var/log/nginx/access.log main; error_log /var/log/nginx/error.log warn; # 级别 debug<info<notice<warn<error<crit
Apache httpd
Apache 跟 Nginx 一样,ErrorLog 和 CustomLog 默认都开,而且访问日志默认就是信息最全的 combined 格式。实测镜像 Apache/2.4.10 (Debian),生效的配置是:
# apache2.conf
ErrorLog ${APACHE_LOG_DIR}/error.log
LogFormat "%h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" combined
LogFormat "%h %l %u %t \"%r\" %>s %O" common
# 000-default.conf
CustomLog ${APACHE_LOG_DIR}/access.log combined
Apache 有个坑跟别人不一样——日志路径随发行版变,应急时得心里有数:Debian/Ubuntu 在 /var/log/apache2/(${APACHE_LOG_DIR} 指向这里),RHEL/CentOS 在 /var/log/httpd/(文件名还是 access_log、error_log 不带点),源码编译的在 /usr/local/apache2/logs/。
combined 格式带 User-Agent,这一点在实测里体现得很直接——下面这张日志能清楚看到 sqlmap/1.7、Hello-World/Nmap 这些扫描器的指纹,而同一批请求打到 Tomcat 上就全丢了(下一节会对比):

容器化的处理跟 Nginx 一模一样,官方镜像把 access.log 软链到 /dev/stdout、error.log 软链到 /dev/stderr,日志进 docker logs。改配置的话,改 httpd.conf(Debian 是 apache2.conf 加 sites-available/*.conf),把 CustomLog/ErrorLog 指到实际路径,apachectl -t && apachectl graceful 重载生效。
Tomcat
Tomcat 的访问日志默认是开的,但开得有点”缩水”。标准 server.xml 的 <Host> 段里默认带着这个 Valve:
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
prefix="localhost_access_log" suffix=".txt"
pattern="%h %l %u %t "%r" %s %b" />
问题就出在 pattern 上——默认用的是 common,%h %l %u %t “%r” %s %b,不记 User-Agent 也不记 Referer。应急时想从 UA 判断是不是扫描器、是什么工具,Tomcat 的默认访问日志给不了。这是它跟 Apache combined 最实在的差距,建议线上统一把 pattern 换成带 UA/Referer 的 combined。
运行日志那边由 JULI 管,conf/logging.properties 里配了一串 AsyncFileHandler,落在 $CATALINA_HOME/logs/ 下(镜像里是 /usr/local/tomcat/logs/),实测目录长这样:
catalina.2026-07-09.log # 引擎/容器运行日志 localhost.2026-07-09.log # 应用相关 manager./host-manager.*.log # 管理台 localhost_access_log.2026-07-09.txt # HTTP 访问日志
这里还有个容易找错的地方:catalina.sh run(前台启动,容器里都是这种)不会生成 catalina.out,只有 catalina.sh start(后台)才会把控制台重定向过去。实测容器的 logs/ 里就只有 catalina.<date>.log,没有 catalina.out,别在容器里白费劲找它。
下面是访问日志的实测,能直观看到 common 格式的短板——一整批请求下来,sqlmap、Nmap 这些 UA 全不见了:

换成 combined 只要改 server.xml 的 Valve pattern:
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
prefix="localhost_access_log" suffix=".txt"
pattern="%h %l %u %t "%r" %s %b "%{Referer}i" "%{User-Agent}i"" />
WebLogic
WebLogic 的日志默认相当齐全,服务器日志、域日志、HTTP 访问日志都开着。实测 10.3.6.0,日志集中在域目录下的这个位置:
domains/base_domain/servers/AdminServer/logs/ ├── AdminServer.log # 服务器日志 ├── access.log # HTTP 访问日志 ├── base_domain.log # 域日志 └── ...
AdminServer.log 用的是 WebLogic 那套很有辨识度的 ####<…> 格式,排查反序列化(CVE-2017-10271、CVE-2018-2628 这类)的时候,异常栈基本都落在这里:
####<Jul 9, 2026 4:09:37 AM UTC> <Info> <EJB> <6d1cfbc80606> <AdminServer> <...> <<WLS Kernel>> <> <> <1783570177771> <BEA-010009> <EJB Deployed EJB with JNDI name ejb.mgmt.MEJB.>
尖括号从左到右依次是:时间、级别、子系统、机器名、服务器名、线程、用户、(两个保留位)、事务 ID、消息 ID、消息正文。访问日志 access.log 默认是 common(CLF)格式,按大小滚动。要调格式或确认开关,在控制台的 环境 > 服务器 > <服务器> > 日志记录 > HTTP 里,或者直接改 config.xml 的 <web-server-log>。
JBoss
从这里开始要小心了——JBoss 默认不记 HTTP 访问日志。实测 AS 6.1.0,日志目录只有两个文件:
$JBOSS_HOME/server/default/log/ ├── boot.log # 启动引导 └── server.log # 服务器主日志
server.log 是标准的 log4j 格式,运行事件、异常都在里面:
2026-07-09 04:11:30,050 INFO [org.jboss.bootstrap.impl.base.server.AbstractServer] (Thread-2) JBossAS [6.1.0.Final "Neo"] Started in 13s:262ms
但全盘 find 下来,没有任何 *access*log* 文件。这意味着 JBoss 反序列化、JMXInvokerServlet、JMX Console 部署 WAR 这类打 Web 层的攻击,如果事先没手动开访问日志,事后几乎无法从请求层面还原。这是 JBoss 应急最大的一个盲区。
日志路径也随版本变:AS 5/6 在 server/<profile>/log/(profile 常见是 default),EAP 6+ 和 WildFly 挪到了 standalone/log/server.log。开访问日志的方式也跟着变:
- AS 6.x(jbossweb,基于 Tomcat):在 server/default/deploy/jbossweb.sar/server.xml 的 <Host> 里加 AccessLogValve,pattern=”combined”。
- WildFly / EAP 7(undertow):standalone.xml 的 undertow 子系统里加 <access-log pattern=”combined” …/>,或用 jboss-cli 执行 /subsystem=undertow/server=default-server/host=default-host/setting=access-log:add(pattern=”combined”)。
Jetty
Jetty 的情况和 JBoss 类似,访问日志默认也是关的,而且连运行日志默认都只往 stdout 走。实测 9.4.40,JETTY_BASE=/opt/jetty,start.ini 里启用的模块是:
server, jsp, resources, deploy, jstl, websocket, http, servlets
没有 requestlog,也没有 console-capture。对应地,logs/ 目录里只有一个 0 字节的占位文件 .donotdelete,一条访问日志都没写。也就是说 Jetty(CVE-2021-34429 这类)默认部署下,磁盘上基本没留下什么可查的东西。
好在开起来很简单,Jetty 是模块化配置,一条命令的事:
cd $JETTY_BASE java -jar $JETTY_HOME/start.jar --add-module=requestlog # 访问日志 -> logs/yyyy_mm_dd.request.log,NCSA/combined java -jar $JETTY_HOME/start.jar --add-module=console-capture # 运行日志落盘 -> logs/yyyy_mm_dd.jetty.log
可选的 requestlog 模块有几种(customrequestlog、requestlog、logback-access),java -jar start.jar –list-modules=requestlog 能列出来。
GlassFish
GlassFish 的服务器日志默认开,访问日志默认关。实测 4.1,域目录下:
domains/domain1/logs/ ├── server.log # 服务器日志,默认开 └── server.log.lck
注意这里没有 access/ 子目录——访问日志默认不生成。server.log 用的是 GlassFish 的统一日志格式(ULF),一眼就能认出来:
[2026-07-09T04:13:37.585+0000] [glassfish 4.1] [INFO] [NCLS-JMX-00025] [javax.enterprise.system.jmx] [tid: _ThreadID=53 _ThreadName=Thread-13] [timeMillis: 1783570417585] [levelValue: 800] [[ 消息正文 ]]
要开访问日志,用 asadmin:
asadmin set configs.config.server-config.http-service.access-logging-enabled=true asadmin restart-domain domain1 # 开启后落在 logs/access/server_access_log.txt
IIS
IIS 只能跑在 Windows 上,没法用 vulhub 复现,这一节是照微软文档来的,没有实测——但它太常见,应急绕不开,还是列一下。
IIS 的访问日志默认是开的,站点日志在 %SystemDrive%\inetpub\logs\LogFiles\W3SVC<站点ID>\,文件名形如 u_exYYMMDD.log,默认按天(UTC)滚动。此外 HTTP.sys 的错误日志在 %windir%\System32\LogFiles\HTTPERR\。
格式默认是 W3C Extended,空格分隔,开头有 #Fields: 声明用了哪些字段:
#Fields: date time s-ip cs-method cs-uri-stem cs-uri-query s-port cs-username c-ip cs(User-Agent) cs(Referer) sc-status sc-substatus sc-win32-status time-taken 2026-07-09 04:20:11 10.0.0.5 GET /index.aspx - 80 - 1.2.3.4 Mozilla/5.0 - 200 0 0 15
有个细节要盯一下:默认字段里虽然含 cs(User-Agent),但不少加固过的服务器会把字段裁掉,应急时先确认 cs(User-Agent)、cs(Referer)、cs-uri-query 还在不在——查 webshell 常靠 cs-uri-stem 配 sc-status=200 再筛可疑的 .aspx,字段缺了就难受。配置在 IIS 管理器的”日志”功能,或 %windir%\System32\inetsrv\config\applicationHost.config。
容器环境的几个共性问题
上面反复提到容器,这里单独收一下,实战里都会遇到:
- 日志被重定向到 stdout/stderr。 Nginx、Apache 官方镜像把 access/error 软链到了 /dev/stdout、/dev/stderr。查的时候先 docker logs,落盘的话在宿主机 /var/lib/docker/containers/<id>/<id>-json.log,拿不准就 readlink -f 确认软链。
- 日志随容器销毁。 没挂 volume 的容器,docker rm 之后日志全没。生产环境该把日志目录挂出来,或者直接接日志系统(ELK/Loki/SLS)。
- 时区对不上。 容器多是 UTC,宿主机可能是 CST(+08:00),拉时间线的时候记得换算,实测里容器日志都是 UTC。
- 前台模式没有 catalina.out。 Tomcat catalina.sh run、Jetty 前台启动,运行日志只在标准输出,别在文件里找。
一份排查时的对照清单
- 先认清中间件类型和版本:nginx -v、httpd -v,或看 catalina.jar、server.log 首行、weblogic.version。
- 确认访问日志是不是真在记——JBoss、Jetty、GlassFish 默认不记,别默认”跑着就有日志”。
- 确认格式带不带 UA/Referer——Tomcat 的 common、被裁过字段的 IIS 都会缺,影响溯源。
- 容器环境先看 docker logs 和宿主机 json-log,再进容器翻文件。
- 留意轮转和留存:不少是按天/按大小滚动,老日志可能已经被覆盖(logrotate、Valve 的 rotatable、WebLogic 的按大小滚动)。
- 交叉着看:访问日志(谁、什么请求)配运行/错误日志(反序列化栈、500、类加载异常)再配系统日志(auth.log、进程)。
语言框架
框架这部分单独说明一下:结论由几个并行任务分别在同一台机器上把对应 vulhub 环境拉起来实测。过程中发现一个有意思的现象——vulhub 里环境的实际跑法,跟”教科书上这个框架默认怎样”经常对不上。比如 Spring4Shell 那个环境其实是 Spring MVC 的 WAR 跑在独立 Tomcat 上、并不是 Spring Boot;Flask 那个用的是 gunicorn 而不是自带的开发服务器。下面对”vulhub 实测的跑法”和”这个框架的通用默认”都做了区分。
| 框架 | 语言 | 应用日志默认落盘 | 框架记访问日志吗 | 默认日志位置 | 要点 |
|---|---|---|---|---|---|
| Spring Boot | Java | 否,仅 stdout | 内嵌 Tomcat access 默认关 | 需配 logging.file.* | 看 docker logs/journald |
| Spring MVC on Tomcat | Java | 是 | 是,Tomcat Valve 默认开 | $CATALINA_HOME/logs/ | 独立 Tomcat 行为 |
| Struts2 | Java | 看应用 log4j 配置 | 看承载容器 | 应用配置决定 | 无配置就只有 stdout |
| Django | Python | 否,仅 stderr | runserver 把请求行打到 stderr | 需配 LOGGING | DEBUG=True 会回显异常页 |
| Flask | Python | 否,仅 stderr | werkzeug 打 stderr;gunicorn 默认关 | 需自己配 | 先看是什么在跑 |
| Laravel | PHP | 是 | 否,交给 Web 服务器 | storage/logs/laravel.log | 看 LOG_CHANNEL |
| ThinkPHP | PHP | 是 | 否,交给 Web 服务器 | runtime/log/YYYYMM/DD.log | debug 关时很稀疏 |
| Rails | Ruby | 是 | 请求块自带访问信息 | log/<环境>.log | dev 下同时广播 stderr |
有几个反常识的点,先摊开:Spring Boot、Django、Flask 的开发跑法默认不落盘,日志只在 stdout/stderr,进容器 find *.log 是白找,得看 docker logs,而且容器一删就没。框架的应用日志跟访问日志也不是一回事——Laravel、ThinkPHP 落盘的那些只记应用自己的错误和调试,记不到”谁打了哪个 URL”,攻击者的 payload URL 往往只在 Web 服务器日志里,两头得一起看。还有 debug 开关的影响:ThinkPHP app_debug=false(生产默认)时日志少得可怜,别看 runtime 日志干净就下结论;Django、Rails 的 development 模式则会把异常栈直接回显到页面,那是信息泄露。
Spring(Boot 与 MVC on Tomcat)
vulhub 的 spring/CVE-2022-22965(Spring4Shell)实测下来,跑的是 Spring Framework 5.3.17 的 WAR,部署在独立的 Apache Tomcat 8.5.77 上,用 catalina.sh run 启动,并不是 Spring Boot。这反而正好把两种最常见的形态都覆盖了,分开说。
形态一:Spring MVC 的 WAR 跑在独立 Tomcat 上(就是 vulhub 这个)。 日志行为跟独立 Tomcat 完全一致:访问日志默认开(server.xml 里默认有 AccessLogValve),实测生成了 logs/localhost_access_log.*.txt;应用/容器日志既落盘(catalina.<date>.log、localhost.<date>.log)也进 stdout。样例:
# 访问日志(common) 127.0.0.1 - - [09/Jul/2026:06:40:54 +0000] "GET /notexist_test HTTP/1.1" 404 682 # localhost.log —— DispatcherServlet 没匹配到路由 09-Jul-2026 06:40:54.443 WARNING [http-nio-8080-exec-2] org.springframework.web.servlet.DispatcherServlet.noHandlerFound No mapping for GET /notexist_test
形态二:Spring Boot(内嵌 Tomcat),跟形态一恰好相反。 应用日志默认只往控制台打,不写文件,除非在 application.properties 里配 logging.file.name 或 logging.file.path;内嵌 Tomcat 的访问日志默认也是关的,要手动开:
logging.file.name=/var/log/app/app.log server.tomcat.accesslog.enabled=true server.tomcat.accesslog.pattern=combined
所以排查 Spring 应用,第一步是分清它是”独立 Tomcat 跑 WAR”还是”Spring Boot 内嵌 Tomcat”。前者日志默认落在 $CATALINA_HOME/logs/、访问日志现成的;后者默认只有 stdout(容器看 docker logs、传统部署看 journald),访问日志默认缺失,得确认有没有配过 logging.file.* 或 server.tomcat.accesslog.*。
Struts2
vulhub 的 struts2/s2-045 实测跑的是 Struts 2.3.30,承载在 Maven Jetty 插件内嵌的 Jetty 9.2.11 上(mvn jetty:run),不是 Tomcat。这个环境默认几乎不留日志:Jetty 启动时 stdout 直接打了一行 [WARNING] !RequestLog,明说没配访问日志;类路径上只有 log4j 1.x、连配置文件都没有,所以 Struts2 自己的运行日志跟着 Maven/Jetty 控制台一起进了 docker logs。发几个请求过去,日志里一条访问记录都没有。
[INFO] Choosing bean (jakarta) for (org.apache.struts2.dispatcher.multipart.MultiPartRequest)
[WARNING] !RequestLog
[INFO] Started ServerConnector@...{HTTP/1.1}{0.0.0.0:8080}
生产里 Struts2 更多是跑在 Tomcat 或 WebLogic 上,那就另说了:访问日志由承载容器决定(Tomcat 的 AccessLogValve 默认开,见前面),Struts2 自身的运行日志会不会落盘,完全取决于应用里有没有配 log4j2/logback——裸应用不配就只有容器 stdout。要留痕,应用层引 log4j2 加 log4j2.xml 配个 RollingFileAppender,容器层把访问日志开出来。
落到应急上,S2-045 这类攻击(payload 在 Content-Type 头里)在默认环境下磁盘上可能一条请求记录都没有,因为访问日志默认不记请求头。取证得靠前置的 Nginx/WAF(把 $http_content_type 记下来),别指望 Struts2 或 Jetty 的默认日志。
Django
vulhub 的 django/CVE-2019-14234 实测是 Django 2.2.3,用 manage.py runserver 开发服务器跑的,DEBUG=True,没配 LOGGING。
Django 框架本身不产生访问日志,当前这些请求行是 runserver 打到 stderr 上的(实测 stdout 一条没有、stderr 有三条)。格式很简陋,只有时间、请求行、状态码、字节数,没有客户端 IP、没有 UA、没有耗时,信息量远不如 Nginx 的 access log:
[09/Jul/2026 01:44:53] "GET /notexist_test HTTP/1.1" 404 1975 [09/Jul/2026 01:44:53] "GET /admin/ HTTP/1.1" 302 0

应用和错误日志同理,默认 LOGGING 没有 file handler,这个项目又完全没自定义,所以全走 console(stderr),容器内找不到任何 .log 文件。加上 DEBUG=True,异常会直接以完整 traceback 页回显给访问者,SECRET_KEY、源码、数据库结构全露出来——这本身就是高危配置。
要落盘就在 settings 的 LOGGING 里加 FileHandler/RotatingFileHandler;生产的访问日志应该换 gunicorn(–access-logfile)或 uwsgi(–logto),别拿 runserver 当访问日志用。排查时取证只能靠 docker logs 或宿主机 json-log,进容器翻文件是白费。
Flask
vulhub 的 flask/ssti 实测是 Flask 1.1.1 配 gunicorn 20.0.0(4 个 worker,降权到 www-data),不是 werkzeug 开发服务器。启动命令里带了 –access-logfile -,- 就是 stdout,所以访问日志(类 combined 格式)打到了标准输出:
127.0.0.1 - - [09/Jul/2026:06:41:29 +0000] "GET /notexist HTTP/1.1" 404 232 "-" "curl/7.64.0"

这里要提醒一句:gunicorn 默认是不开访问日志的,这个环境是启动命令主动加了 –access-logfile 才有。要是管理员没加,就一条访问记录都没有。而如果换成 Flask 自带的开发服务器(app.run()),werkzeug 会默认把请求行打到 stderr,格式还不太一样(时间格式不同、不带 UA)。所以排查 Flask,第一件事是 ss -tlnp/ps 看清楚到底是 werkzeug 还是 gunicorn/uwsgi 在跑,这决定了访问日志有没有、什么格式、有没有 UA。
应用和错误日志默认都不落盘,走 stderr。要落盘就给 app.logger 加个 RotatingFileHandler,gunicorn 用 –access-logfile/–error-logfile 指到文件。SSTI 的 payload({{…}})在访问日志里会以 URL 或查询串的形式出现,但 POST body 和请求头默认不记,证据可能很薄。
Laravel
Laravel 是这批框架里日志最规矩的——应用日志默认就落盘,写在 storage/logs/laravel.log。实测 Laravel 8.26.1(Apache + mod_php,项目根是 /var/www,注意不是 /var/www/html),默认的 channel 链是 stack -> single,触发一次 Log:: 调用就生成文件:
[2026-07-09 06:43:00] local.INFO: IR test access from responder {"src":"127.0.0.1"}
[2026-07-09 06:43:00] local.ERROR: simulated failure

格式是 Monolog 的默认样式:[时间] env.LEVEL: message {context},local 就是 .env 里的 APP_ENV。落不落盘、落到哪,由 .env 的 LOG_CHANNEL 决定:默认的 single/stack 写 laravel.log 单文件一直追加;改成 daily 就按天切成 laravel-YYYY-MM-DD.log(LOG_DAILY_DAYS 控制保留天数,默认 14);
要注意 Laravel 只记应用自己的东西,不记 HTTP 访问日志,那是前面 Apache/Nginx 的活。所以排查 Laravel 异常先看 storage/logs/laravel.log(先用 find / -name artisan 定位项目根),按 .ERROR/.CRITICAL 过滤;但”谁访问了哪个 URL”得回去看 Web 服务器日志。像 CVE-2021-3129(Ignition RCE)打的是 /_ignition/execute-solution,应用异常在 laravel.log 留痕,配合 Apache 里对这个路径的 POST 请求就能对上。
ThinkPHP
ThinkPHP 也是默认落盘的,但有个坑得单独讲。实测 TP 5.0.23(Apache + mod_php,项目根 /var/www,DocumentRoot 是 /var/www/public),日志默认写在 <项目根>/runtime/log/YYYYMM/DD.log,实测生成的是 /var/www/runtime/log/202607/09.log,按月分目录、按天切文件。
坑在于不是每个请求都写日志。app_debug=false(镜像的生产默认)时,普通请求和 404 都不写——404 走的 HttpException 在忽略名单里,只有真正的错误或未捕获异常才以 [ error ] 落盘。这就解释了为什么单纯打个 ?s=/notexist 在日志里看不到。而 app_debug=true 时又反过来,每个请求都记满满一屏,请求信息、运行时统计、trace 全有:
# app_debug=false,只有错误落盘 [ 2026-07-09T14:45:46+08:00 ] 127.0.0.1 GET 127.0.0.1/index.php?s=captcha&test=1 [ error ] [0]致命错误: Call to undefined function think\captcha\imagecreate()
反过来,app_debug=true 时每个请求都记得满满一屏。下面这张是 debug 打开后一次请求的完整记录——请求头、路由、运行时统计全有,注意 PARAM 里的注入参数 admin’ or 1=1 被原样落了下来:

版本之间路径也不一样:TP5.0 是 runtime/log/YYYYMM/DD.log,老的 TP3.2 在 Application/Runtime/Logs/<模块>/YY_MM_DD.log,排查老项目按 3.2 的路子找。落到应急上,TP5.0.23 的那个经典 RCE(?s=index/\think\app/invokefunction…)如果正常执行没报错,runtime 日志里是不留痕的,payload URL 只在 Apache/Nginx 访问日志里——那才是最可靠的证据。runtime 目录 www-data 可写,也要防着攻击者清日志。
Rails
Rails 的请求日志默认落盘,写在项目根的 log/<环境>.log。实测 Rails 5.2.2(Puma,development 模式,RAILS_ENV 为空),日志在 /usr/src/blog/log/development.log(生产就是 production.log),不用任何配置就有。
它的格式是每个请求一个多行”块”,Started → Processing → Completed,块里方法、路径、客户端 IP、状态码、耗时都有——Rails 的请求日志本身就相当于一份访问日志,只是写在 log/<环境>.log 里、不是独立的 access.log。404 在 development 下还会带出完整异常栈:
Started GET "/robots" for 127.0.0.1 at 2026-07-09 06:43:01 +0000 Processing by VulhubController#index as */* Completed 200 OK in 278ms (Views: 277.1ms | ActiveRecord: 0.0ms) Started GET "/notexist" for 127.0.0.1 at 2026-07-09 06:43:01 +0000 ActionController::RoutingError (No route matches [GET] "/notexist"):

有一点实测时容易看走眼:development 下用 rails server 前台启动,Rails 会把日志同时广播一份到 stderr(方便盯前台),但主 logger 还是写文件的那个,落盘才是权威位置。到了容器/云原生场景,生产环境经常会配成 config.logger = ActiveSupport::Logger.new(STDOUT) 或设 RAILS_LOG_TO_STDOUT 把日志改到 stdout,这时落盘文件反而是空的,得同时看 docker logs,别被前台那份广播骗了。排查时先看项目根的 log/<环境>.log,顺手确认下 RAILS_ENV——空的就是 development,异常栈会回显、信息泄露风险高。
框架部分小结
- 先判断运行栈:什么框架 + 什么容器/WSGI(Tomcat/Jetty/gunicorn/uwsgi/Puma/runserver),这决定了日志在哪、有没有访问日志。
- 落盘的框架直接找文件:Laravel storage/logs/laravel.log、ThinkPHP runtime/log/YYYYMM/DD.log、Rails log/<环境>.log、独立 Tomcat 上的 Spring MVC 在 $CATALINA_HOME/logs/。
- 只往 stdout 走的看 docker logs/宿主 json-log/journald:Spring Boot、Django、Flask(dev)、没配置的 Struts2。
- 框架应用日志不等于访问日志,攻击者的 payload URL 通常只在 Web 服务器访问日志里,记得回第一部分查 Nginx/Apache。
- 留意 debug 开关:ThinkPHP app_debug=false 日志稀疏;Django/Rails 的 development 会回显异常栈。
- 日志既容易丢也容易被清:容器 stdout 随 down 销毁,落盘日志攻击者(www-data)也可能清空,取证优先固定、外发集中采集。
微信赞赏
支付宝赞赏
发表评论