Skip to content

错误日志

概述

完善的日志系统是 PHP 应用排错和监控的基础。PHP 提供了 error_log() 函数用于手动记录日志,同时通过 php.ini 配置实现错误自动记录到日志文件。在生产环境中,通常使用 Monolog 等专业日志库替代原生日志机制。

PHP 版本说明

  • error_log():PHP 所有版本可用
  • log_errors / error_log 配置:PHP 所有版本
  • PHP 8.0+ 推荐使用 PSR-3 日志接口和 Monolog 等库

基础概念

PHP 日志的三种方式

方式说明适用场景
error_log() 函数手动发送日志消息快速调试、简单日志需求
php.ini 自动记录错误自动写入日志文件全局错误收集
日志库(Monolog)结构化日志、多通道生产环境、复杂应用

错误日志配置(php.ini)

ini
; 启用错误日志
log_errors = On

; 日志文件路径
error_log = /var/log/php/error.log

; 记录的错误级别
error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT

; 错误日志中包含日期时间
; (某些系统默认不包含,需要在日志处理器中添加)

语法与代码

error_log() 函数详解

php
<?php

declare(strict_types=1);

// 类型 1:发送到系统日志(默认)
error_log('用户登录失败: unknown@example.com');

// 类型 2:发送到指定邮箱(已废弃,不推荐)
// error_log('错误信息', 2, 'admin@example.com');

// 类型 3:追加写入指定文件
error_log('[ERROR] 数据库连接失败', 3, '/var/log/app/custom.log');

// 类型 4:发送到 SAPI 日志处理器
error_log('[INFO] 请求开始处理', 4);

error_log() 函数签名

php
error_log(
    string $message,
    int $message_type = 0,      // 日志类型
    ?string $destination = null, // 目标路径/邮箱
    ?string $additional_headers = null  // 额外头信息(仅 type=1)
): bool
message_type说明destination 参数
0发送到系统日志(OS 决定位置)忽略
1发送到指定邮箱邮箱地址
3追加写入文件文件路径
4发送到 SAPI 日志忽略

格式化日志消息

php
<?php

declare(strict_types=1);

class Logger
{
    private string $logFile;

    public function __construct(string $logFile)
    {
        $this->logFile = $logFile;
    }

    private function formatMessage(string $level, string $message, array $context = []): string
    {
        $timestamp = date('Y-m-d\TH:i:s.v P');
        $contextStr = empty($context) ? '' : ' ' . json_encode($context, JSON_UNESCAPED_UNICODE);

        return "[{$timestamp}] [{$level}] {$message}{$contextStr}";
    }

    public function info(string $message, array $context = []): void
    {
        error_log(
            $this->formatMessage('INFO', $message, $context),
            3,
            $this->logFile
        );
    }

    public function error(string $message, array $context = []): void
    {
        error_log(
            $this->formatMessage('ERROR', $message, $context),
            3,
            $this->logFile
        );
    }

    public function warning(string $message, array $context = []): void
    {
        error_log(
            $this->formatMessage('WARNING', $message, $context),
            3,
            $this->logFile
        );
    }
}

$logger = new Logger('/var/log/app/app.log');
$logger->info('用户登录', ['user_id' => 42, 'ip' => '192.168.1.1']);
$logger->error('支付失败', ['order_id' => 'ORD001', 'reason' => '余额不足']);

php.ini 错误日志配置详解

ini
; ==================== 错误日志配置 ====================

; 是否启用错误日志(推荐生产环境 On)
log_errors = On

; 日志文件路径
; 留空则使用系统默认日志(如 syslog)
error_log = /var/log/php/app-error.log

; 错误报告级别
error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT

; 是否在屏幕上显示错误(生产环境必须 Off)
display_errors = Off
display_startup_errors = Off

; 日志重复过滤(同一消息不重复记录)
; PHP 8.0+ 可设置为 0(不限制)
html_errors = Off

; 错误日志的最大长度(0 = 无限制,PHP 8.0+)
log_errors_max_len = 1024

日志级别分类

php
<?php

declare(strict_types=1);

// PSR-3 日志级别与 PHP 错误级别的映射
function logPsrLevel(string $level, string $message): void
{
    $logFile = '/var/log/app/psr.log';
    $timestamp = date('Y-m-d\TH:i:s.v P');

    $entry = "[{$timestamp}] {$level}: {$message}";
    error_log($entry . PHP_EOL, 3, $logFile);
}

// PSR-3 定义了 8 个日志级别
$levels = [
    'DEBUG'     => '调试信息,开发期使用',
    'INFO'      => '常规信息,如用户登录、请求处理',
    'NOTICE'    => '值得注意但不一定是错误',
    'WARNING'   => '警告:非预期行为但程序可继续',
    'ERROR'     => '运行时错误,需要立即处理',
    'CRITICAL'  => '严重错误,系统部分不可用',
    'ALERT'     => '紧急情况,需立即响应',
    'EMERGENCY' => '系统不可用',
];

