diff --git a/payphone/bot.py b/payphone/bot.py index 5c3e857..39767f5 100644 --- a/payphone/bot.py +++ b/payphone/bot.py @@ -8,7 +8,7 @@ import sys import discord from discord import abc -from discord.ext import commands, tasks +from discord.ext import commands from discord.ext.commands import errors from tortoise import expressions, transactions, exceptions as tortoise_exc @@ -18,7 +18,17 @@ import redis.asyncio as aredis import asyncpg.exceptions from payphone.db import setup, models -from payphone.core import constants, exceptions, formatting, help_command, config +from payphone.core import ( + constants, + exceptions, + formatting, + help_command, + config, + logging +) + + +logging.setup() class Payphone(commands.AutoShardedBot): @@ -42,6 +52,8 @@ class Payphone(commands.AutoShardedBot): `shutdown`. """ + log = logging.logger + redis: aredis.Redis = aredis.Redis(host="localhost" if config.IS_DEBUG else "redis") def __init__(self) -> None: @@ -72,7 +84,7 @@ class Payphone(commands.AutoShardedBot): await self.redis.ping() - print(f"Ready! Logged in as {self.user.display_name}.") + self.log.info("Ready! Logged in as %s." % self.user.display_name) async def on_shard_ready(self, shard_id: int) -> None: """Sets the activity for the shard. @@ -89,7 +101,7 @@ class Payphone(commands.AutoShardedBot): shard_id=shard_id, ) - print(f"Shard {shard_id} is ready!") + self.log.info("Shard %s is ready!" % shard_id) async def on_shard_resumed(self, shard_id: int) -> None: """Sets the activity for the shard. @@ -106,17 +118,7 @@ class Payphone(commands.AutoShardedBot): shard_id=shard_id, ) - print(f"Shard {shard_id} was resumed!") - - async def on_shard_connect(self, shard_id: int) -> None: - try: - self.update_heartbeat.start() - - except RuntimeError: - pass - - async def on_shard_disconnect(self, shard_id: int) -> None: - self.update_heartbeat.cancel() + self.log.info("Shard %s was resumed!" % shard_id) async def get_or_fetch_channel( self, channel_id: int diff --git a/payphone/core/logging.py b/payphone/core/logging.py new file mode 100644 index 0000000..c3785fc --- /dev/null +++ b/payphone/core/logging.py @@ -0,0 +1,19 @@ +import logging + +from rich import logging as rlogging + + +FORMAT = "%(message)s" + +handler = rlogging.RichHandler(level=logging.INFO, rich_tracebacks=True) + +formatter = logging.Formatter(FORMAT) +handler.setFormatter(formatter) + +logger = logging.getLogger("payphone") + + +def setup(): + """Sets up pycord logging.""" + + logging.basicConfig(level=logging.DEBUG, handlers=[handler]) diff --git a/procs/flush_messages.py b/procs/flush_messages.py index 71d1d14..a482fe3 100644 --- a/procs/flush_messages.py +++ b/procs/flush_messages.py @@ -1,3 +1,11 @@ +import asyncio + +from payphone.core import logging + + +log = logging.logger + + async def flush(): import orjson @@ -22,10 +30,10 @@ async def flush(): call_ids = [int(call_id) for call_id in call_ids] if len(call_ids) == 0: - print("Skipping flushing. No calls to flush.") + log.info("Skipping flushing. No calls to flush.") return - print(f"Flushing messages for {len(call_ids)} calls.") + log.info("Flushing messages for %s calls." % len(call_ids)) pipe = redis.pipeline() @@ -72,8 +80,9 @@ async def flush(): asyncpg.exceptions.PostgresError, asyncpg.exceptions.InterfaceError, ) as e: - print( - f"[Call ID: {call_id}] An error occured trying to flush messages for call ID. {e}" + log.error( + "[Call ID: %s] An error occured trying to flush messages for call ID. %s" + % (call_id, e) ) continue @@ -88,14 +97,18 @@ async def flush(): asyncpg.exceptions.PostgresError, asyncpg.exceptions.InterfaceError, ) as e: - print( - f"[Call ID: {call_id}] An error occured trying to flush message contents. {e}" + log.error( + "[Call ID: %s] An error occured trying to flush message contents. %s" + % (call_id, e) ) continue - print(f"[Call ID: {call_id}] Flushed {len(messages)} messages and {len(message_contents)} message contents.") + log.info( + "[Call ID: %s] Flushed %s messages and %s message contents." + % (call_id, len(messages), len(message_contents)) + ) - print("Finished flushing messages.") + log.info("Finished flushing messages.") async def loop() -> None: @@ -107,7 +120,11 @@ async def loop() -> None: await setup.init() - while True: - await asyncio.sleep(30) + try: + while True: + await asyncio.sleep(30) - await flush() + await flush() + + except (KeyboardInterrupt, asyncio.exceptions.CancelledError) as e: + log.exception(e) diff --git a/pyproject.toml b/pyproject.toml index d96af10..38dc137 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,6 +14,7 @@ dependencies = [ "py-cord>=2.6.1", "python-dotenv>=1.0.1", "redis[hiredis]>=5.1.1", + "rich>=13.9.3", "tortoise-orm[asyncpg]>=0.21.7", ] diff --git a/run.py b/run.py index 549278a..0350e3c 100644 --- a/run.py +++ b/run.py @@ -2,6 +2,13 @@ from multiprocessing import managers from datetime import datetime, timezone +from payphone.core import logging + + +logging.setup() + +log = logging.logger + def shard( shard_id: int, @@ -27,7 +34,7 @@ def shard( bot.shutdown = shutdown bot.scheduled_shutdown = scheduled_shutdown - print(f"Running shard {shard_id} with default loop.") + log.info("Running shard %s with default loop." % shard_id) bot.run(config.TOKEN) @@ -40,10 +47,10 @@ def flush_process(): import uvloop # pylint: disable=import-error uvloop.install() - print("Flushing messages with uvloop.") + log.info("Flushing messages with uvloop.") except ImportError: - print("Flushing messages with default loop.") + log.info("Flushing messages with default loop.") finally: asyncio.run(loop()) @@ -79,11 +86,11 @@ if __name__ == "__main__": await redis.flushall() - print("Running DB and Redis cleanup...") + log.info("Cleanup done! Loading recommended shard amount...") run_async(init()) - print("Cleanup done! Loading recommended shard amount...") + log.info("Cleanup done! Loading recommended shard amount...") response = httpx.get( "https://discord.com/api/v10/gateway/bot", @@ -98,7 +105,7 @@ if __name__ == "__main__": shutdown = manager.Value("b", False) scheduled_shutdown = manager.Value("b", False) - print(f"Starting {shard_count} shard{"s" if shard_count != 1 else ""}.") + log.info("Starting %s shards." % shard_count) ps: list[mp.Process] = [ mp.Process( @@ -112,7 +119,7 @@ if __name__ == "__main__": for i, p in enumerate(ps): p.start() - print( + log.info( f"Shard {i}'s process started." + ( " Waiting 5 seconds start initialize next shard..." @@ -124,11 +131,11 @@ if __name__ == "__main__": if i != len(ps) - 1: time.sleep(5) - print("Starting messages flushing process...") + log.info("Starting messages flushing process...") flushing_p.start() - print("Messages flushing process started.") + log.info("Messages flushing process started.") try: while True: @@ -137,7 +144,9 @@ if __name__ == "__main__": for i in range(shard_count): if not ps[i].is_alive(): - print(f"Shard {i}'s process exited. Restarting the shard...") + log.warning( + "Shard %s's process exited. Restarting the shard..." % s + ) ps[i] = mp.Process( target=shard, @@ -151,43 +160,44 @@ if __name__ == "__main__": ) ps[i].start() - print( - f"Shard {i} was restarted. Waiting 5 seconds to check next shard..." + log.info( + "Shard %s was restarted. Waiting 5 seconds to check next shard..." + % i ) time.sleep(5) if not flushing_p.is_alive(): - print("Messages flushing process was stopped. Restarting it...") + log.info("Messages flushing process was stopped. Restarting it...") flushing_p = mp.Process(target=flush_process, name="payphone-flushing") flushing_p.start() - print("Messages flushing process started.") + log.info("Messages flushing process started.") time.sleep(5) except KeyboardInterrupt: - print("Ctrl-C was pressed.") + log.info("Ctrl-C was pressed.") except ShutdownException: - print("A shard shut down the bot.") + log.info("A shard shut down the bot.") finally: - print(f"Terminating {shard_count} shards...") + log.info("Terminating %s shards..." % shard_count) for i, p in enumerate(ps): p.terminate() - print(f"Terminated shard {i}.") + log.info("Terminated shard %s." % i) - print("Stopping messages flushing process...") + log.info("Stopping messages flushing process...") flushing_p.terminate() - print("Checking that all processes have finished...") + log.info("Checking that all processes have finished...") for p in ps: p.join() flushing_p.join() - print("All processes have been terminated.") + log.info("All processes have been terminated.") diff --git a/uv.lock b/uv.lock index 2c2df91..a614cef 100644 --- a/uv.lock +++ b/uv.lock @@ -301,6 +301,27 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6c/0c/f37b6a241f0759b7653ffa7213889d89ad49a2b76eb2ddf3b57b2738c347/iso8601-2.1.0-py3-none-any.whl", hash = "sha256:aac4145c4dcb66ad8b648a02830f5e2ff6c24af20f4f482689be402db2429242", size = 7545 }, ] +[[package]] +name = "markdown-it-py" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528 }, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 }, +] + [[package]] name = "multidict" version = "6.1.0" @@ -378,6 +399,7 @@ dependencies = [ { name = "py-cord" }, { name = "python-dotenv" }, { name = "redis", extra = ["hiredis"] }, + { name = "rich" }, { name = "tortoise-orm", extra = ["asyncpg"] }, ] @@ -392,6 +414,7 @@ requires-dist = [ { name = "py-cord", specifier = ">=2.6.1" }, { name = "python-dotenv", specifier = ">=1.0.1" }, { name = "redis", extras = ["hiredis"], specifier = ">=5.1.1" }, + { name = "rich", specifier = ">=13.9.3" }, { name = "tortoise-orm", extras = ["asyncpg"], specifier = ">=0.21.7" }, ] @@ -535,6 +558,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a5/ae/e14b0ff8b3f48e02394d8acd911376b7b66e164535687ef7dc24ea03072f/pydantic_core-2.23.4-cp313-none-win_amd64.whl", hash = "sha256:5a1504ad17ba4210df3a045132a7baeeba5a200e930f57512ee02909fc5c4cb5", size = 1919411 }, ] +[[package]] +name = "pygments" +version = "2.18.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8e/62/8336eff65bcbc8e4cb5d05b55faf041285951b6e80f33e2bff2024788f31/pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199", size = 4891905 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/3f/01c8b82017c199075f8f788d0d906b9ffbbc5a47dc9918a945e13d5a2bda/pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a", size = 1205513 }, +] + [[package]] name = "pypika-tortoise" version = "0.2.1" @@ -576,6 +608,19 @@ hiredis = [ { name = "hiredis" }, ] +[[package]] +name = "rich" +version = "13.9.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d9/e9/cf9ef5245d835065e6673781dbd4b8911d352fb770d56cf0879cf11b7ee1/rich-13.9.3.tar.gz", hash = "sha256:bc1e01b899537598cf02579d2b9f4a415104d3fc439313a7a2c165d76557a08e", size = 222889 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/e2/10e9819cf4a20bd8ea2f5dabafc2e6bf4a78d6a0965daeb60a4b34d1c11f/rich-13.9.3-py3-none-any.whl", hash = "sha256:9836f5096eb2172c9e77df411c1b009bace4193d6a481d534fea75ebba758283", size = 242157 }, +] + [[package]] name = "sniffio" version = "1.3.1"