feat(observability): log authenticated read routes

This commit is contained in:
Josh Creek
2026-09-01 16:14:20 +01:00
parent e62909d457
commit 5812386676
3 changed files with 73 additions and 3 deletions
+20 -3
View File
@@ -124,7 +124,7 @@ type Service struct {
TierPolicy domain.TierPolicy
RateLimiter *RateLimiter
// Log receives a credential-safe structured event for lifecycle-relevant
// mutations (currently: server registration and result submission). Nil
// reads and mutations. Nil
// is a valid, silent no-op -- every call site must stay optional so
// existing Service literals that don't set it keep working unchanged.
Log func(observability.Event)
@@ -601,15 +601,18 @@ func (s *Service) queueMutation(w http.ResponseWriter, r *http.Request) {
}
var ticket domain.QueueTicket
var err error
now := s.now()
if s.QueueBackend != nil {
ticket, err = s.QueueBackend.Get(r.Context(), playerID, parts[0], s.now())
ticket, err = s.QueueBackend.Get(r.Context(), playerID, parts[0], now)
} else {
ticket, err = s.Queue.Get(playerID, parts[0], s.now())
ticket, err = s.Queue.Get(playerID, parts[0], now)
}
if err != nil {
s.logEvent(observability.Event{Event: "queue_get", QueueID: parts[0], Stage: "rejected", OccurredAt: now})
writeDomainError(w, err)
return
}
s.logEvent(observability.Event{Event: "queue_get", QueueID: ticket.TicketID, Stage: strings.ToLower(string(ticket.State)), OccurredAt: now})
writeJSON(w, http.StatusOK, toQueueResponse(ticket))
return
}
@@ -695,6 +698,7 @@ func (s *Service) proposalMutation(w http.ResponseWriter, r *http.Request) {
if s.ProposalBackend != nil {
proposalValue, providerErr := s.ProposalBackend.Get(r.Context(), playerID, parts[0], s.now())
if providerErr != nil {
s.logEvent(observability.Event{Event: "proposal_get", ProposalID: parts[0], Stage: "rejected", OccurredAt: s.now()})
writeError(w, http.StatusNotFound, "not_found")
return
}
@@ -702,6 +706,7 @@ func (s *Service) proposalMutation(w http.ResponseWriter, r *http.Request) {
exists = true
}
if !exists || proposal == nil || !proposal.HasParticipant(playerID) {
s.logEvent(observability.Event{Event: "proposal_get", ProposalID: parts[0], Stage: "rejected", OccurredAt: s.now()})
writeError(w, http.StatusNotFound, "not_found")
return
}
@@ -709,6 +714,7 @@ func (s *Service) proposalMutation(w http.ResponseWriter, r *http.Request) {
if proposal.Expire(now) {
s.publishProposalEvent(*proposal, now)
}
s.logEvent(observability.Event{Event: "proposal_get", ProposalID: proposal.ProposalID, Stage: strings.ToLower(string(proposal.State)), OccurredAt: now})
writeJSON(w, http.StatusOK, toProposalResponse(*proposal))
return
}
@@ -776,20 +782,24 @@ func (s *Service) assignment(w http.ResponseWriter, r *http.Request) {
return
}
if s.Assignment == nil {
s.logEvent(observability.Event{Event: "assignment_get", MatchID: parts[0], Stage: "rejected", OccurredAt: s.now()})
writeError(w, http.StatusServiceUnavailable, "assignment_unavailable")
return
}
now := s.now()
view, err := s.Assignment(r.Context(), playerID, parts[0], now)
if err != nil || view.MatchID != parts[0] || view.PlayerID != playerID {
s.logEvent(observability.Event{Event: "assignment_get", MatchID: parts[0], ServerID: view.ServerID, Stage: "rejected", OccurredAt: now})
writeError(w, http.StatusNotFound, "not_found")
return
}
if view.ServerID == "" || view.Slot < 0 || view.Slot > 5 || view.ProtocolVersion < 1 || (view.Transport != "enet" && view.Transport != "steam_sdr") || view.JoinAuthorisation == "" || !validAssignmentEndpoint(view.Endpoint) || view.ExpiresAt.IsZero() || !now.Before(view.ExpiresAt) {
s.logEvent(observability.Event{Event: "assignment_get", MatchID: parts[0], ServerID: view.ServerID, Stage: "rejected", OccurredAt: now})
writeError(w, http.StatusServiceUnavailable, "assignment_unavailable")
return
}
_ = s.PublishControlPlaneEvent(assignmentChangedEvent(view, now))
s.logEvent(observability.Event{Event: "assignment_get", MatchID: view.MatchID, ServerID: view.ServerID, Stage: "assignment_ready", OccurredAt: now})
writeJSON(w, http.StatusOK, view)
}
@@ -830,13 +840,16 @@ func (s *Service) profile(w http.ResponseWriter, r *http.Request) {
}
profile, exists, err := s.rankedProfileFor(r.Context(), playerID)
if err != nil {
s.logEvent(observability.Event{Event: "profile_get", Stage: "rejected", OccurredAt: s.now()})
writeError(w, http.StatusServiceUnavailable, "ranked_profile_unavailable")
return
}
if !exists {
s.logEvent(observability.Event{Event: "profile_get", Stage: "not_found", OccurredAt: s.now()})
writeError(w, http.StatusNotFound, "not_found")
return
}
s.logEvent(observability.Event{Event: "profile_get", Stage: "ok", OccurredAt: s.now()})
writeJSON(w, http.StatusOK, map[string]any{
"player_id": playerID,
"rating": profile.Value,
@@ -856,18 +869,22 @@ func (s *Service) rankedProfile(w http.ResponseWriter, r *http.Request) {
}
profile, exists, err := s.rankedProfileFor(r.Context(), playerID)
if err != nil {
s.logEvent(observability.Event{Event: "ranked_profile_get", Stage: "rejected", OccurredAt: s.now()})
writeError(w, http.StatusServiceUnavailable, "ranked_profile_unavailable")
return
}
if !exists {
s.logEvent(observability.Event{Event: "ranked_profile_get", Stage: "not_found", OccurredAt: s.now()})
writeError(w, http.StatusNotFound, "not_found")
return
}
tier, err := domain.RankedTier(profile, s.TierPolicy)
if err != nil {
s.logEvent(observability.Event{Event: "ranked_profile_get", Stage: "rejected", OccurredAt: s.now()})
writeError(w, http.StatusServiceUnavailable, "ranked_profile_unavailable")
return
}
s.logEvent(observability.Event{Event: "ranked_profile_get", Stage: "ok", OccurredAt: s.now()})
writeJSON(w, http.StatusOK, rankedProfileResponse{Rating: profile.Value, RD: profile.RD, Volatility: profile.Volatility, RankedGames: profile.RankedGames, Tier: string(tier), Provisional: domain.RankedIsProvisional(profile), SeasonID: profile.LastSeasonID})
}