feat(log): 新增 DebugMysqlAsync 异步日志驱动与 debug_mysql_async 通道

This commit is contained in:
augushong
2026-08-16 08:06:27 +08:00
parent 413038b47d
commit 4d8cfc8844
3 changed files with 92 additions and 1 deletions

View File

@@ -19,6 +19,7 @@ PREFIX=ul_
FIELDS_CACHE=false
[LOG]
# 日志通道三选一file本地文件/ debug_mysql直写数据库/ debug_mysql_async异步本地 JSONL + 定时导入,写日志零 DB 依赖)
CHANNEL=file
[TIMER]

View File

@@ -72,7 +72,11 @@ return [
'charset' => Env::get('database.charset', 'utf8'),
// 数据库表前缀
'prefix' => Env::get('database.prefix', 'ul_'),
]
],
// 异步模式:本地 JSONL + 定时导入,写日志零 DB 依赖
'debug_mysql_async' => [
'type' => 'DebugMysqlAsync',
],
],
];

View File

@@ -0,0 +1,86 @@
<?php
declare(strict_types=1);
namespace think\log\driver;
use think\App;
use think\contract\LogHandlerInterface;
use think\log\DebugLogToolkit;
/**
* 异步调试日志驱动:本地 JSONL 落盘 + 定时导入,写日志零 DB 依赖.
*
* 与 DebugMysql 的分工DebugMysql 写日志时直连 DB含重连/CSV 文件降级逻辑);
* 本驱动根本不连 DB——save() 只把行追加进 {runtimePath}log/{Ymd}.jsonl
* DB 导入由定时任务异步完成。写日志的动作从物理上不可能成为业务故障点:
* 零 DB 依赖、绝不抛异常、绝不返回 false。
*
* 三条铁律:
* 1. 禁止缓存文件句柄或日期——DebugLogToolkit 每次现算(长驻 CLI 跨天滚动);
* 2. 禁止跨 save() 静态攒批——每次调用独立落盘;
* 3. 禁止任何 DB 访问/重连/降级逻辑——本驱动根本不连 DB。
*
* 行格式契约DebugLogToolkit::FIELDS与 ul_debug_log 列一一对应)。
*/
class DebugMysqlAsync implements LogHandlerInterface
{
/**
* 驱动配置(当前无可用项,保留以兼容通道配置注入).
*
* @var array
*/
protected $config = [];
/**
* 构造器签名与 DebugMysql 一致(框架 Log Manager 经容器 invokeClass 注入 App 实例与通道配置).
*/
public function __construct(App $app, $config = [])
{
if (is_array($config)) {
$this->config = array_merge($this->config, $config);
}
}
/**
* 落盘一批日志(扁平 array<LogRecord>.
*
* 镜像 DebugMysql.save 的遍历方式type 作 level、message 作 content、
* 非字符串 message 转 print_r但落盘动作换为 DebugLogToolkit
* 全部行拼接成一个多行字符串后只调一次 appendLine单次 flock 批量写入);
* 行间以 \n 连接、行尾 \n 由 appendLine 统一补齐,避免双换行。
* 整体 catch \Throwable 后仍返回 true——日志失败绝不反噬业务。
*
* @param array $log Channel save() 传入的扁平 LogRecord 数组
*/
public function save(array $log): bool
{
try {
$lines = [];
foreach ($log as $log_item) {
if (!is_object($log_item) || !isset($log_item->type) || !is_string($log_item->type) || !isset($log_item->message)) {
continue;
}
$content = $log_item->message;
if (!is_string($content)) {
$content = print_r($content, true);
}
$row = DebugLogToolkit::buildRow($log_item->type, $content);
$lines[] = DebugLogToolkit::encodeRow($row);
}
if (!empty($lines)) {
DebugLogToolkit::appendLine(implode("\n", $lines));
}
} catch (\Throwable $th) {
// 铁律:绝不抛异常、绝不返回 false失败即静默丢弃本批日志
}
return true;
}
}