Move game log processing into SCUM companion
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
@@ -12,6 +13,8 @@ import (
|
||||
"browser.local/platform/service"
|
||||
)
|
||||
|
||||
type componentLogServerContextKey struct{}
|
||||
|
||||
const (
|
||||
logEventHeartbeatInterval = 15 * time.Second
|
||||
liveLogSourceClockSkew = 90 * time.Second
|
||||
@@ -24,17 +27,13 @@ func (h *coreHandlers) serverLogEvents(w http.ResponseWriter, r *http.Request) {
|
||||
writeMethodNotAllowed(w, http.MethodGet)
|
||||
return
|
||||
}
|
||||
liveBoundary := time.Now().UTC().Add(-liveLogSourceClockSkew)
|
||||
instance, streams, liveEligible, subscription, err := h.openLogEventSubscription(r)
|
||||
if err != nil {
|
||||
writeServiceError(w, err)
|
||||
return
|
||||
}
|
||||
defer subscription.Close()
|
||||
h.streamCurrentLogEvents(w, r, instance, streams, liveEligible, subscription)
|
||||
}
|
||||
|
||||
func (h *coreHandlers) streamCurrentLogEvents(w http.ResponseWriter, r *http.Request, instance domain.ServerInstance, streams []domain.LogStream, liveEligible bool, subscription service.LogEventSubscription) {
|
||||
liveBoundary := time.Now().UTC().Add(-liveLogSourceClockSkew)
|
||||
flusher, ok := w.(http.Flusher)
|
||||
if !ok {
|
||||
writeServiceError(w, fmt.Errorf("streaming response unsupported"))
|
||||
@@ -50,7 +49,11 @@ func (h *coreHandlers) streamCurrentLogEvents(w http.ResponseWriter, r *http.Req
|
||||
|
||||
active := supervisedLogSession{}
|
||||
if liveEligible {
|
||||
active = activeSupervisedLogSession(streams)
|
||||
if isComponentLogRequest(r) {
|
||||
active = activeComponentLogSession(streams)
|
||||
} else {
|
||||
active = activeSupervisedLogSession(streams)
|
||||
}
|
||||
}
|
||||
emittedThrough, err := h.writeCurrentLogSession(w, instance.ID, active)
|
||||
if err != nil {
|
||||
@@ -74,6 +77,9 @@ func (h *coreHandlers) streamCurrentLogEvents(w http.ResponseWriter, r *http.Req
|
||||
return
|
||||
}
|
||||
if subscriptionEvent.Kind == service.LogEventSubscriptionEventProcessState {
|
||||
if isComponentLogRequest(r) {
|
||||
continue
|
||||
}
|
||||
if subscriptionEvent.ServerInstanceID != instance.ID {
|
||||
continue
|
||||
}
|
||||
@@ -90,7 +96,7 @@ func (h *coreHandlers) streamCurrentLogEvents(w http.ResponseWriter, r *http.Req
|
||||
flusher.Flush()
|
||||
continue
|
||||
}
|
||||
streams, liveEligible, err = h.loadLiveLogSnapshot(instance.ID)
|
||||
streams, liveEligible, err = h.loadLiveLogSnapshot(instance.ID, isComponentLogRequest(r))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -114,8 +120,11 @@ func (h *coreHandlers) streamCurrentLogEvents(w http.ResponseWriter, r *http.Req
|
||||
}
|
||||
event := subscriptionEvent.LogEvent
|
||||
candidate := activeSupervisedLogSession([]domain.LogStream{event.Stream})
|
||||
if active.allStreams {
|
||||
candidate = supervisedLogSession{}
|
||||
}
|
||||
if candidate.sessionID != "" && newerLogSession(candidate, active) {
|
||||
streams, liveEligible, err = h.loadLiveLogSnapshot(instance.ID)
|
||||
streams, liveEligible, err = h.loadLiveLogSnapshot(instance.ID, isComponentLogRequest(r))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -133,9 +142,15 @@ func (h *coreHandlers) streamCurrentLogEvents(w http.ResponseWriter, r *http.Req
|
||||
}
|
||||
}
|
||||
if !active.contains(event.Stream) {
|
||||
continue
|
||||
if !active.allStreams || event.Stream.ServerInstanceID != instance.ID {
|
||||
continue
|
||||
}
|
||||
active.streams = append(active.streams, event.Stream)
|
||||
if err := writeSSEJSON(w, "stream", "", dto.LogStreamFromDomain(event.Stream)); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if !subscriptionEvent.Live && !sourceLogEntryIsLive(event.Entry, liveBoundary) {
|
||||
if !sourceLogEntryIsLive(event.Entry, liveBoundary) {
|
||||
if event.Entry.Seq > emittedThrough[event.Stream.ID] {
|
||||
emittedThrough[event.Stream.ID] = event.Entry.Seq
|
||||
}
|
||||
@@ -147,10 +162,7 @@ func (h *coreHandlers) streamCurrentLogEvents(w http.ResponseWriter, r *http.Req
|
||||
return
|
||||
}
|
||||
}
|
||||
// Live relay traffic is already a current best-effort observation.
|
||||
// Do not sequence-gate it: a restarted Run intentionally starts with
|
||||
// fresh in-memory sequence state and must still reach this subscriber.
|
||||
if !subscriptionEvent.Live && event.Entry.Seq <= emittedThrough[event.Stream.ID] {
|
||||
if event.Entry.Seq <= emittedThrough[event.Stream.ID] {
|
||||
continue
|
||||
}
|
||||
if err := writeSSEJSON(w, "log", logEventID(event), dto.LogStreamEventFromDomain(event)); err != nil {
|
||||
@@ -172,9 +184,14 @@ func sourceLogEntryIsLive(entry domain.LogEntry, liveBoundary time.Time) bool {
|
||||
}
|
||||
|
||||
type supervisedLogSession struct {
|
||||
sessionID string
|
||||
startedAt time.Time
|
||||
streams []domain.LogStream
|
||||
sessionID string
|
||||
startedAt time.Time
|
||||
streams []domain.LogStream
|
||||
allStreams bool
|
||||
}
|
||||
|
||||
func activeComponentLogSession(streams []domain.LogStream) supervisedLogSession {
|
||||
return supervisedLogSession{sessionID: "component", streams: append([]domain.LogStream(nil), streams...), allStreams: true}
|
||||
}
|
||||
|
||||
func activeSupervisedLogSession(streams []domain.LogStream) supervisedLogSession {
|
||||
@@ -217,6 +234,9 @@ func sameSupervisedLogSession(left supervisedLogSession, right supervisedLogSess
|
||||
}
|
||||
|
||||
func (session supervisedLogSession) contains(stream domain.LogStream) bool {
|
||||
if session.allStreams {
|
||||
return session.hasStream(stream.ID)
|
||||
}
|
||||
return session.sessionID != "" && stream.Source == domain.LogStreamSourceProcess && stream.LogSessionID == session.sessionID && stream.SessionStartedAt.Equal(session.startedAt)
|
||||
}
|
||||
|
||||
@@ -235,9 +255,7 @@ func (h *coreHandlers) writeCurrentLogSession(w http.ResponseWriter, serverInsta
|
||||
return nil, err
|
||||
}
|
||||
for _, stream := range active.streams {
|
||||
// Stream metadata identifies the current channel only. The platform
|
||||
// never replays its old body when a live subscriber connects.
|
||||
emittedThrough[stream.ID] = 0
|
||||
emittedThrough[stream.ID] = stream.LatestSeq
|
||||
if err := writeSSEJSON(w, "stream", "", dto.LogStreamFromDomain(stream)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -251,7 +269,12 @@ func (h *coreHandlers) openLogEventSubscription(r *http.Request) (domain.ServerI
|
||||
var liveEligible bool
|
||||
var subscription service.LogEventSubscription
|
||||
var err error
|
||||
if h.enforceAuthorization {
|
||||
if serverInstanceID, ok := r.Context().Value(componentLogServerContextKey{}).(string); ok && strings.TrimSpace(serverInstanceID) != "" {
|
||||
instance, err = h.core.GetServerInstance(serverInstanceID)
|
||||
if err == nil {
|
||||
subscription, err = h.core.SubscribeLogEvents(instance.ID)
|
||||
}
|
||||
} else if h.enforceAuthorization {
|
||||
sessionID := bearerToken(r)
|
||||
instance, err = h.core.GetServerInstanceForSession(sessionID, r.PathValue("id"))
|
||||
if err != nil {
|
||||
@@ -272,7 +295,7 @@ func (h *coreHandlers) openLogEventSubscription(r *http.Request) (domain.ServerI
|
||||
}
|
||||
}
|
||||
if err == nil {
|
||||
streams, liveEligible, err = h.loadLiveLogSnapshot(instance.ID)
|
||||
streams, liveEligible, err = h.loadLiveLogSnapshot(instance.ID, isComponentLogRequest(r))
|
||||
}
|
||||
if err != nil && subscription.Close != nil {
|
||||
subscription.Close()
|
||||
@@ -280,7 +303,16 @@ func (h *coreHandlers) openLogEventSubscription(r *http.Request) (domain.ServerI
|
||||
return instance, streams, liveEligible, subscription, err
|
||||
}
|
||||
|
||||
func (h *coreHandlers) loadLiveLogSnapshot(serverInstanceID string) ([]domain.LogStream, bool, error) {
|
||||
func withComponentLogServer(r *http.Request, serverInstanceID string) *http.Request {
|
||||
return r.WithContext(context.WithValue(r.Context(), componentLogServerContextKey{}, serverInstanceID))
|
||||
}
|
||||
|
||||
func isComponentLogRequest(r *http.Request) bool {
|
||||
_, ok := r.Context().Value(componentLogServerContextKey{}).(string)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (h *coreHandlers) loadLiveLogSnapshot(serverInstanceID string, includeDeclaredStreams bool) ([]domain.LogStream, bool, error) {
|
||||
instance, err := h.core.GetServerInstance(serverInstanceID)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
@@ -305,6 +337,16 @@ func (h *coreHandlers) loadLiveLogSnapshot(serverInstanceID string) ([]domain.Lo
|
||||
}
|
||||
current := make([]domain.LogStream, 0, len(streams))
|
||||
for _, stream := range streams {
|
||||
if includeDeclaredStreams {
|
||||
if stream.LogSessionID != "" && stream.LogSessionID != logSessionID {
|
||||
continue
|
||||
}
|
||||
if stream.Source != domain.LogStreamSourceProcess && stream.Source != domain.LogStreamSourceFile && stream.Source != domain.LogStreamSourceManagementProgram {
|
||||
continue
|
||||
}
|
||||
current = append(current, stream)
|
||||
continue
|
||||
}
|
||||
if stream.Source == domain.LogStreamSourceProcess && stream.LogSessionID == logSessionID {
|
||||
current = append(current, stream)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user