feat: add level support for startup logs (#7067)

This allows external services like our devcontainer support to display
errors and warnings with custom styles to indicate failures to users.
This commit is contained in:
Kyle Carberry
2023-04-10 14:29:59 -05:00
committed by GitHub
parent aa2468b16e
commit 81e2b2500a
17 changed files with 89 additions and 26 deletions

6
coderd/apidoc/docs.go generated
View File

@ -5646,6 +5646,9 @@ const docTemplate = `{
"created_at": {
"type": "string"
},
"level": {
"$ref": "#/definitions/codersdk.LogLevel"
},
"output": {
"type": "string"
}
@ -9158,6 +9161,9 @@ const docTemplate = `{
"id": {
"type": "integer"
},
"level": {
"$ref": "#/definitions/codersdk.LogLevel"
},
"output": {
"type": "string"
}

View File

@ -4991,6 +4991,9 @@
"created_at": {
"type": "string"
},
"level": {
"$ref": "#/definitions/codersdk.LogLevel"
},
"output": {
"type": "string"
}
@ -8262,6 +8265,9 @@
"id": {
"type": "integer"
},
"level": {
"$ref": "#/definitions/codersdk.LogLevel"
},
"output": {
"type": "string"
}

View File

@ -3706,6 +3706,7 @@ func (q *fakeQuerier) InsertWorkspaceAgentStartupLogs(_ context.Context, arg dat
ID: id,
AgentID: arg.AgentID,
CreatedAt: arg.CreatedAt[index],
Level: arg.Level[index],
Output: output,
})
outputLength += int32(len(output))

View File

@ -501,7 +501,8 @@ CREATE TABLE workspace_agent_startup_logs (
agent_id uuid NOT NULL,
created_at timestamp with time zone NOT NULL,
output character varying(1024) NOT NULL,
id bigint NOT NULL
id bigint NOT NULL,
level log_level DEFAULT 'info'::log_level NOT NULL
);
CREATE SEQUENCE workspace_agent_startup_logs_id_seq

View File

@ -0,0 +1,2 @@
ALTER TABLE workspace_agent_startup_logs
DROP COLUMN level;

View File

@ -0,0 +1,2 @@
ALTER TABLE workspace_agent_startup_logs
ADD COLUMN level log_level NOT NULL DEFAULT 'info'::log_level;

View File

@ -1601,6 +1601,7 @@ type WorkspaceAgentStartupLog struct {
CreatedAt time.Time `db:"created_at" json:"created_at"`
Output string `db:"output" json:"output"`
ID int64 `db:"id" json:"id"`
Level LogLevel `db:"level" json:"level"`
}
type WorkspaceAgentStat struct {

View File

@ -111,6 +111,7 @@ func TestInsertWorkspaceAgentStartupLogs(t *testing.T) {
AgentID: agent.ID,
CreatedAt: []time.Time{database.Now()},
Output: []string{"first"},
Level: []database.LogLevel{database.LogLevelInfo},
// 1 MB is the max
OutputLength: 1 << 20,
})
@ -121,6 +122,7 @@ func TestInsertWorkspaceAgentStartupLogs(t *testing.T) {
AgentID: agent.ID,
CreatedAt: []time.Time{database.Now()},
Output: []string{"second"},
Level: []database.LogLevel{database.LogLevelInfo},
OutputLength: 1,
})
require.True(t, database.IsStartupLogsLimitError(err))

View File

