Quellcode durchsuchen

优化日志配置,添加统一的日志配置工具以避免重复日志输出,更新相关模块的日志设置,确保日志处理器不重复添加并控制日志传播。

drggboy vor 6 Tagen
Ursprung
Commit
1ccde4b3d8
4 geänderte Dateien mit 224 neuen und 12 gelöschten Zeilen
  1. 10 6
      app/api/cd_flux.py
  2. 10 6
      app/services/cd_flux_service.py
  3. 103 0
      app/utils/logger_config.py
  4. 101 0
      docs/logging_best_practices.md

+ 10 - 6
app/api/cd_flux.py

@@ -19,13 +19,17 @@ DEFAULT_MAP_FILENAME = "fluxcd_input_map.jpg"
 DEFAULT_HIST_FILENAME = "fluxcd_input_histogram.jpg"
 DEFAULT_CSV_FILENAME = "fluxcd_input.csv"
 LATEST_RESULT_FILENAME = "latest_result.json"
-# 配置日志
+# 配置日志 - 避免重复日志输出
 logger = logging.getLogger(__name__)
-logger.setLevel(logging.INFO)
-handler = logging.StreamHandler()
-formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
-handler.setFormatter(formatter)
-logger.addHandler(handler)
+# 避免重复添加处理器导致重复日志
+if not logger.handlers:
+    logger.setLevel(logging.INFO)
+    handler = logging.StreamHandler()
+    formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
+    handler.setFormatter(formatter)
+    logger.addHandler(handler)
+    # 关闭日志传播,避免与父级日志处理器重复输出
+    logger.propagate = False
 
 router = APIRouter()
 

+ 10 - 6
app/services/cd_flux_service.py

@@ -14,13 +14,17 @@ from app.database import SessionLocal
 from app.models import FarmlandData, FluxCdOutputData, FluxCdInputData
 from app.utils.mapping_utils import MappingUtils, csv_to_raster_workflow
 
-# 配置日志
+# 配置日志 - 避免重复日志输出
 logger = logging.getLogger(__name__)
-logger.setLevel(logging.INFO)
-handler = logging.StreamHandler()
-formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
-handler.setFormatter(formatter)
-logger.addHandler(handler)
+# 避免重复添加处理器导致重复日志
+if not logger.handlers:
+    logger.setLevel(logging.INFO)
+    handler = logging.StreamHandler()
+    formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
+    handler.setFormatter(formatter)
+    logger.addHandler(handler)
+    # 关闭日志传播,避免与父级日志处理器重复输出
+    logger.propagate = False
 
 def get_base_dir():
     """获取基础目录路径(与土地数据处理函数一致)"""

+ 103 - 0
app/utils/logger_config.py

@@ -0,0 +1,103 @@
+"""
+统一的日志配置工具
+用于在整个项目中统一管理日志配置,避免重复日志输出
+"""
+
+import logging
+from typing import Optional
+
+
+def setup_logger(
+    name: str,
+    level: int = logging.INFO,
+    propagate: bool = False,
+    formatter_string: Optional[str] = None
+) -> logging.Logger:
+    """
+    设置并配置logger,避免重复日志输出
+    
+    @param name: logger名称,通常使用 __name__
+    @param level: 日志级别,默认为 INFO
+    @param propagate: 是否向父级logger传播,默认为False,避免重复输出
+    @param formatter_string: 自定义格式字符串,如果不提供则使用默认格式
+    @return: 配置好的logger对象
+    """
+    logger = logging.getLogger(name)
+    
+    # 避免重复添加处理器导致重复日志
+    if not logger.handlers:
+        logger.setLevel(level)
+        
+        # 创建控制台处理器
+        handler = logging.StreamHandler()
+        
+        # 设置日志格式
+        if formatter_string is None:
+            formatter_string = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
+        formatter = logging.Formatter(formatter_string)
+        handler.setFormatter(formatter)
+        
+        # 添加处理器
+        logger.addHandler(handler)
+        
+        # 控制日志传播
+        logger.propagate = propagate
+    
+    return logger
+
+
+def get_api_logger(module_name: str) -> logging.Logger:
+    """
+    为API模块获取标准logger配置
+    
+    @param module_name: 模块名称,通常使用 __name__
+    @return: 配置好的logger对象
+    """
+    return setup_logger(
+        name=module_name,
+        level=logging.INFO,
+        propagate=False  # API层不传播到父级,避免与uvicorn日志冲突
+    )
+
+
+def get_service_logger(module_name: str) -> logging.Logger:
+    """
+    为Service模块获取标准logger配置
+    
+    @param module_name: 模块名称,通常使用 __name__
+    @return: 配置好的logger对象
+    """
+    return setup_logger(
+        name=module_name,
+        level=logging.INFO,
+        propagate=False  # Service层不传播到父级,保持独立
+    )
+
+
+def get_utils_logger(module_name: str) -> logging.Logger:
+    """
+    为工具模块获取标准logger配置
+    
+    @param module_name: 模块名称,通常使用 __name__
+    @return: 配置好的logger对象
+    """
+    return setup_logger(
+        name=module_name,
+        level=logging.INFO,
+        propagate=False  # 工具层不传播到父级,保持独立
+    )
+
+
+# 主应用日志配置函数
+def configure_root_logging(level: int = logging.INFO) -> None:
+    """
+    配置根日志系统,只在应用启动时调用一次
+    
+    @param level: 根日志级别
+    """
+    # 检查是否已经配置过,避免重复配置
+    if not logging.getLogger().handlers:
+        logging.basicConfig(
+            level=level,
+            format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
+        )

