From 4568def37895ac0c0acdf8c318655ae6cebcf547 Mon Sep 17 00:00:00 2001 From: Asaf Gartner Date: Thu, 28 Mar 2024 21:24:46 +0200 Subject: [PATCH] Added a bunch of discord debugging --- src/discord/gateway.go | 48 +++++++++++++++++++ src/discord/history.go | 1 + src/discord/message_handling.go | 5 +- src/discord/payloads.go | 1 + src/hmnurl/urls.go | 6 +++ ...-28T184107Z_AddBackfillToDiscordMessage.go | 47 ++++++++++++++++++ src/templates/src/discord_bot_debug.html | 22 +++++++++ src/website/discord.go | 16 +++++++ src/website/routes.go | 3 +- 9 files changed, 146 insertions(+), 3 deletions(-) create mode 100644 src/migration/migrations/2024-03-28T184107Z_AddBackfillToDiscordMessage.go create mode 100644 src/templates/src/discord_bot_debug.html diff --git a/src/discord/gateway.go b/src/discord/gateway.go index 9b2ac3ef..95cc5ae0 100644 --- a/src/discord/gateway.go +++ b/src/discord/gateway.go @@ -23,6 +23,34 @@ import ( "github.com/jpillora/backoff" ) +type BotEvent struct { + Timestamp time.Time + Name string + Extra string +} + +var botEvents = make([]BotEvent, 0, 1000) +var botEventsMutex = sync.Mutex{} + +func RecordBotEvent(name, extra string) { + botEventsMutex.Lock() + defer botEventsMutex.Unlock() + if len(botEvents) > 1000 { + botEvents = botEvents[len(botEvents)-500:] + } + botEvents = append(botEvents, BotEvent{ + Timestamp: time.Now(), + Name: name, + Extra: extra, + }) +} + +func GetBotEvents() []BotEvent { + botEventsMutex.Lock() + defer botEventsMutex.Unlock() + return botEvents[:] +} + func RunDiscordBot(ctx context.Context, dbConn *pgxpool.Pool) jobs.Job { log := logging.ExtractLogger(ctx).With().Str("module", "discord").Logger() ctx = logging.AttachLoggerToContext(&log, ctx) @@ -56,6 +84,11 @@ func RunDiscordBot(ctx context.Context, dbConn *pgxpool.Pool) jobs.Job { log.Info().Msg("Connecting to the Discord gateway") bot := newBotInstance(dbConn) err := bot.Run(ctx) + disconnectMessage := "" + if err != nil { + disconnectMessage = err.Error() + } + RecordBotEvent("Disconnected", disconnectMessage) if err != nil { dur := boff.Duration() log.Error(). @@ -101,6 +134,8 @@ type botInstance struct { conn *websocket.Conn dbConn *pgxpool.Pool + resuming bool + heartbeatIntervalMs int forceHeartbeat chan struct{} @@ -193,6 +228,7 @@ func (bot *botInstance) Run(ctx context.Context) (err error) { logging.ExtractLogger(ctx).Info().Msg("Discord asked us to reconnect to the gateway") return nil case OpcodeInvalidSession: + RecordBotEvent("Failed to resume - invalid session", "") // We tried to resume but the session was invalid. // Delete the session and reconnect from scratch again. _, err := bot.dbConn.Exec(ctx, `DELETE FROM discord_session`) @@ -264,8 +300,11 @@ func (bot *botInstance) connect(ctx context.Context) error { } } + RecordBotEvent("Connected", "") if shouldResume { + RecordBotEvent("Resuming with session ID", session.ID) // Reconnect to the previous session + bot.resuming = true err := bot.sendGatewayMessage(ctx, GatewayMessage{ Opcode: OpcodeResume, Data: Resume{ @@ -540,11 +579,20 @@ func (bot *botInstance) processEventMsg(ctx context.Context, msg *GatewayMessage panic(fmt.Sprintf("processEventMsg must only be used on Dispatch messages (opcode %d). Validate this before you call this function.", OpcodeDispatch)) } + if bot.resuming { + name := "" + if msg.EventName != nil { + name = *msg.EventName + } + RecordBotEvent("Got event while resuming", name) + } switch *msg.EventName { case "RESUMED": // Nothing to do, but at least we can log something logging.ExtractLogger(ctx).Info().Msg("Finished resuming gateway session") + bot.resuming = false + RecordBotEvent("Done resuming", "") bot.createApplicationCommands(ctx) case "MESSAGE_CREATE": newMessage := *MessageFromMap(msg.Data, "") diff --git a/src/discord/history.go b/src/discord/history.go index 1a3ed596..ba01301c 100644 --- a/src/discord/history.go +++ b/src/discord/history.go @@ -185,6 +185,7 @@ func Scrape(ctx context.Context, dbConn *pgxpool.Pool, channelID string, earlies return true } + msg.Backfilled = true err := HandleIncomingMessage(ctx, dbConn, &msg, createSnippets) if err != nil { diff --git a/src/discord/message_handling.go b/src/discord/message_handling.go index 07410b00..0280b5fd 100644 --- a/src/discord/message_handling.go +++ b/src/discord/message_handling.go @@ -285,8 +285,8 @@ func InternMessage( _, err = dbConn.Exec(ctx, ` - INSERT INTO discord_message (id, channel_id, guild_id, url, user_id, sent_at, snippet_created) - VALUES ($1, $2, $3, $4, $5, $6, $7) + INSERT INTO discord_message (id, channel_id, guild_id, url, user_id, sent_at, snippet_created, backfilled) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8) `, msg.ID, msg.ChannelID, @@ -295,6 +295,7 @@ func InternMessage( msg.Author.ID, msg.Time(), false, + msg.Backfilled, ) if err != nil { return oops.New(err, "failed to save new discord message") diff --git a/src/discord/payloads.go b/src/discord/payloads.go index 4e5e2b6f..f28b9161 100644 --- a/src/discord/payloads.go +++ b/src/discord/payloads.go @@ -275,6 +275,7 @@ type Message struct { Embeds []Embed `json:"embeds"` originalMap map[string]interface{} + Backfilled bool } func (m *Message) JumpURL() string { diff --git a/src/hmnurl/urls.go b/src/hmnurl/urls.go index eaf1187b..d894ea17 100644 --- a/src/hmnurl/urls.go +++ b/src/hmnurl/urls.go @@ -929,6 +929,12 @@ func BuildDiscordShowcaseBacklog() string { return Url("/discord_showcase_backlog", nil) } +var RegexDiscordBotDebugPage = regexp.MustCompile("^/discord_bot_debug$") + +func BuildDiscordBotDebugPage() string { + return Url("/discord_bot_debug", nil) +} + /* * API */ diff --git a/src/migration/migrations/2024-03-28T184107Z_AddBackfillToDiscordMessage.go b/src/migration/migrations/2024-03-28T184107Z_AddBackfillToDiscordMessage.go new file mode 100644 index 00000000..62b5f256 --- /dev/null +++ b/src/migration/migrations/2024-03-28T184107Z_AddBackfillToDiscordMessage.go @@ -0,0 +1,47 @@ +package migrations + +import ( + "context" + "time" + + "git.handmade.network/hmn/hmn/src/migration/types" + "github.com/jackc/pgx/v5" +) + +func init() { + registerMigration(AddBackfillToDiscordMessage{}) +} + +type AddBackfillToDiscordMessage struct{} + +func (m AddBackfillToDiscordMessage) Version() types.MigrationVersion { + return types.MigrationVersion(time.Date(2024, 3, 28, 18, 41, 7, 0, time.UTC)) +} + +func (m AddBackfillToDiscordMessage) Name() string { + return "AddBackfillToDiscordMessage" +} + +func (m AddBackfillToDiscordMessage) Description() string { + return "Add a backfill flag to discord messages" +} + +func (m AddBackfillToDiscordMessage) Up(ctx context.Context, tx pgx.Tx) error { + _, err := tx.Exec(ctx, + ` + ALTER TABLE discord_message + ADD COLUMN backfilled BOOLEAN NOT NULL default FALSE; + `, + ) + return err +} + +func (m AddBackfillToDiscordMessage) Down(ctx context.Context, tx pgx.Tx) error { + _, err := tx.Exec(ctx, + ` + ALTER TABLE discord_message + DROP COLUMN backfilled; + `, + ) + return err +} diff --git a/src/templates/src/discord_bot_debug.html b/src/templates/src/discord_bot_debug.html new file mode 100644 index 00000000..e06ff693 --- /dev/null +++ b/src/templates/src/discord_bot_debug.html @@ -0,0 +1,22 @@ +{{ template "base.html" . }} + +{{ define "content" }} + + + + + + + + + + {{ range .BotEvents }} + + + + + + {{ end }} + +
TimestampNameExtras
{{ rfc3339 .Timestamp }}{{ .Name }}{{ .Extra }}
+{{ end }} diff --git a/src/website/discord.go b/src/website/discord.go index 52345e59..da37e639 100644 --- a/src/website/discord.go +++ b/src/website/discord.go @@ -15,6 +15,7 @@ import ( "git.handmade.network/hmn/hmn/src/hmnurl" "git.handmade.network/hmn/hmn/src/models" "git.handmade.network/hmn/hmn/src/oops" + "git.handmade.network/hmn/hmn/src/templates" "git.handmade.network/hmn/hmn/src/utils" "github.com/google/uuid" ) @@ -428,3 +429,18 @@ func saveDiscordAvatar(ctx context.Context, conn db.ConnOrTx, userID, avatarHash return asset, nil } + +func DiscordBotDebugPage(c *RequestContext) ResponseData { + type DiscordBotDebugData struct { + templates.BaseData + BotEvents []discord.BotEvent + } + botEvents := discord.GetBotEvents() + var res ResponseData + res.MustWriteTemplate("discord_bot_debug.html", DiscordBotDebugData{ + BaseData: getBaseData(c, "", nil), + + BotEvents: botEvents, + }, c.Perf) + return res +} diff --git a/src/website/routes.go b/src/website/routes.go index 3ecd7e9f..1be977df 100644 --- a/src/website/routes.go +++ b/src/website/routes.go @@ -126,9 +126,10 @@ func NewWebsiteRoutes(conn *pgxpool.Pool) http.Handler { hmnOnly.GET(hmnurl.RegexDiscordOAuthCallback, DiscordOAuthCallback) hmnOnly.POST(hmnurl.RegexDiscordUnlink, needsAuth(csrfMiddleware(DiscordUnlink))) hmnOnly.POST(hmnurl.RegexDiscordShowcaseBacklog, needsAuth(csrfMiddleware(DiscordShowcaseBacklog))) + hmnOnly.GET(hmnurl.RegexDiscordBotDebugPage, adminsOnly(DiscordBotDebugPage)) hmnOnly.POST(hmnurl.RegexTwitchEventSubCallback, TwitchEventSubCallback) - hmnOnly.GET(hmnurl.RegexTwitchDebugPage, TwitchDebugPage) + hmnOnly.GET(hmnurl.RegexTwitchDebugPage, adminsOnly(TwitchDebugPage)) hmnOnly.GET(hmnurl.RegexUserProfile, UserProfile) hmnOnly.GET(hmnurl.RegexUserSettings, needsAuth(UserSettings))