Stream plugin file-tail logs through Run plan

This commit is contained in:
npc0-hue
2026-08-11 00:36:03 +08:00
parent 82ee28522c
commit 12fed856f7
5 changed files with 57 additions and 5 deletions
@@ -264,7 +264,7 @@ func runAutonomousLifecyclePlan(distribution domain.RunDistribution, instance do
}
}
for _, source := range plugin.RuntimeProfiles.LogSources {
if source.Kind == "process.stdout" || source.Kind == "process.stderr" {
if autonomousRunLogSourceKind(source.Kind) {
plan.LogSources = append(plan.LogSources, autonomousLogSource(source))
}
}
@@ -318,6 +318,10 @@ func autonomousLogSource(source domain.RuntimeLogSource) domain.RunAutonomousLog
return domain.RunAutonomousLogSource{Key: source.Key, Kind: source.Kind, TargetKey: source.TargetKey, StreamKey: source.StreamKey, CursorKind: source.CursorKind, RetentionDays: source.RetentionDays}
}
func autonomousRunLogSourceKind(kind string) bool {
return kind == "process.stdout" || kind == "process.stderr" || kind == "file.tail"
}
func autonomousDLLExtension(extension domain.RuntimeDLLExtensionPlan) domain.RunAutonomousDLLExtension {
return domain.RunAutonomousDLLExtension{Key: extension.Key, Version: extension.Version, ReleaseURL: extension.ReleaseURL, Checksum: extension.Checksum, SizeBytes: extension.SizeBytes, TargetKey: extension.TargetKey, ModKey: extension.ModKey, DLLRef: extension.DLLRef, SCUMExecutableChecksum: extension.SCUMExecutableChecksum, UE4SSABI: extension.UE4SSABI, RCONPort: extension.RCONPort}
}
@@ -41,7 +41,10 @@ func TestCoreServiceKeepsPlatformBuildKeyOffMachineJobChannel(t *testing.T) {
{Path: "actions/install.json", Content: `{"version":1,"action":"install","mode":"oneshot"}`, Mode: 0o600},
{Path: "bin/install-server", Content: "#!/usr/bin/env sh\n", Mode: 0o700},
}
plugin.RuntimeProfiles.LogSources = append(plugin.RuntimeProfiles.LogSources, domain.RuntimeLogSource{Key: "console", Kind: "process.stdout", TargetKey: "server/process", StreamKey: "console", CursorKind: "sequence", RetentionDays: 14})
plugin.RuntimeProfiles.LogSources = append(plugin.RuntimeProfiles.LogSources,
domain.RuntimeLogSource{Key: "console", Kind: "process.stdout", TargetKey: "server/process", StreamKey: "console", CursorKind: "sequence", RetentionDays: 14},
domain.RuntimeLogSource{Key: "server-events", Kind: "file.tail", TargetKey: "logs/server", StreamKey: "scum.server", CursorKind: "fingerprint", RetentionDays: 90},
)
if err := svc.store.GamePlugins().Update(plugin); err != nil {
t.Fatalf("seed plugin lifecycle assets: %v", err)
}
@@ -87,7 +90,7 @@ func TestCoreServiceKeepsPlatformBuildKeyOffMachineJobChannel(t *testing.T) {
if plan == nil || plan.SchemaVersion != "1" || plan.ServerInstanceID != instance.ID || plan.PluginID != plugin.ID || plan.ProfileKey != "local" || plan.Bootstrap == nil || plan.Bootstrap.Action != domain.ServerLifecycleActionStart || plan.Bootstrap.TargetKey != "actions/start.json" {
t.Fatalf("platform builder received incomplete autonomous lifecycle plan: %+v", plan)
}
if len(plan.DependencyProbes) != 1 || plan.DependencyProbes[0].Key != "java-runtime" || len(plan.InstallPlans) != 1 || plan.InstallPlans[0].Key != "java-install" || len(plan.LogSources) != 1 || plan.LogSources[0].Kind != "process.stdout" || plan.RuntimeBindings["logs/latest"] != "runtime.logs.latest" {
if len(plan.DependencyProbes) != 1 || plan.DependencyProbes[0].Key != "java-runtime" || len(plan.InstallPlans) != 1 || plan.InstallPlans[0].Key != "java-install" || len(plan.LogSources) != 3 || !hasAutonomousLogSource(plan.LogSources, "process.stdout", "console") || !hasAutonomousLogSource(plan.LogSources, "file.tail", "latest-log") || !hasAutonomousLogSource(plan.LogSources, "file.tail", "scum.server") || plan.RuntimeBindings["logs/latest"] != "runtime.logs.latest" {
t.Fatalf("autonomous lifecycle plan lost plugin runtime declarations: %+v", plan)
}
var seededPlan domain.RunAutonomousLifecyclePlan
@@ -143,6 +146,15 @@ func TestCoreServiceKeepsPlatformBuildKeyOffMachineJobChannel(t *testing.T) {
completeDistributionBuild(t, svc, distribution, nil)
}
func hasAutonomousLogSource(sources []domain.RunAutonomousLogSource, kind string, streamKey string) bool {
for _, source := range sources {
if source.Kind == kind && source.StreamKey == streamKey {
return true
}
}
return false
}
func TestCoreServiceBuildsWithoutRegisteredDistributionWorker(t *testing.T) {
svc, session, instance := newDistributionTestFixture(t)
bootstrapEndpoint, err := svc.store.RunEndpoints().Get(instance.RunEndpointID)
+1 -1
View File
@@ -130,7 +130,7 @@ func (svc *CoreService) ensureLogStreamForBatch(batch domain.LogBatchIngest, sta
}
func (svc *CoreService) ensureRunLogStreamForBatch(batch domain.LogBatchIngest, stamp time.Time) error {
if batch.Source != domain.LogStreamSourceProcess && batch.Source != domain.LogStreamSourceManagementProgram {
if batch.Source != domain.LogStreamSourceProcess && batch.Source != domain.LogStreamSourceFile && batch.Source != domain.LogStreamSourceManagementProgram {
return repo.ErrNotFound
}
if batch.LogStreamID != runLogStreamID(batch.RunEndpointID, batch.ServerInstanceID, batch.StreamKey) && !legacyAutonomousLogStream(batch) {
+36
View File
@@ -285,6 +285,42 @@ func TestCoreServiceAcceptsAutonomousRunLogStreamWithoutPlatformJob(t *testing.T
}
}
func TestCoreServiceAcceptsAutonomousRunFileTailLogStreamWithoutPlatformJob(t *testing.T) {
svc, sessionToken := newRegisteredLogIngestService(t)
entry := domain.LogEntry{Seq: 1, Timestamp: time.Date(2026, 7, 3, 12, 0, 1, 0, time.UTC), Level: "info", Line: "LogSCUM: Display: player joined"}
streamID := runLogStreamID("run-local", "server-1", "scum.server")
ack, err := svc.IngestLogBatch(domain.LogBatchIngest{
RunEndpointID: "run-local",
SessionToken: sessionToken,
LogStreamID: streamID,
ServerInstanceID: "server-1",
StreamKey: "scum.server",
Source: domain.LogStreamSourceFile,
FirstSeq: entry.Seq,
LastSeq: entry.Seq,
Compression: "none",
Checksum: validator.LogLineChecksum(entry.Line),
Entries: []domain.LogEntry{entry},
})
if err != nil {
t.Fatalf("ingest autonomous run file-tail log batch: %v", err)
}
if !ack.Accepted || ack.LogStreamID != streamID || ack.LatestSeq != entry.Seq {
t.Fatalf("unexpected autonomous file-tail stream ack: %+v", ack)
}
stream, err := svc.GetLogStream(streamID)
if err != nil {
t.Fatalf("get autonomous file-tail stream: %v", err)
}
if stream.ServerInstanceID != "server-1" || stream.StreamKey != "scum.server" || stream.Source != domain.LogStreamSourceFile {
t.Fatalf("unexpected autonomous file-tail stream metadata: %+v", stream)
}
query, err := svc.QueryLogStream(domain.LogStreamCursorQuery{LogStreamID: streamID, AfterSeq: 0, Limit: 10})
if err != nil || len(query.Entries) != 1 || query.Entries[0].Line != entry.Line {
t.Fatalf("unexpected autonomous file-tail query: %+v err=%v", query, err)
}
}
func TestCoreServiceAcceptsLegacyAutonomousJobLogStreamWithoutPlatformJob(t *testing.T) {
svc, sessionToken := newRegisteredLogIngestService(t)
entry := domain.LogEntry{Seq: 1, Timestamp: time.Date(2026, 7, 3, 12, 0, 1, 0, time.UTC), Level: "info", Line: "legacy autonomous bootstrap output"}