@ -5574,7 +5574,7 @@ func (q *sqlQuerier) GetWorkspaceAgentMetadata(ctx context.Context, workspaceAge
const getWorkspaceAgentStartupLogsAfter = `-- name: GetWorkspaceAgentStartupLogsAfter :many
SELECT
agent_id, created_at, output, id
agent_id, created_at, output, id, level
FROM
workspace_agent_startup_logs
WHERE
@ -5603,6 +5603,7 @@ func (q *sqlQuerier) GetWorkspaceAgentStartupLogsAfter(ctx context.Context, arg
&i.CreatedAt,
&i.Output,
&i.ID,
&i.Level,
); err != nil {
return nil, err
}
@ -5964,21 +5965,23 @@ func (q *sqlQuerier) InsertWorkspaceAgentMetadata(ctx context.Context, arg Inser
const insertWorkspaceAgentStartupLogs = `-- name: InsertWorkspaceAgentStartupLogs :many
WITH new_length AS (
UPDATE workspace_agents SET
startup_logs_length = startup_logs_length + $4 WHERE workspace_agents.id = $1
startup_logs_length = startup_logs_length + $5 WHERE workspace_agents.id = $1
)
INSERT INTO
workspace_agent_startup_logs
workspace_agent_startup_logs (agent_id, created_at, output, level)
SELECT
$1 :: uuid AS agent_id,
unnest($2 :: timestamptz [ ]) AS created_at,
unnest($3 :: VARCHAR(1024) [ ]) AS output
RETURNING workspace_agent_startup_logs.agent_id, workspace_agent_startup_logs.created_at, workspace_agent_startup_logs.output, workspace_agent_startup_logs.id
unnest($3 :: VARCHAR(1024) [ ]) AS output,
unnest($4 :: log_level [ ]) AS level
RETURNING workspace_agent_startup_logs.agent_id, workspace_agent_startup_logs.created_at, workspace_agent_startup_logs.output, workspace_agent_startup_logs.id, workspace_agent_startup_logs.level
`
type InsertWorkspaceAgentStartupLogsParams struct {
AgentID uuid.UUID `db:"agent_id" json:"agent_id"`
CreatedAt []time.Time `db:"created_at" json:"created_at"`
Output []string `db:"output" json:"output"`
Level []LogLevel `db:"level" json:"level"`
OutputLength int32 `db:"output_length" json:"output_length"`
}
@ -5987,6 +5990,7 @@ func (q *sqlQuerier) InsertWorkspaceAgentStartupLogs(ctx context.Context, arg In
arg.AgentID,
pq.Array(arg.CreatedAt),
pq.Array(arg.Output),
pq.Array(arg.Level),
arg.OutputLength,
)
if err != nil {
@ -6001,6 +6005,7 @@ func (q *sqlQuerier) InsertWorkspaceAgentStartupLogs(ctx context.Context, arg In
&i.CreatedAt,
&i.Output,
&i.ID,
&i.Level,
); err != nil {
return nil, err
}

View File

@ -151,11 +151,12 @@ WITH new_length AS (
startup_logs_length = startup_logs_length + @output_length WHERE workspace_agents.id = @agent_id
)
INSERT INTO
workspace_agent_startup_logs
workspace_agent_startup_logs (agent_id, created_at, output, level)
SELECT
@agent_id :: uuid AS agent_id,
unnest(@created_at :: timestamptz [ ]) AS created_at,
unnest(@output :: VARCHAR(1024) [ ]) AS output
unnest(@output :: VARCHAR(1024) [ ]) AS output,
unnest(@level :: log_level [ ]) AS level
RETURNING workspace_agent_startup_logs.*;
-- If an agent hasn't connected in the last 7 days, we purge it's logs.

View File

@ -256,16 +256,31 @@ func (api *API) patchWorkspaceAgentStartupLogs(rw http.ResponseWriter, r *http.R
}
createdAt := make([]time.Time, 0)
output := make([]string, 0)
level := make([]database.LogLevel, 0)
outputLength := 0
for _, log := range req.Logs {
createdAt = append(createdAt, log.CreatedAt)
output = append(output, log.Output)
outputLength += len(log.Output)
if log.Level == "" {
// Default to "info" to support older agents that didn't have the level field.
log.Level = codersdk.LogLevelInfo
}
parsedLevel := database.LogLevel(log.Level)
if !parsedLevel.Valid() {
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
Message: "Invalid log level provided.",
Detail: fmt.Sprintf("invalid log level: %q", log.Level),
})
return
}
level = append(level, parsedLevel)
}
logs, err := api.Database.InsertWorkspaceAgentStartupLogs(ctx, database.InsertWorkspaceAgentStartupLogsParams{
AgentID: workspaceAgent.ID,
CreatedAt: createdAt,
Output: output,
Level: level,
OutputLength: int32(outputLength),
})
if err != nil {
@ -1971,5 +1986,6 @@ func convertWorkspaceAgentStartupLog(log database.WorkspaceAgentStartupLog) code
ID: log.ID,
CreatedAt: log.CreatedAt,
Output: log.Output,
Level: codersdk.LogLevel(log.Level),
}
}