一、为什么错误处理这么重要?
想象一下你正在开发一个电商网站,用户在下单时突然页面白屏了,但没有任何提示。用户不知道是网络问题、库存不足还是系统故障,这种体验有多糟糕?好的错误处理就像贴心的导购员,会明确告诉你"商品已售罄"或"请检查网络连接"。
PHP默认的错误处理其实很"懒"——它可能只在页面上显示一行小字,或者干脆什么都不显示。这就像家里烟雾报警器坏了,着火时你完全不知情。我们需要主动构建错误处理机制,让问题无处可藏。
二、PHP自带的错误处理三板斧
PHP其实提供了不少现成的工具,我们先看看这些"基础装备":
<?php
// 技术栈:PHP 8.2
// 1. 错误报告级别设置(放在入口文件最开头)
error_reporting(E_ALL); // 报告所有错误
ini_set('display_errors', 0); // 不直接显示给用户
ini_set('log_errors', 1); // 开启错误日志
ini_set('error_log', __DIR__.'/app_errors.log'); // 指定日志文件
// 2. 自定义错误处理器
set_error_handler(function($errno, $errstr, $errfile, $errline) {
// 将错误信息格式化为JSON便于分析
$error = [
'time' => date('Y-m-d H:i:s'),
'type' => $errno,
'message' => $errstr,
'file' => $errfile,
'line' => $errline,
'stack' => debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS)
];
file_put_contents(__DIR__.'/error_log.json', json_encode($error)."\n", FILE_APPEND);
return true; // 告诉PHP这个错误我们已经处理了
});
// 3. 致命错误捕获(注册关机函数)
register_shutdown_function(function() {
$error = error_get_last();
if ($error && in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR])) {
// 发送邮件或短信通知开发人员
mail('dev@example.com', '系统崩溃警报', json_encode($error));
}
});
这三个工具配合使用,就像给系统装上了:
- 显微镜(error_reporting):发现所有问题
- 记事本(error_log):记录问题细节
- 警报器(shutdown_function):紧急情况通知
三、异常处理的艺术
如果说错误处理是消防演习,那么异常处理就是应急预案。看这个订单处理的例子:
<?php
// 技术栈:PHP 8.2
class InventoryException extends Exception {
// 自定义库存异常类
public function __construct($message, $sku) {
parent::__construct("商品 {$sku} ".$message);
$this->sku = $sku;
}
}
class OrderService {
public function createOrder($items) {
try {
foreach ($items as $item) {
$this->checkInventory($item['sku'], $item['qty']);
}
// 正常订单处理逻辑...
} catch (InventoryException $e) {
// 库存不足的特殊处理
return [
'success' => false,
'code' => 'INVENTORY_SHORTAGE',
'message' => $e->getMessage(),
'sku' => $e->sku
];
} catch (Exception $e) {
// 其他异常的通用处理
error_log("订单创建失败: ".$e->getMessage());
return [
'success' => false,
'code' => 'SYSTEM_ERROR',
'message' => '系统繁忙,请稍后再试'
];
}
}
private function checkInventory($sku, $qty) {
$stock = $this->queryInventory($sku);
if ($stock < $qty) {
throw new InventoryException('库存不足', $sku);
}
}
}
// 使用示例
$service = new OrderService();
$result = $service->createOrder([
['sku' => 'IPHONE13', 'qty' => 2],
['sku' => 'AIRPODS', 'qty' => 1]
]);
if (!$result['success']) {
// 给用户友好的提示
echo "下单失败原因:".$result['message'];
if ($result['code'] == 'INVENTORY_SHORTAGE') {
echo ",您可以先购买其他商品";
}
}
这种处理方式的好处是:
- 业务异常(如库存不足)和系统异常(如数据库连接失败)区分处理
- 给用户的信息友好且 actionable(可采取行动)
- 开发人员能通过日志准确定位问题
四、日志记录的进阶技巧
光是记录错误还不够,好的日志系统要像侦探的记事本,能还原案发现场。我们结合Monolog这个强大的日志库来看看:
<?php
// 技术栈:PHP 8.2 + Monolog
require __DIR__.'/vendor/autoload.php';
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\SlackWebhookHandler;
use Monolog\Formatter\JsonFormatter;
// 创建日志频道
$log = new Logger('order_system');
// 文件日志(JSON格式)
$fileHandler = new StreamHandler(__DIR__.'/logs/order.log');
$fileHandler->setFormatter(new JsonFormatter());
$log->pushHandler($fileHandler);
// 严重错误发Slack通知
$slackHandler = new SlackWebhookHandler(
'https://hooks.slack.com/services/XXX',
'#tech-alerts',
'PHP错误机器人',
true,
null,
Logger::ERROR
);
$log->pushHandler($slackHandler);
// 添加上下文信息(自动记录在每个日志中)
$log->pushProcessor(function($record) {
$record['extra']['ip'] = $_SERVER['REMOTE_ADDR'] ?? 'cli';
$record['extra']['user_id'] = $_SESSION['user_id'] ?? null;
return $record;
});
// 使用示例
try {
$log->info("订单创建开始", ['sku' => 'IPHONE13', 'qty' => 2]);
// 业务逻辑...
if (rand(0, 10) > 8) {
throw new Exception("模拟的随机错误");
}
$log->info("订单创建成功", ['order_id' => 10086]);
} catch (Exception $e) {
$log->error("订单创建异常", [
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString()
]);
throw $e;
}
这样的日志系统可以:
- 不同级别日志分开处理(info存文件,error发通知)
- 自动记录请求上下文(用户ID、IP等)
- 支持多种输出方式(文件、Slack等)
- 结构化存储便于分析(JSON格式)
五、实战中的注意事项
- 敏感信息过滤:
$log->pushProcessor(function($record) {
// 过滤信用卡号
if (isset($record['context']['card_no'])) {
$record['context']['card_no'] = substr($record['context']['card_no'], -4).'****';
}
return $record;
});
- 日志轮转: 使用Linux的logrotate工具防止日志文件过大:
/path/to/your/logs/*.log {
daily
rotate 30
missingok
compress
delaycompress
notifempty
}
- 性能考量:
- 高频操作使用debug级别日志
- 避免在循环中记录重复信息
- 考虑使用syslog或异步写入
六、错误处理的最佳实践
- 用户视角:
- 前端显示友好的错误提示
- 提供错误代码(便于客服查询)
- 给出解决方案(如"请刷新重试")
- 开发视角:
- 在开发环境显示详细错误
- 生产环境隐藏技术细节但记录完整日志
- 使用Sentry/Bugsnag等工具收集错误
- 运维视角:
- 监控错误日志频率
- 设置不同级别的报警阈值
- 定期分析错误趋势
七、总结
好的错误处理系统就像飞机的黑匣子+驾驶舱警报系统的结合体:
- 平时安静记录所有细节(黑匣子)
- 出现问题时立即告警(警报系统)
- 事后能完整复现问题(调查工具)
记住三个核心原则:
- 不要信任任何输入 - 验证、验证、再验证
- 失败要优雅 - 给用户留条退路
- 记录足够多 - 但不要太多(避免日志爆炸)
从今天开始,检查你的项目是否:
- [ ] 有完整的错误分类处理
- [ ] 关键操作都有日志记录
- [ ] 生产环境不会暴露敏感信息
- [ ] 有错误监控和报警机制
把这些做好,你的应用健壮性就能超过80%的项目了!
评论