feat: include rotated agent logs in support bundles (#26055)

Support bundles previously captured only the active coder-agent.log, losing
history across agent restarts. Add an optional `after` filter to the agent's
/debug/logs endpoint: without it the endpoint is unchanged (active log only,
10 MiB cap); with it the response includes the active log plus rotated
coder-agent-*.log files modified after the cutoff, newest first. Support
bundles request the last 24h.

Closes #25395
This commit is contained in:
Ehab Younes
2026-06-22 16:38:18 +03:00
committed by GitHub
parent adad5bdd49
commit f5cb2e547e
10 changed files with 512 additions and 30 deletions
+56
View File
@@ -80,6 +80,11 @@ func TestSupportBundle(t *testing.T) {
agents[0].Env["SECRET_VALUE"] = secretValue
return agents
})
workspaceWithRotatedAgentLogs := setupSupportBundleTestFixture(setupCtx, t, api.Database, owner.OrganizationID, owner.UserID, func(agents []*proto.Agent) []*proto.Agent {
// This should not show up in the bundle output
agents[0].Env["SECRET_VALUE"] = secretValue
return agents
})
workspaceWithoutAgent := setupSupportBundleTestFixture(setupCtx, t, api.Database, owner.OrganizationID, owner.UserID, nil)
memberWorkspace := setupSupportBundleTestFixture(setupCtx, t, api.Database, owner.OrganizationID, member.ID, nil)
@@ -105,6 +110,57 @@ func TestSupportBundle(t *testing.T) {
assertBundleContents(t, path, true, true, []string{secretValue})
})
t.Run("WorkspaceWithRotatedAgentLogs", func(t *testing.T) {
t.Parallel()
tempDir := t.TempDir()
logPath := filepath.Join(tempDir, "coder-agent.log")
require.NoError(t, os.WriteFile(logPath, []byte("hello from the agent"), 0o600))
rotatedPath := filepath.Join(tempDir, "coder-agent-2026-05-18T00-00-00.000.log")
require.NoError(t, os.WriteFile(rotatedPath, []byte("rotated log"), 0o600))
oldRotatedPath := filepath.Join(tempDir, "coder-agent-2026-05-17T00-00-00.000.log")
require.NoError(t, os.WriteFile(oldRotatedPath, []byte("old rotated log"), 0o600))
now := time.Now()
require.NoError(t, os.Chtimes(rotatedPath, now, now))
oldRotatedTime := now.Add(-48 * time.Hour)
require.NoError(t, os.Chtimes(oldRotatedPath, oldRotatedTime, oldRotatedTime))
agt := agenttest.New(t, client.URL, workspaceWithRotatedAgentLogs.AgentToken, func(o *agent.Options) {
o.LogDir = tempDir
})
defer agt.Close()
coderdtest.NewWorkspaceAgentWaiter(t, client, workspaceWithRotatedAgentLogs.Workspace.ID).Wait()
d := t.TempDir()
path := filepath.Join(d, "bundle.zip")
inv, root := clitest.New(t, "support", "bundle", workspaceWithRotatedAgentLogs.Workspace.Name, "--output-file", path, "--yes")
//nolint: gocritic // requires owner privilege
clitest.SetupConfig(t, client, root)
ctx := testutil.Context(t, testutil.WaitLong)
err := inv.WithContext(ctx).Run()
require.NoError(t, err)
r, err := zip.OpenReader(path)
require.NoError(t, err, "open zip file")
defer r.Close()
found := false
for _, f := range r.File {
assertDoesNotContain(t, f, secretValue)
if f.Name != "agent/logs.txt" {
continue
}
found = true
bs := readBytesFromZip(t, f)
body := string(bs)
require.Contains(t, body, "hello from the agent")
require.Contains(t, body, "rotated log")
require.NotContains(t, body, "old rotated log")
}
require.True(t, found, "expected agent/logs.txt in bundle")
})
t.Run("NoWorkspace", func(t *testing.T) {
t.Parallel()