(meta): Improve logging.

This commit is contained in:
2023-10-08 09:05:20 +03:00
parent 7e82acd9f8
commit 0190982291
5 changed files with 121 additions and 10 deletions

View File

@@ -2,6 +2,7 @@ from typing import Optional
import datetime as dt
import pytz
import discord
import logging
from meta import LionBot
from utils.lib import Timezoned
@@ -13,6 +14,9 @@ from .lion_user import LionUser
from .lion_guild import LionGuild
logger = logging.getLogger(__name__)
class MemberConfig(ModelConfig):
settings = SettingDotDict()
_model_settings = set()
@@ -103,12 +107,16 @@ class LionMember(Timezoned):
async def remove_role(self, role: discord.Role):
member = await self.fetch_member()
if member is not None and role in member.roles:
if member is not None:
try:
await member.remove_roles(role)
except discord.HTTPException:
except discord.HTTPException as e:
# TODO: Logging, audit logging
pass
logger.warning(
"Lion role removal failed for "
f"<uid: {member.id}>, <gid: {member.guild.id}>, <rid: {role.id}>. "
f"Error: {repr(e)}",
)
else:
# Remove the role from persistent role storage
cog = self.bot.get_cog('MemberAdminCog')

View File

@@ -14,6 +14,7 @@ from meta import LionCog, LionBot, LionContext
from meta.logger import log_wrap
from meta.errors import ResponseTimedOut, UserInputError, UserCancelled, SafeCancellation
from meta.sharding import THIS_SHARD
from meta.monitor import ComponentMonitor, ComponentStatus, StatusLevel
from utils.lib import utc_now, error_embed
from utils.ui import Confirm, ChoicedEnum, Transformed, AButton, AsComponents
from utils.transformers import DurationTransformer
@@ -142,6 +143,9 @@ class RoleMenuCog(LionCog):
def __init__(self, bot: LionBot):
self.bot = bot
self.data = bot.db.load_registry(RoleMenuData())
self.monitor = ComponentMonitor('RoleMenus', self._monitor)
self.ready = asyncio.Event()
# Menu caches
self.live_menus = RoleMenu.attached_menus # guildid -> messageid -> menuid
@@ -149,11 +153,42 @@ class RoleMenuCog(LionCog):
# Expiry manage
self.expiry_monitor = ExpiryMonitor(executor=self._expire)
async def _monitor(self):
state = (
"<"
"RoleMenus"
" ready={ready}"
" cached={cached}"
" views={views}"
" live={live}"
" expiry={expiry}"
">"
)
data = dict(
ready=self.ready.is_set(),
live=sum(len(gmenus) for gmenus in self.live_menus.values()),
expiry=repr(self.expiry_monitor),
cached=len(RoleMenu._menus),
views=len(RoleMenu.menu_views),
)
if not self.ready.is_set():
level = StatusLevel.STARTING
info = f"(STARTING) Not initialised. {state}"
elif not self.expiry_monitor._monitor_task:
level = StatusLevel.ERRORED
info = f"(ERRORED) Expiry monitor not running. {state}"
else:
level = StatusLevel.OKAY
info = f"(OK) RoleMenu loaded and listening. {state}"
return ComponentStatus(level, info, info, data)
# ----- Initialisation -----
async def cog_load(self):
self.bot.system_monitor.add_component(self.monitor)
await self.data.init()
self.bot.tree.add_command(rolemenu_ctxcmd)
self.bot.tree.add_command(rolemenu_ctxcmd, override=True)
if self.bot.is_ready():
await self.initialise()
@@ -164,17 +199,28 @@ class RoleMenuCog(LionCog):
self.live_menus.clear()
if self.expiry_monitor._monitor_task:
self.expiry_monitor._monitor_task.cancel()
self.bot.tree.remove_command(rolemenu_ctxcmd)
@LionCog.listener('on_ready')
@log_wrap(action="Initialise Role Menus")
async def initialise(self):
self.ready.clear()
# Clean up live menu tasks
for menu in list(RoleMenu._menus.values()):
menu.detach()
self.live_menus.clear()
if self.expiry_monitor._monitor_task:
self.expiry_monitor._monitor_task.cancel()
# Start monitor
self.expiry_monitor = ExpiryMonitor(executor=self._expire)
self.expiry_monitor.start()
# Load guilds
guildids = [guild.id for guild in self.bot.guilds]
if guildids:
await self._initialise_guilds(*guildids)
self.ready.set()
async def _initialise_guilds(self, *guildids):
"""
@@ -262,7 +308,7 @@ class RoleMenuCog(LionCog):
If the bot is no longer in the server, ignores the expiry.
If the member is no longer in the server, removes the role from persisted roles, if applicable.
"""
logger.debug(f"Expiring RoleMenu equipped role {equipid}")
logger.info(f"Expiring RoleMenu equipped role {equipid}")
rows = await self.data.RoleMenuHistory.fetch_expiring_where(equipid=equipid)
if rows:
equip_row = rows[0]
@@ -277,6 +323,7 @@ class RoleMenuCog(LionCog):
await equip_row.update(removed_at=now)
else:
# equipid is no longer valid or is not expiring
logger.info(f"RoleMenu equipped role {equipid} is no longer valid or is not expiring.")
pass
# ----- Private Utils -----

