跳转至

日志

FORMAT_COLORED_DEFAULT module-attribute

FORMAT_COLORED_DEFAULT = '%(asctime)s [%(log_color)s%(levelname).1s%(reset)s] %(module)s:%(funcName)s:%(lineno)d - %(blue)s%(message)s'

FORMAT_DEFAULT module-attribute

FORMAT_DEFAULT = '%(asctime)s [%(levelname).1s] %(module)s:%(funcName)s:%(lineno)d - %(message)s'

FORMAT_NET_DEFAULT module-attribute

FORMAT_NET_DEFAULT = '%(asctime)s [%(levelname).1s] %(client_addr)s - "%(request_line)s" %(status_code)s'

DictConfigBuilder

支持从对象实例中提取配置, 并将其转换为 logging.config.dictConfig 兼容的字典配置 1. 内置LoggerFormat枚举的所有格式

源代码位于: logis/_log/util.py
class DictConfigBuilder:
    """
    支持从对象实例中提取配置,
    并将其转换为 logging.config.dictConfig 兼容的字典配置
    1. 内置LoggerFormat枚举的所有格式
    """

    def dir(self, log_dir: str):
        self._log_dir = Path(log_dir)
        return self

    def __init__(self):
        self._log_dir: Optional[Path] = None
        self.__dict_config__ = get_dict_config_tmpl()
        self.__loggers__: Dict[str, Dict[str, Any]] = self.__dict_config__["loggers"]
        self.__handlers__: Dict[str, Dict[str, Any]] = self.__dict_config__["handlers"]
        self.__filters__: Dict[str, Dict[str, Any]] = self.__dict_config__["filters"]
        self.__formatters__: Dict[str, Dict[str, Any]] = self.__dict_config__[
            "formatters"
        ]

        for e in LoggerFormat:
            if "color" in e.name.lower():
                formatter = ColoredFormatter(fmt=e.value)
            else:
                formatter = logging.Formatter(fmt=e.value)
            self.formatter(formatter, name=e.formatter_name())

    def build(self):
        return self.__dict_config__

    def filter(self, f: logging.Filter, name: Optional[str] = None):
        """
        将 logging.Filter 实例转换为 logging.config.dictConfig 兼容的字典配置

        返回格式符合 Python logging 官方 dict 配置规范,支持直接用于 dictConfig()
        """
        if not isinstance(f, logging.Filter):
            logging.warning("only logging.Filter instance supported now: %s", f)
            return self
        name = name if name is not None else get_name_attr(f)
        setattr(f, "name", name)
        filter_dict: dict = self.__filters__[name]
        filter_dict.update({"()": get_class_full_path(f.__class__)})
        return self

    def handler(self, handler: logging.Handler, name: Optional[str] = None):
        """
        将 logging.Handler 实例转换为 logging.config.dictConfig 兼容的字典配置

        返回格式符合 Python logging 官方 dict 配置规范,支持直接用于 dictConfig()
        """
        name = name if name is not None else handler.name
        if name is None:
            name = str(uuid4())
        handler.name = name

        format_filename_if_necessary(
            handler, log_dir=self._log_dir, ensure_suffix=".log"
        )

        for f in handler.filters:
            if get_name_attr(f) is None:
                setattr(f, "name", str(uuid4()))

        if formatter := getattr(handler, "formatter", None):
            formatter_name = getattr(formatter, "name", None)
            if formatter_name is None:
                formatter_name = str(uuid4())
            setattr(formatter, "name", formatter_name)

        handler_dict = handler_to_dict(handler)
        formatter = getattr(handler, "formatter", None)
        if formatter is not None:
            self.formatter(formatter)
        for f in handler.filters:
            self.filter(f)
        self.__handlers__[name].update(handler_dict)
        return self

    def formatter(self, formatter: logging.Formatter, name: Optional[str] = None):
        """
        将 logging.Formatter 实例转换为 logging.config.dictConfig 兼容的字典配置

        返回格式符合 Python logging 官方 dict 配置规范,支持直接用于 dictConfig()
        """
        if formatter is None:
            return self
        name = name or getattr(formatter, "name", None)
        setattr(formatter, "name", name)
        formatter_dict = formatter_to_dict(formatter)
        self.__formatters__[name].update(formatter_dict)
        return self

    def logger(self, logger: logging.Logger):
        """
        将 logging.Logger 实例转换为 logging.config.dictConfig 兼容的字典配置

        返回格式符合 Python logging 官方 dict 配置规范,支持直接用于 dictConfig()
        """
        if logger is None:
            return self
        logger_dict: dict = self.__loggers__[logger.name]
        handler_ids = []
        for handler in logger.handlers:
            self.handler(handler)
            handler_ids.append(handler.name)

        logger_dict.update(
            {
                "level": logger.getEffectiveLevel(),
                "propagate": "yes" if logger.propagate else "no",
                "handlers": handler_ids,
            }
        )

        return self

    def logger_dict(self, config: LoggerDictConfig):
        self.__loggers__[config.name].update(config.model_dump(exclude={"name"}))
        return self

    def handler_dict(self, config: HandlerDictConfig):
        config.filename = format_filename(
            config.filename, log_dir=self._log_dir, ensure_suffix=".log"
        )
        self.__handlers__[config.name].update(config.model_dump(exclude={"name"}))
        return self

