PHP 完全指南 / 第 21 章 — 日志
第 21 章 — 日志:Monolog、PSR-3 与结构化日志
21.1 PSR-3 日志接口
<?php
// PSR-3 定义了 8 个日志级别
interface LoggerInterface
{
public function emergency(string|\Stringable $message, array $context = []): void;
public function alert(string|\Stringable $message, array $context = []): void;
public function critical(string|\Stringable $message, array $context = []): void;
public function error(string|\Stringable $message, array $context = []): void;
public function warning(string|\Stringable $message, array $context = []): void;
public function notice(string|\Stringable $message, array $context = []): void;
public function info(string|\Stringable $message, array $context = []): void;
public function debug(string|\Stringable $message, array $context = []): void;
public function log($level, string|\Stringable $message, array $context = []): void;
}
| 级别 | 常量 | 数值 | 说明 |
|---|
| Emergency | LogLevel::EMERGENCY | 600 | 系统不可用 |
| Alert | LogLevel::ALERT | 550 | 需要立即处理 |
| Critical | LogLevel::CRITICAL | 500 | 严重错误 |
| Error | LogLevel::ERROR | 400 | 运行时错误 |
| Warning | LogLevel::WARNING | 300 | 警告 |
| Notice | LogLevel::NOTICE | 250 | 重要但正常 |
| Info | LogLevel::INFO | 200 | 关键事件 |
| Debug | LogLevel::DEBUG | 100 | 调试信息 |
21.2 Monolog
composer require monolog/monolog
21.2.1 基本用法
<?php
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Formatter\JsonFormatter;
$logger = new Logger('app');
// 文件处理器
$logger->pushHandler(new StreamHandler(
'/var/log/php/app.log',
Logger::DEBUG
));
// 使用
$logger->info('User logged in', ['user_id' => 42]);
$logger->error('Payment failed', ['order_id' => 'ORD-001']);
21.2.2 多处理器
<?php
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\SlackWebhookHandler;
use Monolog\Handler\RotatingFileHandler;
$logger = new Logger('app');
// Debug 及以上写文件
$logger->pushHandler(new RotatingFileHandler(
'/var/log/php/app.log',
30, // 保留 30 天
Logger::DEBUG
));
// Error 及以上发送 Slack
$logger->pushHandler(new SlackWebhookHandler(
'https://hooks.slack.com/services/xxx',
'#alerts',
'PHP Logger',
true,
null,
Logger::ERROR
));
// Critical 及以上写紧急日志
$logger->pushHandler(new StreamHandler(
'/var/log/php/critical.log',
Logger::CRITICAL
));
21.2.3 常用 Handler
| Handler | 说明 |
|---|
StreamHandler | 写入流(文件、php://output) |
RotatingFileHandler | 按日期轮转文件 |
SyslogHandler | 写入系统日志 |
ErrorLogHandler | 写入 PHP error_log |
SlackWebhookHandler | 发送到 Slack |
RedisHandler | 写入 Redis |
SocketHandler | 写入 TCP/UDP 套接字 |
FingersCrossedHandler | 条件触发(错误时才记录) |
BufferHandler | 缓冲后批量写入 |
DeduplicationHandler | 去重 |
21.3 结构化日志
<?php
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Processor\PsrLogMessageProcessor;
use Monolog\Processor\WebProcessor;
use Monolog\Processor\MemoryUsageProcessor;
$logger = new Logger('app');
// 添加处理器链
$logger->pushProcessor(new PsrLogMessageProcessor());
$logger->pushProcessor(new WebProcessor()); // 自动添加 request 信息
$logger->pushProcessor(new MemoryUsageProcessor()); // 自动添加内存使用
// 自定义处理器
$logger->pushProcessor(function (array $record): array {
$record['extra']['app_version'] = '1.0.0';
$record['extra']['hostname'] = gethostname();
$record['extra']['request_id'] = $_SERVER['HTTP_X_REQUEST_ID'] ?? uniqid();
return $record;
});
// JSON 格式
$handler = new StreamHandler('/var/log/php/app.json');
$handler->setFormatter(new \Monolog\Formatter\JsonFormatter());
$logger->pushHandler($handler);
21.4 业务场景:请求日志中间件
<?php
declare(strict_types=1);
class RequestLoggingMiddleware
{
public function __construct(
private readonly LoggerInterface $logger,
) {}
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
$requestId = bin2hex(random_bytes(16));
$startTime = microtime(true);
$this->logger->info('Request started', [
'request_id' => $requestId,
'method' => $request->getMethod(),
'uri' => (string) $request->getUri(),
'ip' => $request->getServerParams()['REMOTE_ADDR'] ?? '',
]);
try {
$response = $handler->handle($request);
} catch (\Throwable $e) {
$this->logger->error('Request failed', [
'request_id' => $requestId,
'exception' => get_class($e),
'message' => $e->getMessage(),
]);
throw $e;
}
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->logger->info('Request completed', [
'request_id' => $requestId,
'status' => $response->getStatusCode(),
'elapsed_ms' => $elapsed,
]);
return $response->withHeader('X-Request-ID', $requestId);
}
}
21.5 扩展阅读
上一章:第 20 章 — 测试
下一章:第 22 章 — 安全