diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index d665c1bcee650..9a8855d0b79be 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -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" } diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index b4745e9895301..f32e9a87713d0 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -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" } diff --git a/coderd/database/dbfake/databasefake.go b/coderd/database/dbfake/databasefake.go index 465cd69ec770e..cb4ada860a8a2 100644 --- a/coderd/database/dbfake/databasefake.go +++ b/coderd/database/dbfake/databasefake.go @@ -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)) diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index 964cb7fe17cf1..f5bbe0cc04a85 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -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 diff --git a/coderd/database/migrations/000116_startup_logs_level.down.sql b/coderd/database/migrations/000116_startup_logs_level.down.sql new file mode 100644 index 0000000000000..5df24b3532f74 --- /dev/null +++ b/coderd/database/migrations/000116_startup_logs_level.down.sql @@ -0,0 +1,2 @@ +ALTER TABLE workspace_agent_startup_logs + DROP COLUMN level; diff --git a/coderd/database/migrations/000116_startup_logs_level.up.sql b/coderd/database/migrations/000116_startup_logs_level.up.sql new file mode 100644 index 0000000000000..cab77580a4ed0 --- /dev/null +++ b/coderd/database/migrations/000116_startup_logs_level.up.sql @@ -0,0 +1,2 @@ +ALTER TABLE workspace_agent_startup_logs + ADD COLUMN level log_level NOT NULL DEFAULT 'info'::log_level; diff --git a/coderd/database/models.go b/coderd/database/models.go index c4c830ef14b27..529bb413e48f6 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -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 { diff --git a/coderd/database/querier_test.go b/coderd/database/querier_test.go index 823079b7c6be6..ccbeb68a8d05d 100644 --- a/coderd/database/querier_test.go +++ b/coderd/database/querier_test.go @@ -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)) diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 3a8dda0fc4e39..947371b8f69db 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -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 } diff --git a/coderd/database/queries/workspaceagents.sql b/coderd/database/queries/workspaceagents.sql index 36c0ab31e13a0..f8fd756c7d888 100644 --- a/coderd/database/queries/workspaceagents.sql +++ b/coderd/database/queries/workspaceagents.sql @@ -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. diff --git a/coderd/workspaceagents.go b/coderd/workspaceagents.go index c63437486ef94..1abf0af78178d 100644 --- a/coderd/workspaceagents.go +++ b/coderd/workspaceagents.go @@ -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), } } diff --git a/codersdk/agentsdk/agentsdk.go b/codersdk/agentsdk/agentsdk.go index db6c839c5a994..df98961f5c488 100644 --- a/codersdk/agentsdk/agentsdk.go +++ b/codersdk/agentsdk/agentsdk.go @@ -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 { diff --git a/codersdk/workspaceagents.go b/codersdk/workspaceagents.go index 706ea1ec6a257..e398c981a8844 100644 --- a/codersdk/workspaceagents.go +++ b/codersdk/workspaceagents.go @@ -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"` } diff --git a/docs/api/agents.md b/docs/api/agents.md index e4c548568dfbb..cfdb087c30b8b 100644 --- a/docs/api/agents.md +++ b/docs/api/agents.md @@ -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). diff --git a/docs/api/schemas.md b/docs/api/schemas.md index 3bf9df5dced14..1a6fed6b99e5a 100644 --- a/docs/api/schemas.md +++ b/docs/api/schemas.md @@ -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 diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 0e07d764bb2fe..e9eb7ce0d0771 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -1131,6 +1131,7 @@ export interface WorkspaceAgentStartupLog { readonly id: number readonly created_at: string readonly output: string + readonly level: LogLevel } // From codersdk/workspaceapps.go diff --git a/site/src/xServices/workspaceAgentLogs/workspaceAgentLogsXService.ts b/site/src/xServices/workspaceAgentLogs/workspaceAgentLogsXService.ts index 69340c014be9b..ec987e523fd7d 100644 --- a/site/src/xServices/workspaceAgentLogs/workspaceAgentLogsXService.ts +++ b/site/src/xServices/workspaceAgentLogs/workspaceAgentLogsXService.ts @@ -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, })),