__dict_config__ instance-attribute

__dict_config__ = get_dict_config_tmpl()

__filters__ instance-attribute

__filters__: Dict[str, Dict[str, Any]] = __dict_config__['filters']

__formatters__ instance-attribute

__formatters__: Dict[str, Dict[str, Any]] = __dict_config__['formatters']

__handlers__ instance-attribute

__handlers__: Dict[str, Dict[str, Any]] = __dict_config__['handlers']

__loggers__ instance-attribute

__loggers__: Dict[str, Dict[str, Any]] = __dict_config__['loggers']

__init__

__init__()
源代码位于: logis/_log/util.py
def __init__(self):
    self._log_dir: Optional[Path] = None
    self.__dict_config__ = get_dict_config_tmpl()
    self.__loggers__: Dict[str, Dict[str, Any]] = self.__dict_config__["loggers"]
    self.__handlers__: Dict[str, Dict[str, Any]] = self.__dict_config__["handlers"]
    self.__filters__: Dict[str, Dict[str, Any]] = self.__dict_config__["filters"]
    self.__formatters__: Dict[str, Dict[str, Any]] = self.__dict_config__[
        "formatters"
    ]

    for e in LoggerFormat:
        if "color" in e.name.lower():
            formatter = ColoredFormatter(fmt=e.value)
        else:
            formatter = logging.Formatter(fmt=e.value)
        self.formatter(formatter, name=e.formatter_name())

build

build()
源代码位于: logis/_log/util.py
def build(self):
    return self.__dict_config__

dir

dir(log_dir: str)
源代码位于: logis/_log/util.py
def dir(self, log_dir: str):
    self._log_dir = Path(log_dir)
    return self

filter

filter(f: Filter, name: Optional[str] = None)

将 logging.Filter 实例转换为 logging.config.dictConfig 兼容的字典配置

返回格式符合 Python logging 官方 dict 配置规范,支持直接用于 dictConfig()

源代码位于: logis/_log/util.py
def filter(self, f: logging.Filter, name: Optional[str] = None):
    """
    将 logging.Filter 实例转换为 logging.config.dictConfig 兼容的字典配置

    返回格式符合 Python logging 官方 dict 配置规范,支持直接用于 dictConfig()
    """
    if not isinstance(f, logging.Filter):
        logging.warning("only logging.Filter instance supported now: %s", f)
        return self
    name = name if name is not None else get_name_attr(f)
    setattr(f, "name", name)
    filter_dict: dict = self.__filters__[name]
    filter_dict.update({"()": get_class_full_path(f.__class__)})
    return self

formatter

formatter(formatter: Formatter, name: Optional[str] = None)

将 logging.Formatter 实例转换为 logging.config.dictConfig 兼容的字典配置

返回格式符合 Python logging 官方 dict 配置规范,支持直接用于 dictConfig()

源代码位于: logis/_log/util.py
def formatter(self, formatter: logging.Formatter, name: Optional[str] = None):
    """
    将 logging.Formatter 实例转换为 logging.config.dictConfig 兼容的字典配置

    返回格式符合 Python logging 官方 dict 配置规范,支持直接用于 dictConfig()
    """
    if formatter is None:
        return self
    name = name or getattr(formatter, "name", None)
    setattr(formatter, "name", name)
    formatter_dict = formatter_to_dict(formatter)
    self.__formatters__[name].update(formatter_dict)
    return self

handler

