from __future__ import annotations import logging from pathlib import Path from typing import Optional APP_LOGGER_NAME = "sync_state_machine" class ScopeLoggerAdapter(logging.LoggerAdapter): def process(self, msg, kwargs): scope = self.extra.get("scope") if scope and not str(msg).startswith("["): msg = f"[{scope}] {msg}" return msg, kwargs class _NodeLogFilter(logging.Filter): def filter(self, record: logging.LogRecord) -> bool: message = record.getMessage() if "node=" in message: return False if "节点 " in message and "状态" in message: return False return True def get_app_logger() -> logging.Logger: return logging.getLogger(APP_LOGGER_NAME) def get_logger(name: Optional[str] = None) -> logging.Logger: if not name or name == "__main__": return get_app_logger() if name == APP_LOGGER_NAME or name.startswith(f"{APP_LOGGER_NAME}."): return logging.getLogger(name) return logging.getLogger(f"{APP_LOGGER_NAME}.{name}") def get_scope_logger(scope: str, name: Optional[str] = None) -> logging.LoggerAdapter: return ScopeLoggerAdapter(get_logger(name), {"scope": scope}) def clear_app_logger_handlers(logger: Optional[logging.Logger] = None) -> None: target = logger or get_app_logger() handlers = list(target.handlers) for handler in handlers: target.removeHandler(handler) try: handler.close() except Exception: pass target.propagate = True def init_app_logger( *, log_file_path: Optional[str] = None, level: int = logging.INFO, mirror_console: bool = True, replace_handlers: bool = True, suppress_node_logs: bool = True, ) -> logging.Logger: logger = get_app_logger() logger.setLevel(level) logger.propagate = False if logger.handlers and not replace_handlers: return logger if replace_handlers: clear_app_logger_handlers(logger) formatter = logging.Formatter("%(message)s") node_filter = _NodeLogFilter() if suppress_node_logs else None if mirror_console: console_handler = logging.StreamHandler() console_handler.setLevel(level) console_handler.setFormatter(formatter) if node_filter is not None: console_handler.addFilter(node_filter) logger.addHandler(console_handler) if log_file_path: path = Path(log_file_path) path.parent.mkdir(parents=True, exist_ok=True) file_handler = logging.FileHandler(path, mode="w", encoding="utf-8") file_handler.setLevel(level) file_handler.setFormatter(formatter) if node_filter is not None: file_handler.addFilter(node_filter) logger.addHandler(file_handler) return logger def configure_app_logging(config, *, replace_handlers: bool = False, mirror_console: bool = True) -> logging.Logger: if not getattr(config, "initialize", True): return get_app_logger() if not getattr(config, "file_path", None): from .config.logging_config import resolve_log_file_path config.file_path = resolve_log_file_path(config) return init_app_logger( log_file_path=config.file_path, level=config.level, mirror_console=mirror_console, replace_handlers=replace_handlers, suppress_node_logs=config.suppress_node_logs, ) def ensure_app_logging(config, *, mirror_console: bool = True) -> logging.Logger: logger = get_app_logger() if not getattr(config, "initialize", True): return logger if logger.handlers: return logger return configure_app_logging(config, replace_handlers=False, mirror_console=mirror_console)