mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: add link for viewing raw build logs in workspace and template build jobs (#21727)
* Adds support for parameter `format=text` in 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` * Adds links to view raw logs on the following pages: * Workspace build page * Template editor page * Template version page * Refactors existing log formatting in `cli/logs.go` to live in `codersdk`. 🤖 Generated with Claude Opus 4.5, reviewed by me. --------- Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -216,6 +216,19 @@ type ProvisionerJobLog struct {
|
||||
Output string `json:"output"`
|
||||
}
|
||||
|
||||
// Text formats the log entry as human-readable text.
|
||||
func (l ProvisionerJobLog) Text() string {
|
||||
var sb strings.Builder
|
||||
_, _ = sb.WriteString(l.CreatedAt.Format(time.RFC3339))
|
||||
_, _ = sb.WriteString(" [")
|
||||
_, _ = sb.WriteString(string(l.Level))
|
||||
_, _ = sb.WriteString("] [provisioner|")
|
||||
_, _ = sb.WriteString(l.Stage)
|
||||
_, _ = sb.WriteString("] ")
|
||||
_, _ = sb.WriteString(l.Output)
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// provisionerJobLogsAfter streams logs that occurred after a specific time.
|
||||
func (c *Client) provisionerJobLogsAfter(ctx context.Context, path string, after int64) (<-chan ProvisionerJobLog, io.Closer, error) {
|
||||
afterQuery := ""
|
||||
|
||||
@@ -217,6 +217,26 @@ type WorkspaceAgentLog struct {
|
||||
SourceID uuid.UUID `json:"source_id" format:"uuid"`
|
||||
}
|
||||
|
||||
// Text formats the log entry as human-readable text.
|
||||
func (l WorkspaceAgentLog) Text(agentName, sourceName string) string {
|
||||
var sb strings.Builder
|
||||
_, _ = sb.WriteString(l.CreatedAt.Format(time.RFC3339))
|
||||
_, _ = sb.WriteString(" [")
|
||||
_, _ = sb.WriteString(string(l.Level))
|
||||
_, _ = sb.WriteString("] [agent")
|
||||
if agentName != "" {
|
||||
_, _ = sb.WriteString(".")
|
||||
_, _ = sb.WriteString(agentName)
|
||||
}
|
||||
if sourceName != "" {
|
||||
_, _ = sb.WriteString("|")
|
||||
_, _ = sb.WriteString(sourceName)
|
||||
}
|
||||
_, _ = sb.WriteString("] ")
|
||||
_, _ = sb.WriteString(l.Output)
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
type AgentSubsystem string
|
||||
|
||||
const (
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
package codersdk_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
)
|
||||
|
||||
func TestProvisionerJobLogText(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ts := time.Date(2024, 1, 28, 10, 30, 0, 0, time.UTC)
|
||||
log := codersdk.ProvisionerJobLog{
|
||||
CreatedAt: ts,
|
||||
Level: codersdk.LogLevelInfo,
|
||||
Source: codersdk.LogSourceProvisioner,
|
||||
Stage: "Planning",
|
||||
Output: "Terraform init complete",
|
||||
}
|
||||
result := log.Text()
|
||||
require.Equal(t, "2024-01-28T10:30:00Z [info] [provisioner|Planning] Terraform init complete", result)
|
||||
}
|
||||
|
||||
func TestProvisionerJobLogTextEmptyOutput(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ts := time.Date(2024, 1, 28, 10, 30, 0, 0, time.UTC)
|
||||
log := codersdk.ProvisionerJobLog{
|
||||
CreatedAt: ts,
|
||||
Level: codersdk.LogLevelInfo,
|
||||
Source: codersdk.LogSourceProvisioner,
|
||||
Stage: "Planning",
|
||||
Output: "",
|
||||
}
|
||||
result := log.Text()
|
||||
require.Equal(t, "2024-01-28T10:30:00Z [info] [provisioner|Planning] ", result)
|
||||
}
|
||||
|
||||
func TestProvisionerJobLogTextSpecialChars(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ts := time.Date(2024, 1, 28, 10, 30, 0, 0, time.UTC)
|
||||
log := codersdk.ProvisionerJobLog{
|
||||
CreatedAt: ts,
|
||||
Level: codersdk.LogLevelInfo,
|
||||
Source: codersdk.LogSourceProvisioner,
|
||||
Stage: "Applying",
|
||||
Output: "\033[32mSuccess!\033[0m Unicode: 你好世界",
|
||||
}
|
||||
result := log.Text()
|
||||
require.Equal(t, "2024-01-28T10:30:00Z [info] [provisioner|Applying] \033[32mSuccess!\033[0m Unicode: 你好世界", result)
|
||||
}
|
||||
|
||||
func TestWorkspaceAgentLogText(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ts := time.Date(2024, 1, 28, 10, 30, 0, 0, time.UTC)
|
||||
log := codersdk.WorkspaceAgentLog{
|
||||
CreatedAt: ts,
|
||||
Level: codersdk.LogLevelInfo,
|
||||
Output: "Agent started successfully",
|
||||
SourceID: uuid.New(),
|
||||
}
|
||||
result := log.Text("main", "startup_script")
|
||||
require.Equal(t, "2024-01-28T10:30:00Z [info] [agent.main|startup_script] Agent started successfully", result)
|
||||
}
|
||||
|
||||
func TestWorkspaceAgentLogTextEmptySourceAndAgent(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ts := time.Date(2024, 1, 28, 10, 30, 0, 0, time.UTC)
|
||||
log := codersdk.WorkspaceAgentLog{
|
||||
CreatedAt: ts,
|
||||
Level: codersdk.LogLevelWarn,
|
||||
Output: "Warning message",
|
||||
SourceID: uuid.New(),
|
||||
}
|
||||
result := log.Text("", "")
|
||||
require.Equal(t, "2024-01-28T10:30:00Z [warn] [agent] Warning message", result)
|
||||
}
|
||||
|
||||
func TestWorkspaceAgentLogTextMultiline(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ts := time.Date(2024, 1, 28, 10, 30, 0, 0, time.UTC)
|
||||
log := codersdk.WorkspaceAgentLog{
|
||||
CreatedAt: ts,
|
||||
Level: codersdk.LogLevelInfo,
|
||||
Output: "Line 1\nLine 2\nLine 3",
|
||||
SourceID: uuid.New(),
|
||||
}
|
||||
result := log.Text("main", "startup_script")
|
||||
require.Equal(t, "2024-01-28T10:30:00Z [info] [agent.main|startup_script] Line 1\nLine 2\nLine 3", result)
|
||||
}
|
||||
|
||||
func TestWorkspaceAgentLogTextSpecialChars(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ts := time.Date(2024, 1, 28, 10, 30, 0, 0, time.UTC)
|
||||
log := codersdk.WorkspaceAgentLog{
|
||||
CreatedAt: ts,
|
||||
Level: codersdk.LogLevelDebug,
|
||||
Output: "\033[31mError!\033[0m 🚀 Unicode: 日本語",
|
||||
SourceID: uuid.New(),
|
||||
}
|
||||
result := log.Text("main", "startup_script")
|
||||
require.Equal(t, "2024-01-28T10:30:00Z [debug] [agent.main|startup_script] \033[31mError!\033[0m 🚀 Unicode: 日本語", result)
|
||||
}
|
||||
Reference in New Issue
Block a user