Skip to content

Commit ac10249

Browse files
authored
feat(logging): unify HTTP/WS handling and logging (#580)
- Add Axios interceptor with per-request UUID, timing, and status - Centralize net logging; introduce `coder.httpClientLogLevel` (none|basic|headers|body) - Standardize WebSocket creation via OneWayWebSocket (ws in VS Code); add connect/error/close logs - Replace SSE watchers with one-way WebSockets for consistency and performance - Refactor client API into `CoderApi` for both REST and WS requests - Default log levels: trace for non-error logs, error for failures; isolate logging utilities Fixes #532
1 parent 0013ead commit ac10249

27 files changed

+1112
-508
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010
### Added
1111

1212
- Add support for CLI global flag configurations through the `coder.globalFlags` setting.
13+
- Add logging for all REST traffic. Verbosity is configurable via `coder.httpClientLogLevel` (`none`, `basic`, `headers`, `body`).
14+
- Add lifecycle logs for WebSocket creation, errors, and closures.
15+
- Include UUIDs in REST and WebSocket logs to correlate events and measure duration.
1316

1417
## [1.10.1](https://github.com/coder/vscode-coder/releases/tag/v1.10.1) 2025-08-13
1518

package.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,23 @@
126126
"items": {
127127
"type": "string"
128128
}
129+
},
130+
"coder.httpClientLogLevel": {
131+
"markdownDescription": "Controls the verbosity of HTTP client logging. This affects what details are logged for each HTTP request and response.",
132+
"type": "string",
133+
"enum": [
134+
"none",
135+
"basic",
136+
"headers",
137+
"body"
138+
],
139+
"markdownEnumDescriptions": [
140+
"Disables all HTTP client logging",
141+
"Logs the request method, URL, length, and the response status code",
142+
"Logs everything from *basic* plus sanitized request and response headers",
143+
"Logs everything from *headers* plus request and response bodies (may include sensitive data)"
144+
],
145+
"default": "basic"
129146
}
130147
}
131148
},

src/agentMetadataHelper.ts

Lines changed: 33 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
1-
import { Api } from "coder/site/src/api/api";
21
import { WorkspaceAgent } from "coder/site/src/api/typesGenerated";
3-
import { EventSource } from "eventsource";
42
import * as vscode from "vscode";
5-
import { createStreamingFetchAdapter } from "./api";
63
import {
74
AgentMetadataEvent,
85
AgentMetadataEventSchemaArray,
96
errToStr,
10-
} from "./api-helper";
7+
} from "./api/api-helper";
8+
import { CoderApi } from "./api/coderApi";
119

1210
export type AgentMetadataWatcher = {
1311
onChange: vscode.EventEmitter<null>["event"];
@@ -17,47 +15,62 @@ export type AgentMetadataWatcher = {
1715
};
1816

1917
/**
20-
* Opens an SSE connection to watch metadata for a given workspace agent.
18+
* Opens a websocket connection to watch metadata for a given workspace agent.
2119
* Emits onChange when metadata updates or an error occurs.
2220
*/
2321
export function createAgentMetadataWatcher(
2422
agentId: WorkspaceAgent["id"],
25-
restClient: Api,
23+
client: CoderApi,
2624
): AgentMetadataWatcher {
27-
// TODO: Is there a better way to grab the url and token?
28-
const url = restClient.getAxiosInstance().defaults.baseURL;
29-
const metadataUrl = new URL(
30-
`${url}/api/v2/workspaceagents/${agentId}/watch-metadata`,
31-
);
32-
const eventSource = new EventSource(metadataUrl.toString(), {
33-
fetch: createStreamingFetchAdapter(restClient.getAxiosInstance()),
34-
});
25+
const socket = client.watchAgentMetadata(agentId);
3526

3627
let disposed = false;
3728
const onChange = new vscode.EventEmitter<null>();
3829
const watcher: AgentMetadataWatcher = {
3930
onChange: onChange.event,
4031
dispose: () => {
4132
if (!disposed) {
42-
eventSource.close();
33+
socket.close();
4334
disposed = true;
4435
}
4536
},
4637
};
4738

48-
eventSource.addEventListener("data", (event) => {
39+
const handleError = (error: unknown) => {
40+
watcher.error = error;
41+
onChange.fire(null);
42+
};
43+
44+
socket.addEventListener("message", (event) => {
4945
try {
50-
const dataEvent = JSON.parse(event.data);
51-
const metadata = AgentMetadataEventSchemaArray.parse(dataEvent);
46+
if (event.parseError) {
47+
handleError(event.parseError);
48+
return;
49+
}
50+
51+
const metadata = AgentMetadataEventSchemaArray.parse(
52+
event.parsedMessage.data,
53+
);
5254

5355
// Overwrite metadata if it changed.
5456
if (JSON.stringify(watcher.metadata) !== JSON.stringify(metadata)) {
5557
watcher.metadata = metadata;
5658
onChange.fire(null);
5759
}
5860
} catch (error) {
59-
watcher.error = error;
60-
onChange.fire(null);
61+
handleError(error);
62+
}
63+
});
64+
65+
socket.addEventListener("error", handleError);
66+
67+
socket.addEventListener("close", (event) => {
68+
if (event.code !== 1000) {
69+
handleError(
70+
new Error(
71+
`WebSocket closed unexpectedly: ${event.code} ${event.reason}`,
72+
),
73+
);
6174
}
6275
});
6376

0 commit comments

Comments
 (0)