PHP 怎么调试线上

wen PHP项目 3

本文目录导读:

PHP 怎么调试线上

  1. 第一步:止血与快速定位(优先做这个)
  2. 第二步:高效调试线上问题(必须“隐身”调试)
  3. 第三步:针对不同问题的专项排查
  4. 第四步:运维级救命方案(应对难缠问题)
  5. 必须遵守的“红线”清单(防止二次事故)
  6. 最佳实践总结(一句话)

调试线上 PHP 应用是高风险操作,核心原则是 “先止血,再查因”,且绝对不能在正式环境直接开启 display_errors(会把报错信息暴露给用户),以下是分场景的稳妥方案:

第一步:止血与快速定位(优先做这个)

线上首要任务是快速定位错误日志,立即恢复服务。

查看错误日志(最安全、最推荐)

不要打印到页面,看日志文件。

  • 查看位置(根据你的配置变动):
    • LNMP/LAMP/var/log/nginx/error.log/var/log/php-fpm/www-error.log(或 php_errors.log
    • 宝塔面板/www/wwwlogs/站点名.error.log
  • 实时跟踪(立刻看最新报错):
    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 断点调试,但需配置:

  1. 服务器上临时开启 Xdebug 扩展(确认已安装,php -m | grep xdebug)。
  2. php.ini 临时加入(仅调试时):
    zend_extension=xdebug.so
    xdebug.mode = debug
    xdebug.client_host = 你的本机IP
    xdebug.client_port = 9003
    xdebug.start_with_request = yes
  3. 本机 PHPStorm 开启“Start Listening for PHP Debug Connections”。
  4. 访问线上 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-replaytcpcopy 把流量复制到测试环境,在测试环境加日志调试,不影响线上。
  • 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 限制)。

抱歉,评论功能暂时关闭!