错误日志
概述
完善的日志系统是 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_ERROR | ERROR | 致命错误 |
E_WARNING | WARNING | 警告 |
E_NOTICE | NOTICE | 通知 |
E_DEPRECATED | DEBUG | 废弃功能 |
Exception | ERROR | 运行时异常 |
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']);注意事项
日志文件权限:确保 PHP 进程(如
www-data)对日志目录有写入权限。推荐chmod 755 /var/log/app。日志文件轮转:生产环境必须配置日志轮转(logrotate),否则日志文件可能无限增长。Monolog 的
RotatingFileHandler可自动处理。敏感信息脱敏:记录日志时,避免记录密码、密钥、Token 等敏感信息。
error_log() 不使用缓冲区:每次调用直接写入,频繁调用可能影响性能。Monolog 的
BufferHandler可缓冲后批量写入。syslog 与文件日志的区别:
error_log()默认使用 syslog,输出位置取决于 OS 配置(Linux 默认/var/log/syslog,macOS 默认/var/log/system.log)。
最佳实践
推荐做法
- 生产环境使用 Monolog:功能更完善,支持通道、处理器、格式化器
- 日志级别分明:DEBUG 用于开发,INFO 用于常规,WARNING/ERROR 用于异常
- 结构化日志:使用 JSON 格式记录,便于 ELK 等日志系统解析
- 日志包含上下文:记录请求 ID、用户 ID、IP 等上下文信息
- 统一日志入口:所有日志通过同一接口写入,避免散落各处的
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 修正 |
| 性能下降 | 索引缺失/数据量大 | 添加索引,优化查询 |
| 数据不一致 | 并发冲突/事务残留 | 使用锁机制和事务 |
| 内存溢出 | 大数据集/未释放资源 | 增大内存限制,分批处理 |
故障排除步骤
- 检查错误日志和异常信息
- 确认配置和环境是否正确
- 使用调试工具逐步排查
- 参考官方文档查找已知问题
版本兼容性说明
| 功能 | 最低版本 | 说明 |
|---|---|---|
| 基础功能 | PHP 8.1 | 本文档基准版本 |
| 只读属性 | PHP 8.1 | public readonly 修饰符 |
| 枚举类型 | PHP 8.1 | enum 类型和 match 表达式 |
| Fiber | PHP 8.1 | 协程/轻量级并发 |
| 命名参数 | PHP 8.0 | foo(arg_name: value) |
| 联合类型 | PHP 8.0 | `int |
| Null 安全运算符 | PHP 8.0 | $obj?->method() |
| 析构器 promotion | PHP 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');