日志通道与驱动
日志通道(Channel)是日志系统的核心抽象,每个通道对应一个独立的日志驱动实例。通过通道可以实现日志的分类存储和路由。
通道概念
text
LogManager
├── file 通道 → File Driver → runtime/logs/
├── email 通道 → Email Driver → SMTP
└── custom 通道 → Custom Driver → ...不同类型的日志可以路由到不同的通道,实现灵活的日志管理策略。
内置驱动:File
框架内置文件日志驱动 Viswoole\Log\Drives\File,提供完整的文件日志解决方案。
存储结构
text
runtime/logs/
├── 20240101/ # 按日期分目录
│ ├── info/
│ │ ├── info_0.log
│ │ ├── info_1.log
│ │ └── info_2.log
│ ├── error/
│ │ ├── error_0.log
│ │ └── error_1.log
│ ├── debug/
│ └── sql/
├── 20240102/
│ └── ...特性说明
1. 按日期分级目录
日志按 {日期}/{级别} 的目录结构组织,便于查找和管理。
2. 文件大小切割
单个日志文件超过设定大小时自动切割为新文件:
php
// 文件命名规则: {level}_{序号}.log
// info_0.log → info_1.log → info_2.log → ...3. 自动过期清理
通过 Swoole 定时器每日凌晨自动清理超过保留天数的日志:
php
// 启动定时器(File 驱动构造函数中自动启动)
$fileDriver = new File(
storageDays: 7, // 保留7天
maxFiles: 30, // 单级最多30个文件
fileSize: 10485760 // 单文件最大10MB
);4. JSON 格式存储
默认以 JSON 格式存储每条日志,便于后续分析和检索:
json
{
"timestamp": "2024-01-01T12:00:00+08:00",
"level": "info",
"message": "用户登录",
"context": { "user_id": 100 },
"source": "AuthController.php:25"
}也可配置为纯文本格式:
php
new File(logFormat: '[%timestamp][%level]: %message %context in %source', json: false);File 驱动完整参数
php
new \Viswoole\Log\Drives\File(
storageDays: 7, // 日志保留天数
maxFiles: 30, // 单级别最大文件数
fileSize: 1024 * 1024 * 10, // 单文件最大字节数(10MB)
dateFormat: 'c', // 时间戳格式(ISO 8601)
logFormat: '[%timestamp][%level]: %message %context in %source',
json: true, // 是否JSON格式
json_flags: JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES,
log_dir: BASE_PATH . '/runtime/logs' // 日志根目录
);通道配置
基础配置
php
// config/log.php
return [
'default' => 'file',
'channels' => [
// 字符串形式:直接指定驱动类名
'file' => \Viswoole\Log\Drives\File::class,
// 数组形式:带选项配置
'daily' => [
'driver' => \Viswoole\Log\Drives\File::class,
'options' => [
'storageDays' => 30,
'maxFiles' => 100,
'fileSize' => 20 * 1024 * 1024, // 20MB
'log_dir' => BASE_PATH . '/runtime/daily_logs',
]
],
// 实例形式:直接传入驱动实例
'custom' => new CustomLogDriver(...),
]
];级别路由(type_channel)
将特定级别的日志自动路由到指定通道:
php
return [
'default' => 'file',
// 按级别指定通道
'type_channel' => [
// error 级别及以上的日志写到 error 通道
'error' => 'error_channel',
// 支持数组形式同时写多个通道
'critical' => ['error_channel', 'alert_service'],
// SQL 日志单独记录
'sql' => 'sql_channel',
],
'channels' => [
'file' => File::class,
'error_channel' => [
'driver' => File::class,
'options' => ['log_dir' => BASE_PATH . '/runtime/error_logs']
],
'sql_channel' => [
'driver' => File::class,
'options' => ['log_dir' => BASE_PATH . '/runtime/sql_logs']
],
],
];路由优先级:type_channel 配置 > default 默认通道
自定义驱动
实现接口
自定义日志驱动需实现 DriveInterface 接口:
php
namespace App\Log;
use Viswoole\Log\Contract\DriveInterface;
use Viswoole\Log\Collector;
class DatabaseLogDrive extends Collector implements DriveInterface
{
public function __construct()
{
// 初始化数据库连接等资源
}
/**
* 批量保存日志记录
*
* @param array<int,array{timestamp:int,level:string,message:string,context:array,source:string}> $logRecords
*/
public function save(array $logRecords): void
{
foreach ($logRecords as $record) {
// 写入数据库
DB::table('system_logs')->insert([
'level' => $record['level'],
'message' => $record['message'],
'context' => json_encode($record['context']),
'source' => $record['source'],
'created_at' => date('Y-m-d H:i:s', $record['timestamp']),
]);
}
}
/**
* 绕过缓存直接写入
*/
public function write(string $level, string|\Stringable $message, array $context = []): void
{
$this->save([
\Viswoole\Log\LogManager::createLogData($level, $message, $context)
]);
}
/**
* 缓存日志(协程结束时会调用 save)
*/
public function record(string $level, string|\Stringable $message, array $context = []): void
{
// 调用基类 Collector 的 mixed 方法进行缓存
$this->mixed($level, $message, $context);
}
}继承基类
也可以继承 Viswoole\Log\Drive 抽象类,简化实现:
php
namespace App\Log;
use Viswoole\Log\Drive;
class ElasticsearchDrive extends Drive
{
protected \Elasticsearch\Client $client;
public function __construct(array $config)
{
$this->client = \Elasticsearch\ClientBuilder::create()
->setHosts($config['hosts'])
->build();
}
public function save(array $logRecords): void
{
$params = ['body' => []];
foreach ($logRecords as $record) {
$params['body'][] = [
'index' => [
'_index' => 'app_logs',
]
];
$params['body'][] = $record;
}
if (!empty($params['body'])) {
$this->client->bulk($params);
}
}
}注册自定义驱动
php
// 方式一:在配置文件中注册
// config/log.php
'channels' => [
'elasticsearch' => [
'driver' => \App\Log\ElasticsearchDrive::class,
'options' => [
'hosts' => ['http://localhost:9200']
]
]
]
// 方式二:动态注册(需在服务启动前)
Log::addChannel('database', \App\Log\DatabaseLogDrive::class);
// 方式三:传入实例
Log::addChannel('custom', new CustomLogDriver($config));多通道实战示例
分级存储策略
php
// config/log.php
return [
'default' => 'app',
// 错误日志单独存储,方便监控告警
'type_channel' => [
'error' => 'error',
'critical' => ['error', 'alert'], // critical 同时写两个通道
],
'channels' => [
// 应用主日志
'app' => [
'driver' => File::class,
'options' => [
'log_dir' => BASE_PATH . '/runtime/logs/app',
'storageDays' => 14,
'json' => true,
]
],
// 错误专用日志(更长保留期)
'error' => [
'driver' => File::class,
'options' => [
'log_dir' => BASE_PATH . '/runtime/logs/error',
'storageDays' => 90,
'json' => true,
]
],
// 告警通道(可接入外部通知服务)
'alert' => AlertNotificationDrive::class,
],
];