🐛 Fix Logger

Fix the issue of incorrect colors being printed by logger when running on Linux
This commit is contained in:
Karako 2023-03-21 13:53:56 +08:00 committed by GitHub
parent b3b836fb15
commit eed8c3ad9c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 103 additions and 88 deletions

View File

@ -1,6 +1,6 @@
from multiprocessing import RLock as Lock from multiprocessing import RLock as Lock
from pathlib import Path from pathlib import Path
from typing import List, Optional, Union from typing import List, Literal, Optional, Union
from pydantic import BaseSettings from pydantic import BaseSettings
@ -22,17 +22,28 @@ class LoggerConfig(BaseSettings):
cls._instance = result cls._instance = result
return cls._instance return cls._instance
name: str = "logger" name: str = "PaiGram-logger"
"""logger 名称"""
level: Optional[Union[str, int]] = None level: Optional[Union[str, int]] = None
"""logger 的 level"""
debug: bool = False debug: bool = False
"""是否 debug"""
width: Optional[int] = None width: Optional[int] = None
"""输出时的宽度"""
keywords: List[str] = [] keywords: List[str] = []
"""高亮的关键字"""
time_format: str = "[%Y-%m-%d %X]" time_format: str = "[%Y-%m-%d %X]"
"""时间格式"""
capture_warnings: bool = True capture_warnings: bool = True
"""是否捕获 warning"""
color_system: Literal["auto", "standard", "256", "truecolor", "windows"] = "auto"
"""颜色模式: 自动、标准、256色、真彩、Windows模式"""
log_path: Union[str, Path] = "./logs" log_path: Union[str, Path] = "./logs"
"""log 所保存的路径,项目根目录的相对路径"""
project_root: Union[str, Path] = PROJECT_ROOT project_root: Union[str, Path] = PROJECT_ROOT
"""项目根目录"""
traceback_max_frames: int = 20 traceback_max_frames: int = 20
traceback_locals_max_depth: Optional[int] = None traceback_locals_max_depth: Optional[int] = None

View File

