feat(bot): ✨ Add rich logging.

This commit is contained in:
Nyeki.py
2024-10-27 01:30:04 -03:00
parent ec00a557fa
commit 09d2808bd4
6 changed files with 141 additions and 47 deletions
+17 -15
View File
@@ -8,7 +8,7 @@ import sys
import discord import discord
from discord import abc from discord import abc
from discord.ext import commands, tasks from discord.ext import commands
from discord.ext.commands import errors from discord.ext.commands import errors
from tortoise import expressions, transactions, exceptions as tortoise_exc from tortoise import expressions, transactions, exceptions as tortoise_exc
@@ -18,7 +18,17 @@ import redis.asyncio as aredis
import asyncpg.exceptions import asyncpg.exceptions
from payphone.db import setup, models 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): class Payphone(commands.AutoShardedBot):
@@ -42,6 +52,8 @@ class Payphone(commands.AutoShardedBot):
`shutdown`. `shutdown`.
""" """
log = logging.logger
redis: aredis.Redis = aredis.Redis(host="localhost" if config.IS_DEBUG else "redis") redis: aredis.Redis = aredis.Redis(host="localhost" if config.IS_DEBUG else "redis")
def __init__(self) -> None: def __init__(self) -> None:
@@ -72,7 +84,7 @@ class Payphone(commands.AutoShardedBot):
await self.redis.ping() 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: async def on_shard_ready(self, shard_id: int) -> None:
"""Sets the activity for the shard. """Sets the activity for the shard.
@@ -89,7 +101,7 @@ class Payphone(commands.AutoShardedBot):
shard_id=shard_id, 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: async def on_shard_resumed(self, shard_id: int) -> None:
"""Sets the activity for the shard. """Sets the activity for the shard.
@@ -106,17 +118,7 @@ class Payphone(commands.AutoShardedBot):
shard_id=shard_id, shard_id=shard_id,
) )
print(f"Shard {shard_id} was resumed!") self.log.info("Shard %s was resumed!" % shard_id)
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()
async def get_or_fetch_channel( async def get_or_fetch_channel(
self, channel_id: int self, channel_id: int
+19
View File
@@ -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])
+28 -11
View File
@@ -1,3 +1,11 @@
import asyncio
from payphone.core import logging
log = logging.logger
async def flush(): async def flush():
import orjson import orjson
@@ -22,10 +30,10 @@ async def flush():
call_ids = [int(call_id) for call_id in call_ids] call_ids = [int(call_id) for call_id in call_ids]
if len(call_ids) == 0: if len(call_ids) == 0:
print("Skipping flushing. No calls to flush.") log.info("Skipping flushing. No calls to flush.")
return return
print(f"Flushing messages for {len(call_ids)} calls.") log.info("Flushing messages for %s calls." % len(call_ids))
pipe = redis.pipeline() pipe = redis.pipeline()
@@ -72,8 +80,9 @@ async def flush():
asyncpg.exceptions.PostgresError, asyncpg.exceptions.PostgresError,
asyncpg.exceptions.InterfaceError, asyncpg.exceptions.InterfaceError,
) as e: ) as e:
print( log.error(
f"[Call ID: {call_id}] An error occured trying to flush messages for call ID. {e}" "[Call ID: %s] An error occured trying to flush messages for call ID. %s"
% (call_id, e)
) )
continue continue
@@ -88,14 +97,18 @@ async def flush():
asyncpg.exceptions.PostgresError, asyncpg.exceptions.PostgresError,
asyncpg.exceptions.InterfaceError, asyncpg.exceptions.InterfaceError,
) as e: ) as e:
print( log.error(
f"[Call ID: {call_id}] An error occured trying to flush message contents. {e}" "[Call ID: %s] An error occured trying to flush message contents. %s"
% (call_id, e)
) )
continue 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: async def loop() -> None:
@@ -107,7 +120,11 @@ async def loop() -> None:
await setup.init() await setup.init()
while True: try:
await asyncio.sleep(30) while True:
await asyncio.sleep(30)
await flush() await flush()
except (KeyboardInterrupt, asyncio.exceptions.CancelledError) as e:
log.exception(e)
+1
View File
@@ -14,6 +14,7 @@ dependencies = [
"py-cord>=2.6.1", "py-cord>=2.6.1",
"python-dotenv>=1.0.1", "python-dotenv>=1.0.1",
"redis[hiredis]>=5.1.1", "redis[hiredis]>=5.1.1",
"rich>=13.9.3",
"tortoise-orm[asyncpg]>=0.21.7", "tortoise-orm[asyncpg]>=0.21.7",
] ]
+31 -21
View File
@@ -2,6 +2,13 @@ from multiprocessing import managers
from datetime import datetime, timezone from datetime import datetime, timezone
from payphone.core import logging
logging.setup()
log = logging.logger
def shard( def shard(
shard_id: int, shard_id: int,
@@ -27,7 +34,7 @@ def shard(
bot.shutdown = shutdown bot.shutdown = shutdown
bot.scheduled_shutdown = scheduled_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) bot.run(config.TOKEN)
@@ -40,10 +47,10 @@ def flush_process():
import uvloop # pylint: disable=import-error import uvloop # pylint: disable=import-error
uvloop.install() uvloop.install()
print("Flushing messages with uvloop.") log.info("Flushing messages with uvloop.")
except ImportError: except ImportError:
print("Flushing messages with default loop.") log.info("Flushing messages with default loop.")
finally: finally:
asyncio.run(loop()) asyncio.run(loop())
@@ -79,11 +86,11 @@ if __name__ == "__main__":
await redis.flushall() await redis.flushall()
print("Running DB and Redis cleanup...") log.info("Cleanup done! Loading recommended shard amount...")
run_async(init()) run_async(init())
print("Cleanup done! Loading recommended shard amount...") log.info("Cleanup done! Loading recommended shard amount...")
response = httpx.get( response = httpx.get(
"https://discord.com/api/v10/gateway/bot", "https://discord.com/api/v10/gateway/bot",
@@ -98,7 +105,7 @@ if __name__ == "__main__":
shutdown = manager.Value("b", False) shutdown = manager.Value("b", False)
scheduled_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] = [ ps: list[mp.Process] = [
mp.Process( mp.Process(
@@ -112,7 +119,7 @@ if __name__ == "__main__":
for i, p in enumerate(ps): for i, p in enumerate(ps):
p.start() p.start()
print( log.info(
f"Shard {i}'s process started." f"Shard {i}'s process started."
+ ( + (
" Waiting 5 seconds start initialize next shard..." " Waiting 5 seconds start initialize next shard..."
@@ -124,11 +131,11 @@ if __name__ == "__main__":
if i != len(ps) - 1: if i != len(ps) - 1:
time.sleep(5) time.sleep(5)
print("Starting messages flushing process...") log.info("Starting messages flushing process...")
flushing_p.start() flushing_p.start()
print("Messages flushing process started.") log.info("Messages flushing process started.")
try: try:
while True: while True:
@@ -137,7 +144,9 @@ if __name__ == "__main__":
for i in range(shard_count): for i in range(shard_count):
if not ps[i].is_alive(): 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( ps[i] = mp.Process(
target=shard, target=shard,
@@ -151,43 +160,44 @@ if __name__ == "__main__":
) )
ps[i].start() ps[i].start()
print( log.info(
f"Shard {i} was restarted. Waiting 5 seconds to check next shard..." "Shard %s was restarted. Waiting 5 seconds to check next shard..."
% i
) )
time.sleep(5) time.sleep(5)
if not flushing_p.is_alive(): 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 = mp.Process(target=flush_process, name="payphone-flushing")
flushing_p.start() flushing_p.start()
print("Messages flushing process started.") log.info("Messages flushing process started.")
time.sleep(5) time.sleep(5)
except KeyboardInterrupt: except KeyboardInterrupt:
print("Ctrl-C was pressed.") log.info("Ctrl-C was pressed.")
except ShutdownException: except ShutdownException:
print("A shard shut down the bot.") log.info("A shard shut down the bot.")
finally: finally:
print(f"Terminating {shard_count} shards...") log.info("Terminating %s shards..." % shard_count)
for i, p in enumerate(ps): for i, p in enumerate(ps):
p.terminate() 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() flushing_p.terminate()
print("Checking that all processes have finished...") log.info("Checking that all processes have finished...")
for p in ps: for p in ps:
p.join() p.join()
flushing_p.join() flushing_p.join()
print("All processes have been terminated.") log.info("All processes have been terminated.")
Generated
+45
View File
@@ -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 }, { 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]] [[package]]
name = "multidict" name = "multidict"
version = "6.1.0" version = "6.1.0"
@@ -378,6 +399,7 @@ dependencies = [
{ name = "py-cord" }, { name = "py-cord" },
{ name = "python-dotenv" }, { name = "python-dotenv" },
{ name = "redis", extra = ["hiredis"] }, { name = "redis", extra = ["hiredis"] },
{ name = "rich" },
{ name = "tortoise-orm", extra = ["asyncpg"] }, { name = "tortoise-orm", extra = ["asyncpg"] },
] ]
@@ -392,6 +414,7 @@ requires-dist = [
{ name = "py-cord", specifier = ">=2.6.1" }, { name = "py-cord", specifier = ">=2.6.1" },
{ name = "python-dotenv", specifier = ">=1.0.1" }, { name = "python-dotenv", specifier = ">=1.0.1" },
{ name = "redis", extras = ["hiredis"], specifier = ">=5.1.1" }, { name = "redis", extras = ["hiredis"], specifier = ">=5.1.1" },
{ name = "rich", specifier = ">=13.9.3" },
{ name = "tortoise-orm", extras = ["asyncpg"], specifier = ">=0.21.7" }, { 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 }, { 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]] [[package]]
name = "pypika-tortoise" name = "pypika-tortoise"
version = "0.2.1" version = "0.2.1"
@@ -576,6 +608,19 @@ hiredis = [
{ name = "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]] [[package]]
name = "sniffio" name = "sniffio"
version = "1.3.1" version = "1.3.1"