(meta): Improve logging.
This commit is contained in:
@@ -2,6 +2,7 @@ from typing import Optional
|
|||||||
import datetime as dt
|
import datetime as dt
|
||||||
import pytz
|
import pytz
|
||||||
import discord
|
import discord
|
||||||
|
import logging
|
||||||
|
|
||||||
from meta import LionBot
|
from meta import LionBot
|
||||||
from utils.lib import Timezoned
|
from utils.lib import Timezoned
|
||||||
@@ -13,6 +14,9 @@ from .lion_user import LionUser
|
|||||||
from .lion_guild import LionGuild
|
from .lion_guild import LionGuild
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class MemberConfig(ModelConfig):
|
class MemberConfig(ModelConfig):
|
||||||
settings = SettingDotDict()
|
settings = SettingDotDict()
|
||||||
_model_settings = set()
|
_model_settings = set()
|
||||||
@@ -103,12 +107,16 @@ class LionMember(Timezoned):
|
|||||||
|
|
||||||
async def remove_role(self, role: discord.Role):
|
async def remove_role(self, role: discord.Role):
|
||||||
member = await self.fetch_member()
|
member = await self.fetch_member()
|
||||||
if member is not None and role in member.roles:
|
if member is not None:
|
||||||
try:
|
try:
|
||||||
await member.remove_roles(role)
|
await member.remove_roles(role)
|
||||||
except discord.HTTPException:
|
except discord.HTTPException as e:
|
||||||
# TODO: Logging, audit logging
|
# 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:
|
else:
|
||||||
# Remove the role from persistent role storage
|
# Remove the role from persistent role storage
|
||||||
cog = self.bot.get_cog('MemberAdminCog')
|
cog = self.bot.get_cog('MemberAdminCog')
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ from meta import LionCog, LionBot, LionContext
|
|||||||
from meta.logger import log_wrap
|
from meta.logger import log_wrap
|
||||||
from meta.errors import ResponseTimedOut, UserInputError, UserCancelled, SafeCancellation
|
from meta.errors import ResponseTimedOut, UserInputError, UserCancelled, SafeCancellation
|
||||||
from meta.sharding import THIS_SHARD
|
from meta.sharding import THIS_SHARD
|
||||||
|
from meta.monitor import ComponentMonitor, ComponentStatus, StatusLevel
|
||||||
from utils.lib import utc_now, error_embed
|
from utils.lib import utc_now, error_embed
|
||||||
from utils.ui import Confirm, ChoicedEnum, Transformed, AButton, AsComponents
|
from utils.ui import Confirm, ChoicedEnum, Transformed, AButton, AsComponents
|
||||||
from utils.transformers import DurationTransformer
|
from utils.transformers import DurationTransformer
|
||||||
@@ -142,6 +143,9 @@ class RoleMenuCog(LionCog):
|
|||||||
def __init__(self, bot: LionBot):
|
def __init__(self, bot: LionBot):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
self.data = bot.db.load_registry(RoleMenuData())
|
self.data = bot.db.load_registry(RoleMenuData())
|
||||||
|
self.monitor = ComponentMonitor('RoleMenus', self._monitor)
|
||||||
|
|
||||||
|
self.ready = asyncio.Event()
|
||||||
|
|
||||||
# Menu caches
|
# Menu caches
|
||||||
self.live_menus = RoleMenu.attached_menus # guildid -> messageid -> menuid
|
self.live_menus = RoleMenu.attached_menus # guildid -> messageid -> menuid
|
||||||
@@ -149,11 +153,42 @@ class RoleMenuCog(LionCog):
|
|||||||
# Expiry manage
|
# Expiry manage
|
||||||
self.expiry_monitor = ExpiryMonitor(executor=self._expire)
|
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 -----
|
# ----- Initialisation -----
|
||||||
async def cog_load(self):
|
async def cog_load(self):
|
||||||
|
self.bot.system_monitor.add_component(self.monitor)
|
||||||
await self.data.init()
|
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():
|
if self.bot.is_ready():
|
||||||
await self.initialise()
|
await self.initialise()
|
||||||
@@ -164,17 +199,28 @@ class RoleMenuCog(LionCog):
|
|||||||
self.live_menus.clear()
|
self.live_menus.clear()
|
||||||
if self.expiry_monitor._monitor_task:
|
if self.expiry_monitor._monitor_task:
|
||||||
self.expiry_monitor._monitor_task.cancel()
|
self.expiry_monitor._monitor_task.cancel()
|
||||||
self.bot.tree.remove_command(rolemenu_ctxcmd)
|
|
||||||
|
|
||||||
@LionCog.listener('on_ready')
|
@LionCog.listener('on_ready')
|
||||||
@log_wrap(action="Initialise Role Menus")
|
@log_wrap(action="Initialise Role Menus")
|
||||||
async def initialise(self):
|
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 = ExpiryMonitor(executor=self._expire)
|
||||||
self.expiry_monitor.start()
|
self.expiry_monitor.start()
|
||||||
|
|
||||||
|
# Load guilds
|
||||||
guildids = [guild.id for guild in self.bot.guilds]
|
guildids = [guild.id for guild in self.bot.guilds]
|
||||||
if guildids:
|
if guildids:
|
||||||
await self._initialise_guilds(*guildids)
|
await self._initialise_guilds(*guildids)
|
||||||
|
self.ready.set()
|
||||||
|
|
||||||
async def _initialise_guilds(self, *guildids):
|
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 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.
|
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)
|
rows = await self.data.RoleMenuHistory.fetch_expiring_where(equipid=equipid)
|
||||||
if rows:
|
if rows:
|
||||||
equip_row = rows[0]
|
equip_row = rows[0]
|
||||||
@@ -277,6 +323,7 @@ class RoleMenuCog(LionCog):
|
|||||||
await equip_row.update(removed_at=now)
|
await equip_row.update(removed_at=now)
|
||||||
else:
|
else:
|
||||||
# equipid is no longer valid or is not expiring
|
# 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
|
pass
|
||||||
|
|
||||||
# ----- Private Utils -----
|
# ----- Private Utils -----
|
||||||
|
|||||||
@@ -982,7 +982,7 @@ class ScheduleCog(LionCog):
|
|||||||
value=partial
|
value=partial
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return choices
|
return choices[:25]
|
||||||
|
|
||||||
@schedule_cmd.autocomplete('cancel')
|
@schedule_cmd.autocomplete('cancel')
|
||||||
async def schedule_cmd_cancel_acmpl(self, interaction: discord.Interaction, partial: str):
|
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.logger import log_wrap, logging_context
|
||||||
from meta.sharding import THIS_SHARD
|
from meta.sharding import THIS_SHARD
|
||||||
from meta.app import appname
|
from meta.app import appname
|
||||||
|
from meta.monitor import ComponentMonitor, ComponentStatus, StatusLevel
|
||||||
from utils.lib import utc_now, error_embed
|
from utils.lib import utc_now, error_embed
|
||||||
|
|
||||||
from wards import low_management_ward, sys_admin_ward
|
from wards import low_management_ward, sys_admin_ward
|
||||||
@@ -42,10 +43,14 @@ class TextTrackerCog(LionCog):
|
|||||||
self.data = bot.db.load_registry(TextTrackerData())
|
self.data = bot.db.load_registry(TextTrackerData())
|
||||||
self.settings = TextTrackerSettings()
|
self.settings = TextTrackerSettings()
|
||||||
self.global_settings = TextTrackerGlobalSettings()
|
self.global_settings = TextTrackerGlobalSettings()
|
||||||
|
self.monitor = ComponentMonitor('TextTracker', self._monitor)
|
||||||
self.babel = babel
|
self.babel = babel
|
||||||
|
|
||||||
self.sessionq = asyncio.Queue(maxsize=0)
|
self.sessionq = asyncio.Queue(maxsize=0)
|
||||||
|
|
||||||
|
self.ready = asyncio.Event()
|
||||||
|
self.errors = 0
|
||||||
|
|
||||||
# Map of ongoing text sessions
|
# Map of ongoing text sessions
|
||||||
# guildid -> (userid -> TextSession)
|
# guildid -> (userid -> TextSession)
|
||||||
self.ongoing = defaultdict(dict)
|
self.ongoing = defaultdict(dict)
|
||||||
@@ -54,7 +59,41 @@ class TextTrackerCog(LionCog):
|
|||||||
|
|
||||||
self.untracked_channels = self.settings.UntrackedTextChannels._cache
|
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):
|
async def cog_load(self):
|
||||||
|
self.bot.system_monitor.add_component(self.monitor)
|
||||||
await self.data.init()
|
await self.data.init()
|
||||||
|
|
||||||
self.bot.core.guild_config.register_model_setting(self.settings.XPPerPeriod)
|
self.bot.core.guild_config.register_model_setting(self.settings.XPPerPeriod)
|
||||||
@@ -83,6 +122,7 @@ class TextTrackerCog(LionCog):
|
|||||||
await self.initialise()
|
await self.initialise()
|
||||||
|
|
||||||
async def cog_unload(self):
|
async def cog_unload(self):
|
||||||
|
self.ready.clear()
|
||||||
if self._consumer_task is not None:
|
if self._consumer_task is not None:
|
||||||
self._consumer_task.cancel()
|
self._consumer_task.cancel()
|
||||||
|
|
||||||
@@ -104,7 +144,7 @@ class TextTrackerCog(LionCog):
|
|||||||
await self.bot.core.lions.fetch_member(session.guildid, session.userid)
|
await self.bot.core.lions.fetch_member(session.guildid, session.userid)
|
||||||
self.sessionq.put_nowait(session)
|
self.sessionq.put_nowait(session)
|
||||||
|
|
||||||
@log_wrap(stack=['Text Sessions', 'Message Event'])
|
@log_wrap(stack=['Text Sessions', 'Consumer'])
|
||||||
async def _session_consumer(self):
|
async def _session_consumer(self):
|
||||||
"""
|
"""
|
||||||
Process completed sessions in batches of length `batchsize`.
|
Process completed sessions in batches of length `batchsize`.
|
||||||
@@ -132,6 +172,7 @@ class TextTrackerCog(LionCog):
|
|||||||
logger.exception(
|
logger.exception(
|
||||||
"Unknown exception processing batch of text sessions! Discarding and continuing."
|
"Unknown exception processing batch of text sessions! Discarding and continuing."
|
||||||
)
|
)
|
||||||
|
self.errors += 1
|
||||||
batch = []
|
batch = []
|
||||||
counter = 0
|
counter = 0
|
||||||
last_time = time.monotonic()
|
last_time = time.monotonic()
|
||||||
@@ -202,9 +243,11 @@ class TextTrackerCog(LionCog):
|
|||||||
"""
|
"""
|
||||||
Launch the session consumer.
|
Launch the session consumer.
|
||||||
"""
|
"""
|
||||||
|
self.ready.clear()
|
||||||
if self._consumer_task and not self._consumer_task.cancelled():
|
if self._consumer_task and not self._consumer_task.cancelled():
|
||||||
self._consumer_task.cancel()
|
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.")
|
logger.info("Launched text session consumer.")
|
||||||
|
|
||||||
@LionCog.listener('on_message')
|
@LionCog.listener('on_message')
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ class TaskMonitor(Generic[Taskid]):
|
|||||||
self.executor: Optional[Callable[[Taskid], Coroutine[Any, Any, None]]] = executor
|
self.executor: Optional[Callable[[Taskid], Coroutine[Any, Any, None]]] = executor
|
||||||
|
|
||||||
self._wakeup: asyncio.Event = asyncio.Event()
|
self._wakeup: asyncio.Event = asyncio.Event()
|
||||||
self._monitor_task: Optional[self.Task] = None
|
self._monitor_task: Optional[asyncio.Task] = None
|
||||||
|
|
||||||
# Task data
|
# Task data
|
||||||
self._tasklist: list[Taskid] = []
|
self._tasklist: list[Taskid] = []
|
||||||
@@ -42,6 +42,19 @@ class TaskMonitor(Generic[Taskid]):
|
|||||||
# And allows simpler external cancellation if required
|
# And allows simpler external cancellation if required
|
||||||
self._running: dict[Taskid, asyncio.Future] = {}
|
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:
|
def set_tasks(self, *tasks: tuple[Taskid, int]) -> None:
|
||||||
"""
|
"""
|
||||||
Similar to `schedule_tasks`, but wipe and reset the tasklist.
|
Similar to `schedule_tasks`, but wipe and reset the tasklist.
|
||||||
|
|||||||
Reference in New Issue
Block a user