@ -1,9 +1,17 @@
import logging import logging
import os import os
import sys
from datetime import datetime from datetime import datetime
from pathlib import Path 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.console import Console
from rich.logging import LogRender as DefaultLogRender, RichHandler as DefaultRichHandler 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._style import DEFAULT_STYLE
from utils.log._traceback import Traceback 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: 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] FormatTimeCallable = Callable[[datetime], Text]
logging.addLevelName(5, "TRACE") logging.addLevelName(5, "TRACE")
logging.addLevelName(25, "SUCCESS") logging.addLevelName(25, "SUCCESS")
color_system: Literal["windows", "truecolor"]
if sys.platform == "win32":
color_system = "windows"
else:
color_system = "truecolor"
class LogRender(DefaultLogRender): class LogRender(DefaultLogRender):
@ -108,7 +120,9 @@ class Handler(DefaultRichHandler):
tracebacks_max_frames: int = 100, tracebacks_max_frames: int = 100,
keywords: Optional[List[str]] = None, keywords: Optional[List[str]] = None,
log_time_format: Union[str, FormatTimeCallable] = "[%x %X]", 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, **kwargs,
) -> None: ) -> None:
super(Handler, self).__init__(*args, rich_tracebacks=rich_tracebacks, **kwargs) 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.console = Console(color_system=color_system, theme=Theme(DEFAULT_STYLE), width=width)
self.tracebacks_show_locals = True self.tracebacks_show_locals = True
self.tracebacks_max_frames = tracebacks_max_frames 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.locals_max_depth = locals_max_depth
self.project_root = project_root self.project_root = project_root
self.auto_load_json = auto_load_json
def render( def render(
self, self,
@ -153,15 +168,13 @@ class Handler(DefaultRichHandler):
log_time = datetime.fromtimestamp(record.created) log_time = datetime.fromtimestamp(record.created)
if not traceback: if not traceback:
traceback_content = [message_renderable] renderables = [message_renderable]
elif message_renderable is not None:
traceback_content = [message_renderable, traceback]
else: else:
traceback_content = [traceback] renderables = [message_renderable, traceback] if message_renderable is not None else [traceback]
log_renderable = self._log_render( log_renderable = self._log_render(
self.console, self.console,
traceback_content, renderables,
log_time=log_time, log_time=log_time,
time_format=time_format, time_format=time_format,
level=_level, level=_level,
@ -177,10 +190,8 @@ class Handler(DefaultRichHandler):
message: Any, message: Any,
) -> "ConsoleRenderable": ) -> "ConsoleRenderable":
use_markup = getattr(record, "markup", self.markup) use_markup = getattr(record, "markup", self.markup)
tag = getattr(record, "tag", None)
if isinstance(message, str): if isinstance(message, str):
message_text = Text.from_markup(message) if use_markup else Text(message) 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) highlighter = getattr(record, "highlighter", self.highlighter)
else: else:
from rich.highlighter import JSONHighlighter from rich.highlighter import JSONHighlighter
@ -188,17 +199,16 @@ class Handler(DefaultRichHandler):
highlighter = JSONHighlighter() highlighter = JSONHighlighter()
message_text = JSON.from_data(message, indent=4).text 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: if highlighter is not None:
# noinspection PyCallingNonCallable # noinspection PyCallingNonCallable
message_text = highlighter(message_text) message_text = highlighter(message_text)
if self.keywords is None: if self.render_keywords is None:
self.keywords = self.KEYWORDS self.render_keywords = self.KEYWORDS
if self.keywords: if self.render_keywords:
message_text.highlight_words(self.keywords, "logging.keyword") message_text.highlight_words(self.render_keywords, "logging.keyword")
return message_text return message_text
@ -209,22 +219,23 @@ class Handler(DefaultRichHandler):
exc_type, exc_value, exc_traceback = record.exc_info exc_type, exc_value, exc_traceback = record.exc_info
if exc_type is None or exc_value is None: if exc_type is None or exc_value is None:
raise ValueError(record) raise ValueError(record)
_traceback = Traceback.from_exception( try:
exc_type, _traceback = Traceback.from_exception(
exc_value, exc_type,
exc_traceback, exc_value,
width=self.tracebacks_width, exc_traceback,
extra_lines=self.tracebacks_extra_lines, width=self.tracebacks_width,
word_wrap=self.tracebacks_word_wrap, extra_lines=self.tracebacks_extra_lines,
show_locals=(getattr(record, "show_locals", None) or self.tracebacks_show_locals), word_wrap=self.tracebacks_word_wrap,
locals_max_length=(getattr(record, "locals_max_length", None) or self.locals_max_length), show_locals=(getattr(record, "show_locals", None) or self.tracebacks_show_locals),
locals_max_string=(getattr(record, "locals_max_string", None) or self.locals_max_string), locals_max_length=(getattr(record, "locals_max_length", None) or self.locals_max_length),
locals_max_depth=( locals_max_string=(getattr(record, "locals_max_string", None) or self.locals_max_string),
record.locals_max_depth if hasattr(record, "locals_max_depth") else self.locals_max_depth locals_max_depth=getattr(record, "locals_max_depth", self.locals_max_depth),
), suppress=self.tracebacks_suppress,
suppress=self.tracebacks_suppress, max_frames=self.tracebacks_max_frames,
max_frames=self.tracebacks_max_frames, )
) except ImportError:
return
message = record.getMessage() message = record.getMessage()
if self.formatter: if self.formatter:
record.message = record.getMessage() record.message = record.getMessage()
@ -235,10 +246,15 @@ class Handler(DefaultRichHandler):
if message == str(exc_value): if message == str(exc_value):
message = None message = None
message_renderable = None
if message is not None: if message is not None:
message_renderable = self.render_message(record, message) try:
else: if self.auto_load_json:
message_renderable = None 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) log_renderable = self.render(record=record, traceback=_traceback, message_renderable=message_renderable)
# noinspection PyBroadException # noinspection PyBroadException
try: try:

View File