handler(handler: Handler, name: Optional[str] = None)

将 logging.Handler 实例转换为 logging.config.dictConfig 兼容的字典配置

返回格式符合 Python logging 官方 dict 配置规范,支持直接用于 dictConfig()

源代码位于: logis/_log/util.py
def handler(self, handler: logging.Handler, name: Optional[str] = None):
    """
    将 logging.Handler 实例转换为 logging.config.dictConfig 兼容的字典配置

    返回格式符合 Python logging 官方 dict 配置规范,支持直接用于 dictConfig()
    """
    name = name if name is not None else handler.name
    if name is None:
        name = str(uuid4())
    handler.name = name

    format_filename_if_necessary(
        handler, log_dir=self._log_dir, ensure_suffix=".log"
    )

    for f in handler.filters:
        if get_name_attr(f) is None:
            setattr(f, "name", str(uuid4()))

    if formatter := getattr(handler, "formatter", None):
        formatter_name = getattr(formatter, "name", None)
        if formatter_name is None:
            formatter_name = str(uuid4())
        setattr(formatter, "name", formatter_name)

    handler_dict = handler_to_dict(handler)
    formatter = getattr(handler, "formatter", None)
    if formatter is not None:
        self.formatter(formatter)
    for f in handler.filters:
        self.filter(f)
    self.__handlers__[name].update(handler_dict)
    return self

handler_dict

handler_dict(config: HandlerDictConfig)
源代码位于: logis/_log/util.py
def handler_dict(self, config: HandlerDictConfig):
    config.filename = format_filename(
        config.filename, log_dir=self._log_dir, ensure_suffix=".log"
    )
    self.__handlers__[config.name].update(config.model_dump(exclude={"name"}))
    return self

logger

logger(logger: Logger)

将 logging.Logger 实例转换为 logging.config.dictConfig 兼容的字典配置

返回格式符合 Python logging 官方 dict 配置规范,支持直接用于 dictConfig()

源代码位于: logis/_log/util.py
def logger(self, logger: logging.Logger):
    """
    将 logging.Logger 实例转换为 logging.config.dictConfig 兼容的字典配置

    返回格式符合 Python logging 官方 dict 配置规范,支持直接用于 dictConfig()
    """
    if logger is None:
        return self
    logger_dict: dict = self.__loggers__[logger.name]
    handler_ids = []
    for handler in logger.handlers:
        self.handler(handler)
        handler_ids.append(handler.name)

    logger_dict.update(
        {
            "level": logger.getEffectiveLevel(),
            "propagate": "yes" if logger.propagate else "no",
            "handlers": handler_ids,
        }
    )

    return self

logger_dict

logger_dict(config: LoggerDictConfig)
源代码位于: logis/_log/util.py
def logger_dict(self, config: LoggerDictConfig):
    self.__loggers__[config.name].update(config.model_dump(exclude={"name"}))
    return self

HandlerDictConfig

Handler配置条目

源代码位于: logis/_log/model/dict_config.py
class HandlerDictConfig(BaseModel):
    """
    Handler配置条目
    """

    model_config = DEFAULT_PYDANTIC_MODEL_CONFIG

    name: str
    clazz: Type[logging.Handler]
    level: int = logging.INFO
    formatter: Optional[str] = None
    filters: List[str] = []

    filename: Optional[str] = None
    max_bytes: Optional[int] = Field(100 * 1024 * 1024, alias="maxBytes")
    backup_count: Optional[int] = Field(3, alias="backupCount")
    when: Optional[str] = "d"
    encoding: str = "utf-8"
    delay: bool = True

    def model_dump(self, by_alias=True, exclude_none=True, **kwargs):
        exclude = kwargs.pop("exclude", set())
        exclude.add("clazz")
        if self.clazz is RotatingFileHandler:
            exclude.update({"when"})
        elif self.clazz is TimedRotatingFileHandler:
            exclude.update({"max_bytes"})
        elif not issubclass(self.clazz, logging.FileHandler):
            exclude.update(
                {"filename", "max_bytes", "backup_count", "when", "encoding", "delay"}
            )
        class_path = get_class_full_path(self.clazz)
        return {
            **super().model_dump(
                by_alias=by_alias, exclude=exclude, exclude_none=exclude_none, **kwargs
            ),
            **{"class": class_path},
        }

backup_count class-attribute instance-attribute

backup_count: Optional[int] = Field(3, alias='backupCount')

