QR2: Add event logging

This commit is contained in:
Palapeli 2026-04-05 12:55:33 -04:00
parent d3b8508e6a
commit 18140bebe9
No known key found for this signature in database
GPG Key ID: 1FFE8F556A474925
8 changed files with 144 additions and 23 deletions

View File

@ -111,6 +111,24 @@
<!-- <event>all</event> -->
<event>backend_started</event>
<event>backend_stopped</event>
<event>profile_created</event>
<event>logged_in</event>
<event>logged_out</event>
<event>received_login_info</event>
<event>device_authenticated</event>
<event>reported_bad_packet</event>
<event>reported_stall</event>
<event>gpcm_returned_error</event>
<event>group_created</event>
<event>group_deleted</event>
<event>group_joined</event>
<event>group_left</event>
<event>group_host_changed</event>
<event>natneg_succeeded</event>
<event>natneg_failed</event>
<event>profile_kicked</event>
<event>profile_banned</event>
<event>profile_unbanned</event>
</eventTypes>
</webhook>
</discord>

View File

@ -487,7 +487,7 @@ func (g *GameSpySession) verifyExLoginInfo(command common.GameSpyCommand, authTo
"device_authenticated",
map[string]any{
"profile_id": g.User.ProfileId,
"ng_device_id": g.User.NgDeviceId,
"ng_device_id": g.DeviceId,
"payload_version": payloadVer,
},
)

View File

