Skip to content

错误报告配置

概述

PHP 的错误报告系统决定了错误如何被检测、记录和展示。通过 php.ini 配置项和运行时函数,开发者可以精细控制错误报告的行为,包括是否显示错误、记录到日志、以及报告哪些级别的错误。

合理的错误报告配置是应用程序安全性和可维护性的关键。开发环境需要详细的错误信息,生产环境需要安全的日志记录。

版本说明

  • error_reporting() 函数从 PHP 起即可用
  • PHP 8.0 将 default_charset 默认设为 UTF-8,影响错误信息的编码
  • PHP 8.1 中 E_STRICTE_ALL 包含

基础概念

核心配置项

配置项php.ini运行时设置说明
error_reportingerror_reporting = E_ALLerror_reporting(E_ALL)报告哪些级别的错误
display_errorsdisplay_errors = Offini_set('display_errors', '0')是否输出错误到屏幕
log_errorslog_errors = Onini_set('log_errors', '1')是否记录错误到日志
error_logerror_log = /path/to/logini_set('error_log', '/path')错误日志文件路径
display_startup_errorsdisplay_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_PARSE

php.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("日志系统初始化成功");

最佳实践

  1. 生产环境 display_errors 必须关闭:防止敏感信息泄露
  2. 始终开启 log_errors:即使开发环境也应记录日志
  3. 使用 E_ALL 或 -1 开发:开发阶段捕获所有错误
  4. 合理设置 ignore_repeated_errors:生产环境设为 1 减少重复日志
  5. 定期轮转日志文件:使用 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');

参考链接