import logging import sys from typing import Optional from app.core.config import settings try: from fluent import handler as fluent_handler except Exception: # pragma: no cover - 运行时兜底 fluent_handler = None def _build_fluent_handler() -> Optional[logging.Handler]: """ 创建 Fluentd Handler。 若 fluent-logger 未安装或初始化失败,返回 None。 """ if not settings.FLUENTD_ENABLED: return None if fluent_handler is None: logging.getLogger(__name__).warning("fluent-logger 未安装,已跳过 Fluentd 日志输出。") return None try: fh = fluent_handler.FluentHandler( tag=settings.FLUENTD_EFFECTIVE_TAG, host=settings.FLUENTD_HOST, port=settings.FLUENTD_PORT, timeout=settings.FLUENTD_TIMEOUT_SEC ) # 输出结构化字段,避免 record 退化为 string。 if hasattr(fluent_handler, "FluentRecordFormatter"): fh.setFormatter(fluent_handler.FluentRecordFormatter({ "level": "%(levelname)s", "logger": "%(name)s", "message": "%(message)s", "time": "%(asctime)s", "app": settings.APP_NAME, "env": settings.APP_ENV, })) return fh except Exception as e: logging.getLogger(__name__).exception("初始化 Fluentd Handler 失败: %s", str(e)) return None def setup_logging(): """ 配置日志系统,使其同时输出到文件和控制台。 这个函数应该在应用程序启动时只调用一次。 """ root_logger = logging.getLogger() # 避免重复初始化 if root_logger.handlers: return root_logger.setLevel(getattr(logging, settings.LOG_LEVEL.upper(), logging.INFO)) stream_formatter = logging.Formatter("%(asctime)s %(levelname)s [%(name)s] %(message)s") file_formatter = logging.Formatter("%(asctime)s %(levelname)s [%(name)s] %(message)s") console_handler = logging.StreamHandler(sys.stdout) console_handler.setFormatter(stream_formatter) root_logger.addHandler(console_handler) file_handler = logging.FileHandler('app.log', mode='w', encoding='utf-8') file_handler.setFormatter(file_formatter) root_logger.addHandler(file_handler) if not settings.FLUENTD_ENABLED: root_logger.info("日志系统已成功配置,将输出到控制台和 app.log 文件。") return fluentd_handler = _build_fluent_handler() if fluentd_handler: root_logger.addHandler(fluentd_handler) root_logger.info( "Fluentd日志输出已启用: host=%s port=%s tag=%s", settings.FLUENTD_HOST, settings.FLUENTD_PORT, settings.FLUENTD_EFFECTIVE_TAG ) root_logger.info("日志系统已成功配置,将输出到控制台和 app.log 文件。") def get_logger(name: str) -> logging.Logger: """ 获取一个指定名称的日志记录器实例。 假设 setup_logging() 已经在此之前被调用。 """ return logging.getLogger(name)