@ -25,6 +25,18 @@ func Event(eventType string, eventData map[string]any) {
}
}
func EventSynced(eventType string, eventData map[string]any) {
mutex.RLock()
defer mutex.RUnlock()
for _, callback := range eventCallbacks {
if callback.AllEvents {
callback.Function(eventType, eventData)
} else if _, ok := callback.EventTypes[eventType]; ok {
callback.Function(eventType, eventData)
}
}
}
func RegisterEventCallback(eventTypes []string, callback func(eventType string, eventData map[string]any)) {
eventTypeSet := make(map[string]struct{})
allEvents := false

View File

@ -2,8 +2,9 @@ package logging
import (
"encoding/json"
"fmt"
"net/http"
"strconv"
"reflect"
"strings"
)
@ -38,31 +39,19 @@ type webhookPayload struct {
}
func encodeWebhookValue(value any) string {
switch v := value.(type) {
case string:
return " - ``" + strings.ReplaceAll(v, "``", "` `") + "``"
case int:
return " - " + strconv.Itoa(v)
case int32:
return " - " + strconv.Itoa(int(v))
case int64:
return " - " + strconv.FormatInt(v, 10)
case float32:
return " - " + strconv.FormatFloat(float64(v), 'f', -1, 32)
case float64:
return " - " + strconv.FormatFloat(v, 'f', -1, 64)
case []any:
if reflect.TypeOf(value).Kind() == reflect.Array {
var sb strings.Builder
for i, item := range v {
sb.WriteString(encodeWebhookValue(item))
if i < len(v)-1 {
s := reflect.ValueOf(value)
for i := 0; i < s.Len(); i++ {
sb.WriteString(encodeWebhookValue(s.Index(i).Interface()))
if i < s.Len()-1 {
sb.WriteString("\n")
}
}
return sb.String()
default:
return " - (unknown)"
}
return " - ``" + strings.ReplaceAll(fmt.Sprintf("%v", value), "``", "` `") + "``"
}
func (w WebhookConfig) ReportEvent(eventType string, eventData map[string]any) {

View File

@ -74,7 +74,7 @@ type RPCPacket struct {
func connectAndLogEvent(eventType string) {
var db database.Connection
defer db.Close()
defer logging.Event(eventType, map[string]any{})
defer logging.EventSynced(eventType, map[string]any{})
db = database.Start(config)
db.RegisterEvents(config, []string{eventType})
}

View File

@ -85,6 +85,17 @@ func processResvOK(moduleName string, matchVersion int, reservation common.Match
groups[group.GroupName] = group
logging.Notice(moduleName, "Created new group", aurora.Cyan(group.GroupName))
eventData := map[string]any{
"group_id": group.GroupID,
"group_name": group.GroupName,
"game_name": group.GameName,
"match_type": group.MatchType,
"host_id": sender.Data["dwc_pid"],
}
if group.GameName == "mariokartwii" {
eventData["mario_kart_wii_region"] = group.MKWRegion
}
logging.Event("group_created", eventData)
}
// Keep group ID updated
@ -101,6 +112,15 @@ func processResvOK(moduleName string, matchVersion int, reservation common.Match
logging.Notice(moduleName, "New player", aurora.BrightCyan(destination.Data["dwc_pid"]), "in group", aurora.Cyan(group.GroupName))
logging.Event(
"group_joined",
map[string]any{
"group_id": group.GroupID,
"group_name": group.GroupName,
"profile_id": destination.Data["dwc_pid"],
},
)
group.LastJoinIndex++
destination.Data["+joinindex"] = strconv.Itoa(group.LastJoinIndex)
if matchVersion == 90 {
@ -332,7 +352,7 @@ func ProcessNATNEGReport(result byte, ip1 string, ip2 string) {
}
if session1.groupPointer == nil || session1.groupPointer != session2.groupPointer {
logging.Warn(moduleName, "Received NATNEG report for two IPs in different groups")
logging.Error(moduleName, "Received NATNEG report for two IPs in different groups")
return
}
@ -353,6 +373,43 @@ func ProcessNATNEGReport(result byte, ip1 string, ip2 string) {
connFail2++
session1.Data["+conn_fail"] = strconv.Itoa(connFail1)
session2.Data["+conn_fail"] = strconv.Itoa(connFail2)
logging.Event(
"natneg_failed",
map[string]any{
"group_id": session1.groupPointer.GroupID,
"group_name": session1.groupPointer.GroupName,
"peers": []map[string]string{
{
"profile_id": session1.Data["dwc_pid"],
"failed_count": strconv.Itoa(connFail1),
"ip_address": ip1,
},
{
"profile_id": session2.Data["dwc_pid"],
"failed_count": strconv.Itoa(connFail2),
"ip_address": ip2,
},
},
},
)
} else {
logging.Event(
"natneg_succeeded",
map[string]any{
"group_id": session1.groupPointer.GroupID,
"group_name": session1.groupPointer.GroupName,
"peers": []map[string]string{
{
"profile_id": session1.Data["dwc_pid"],
"ip_address": ip1,
},
{
"profile_id": session2.Data["dwc_pid"],
"ip_address": ip2,
},
},
},
)
}
}
@ -449,8 +506,21 @@ func (g *Group) findNewServer() {
}
}
eventData := map[string]any{
"group_id": g.GroupID,
"group_name": g.GroupName,
}
if server != nil {
eventData["new_host_id"] = server.Data["dwc_pid"]
}
if g.server != nil {
eventData["old_host_id"] = g.server.Data["dwc_pid"]
}
g.server = server
g.updateMatchType()
logging.Event("group_host_changed", eventData)
}
// updateMatchType updates the match type of the group based on the host's dwc_mtype value.

View File

@ -6,6 +6,7 @@ import (
"sync"
"time"
"wwfc/common"
"wwfc/database"
"wwfc/logging"
"wwfc/nhttp"
@ -25,6 +26,7 @@ const (
AvailableRequest = 0x09
ClientRegisteredReply = 0x0A
// Added by WiiLink
ClientExploitReply = 0x10
)
@ -44,6 +46,20 @@ func StartServer(reload bool) {
panic(err)
}
// Connect to database for event logging
if config.EventReporting.LogToDatabase {
db := database.Start(config)
db.RegisterEvents(config, []string{
"group_created",
"group_deleted",
"group_joined",
"group_left",
"group_host_changed",
"natneg_succeeded",
"natneg_failed",
})
}
masterConn = conn
inShutdown.SetFalse()

View File

@ -83,6 +83,13 @@ func (session *Session) removeFromGroup() {
if len(session.groupPointer.players) == 0 {
logging.Notice("QR2", "Deleting group", aurora.Cyan(session.groupPointer.GroupName))
delete(groups, session.groupPointer.GroupName)
logging.Event(
"group_deleted",
map[string]any{
"group_id": session.groupPointer.GroupID,
"group_name": session.groupPointer.GroupName,
},
)
} else if session.groupPointer.server == session {
logging.Notice("QR2", "Server down in group", aurora.Cyan(session.groupPointer.GroupName))
session.groupPointer.server = nil
@ -101,6 +108,15 @@ func (session *Session) removeFromGroup() {
session.groupPointer = nil
session.GroupName = ""
logging.Event(
"group_left",
map[string]any{
"group_id": session.groupPointer.GroupID,
"group_name": session.groupPointer.GroupName,
"profile_id": session.Data["dwc_pid"],
},
)
}
// Update session data, creating the session if it doesn't exist. Returns a copy of the session data.