"""统一日志配置。 特性: - 五级日志:DEBUG / INFO / WARNING / ERROR / CRITICAL,默认 INFO - 同时输出到控制台和文件(RotatingFileHandler 按大小轮转) - 通过环境变量 ``MESHSAFE_LOG_LEVEL`` 控制级别 - 通过环境变量 ``MESHSAFE_LOG_DIR`` 控制日志文件目录 - 日志文件按 10 MB 轮转,保留最近 5 份 - 自动为 FastAPI / uvicorn / sqlalchemy 等第三方库设置一致的级别 """ from __future__ import annotations import logging import logging.handlers import os import sys from pathlib import Path from app.config import get_settings _LOG_FORMAT = "%(asctime)s [%(levelname)s] %(name)s - %(message)s" _DATE_FORMAT = "%Y-%m-%d %H:%M:%S" _MAX_BYTES = 10 * 1024 * 1024 # 10 MB _BACKUP_COUNT = 5 def setup_logging() -> None: """初始化全局日志配置。幂等:多次调用不会重复添加 handler。""" settings = get_settings() level_name = os.environ.get("MESHSAFE_LOG_LEVEL", settings.log_level).upper() level = getattr(logging, level_name, logging.INFO) log_dir = os.environ.get("MESHSAFE_LOG_DIR", "/var/log/meshsafe") Path(log_dir).mkdir(parents=True, exist_ok=True) root_logger = logging.getLogger() root_logger.setLevel(level) # 幂等:已初始化则跳过 if root_logger.handlers: # 更新级别即可 root_logger.setLevel(level) for handler in root_logger.handlers: handler.setLevel(level) return formatter = logging.Formatter(_LOG_FORMAT, datefmt=_DATE_FORMAT) # 控制台 handler console = logging.StreamHandler(sys.stdout) console.setLevel(level) console.setFormatter(formatter) root_logger.addHandler(console) # 文件 handler(按大小轮转) log_file = os.path.join(log_dir, "meshsafe.log") file_handler = logging.handlers.RotatingFileHandler( log_file, maxBytes=_MAX_BYTES, backupCount=_BACKUP_COUNT, encoding="utf-8", ) file_handler.setLevel(level) file_handler.setFormatter(formatter) root_logger.addHandler(file_handler) # 第三方库级别收敛:避免 uvicorn.access / sqlalchemy 刷屏 logging.getLogger("uvicorn.access").setLevel(logging.WARNING) logging.getLogger("sqlalchemy.engine").setLevel(logging.WARNING) logging.getLogger("redis").setLevel(logging.WARNING) logging.getLogger("pydantic").setLevel(logging.WARNING) # 启动日志 root_logger.info( "日志系统初始化完成:级别=%s,日志文件=%s(按 %d MB 轮转,保留 %d 份)", logging.getLevelName(level), log_file, _MAX_BYTES // (1024 * 1024), _BACKUP_COUNT, ) def get_logger(name: str) -> logging.Logger: """获取模块级 logger。""" return logging.getLogger(name)