主流中间件与语言框架的默认日志实测梳理

主流中间件与语言框架的默认日志实测梳理

做应急响应,日志是还原攻击链的第一手材料。可真正上手翻的时候会发现,”日志到底有没有、在哪、什么格式、怎么开”,各家中间件和框架的答案差得很远,有的开箱就记而且落盘,有的默认根本不记访问日志,还有的在容器里只往 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)
WebLogicdomains/<域>/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 &quot;%r&quot; %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 &quot;%r&quot; %s %b &quot;%{Referer}i&quot; &quot;%{User-Agent}i&quot;" />

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     # 访问日志 -&gt; logs/yyyy_mm_dd.request.log,NCSA/combined
java -jar $JETTY_HOME/start.jar --add-module=console-capture # 运行日志落盘 -&gt; 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。

容器环境的几个共性问题

上面反复提到容器,这里单独收一下,实战里都会遇到:

  1. 日志被重定向到 stdout/stderr。 Nginx、Apache 官方镜像把 access/error 软链到了 /dev/stdout、/dev/stderr。查的时候先 docker logs,落盘的话在宿主机 /var/lib/docker/containers/<id>/<id>-json.log,拿不准就 readlink -f 确认软链。
  2. 日志随容器销毁。 没挂 volume 的容器,docker rm 之后日志全没。生产环境该把日志目录挂出来,或者直接接日志系统(ELK/Loki/SLS)。
  3. 时区对不上。 容器多是 UTC,宿主机可能是 CST(+08:00),拉时间线的时候记得换算,实测里容器日志都是 UTC。
  4. 前台模式没有 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 BootJava否,仅 stdout内嵌 Tomcat access 默认关需配 logging.file.*看 docker logs/journald
Spring MVC on TomcatJava是,Tomcat Valve 默认开$CATALINA_HOME/logs/独立 Tomcat 行为
Struts2Java看应用 log4j 配置看承载容器应用配置决定无配置就只有 stdout
DjangoPython否,仅 stderrrunserver 把请求行打到 stderr需配 LOGGINGDEBUG=True 会回显异常页
FlaskPython否,仅 stderrwerkzeug 打 stderr;gunicorn 默认关需自己配先看是什么在跑
LaravelPHP否,交给 Web 服务器storage/logs/laravel.log看 LOG_CHANNEL
ThinkPHPPHP否,交给 Web 服务器runtime/log/YYYYMM/DD.logdebug 关时很稀疏
RailsRuby请求块自带访问信息log/<环境>.logdev 下同时广播 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)也可能清空,取证优先固定、外发集中采集。
赞赏

微信赞赏支付宝赞赏

Zgao

愿有一日,安全圈的师傅们都能用上Zgao写的工具。

发表评论