Skip to content

错误处理与日志记录

良好的错误处理和日志记录是应用稳定运行的保障。本文介绍 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');
            // 发送告警通知(邮件/短信/钉钉)
        }
    });
}

注意事项

  1. 生产环境永远不要开启 display_errors
  2. 敏感信息(密码、密钥)不要写入日志
  3. 日志文件需要定期清理或轮转,防止磁盘占满
  4. 重要错误需要配合监控告警系统

Binstork