Skip to content

feat: add link for viewing raw build logs in workspace and template build jobs#21727

Merged
johnstcn merged 3 commits intomainfrom
cj/task/raw-text-log-format
Feb 3, 2026
Merged

feat: add link for viewing raw build logs in workspace and template build jobs#21727
johnstcn merged 3 commits intomainfrom
cj/task/raw-text-log-format

Conversation

@johnstcn
Copy link
Member

@johnstcn johnstcn commented Jan 28, 2026

This addresses a long-standing peeve of mine: it's not easy to link someone to just the logs for a single provisioner job or workspace agent.

You can now add ?format=text to the following API routes:

  • /api/v2/workspaceagents/:id/logs
  • /api/v2/workspacebuilds/:id/logs
  • /api/v2/templateversions/:id/logs
  • /api/v2/templateversions/:id/dry-run/:id/logs

Note: format=text is not supported with websocket streaming (follow). Specifying both will return 400 Bad Request.

In addition, added links to the raw logs on the following pages:

  • Workspace build page
image
  • Template editor page
image
  • Template version page
image

As a side-effect, refactored the existing log formatting in cli/logs.go to live in codersdk.

🤖 Generated with Claude Opus 4.5, reviewed by me.

@johnstcn johnstcn force-pushed the cj/task/raw-text-log-format branch 4 times, most recently from 4b42e92 to 5842d06 Compare January 28, 2026 18:19
@johnstcn johnstcn changed the title feat: add raw text format support for log API endpoints feat: add link to view raw build logs for workspace and template build jobs Jan 28, 2026
@johnstcn johnstcn force-pushed the cj/task/raw-text-log-format branch 2 times, most recently from 99531d0 to 66a7305 Compare January 29, 2026 11:55
This adds a `format` query parameter to workspace build logs, workspace
agent logs, template version logs, and template version dry-run logs
endpoints. When `format=text` is specified, logs are returned as plain
text with RFC3339 timestamps instead of JSON.

Each log line is formatted as:
- Provisioner logs: `{timestamp} [{level}] [{source}] {stage}: {output}`
- Agent logs: `{timestamp} [{level}] {output}`

ANSI escape sequences in log output are preserved.

The UI now includes "View raw logs" links:
- Workspace build page (build logs and agent logs tabs)
- Template version editor (output tab)
- Template version page (stats bar)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
@johnstcn johnstcn force-pushed the cj/task/raw-text-log-format branch from 66a7305 to 5387e4a Compare January 29, 2026 16:05
@johnstcn johnstcn changed the title feat: add link to view raw build logs for workspace and template build jobs feat: add link for viewing raw build logs in workspace and template build jobs Jan 29, 2026
@johnstcn johnstcn marked this pull request as ready for review January 29, 2026 16:36
@coder-tasks
Copy link
Contributor

coder-tasks bot commented Jan 29, 2026

Documentation Check

New Documentation Needed

  • docs/admin/monitoring/logs.md - Add section explaining the new ?format=text query parameter for viewing raw logs
    • Document that users can append ?format=text to log API endpoints to get plain text output with RFC3339 timestamps
    • Explain that this format is useful for sharing logs externally or viewing in browser/curl
    • Note the limitation: cannot be combined with follow=true (streaming)
    • List the four endpoints that support this parameter:
      • /api/v2/workspaceagents/{id}/logs?format=text
      • /api/v2/workspacebuilds/{id}/logs?format=text
      • /api/v2/templateversions/{id}/logs?format=text
      • /api/v2/templateversions/{id}/dry-run/{id}/logs?format=text
    • Mention that "View raw logs" links are available in the UI (workspace build page, template editor, template version page)

Rationale

This PR adds a user-facing feature that makes build logs easier to share and consume outside the Coder UI. The PR description specifically mentions this addresses "a long-standing peeve" about not being able to easily link someone to logs. Since users will see "View raw logs" links in the UI and may want to understand how to use the API directly, this deserves documentation in the logs section.

The auto-generated API reference docs were already updated, but user-facing documentation explaining the use case and workflow is missing.


Automated review via Coder Tasks

@johnstcn

This comment was marked as outdated.

Copy link
Contributor

@dannykopping dannykopping left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome addition! Thanks a lot

href={`/api/v2/templateversions/${templateVersion.id}/logs?format=text`}
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-1 px-3 text-xs text-content-secondary hover:text-content-primary"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Someone from frontend would know better, but I think we should give this a specific class; this combo may not age well.