clazz instance-attribute

clazz: Type[Handler]

delay class-attribute instance-attribute

delay: bool = True

encoding class-attribute instance-attribute

encoding: str = 'utf-8'

filename class-attribute instance-attribute

filename: Optional[str] = None

filters class-attribute instance-attribute

filters: List[str] = []

formatter class-attribute instance-attribute

formatter: Optional[str] = None

level class-attribute instance-attribute

level: int = INFO

max_bytes class-attribute instance-attribute

max_bytes: Optional[int] = Field(100 * 1024 * 1024, alias='maxBytes')

model_config class-attribute instance-attribute

model_config = DEFAULT_PYDANTIC_MODEL_CONFIG

name instance-attribute

name: str

when class-attribute instance-attribute

when: Optional[str] = 'd'

model_dump

model_dump(by_alias=True, exclude_none=True, **kwargs)
源代码位于: logis/_log/model/dict_config.py
def model_dump(self, by_alias=True, exclude_none=True, **kwargs):
    exclude = kwargs.pop("exclude", set())
    exclude.add("clazz")
    if self.clazz is RotatingFileHandler:
        exclude.update({"when"})
    elif self.clazz is TimedRotatingFileHandler:
        exclude.update({"max_bytes"})
    elif not issubclass(self.clazz, logging.FileHandler):
        exclude.update(
            {"filename", "max_bytes", "backup_count", "when", "encoding", "delay"}
        )
    class_path = get_class_full_path(self.clazz)
    return {
        **super().model_dump(
            by_alias=by_alias, exclude=exclude, exclude_none=exclude_none, **kwargs
        ),
        **{"class": class_path},
    }

LoggerBuilder

日志构建器接口

源代码位于: logis/_log/builder.py
class LoggerBuilder:
    """
    日志构建器接口
    """

    def __init__(self, **kwargs) -> None:
        self.dir(Path(user_data_dir()) / "logs").name("root").level(
            logging.WARNING
        ).encoding("utf-8").backup_count(5).rotation_when("h").max_bytes(
            200 * 1024 * 1024
        ).propagate(
            False
        )

        self._handlers: Dict[str, logging.Handler] = defaultdict()

    def _resolve_file_path(self, filename: str) -> Optional[Path]:
        if not filename:
            return None
        return Path(format_filename(filename, self._log_dir))

    def name(self, name: str) -> "LoggerBuilder":
        """
        设置日志名称
        """
        self._name = name
        return self

    def propagate(self, propagate: bool) -> "LoggerBuilder":
        """
        设置是否 Propagate 日志到父 Logger
        """
        self._propagate = propagate
        return self

    def dir(self, path: Union[str, Path]) -> "LoggerBuilder":
        """
        设置日志目录
        """
        self._log_dir = path if isinstance(path, Path) else Path(path)
        self._log_dir.mkdir(parents=True, exist_ok=True)
        logging.debug("Log directory set to: %s", str(self._log_dir))
        return self

    def level(self, level: int = logging.WARNING) -> "LoggerBuilder":
        """
        设置root日志级别
        """
        level = format_level(level)
        self._level = level
        return self

    def max_bytes(self, max_bytes: int) -> "LoggerBuilder":
        """
        设置日志文件最大字节数
        """
        self._max_bytes = max_bytes
        return self

    def encoding(self, encoding: str) -> "LoggerBuilder":
        """
        设置日志文件编码
        """
        self._encoding = encoding
        return self

    def backup_count(self, count: int) -> "LoggerBuilder":
        """
        设置日志文件最大备份数量
        """
        self._backup_count = count
        return self

    def rotation_when(self, when: str) -> "LoggerBuilder":
        """
        设置日志文件轮转时间间隔单位
        """
        self._rotation_when = when
        return self

    def add_handler(
        self,
        handler_type: Union[Type[logging.Handler], logging.Handler],
        level: Optional[int] = None,
        filename: Optional[str] = None,
        name: Optional[str] = None,
        filters: Optional[List[logging.Filter]] = None,
        **kwargs,
    ) -> "LoggerBuilder":
        """
        添加日志处理器

        Args:
            handler_type: 日志处理器类型
            level: 日志级别
            filename: 日志文件名
            name: 日志处理器名称,如果未提供,则使用文件名或随机UUID
            filters: 日志过滤器列表
        """
        filename = self._resolve_file_path(filename)

        is_handler_instance = isinstance(handler_type, logging.Handler)
        default_handler_name = None
        if is_handler_instance:
            default_handler_name = handler_type.name
        handler_name = str(name or default_handler_name or filename or uuid4())

        if handler_name in self._handlers:
            logging.warning(
                "Handler with name %s already exists, will duplicate.", handler_name
            )

        if isinstance(handler_type, logging.FileHandler) or issubclass(
            handler_type, logging.FileHandler
        ):
            kwargs.setdefault("delay", True)
            kwargs.update(encoding=self._encoding, filename=filename)
        if handler_type is TimedRotatingFileHandler:
            handler = TimedRotatingFileHandler(
                when=self._rotation_when,
                backupCount=self._backup_count,
                **kwargs,
            )
        elif handler_type is RotatingFileHandler:
            handler = RotatingFileHandler(
                maxBytes=self._max_bytes,
                backupCount=self._backup_count,
                **kwargs,
            )
        elif handler_type is StreamHandler:
            handler = StreamHandler(stream=kwargs.get("stream", sys.stdout))
        elif is_handler_instance:
            handler = handler_type
        elif issubclass(handler_type, logging.Handler):
            handler = handler_type(**kwargs)
        else:
            raise ValueError(f"Unknown handler type: {handler_type}")

        add_formatter_if_not(handler)

        level = level if level is not None else self._level
        handler.setLevel(format_level(level))

        handler.name = handler_name

        for f in filters or []:
            handler.addFilter(f)

        self._handlers[handler_name] = handler
        return self

    def build(self) -> logging.Logger:
        assert self._name, "Logger name must be provided."
        name = self._name if self._name is not None else "root"
        logger = logging.getLogger(name)
        if self._level is not None:
            logger.setLevel(self._level)
        logger.propagate = self._propagate
        for _, handler in self._handlers.items():
            if handler is None:
                continue
            logger.addHandler(handler)
        return logger