+ 101 - 0
docs/logging_best_practices.md

@@ -0,0 +1,101 @@
+# 日志配置最佳实践
+
+## 问题背景
+
+在项目中出现了重复日志的问题,主要原因是:
+
+1. **多重日志处理器配置**:多个模块都手动配置了 `StreamHandler`
+2. **日志传播机制**:子 logger 的日志会传播到父 logger,如果都有处理器就会重复输出
+3. **缺乏统一配置**:各个模块独立配置日志,没有统一的配置模式
+
+## 解决方案
+
+### 1. 统一日志配置工具
+
+创建了 `app/utils/logger_config.py` 提供统一的日志配置功能:
+
+```python
+from app.utils.logger_config import get_api_logger
+
+# 在API模块中使用
+logger = get_api_logger(__name__)
+logger.info("这是API日志")
+```
+
+### 2. 避免重复配置的关键点
+
+```python
+# ✅ 正确的配置方式
+logger = logging.getLogger(__name__)
+if not logger.handlers:  # 检查是否已配置
+    logger.setLevel(logging.INFO)
+    handler = logging.StreamHandler()
+    formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
+    handler.setFormatter(formatter)
+    logger.addHandler(handler)
+    logger.propagate = False  # 关闭传播,避免重复输出
+
+# ❌ 错误的配置方式(会导致重复日志)
+logger = logging.getLogger(__name__)
+logger.setLevel(logging.INFO)
+handler = logging.StreamHandler()  # 每次都添加新的处理器
+logger.addHandler(handler)  # 重复添加处理器
+```
+
+### 3. 不同模块的使用方式
+
+#### API模块
+```python
+from app.utils.logger_config import get_api_logger
+
+logger = get_api_logger(__name__)
+logger.info("API操作日志")
+```
+
+#### Service模块
+```python
+from app.utils.logger_config import get_service_logger
+
+logger = get_service_logger(__name__)
+logger.info("业务逻辑日志")
+```
+
+#### 工具模块
+```python
+from app.utils.logger_config import get_utils_logger
+
+logger = get_utils_logger(__name__)
+logger.info("工具函数日志")
+```
+
+## 修复的文件
+
+已修复以下文件中的重复日志配置:
+
+1. `app/api/water.py` - 添加了处理器检查和传播控制
+2. `app/api/cd_flux.py` - 添加了处理器检查和传播控制  
+3. `app/services/cd_flux_service.py` - 添加了处理器检查和传播控制
+4. `app/utils/mapping_utils.py` - 已经有正确的配置
+
+## 测试方法
+
+运行演示脚本来验证修复效果:
+
+```bash
+conda activate GeoSys
+python scripts/demos/logging_demo.py
+```
+
+## 最佳实践总结
+
+1. **使用统一配置工具**:优先使用 `app/utils/logger_config.py` 中的函数
+2. **检查处理器**:配置前检查 `if not logger.handlers:`
+3. **控制传播**:设置 `logger.propagate = False` 避免重复输出
+4. **避免重复配置**:不要在多个地方配置同一个logger
+5. **根日志配置**:只在应用启动时配置一次根日志系统
+
+## 注意事项
+
+- 在类中使用日志时,建议在 `__init__` 方法中配置,避免每次调用都重新配置
+- 对于 FastAPI 应用,uvicorn 已经有自己的日志配置,避免与其冲突
+- 测试环境和生产环境可能需要不同的日志级别和输出方式