Skip to content

Commit 86588f5

Browse files
committed
Add diagnostic logging to NSFW review flow
NSFW reports posted via the website-side GraphQL mutation aren't triggering reviews when moderators react in Discord, but the bot-command path (!review) works. Adds structured logging to disambiguate the two paths and pinpoint which step in the path-B (graphql) flow is failing: - DiscordEventHandler: client lifecycle events (ready, error, disconnect, reconnecting, shardError), login failures, top-level messageReactionAdd listener so we can see whether the Next.js-side gateway receives reactions at all - DiscordEventHandler.handle: entry log + try/catch so swallowed errors surface (notify is fire-and-forget) - requestReview: entry, resolved tweetStatus, explicit "skipping already-reviewed" log - postSkin: new optional source param so logs can distinguish the bot command from the graphql mutation; logs at entry, after send, on bot reaction success/failure, awaiting reactions, awaitReactions resolved - loggingFilter wrapping the awaitReactions filter to log every observed reaction with pass/fail and any filter errors
1 parent 646ee1c commit 86588f5

3 files changed

Lines changed: 129 additions & 5 deletions

File tree

packages/skin-database/api/DiscordEventHandler.ts

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,57 @@ import * as Config from "../config";
44
import SkinModel from "../data/SkinModel";
55
import * as DiscordUtils from "../discord-bot/utils";
66
import UserContext from "../data/UserContext";
7+
import logger from "../logger";
78