View File

@@ -982,7 +982,7 @@ class ScheduleCog(LionCog):
value=partial
)
)
return choices
return choices[:25]
@schedule_cmd.autocomplete('cancel')
async def schedule_cmd_cancel_acmpl(self, interaction: discord.Interaction, partial: str):

View File

@@ -13,6 +13,7 @@ from meta.errors import UserInputError
from meta.logger import log_wrap, logging_context
from meta.sharding import THIS_SHARD
from meta.app import appname
from meta.monitor import ComponentMonitor, ComponentStatus, StatusLevel
from utils.lib import utc_now, error_embed
from wards import low_management_ward, sys_admin_ward
@@ -42,10 +43,14 @@ class TextTrackerCog(LionCog):
self.data = bot.db.load_registry(TextTrackerData())
self.settings = TextTrackerSettings()
self.global_settings = TextTrackerGlobalSettings()
self.monitor = ComponentMonitor('TextTracker', self._monitor)
self.babel = babel
self.sessionq = asyncio.Queue(maxsize=0)
self.ready = asyncio.Event()
self.errors = 0
# Map of ongoing text sessions
# guildid -> (userid -> TextSession)
self.ongoing = defaultdict(dict)
@@ -54,7 +59,41 @@ class TextTrackerCog(LionCog):
self.untracked_channels = self.settings.UntrackedTextChannels._cache
async def _monitor(self):
state = (
"<"
"TextTracker"
" ready={ready}"
" queued={queued}"
" errors={errors}"
" running={running}"
" consumer={consumer}"
">"
)
data = dict(
ready=self.ready.is_set(),
queued=self.sessionq.qsize(),
errors=self.errors,
running=sum(len(usessions) for usessions in self.ongoing.values()),
consumer="'Running'" if (self._consumer_task and not self._consumer_task.done()) else "'Not Running'",
)
if not self.ready.is_set():
level = StatusLevel.STARTING
info = f"(STARTING) Not initialised. {state}"
elif not self._consumer_task:
level = StatusLevel.ERRORED
info = f"(ERROR) Consumer task not running. {state}"
elif self.errors > 1:
level = StatusLevel.UNSURE
info = f"(UNSURE) Errors occurred while consuming. {state}"
else:
level = StatusLevel.OKAY
info = f"(OK) Message tracking operational. {state}"
return ComponentStatus(level, info, info, data)
async def cog_load(self):
self.bot.system_monitor.add_component(self.monitor)
await self.data.init()
self.bot.core.guild_config.register_model_setting(self.settings.XPPerPeriod)
@@ -83,6 +122,7 @@ class TextTrackerCog(LionCog):
await self.initialise()
async def cog_unload(self):
self.ready.clear()
if self._consumer_task is not None:
self._consumer_task.cancel()
@@ -104,7 +144,7 @@ class TextTrackerCog(LionCog):
await self.bot.core.lions.fetch_member(session.guildid, session.userid)
self.sessionq.put_nowait(session)
@log_wrap(stack=['Text Sessions', 'Message Event'])
@log_wrap(stack=['Text Sessions', 'Consumer'])
async def _session_consumer(self):
"""
Process completed sessions in batches of length `batchsize`.
@@ -132,6 +172,7 @@ class TextTrackerCog(LionCog):
logger.exception(
"Unknown exception processing batch of text sessions! Discarding and continuing."
)
self.errors += 1
batch = []
counter = 0
last_time = time.monotonic()
@@ -202,9 +243,11 @@ class TextTrackerCog(LionCog):
"""
Launch the session consumer.
"""
self.ready.clear()
if self._consumer_task and not self._consumer_task.cancelled():
self._consumer_task.cancel()
self._consumer_task = asyncio.create_task(self._session_consumer())
self._consumer_task = asyncio.create_task(self._session_consumer(), name='text-session-consumer')
self.ready.set()
logger.info("Launched text session consumer.")
@LionCog.listener('on_message')

View File

@@ -32,7 +32,7 @@ class TaskMonitor(Generic[Taskid]):
self.executor: Optional[Callable[[Taskid], Coroutine[Any, Any, None]]] = executor
self._wakeup: asyncio.Event = asyncio.Event()
self._monitor_task: Optional[self.Task] = None
self._monitor_task: Optional[asyncio.Task] = None
# Task data
self._tasklist: list[Taskid] = []
@@ -42,6 +42,19 @@ class TaskMonitor(Generic[Taskid]):
# And allows simpler external cancellation if required
self._running: dict[Taskid, asyncio.Future] = {}
def __repr__(self):
return (
"<"
f"{self.__class__.__name__}"
f" tasklist={len(self._tasklist)}"
f" taskmap={len(self._taskmap)}"
f" wakeup={self._wakeup.is_set()}"
f" bucket={self._bucket}"
f" running={len(self._running)}"
f" task={self._monitor_task}"
f">"
)
def set_tasks(self, *tasks: tuple[Taskid, int]) -> None:
"""
Similar to `schedule_tasks`, but wipe and reset the tasklist.