diff --git a/utils/log/_config.py b/utils/log/_config.py index dda61779..cd3c1628 100644 --- a/utils/log/_config.py +++ b/utils/log/_config.py @@ -1,6 +1,6 @@ from multiprocessing import RLock as Lock from pathlib import Path -from typing import List, Optional, Union +from typing import List, Literal, Optional, Union from pydantic import BaseSettings @@ -22,17 +22,28 @@ class LoggerConfig(BaseSettings): cls._instance = result return cls._instance - name: str = "logger" + name: str = "PaiGram-logger" + """logger 名称""" level: Optional[Union[str, int]] = None + """logger 的 level""" debug: bool = False + """是否 debug""" width: Optional[int] = None + """输出时的宽度""" keywords: List[str] = [] + """高亮的关键字""" time_format: str = "[%Y-%m-%d %X]" + """时间格式""" capture_warnings: bool = True + """是否捕获 warning""" + color_system: Literal["auto", "standard", "256", "truecolor", "windows"] = "auto" + """颜色模式: 自动、标准、256色、真彩、Windows模式""" log_path: Union[str, Path] = "./logs" + """log 所保存的路径,项目根目录的相对路径""" project_root: Union[str, Path] = PROJECT_ROOT + """项目根目录""" traceback_max_frames: int = 20 traceback_locals_max_depth: Optional[int] = None diff --git a/utils/log/_handler.py b/utils/log/_handler.py index caa8a42f..39e8432a 100644 --- a/utils/log/_handler.py +++ b/utils/log/_handler.py @@ -1,9 +1,17 @@ import logging import os -import sys from datetime import datetime from pathlib import Path -from typing import Any, Callable, Iterable, List, Literal, Optional, TYPE_CHECKING, Union +from typing import ( + Any, + Callable, + Iterable, + List, + Literal, + Optional, + TYPE_CHECKING, + Union, +) from rich.console import Console from rich.logging import LogRender as DefaultLogRender, RichHandler as DefaultRichHandler @@ -15,22 +23,26 @@ from utils.log._file import FileIO from utils.log._style import DEFAULT_STYLE from utils.log._traceback import Traceback +try: + import ujson as json + from ujson import JSONDecodeError +except ImportError: + import json + from json import JSONDecodeError + if TYPE_CHECKING: - from logging import LogRecord # pylint: disable=unused-import + from rich.console import ( + ConsoleRenderable, + RenderableType, + ) + from logging import LogRecord - from rich.console import ConsoleRenderable, RenderableType # pylint: disable=unused-import - -__all__ = ("LogRender", "Handler", "FileHandler") +__all__ = ["LogRender", "Handler", "FileHandler"] FormatTimeCallable = Callable[[datetime], Text] logging.addLevelName(5, "TRACE") logging.addLevelName(25, "SUCCESS") -color_system: Literal["windows", "truecolor"] -if sys.platform == "win32": - color_system = "windows" -else: - color_system = "truecolor" class LogRender(DefaultLogRender): @@ -108,7 +120,9 @@ class Handler(DefaultRichHandler): tracebacks_max_frames: int = 100, keywords: Optional[List[str]] = None, log_time_format: Union[str, FormatTimeCallable] = "[%x %X]", - project_root: Union[str, Path] = "./logs", + color_system: Literal["auto", "standard", "256", "truecolor", "windows"] = "auto", + project_root: Union[str, Path] = os.getcwd(), + auto_load_json: bool = False, **kwargs, ) -> None: super(Handler, self).__init__(*args, rich_tracebacks=rich_tracebacks, **kwargs) @@ -116,9 +130,10 @@ class Handler(DefaultRichHandler): self.console = Console(color_system=color_system, theme=Theme(DEFAULT_STYLE), width=width) self.tracebacks_show_locals = True self.tracebacks_max_frames = tracebacks_max_frames - self.keywords = self.KEYWORDS + (keywords or []) + self.render_keywords = self.KEYWORDS + (keywords or []) self.locals_max_depth = locals_max_depth self.project_root = project_root + self.auto_load_json = auto_load_json def render( self, @@ -153,15 +168,13 @@ class Handler(DefaultRichHandler): log_time = datetime.fromtimestamp(record.created) if not traceback: - traceback_content = [message_renderable] - elif message_renderable is not None: - traceback_content = [message_renderable, traceback] + renderables = [message_renderable] else: - traceback_content = [traceback] + renderables = [message_renderable, traceback] if message_renderable is not None else [traceback] log_renderable = self._log_render( self.console, - traceback_content, + renderables, log_time=log_time, time_format=time_format, level=_level, @@ -177,10 +190,8 @@ class Handler(DefaultRichHandler): message: Any, ) -> "ConsoleRenderable": use_markup = getattr(record, "markup", self.markup) - tag = getattr(record, "tag", None) if isinstance(message, str): message_text = Text.from_markup(message) if use_markup else Text(message) - message_text = (Text.from_markup(f"[purple][{tag}][/]") + message_text) if tag is not None else message_text highlighter = getattr(record, "highlighter", self.highlighter) else: from rich.highlighter import JSONHighlighter @@ -188,17 +199,16 @@ class Handler(DefaultRichHandler): highlighter = JSONHighlighter() message_text = JSON.from_data(message, indent=4).text - message_text = (Text.from_markup(f"[purple][{tag}][/]") + message_text) if tag is not None else message_text if highlighter is not None: # noinspection PyCallingNonCallable message_text = highlighter(message_text) - if self.keywords is None: - self.keywords = self.KEYWORDS + if self.render_keywords is None: + self.render_keywords = self.KEYWORDS - if self.keywords: - message_text.highlight_words(self.keywords, "logging.keyword") + if self.render_keywords: + message_text.highlight_words(self.render_keywords, "logging.keyword") return message_text @@ -209,22 +219,23 @@ class Handler(DefaultRichHandler): exc_type, exc_value, exc_traceback = record.exc_info if exc_type is None or exc_value is None: raise ValueError(record) - _traceback = Traceback.from_exception( - exc_type, - exc_value, - exc_traceback, - width=self.tracebacks_width, - extra_lines=self.tracebacks_extra_lines, - word_wrap=self.tracebacks_word_wrap, - show_locals=(getattr(record, "show_locals", None) or self.tracebacks_show_locals), - locals_max_length=(getattr(record, "locals_max_length", None) or self.locals_max_length), - locals_max_string=(getattr(record, "locals_max_string", None) or self.locals_max_string), - locals_max_depth=( - record.locals_max_depth if hasattr(record, "locals_max_depth") else self.locals_max_depth - ), - suppress=self.tracebacks_suppress, - max_frames=self.tracebacks_max_frames, - ) + try: + _traceback = Traceback.from_exception( + exc_type, + exc_value, + exc_traceback, + width=self.tracebacks_width, + extra_lines=self.tracebacks_extra_lines, + word_wrap=self.tracebacks_word_wrap, + show_locals=(getattr(record, "show_locals", None) or self.tracebacks_show_locals), + locals_max_length=(getattr(record, "locals_max_length", None) or self.locals_max_length), + locals_max_string=(getattr(record, "locals_max_string", None) or self.locals_max_string), + locals_max_depth=getattr(record, "locals_max_depth", self.locals_max_depth), + suppress=self.tracebacks_suppress, + max_frames=self.tracebacks_max_frames, + ) + except ImportError: + return message = record.getMessage() if self.formatter: record.message = record.getMessage() @@ -235,10 +246,15 @@ class Handler(DefaultRichHandler): if message == str(exc_value): message = None + message_renderable = None if message is not None: - message_renderable = self.render_message(record, message) - else: - message_renderable = None + try: + if self.auto_load_json: + message_renderable = self.render_message(record, json.loads(message)) + except JSONDecodeError: + pass + finally: + message_renderable = message_renderable or self.render_message(record, message) log_renderable = self.render(record=record, traceback=_traceback, message_renderable=message_renderable) # noinspection PyBroadException try: diff --git a/utils/log/_logger.py b/utils/log/_logger.py index 66a391d8..fc8b734f 100644 --- a/utils/log/_logger.py +++ b/utils/log/_logger.py @@ -6,7 +6,7 @@ import traceback as traceback_ from multiprocessing import RLock as Lock from pathlib import Path from types import TracebackType -from typing import TYPE_CHECKING, Any, Callable, List, Mapping, Optional, Tuple, Type, Union +from typing import Any, Callable, List, Mapping, Optional, TYPE_CHECKING, Tuple, Type, Union from typing_extensions import Self @@ -14,9 +14,9 @@ from utils.log._handler import FileHandler, Handler from utils.typedefs import LogFilterType if TYPE_CHECKING: - from logging import LogRecord # pylint: disable=unused-import + from logging import LogRecord - from utils.log._config import LoggerConfig # pylint: disable=unused-import + from utils.log._config import LoggerConfig __all__ = ("Logger", "LogFilter") @@ -52,40 +52,25 @@ class Logger(logging.Logger): # skipcq: PY-A6006 ) log_path = Path(self.config.project_root).joinpath(self.config.log_path) + handler_config = { + "width": self.config.width, + "keywords": self.config.keywords, + "locals_max_length": self.config.traceback_locals_max_length, + "locals_max_string": self.config.traceback_locals_max_string, + "project_root": self.config.project_root, + "log_time_format": self.config.time_format, + } handler, debug_handler, error_handler = ( # 控制台 log 配置 - Handler( - width=self.config.width, - keywords=self.config.keywords, - locals_max_length=self.config.traceback_locals_max_length, - locals_max_string=self.config.traceback_locals_max_string, - locals_max_depth=self.config.traceback_locals_max_depth, - project_root=self.config.project_root, - log_time_format=self.config.time_format, - ), + Handler(color_system=self.config.color_system, **handler_config), # debug.log 配置 - FileHandler( - width=self.config.width, - keywords=self.config.keywords, - level=10, - path=log_path.joinpath("debug/debug.log"), - locals_max_depth=1, - locals_max_length=self.config.traceback_locals_max_length, - locals_max_string=self.config.traceback_locals_max_string, - project_root=self.config.project_root, - log_time_format=self.config.time_format, - ), + FileHandler(level=10, path=log_path.joinpath("debug/debug.log"), locals_max_depth=1, **handler_config), # error.log 配置 FileHandler( - width=self.config.width, - keywords=self.config.keywords, level=40, path=log_path.joinpath("error/error.log"), - locals_max_length=self.config.traceback_locals_max_length, - locals_max_string=self.config.traceback_locals_max_string, locals_max_depth=self.config.traceback_locals_max_depth, - project_root=self.config.project_root, - log_time_format=self.config.time_format, + **handler_config, ), ) logging.basicConfig( diff --git a/utils/log/_traceback.py b/utils/log/_traceback.py index 96b33079..9918a22e 100644 --- a/utils/log/_traceback.py +++ b/utils/log/_traceback.py @@ -1,7 +1,7 @@ import os import traceback as traceback_ from types import ModuleType, TracebackType -from typing import TYPE_CHECKING, Any, Dict, Iterable, List, Mapping, Optional, Tuple, Type, Union +from typing import Any, Dict, Iterable, List, Mapping, Optional, TYPE_CHECKING, Tuple, Type, Union from rich import pretty from rich.columns import Columns @@ -12,13 +12,20 @@ from rich.pretty import Pretty from rich.syntax import PygmentsSyntaxTheme, Syntax from rich.table import Table from rich.text import Text, TextType -from rich.traceback import Frame, PathHighlighter, Stack, Trace, LOCALS_MAX_LENGTH, LOCALS_MAX_STRING -from rich.traceback import Traceback as BaseTraceback +from rich.traceback import ( + Frame, + LOCALS_MAX_LENGTH, + LOCALS_MAX_STRING, + PathHighlighter, + Stack, + Trace, + Traceback as BaseTraceback, +) from utils.log._style import MonokaiProStyle if TYPE_CHECKING: - from rich.console import ConsoleRenderable # pylint: disable=W0611 + from rich.console import ConsoleRenderable __all__ = ("render_scope", "Traceback") @@ -96,20 +103,18 @@ class Traceback(BaseTraceback): exc_type: Type[BaseException], exc_value: BaseException, traceback: Optional[TracebackType], - *, width: Optional[int] = 100, extra_lines: int = 3, theme: Optional[str] = None, word_wrap: bool = False, show_locals: bool = False, + indent_guides: bool = True, locals_max_length: int = LOCALS_MAX_LENGTH, locals_max_string: int = LOCALS_MAX_STRING, locals_max_depth: Optional[int] = None, - locals_hide_dunder: bool = True, - locals_hide_sunder: bool = False, - indent_guides: bool = True, suppress: Iterable[Union[str, ModuleType]] = (), max_frames: int = 100, + **kwargs, ) -> "Traceback": rich_traceback = cls.extract( exc_type=exc_type, @@ -141,13 +146,11 @@ class Traceback(BaseTraceback): exc_type: Type[BaseException], exc_value: BaseException, traceback: Optional[TracebackType], - *, show_locals: bool = False, - locals_max_length: int = LOCALS_MAX_LENGTH, - locals_max_string: int = LOCALS_MAX_STRING, + locals_max_length: int = 10, + locals_max_string: int = 80, locals_max_depth: Optional[int] = None, - locals_hide_dunder: bool = True, - locals_hide_sunder: bool = False, + **kwargs, ) -> Trace: # noinspection PyProtectedMember from rich import _IMPORT_CWD