expectedStatus: http.StatusOK,
expectedContentType: "application/json",
checkBody: func(t *testing.T, body string) {
assert.NotEmpty(t, body) // This is checked more thoroughly in the case above.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which case above?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant TestTemplateVersionLogs; updated to clarify.

Copilot AI review requested due to automatic review settings February 3, 2026 09:22
@coder-tasks
Copy link
Contributor

coder-tasks bot commented Feb 3, 2026

Documentation Check

Previous Feedback

Not yet addressed - The suggestion from the previous doc-check review (2026-01-29) has not been implemented.

Updates Needed

  • docs/admin/monitoring/logs.md - Add section explaining the new ?format=text query parameter for viewing raw logs
    • Document that users can append ?format=text to log API endpoints to get plain text output with RFC3339 timestamps
    • Explain that this format is useful for sharing logs externally, viewing in browser, or using with curl/wget
    • Note the limitation: cannot be combined with follow=true (streaming)
    • List the four endpoints that support this parameter:
      • /api/v2/workspaceagents/{id}/logs?format=text
      • /api/v2/workspacebuilds/{id}/logs?format=text
      • /api/v2/templateversions/{id}/logs?format=text
      • /api/v2/templateversions/{id}/dry-run/{jobID}/logs?format=text
    • Mention that "View raw logs" links are available in the UI (workspace build page, template editor, template version page)
    • Include example: curl https://coder.example.com/api/v2/workspacebuilds/{build-id}/logs?format=text

Rationale

The PR is fully implemented with UI links and backend functionality, but user-facing documentation explaining the feature is still missing. Users seeing "View raw logs" links in the UI will want to understand:

  • What the raw format provides (RFC3339 timestamps + plain text)
  • When to use it (sharing logs, external tools, debugging)
  • How to use it programmatically (API endpoints with ?format=text)
  • What limitations exist (no streaming support)

The auto-generated API reference docs document the parameter but don't explain the use case or workflow.


Automated review via Coder Tasks

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a text/plain log output format to several log APIs and exposes “View raw logs” links in the UI, while centralizing log line formatting in the SDK.

Changes:

  • Backend: Introduces a format query parameter (json / text) on workspace build, workspace agent, template version, and template dry-run log endpoints, including validation (rejecting invalid values and format=text with follow), text rendering using new SDK helpers, DB→SDK mappers, and updated OpenAPI/markdown docs.
  • SDK/CLI: Moves log formatting into codersdk.ProvisionerJobLog.Text() and codersdk.WorkspaceAgentLog.Text(...), updates the CLI logs command to use these helpers, and adds unit tests to verify the log text format.
  • Frontend: Adds “View raw logs” links on the workspace build page (for builds and agents), template version page, and template editor logs tab, pointing at the new ?format=text API behavior.

Reviewed changes

Copilot reviewed 20 out of 20 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
site/src/pages/WorkspaceBuildPage/WorkspaceBuildPageView.tsx Adds a header toolbar around the logs tabs and “View raw logs” links for build logs and per-agent logs, wired to the new ?format=text endpoints.
site/src/pages/TemplateVersionPage/TemplateVersionPageView.tsx Extends the stats header to include a “View raw logs” external link for the template version’s provisioner logs.
site/src/pages/TemplateVersionEditorPage/TemplateVersionEditor.tsx Adds a conditional “View raw logs” link in the logs tab when build logs are available and adjusts the close button alignment accordingly.
docs/reference/api/templates.md Documents the new format query parameter and its enum (json, text) for template version and template dry-run logs.
docs/reference/api/builds.md Documents the format query parameter and enum for workspace build logs.
docs/reference/api/agents.md Documents the format query parameter and enum for workspace agent logs.
codersdk/workspaceagents.go Adds WorkspaceAgentLog.Text(agentName, sourceName) to format agent logs as RFC3339 + level + `agent[.name
codersdk/workspaceagents_test.go Adds tests for ProvisionerJobLog.Text() and WorkspaceAgentLog.Text(...), including empty, multiline, and special-character cases.
codersdk/provisionerdaemons.go Adds ProvisionerJobLog.Text() to format provisioner logs as RFC3339 + level + `[provisioner
coderd/workspacebuilds.go Extends the workspace build logs endpoint swagger comments with format while delegating handling to the shared provisioner log helper.
coderd/workspacebuilds_test.go Adds TestWorkspaceBuildLogsFormat to verify JSON vs text responses, content-type, invalid format, and rejection of format=text&follow; uses db2sdk + .Text() for expectations.
coderd/workspaceagents.go Adds format handling to workspace agent logs (validation, disallowing text with follow), a text-rendering code path that uses db2sdk + .Text(...), and updates convertWorkspaceAgentLogs to use db2sdk.WorkspaceAgentLog.
coderd/workspaceagents_test.go Adds TestWorkspaceAgentLogsFormat mirroring the workspace build format tests for agent logs and checking text output via db2sdk + .Text(...).
coderd/templateversions.go Extends swagger comments for template version logs and dry-run logs with the format parameter and delegates actual behavior to the updated shared provisioner logs helper.
coderd/templateversions_test.go Adds TestTemplateVersionLogsFormat and TestTemplateVersionDryRunLogsFormat to cover JSON/text outputs, invalid format, and format=text&follow cases for both regular and dry-run template logs, asserting error messages via codersdk.Error.
coderd/provisionerjobs.go Implements format parsing/validation for provisioner job logs, disallows text with follow, and updates fetchAndWriteLogs to emit either JSON or text/plain using db2sdk + .Text() with the correct content type.
coderd/database/db2sdk/db2sdk.go Adds ProvisionerJobLog and WorkspaceAgentLog converters to map DB log rows into the corresponding codersdk log types used by both JSON and text responses.
coderd/apidoc/swagger.json Updates OpenAPI definitions for the affected log endpoints to include the format query parameter and enumerated values.
coderd/apidoc/docs.go Regenerates the embedded swagger document template to include the new format parameter for the same endpoints.
cli/logs.go Refactors log formatting out of the CLI into SDK Text() methods, updates the internal log line representation to store preformatted text, and uses .Text() for both initial and streaming logs.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +1162 to +1184
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
ctx := testutil.Context(t, testutil.WaitLong)

urlPath := fmt.Sprintf("/api/v2/workspacebuilds/%s/logs%s", r.Build.ID, tt.queryParams)

res, err := client.Request(ctx, http.MethodGet, urlPath, nil)
require.NoError(t, err)
defer res.Body.Close()

require.Equal(t, tt.expectedStatus, res.StatusCode)
if tt.expectedContentType != "" {
require.Contains(t, res.Header.Get("Content-Type"), tt.expectedContentType)
}

if assert.NotNil(t, tt.checkBody) {
body, err := io.ReadAll(res.Body)
require.NoError(t, err)
tt.checkBody(t, string(body))
}
})
}

