(meta): Improve logging.
This commit is contained in:
@@ -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')
|
||||
|
||||
@@ -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 -----
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user