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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
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),
}
}

View File

@ -545,8 +545,9 @@ func (c *Client) PostStartup(ctx context.Context, req PostStartupRequest) error
}
type StartupLog struct {
CreatedAt time.Time `json:"created_at"`
Output string `json:"output"`
CreatedAt time.Time `json:"created_at"`
Output string `json:"output"`
Level codersdk.LogLevel `json:"level"`
}
type PatchStartupLogs struct {

View File

@ -510,4 +510,5 @@ type WorkspaceAgentStartupLog struct {
ID int64 `json:"id"`
CreatedAt time.Time `json:"created_at" format:"date-time"`
Output string `json:"output"`
Level LogLevel `json:"level"`
}

View File

@ -701,6 +701,7 @@ curl -X GET http://coder-server:8080/api/v2/workspaceagents/{workspaceagent}/sta
{
"created_at": "2019-08-24T14:15:22Z",
"id": 0,
"level": "trace",
"output": "string"
}
]
@ -716,11 +717,22 @@ curl -X GET http://coder-server:8080/api/v2/workspaceagents/{workspaceagent}/sta
Status Code **200**
| Name | Type | Required | Restrictions | Description |
| -------------- | ----------------- | -------- | ------------ | ----------- |
| `[array item]` | array | false | | |
| `» created_at` | string(date-time) | false | | |
| `» id` | integer | false | | |
| `» output` | string | false | | |
| Name | Type | Required | Restrictions | Description |
| -------------- | ------------------------------------------------ | -------- | ------------ | ----------- |
| `[array item]` | array | false | | |
| `» created_at` | string(date-time) | false | | |
| `» id` | integer | false | | |
| `» level` | [codersdk.LogLevel](schemas.md#codersdkloglevel) | false | | |
| `» output` | string | false | | |
#### Enumerated Values
| Property | Value |
| -------- | ------- |
| `level` | `trace` |
| `level` | `debug` |
| `level` | `info` |
| `level` | `warn` |
| `level` | `error` |
To perform this operation, you must be authenticated. [Learn more](authentication.md).

View File

@ -217,6 +217,7 @@
"logs": [
{
"created_at": "string",
"level": "trace",
"output": "string"
}
]
@ -302,16 +303,18 @@
```json
{
"created_at": "string",
"level": "trace",
"output": "string"
}
```
### Properties
| Name | Type | Required | Restrictions | Description |
| ------------ | ------ | -------- | ------------ | ----------- |
| `created_at` | string | false | | |
| `output` | string | false | | |
| Name | Type | Required | Restrictions | Description |
| ------------ | -------------------------------------- | -------- | ------------ | ----------- |
| `created_at` | string | false | | |
| `level` | [codersdk.LogLevel](#codersdkloglevel) | false | | |
| `output` | string | false | | |
## agentsdk.Stats
@ -4766,17 +4769,19 @@ Parameter represents a set value for the scope.
{
"created_at": "2019-08-24T14:15:22Z",
"id": 0,
"level": "trace",
"output": "string"
}
```
### Properties
| Name | Type | Required | Restrictions | Description |
| ------------ | ------- | -------- | ------------ | ----------- |
| `created_at` | string | false | | |
| `id` | integer | false | | |
| `output` | string | false | | |
| Name | Type | Required | Restrictions | Description |
| ------------ | -------------------------------------- | -------- | ------------ | ----------- |
| `created_at` | string | false | | |
| `id` | integer | false | | |
| `level` | [codersdk.LogLevel](#codersdkloglevel) | false | | |
| `output` | string | false | | |
## codersdk.WorkspaceAgentStatus

View File

@ -1131,6 +1131,7 @@ export interface WorkspaceAgentStartupLog {
readonly id: number
readonly created_at: string
readonly output: string
readonly level: LogLevel
}
// From codersdk/workspaceapps.go

View File

@ -73,7 +73,7 @@ export const workspaceAgentLogsMachine = createMachine(
API.getWorkspaceAgentStartupLogs(ctx.agentID).then((data) =>
data.map((log) => ({
id: log.id,
level: "info" as TypesGen.LogLevel,
level: log.level || "info",
output: log.output,
time: log.created_at,
})),