This comment was marked as outdated.

Comment on lines +408 to +430
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
ctx := testutil.Context(t, testutil.WaitLong)

urlPath := fmt.Sprintf("/api/v2/workspaceagents/%s/logs%s", workspaceAgent.ID, tt.queryParams)

res, err := client.Request(ctx, http.MethodGet, urlPath, nil)
require.NoError(t, err)
defer res.Body.Close()

require.Equal(t, tt.expectedStatus, res.StatusCode)
if tt.expectedContentType != "" {
require.Contains(t, res.Header.Get("Content-Type"), tt.expectedContentType)
}

if assert.NotNil(t, tt.checkBody) {
body, err := io.ReadAll(res.Body)
require.NoError(t, err)
tt.checkBody(string(body))
}
})
}

This comment was marked as outdated.

Comment on lines +1079 to +1100
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
ctx := testutil.Context(t, testutil.WaitLong)

urlPath := fmt.Sprintf("/api/v2/templateversions/%s/logs%s", tv.TemplateVersion.ID, tt.queryParams)

res, err := client.Request(ctx, http.MethodGet, urlPath, nil)
require.NoError(t, err)
defer res.Body.Close()

require.Equal(t, tt.expectedStatus, res.StatusCode)
if tt.expectedContentType != "" {
require.Contains(t, res.Header.Get("Content-Type"), tt.expectedContentType)
}
if assert.NotNil(t, tt.checkBody) {
body, err := io.ReadAll(res.Body)
require.NoError(t, err)
tt.checkBody(t, string(body))
}
})
}

This comment was marked as outdated.

Comment on lines +1648 to +1670
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
ctx := testutil.Context(t, testutil.WaitLong)

urlPath := fmt.Sprintf("/api/v2/templateversions/%s/dry-run/%s/logs%s", tv.TemplateVersion.ID, dryRunJob.ID, tt.queryParams)

res, err := client.Request(ctx, http.MethodGet, urlPath, nil)
require.NoError(t, err)
defer res.Body.Close()

require.Equal(t, tt.expectedStatus, res.StatusCode)
if tt.expectedContentType != "" {
require.Contains(t, res.Header.Get("Content-Type"), tt.expectedContentType)
}

if assert.NotNil(t, tt.checkBody) {
body, err := io.ReadAll(res.Body)
require.NoError(t, err)
tt.checkBody(t, string(body))
}
})
}

This comment was marked as outdated.

@johnstcn johnstcn merged commit 353ebd9 into main Feb 3, 2026
30 of 32 checks passed
@johnstcn johnstcn deleted the cj/task/raw-text-log-format branch February 3, 2026 09:45
@github-actions github-actions bot locked and limited conversation to collaborators Feb 3, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants