错误报告配置
概述
PHP 的错误报告系统决定了错误如何被检测、记录和展示。通过 php.ini 配置项和运行时函数,开发者可以精细控制错误报告的行为,包括是否显示错误、记录到日志、以及报告哪些级别的错误。
合理的错误报告配置是应用程序安全性和可维护性的关键。开发环境需要详细的错误信息,生产环境需要安全的日志记录。
版本说明
error_reporting()函数从 PHP 起即可用- PHP 8.0 将
default_charset默认设为 UTF-8,影响错误信息的编码 - PHP 8.1 中
E_STRICT被E_ALL包含
基础概念
核心配置项
| 配置项 | php.ini | 运行时设置 | 说明 |
|---|---|---|---|
error_reporting | error_reporting = E_ALL | error_reporting(E_ALL) | 报告哪些级别的错误 |
display_errors | display_errors = Off | ini_set('display_errors', '0') | 是否输出错误到屏幕 |
log_errors | log_errors = On | ini_set('log_errors', '1') | 是否记录错误到日志 |
error_log | error_log = /path/to/log | ini_set('error_log', '/path') | 错误日志文件路径 |
display_startup_errors | display_startup_errors = Off | 不可运行时设置 | 是否显示启动错误 |
语法与代码
error_reporting 设置
php
<?php
declare(strict_types=1);
// error_reporting() 设置报告哪些级别的错误
// 接受整数参数(位掩码)
// 报告所有错误
error_reporting(E_ALL);
// 报告所有错误,除了 Notice
error_reporting(E_ALL & ~E_NOTICE);
// 报告所有错误,除了 Notice 和 Deprecated
error_reporting(E_ALL & ~E_NOTICE & ~E_DEPRECATED);
// 只报告 Error 和 Warning
error_reporting(E_ERROR | E_WARNING);
// 关闭所有错误报告
error_reporting(0);
// 获取当前报告级别
$currentLevel = error_reporting();
echo "当前级别: " . $currentLevel;
// 使用 -1 表示所有错误(推荐,不依赖 E_ALL 的具体值)
error_reporting(-1);display_errors 和 log_errors
php
<?php
declare(strict_types=1);
// display_errors — 是否在输出中显示错误
// 生产环境必须关闭(安全考虑)
ini_set('display_errors', '0'); // 关闭
ini_set('display_errors', '1'); // 开启
// log_errors — 是否将错误记录到日志文件
ini_set('log_errors', '1'); // 开启(推荐)
ini_set('log_errors', '0'); // 关闭
// error_log — 日志文件路径
ini_set('error_log', '/var/log/php_errors.log');
// syslog — 记录到系统日志
ini_set('error_log', 'syslog');
// 特殊值:发送到 SAPI 错误日志
ini_set('error_log', '');
// 完整配置示例
ini_set('display_errors', '0');
ini_set('display_startup_errors', '0');
ini_set('log_errors', '1');
ini_set('error_log', '/var/log/app_errors.log');
error_reporting(E_ALL & ~E_NOTICE & ~E_DEPRECATED);error_reporting 级别组合
php
<?php
declare(strict_types=1);
// 位运算组合错误级别
// OR (|) — 添加级别
error_reporting(E_ERROR | E_WARNING | E_PARSE);
// 报告 Fatal、Warning 和 Parse 错误
// AND NOT (& ~) — 排除级别
error_reporting(E_ALL & ~E_NOTICE & ~E_STRICT);
// 报告所有,排除 Notice 和 Strict
// AND (&) — 交集
error_reporting(E_ALL & E_WARNING);
// 只报告 Warning(如果它在 E_ALL 中)
// 常用组合
$prodLevel = E_ALL & ~E_NOTICE & ~E_DEPRECATED & ~E_STRICT;
$devLevel = E_ALL | E_STRICT;
$testLevel = E_ALL & ~E_DEPRECATED;
error_reporting($prodLevel);error_log() 函数
php
<?php
declare(strict_types=1);
// error_log() — 将消息发送到日志系统
// 不受 error_reporting 级别影响
// 发送到 php.ini 配置的 error_log 文件
error_log('这是一条日志消息');
// 发送到指定文件
error_log('自定义日志文件', 3, '/var/log/app.log');
// 发送到邮箱(第二个参数 = 1)
// error_log('错误报告', 1, 'admin@example.com', 'Subject: Error');
// 参数说明:
// 0 — 默认,发送到 php.ini 的 error_log
// 1 — 发送到邮箱
// 3 — 发送到指定文件
// 4 — 发送到 SAPI 日志(如 Apache/Nginx)详细说明
错误报告的优先级和流程
php
<?php
declare(strict_types=1);
// PHP 错误处理流程:
// 1. 错误发生
// 2. 检查 error_reporting() 是否包含该级别
// - 不包含 → 忽略
// - 包含 → 继续
// 3. 检查是否设置了 set_error_handler
// - 有自定义处理器 → 调用自定义处理器
// - 无自定义处理器 → 使用内置处理
// 4. 内置处理根据配置决定:
// - display_errors=On → 输出到页面
// - log_errors=On → 写入日志文件
// - 两者都不启用 → 静默忽略(不推荐)
// 注意:set_error_handler 不影响 E_ERROR 和 E_PARSEphp.ini 完整配置示例
ini
; 开发环境 php.ini
error_reporting = E_ALL
display_errors = On
display_startup_errors = On
log_errors = On
error_log = /tmp/php_dev_errors.log
html_errors = On
docref_root = "/phpmanual/"
track_errors = Off
; 生产环境 php.ini
error_reporting = E_ALL & ~E_NOTICE & ~E_DEPRECATED & ~E_STRICT
display_errors = Off
display_startup_errors = Off
log_errors = On
error_log = /var/log/php_errors.log
html_errors = Off
track_errors = Off运行时 vs php.ini 配置
php
<?php
declare(strict_types=1);
// 某些配置只能在 php.ini 中设置(不可运行时修改)
// - display_startup_errors
// - error_log(某些 SAPI 模式下)
// - log_errors_max_len(某些情况下)
// 大部分可以在运行时用 ini_set() 修改
ini_set('error_reporting', (string) E_ALL);
ini_set('display_errors', '0');
ini_set('log_errors', '1');
ini_set('error_log', '/tmp/custom.log');
// 检查配置值
echo ini_get('display_errors'); // 0
echo ini_get('log_errors'); // 1
// 检查是否可修改
echo ini_get_all('error_reporting')['access']; // 7 = PHP_INI_ALL
// access 值:
// 1 = PHP_INI_USER(可在用户脚本中设置)
// 2 = PHP_INI_PERDIR(可在 php.ini, .htaccess 中设置)
// 4 = PHP_INI_SYSTEM(只能在 php.ini 或 httpd.conf 中设置)
// 7 = PHP_INI_ALL(所有位置都可设置)@ 运算符与 error_reporting 的交互
php
<?php
declare(strict_types=1);
// @ 运算符临时将 error_reporting 设为 0
// 在 @ 作用域内,自定义错误处理器仍会被调用
// 但 error_reporting() 返回 0
set_error_handler(function (int $severity, string $message): bool {
$current = error_reporting();
if ($current === 0) {
// 被 @ 运算符抑制
echo "(错误被 @ 抑制)\n";
return true;
}
echo "正常处理: {$message}\n";
return true;
});
$undefined; // 正常处理: Undefined variable
@ $undefined2; // (错误被 @ 抑制)
// 恢复 error_reporting
error_reporting(E_ALL);@ 运算符与错误处理器
即使使用了 @ 运算符,自定义的 set_error_handler 仍然会被调用。在处理器中检查 error_reporting() 的返回值是否为 0,可以判断错误是否被 @ 抑制。
实战示例
环境感知的错误配置
php
<?php
declare(strict_types=1);
class ErrorConfigManager
{
private const ENV_DEV = 'development';
private const ENV_TEST = 'testing';
private const ENV_PROD = 'production';
public function __construct(
private readonly string $environment
) {}
public function configure(): void
{
$config = match ($this->environment) {
self::ENV_DEV => [
'error_reporting' => E_ALL | E_STRICT,
'display_errors' => '1',
'display_startup_errors' => '1',
'log_errors' => '1',
'error_log' => '/tmp/php_dev.log',
'html_errors' => '1',
'log_errors_max_len' => '4096',
'ignore_repeated_errors' => '0',
'ignore_repeated_source' => '0',
'report_memleaks' => '1',
],
self::ENV_TEST => [
'error_reporting' => E_ALL & ~E_STRICT & ~E_DEPRECATED,
'display_errors' => '0',
'display_startup_errors' => '0',
'log_errors' => '1',
'error_log' => '/var/log/php_test.log',
'html_errors' => '0',
'log_errors_max_len' => '1024',
'ignore_repeated_errors' => '1',
'ignore_repeated_source' => '0',
'report_memleaks' => '1',
],
self::ENV_PROD => [
'error_reporting' => E_ALL & ~E_NOTICE & ~E_DEPRECATED & ~E_STRICT,
'display_errors' => '0',
'display_startup_errors' => '0',
'log_errors' => '1',
'error_log' => '/var/log/php_errors.log',
'html_errors' => '0',
'log_errors_max_len' => '1024',
'ignore_repeated_errors' => '1',
'ignore_repeated_source' => '1',
'report_memleaks' => '1',
],
};
foreach ($config as $key => $value) {
ini_set($key, (string) $value);
}
// 同步 error_reporting
error_reporting((int) ini_get('error_reporting'));
error_log(sprintf(
"[%s] 错误报告配置完成, level=%d",
date('Y-m-d H:i:s'),
error_reporting()
));
}
public function getEnvironment(): string
{
return $this->environment;
}
}
// 使用
$env = getenv('APP_ENV') ?: 'production';
$manager = new ErrorConfigManager($env);
$manager->configure();注意事项
display_errors 的安全风险
php
<?php
declare(strict_types=1);
// 生产环境必须关闭 display_errors
// 否则可能暴露:
// 1. 文件路径和目录结构
// 2. 数据库连接信息
// 3. 源代码片段
// 4. 第三方库版本信息
// 错误示例(display_errors = On 时):
// Warning: mysqli_connect(): (HY000/1045): Access denied for user
// 'root'@'localhost' (using password: YES) in /var/www/html/config/database.php:15
// ↑ 暴露了数据库用户名和文件路径
// 安全替代方案:
// display_errors = Off
// log_errors = On
// 自定义错误页面展示友好的错误信息error_log 文件权限
php
<?php
declare(strict_types=1);
// 错误日志文件需要正确的权限
// - Web 服务器用户需要有写入权限
// - 其他用户不应有读取权限
// 推荐权限:
// - 文件权限: 640 (rw-r-----)
// - 目录权限: 750 (rwxr-x---)
// - 所有者: Web 服务器用户 (www-data, nginx, apache)
// 检查日志是否可写
$logPath = '/var/log/php_errors.log';
if (!is_writable(dirname($logPath))) {
die("错误日志目录不可写: {$logPath}");
}
error_log("日志系统初始化成功");最佳实践
- 生产环境 display_errors 必须关闭:防止敏感信息泄露
- 始终开启 log_errors:即使开发环境也应记录日志
- 使用 E_ALL 或 -1 开发:开发阶段捕获所有错误
- 合理设置 ignore_repeated_errors:生产环境设为 1 减少重复日志
- 定期轮转日志文件:使用 logrotate 或自定义方案
php
<?php
declare(strict_types=1);
// 应用入口统一配置
error_reporting(-1);
ini_set('display_errors', '0');
ini_set('log_errors', '1');
ini_set('error_log', '/var/log/app_errors.log');
ini_set('ignore_repeated_errors', '1');
ini_set('ignore_repeated_source', '1');
ini_set('html_errors', '0');
ini_set('log_errors_max_len', '1024');