错误处理与日志记录
良好的错误处理和日志记录是应用稳定运行的保障。本文介绍 PHP 中的错误处理机制。
错误级别配置
php
// php.ini 或代码中配置
error_reporting(E_ALL); // 报告所有错误
display_errors = 0; // 生产环境关闭显示
log_errors = 1; // 开启错误日志
error_log = /var/log/php_errors.log; // 错误日志路径
// 代码中动态设置(开发环境)
error_reporting(E_ALL);
ini_set('display_errors', '1');自定义错误处理器
php
set_error_handler(function ($severity, $message, $file, $line) {
// 不处理被 @ 抑制的错误
if (!(error_reporting() & $severity)) {
return false;
}
$levels = [
E_WARNING => 'Warning',
E_NOTICE => 'Notice',
E_DEPRECATED => 'Deprecated',
E_USER_ERROR => 'User Error',
E_USER_WARNING => 'User Warning',
E_USER_NOTICE => 'User Notice',
];
$level = $levels[$severity] ?? 'Unknown';
$log = sprintf("[%s] %s: %s in %s:%d",
date('Y-m-d H:i:s'),
$level,
$message,
$file,
$line
);
error_log($log . PHP_EOL, 3, '/var/log/myapp/errors.log');
// 严重错误时抛出异常
if (in_array($severity, [E_USER_ERROR])) {
throw new ErrorException($message, 0, $severity, $file, $line);
}
return true;
});异常处理与自定义异常
php
// 自定义业务异常
class ValidationException extends Exception {}
class DatabaseException extends Exception {}
class NotFoundException extends Exception {
protected $code = 404;
}
// 全局异常处理器
set_exception_handler(function (Throwable $e) {
$log = sprintf("[%s] Uncaught %s: %s in %s:%d\nStack trace:\n%s",
date('Y-m-d H:i:s'),
get_class($e),
$e->getMessage(),
$e->getFile(),
$e->getLine(),
$e->getTraceAsString()
);
error_log($log, 3, '/var/log/myapp/exceptions.log');
// 返回用户友好的错误信息
if (ini_get('display_errors')) {
echo "<pre>$log</pre>";
} else {
http_response_code(500);
echo "系统出现错误,请稍后再试";
}
});使用 Monolog 记录日志
php
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\RotatingFileHandler;
// 创建日志实例
$logger = new Logger('app');
// 按日期轮转日志
$logger->pushHandler(new RotatingFileHandler('/var/log/myapp/app.log', 7, Logger::INFO));
// 错误日志单独记录
$logger->pushHandler(new StreamHandler('/var/log/myapp/error.log', Logger::ERROR));
// 使用示例
$logger->info('用户登录', ['user_id' => 123, 'ip' => $_SERVER['REMOTE_ADDR']]);
$logger->error('数据库连接失败', ['exception' => $e->getMessage()]);
$logger->debug('SQL查询', ['sql' => $sql, 'params' => $params]);try-catch 最佳实践
php
try {
// 业务代码
$user = User::find($id);
if (!$user) {
throw new NotFoundException('用户不存在');
}
$user->update($data);
} catch (NotFoundException $e) {
// 特定异常处理
http_response_code(404);
echo json_encode(['error' => $e->getMessage()]);
} catch (DatabaseException $e) {
// 数据库异常记录并返回友好提示
$logger->error('数据库操作失败', ['message' => $e->getMessage()]);
http_response_code(500);
echo json_encode(['error' => '数据操作失败']);
} catch (Throwable $e) {
// 兜底处理
$logger->critical('未处理的异常', [
'message' => $e->getMessage(),
'trace' => $e->getTraceAsString()
]);
http_response_code(500);
echo json_encode(['error' => '系统错误']);
}生产环境错误处理建议
php
// 生产环境配置
if (getenv('APP_ENV') === 'production') {
error_reporting(E_ALL & ~E_DEPRECATED & ~E_STRICT);
ini_set('display_errors', '0');
ini_set('log_errors', '1');
// 注册致命错误捕获
register_shutdown_function(function () {
$error = error_get_last();
if ($error && in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR])) {
error_log("Fatal error: {$error['message']}", 3, '/var/log/myapp/fatal.log');
// 发送告警通知(邮件/短信/钉钉)
}
});
}注意事项
- 生产环境永远不要开启
display_errors - 敏感信息(密码、密钥)不要写入日志
- 日志文件需要定期清理或轮转,防止磁盘占满
- 重要错误需要配合监控告警系统