生产环境日志需确保Web进程可读runtime/log目录,权限不足时用ls -l检查;禁用public/硬链,修改log.path后重启FPM;推荐tail-f+SSE流式推送,关闭output_buffering并用stream_get_line;LogViewer类库需兼容PSR-15;app_debug=false不丢日志,关键在log.level配置。
日志文件权限和路径是否可被Web进程读取
生产环境里
目录默认是 PHP-FPM 用户(如
或
)写入的,但 Web 服务用户未必有读权限。直接用
或
前先确认权限:运行
,确保目录和最新日志文件对 Web 用户是可读的。若用
或
缓存了日志路径,还要清缓存再试。
别把日志放在
下硬链——不安全,且 ThinkPHP 默认不记录到该目录
ThinkPHP 的日志路径由
配置控制,修改后需重启 FPM 才生效(部分版本热加载不刷新句柄)
如果用 Docker,注意宿主机挂载卷的 UID/GID 是否与容器内 Web 用户一致
用 tail -f + SSE 实现实时流式推送(非轮询)
前端每秒发 AJAX 查日志是反模式:既压服务器又延迟高。更稳的做法是后端开一个长期连接,用
持续读取并以 SSE(Server-Sent Events)格式输出。ThinkPHP 本身不内置 SSE 支持,得手动处理响应头和 flush:
必须关闭
(
php
.ini 中设为
或
),否则
无效
比
更可靠——后者在空行或 EOF 时可能阻塞
别在控制器里直接
:权限、超时、僵尸进程都难控;优先用 PHP 原生流操作
LogViewer 类库与 ThinkPHP 6/7 兼容性陷阱
社区常见方案如
或
移植版,在 ThinkPHP 6+ 上容易报错:不是因为功能不行,而是其路由注册方式和中间件生命周期冲突。典型错误是
或空白页无响应。
必须确认类库是否支持 PSR-15 中间件规范(TP6+ 使用);老版本 TP5 的
扩展基本不可用
日志格式要匹配:ThinkPHP 默认用
,而某些 viewer 只认 Laravel 的单行 JSON 格式
若启用日志分片(
> 0),务必让 viewer 支持按时间戳排序读取多个文件,否则只显示当前活跃日志
线上禁用 debug 模式后如何保留关键日志可见性
TP 的
不仅关掉调试栏,还会让
、
等调用静默丢弃——除非你显式配置了
。真正影响日志落地的是
和
,不是
。
Midjourney AI 提示词工具
PHP中文网提供Midjourney AI 提示词在线生成工具,专为解决 AI 绘画“词穷”痛点而生,用户只需输入“一只猫”等简单想法,工具便能利用 AI 智能扩展出包含艺术风格、光影构图、镜头参数及负面提示词的专业级英文 Prompt。它完美兼容 Midjourney V8.1 及 Niji 模型,支持一键复制与参数自动优化,大幅降低创作门槛,是提升出图质量与效率的必备辅助神器。
下载
立即学习
“
PHP免费学习笔记(深入)
”;
生产环境应设
,避免 info 日志刷爆磁盘
(10MB)比无限追加更可控,配合
自动轮转
想临时查某次请求日志?给
加唯一 trace_id,并在日志中打标,再用
快速定位
最麻烦的其实是日志时间戳和服务器时区不一致——ThinkPHP 默认用
,但 Docker 容器里可能仍是 UTC,导致日志时间比 Nginx 访问日志慢 8 小时,排查链路时容易误判。
runtime/logwww-datanginxfile_get_contentstail -fls -l runtime/log/opcacheAPCupublic/log.pathtail -fheader('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
header('X-Accel-Buffering: no');
while (true) {
$line = stream_get_line($fp, 1024, "\n");
if ($line) echo "data: " . json_encode(['line' => $line]) . "\n\n";
ob_flush();
flush();
usleep(500000);
}output_bufferingOff0flush()stream_get_line()fgets()exec('tail -f')arcz/log-viewerrap2hpoutre/laravel-log-viewerClass logviewer does not existthinkphp/log[info] [2024-05-12 10:23:45] app.Debug: ...log.file_sizeapp_debug = falseLog::debug()Log::info()log.levellog.typelog.levelapp_debuglog.level = 'error,notice,warning'log.file_size = 10485760log.max_file = 10Log::record()grep trace_id runtime/log/*.logdate_default_timezone_set('Asia/Shanghai')