本文目录导读:

- 第一步:止血与快速定位(优先做这个)
- 第二步:高效调试线上问题(必须“隐身”调试)
- 第三步:针对不同问题的专项排查
- 第四步:运维级救命方案(应对难缠问题)
- 必须遵守的“红线”清单(防止二次事故)
- 最佳实践总结(一句话)
调试线上 PHP 应用是高风险操作,核心原则是 “先止血,再查因”,且绝对不能在正式环境直接开启 display_errors(会把报错信息暴露给用户),以下是分场景的稳妥方案:
第一步:止血与快速定位(优先做这个)
线上首要任务是快速定位错误日志,立即恢复服务。
查看错误日志(最安全、最推荐)
不要打印到页面,看日志文件。
- 查看位置(根据你的配置变动):
- LNMP/LAMP:
/var/log/nginx/error.log和/var/log/php-fpm/www-error.log(或php_errors.log) - 宝塔面板:
/www/wwwlogs/站点名.error.log
- LNMP/LAMP:
- 实时跟踪(立刻看最新报错):
tail -f /var/log/php-fpm/www-error.log | grep -i "error\|fatal" --color
开启“仅写日志”模式(临时应急)
如果日志没开,快速修改 php.ini(改完重载 PHP-FPM):
display_errors = Off # 必须关闭,防止信息泄露 log_errors = On error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT error_log = /var/log/php-fpm/php-error.log # 确保目录可写
第二步:高效调试线上问题(必须“隐身”调试)
如果日志模糊,需要主动调试,绝对不要用 var_dump + 浏览器访问,用以下“隐身”技术:
方案 A:使用调试日志函数(最通用)
写一个临时脚本,不输出到浏览器,只写文件。
核心技巧:永远不要直接访问 debug.php,要在代码里加IP白名单限制或密钥参数。
<?php
// debug_tmp.php (使用后立即删除!)
if ($_SERVER['REMOTE_ADDR'] !== '你的固定IP') {
http_response_code(404); // 假装不存在
exit;
}
if ($_SERVER['HTTP_X_AUTH_KEY'] !== '你的秘钥') {
exit('404');
}
// 开启严格错误捕获
error_reporting(E_ALL);
ini_set('display_errors', 'On');
ini_set('log_errors', 'On');
// 测试点1:检查某个变量
$data = SomeClass::getData();
file_put_contents('/tmp/debug.log',
date('Y-m-d H:i:s') . ' - 变量值: ' . var_export($data, true) . "\n",
FILE_APPEND);
// 测试点2:检查某段逻辑耗时(性能分析)
$start = microtime(true);
// 执行你的业务代码...
file_put_contents('/tmp/debug.log', '耗时:'. (microtime(true)-$start) . '秒', FILE_APPEND);
?>
然后命令行看日志:
cat /tmp/debug.log | tail -20
方案 B:开启远程调试(Xdebug 高级用法,不碰代码)
最适合“病发时”抓取堆栈,通过 IDE 断点调试,但需配置:
- 服务器上临时开启 Xdebug 扩展(确认已安装,
php -m | grep xdebug)。 - 在
php.ini临时加入(仅调试时):zend_extension=xdebug.so xdebug.mode = debug xdebug.client_host = 你的本机IP xdebug.client_port = 9003 xdebug.start_with_request = yes
- 本机 PHPStorm 开启“Start Listening for PHP Debug Connections”。
- 访问线上 URL 触发,IDE 会中断在首个断点,整个过程用户无感知,数据不会打印到网页。
风险警告:使用完必须立即删除
xdebug.start_with_request = yes并重载,否则所有请求都会阻塞等待调试器连接(导致站点崩溃)。
第三步:针对不同问题的专项排查
如果是“白屏/500” (常见于 Fatal Error)
- 快速定位:直接看第一步的日志文件。
- 高级技巧:在入口文件 (
index.php) 最顶部临时加:register_shutdown_function(function() { $error = error_get_last(); if ($error && in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR])) { file_put_contents('/tmp/fatal.log', print_r($error, true), FILE_APPEND); } });重启后,访问一次报错页面,然后看
/tmp/fatal.log。
如果是“偶发性 bug” (例如并发、特定用户)
核心方法:使用日志记录请求上下文——不要猜,要记录。 在控制器入口加临时代码:
// 在业务第一行
file_put_contents('/tmp/ctx.log',
json_encode(['user_id' => $userId, 'get' => $_GET, 'post' => $_POST, 'time' => date('Y-m-d H:i:s.u')]) . "\n",
FILE_APPEND);
复现一次后,对比正常与异常日志的差异。
如果是“慢查询/SQL 问题”
开启 MySQL 慢查询日志(不用动 PHP 代码):
SET GLOBAL slow_query_log = 'ON'; SET GLOBAL long_query_time = 1; -- 超过1秒就记
然后看 /var/log/mysql/mysql-slow.log,再配合 EXPLAIN 分析。
第四步:运维级救命方案(应对难缠问题)
方案 C:线上流量复制/灰度
- Nginx 与访问日志:把线上真实请求日志(
access.log)取下来,用go-replay或tcpcopy把流量复制到测试环境,在测试环境加日志调试,不影响线上。 - SQL 审计:用
pt-query-digest分析线上慢查询。
方案 D:使用 APM 工具(不碰代码,可视化)
如果允许装扩展,优先装:
- APM:阿里云 ARMS / 腾讯云 APM / 开源 SkyWalking,能自动抓 Function 调用链、错误堆栈、SQL 耗时。强烈推荐线上装这个,成本最低,收益最高。
必须遵守的“红线”清单(防止二次事故)
| 操作 | 是否允许 | 原因 |
|---|---|---|
线上开启 display_errors=On |
❌️ 严禁 | 泄露真实路径、数据库密码,引发安全风险 |
修改代码后不重启 php-fpm |
⚠️ 看情况 | 若用了 opcache,需 kill -USR2 重载 |
直接在生产环境用 composer dump-autoload |
⚠️ 谨慎 | 可能触发类映射冲突,建议先备份 |
| 删除临时调试文件 | ✅ 必须 | 忘记删除 = 留后门,可能导致恶意攻击 |
| 回滚代码而非修补 | ✅ 推荐 | 如果改了几行没头绪,先回滚到上一个稳定版本,再慢慢分析 |
最佳实践总结(一句话)
最好的线上调试,是永远不调试——通过完善的日志和 APM 监控避免被动排查。 如果必须查,日志是基本盘,IP白名单+临时文件是武器,Xdebug断点是大招,回滚代码是底线。
如果问题特别棘手且无日志,终极手段:在测试环境复现,用同样版本代码+数据库副本,通过 debug_backtrace() 逐步打印(但必须加 IP 限制)。