diff --git a/discord/__main__.py b/discord/__main__.py index ed34bdf42a..f25a7656d3 100644 --- a/discord/__main__.py +++ b/discord/__main__.py @@ -25,6 +25,7 @@ import argparse import importlib.metadata +import logging import platform import sys from pathlib import Path @@ -34,6 +35,8 @@ import discord +_log = logging.getLogger(__name__) + def show_version() -> None: entries = [ @@ -238,8 +241,8 @@ def newbot(parser, args) -> None: cogs.mkdir(exist_ok=True) init = cogs / "__init__.py" init.touch() - except OSError as exc: - print(f"warning: could not create cogs directory ({exc})") + except OSError: + _log.exception(f"Could not create cogs directory.") try: with open(str(new_directory / "config.py"), "w", encoding="utf-8") as fp: @@ -258,18 +261,18 @@ def newbot(parser, args) -> None: try: with open(str(new_directory / ".gitignore"), "w", encoding="utf-8") as fp: fp.write(_gitignore_template) - except OSError as exc: - print(f"warning: could not create .gitignore file ({exc})") + except OSError: + _log.exception(f"Could not create .gitignore file.") - print("successfully made bot at", new_directory) + print("Successfully made bot at", new_directory) def newcog(parser, args) -> None: cog_dir = to_path(parser, args.directory) try: cog_dir.mkdir(exist_ok=True) - except OSError as exc: - print(f"warning: could not create cogs directory ({exc})") + except OSError: + _log.exception(f"Could not create cogs directory.") directory = cog_dir / to_path(parser, args.name) directory = directory.with_suffix(".py") diff --git a/discord/bot.py b/discord/bot.py index 7dd246afe3..51868eb9df 100644 --- a/discord/bot.py +++ b/discord/bot.py @@ -31,8 +31,6 @@ import copy import inspect import logging -import sys -import traceback from abc import ABC, abstractmethod from typing import ( TYPE_CHECKING, @@ -1240,7 +1238,7 @@ async def on_application_command_error( The default command error handler provided by the bot. - By default, this prints to :data:`sys.stderr` however it could be + By default, this logs with :meth:`logging.exception` however it could be overridden to have a different implementation. This only fires if you do not specify any listeners for command error. @@ -1255,9 +1253,8 @@ async def on_application_command_error( if cog and cog.has_error_handler(): return - print(f"Ignoring exception in command {context.command}:", file=sys.stderr) - traceback.print_exception( - type(exception), exception, exception.__traceback__, file=sys.stderr + _log.error( + f"Ignoring exception in command {context.command}.", exc_info=exception ) # global check registration diff --git a/discord/client.py b/discord/client.py index 2d0f1b8770..06d63da01d 100644 --- a/discord/client.py +++ b/discord/client.py @@ -28,7 +28,6 @@ import asyncio import logging import signal -import sys import traceback from types import TracebackType from typing import TYPE_CHECKING, Any, Callable, Coroutine, Generator, Sequence, TypeVar @@ -536,11 +535,11 @@ async def on_error(self, event_method: str, *args: Any, **kwargs: Any) -> None: The default error handler provided by the client. - By default, this prints to :data:`sys.stderr` however it could be + By default, this logs with :meth:`logging.error` however it could be overridden to have a different implementation. Check :func:`~discord.on_error` for more details. """ - print(f"Ignoring exception in {event_method}", file=sys.stderr) + _log.error(f"Ignoring exception in {event_method}") traceback.print_exc() async def on_view_error( @@ -553,27 +552,21 @@ async def on_view_error( This only fires for a view if you did not define its :func:`~discord.ui.View.on_error`. """ - print( + _log.error( f"Ignoring exception in view {interaction.view} for item {item}:", - file=sys.stderr, - ) - traceback.print_exception( - error.__class__, error, error.__traceback__, file=sys.stderr + exc_info=error, ) async def on_modal_error(self, error: Exception, interaction: Interaction) -> None: """|coro| The default modal error handler provided by the client. - The default implementation prints the traceback to stderr. + The default implementation logs the traceback with :meth:`logging.error`. This only fires for a modal if you did not define its :func:`~discord.ui.Modal.on_error`. """ - print(f"Ignoring exception in modal {interaction.modal}:", file=sys.stderr) - traceback.print_exception( - error.__class__, error, error.__traceback__, file=sys.stderr - ) + _log.error(f"Ignoring exception in modal {interaction.modal}", exc_info=error) # hooks diff --git a/discord/ext/commands/bot.py b/discord/ext/commands/bot.py index d348b6c536..8fa464db29 100644 --- a/discord/ext/commands/bot.py +++ b/discord/ext/commands/bot.py @@ -27,8 +27,7 @@ import collections import collections.abc -import sys -import traceback +import logging from typing import TYPE_CHECKING, Any, Callable, Coroutine, Iterable, TypeVar import discord @@ -51,6 +50,8 @@ "AutoShardedBot", ) +_log = logging.getLogger(__name__) + MISSING: Any = discord.utils.MISSING T = TypeVar("T") @@ -156,7 +157,7 @@ async def on_command_error( The default command error handler provided by the bot. - By default, this prints to :data:`sys.stderr` however it could be + By default, this logs with :meth:`logging.error` however it could be overridden to have a different implementation. This only fires if you do not specify any listeners for command error. @@ -172,9 +173,8 @@ async def on_command_error( if cog and cog.has_error_handler(): return - print(f"Ignoring exception in command {context.command}:", file=sys.stderr) - traceback.print_exception( - type(exception), exception, exception.__traceback__, file=sys.stderr + logging.error( + f"Ignoring exception in command {context.command}", exc_info=exception ) async def can_run(self, ctx: Context, *, call_once: bool = False) -> bool: diff --git a/discord/ext/tasks/__init__.py b/discord/ext/tasks/__init__.py index af34cc6844..15e194f3a4 100644 --- a/discord/ext/tasks/__init__.py +++ b/discord/ext/tasks/__init__.py @@ -28,8 +28,7 @@ import asyncio import datetime import inspect -import sys -import traceback +import logging from collections.abc import Sequence from typing import Any, Awaitable, Callable, Generic, TypeVar, cast @@ -47,6 +46,8 @@ FT = TypeVar("FT", bound=_func) ET = TypeVar("ET", bound=Callable[[Any, BaseException], Awaitable[Any]]) +_log = logging.getLogger(__name__) + class SleepHandle: __slots__ = ("future", "loop", "handle") @@ -474,12 +475,9 @@ def is_running(self) -> bool: async def _error(self, *args: Any) -> None: exception: Exception = args[-1] - print( + _log.error( f"Unhandled exception in internal background task {self.coro.__name__!r}.", - file=sys.stderr, - ) - traceback.print_exception( - type(exception), exception, exception.__traceback__, file=sys.stderr + exc_info=exception, ) def before_loop(self, coro: FT) -> FT: @@ -544,7 +542,7 @@ def error(self, coro: ET) -> ET: The coroutine must take only one argument the exception raised (except ``self`` in a class context). - By default, this prints to :data:`sys.stderr` however it could be + By default, this logs with the logging module however it could be overridden to have a different implementation. .. versionadded:: 1.4 diff --git a/discord/opus.py b/discord/opus.py index 6ea6f84308..0aee74e0d3 100644 --- a/discord/opus.py +++ b/discord/opus.py @@ -550,7 +550,7 @@ def run(self): data.decrypted_data ) except OpusError: - print("Error occurred while decoding opus frame.") + _log.exception("Error occurred while decoding opus frame.") continue self.client.recv_decoded_audio(data) @@ -560,7 +560,7 @@ def stop(self): time.sleep(0.1) self.decoder = {} gc.collect() - print("Decoder Process Killed") + _log.info("Decoder Process Killed") self._end_thread.set() def get_decoder(self, ssrc): diff --git a/discord/player.py b/discord/player.py index 65b23ed42a..4d2004dee6 100644 --- a/discord/player.py +++ b/discord/player.py @@ -808,8 +808,6 @@ def _call_after(self) -> None: elif error: msg = f"Exception in voice thread {self.name}" _log.exception(msg, exc_info=error) - print(msg, file=sys.stderr) - traceback.print_exception(type(error), error, error.__traceback__) def stop(self) -> None: self._end.set() diff --git a/discord/ui/modal.py b/discord/ui/modal.py index 58cf7db1e6..b5aa747263 100644 --- a/discord/ui/modal.py +++ b/discord/ui/modal.py @@ -259,7 +259,7 @@ async def on_error(self, error: Exception, interaction: Interaction) -> None: A callback that is called when the modal's callback fails with an error. - The default implementation prints the traceback to stderr. + The default implementation logs the traceback with the logging module. Parameters ---------- diff --git a/discord/ui/view.py b/discord/ui/view.py index 64b0520172..bc11b213ef 100644 --- a/discord/ui/view.py +++ b/discord/ui/view.py @@ -520,7 +520,7 @@ async def on_error( A callback that is called when an item's callback or :meth:`interaction_check` fails with an error. - The default implementation prints the traceback to stderr. + The default implementation logs the traceback with the logging module. Parameters ---------- diff --git a/discord/voice_client.py b/discord/voice_client.py index 3ed983e2b6..448bc9c01b 100644 --- a/discord/voice_client.py +++ b/discord/voice_client.py @@ -863,7 +863,7 @@ def recv_audio(self, sink, callback, *args): ready, _, err = select.select([self.socket], [], [self.socket], 0.01) if not ready: if err: - print(f"Socket error: {err}") + _log.error(f"Socket error: {err}") continue try: @@ -880,7 +880,7 @@ def recv_audio(self, sink, callback, *args): result = callback.result() if result is not None: - print(result) + _log.info(result) def recv_decoded_audio(self, data: RawData): # Add silence when they were not being recorded.