From 5adf3930e2e0ab9ccd8c50e9626a867f780067c2 Mon Sep 17 00:00:00 2001 From: zimoyin <2556608754@qq.com> Date: Wed, 11 Mar 2026 03:58:21 +0800 Subject: [PATCH] =?UTF-8?q?fix(config):=20=E4=BF=AE=E5=A4=8DConfig?= =?UTF-8?q?=E7=B1=BB=E5=BE=AA=E7=8E=AF=E4=BE=9D=E8=B5=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/config/Config.php | 64 +++++++++++++++++--- app/utils/Logger.php | 137 +++++++++++++++++++++++++++++++++++++++++- config/log.php | 36 ----------- 3 files changed, 192 insertions(+), 45 deletions(-) diff --git a/app/config/Config.php b/app/config/Config.php index 57a8e0c..953ab76 100644 --- a/app/config/Config.php +++ b/app/config/Config.php @@ -2,8 +2,7 @@ namespace app\config; -use app\config\DatabaseConfig; -use app\utils\Logger; + class Config { @@ -83,10 +82,12 @@ class Config */ public bool $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() { + $this->detectCircularDependency(); $this->database = new DatabaseConfig(); $this->customProcess = require __DIR__ . '/custom_process_config.php'; $this->machineId = self::getStringEnv("MACHINE_ID", "0"); @@ -142,6 +144,52 @@ class Config $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 * 兼容Linux/Windows系统,失败时降级使用IP散列 @@ -201,8 +249,8 @@ class Config // 步骤4:补零为2位(如5→05,99→99) return str_pad($machineId, 2, '0', STR_PAD_LEFT); } catch (\Exception $e) { - // 极端异常时返回默认值(建议日志记录) - Logger::error('获取机器ID失败:', ['error' => $e->getMessage()]); + // 红色输出 + echo "\033[31m获取机器ID失败:{$e->getMessage()}\033[0m\n"; return '01'; // 默认机器ID } } diff --git a/app/utils/Logger.php b/app/utils/Logger.php index e895852..e7fda4f 100644 --- a/app/utils/Logger.php +++ b/app/utils/Logger.php @@ -2,6 +2,7 @@ namespace app\utils; +use app\config\Config; use support\Log; use Throwable; @@ -145,6 +146,7 @@ class LoggerIns public function info(string $message, array|Throwable $context = []): void { $processedMessage = $this->buildMessage($message, $context); + if (empty($processedMessage)) return; Log::info($processedMessage); } @@ -156,6 +158,7 @@ class LoggerIns public function debug(string $message, array|Throwable $context = []): void { $processedMessage = $this->buildMessage($message, $context); + if (empty($processedMessage)) return; Log::debug($processedMessage); } @@ -167,6 +170,7 @@ class LoggerIns public function warning(string $message, array|Throwable $context = []): void { $processedMessage = $this->buildMessage($message, $context); + if (empty($processedMessage)) return; Log::warning($processedMessage); } @@ -188,6 +192,7 @@ class LoggerIns public function error(string $message, array|Throwable $context = []): void { $processedMessage = $this->buildMessage($message, $context); + if (empty($processedMessage)) return; Log::error($processedMessage); } @@ -213,6 +218,7 @@ class LoggerIns } $processedMessage = $this->buildMessage($message, $context); + if (empty($processedMessage)) return; Log::{$level}($processedMessage); } @@ -237,7 +243,136 @@ class LoggerIns // 3. 添加名称前缀 [name] $namePrefix = "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; } /** diff --git a/config/log.php b/config/log.php index b6fec0a..19fe364 100644 --- a/config/log.php +++ b/config/log.php @@ -174,44 +174,8 @@ return [ } $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; } ], - ], ]; \ No newline at end of file