89
export default class DiscordEventHandler {
910
_clientPromise: Promise<Discord.Client>;
1011

1112
constructor() {
13+
logger.info("DiscordEventHandler: constructing");
1214
const _client = new Discord.Client();
15+
_client.on("ready", () => {
16+
logger.info("DiscordEventHandler: client ready", {
17+
user: _client.user?.tag,
18+
});
19+
});
20+
_client.on("error", (err: any) => {
21+
logger.error("DiscordEventHandler: client error", {
22+
err: err?.message,
23+
stack: err?.stack,
24+
});
25+
});
26+
_client.on("disconnect", (event: any) => {
27+
logger.warn("DiscordEventHandler: client disconnect", { event });
28+
});
29+
_client.on("reconnecting", () => {
30+
logger.warn("DiscordEventHandler: client reconnecting");
31+
});
32+
_client.on("shardError", (err: any) => {
33+
logger.error("DiscordEventHandler: shard error", {
34+
err: err?.message,
35+
stack: err?.stack,
36+
});
37+
});
38+
_client.on("messageReactionAdd", (reaction: any, user: any) => {
39+
logger.info("DiscordEventHandler: messageReactionAdd", {
40+
emoji: reaction?.emoji?.name,
41+
msgId: reaction?.message?.id,
42+
channelId: reaction?.message?.channel?.id,
43+
userId: user?.id,
44+
username: user?.username,
45+
userIsBot: user?.bot,
46+
});
47+
});
1348
this._clientPromise = _client
1449
.login(Config.discordToken)
15-
.then(() => _client);
50+
.then(() => _client)
51+
.catch((err) => {
52+
logger.error("DiscordEventHandler: login failed", {
53+
err: err?.message,
54+
stack: err?.stack,
55+
});
56+
throw err;
57+
});
1658
}
1759

1860
async dispose() {
@@ -34,6 +76,24 @@ export default class DiscordEventHandler {
3476
}
3577

3678
async handle(action: ApiAction): Promise<void> {
79+
const actionMd5 = "md5" in action ? action.md5 : undefined;
80+
logger.info("DiscordEventHandler.handle: entry", {
81+
type: action.type,
82+
md5: actionMd5,
83+
});
84+
try {
85+
await this._handle(action);
86+
} catch (err: any) {
87+
logger.error("DiscordEventHandler.handle: failed", {
88+
type: action.type,
89+
md5: actionMd5,
90+
err: err?.message,
91+
stack: err?.stack,
92+
});
93+
}
94+
}
95+
96+
private async _handle(action: ApiAction): Promise<void> {
3797
const ctx = new UserContext();
3898
switch (action.type) {
3999
case "REVIEW_REQUESTED":
@@ -159,19 +219,31 @@ export default class DiscordEventHandler {
159219
}
160220

161221
private async requestReview(md5: string, ctx: UserContext) {
222+
logger.info("requestReview: entry", { md5 });
162223
const skin = await SkinModel.fromMd5(ctx, md5);
163224
if (skin == null) {
225+
logger.warn("requestReview: skin not found", { md5 });
164226
return;
165227
}
166228
const dest = await this.getChannel(Config.NSFW_SKIN_CHANNEL_ID);
167229
const tweetStatus = await skin.getTweetStatus();
230+
logger.info("requestReview: resolved", {
231+
md5,
232+
tweetStatus,
233+
channelId: Config.NSFW_SKIN_CHANNEL_ID,
234+
});
168235
if (tweetStatus === "UNREVIEWED") {
169236
await DiscordUtils.postSkin({
170237
md5,
171238
title: (filename) => `Review: ${filename}`,
172239
dest,
240+
source: "graphql:request_nsfw_review_for_skin",
173241
});
174242
} else {
243+
logger.info("requestReview: skipping post, already reviewed", {
244+
md5,
245+
tweetStatus,
246+
});
175247
// Too much nosie
176248
// await DiscordUtils.sendAlreadyReviewed({ md5, dest });
177249
}

packages/skin-database/discord-bot/commands/review.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ async function reviewSkin(message: Message): Promise<void> {
1515
md5,
1616
title: (filename) => `Review: ${filename}`,
1717
dest: message.channel,
18+
source: "bot:!review",
1819
});
1920
}
2021

packages/skin-database/discord-bot/utils.ts

Lines changed: 55 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,18 +29,21 @@ export async function postSkin({
2929
md5,
3030
title: _title,
3131
dest,
32+
source = "unknown",
3233
}: {
3334
md5: string;
3435
title?: (filename: string | null) => string;
3536
dest: TextChannel | DMChannel;
37+
source?: string;
3638
}) {
3739
const ctx = new UserContext();
3840

39-
console.log("postSkin...");
41+
const destName = "name" in dest ? dest.name : "DM";
42+
logger.info("postSkin: entry", { md5, source, dest: destName });
4043
const skin = await SkinModel.fromMd5(ctx, md5);
4144
if (skin == null) {
4245
console.warn("Could not find skin for md5", { md5, alert: true });
43-
logger.warn("Could not find skin for md5", { md5, alert: true });
46+
logger.warn("Could not find skin for md5", { md5, alert: true, source });
4447
return;
4548
}
4649
const readmeText = await skin.getReadme();
@@ -108,14 +111,62 @@ export async function postSkin({
108111

109112
// @ts-ignore WAT?
110113
const msg = await dest.send(embed);
114+
const msgId = Array.isArray(msg) ? msg.map((m) => m.id).join(",") : msg.id;
115+
logger.info("postSkin: sent message", { md5, source, msgId, isArray: Array.isArray(msg) });
111116
if (tweetStatus !== "UNREVIEWED") {
117+
logger.info("postSkin: skipping reactions, not UNREVIEWED", { md5, source, msgId, tweetStatus });
112118
return;
113119
}
114120

115121
// Don't await
116-
Promise.all([msg.react("👍"), msg.react("👎"), msg.react("🔞")]);
122+
Promise.all([msg.react("👍"), msg.react("👎"), msg.react("🔞")])
123+
.then(() => logger.info("postSkin: bot reactions added", { md5, source, msgId }))
124+
.catch((err) =>
125+
logger.error("postSkin: bot reactions failed", {
126+
md5,
127+
source,
128+
msgId,
129+
err: err?.message,
130+
stack: err?.stack,
131+
})
132+
);
133+
134+
const loggingFilter = async (
135+
reaction: MessageReaction,
136+
user: User
137+
): Promise<boolean> => {
138+
let passes = false;
139+
let err: string | undefined;
140+
try {
141+
passes = await filter(reaction);
142+
} catch (e: any) {
143+
err = e?.message ?? String(e);
144+
}
145+
logger.info("postSkin: reaction observed", {
146+
md5,
147+
source,
148+
msgId,
149+
emoji: reaction.emoji.name,
150+
userId: user?.id,
151+
username: user?.username,
152+
userIsBot: user?.bot,
153+
passes,
154+
filterErr: err,
155+
});
156+
return passes;
157+
};
158+
159+
logger.info("postSkin: awaiting reactions", { md5, source, msgId });
117160
// TODO: Timeout at some point
118-
await msg.awaitReactions(filter, { max: 1 }).then(async (collected) => {
161+
await msg
162+
.awaitReactions(loggingFilter, { max: 1 })
163+
.then(async (collected) => {
164+
logger.info("postSkin: awaitReactions resolved", {
165+
md5,
166+
source,
167+
msgId,
168+
collectedSize: collected.size,
169+
});
119170
const vote = collected.first();
120171
if (vote == null) {
121172
throw new Error("Did not expect vote to be empty");

0 commit comments

Comments
 (0)