__init__

__init__(**kwargs) -> None
源代码位于: logis/_log/builder.py
def __init__(self, **kwargs) -> None:
    self.dir(Path(user_data_dir()) / "logs").name("root").level(
        logging.WARNING
    ).encoding("utf-8").backup_count(5).rotation_when("h").max_bytes(
        200 * 1024 * 1024
    ).propagate(
        False
    )

    self._handlers: Dict[str, logging.Handler] = defaultdict()

add_handler

add_handler(handler_type: Union[Type[Handler], Handler], level: Optional[int] = None, filename: Optional[str] = None, name: Optional[str] = None, filters: Optional[List[Filter]] = None, **kwargs) -> LoggerBuilder

添加日志处理器

参数:

名称 类型 描述 默认
handler_type Union[Type[Handler], Handler]

日志处理器类型

必需
level Optional[int]

日志级别

None
filename Optional[str]

日志文件名

None
name Optional[str]

日志处理器名称,如果未提供,则使用文件名或随机UUID

None
filters Optional[List[Filter]]

日志过滤器列表

None
源代码位于: logis/_log/builder.py
def add_handler(
    self,
    handler_type: Union[Type[logging.Handler], logging.Handler],
    level: Optional[int] = None,
    filename: Optional[str] = None,
    name: Optional[str] = None,
    filters: Optional[List[logging.Filter]] = None,
    **kwargs,
) -> "LoggerBuilder":
    """
    添加日志处理器

    Args:
        handler_type: 日志处理器类型
        level: 日志级别
        filename: 日志文件名
        name: 日志处理器名称,如果未提供,则使用文件名或随机UUID
        filters: 日志过滤器列表
    """
    filename = self._resolve_file_path(filename)

    is_handler_instance = isinstance(handler_type, logging.Handler)
    default_handler_name = None
    if is_handler_instance:
        default_handler_name = handler_type.name
    handler_name = str(name or default_handler_name or filename or uuid4())

    if handler_name in self._handlers:
        logging.warning(
            "Handler with name %s already exists, will duplicate.", handler_name
        )

    if isinstance(handler_type, logging.FileHandler) or issubclass(
        handler_type, logging.FileHandler
    ):
        kwargs.setdefault("delay", True)
        kwargs.update(encoding=self._encoding, filename=filename)
    if handler_type is TimedRotatingFileHandler:
        handler = TimedRotatingFileHandler(
            when=self._rotation_when,
            backupCount=self._backup_count,
            **kwargs,
        )
    elif handler_type is RotatingFileHandler:
        handler = RotatingFileHandler(
            maxBytes=self._max_bytes,
            backupCount=self._backup_count,
            **kwargs,
        )
    elif handler_type is StreamHandler:
        handler = StreamHandler(stream=kwargs.get("stream", sys.stdout))
    elif is_handler_instance:
        handler = handler_type
    elif issubclass(handler_type, logging.Handler):
        handler = handler_type(**kwargs)
    else:
        raise ValueError(f"Unknown handler type: {handler_type}")

    add_formatter_if_not(handler)

    level = level if level is not None else self._level
    handler.setLevel(format_level(level))

    handler.name = handler_name

    for f in filters or []:
        handler.addFilter(f)

    self._handlers[handler_name] = handler
    return self

