Skip to content

Commit ce0abcc

Browse files
feat: debugger module for dumping logs in guild
Signed-off-by: onerandomusername <genericusername414+git@gmail.com>
1 parent a20721d commit ce0abcc

File tree

1 file changed

+143
-0
lines changed

1 file changed

+143
-0
lines changed

modmail/extensions/debug.py

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
"""
2+
Useful commands for debugging built in bot issues.
3+
4+
Often a problem is a configuration issue, this cog will help determine issues.
5+
"""
6+
7+
import logging
8+
import typing
9+
10+
import discord
11+
from discord.ext import commands
12+
from discord.ext.commands import Context
13+
14+
import modmail
15+
from modmail.bot import ModmailBot
16+
from modmail.log import ModmailLogger
17+
from modmail.utils.cogs import BotModes, ExtMetadata, ModmailCog
18+
from modmail.utils.extensions import BOT_MODE
19+
from modmail.utils.pagination import ButtonPaginator
20+
21+
22+
logger: ModmailLogger = logging.getLogger(__name__)
23+
24+
EXT_METADATA = ExtMetadata()
25+
26+
EXTRAS_DEBUG_KEY = "only_if_cog_debug"
27+
28+
29+
class AlreadyInModeError(Exception):
30+
"""Raised if the cog is already in a mode."""
31+
32+
pass
33+
34+
35+
class DebugCog(ModmailCog, name="Debugger"):
36+
"""Debug commands."""
37+
38+
def __init__(self, bot: ModmailBot):
39+
self.bot = bot
40+
self.enable_debug_commands = bool(BOT_MODE & BotModes.DEVELOP.value)
41+
self.enable_or_disable_commands(self.enable_debug_commands, force=True)
42+
43+
def enable_or_disable_commands(self, new_status: bool = None, *, force: bool = False) -> bool:
44+
"""Enable or disable debug only commands."""
45+
if not force and (new_status == self.enable_debug_commands):
46+
raise AlreadyInModeError(new_status)
47+
elif new_status is not None:
48+
self.enable_debug_commands = new_status
49+
else:
50+
self.enable_debug_commands = not self.enable_debug_commands
51+
52+
for com in self.walk_commands():
53+
if com.extras.get(EXTRAS_DEBUG_KEY, False):
54+
com.enabled = self.enable_debug_commands
55+
return self.enable_debug_commands
56+
57+
@commands.group(invoke_without_command=True)
58+
async def debug(self, ctx: Context, enable_or_disable: bool = None) -> None:
59+
"""Commands to show logs and debug portions of the bot."""
60+
if enable_or_disable is not None:
61+
print(enable_or_disable)
62+
if enable_or_disable:
63+
self.enable_or_disable_commands(True)
64+
msg = "Enabled the debugger commands."
65+
else:
66+
self.enable_or_disable_commands(False)
67+
msg = "Disabled the debugger commands."
68+
await ctx.send(msg)
69+
return
70+
71+
await ctx.send(self.enable_debug_commands)
72+
73+
@staticmethod
74+
def get_logs(tail_lines: int = 200, lines_per_page: int = 10) -> typing.List[str]:
75+
"""Get a list of the tail logs."""
76+
logs = []
77+
78+
pages = tail_lines // lines_per_page
79+
if tail_lines < lines_per_page:
80+
pages = 1
81+
82+
try:
83+
f = open(modmail.log_file)
84+
all_logs = f.read()
85+
finally:
86+
f.close()
87+
# get the last lines on the code
88+
pos = len(all_logs)
89+
for _ in range(tail_lines):
90+
if (nl_pos := all_logs.rfind("\n", None, pos)) == -1:
91+
break
92+
pos = nl_pos
93+
94+
# split away the useless section
95+
all_logs = all_logs[pos:]
96+
for _ in range(pages):
97+
slice_pos = -1
98+
for _ in range(lines_per_page):
99+
if (
100+
(inital_slice_pos := all_logs.find("\n", slice_pos + 1)) == -1
101+
) or slice_pos == inital_slice_pos:
102+
break
103+
slice_pos = inital_slice_pos
104+
if not len(to_append := all_logs[:slice_pos]) > 0:
105+
continue
106+
logs.append(to_append)
107+
all_logs = all_logs[slice_pos:]
108+
109+
print(len(logs))
110+
return logs
111+
112+
@debug.command(name="logs", aliases=("log",), extras={EXTRAS_DEBUG_KEY: True})
113+
async def logs(self, ctx: Context) -> None:
114+
"""Paginate through the last 10 pages of logs."""
115+
logs = self.get_logs()
116+
await ButtonPaginator.paginate(
117+
logs,
118+
ctx.message,
119+
title="**Modmail Logs**",
120+
prefix="```prolog\n",
121+
suffix="```",
122+
embed=None,
123+
max_size=2000,
124+
)
125+
126+
@debug.command(name="dump", extras={EXTRAS_DEBUG_KEY: True})
127+
async def dump_logs(self, ctx: Context) -> None:
128+
"""Dump the log file in the chat."""
129+
logger.info(f"{ctx.author!s} requested the logs")
130+
async with ctx.typing():
131+
with open(modmail.log_file, "rb") as f:
132+
file = discord.File(f, filename="modmail-logs.prolog")
133+
134+
await ctx.send(
135+
"Please share this file with the developers. Do not share this with anyone else, "
136+
"as there may be sensitive information in this file.",
137+
file=file,
138+
)
139+
140+
141+
def setup(bot: ModmailBot) -> None:
142+
"""Add the debug cog to the bot."""
143+
bot.add_cog(DebugCog(bot))

0 commit comments

Comments
 (0)