mirror of
https://github.com/Cog-Creators/Red-DiscordBot.git
synced 2025-11-06 03:08:55 -05:00
Various improvements in usage of Rich in Red (#4726)
* Use Rich's default console object instead of making one * Bump Rich to version 9.8.2 * Disable indent guides in tracebacks * Skip empty lines between stack levels in traceback rendering * Use full width of the terminal when printing tracebacks * Disabling syntax highlighting on the log messages * Make logger name bold * Make logger level bold * Make URLs in console bold * Change `bright_blue` and `blue` colors in syntax theme (NEEDS CHANGING) * Show only one line per stack level in tracebacks * Shuffle things in `redbot.logging` a bit * Change logging handler while redirecting stdout in Dev cog * Revert last two commits... This reverts commit cf563bd06a6ae398da12713ceef3db9cc903d902. This reverts commit 6dddf300726ddf89b8924441eed59b67b58faca0. * Change Rich console to always print to sys.stdout (therefore ignoring stdout redirects) * Pass cli_flags to init_logging() * Add a flag to set the amount of extra lines in rich tracebacks * First take on the syntax theme colors * Use the Windows trick * ARE YOU SERIOUS!? * Remove dead code * Use Monokai when Terminal application supports truecolor * Syntax theme update * Change logger name color * This is not needed * Adjust logging level colors * Add a flag for showing local variables in Rich tracebacks * change imports a bit * Remove usage of blue color fully * Stop highlighting in Red-DiscordBot splash * Fix unreadable paths in tracebacks * Make CRITICAL logging level more readable * Make time in logs more readable * Fix the first row being bolded in tables * Update rich to 9.9.0
This commit is contained in:
parent
663876fba3
commit
7df1570d51
@ -337,7 +337,7 @@ async def run_bot(red: Red, cli_flags: Namespace) -> None:
|
||||
redbot.logging.init_logging(
|
||||
level=cli_flags.logging_level,
|
||||
location=data_manager.core_data_path() / "logs",
|
||||
force_rich_logging=cli_flags.rich_logging,
|
||||
cli_flags=cli_flags,
|
||||
)
|
||||
|
||||
log.debug("====Basic Config====")
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import colorama as _colorama
|
||||
import discord as _discord
|
||||
|
||||
from .. import __version__, version_info, VersionInfo
|
||||
@ -7,7 +6,5 @@ from .utils.safety import warn_unsafe as _warn_unsafe
|
||||
|
||||
__all__ = ["Config", "__version__", "version_info", "VersionInfo"]
|
||||
|
||||
_colorama.init()
|
||||
|
||||
# Prevent discord PyNaCl missing warning
|
||||
_discord.voice_client.VoiceClient.warn_nacl = False
|
||||
|
||||
@ -29,7 +29,6 @@ from typing import (
|
||||
overload,
|
||||
)
|
||||
from types import MappingProxyType
|
||||
from rich.console import Console
|
||||
|
||||
import discord
|
||||
from discord.ext import commands as dpy_commands
|
||||
@ -224,9 +223,6 @@ class RedBase(
|
||||
|
||||
self._deletion_requests: MutableMapping[int, asyncio.Lock] = weakref.WeakValueDictionary()
|
||||
|
||||
# Although I see the use of keeping this public, lets rather make it private.
|
||||
self._rich_console = Console()
|
||||
|
||||
def set_help_formatter(self, formatter: commands.help.HelpFormatterABC):
|
||||
"""
|
||||
Set's Red's help formatter.
|
||||
|
||||
@ -263,6 +263,20 @@ def parse_cli_flags(args):
|
||||
default=None,
|
||||
help="Forcefully disables the Rich logging handlers.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--rich-traceback-extra-lines",
|
||||
type=positive_int,
|
||||
default=0,
|
||||
help="Set the number of additional lines of code before and after the executed line"
|
||||
" that should be shown in tracebacks generated by Rich.\n"
|
||||
"Useful for development.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--rich-traceback-show-locals",
|
||||
action="store_true",
|
||||
help="Enable showing local variables in tracebacks generated by Rich.\n"
|
||||
"Useful for development.",
|
||||
)
|
||||
|
||||
args = parser.parse_args(args)
|
||||
|
||||
|
||||
@ -10,7 +10,6 @@ from datetime import datetime, timedelta
|
||||
import aiohttp
|
||||
import discord
|
||||
import pkg_resources
|
||||
from colorama import Fore, Style, init
|
||||
from pkg_resources import DistributionNotFound
|
||||
from redbot.core import data_manager
|
||||
|
||||
@ -33,15 +32,16 @@ from .utils._internal_utils import (
|
||||
)
|
||||
from .utils.chat_formatting import inline, bordered, format_perms_list, humanize_timedelta
|
||||
|
||||
import rich
|
||||
from rich import box
|
||||
from rich.table import Table
|
||||
from rich.columns import Columns
|
||||
from rich.panel import Panel
|
||||
from rich.text import Text
|
||||
|
||||
log = logging.getLogger("red")
|
||||
init()
|
||||
|
||||
INTRO = r"""[red]
|
||||
INTRO = r"""
|
||||
______ _ ______ _ _ ______ _
|
||||
| ___ \ | | | _ (_) | | | ___ \ | |
|
||||
| |_/ /___ __| | ______ | | | |_ ___ ___ ___ _ __ __| | | |_/ / ___ | |_
|
||||
@ -88,14 +88,14 @@ def init_events(bot, cli_flags):
|
||||
red_pkg = pkg_resources.get_distribution("Red-DiscordBot")
|
||||
dpy_version = discord.__version__
|
||||
|
||||
table_general_info = Table(show_edge=False, show_header=False)
|
||||
table_general_info = Table(show_edge=False, show_header=False, box=box.MINIMAL)
|
||||
table_general_info.add_row("Prefixes", ", ".join(prefixes))
|
||||
table_general_info.add_row("Language", lang)
|
||||
table_general_info.add_row("Red version", red_version)
|
||||
table_general_info.add_row("Discord.py version", dpy_version)
|
||||
table_general_info.add_row("Storage type", data_manager.storage_type())
|
||||
|
||||
table_counts = Table(show_edge=False, show_header=False)
|
||||
table_counts = Table(show_edge=False, show_header=False, box=box.MINIMAL)
|
||||
# String conversion is needed as Rich doesn't deal with ints
|
||||
table_counts.add_row("Shards", str(bot.shard_count))
|
||||
table_counts.add_row("Servers", str(guilds))
|
||||
@ -114,8 +114,8 @@ def init_events(bot, cli_flags):
|
||||
).format(pypi_version, red_version)
|
||||
rich_outdated_message = (
|
||||
f"[red]Outdated version![/red]\n"
|
||||
f"[red]!!![/red]Version {pypi_version} is available, "
|
||||
f"but you're using {red_version}[red]!!![/red]"
|
||||
f"[red]!!![/red]Version [cyan]{pypi_version}[/] is available, "
|
||||
f"but you're using [cyan]{red_version}[/][red]!!![/red]"
|
||||
)
|
||||
current_python = platform.python_version()
|
||||
extra_update = _(
|
||||
@ -167,9 +167,10 @@ def init_events(bot, cli_flags):
|
||||
).format(py_version=current_python, req_py=py_version_req)
|
||||
outdated_red_message += extra_update
|
||||
|
||||
bot._rich_console.print(INTRO)
|
||||
rich_console = rich.get_console()
|
||||
rich_console.print(INTRO, style="red", markup=False, highlight=False)
|
||||
if guilds:
|
||||
bot._rich_console.print(
|
||||
rich_console.print(
|
||||
Columns(
|
||||
[Panel(table_general_info, title=str(bot.user.name)), Panel(table_counts)],
|
||||
equal=True,
|
||||
@ -177,23 +178,21 @@ def init_events(bot, cli_flags):
|
||||
)
|
||||
)
|
||||
else:
|
||||
bot._rich_console.print(Columns([Panel(table_general_info, title=str(bot.user.name))]))
|
||||
rich_console.print(Columns([Panel(table_general_info, title=str(bot.user.name))]))
|
||||
|
||||
bot._rich_console.print(
|
||||
rich_console.print(
|
||||
"Loaded {} cogs with {} commands".format(len(bot.cogs), len(bot.commands))
|
||||
)
|
||||
|
||||
if invite_url:
|
||||
bot._rich_console.print(
|
||||
f"\nInvite URL: {Text(invite_url, style=f'link {invite_url}')}"
|
||||
)
|
||||
rich_console.print(f"\nInvite URL: {Text(invite_url, style=f'link {invite_url}')}")
|
||||
# We generally shouldn't care if the client supports it or not as Rich deals with it.
|
||||
if not guilds:
|
||||
bot._rich_console.print(
|
||||
rich_console.print(
|
||||
f"Looking for a quick guide on setting up Red? Checkout {Text('https://start.discord.red', style='link https://start.discord.red}')}"
|
||||
)
|
||||
if rich_outdated_message:
|
||||
bot._rich_console.print(rich_outdated_message)
|
||||
rich_console.print(rich_outdated_message)
|
||||
|
||||
if not bot.owner_ids:
|
||||
# we could possibly exit here in future
|
||||
@ -404,37 +403,3 @@ def init_events(bot, cli_flags):
|
||||
uuid = c.unique_identifier
|
||||
group_data = c.custom_groups
|
||||
await bot._config.custom("CUSTOM_GROUPS", c.cog_name, uuid).set(group_data)
|
||||
|
||||
|
||||
def _get_startup_screen_specs():
|
||||
"""Get specs for displaying the startup screen on stdout.
|
||||
|
||||
This is so we don't get encoding errors when trying to print unicode
|
||||
emojis to stdout (particularly with Windows Command Prompt).
|
||||
|
||||
Returns
|
||||
-------
|
||||
`tuple`
|
||||
Tuple in the form (`str`, `str`, `bool`) containing (in order) the
|
||||
on symbol, off symbol and whether or not the border should be pure ascii.
|
||||
|
||||
"""
|
||||
encoder = codecs.getencoder(sys.stdout.encoding)
|
||||
check_mark = "\N{SQUARE ROOT}"
|
||||
try:
|
||||
encoder(check_mark)
|
||||
except UnicodeEncodeError:
|
||||
on_symbol = "[X]"
|
||||
off_symbol = "[ ]"
|
||||
else:
|
||||
on_symbol = check_mark
|
||||
off_symbol = "X"
|
||||
|
||||
try:
|
||||
encoder("┌┐└┘─│") # border symbols
|
||||
except UnicodeEncodeError:
|
||||
ascii_border = True
|
||||
else:
|
||||
ascii_border = False
|
||||
|
||||
return on_symbol, off_symbol, ascii_border
|
||||
|
||||
@ -1,19 +1,37 @@
|
||||
import argparse
|
||||
import logging.handlers
|
||||
import pathlib
|
||||
import re
|
||||
import sys
|
||||
|
||||
from typing import List, Tuple, Optional, Union
|
||||
from typing import List, Tuple, Optional
|
||||
from logging import LogRecord
|
||||
from datetime import datetime # This clearly never leads to confusion...
|
||||
from os import isatty
|
||||
|
||||
from rich._log_render import LogRender
|
||||
import rich
|
||||
from pygments.styles.monokai import MonokaiStyle
|
||||
from pygments.token import (
|
||||
Comment,
|
||||
Error,
|
||||
Keyword,
|
||||
Name,
|
||||
Number,
|
||||
Operator,
|
||||
String,
|
||||
Token,
|
||||
)
|
||||
from rich._log_render import LogRender # DEP-WARN
|
||||
from rich.console import render_group
|
||||
from rich.containers import Renderables
|
||||
from rich.highlighter import NullHighlighter
|
||||
from rich.logging import RichHandler
|
||||
from rich.style import Style
|
||||
from rich.syntax import ANSISyntaxTheme, PygmentsSyntaxTheme
|
||||
from rich.table import Table
|
||||
from rich.text import Text
|
||||
from rich.traceback import Traceback
|
||||
from rich.theme import Theme
|
||||
from rich.traceback import PathHighlighter, Traceback
|
||||
|
||||
|
||||
MAX_OLD_LOGS = 8
|
||||
@ -109,6 +127,37 @@ class RotatingFileHandler(logging.handlers.RotatingFileHandler):
|
||||
self.stream = self._open()
|
||||
|
||||
|
||||
SYNTAX_THEME = {
|
||||
Token: Style(),
|
||||
Comment: Style(color="bright_black"),
|
||||
Keyword: Style(color="cyan", bold=True),
|
||||
Keyword.Constant: Style(color="bright_magenta"),
|
||||
Keyword.Namespace: Style(color="bright_red"),
|
||||
Operator: Style(bold=True),
|
||||
Operator.Word: Style(color="cyan", bold=True),
|
||||
Name.Builtin: Style(bold=True),
|
||||
Name.Builtin.Pseudo: Style(color="bright_red"),
|
||||
Name.Exception: Style(bold=True),
|
||||
Name.Class: Style(color="bright_green"),
|
||||
Name.Function: Style(color="bright_green"),
|
||||
String: Style(color="yellow"),
|
||||
Number: Style(color="cyan"),
|
||||
Error: Style(bgcolor="red"),
|
||||
}
|
||||
|
||||
|
||||
class FixedMonokaiStyle(MonokaiStyle):
|
||||
styles = {**MonokaiStyle.styles, Token: "#f8f8f2"}
|
||||
|
||||
|
||||
class RedTraceback(Traceback):
|
||||
@render_group()
|
||||
def _render_stack(self, stack):
|
||||
for obj in super()._render_stack.__wrapped__(self, stack):
|
||||
if obj != "":
|
||||
yield obj
|
||||
|
||||
|
||||
class RedLogRender(LogRender):
|
||||
def __call__(
|
||||
self,
|
||||
@ -155,7 +204,7 @@ class RedLogRender(LogRender):
|
||||
|
||||
if logger_name:
|
||||
logger_name_text = Text()
|
||||
logger_name_text.append(f"[{logger_name}]")
|
||||
logger_name_text.append(f"[{logger_name}]", style="bright_black")
|
||||
row.append(logger_name_text)
|
||||
|
||||
output.add_row(*row)
|
||||
@ -174,6 +223,19 @@ class RedRichHandler(RichHandler):
|
||||
level_width=self._log_render.level_width,
|
||||
)
|
||||
|
||||
def get_level_text(self, record: LogRecord) -> Text:
|
||||
"""Get the level name from the record.
|
||||
|
||||
Args:
|
||||
record (LogRecord): LogRecord instance.
|
||||
|
||||
Returns:
|
||||
Text: A tuple of the style and level name.
|
||||
"""
|
||||
level_text = super().get_level_text(record)
|
||||
level_text.stylize("bold")
|
||||
return level_text
|
||||
|
||||
def emit(self, record: LogRecord) -> None:
|
||||
"""Invoked by logging."""
|
||||
path = pathlib.Path(record.pathname).name
|
||||
@ -187,7 +249,7 @@ class RedRichHandler(RichHandler):
|
||||
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(
|
||||
traceback = RedTraceback.from_exception(
|
||||
exc_type,
|
||||
exc_value,
|
||||
exc_traceback,
|
||||
@ -198,6 +260,7 @@ class RedRichHandler(RichHandler):
|
||||
show_locals=self.tracebacks_show_locals,
|
||||
locals_max_length=self.locals_max_length,
|
||||
locals_max_string=self.locals_max_string,
|
||||
indent_guides=False,
|
||||
)
|
||||
message = record.getMessage()
|
||||
|
||||
@ -215,7 +278,7 @@ class RedRichHandler(RichHandler):
|
||||
self.console.print(
|
||||
self._log_render(
|
||||
self.console,
|
||||
[message_text] if not traceback else [message_text, traceback],
|
||||
[message_text],
|
||||
log_time=log_time,
|
||||
time_format=time_format,
|
||||
level=level,
|
||||
@ -225,11 +288,11 @@ class RedRichHandler(RichHandler):
|
||||
logger_name=record.name,
|
||||
)
|
||||
)
|
||||
if traceback:
|
||||
self.console.print(traceback)
|
||||
|
||||
|
||||
def init_logging(
|
||||
level: int, location: pathlib.Path, force_rich_logging: Union[bool, None]
|
||||
) -> None:
|
||||
def init_logging(level: int, location: pathlib.Path, cli_flags: argparse.Namespace) -> None:
|
||||
root_logger = logging.getLogger()
|
||||
|
||||
base_logger = logging.getLogger("red")
|
||||
@ -239,12 +302,31 @@ def init_logging(
|
||||
warnings_logger = logging.getLogger("py.warnings")
|
||||
warnings_logger.setLevel(logging.WARNING)
|
||||
|
||||
rich_console = rich.get_console()
|
||||
rich.reconfigure(tab_size=4)
|
||||
rich_console.push_theme(
|
||||
Theme(
|
||||
{
|
||||
"log.time": Style(dim=True),
|
||||
"logging.level.warning": Style(color="yellow"),
|
||||
"logging.level.critical": Style(color="white", bgcolor="red"),
|
||||
"repr.number": Style(color="cyan"),
|
||||
"repr.url": Style(underline=True, italic=True, bold=False, color="cyan"),
|
||||
}
|
||||
)
|
||||
)
|
||||
rich_console.file = sys.stdout
|
||||
# This is terrible solution, but it's the best we can do if we want the paths in tracebacks
|
||||
# to be visible. Rich uses `pygments.string` style which is fine, but it also uses
|
||||
# this highlighter which dims most of the path and therefore makes it unreadable on Mac.
|
||||
PathHighlighter.highlights = []
|
||||
|
||||
enable_rich_logging = False
|
||||
|
||||
if isatty(0) and force_rich_logging is None:
|
||||
if isatty(0) and cli_flags.rich_logging is None:
|
||||
# Check if the bot thinks it has a active terminal.
|
||||
enable_rich_logging = True
|
||||
elif force_rich_logging is True:
|
||||
elif cli_flags.rich_logging is True:
|
||||
enable_rich_logging = True
|
||||
|
||||
file_formatter = logging.Formatter(
|
||||
@ -253,7 +335,18 @@ def init_logging(
|
||||
if enable_rich_logging is True:
|
||||
rich_formatter = logging.Formatter("{message}", datefmt="[%X]", style="{")
|
||||
|
||||
stdout_handler = RedRichHandler(rich_tracebacks=True, show_path=False)
|
||||
stdout_handler = RedRichHandler(
|
||||
rich_tracebacks=True,
|
||||
show_path=False,
|
||||
highlighter=NullHighlighter(),
|
||||
tracebacks_extra_lines=cli_flags.rich_traceback_extra_lines,
|
||||
tracebacks_show_locals=cli_flags.rich_traceback_show_locals,
|
||||
tracebacks_theme=(
|
||||
PygmentsSyntaxTheme(FixedMonokaiStyle)
|
||||
if rich_console.color_system == "truecolor"
|
||||
else ANSISyntaxTheme(SYNTAX_THEME)
|
||||
),
|
||||
)
|
||||
stdout_handler.setFormatter(rich_formatter)
|
||||
else:
|
||||
stdout_handler = logging.StreamHandler(sys.stdout)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user