backup_count

backup_count(count: int) -> LoggerBuilder

设置日志文件最大备份数量

源代码位于: logis/_log/builder.py
def backup_count(self, count: int) -> "LoggerBuilder":
    """
    设置日志文件最大备份数量
    """
    self._backup_count = count
    return self

build

build() -> logging.Logger
源代码位于: logis/_log/builder.py
def build(self) -> logging.Logger:
    assert self._name, "Logger name must be provided."
    name = self._name if self._name is not None else "root"
    logger = logging.getLogger(name)
    if self._level is not None:
        logger.setLevel(self._level)
    logger.propagate = self._propagate
    for _, handler in self._handlers.items():
        if handler is None:
            continue
        logger.addHandler(handler)
    return logger

dir

dir(path: Union[str, Path]) -> LoggerBuilder

设置日志目录

源代码位于: logis/_log/builder.py
def dir(self, path: Union[str, Path]) -> "LoggerBuilder":
    """
    设置日志目录
    """
    self._log_dir = path if isinstance(path, Path) else Path(path)
    self._log_dir.mkdir(parents=True, exist_ok=True)
    logging.debug("Log directory set to: %s", str(self._log_dir))
    return self

encoding

encoding(encoding: str) -> LoggerBuilder

设置日志文件编码

源代码位于: logis/_log/builder.py
def encoding(self, encoding: str) -> "LoggerBuilder":
    """
    设置日志文件编码
    """
    self._encoding = encoding
    return self

level

level(level: int = logging.WARNING) -> LoggerBuilder

设置root日志级别

源代码位于: logis/_log/builder.py
def level(self, level: int = logging.WARNING) -> "LoggerBuilder":
    """
    设置root日志级别
    """
    level = format_level(level)
    self._level = level
    return self

max_bytes

max_bytes(max_bytes: int) -> LoggerBuilder

设置日志文件最大字节数

源代码位于: logis/_log/builder.py
def max_bytes(self, max_bytes: int) -> "LoggerBuilder":
    """
    设置日志文件最大字节数
    """
    self._max_bytes = max_bytes
    return self

name

name(name: str) -> LoggerBuilder

设置日志名称

源代码位于: logis/_log/builder.py
def name(self, name: str) -> "LoggerBuilder":
    """
    设置日志名称
    """
    self._name = name
    return self

propagate

propagate(propagate: bool) -> LoggerBuilder

设置是否 Propagate 日志到父 Logger

源代码位于: logis/_log/builder.py
def propagate(self, propagate: bool) -> "LoggerBuilder":
    """
    设置是否 Propagate 日志到父 Logger
    """
    self._propagate = propagate
    return self

rotation_when

rotation_when(when: str) -> LoggerBuilder

设置日志文件轮转时间间隔单位

源代码位于: logis/_log/builder.py
def rotation_when(self, when: str) -> "LoggerBuilder":
    """
    设置日志文件轮转时间间隔单位
    """
    self._rotation_when = when
    return self

LoggerDictConfig

Logger配置条目

源代码位于: logis/_log/model/dict_config.py
class LoggerDictConfig(BaseModel):
    """
    Logger配置条目
    """

    name: str
    level: int = logging.INFO
    propagate: bool = False
    handlers: List[str] = []

handlers class-attribute instance-attribute

handlers: List[str] = []

level class-attribute instance-attribute

level: int = INFO

name instance-attribute

name: str

propagate class-attribute instance-attribute

propagate: bool = False

LoggerFormat

日志格式