@ -6,7 +6,7 @@ import traceback as traceback_
from multiprocessing import RLock as Lock from multiprocessing import RLock as Lock
from pathlib import Path from pathlib import Path
from types import TracebackType 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 from typing_extensions import Self
@ -14,9 +14,9 @@ from utils.log._handler import FileHandler, Handler
from utils.typedefs import LogFilterType from utils.typedefs import LogFilterType
if TYPE_CHECKING: 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") __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) 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 = ( handler, debug_handler, error_handler = (
# 控制台 log 配置 # 控制台 log 配置
Handler( Handler(color_system=self.config.color_system, **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,
locals_max_depth=self.config.traceback_locals_max_depth,
project_root=self.config.project_root,
log_time_format=self.config.time_format,
),
# debug.log 配置 # debug.log 配置
FileHandler( FileHandler(level=10, path=log_path.joinpath("debug/debug.log"), locals_max_depth=1, **handler_config),
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,
),
# error.log 配置 # error.log 配置
FileHandler( FileHandler(
width=self.config.width,
keywords=self.config.keywords,
level=40, level=40,
path=log_path.joinpath("error/error.log"), 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, locals_max_depth=self.config.traceback_locals_max_depth,
project_root=self.config.project_root, **handler_config,
log_time_format=self.config.time_format,
), ),
) )
logging.basicConfig( logging.basicConfig(

View File

@ -1,7 +1,7 @@
import os import os
import traceback as traceback_ import traceback as traceback_
from types import ModuleType, TracebackType 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 import pretty
from rich.columns import Columns from rich.columns import Columns
@ -12,13 +12,20 @@ from rich.pretty import Pretty
from rich.syntax import PygmentsSyntaxTheme, Syntax from rich.syntax import PygmentsSyntaxTheme, Syntax
from rich.table import Table from rich.table import Table
from rich.text import Text, TextType from rich.text import Text, TextType
from rich.traceback import Frame, PathHighlighter, Stack, Trace, LOCALS_MAX_LENGTH, LOCALS_MAX_STRING from rich.traceback import (
from rich.traceback import Traceback as BaseTraceback Frame,
LOCALS_MAX_LENGTH,
LOCALS_MAX_STRING,
PathHighlighter,
Stack,
Trace,
Traceback as BaseTraceback,
)
from utils.log._style import MonokaiProStyle from utils.log._style import MonokaiProStyle
if TYPE_CHECKING: if TYPE_CHECKING:
from rich.console import ConsoleRenderable # pylint: disable=W0611 from rich.console import ConsoleRenderable
__all__ = ("render_scope", "Traceback") __all__ = ("render_scope", "Traceback")
@ -96,20 +103,18 @@ class Traceback(BaseTraceback):
exc_type: Type[BaseException], exc_type: Type[BaseException],
exc_value: BaseException, exc_value: BaseException,
traceback: Optional[TracebackType], traceback: Optional[TracebackType],
*,
width: Optional[int] = 100, width: Optional[int] = 100,
extra_lines: int = 3, extra_lines: int = 3,
theme: Optional[str] = None, theme: Optional[str] = None,
word_wrap: bool = False, word_wrap: bool = False,
show_locals: bool = False, show_locals: bool = False,
indent_guides: bool = True,
locals_max_length: int = LOCALS_MAX_LENGTH, locals_max_length: int = LOCALS_MAX_LENGTH,
locals_max_string: int = LOCALS_MAX_STRING, locals_max_string: int = LOCALS_MAX_STRING,
locals_max_depth: Optional[int] = None, 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]] = (), suppress: Iterable[Union[str, ModuleType]] = (),
max_frames: int = 100, max_frames: int = 100,
**kwargs,
) -> "Traceback": ) -> "Traceback":
rich_traceback = cls.extract( rich_traceback = cls.extract(
exc_type=exc_type, exc_type=exc_type,
@ -141,13 +146,11 @@ class Traceback(BaseTraceback):
exc_type: Type[BaseException], exc_type: Type[BaseException],
exc_value: BaseException, exc_value: BaseException,
traceback: Optional[TracebackType], traceback: Optional[TracebackType],
*,
show_locals: bool = False, show_locals: bool = False,
locals_max_length: int = LOCALS_MAX_LENGTH, locals_max_length: int = 10,
locals_max_string: int = LOCALS_MAX_STRING, locals_max_string: int = 80,
locals_max_depth: Optional[int] = None, locals_max_depth: Optional[int] = None,
locals_hide_dunder: bool = True, **kwargs,
locals_hide_sunder: bool = False,
) -> Trace: ) -> Trace:
# noinspection PyProtectedMember # noinspection PyProtectedMember
from rich import _IMPORT_CWD from rich import _IMPORT_CWD