fix(config): 修复Config类循环依赖
This commit is contained in:
+55
-7
@@ -2,8 +2,7 @@
|
|||||||
|
|
||||||
namespace app\config;
|
namespace app\config;
|
||||||
|
|
||||||
use app\config\DatabaseConfig;
|
|
||||||
use app\utils\Logger;
|
|
||||||
|
|
||||||
class Config
|
class Config
|
||||||
{
|
{
|
||||||
@@ -83,10 +82,12 @@ class Config
|
|||||||
*/
|
*/
|
||||||
public bool $blockMode {
|
public bool $blockMode {
|
||||||
get => $this->blockMode;
|
get => $this->blockMode;
|
||||||
set(bool $value){
|
|
||||||
Logger::error("禁止修改热修改阻断模式,这只是用于测试方法");
|
|
||||||
$this->blockMode = $value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function setBlockMode(bool $value)
|
||||||
|
{
|
||||||
|
echo "\033[31m获取禁止修改热修改阻断模式,这只是用于测试方法\033[0m\n";
|
||||||
|
$this->blockMode = $value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -113,6 +114,7 @@ class Config
|
|||||||
|
|
||||||
private function __construct()
|
private function __construct()
|
||||||
{
|
{
|
||||||
|
$this->detectCircularDependency();
|
||||||
$this->database = new DatabaseConfig();
|
$this->database = new DatabaseConfig();
|
||||||
$this->customProcess = require __DIR__ . '/custom_process_config.php';
|
$this->customProcess = require __DIR__ . '/custom_process_config.php';
|
||||||
$this->machineId = self::getStringEnv("MACHINE_ID", "0");
|
$this->machineId = self::getStringEnv("MACHINE_ID", "0");
|
||||||
@@ -142,6 +144,52 @@ class Config
|
|||||||
$this->enableVirtualCleanerParser = self::getBoolEnv('ENABLE_VIRTUAL_CLEANER_PARSER', false);
|
$this->enableVirtualCleanerParser = self::getBoolEnv('ENABLE_VIRTUAL_CLEANER_PARSER', false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function __clone() {}
|
||||||
|
|
||||||
|
private function detectCircularDependency(): void
|
||||||
|
{
|
||||||
|
// 获取调用栈(深度10层,足够覆盖依赖链)
|
||||||
|
$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 10);
|
||||||
|
$logRelatedPaths = [
|
||||||
|
'support/Log.php', // Log类文件
|
||||||
|
'support\Log.php', // Windows路径
|
||||||
|
'config/log.php', // 日志配置文件
|
||||||
|
'monolog' // 日志组件
|
||||||
|
];
|
||||||
|
|
||||||
|
// 遍历调用栈,检查是否包含日志相关文件
|
||||||
|
foreach ($trace as $step) {
|
||||||
|
if (isset($step['file'])) {
|
||||||
|
$file = strtolower($step['file']);
|
||||||
|
foreach ($logRelatedPaths as $path) {
|
||||||
|
if (str_contains($file, strtolower($path))) {
|
||||||
|
throw new \RuntimeException(
|
||||||
|
"循环依赖检测:Config类初始化时检测到日志组件({$step['file']})被引入\n" .
|
||||||
|
"请移除Config类中对日志的依赖,或调整日志配置加载逻辑。"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否直接调用了 Log 类
|
||||||
|
if (isset($step['class']) && str_contains(strtolower($step['class']), 'support\log')) {
|
||||||
|
throw new \RuntimeException(
|
||||||
|
"循环依赖检测:Config类被 support\Log 类直接调用,导致循环依赖!"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查当前文件是否主动引入了 Log 类(防止硬编码引入)
|
||||||
|
$includedFiles = get_included_files();
|
||||||
|
foreach ($includedFiles as $file) {
|
||||||
|
if (str_contains(strtolower($file), 'support/log')) {
|
||||||
|
throw new \RuntimeException(
|
||||||
|
"循环依赖检测:Config类加载时,support\Log 已被提前引入({$file}),禁止循环依赖!"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取服务器MAC地址并散列成2位数字(00-99)作为机器ID
|
* 获取服务器MAC地址并散列成2位数字(00-99)作为机器ID
|
||||||
* 兼容Linux/Windows系统,失败时降级使用IP散列
|
* 兼容Linux/Windows系统,失败时降级使用IP散列
|
||||||
@@ -201,8 +249,8 @@ class Config
|
|||||||
// 步骤4:补零为2位(如5→05,99→99)
|
// 步骤4:补零为2位(如5→05,99→99)
|
||||||
return str_pad($machineId, 2, '0', STR_PAD_LEFT);
|
return str_pad($machineId, 2, '0', STR_PAD_LEFT);
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
// 极端异常时返回默认值(建议日志记录)
|
// 红色输出
|
||||||
Logger::error('获取机器ID失败:', ['error' => $e->getMessage()]);
|
echo "\033[31m获取机器ID失败:{$e->getMessage()}\033[0m\n";
|
||||||
return '01'; // 默认机器ID
|
return '01'; // 默认机器ID
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+136
-1
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace app\utils;
|
namespace app\utils;
|
||||||
|
|
||||||
|
use app\config\Config;
|
||||||
use support\Log;
|
use support\Log;
|
||||||
use Throwable;
|
use Throwable;
|
||||||
|
|
||||||
@@ -145,6 +146,7 @@ class LoggerIns
|
|||||||
public function info(string $message, array|Throwable $context = []): void
|
public function info(string $message, array|Throwable $context = []): void
|
||||||
{
|
{
|
||||||
$processedMessage = $this->buildMessage($message, $context);
|
$processedMessage = $this->buildMessage($message, $context);
|
||||||
|
if (empty($processedMessage)) return;
|
||||||
Log::info($processedMessage);
|
Log::info($processedMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,6 +158,7 @@ class LoggerIns
|
|||||||
public function debug(string $message, array|Throwable $context = []): void
|
public function debug(string $message, array|Throwable $context = []): void
|
||||||
{
|
{
|
||||||
$processedMessage = $this->buildMessage($message, $context);
|
$processedMessage = $this->buildMessage($message, $context);
|
||||||
|
if (empty($processedMessage)) return;
|
||||||
Log::debug($processedMessage);
|
Log::debug($processedMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,6 +170,7 @@ class LoggerIns
|
|||||||
public function warning(string $message, array|Throwable $context = []): void
|
public function warning(string $message, array|Throwable $context = []): void
|
||||||
{
|
{
|
||||||
$processedMessage = $this->buildMessage($message, $context);
|
$processedMessage = $this->buildMessage($message, $context);
|
||||||
|
if (empty($processedMessage)) return;
|
||||||
Log::warning($processedMessage);
|
Log::warning($processedMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,6 +192,7 @@ class LoggerIns
|
|||||||
public function error(string $message, array|Throwable $context = []): void
|
public function error(string $message, array|Throwable $context = []): void
|
||||||
{
|
{
|
||||||
$processedMessage = $this->buildMessage($message, $context);
|
$processedMessage = $this->buildMessage($message, $context);
|
||||||
|
if (empty($processedMessage)) return;
|
||||||
Log::error($processedMessage);
|
Log::error($processedMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -213,6 +218,7 @@ class LoggerIns
|
|||||||
}
|
}
|
||||||
|
|
||||||
$processedMessage = $this->buildMessage($message, $context);
|
$processedMessage = $this->buildMessage($message, $context);
|
||||||
|
if (empty($processedMessage)) return;
|
||||||
Log::{$level}($processedMessage);
|
Log::{$level}($processedMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,7 +243,136 @@ class LoggerIns
|
|||||||
|
|
||||||
// 3. 添加名称前缀 [name]
|
// 3. 添加名称前缀 [name]
|
||||||
$namePrefix = "<RP_NAME:{$this->name}>";
|
$namePrefix = "<RP_NAME:{$this->name}>";
|
||||||
return $namePrefix . $processedMessage;
|
$result = $namePrefix . $processedMessage;
|
||||||
|
if ($this->isFilter()) return "";
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断当前日志是否需要过滤(核心过滤逻辑)
|
||||||
|
* @return bool true=需要过滤,false=保留日志
|
||||||
|
*/
|
||||||
|
private function isFilter(): bool
|
||||||
|
{
|
||||||
|
$logFilter = Config::getInstance()->logFilter;
|
||||||
|
// 无过滤规则时直接返回false(不过滤)
|
||||||
|
if (empty($logFilter)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. 解析调用堆栈,获取业务代码的类名/方法名
|
||||||
|
$stackInfo = $this->parseBusinessStackInfo();
|
||||||
|
$className = $stackInfo['class'] ?? $this->name ?? '';
|
||||||
|
$methodName = $stackInfo['method'] ?? '';
|
||||||
|
|
||||||
|
// 2. 通配符匹配函数
|
||||||
|
$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;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 3. 遍历过滤规则,匹配则返回true(需要过滤)
|
||||||
|
foreach ($logFilter as $filter) {
|
||||||
|
// 跳过非法过滤规则
|
||||||
|
if (!is_string($filter)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// 拆分规则为类规则和方法规则(最多拆2部分,避免方法名含:)
|
||||||
|
[$filterClassRule, $filterMethodRule] = array_pad(explode(':', $filter, 2), 2, '*');
|
||||||
|
|
||||||
|
// 匹配类名规则(支持通配符*)
|
||||||
|
$isClassMatch = $matchWildcard($filterClassRule, $className);
|
||||||
|
// 匹配方法名规则(支持通配符*)
|
||||||
|
$isMethodMatch = $matchWildcard($filterMethodRule, $methodName);
|
||||||
|
|
||||||
|
// 类和方法都匹配时,返回true(过滤该日志)
|
||||||
|
if ($isClassMatch && $isMethodMatch) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 无匹配规则,返回false(保留日志)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析调用堆栈,获取实际业务代码的类名和方法名
|
||||||
|
* @return array ['class' => 业务类名, 'method' => 业务方法名]
|
||||||
|
*/
|
||||||
|
private function parseBusinessStackInfo(): array
|
||||||
|
{
|
||||||
|
$stackInfo = ['class' => '', 'method' => ''];
|
||||||
|
// 获取调用栈(忽略参数,避免性能损耗,取前10层足够)
|
||||||
|
$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 10);
|
||||||
|
|
||||||
|
// 跳过的日志相关类/方法(根据实际项目调整)
|
||||||
|
$skipPatterns = [
|
||||||
|
'class' => [
|
||||||
|
'Monolog\\', // 跳过Monolog核心类
|
||||||
|
__CLASS__, // 跳过当前类自身
|
||||||
|
'app\\logger\\', // 跳过大日志封装类(根据你的项目调整)
|
||||||
|
],
|
||||||
|
'method' => [
|
||||||
|
'buildMessage', // 跳过当前方法
|
||||||
|
'isFilter', // 跳过过滤方法
|
||||||
|
'parseBusinessStackInfo', // 跳过堆栈解析方法
|
||||||
|
'replacePlaceholders',// 跳過占位符替换方法
|
||||||
|
'formatExceptionStackTrace', // 跳过异常格式化方法
|
||||||
|
'log', 'error', 'info', 'warning', 'debug' // 跳过日志级别方法
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
// 遍历调用栈,找到第一个业务代码层
|
||||||
|
foreach ($trace as $step) {
|
||||||
|
// 跳过无文件/无方法的栈帧
|
||||||
|
if (!isset($step['file']) || !isset($step['function'])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 跳过日志相关类
|
||||||
|
$currentClass = $step['class'] ?? '';
|
||||||
|
$isSkipClass = false;
|
||||||
|
foreach ($skipPatterns['class'] as $pattern) {
|
||||||
|
if ($pattern && str_contains($currentClass, $pattern)) {
|
||||||
|
$isSkipClass = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($isSkipClass) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 跳过日志相关方法
|
||||||
|
$currentMethod = $step['function'];
|
||||||
|
if (in_array($currentMethod, $skipPatterns['method'])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析业务类名(简化为项目内相对路径,和原逻辑一致)
|
||||||
|
$projectRoot = defined('BASE_PATH') ? BASE_PATH : (function_exists('base_path') ? base_path() : '');
|
||||||
|
$file = $step['file'];
|
||||||
|
if ($projectRoot && str_starts_with($file, $projectRoot)) {
|
||||||
|
$file = substr($file, strlen($projectRoot) + 1);
|
||||||
|
// 转换为类名格式(app/controller/Index.php → app.controller.Index)
|
||||||
|
$class = str_replace(['.php', '\\', '/'], ['', '.', '.'], $file);
|
||||||
|
} else {
|
||||||
|
$class = $currentClass ?: basename($file, '.php');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 赋值并终止遍历(找到第一个业务层即可)
|
||||||
|
$stackInfo['class'] = trim($class);
|
||||||
|
$stackInfo['method'] = $currentMethod;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $stackInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -174,44 +174,8 @@ return [
|
|||||||
}
|
}
|
||||||
|
|
||||||
$record['Level'] = str_pad(strtoupper($record['level_name']), 5);
|
$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;
|
return $record;
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
Reference in New Issue
Block a user