源代码位于: logis/_log/fmt.py
class LoggerFormat(Enum):
    """
    日志格式
    """

    DEFAULT = FORMAT_DEFAULT
    NET_DEFAULT = FORMAT_NET_DEFAULT
    COLORED_DEFAULT = FORMAT_COLORED_DEFAULT

    def formatter_name(self) -> str:
        return self.name.lower()

COLORED_DEFAULT class-attribute instance-attribute

COLORED_DEFAULT = FORMAT_COLORED_DEFAULT

DEFAULT class-attribute instance-attribute

DEFAULT = FORMAT_DEFAULT

NET_DEFAULT class-attribute instance-attribute

NET_DEFAULT = FORMAT_NET_DEFAULT

formatter_name

formatter_name() -> str
源代码位于: logis/_log/fmt.py
def formatter_name(self) -> str:
    return self.name.lower()

add_formatter_if_not

add_formatter_if_not(handler: Handler) -> None

如果给定的handler上尚未配置formatter, 则为其设置默认的formatter。

源代码位于: logis/_log/util.py
def add_formatter_if_not(handler: logging.Handler) -> None:
    """
    如果给定的handler上尚未配置formatter, 则为其设置默认的formatter。
    """
    if not handler:
        return
    if handler.formatter is not None:
        return
    if isinstance(handler, logging.FileHandler):
        handler.setFormatter(logging.Formatter(LoggerFormat.DEFAULT.value))
    else:
        handler.setFormatter(ColoredFormatter(LoggerFormat.COLORED_DEFAULT.value))

format_filename

format_filename(filename: str, log_dir: Optional[Path] = None, ensure_suffix: Optional[str] = '.log') -> str

格式化日志文件名,确保文件名以指定后缀结尾。 如果未指定后缀,则默认使用 ".log"。 如果未指定日志根目录,则直接返回文件名。 如果指定了日志根目录,则确保目录存在,并返回绝对路径。

源代码位于: logis/_log/util.py
def format_filename(
    filename: str,
    log_dir: Optional[Path] = None,
    ensure_suffix: Optional[str] = ".log",
) -> str:
    """
    格式化日志文件名,确保文件名以指定后缀结尾。
    如果未指定后缀,则默认使用 ".log"。
    如果未指定日志根目录,则直接返回文件名。
    如果指定了日志根目录,则确保目录存在,并返回绝对路径。
    """
    if ensure_suffix and not filename.endswith(ensure_suffix):
        filename += ensure_suffix
    if log_dir is None:
        return filename
    log_dir = Path(log_dir)
    log_dir.mkdir(parents=True, exist_ok=True)
    return str(log_dir / filename)

format_filename_if_necessary

format_filename_if_necessary(handler: Handler, log_dir: Optional[Path] = None, ensure_suffix: Optional[str] = '.log') -> None

如果给定的handler是文件处理器类型,则格式化其文件名。

源代码位于: logis/_log/util.py
def format_filename_if_necessary(
    handler: logging.Handler,
    log_dir: Optional[Path] = None,
    ensure_suffix: Optional[str] = ".log",
) -> None:
    """
    如果给定的handler是文件处理器类型,则格式化其文件名。
    """
    if not handler:
        return
    if isinstance(handler, logging.FileHandler):
        handler.baseFilename = format_filename(
            handler.baseFilename, log_dir, ensure_suffix
        )

format_level

format_level(level: Union[int, str]) -> int

将日志级别统一调整为logging内置常量

源代码位于: logis/_log/util.py
def format_level(level: Union[int, str]) -> int:
    """
    将日志级别统一调整为logging内置常量
    """
    if isinstance(level, str):
        level = level.upper()
        level = getattr(logging, level)
    return level

formatter_to_dict

formatter_to_dict(formatter: Formatter) -> Dict[str, Any]

将 logging.Formatter 实例转换为 logging.config.dictConfig 兼容的字典配置

源代码位于: logis/_log/util.py
def formatter_to_dict(formatter: logging.Formatter) -> Dict[str, Any]:
    """
    将 logging.Formatter 实例转换为 logging.config.dictConfig 兼容的字典配置
    """
    return {
        "class": get_class_full_path(formatter.__class__),
        "format": formatter._fmt,
        "datefmt": formatter.datefmt,
    }

get_default_console_handler

