From 8d3dc3a1d2cadd0067a81be9dcc91a8e7c306e28 Mon Sep 17 00:00:00 2001 From: Adam Cooper Date: Sun, 17 Nov 2024 00:25:47 -0500 Subject: [PATCH] Fix issues with startup and logging Remove much of the code execution in package init files to remove import order issues and other undesired side-effects. --- memebot/client.py | 4 ++- memebot/db/__init__.py | 3 -- memebot/log/__init__.py | 71 ++++++++++++++++++++++++++++------------- memebot/log/logger.py | 6 ++-- memebot/main.py | 8 +++-- requirements.txt | 1 + tests/requirements.txt | 2 +- 7 files changed, 61 insertions(+), 34 deletions(-) diff --git a/memebot/client.py b/memebot/client.py index 9597e6b..3ebba93 100644 --- a/memebot/client.py +++ b/memebot/client.py @@ -44,7 +44,9 @@ async def on_command_error( ) -> None: command = interaction.command if not isinstance(command, discord.app_commands.Command): - log.exception(error) + log.critical( + "Non-command error handled by command error handler!", exc_info=error + ) return invocation = util.parse_invocation(interaction) diff --git a/memebot/db/__init__.py b/memebot/db/__init__.py index 456c961..55412c1 100644 --- a/memebot/db/__init__.py +++ b/memebot/db/__init__.py @@ -3,9 +3,6 @@ db_internals = DatabaseInternals() -if config.database_enabled: - db_internals.connect() - def test() -> bool: """ diff --git a/memebot/log/__init__.py b/memebot/log/__init__.py index d1c32fc..62eeda7 100644 --- a/memebot/log/__init__.py +++ b/memebot/log/__init__.py @@ -1,42 +1,67 @@ import atexit import contextlib import logging -from typing import cast, Any, TYPE_CHECKING +from typing import cast, Any + +import discord from memebot import config from . import formatter, logger -config.log_location.setFormatter(formatter.MemeBotLogFormatter()) -logging.setLoggerClass(logger.MemeBotLogger) +memebot_logger: logger.MemeBotLogger +stdout_logger: logging.Logger + + +def log(level: int, msg: str, *args: Any, **kwargs: Any) -> None: + memebot_logger.log(level, msg, *args, **kwargs) + + +def debug(msg: str, *args: Any, **kwargs: Any) -> None: + memebot_logger.debug(msg, *args, **kwargs) + + +def info(msg: str, *args: Any, **kwargs: Any) -> None: + memebot_logger.info(msg, *args, **kwargs) + + +def warning(msg: str, *args: Any, **kwargs: Any) -> None: + memebot_logger.warning(msg, *args, **kwargs) + + +def error(msg: str, *args: Any, **kwargs: Any) -> None: + memebot_logger.error(msg, *args, **kwargs) + + +def critical(msg: str, *args: Any, **kwargs: Any) -> None: + memebot_logger.critical(msg, *args, **kwargs) + + +def exception(msg: str, exc_info: Any = True, *args: Any, **kwargs: Any) -> None: + memebot_logger.exception(msg, *args, exc_info=exc_info, **kwargs) -# We use a project-wide logger because of how we shim metadata into log statements -memebot_logger = cast(logger.MemeBotLogger, logging.getLogger("memebot")) -# Redirect all stdout to a logger, as some packages in the stdlib still use print -# for debug messages -stdout_logger = logging.getLogger("stdout") -contextlib.redirect_stdout(stdout_logger).__enter__() # type: ignore[type-var] +def configure_logging() -> None: + config.populate_config_from_command_line() -# Ensure the handler is properly flushed when MemeBot is killed -atexit.register(logging.shutdown) + config.log_location.setFormatter(formatter.MemeBotLogFormatter()) + logging.setLoggerClass(logger.MemeBotLogger) + # We use a project-wide logger because of how we shim metadata into log statements + global memebot_logger + memebot_logger = cast(logger.MemeBotLogger, logging.getLogger("memebot")) -# Forward memebot_logger's logging methods as module-level functions -debug = memebot_logger.debug -info = memebot_logger.info -warning = memebot_logger.warning -error = memebot_logger.error -critical = memebot_logger.critical -log = memebot_logger.log -exception = memebot_logger.exception + # Redirect all stdout to a logger, as some packages in the stdlib still use print + # for debug messages + global stdout_logger + stdout_logger = logging.getLogger("stdout") + contextlib.redirect_stdout(stdout_logger).__enter__() # type: ignore[type-var] -# discord is imported as late as possible to ensure its logger is set properly -if TYPE_CHECKING: - import discord + # Ensure the handler is properly flushed when MemeBot is killed + atexit.register(logging.shutdown) def interaction( - inter: "discord.Interaction", + inter: discord.Interaction, msg: str, level: int = logging.INFO, *args: Any, diff --git a/memebot/log/logger.py b/memebot/log/logger.py index 04c8d7b..926c692 100644 --- a/memebot/log/logger.py +++ b/memebot/log/logger.py @@ -36,8 +36,8 @@ class MemeBotLogger(logging.Logger, io.IOBase): accross all modules """ - def __init__(self, name: str, level: Union[int, str] = config.log_level): - super(MemeBotLogger, self).__init__(name, level) + def __init__(self, name: str, level: Optional[Union[int, str]] = None): + super(MemeBotLogger, self).__init__(name, level or config.log_level) self.propagate = False self.is_interactive = sys.stdin.isatty() or "pydev" in repr( __builtins__.get("__import__") # type: ignore[attr-defined] @@ -96,7 +96,7 @@ def write(self, msg: str) -> None: """ # This means we are in interactive mode, in which case peeking at the stack # can get very wonky - if self.is_interactive: + if self.is_interactive and sys.__stdout__: sys.__stdout__.writelines([msg]) elif msg and not msg.isspace(): # Capture the stack here, on a line without the string "print(" in it diff --git a/memebot/main.py b/memebot/main.py index 519ec5c..3915956 100644 --- a/memebot/main.py +++ b/memebot/main.py @@ -1,4 +1,5 @@ from memebot import config +from memebot import db from memebot import log from memebot.client import get_memebot @@ -9,9 +10,10 @@ def main() -> None: :return: Exit status of discord.Client.run() """ config.populate_config_from_command_line() - # the ``log`` package should be imported ASAP to ensure our logging shims - # are injected into the runtime before external packages configure - # their logging + log.configure_logging() + if config.database_enabled: + db.db_internals.connect() + log.info("Starting up memebot!") memebot = get_memebot() diff --git a/requirements.txt b/requirements.txt index 8e2df4e..14a73ba 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,4 +2,5 @@ discord.py~=2.1.0 emoji~=2.0.0 pymongo~=3.13.0 pymongo-stubs~=0.2.0 +requests~=2.32.0 types-emoji~=2.0.0 diff --git a/tests/requirements.txt b/tests/requirements.txt index 6f1d32d..c10ed7b 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -1,5 +1,5 @@ dpytest~=0.6.0 -mypy~=0.0 +mypy~=1.13.0 pytest~=7.2.0 pytest-asyncio~=0.20.0 pytest-mock~=3.10.0