foreach ($levels as $level => $desc) {
    logPsrLevel($level, $desc);
}

PHP 错误级别与日志级别的映射

PHP 错误级别PSR-3 日志级别说明
E_ERRORERROR致命错误
E_WARNINGWARNING警告
E_NOTICENOTICE通知
E_DEPRECATEDDEBUG废弃功能
ExceptionERROR运行时异常
Error (TypeError 等)CRITICAL内部错误
用户日志INFO/DEBUG自定义日志

详细说明

Monolog 简介

Monolog 是 PHP 最流行的日志库,实现了 PSR-3 日志接口。

php
<?php

declare(strict_types=1);

// 安装: composer require monolog/monolog
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\RotatingFileHandler;
use Monolog\Processor\WebProcessor;
use Monolog\Processor\IntrospectionProcessor;

// 创建日志通道
$log = new Logger('app');

// 按天轮转日志文件(保留 30 天)
$log->pushHandler(
    new RotatingFileHandler(
        filename: '/var/log/app/app.log',
        maxFiles: 30,
        level: Logger::DEBUG
    )
);

// 添加处理器:自动记录调用位置
$log->pushProcessor(new IntrospectionProcessor());

// 添加处理器:记录 Web 请求信息
$log->pushProcessor(new WebProcessor());

// 使用
$log->info('用户登录成功', ['user_id' => 42]);
$log->warning('API 响应缓慢', ['endpoint' => '/api/users', 'duration_ms' => 2300]);
$log->error('数据库连接失败', ['host' => 'db-master', 'error' => 'Connection refused']);

Monolog 常用 Handler

Handler说明适用场景
StreamHandler写入文件流通用文件日志
RotatingFileHandler按天轮转文件生产环境长期日志
SyslogHandler写入系统日志服务器集成
MailHandler发送邮件紧急告警
SlackHandler发送 Slack 消息团队通知
NullHandler丢弃日志测试/禁用
BufferHandler缓冲后批量发送性能优化

PSR-3 接口