get_default_console_handler(level=logging.INFO) -> StreamHandler
源代码位于: logis/_log/handler.py
def get_default_console_handler(level=logging.INFO) -> StreamHandler:
    handler = StreamHandler(sys.stdout)
    handler.setFormatter(ColoredFormatter(fmt=LoggerFormat.COLORED_DEFAULT.value))
    handler.setLevel(level)
    return handler

get_default_dict_config_builder

get_default_dict_config_builder()

默认内置 1. 名为console的控制台Logger,日志级别INFO,彩色输出 2. 名为app的Logger,日志级别INFO,输出到控制台和文件

源代码位于: logis/_log/__init__.py
def get_default_dict_config_builder():
    """
    默认内置
    1. 名为console的控制台Logger,日志级别INFO,彩色输出
    2. 名为app的Logger,日志级别INFO,输出到控制台和文件
    """
    return (
        DictConfigBuilder()
        .handler(get_default_console_handler(), name="console")
        .handler_dict(
            HandlerDictConfig(
                name="error_file_handler",
                clazz=RotatingFileHandler,
                level=logging.ERROR,
                filename="error.log",
                formatter=LoggerFormat.DEFAULT.formatter_name(),
            )
        )
        .handler_dict(
            HandlerDictConfig(
                name="info_file_handler",
                clazz=TimedRotatingFileHandler,
                level=logging.INFO,
                filename="info.log",
                formatter=LoggerFormat.DEFAULT.formatter_name(),
            )
        )
        .logger_dict(
            LoggerDictConfig(
                name="root",
                level=logging.WARNING,
                handlers=["console", "error_file_handler"],
            )
        )
        .logger_dict(
            LoggerDictConfig(
                name="app",
                level=logging.INFO,
                handlers=["console", "info_file_handler", "error_file_handler"],
            )
        )
    )

get_dict_config_tmpl

get_dict_config_tmpl()

获取默认的 logging.config.dictConfig 字典配置模板

源代码位于: logis/_log/util.py
def get_dict_config_tmpl():
    """
    获取默认的 logging.config.dictConfig 字典配置模板
    """
    return {
        "version": 1,
        "disable_existing_loggers": False,
        "formatters": defaultdict(dict),
        "filters": defaultdict(dict),
        "handlers": defaultdict(dict),
        "loggers": defaultdict(dict),
    }

get_name_attr

get_name_attr(obj: Any) -> str

获取对象的名称, 如果对象没有名称,则返回其类型名。

源代码位于: logis/_log/util.py
def get_name_attr(obj: Any) -> str:
    """
    获取对象的名称, 如果对象没有名称,则返回其类型名。
    """
    return getattr(obj, "name", None)

handler_to_dict

handler_to_dict(handler: Handler) -> Dict[str, Any]

将 logging.Handler 实例转换为 logging.config.dictConfig 兼容的字典配置

源代码位于: logis/_log/util.py
def handler_to_dict(handler: logging.Handler) -> Dict[str, Any]:
    """
    将 logging.Handler 实例转换为 logging.config.dictConfig 兼容的字典配置
    """
    formatter = getattr(handler, "formatter", None)
    formatter_name = getattr(formatter, "name", None)
    handler_args: Dict[str, Any] = {
        "class": get_class_full_path(handler.__class__),
        "level": handler.level,
        "formatter": formatter_name,
        "filters": [
            get_name_attr(f)
            for f in handler.filters
            if isinstance(f, logging.Filter) and get_name_attr(f) is not None
        ],
    }
    if isinstance(handler, logging.FileHandler):
        handler_args["filename"] = handler.baseFilename
        handler_args["mode"] = handler.mode
        handler_args["encoding"] = handler.encoding
        handler_args["delay"] = handler.delay
    elif isinstance(handler, logging.StreamHandler):
        try:
            fileno = handler.stream.fileno()
            handler_args["stream"] = (
                "ext://sys.stdout" if fileno == 1 else "ext://sys.stderr"
            )
        except (AttributeError, OSError):
            handler_args["stream"] = "ext://sys.stdout"
    else:
        raise ValueError(f"Unsupported handler type: {handler}")

    if isinstance(handler, logging.handlers.RotatingFileHandler):
        handler_args["maxBytes"] = handler.maxBytes
        handler_args["backupCount"] = handler.backupCount
    elif isinstance(handler, logging.handlers.TimedRotatingFileHandler):
        handler_args["when"] = handler.when
        handler_args["interval"] = handler.interval
        handler_args["backupCount"] = handler.backupCount

    return handler_args