mirror of
https://github.com/Cog-Creators/Red-DiscordBot.git
synced 2025-11-21 18:27:59 -05:00
[Core] Rich logging for not so rich people (#4577)
* Pop stash * add rich to setup * Added forceful enabling of rich logging * revert some unintended pushed * Fix possible unbound var Fix possible 0 members w/out members intent * One day I won't forget to do style passes * So this is a thing apperently... * Bump rich to 9.5.1 * Lock secondary deps * Different stuff, see the full commit description for more info - Change few things from print to log.info - put the log handlers on the root logger instead of individual loggers - capture warnings to a logger * Modify log handler to show logger name * Add a Triboolian to force disable rich * Style checks * shortened time, added logger name... again. * aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa * Style & linking * Be or not to be? Whatever man, it's 4:30 in the morning, goto sleep >.< * Reintroduce outdated message. Co-authored-by: jack1142 <6032823+jack1142@users.noreply.github.com>
This commit is contained in:
@@ -2,7 +2,19 @@ import logging.handlers
|
||||
import pathlib
|
||||
import re
|
||||
import sys
|
||||
from typing import List, Tuple, Optional
|
||||
|
||||
from typing import List, Tuple, Optional, Union
|
||||
from logging import LogRecord
|
||||
from datetime import datetime # This clearly never leads to confusion...
|
||||
from os import isatty
|
||||
|
||||
from rich._log_render import LogRender
|
||||
from rich.containers import Renderables
|
||||
from rich.logging import RichHandler
|
||||
from rich.table import Table
|
||||
from rich.text import Text
|
||||
from rich.traceback import Traceback
|
||||
|
||||
|
||||
MAX_OLD_LOGS = 8
|
||||
|
||||
@@ -97,20 +109,158 @@ class RotatingFileHandler(logging.handlers.RotatingFileHandler):
|
||||
self.stream = self._open()
|
||||
|
||||
|
||||
def init_logging(level: int, location: pathlib.Path) -> None:
|
||||
dpy_logger = logging.getLogger("discord")
|
||||
dpy_logger.setLevel(logging.WARNING)
|
||||
class RedLogRender(LogRender):
|
||||
def __call__(
|
||||
self,
|
||||
console,
|
||||
renderables,
|
||||
log_time=None,
|
||||
time_format=None,
|
||||
level="",
|
||||
path=None,
|
||||
line_no=None,
|
||||
link_path=None,
|
||||
logger_name=None,
|
||||
):
|
||||
output = Table.grid(padding=(0, 1))
|
||||
output.expand = True
|
||||
if self.show_time:
|
||||
output.add_column(style="log.time")
|
||||
if self.show_level:
|
||||
output.add_column(style="log.level", width=self.level_width)
|
||||
output.add_column(ratio=1, style="log.message", overflow="fold")
|
||||
if self.show_path and path:
|
||||
output.add_column(style="log.path")
|
||||
if logger_name:
|
||||
output.add_column()
|
||||
row = []
|
||||
if self.show_time:
|
||||
log_time = log_time or console.get_datetime()
|
||||
log_time_display = log_time.strftime(time_format or self.time_format)
|
||||
if log_time_display == self._last_time:
|
||||
row.append(Text(" " * len(log_time_display)))
|
||||
else:
|
||||
row.append(Text(log_time_display))
|
||||
self._last_time = log_time_display
|
||||
if self.show_level:
|
||||
row.append(level)
|
||||
|
||||
row.append(Renderables(renderables))
|
||||
if self.show_path and path:
|
||||
path_text = Text()
|
||||
path_text.append(path, style=f"link file://{link_path}" if link_path else "")
|
||||
if line_no:
|
||||
path_text.append(f":{line_no}")
|
||||
row.append(path_text)
|
||||
|
||||
if logger_name:
|
||||
logger_name_text = Text()
|
||||
logger_name_text.append(f"[{logger_name}]")
|
||||
row.append(logger_name_text)
|
||||
|
||||
output.add_row(*row)
|
||||
return output
|
||||
|
||||
|
||||
class RedRichHandler(RichHandler):
|
||||
"""Adaptation of Rich's RichHandler to manually adjust the path to a logger name"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self._log_render = RedLogRender(
|
||||
show_time=self._log_render.show_time,
|
||||
show_level=self._log_render.show_level,
|
||||
show_path=self._log_render.show_path,
|
||||
level_width=self._log_render.level_width,
|
||||
)
|
||||
|
||||
def emit(self, record: LogRecord) -> None:
|
||||
"""Invoked by logging."""
|
||||
path = pathlib.Path(record.pathname).name
|
||||
level = self.get_level_text(record)
|
||||
message = self.format(record)
|
||||
time_format = None if self.formatter is None else self.formatter.datefmt
|
||||
log_time = datetime.fromtimestamp(record.created)
|
||||
|
||||
traceback = None
|
||||
if self.rich_tracebacks and record.exc_info and record.exc_info != (None, None, None):
|
||||
exc_type, exc_value, exc_traceback = record.exc_info
|
||||
assert exc_type is not None
|
||||
assert exc_value is not None
|
||||
traceback = Traceback.from_exception(
|
||||
exc_type,
|
||||
exc_value,
|
||||
exc_traceback,
|
||||
width=self.tracebacks_width,
|
||||
extra_lines=self.tracebacks_extra_lines,
|
||||
theme=self.tracebacks_theme,
|
||||
word_wrap=self.tracebacks_word_wrap,
|
||||
show_locals=self.tracebacks_show_locals,
|
||||
locals_max_length=self.locals_max_length,
|
||||
locals_max_string=self.locals_max_string,
|
||||
)
|
||||
message = record.getMessage()
|
||||
|
||||
use_markup = getattr(record, "markup") if hasattr(record, "markup") else self.markup
|
||||
if use_markup:
|
||||
message_text = Text.from_markup(message)
|
||||
else:
|
||||
message_text = Text(message)
|
||||
|
||||
if self.highlighter:
|
||||
message_text = self.highlighter(message_text)
|
||||
if self.KEYWORDS:
|
||||
message_text.highlight_words(self.KEYWORDS, "logging.keyword")
|
||||
|
||||
self.console.print(
|
||||
self._log_render(
|
||||
self.console,
|
||||
[message_text] if not traceback else [message_text, traceback],
|
||||
log_time=log_time,
|
||||
time_format=time_format,
|
||||
level=level,
|
||||
path=path,
|
||||
line_no=record.lineno,
|
||||
link_path=record.pathname if self.enable_link_path else None,
|
||||
logger_name=record.name,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def init_logging(
|
||||
level: int, location: pathlib.Path, force_rich_logging: Union[bool, None]
|
||||
) -> None:
|
||||
root_logger = logging.getLogger()
|
||||
|
||||
base_logger = logging.getLogger("red")
|
||||
base_logger.setLevel(level)
|
||||
dpy_logger = logging.getLogger("discord")
|
||||
dpy_logger.setLevel(logging.WARNING)
|
||||
warnings_logger = logging.getLogger("py.warnings")
|
||||
warnings_logger.setLevel(logging.WARNING)
|
||||
|
||||
formatter = logging.Formatter(
|
||||
enable_rich_logging = False
|
||||
|
||||
if isatty(0) and force_rich_logging is None:
|
||||
# Check if the bot thinks it has a active terminal.
|
||||
enable_rich_logging = True
|
||||
elif force_rich_logging is True:
|
||||
enable_rich_logging = True
|
||||
|
||||
file_formatter = logging.Formatter(
|
||||
"[{asctime}] [{levelname}] {name}: {message}", datefmt="%Y-%m-%d %H:%M:%S", style="{"
|
||||
)
|
||||
if enable_rich_logging is True:
|
||||
rich_formatter = logging.Formatter("{message}", datefmt="[%X]", style="{")
|
||||
|
||||
stdout_handler = logging.StreamHandler(sys.stdout)
|
||||
stdout_handler.setFormatter(formatter)
|
||||
base_logger.addHandler(stdout_handler)
|
||||
dpy_logger.addHandler(stdout_handler)
|
||||
stdout_handler = RedRichHandler(rich_tracebacks=True, show_path=False)
|
||||
stdout_handler.setFormatter(rich_formatter)
|
||||
else:
|
||||
stdout_handler = logging.StreamHandler(sys.stdout)
|
||||
stdout_handler.setFormatter(file_formatter)
|
||||
|
||||
root_logger.addHandler(stdout_handler)
|
||||
logging.captureWarnings(True)
|
||||
|
||||
if not location.exists():
|
||||
location.mkdir(parents=True, exist_ok=True)
|
||||
@@ -146,6 +296,7 @@ def init_logging(level: int, location: pathlib.Path) -> None:
|
||||
backupCount=MAX_OLD_LOGS,
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
for fhandler in (latest_fhandler, all_fhandler):
|
||||
fhandler.setFormatter(formatter)
|
||||
base_logger.addHandler(fhandler)
|
||||
fhandler.setFormatter(file_formatter)
|
||||
root_logger.addHandler(fhandler)
|
||||
|
||||
Reference in New Issue
Block a user