Monolog 实现了 PSR-3(PHP 标准推荐 #3)日志接口。如果你的框架支持依赖注入,可以注入 Psr\Log\LoggerInterface 而非具体类,提高可替换性。

实战示例

简易日志管理器

php
<?php

declare(strict_types=1);

class FileLogger
{
    private const LEVEL_DEBUG     = 'DEBUG';
    private const LEVEL_INFO      = 'INFO';
    private const LEVEL_WARNING   = 'WARNING';
    private const LEVEL_ERROR     = 'ERROR';

    private string $logDir;
    private string $channel;

    private const LEVEL_PRIORITY = [
        self::LEVEL_DEBUG   => 0,
        self::LEVEL_INFO    => 1,
        self::LEVEL_WARNING => 2,
        self::LEVEL_ERROR   => 3,
    ];

    public function __construct(string $logDir, string $channel = 'app')
    {
        $this->logDir = rtrim($logDir, '/');
        $this->channel = $channel;
    }

    public function debug(string $message, array $context = []): void
    {
        $this->log(self::LEVEL_DEBUG, $message, $context);
    }

    public function info(string $message, array $context = []): void
    {
        $this->log(self::LEVEL_INFO, $message, $context);
    }

    public function warning(string $message, array $context = []): void
    {
        $this->log(self::LEVEL_WARNING, $message, $context);
    }

    public function error(string $message, array $context = []): void
    {
        $this->log(self::LEVEL_ERROR, $message, $context);
    }

    private function log(string $level, string $message, array $context): void
    {
        $date = date('Y-m-d');
        $logFile = "{$this->logDir}/{$this->channel}-{$date}.log";
        $timestamp = date('Y-m-d\TH:i:s.v P');
        $contextStr = empty($context) ? '' : ' ' . json_encode($context, JSON_UNESCAPED_UNICODE);

        $entry = "[{$timestamp}] [{$level}] {$message}{$contextStr}" . PHP_EOL;
        error_log($entry, 3, $logFile);
    }
}

$logger = new FileLogger('/var/log/app', 'order-service');
$logger->info('订单创建成功', ['order_id' => '20240101001', 'amount' => 99.99]);
$logger->error('支付回调失败', ['order_id' => '20240101001', 'error' => 'timeout']);

注意事项

  1. 日志文件权限:确保 PHP 进程(如 www-data)对日志目录有写入权限。推荐 chmod 755 /var/log/app

  2. 日志文件轮转:生产环境必须配置日志轮转(logrotate),否则日志文件可能无限增长。Monolog 的 RotatingFileHandler 可自动处理。

  3. 敏感信息脱敏:记录日志时,避免记录密码、密钥、Token 等敏感信息。

  4. error_log() 不使用缓冲区:每次调用直接写入,频繁调用可能影响性能。Monolog 的 BufferHandler 可缓冲后批量写入。

  5. syslog 与文件日志的区别error_log() 默认使用 syslog,输出位置取决于 OS 配置(Linux 默认 /var/log/syslog,macOS 默认 /var/log/system.log)。

最佳实践

推荐做法

  1. 生产环境使用 Monolog:功能更完善,支持通道、处理器、格式化器
  2. 日志级别分明:DEBUG 用于开发,INFO 用于常规,WARNING/ERROR 用于异常
  3. 结构化日志:使用 JSON 格式记录,便于 ELK 等日志系统解析
  4. 日志包含上下文:记录请求 ID、用户 ID、IP 等上下文信息
  5. 统一日志入口:所有日志通过同一接口写入,避免散落各处的 error_log() 调用

进阶用法

调试与测试技巧

php
<?php
declare(strict_types=1);

// 单元测试辅助函数
function createTestResource(): mixed
{
    return match (true) {
        default => new stdClass(),
    };
}

// 调试输出函数
function debugOutput(mixed , string  = ''): void
{
     =  ? ": " : '';
     .= print_r(, true);
    fwrite(STDERR,  . "\n");
}

// 性能基准测试
function benchmark(callable , int  = 1000): float
{
     = hrtime(true);
    for ($i = 0; $i < $iterations; $i++) {
        $fn();
    }
    return (hrtime(true) - $start) / 1e9;
}

日志记录实践

php
<?php
declare(strict_types=1);

/**
 * 简易日志记录器
 */
class SimpleLogger
{
    private string $logFile;
    private string $level = 'INFO';

    public function __construct(string $logFile)
    {
        $this->logFile = $logFile;
    }

    public function info(string $message, array $context = []): void
    {
        $this->log('INFO', $message, $context);
    }

    public function warning(string $message, array $context = []): void
    {
        $this->log('WARNING', $message, $context);
    }

    public function error(string $message, array $context = []): void
    {
        $this->log('ERROR', $message, $context);
    }

    private function log(string $level, string $message, array $context): void
    {
        $timestamp = date('Y-m-d H:i:s');
        $contextStr = $context ? ' ' . json_encode($context, JSON_UNESCAPED_UNICODE) : '';
        $line = "[{$timestamp}] [{$level}] {$message}{$contextStr}\n";
        file_put_contents($this->logFile, $line, FILE_APPEND | LOCK_EX);
    }
}

配置与环境检测

php
<?php
declare(strict_types=1);

// 环境检测工具
class EnvironmentChecker
{
    public static function checkRequirements(array $requirements): array
    {
        $results = [];
        foreach ($requirements as $name => $check) {
            $results[$name] = is_callable($check) ? $check() : false;
        }
        return $results;
    }

    public static function getSystemInfo(): array
    {
        return [
            'php_version' => PHP_VERSION,
            'os' => PHP_OS,
            'sapi' => PHP_SAPI,
            'memory_limit' => ini_get('memory_limit'),
            'max_execution_time' => ini_get('max_execution_time'),
            'loaded_extensions' => get_loaded_extensions(),
        ];
    }
}

常见问题排查

问题可能原因解决方案
连接超时网络问题/配置错误检查配置,增加超时时间
权限不足文件/目录权限使用 chmod/chown 修正
性能下降索引缺失/数据量大添加索引,优化查询
数据不一致并发冲突/事务残留使用锁机制和事务
内存溢出大数据集/未释放资源增大内存限制,分批处理

故障排除步骤

  1. 检查错误日志和异常信息
  2. 确认配置和环境是否正确
  3. 使用调试工具逐步排查
  4. 参考官方文档查找已知问题

版本兼容性说明

功能最低版本说明
基础功能PHP 8.1本文档基准版本
只读属性PHP 8.1public readonly 修饰符
枚举类型PHP 8.1enum 类型和 match 表达式
FiberPHP 8.1协程/轻量级并发
命名参数PHP 8.0foo(arg_name: value)
联合类型PHP 8.0`int
Null 安全运算符PHP 8.0$obj?->method()
析构器 promotionPHP 8.0__construct(public $x)
php
<?php
declare(strict_types=1);

// 版本兼容性检测
function ensureVersion(string $minVersion): void
{
    if (version_compare(PHP_VERSION, $minVersion, '<')) {
        throw new RuntimeException(
            sprintf('需要 PHP %s+, 当前版本: %s', $minVersion, PHP_VERSION)
        );
    }
}

ensureVersion('8.1.0');

参考链接