Files
tcpserver-flow/config/log.php
T
2026-03-08 22:58:56 +08:00

217 lines
9.5 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
use Monolog\Logger;
use Monolog\Handler\RotatingFileHandler;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\FilterHandler;
use Monolog\Processor\ProcessIdProcessor;
use app\config\Config;
return [
'default' => [
'handlers' => [
// 控制台输出处理器
[
'class' => StreamHandler::class,
'constructor' => [
'php://stdout',
Config::getInstance()->logLevel,
],
'formatter' => [
'class' => Monolog\Formatter\LineFormatter::class,
/**
* 格式化日志输出
* %start%: 标记日志颜色
* %end%: 结束标记颜色
* %logger%: 日志记录器名称
* %L: 行号
* %M: 方法名
* %P: 进程ID
* %thread%: 线程ID
* %Level%: 日志级别(带有占位符)
* %level_name%: 日志级别名称
* %level%: 日志等级数字
* %message%: 日志内容
* %datetime%: 时间
*/
'constructor' => [
"%start%%datetime% [%thread%] %Level% %logger%:%L% - %message%%end%\n",
'Y-m-d H:i:s',
true
],
],
],
// 默认文件输出处理器
[
'class' => RotatingFileHandler::class,
'constructor' => [
runtime_path() . '/logs/webman.log',
Config::getInstance()->logRotationTimeByDay,
Config::getInstance()->logLevel,
],
'formatter' => [
'class' => Monolog\Formatter\LineFormatter::class,
'constructor' => [
"%datetime% [%thread%] %Level% %logger%:%L% - %message%\n",
'Y-m-d H:i:s',
true
],
],
],
// Error级别单独文件处理器
[
'class' => FilterHandler::class,
'constructor' => [
new RotatingFileHandler(
runtime_path() . '/logs/error.log',
Config::getInstance()->errorLogRotationTimeByDay,
Logger::DEBUG
),
Logger::ERROR,
Logger::EMERGENCY
],
'formatter' => [
'class' => Monolog\Formatter\LineFormatter::class,
'constructor' => [
"%datetime% [%thread%] %Level% %logger%:%L% - %message%\n",
'Y-m-d H:i:s',
true
],
],
],
],
// 全局处理器:手动解析调用栈,精准定位业务代码
'processors' => [
// 1. 注入进程ID(生成线程名)
new ProcessIdProcessor(),
// 2. 手动解析调用栈,获取实际业务代码位置 + 日志过滤
function ($record) {
$message = $record['message'];
// 线程名
$processId = $record['extra']['process'] ?? 0;
$record['thread'] = "thread-{$processId}";
// 手动解析调用栈,定位实际业务代码
$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 10); // 获取调用栈(前10层)
$file = $record['channel'];
$function = '';
$line = '0';
// 遍历调用栈,跳过日志类相关的层,找到业务代码层
foreach ($trace as $step) {
// 跳过 webman 日志类、monolog 相关的调用层
if (isset($step['file'])
&& str_contains($step['file'], 'support') === false
&& str_contains($step['file'], 'monolog') === false
&& str_contains($step['file'], 'Logger') === false
) {
// 获取文件路径(简化为项目内相对路径)
$file = $step['file'];
$projectRoot = base_path();
if (str_starts_with($file, $projectRoot)) {
$file = substr($file, strlen($projectRoot) + 1);
}
$file = str_replace('.php', '', $file);
$file = str_replace('\\', '.', $file);
// 获取方法名
$function = $step['function'];
// 获取行
$line = $step['line'] ?? '0';
break; // 找到第一个业务代码层就停止
}
}
$record['M'] = $function;
$record['L'] = $line;
$record['P'] = $record['extra']['process_id'];
// 解析 <RP_NAME:xxx> 标签
$pattern = '/<RP_NAME:(.*?)>/si';
if (preg_match_all($pattern, $message, $matches) && !empty($matches[1])) {
$record['logger'] = $matches[1][0];
$record['message'] = preg_replace($pattern, '', $message);
if ($matches[1][0] == '__default') $record['logger'] = $file;
} else {
$record['logger'] = $file;
$record['message'] = $message;
}
// 解析 SQL_LOG 相关标签
$tagPatterns = [
'logger' => '/<SQL_LOG_F>(.*?)<\/SQL_LOG_F>/s', // 文件路径
'L' => '/<SQL_LOG_L>(.*?)<\/SQL_LOG_L>/s', // 行号
'M' => '/<SQL_LOG_M>(.*?)<\/SQL_LOG_M>/s' // 方法名
];
$tempMessage = $record['message']; // 基于处理后的message继续解析
foreach ($tagPatterns as $key => $pattern) {
if (preg_match($pattern, $tempMessage, $matches)) {
$record[$key] = $matches[1];
$tempMessage = preg_replace($pattern, '', $tempMessage); // 移除当前标签
$record['message'] = trim($tempMessage);
}
}
// 日志级别颜色映射
$levelColorMap = [
Logger::EMERGENCY => "\033[41m\033[37m", // 最高级别:红色底色+白色字体
Logger::ALERT => "\033[41m\033[37m", // 警报:红色底色+白色字体
Logger::CRITICAL => "\033[41m\033[37m", // 严重:红色底色+白色字体
Logger::ERROR => "\033[31m", // 错误:红色字体(无底色)
Logger::WARNING => "\033[33m", // 警告:深黄色字体
Logger::INFO => "\033[32m", // 信息:绿色字体
Logger::DEBUG => "\033[34m", // 调试:蓝色字体
];
$record['start'] = "\033[0m";
$record['end'] = "\033[0m";
foreach ($levelColorMap as $level => $color) {
if ($record['level'] >= $level) {
$record['start'] = $color;
$record['end'] = "\033[0m";
break;
}
}
$record['Level'] = str_pad(strtoupper($record['level_name']), 5);
$logFilter = Config::getInstance()->logFilter;
$matchWildcard = function (string $pattern, string $value): bool {
// 空规则特殊处理:如:debug 拆分后类规则为空,代表匹配所有类
if ($pattern === '') {
return true;
}
// 将通配符*转换为正则的.*,并转义其他特殊字符
$regexPattern = preg_quote($pattern, '/');
$regexPattern = str_replace('\*', '.*', $regexPattern);
// 正则全程匹配
return preg_match('/^' . $regexPattern . '$/', $value) === 1;
};
if (!empty($logFilter)) {
$className = $record['logger'] ?? '';
$methodName = $record['M'] ?? '';
// 遍历过滤规则,匹配则返回null(过滤日志)
foreach ($logFilter as $filter) {
// 拆分规则为类规则和方法规则(最多拆2部分,避免方法名含:)
[$filterClassRule, $filterMethodRule] = array_pad(explode(':', $filter, 2), 2, '*');
// 匹配类名规则(支持通配符*
$isClassMatch = $matchWildcard($filterClassRule, $className);
// 匹配方法名规则(支持通配符*
$isMethodMatch = $matchWildcard($filterMethodRule, $methodName);
// 类和方法都匹配时,过滤该日志
if ($isClassMatch && $isMethodMatch) {
return null;
}
}
}
return $record;
}
],
],
];