TUTORIAL

调试工具与日志规范

调试工具与日志规范

调试工具与日志规范

代码能写出来不算本事,能快速排查出 bug 才是真功夫。排查问题的两大利器是调试工具和日志——前者帮你看清代码运行时的状态,后者帮你还原线上发生的问题。很多新手只会用 var_dump 调试、日志乱写一气,效率低下且上线后两眼一抹黑。本篇讲清建站开发中该用什么调试工具、日志该怎么规范记录。

一、Xdebug:断点调试利器

var_dumpdd() 能看变量值,但碰到复杂逻辑、多层调用、循环里的诡异问题就力不从心。Xdebug 让你像调试桌面程序一样打断点——代码执行到断点处暂停,你可以逐行执行、查看所有变量、追踪函数调用栈。一旦用上就再也回不去 var_dump 了。

# 安装 Xdebug(通过 pecl)
pecl install xdebug

# php.ini 配置
[xdebug]
zend_extension=xdebug
xdebug.mode=debug              # 开启调试模式
xdebug.start_with_request=yes  # 每个请求自动启动调试
xdebug.client_host=127.0.0.1   # IDE 监听地址
xdebug.client_port=9003        # IDE 监听端口
xdebug.log=/tmp/xdebug.log     # 调试日志(排查连接问题用)

# VS Code 配置(.vscode/launch.json)
{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Listen for Xdebug",
      "type": "php",
      "request": "launch",
      "port": 9003,
      "pathMappings": {
        "/var/www/html": "${workspaceFolder}"
      }
    }
  ]
}

# 调试流程:
# 1. VS Code 点"开始调试"(监听 9003 端口)
# 2. 浏览器访问页面(装 Xdebug helper 扩展激活)
# 3. 代码执行到断点处暂停,可查看变量、调用栈
# 4. F10 单步跳过、F11 单步进入、F5 继续执行

Xdebug 调试的三板斧:断点(在某行暂停)、单步(一行一行执行观察变化)、调用栈(看是怎么一层层调用到这里的)。尧图曾有个缓存失效的 bug——代码逻辑看着没问题,但缓存就是不命中。用 Xdebug 单步走一遍,发现是某个中间变量被意外覆盖,肉眼根本看不出来。注意 Xdebug 会拖慢性能,生产环境绝不能开。

二、日志分级与 Monolog

日志不是"想起来了写一条",而是分级别、有结构、可检索的。PSR-3 日志规范定义了八个级别,从紧急到调试,不同级别对应不同严重程度。生产环境只记录 warning 以上,开发环境记录 debug 才有用。PHP 生态里 Monolog 是事实标准,几乎所有框架都用它。

// Monolog 基础用法
use Monolog\Logger;
use Monolog\Handler\RotatingFileHandler;

$logger = new Logger('ldpk');

// 按天滚动日志文件,保留30天(防日志撑爆磁盘)
$logger->pushHandler(new RotatingFileHandler(
  __DIR__ . '/logs/app.log',
  30,                              // 保留30天
  Logger::WARNING                  // 只记录 WARNING 及以上
));

// 八个日志级别(从高到低)
$logger->emergency('数据库连不上,站点不可用');  // EMERGENCY:系统不可用
$logger->alert('支付回调验签失败');              // ALERT:必须立即处理
$logger->critical('订单状态异常: {order}', ['order' => $id]); // CRITICAL
$logger->error('接口调用失败: {msg}', ['msg' => $e->getMessage()]); // ERROR
$logger->warning('缓存命中率低: {rate}%', ['rate' => 30]); // WARNING
$logger->notice('用户登录: {uid}', ['uid' => $userId]);    // NOTICE
$logger->info('文章发布: {id}', ['id' => $postId]);        // INFO
$logger->debug('SQL: {sql}', ['sql' => $query]);           // DEBUG

日志要带"上下文"——光写"订单异常"没用,得带上订单号、用户 ID、异常信息。Monolog 的第二个参数就是上下文,会被序列化成 JSON 跟在日志后面。排查问题时靠 grep 订单号就能把相关日志全捞出来。尧图规定:所有 catch 块必须记日志,且日志必须包含异常堆栈($e->getTraceAsString()),否则排查时只能靠猜。

三、异常处理与上报

生产环境不能把错误直接显示给用户(既难看又泄露信息),要捕获所有未处理异常,记录日志后给用户友好的提示。同时把严重错误上报到监控平台(如 Sentry),第一时间收到告警而非等用户投诉。

// 全局异常处理器
class ExceptionHandler {
  function handle(Throwable $e) {
    // 1. 记录日志(带完整堆栈)
    $this->logger->error('未处理异常: {msg}', [
      'msg'   => $e->getMessage(),
      'file'  => $e->getFile() . ':' . $e->getLine(),
      'trace' => $e->getTraceAsString(),
      'url'   => $_SERVER['REQUEST_URI'] ?? '',
      'uid'   => $_SESSION['user_id'] ?? 0
    ]);

    // 2. 上报到 Sentry(生产环境,实时告警)
    if (env('APP_ENV') === 'production') {
      \Sentry\captureException($e);  // 异步上报,不阻塞
    }

    // 3. 给用户友好提示(不泄露技术细节)
    if ($e instanceof BusinessException) {
      return json(['code' => $e->getCode(), 'msg' => $e->getMessage()]);
    } else {
      return json(['code' => 500, 'msg' => '系统繁忙,请稍后重试']);
    }
  }
}

// 业务异常类(用于可控的业务错误,如"库存不足")
class BusinessException extends RuntimeException {}

// 注册全局处理器
set_exception_handler([new ExceptionHandler(), 'handle']);

区分"业务异常"和"系统异常"很重要——前者是用户操作导致的预期错误(库存不足、余额不够),记日志但不需要告警;后者是代码 bug(空指针、SQL 出错),必须告警让开发者修。不区分的话告警会被业务异常淹没,真正的问题被淹没。尧图用 Sentry 后,线上 bug 平均发现时间从"用户投诉后"缩短到"3 分钟内告警",运维压力大幅下降。这套调试 + 日志 + 异常上报的组合拳,是站点稳定运行的基础保障。

返回教程列表