Skip to content

Commit 723960d

Browse files
committed
More review comments fixes
1 parent 5ae5b5a commit 723960d

16 files changed

+104
-79
lines changed

CHANGELOG.md

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,13 @@
55
### Changed
66

77
- Always enable verbose (`-v`) flag when a log directory is configured (`coder.proxyLogDir`).
8-
- Replaced SSE paths with a one-way WebSocket and centralized creation on `OneWayWebSocket`, and unified socket handling.
98

109
### Added
1110

1211
- Add support for CLI global flag configurations through the `coder.globalFlags` setting.
13-
- Add logging for all REST and some WebSocket traffic, with REST verbosity configurable via `coder.httpClientLogLevel` (`none`, `basic`, `headers`, `body`).
14-
- An Axios interceptor that tags each request with a UUID to correlate with its response and measure duration.
15-
- Lifecycle logs for WebSocket creation, errors, and closures.
12+
- Add logging for all REST traffic. Verbosity is configurable via `coder.httpClientLogLevel` (`none`, `basic`, `headers`, `body`).
13+
- Add lifecycle logs for WebSocket creation, errors, and closures.
14+
- Include UUIDs in REST and WebSocket logs to correlate events and measure duration.
1615

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

src/agentMetadataHelper.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
AgentMetadataEventSchemaArray,
66
errToStr,
77
} from "./api/api-helper";
8-
import { CodeApi } from "./api/codeApi";
8+
import { CoderApi } from "./api/coderApi";
99

1010
export type AgentMetadataWatcher = {
1111
onChange: vscode.EventEmitter<null>["event"];
@@ -20,7 +20,7 @@ export type AgentMetadataWatcher = {
2020
*/
2121
export function createAgentMetadataWatcher(
2222
agentId: WorkspaceAgent["id"],
23-
client: CodeApi,
23+
client: CoderApi,
2424
): AgentMetadataWatcher {
2525
const socket = client.watchAgentMetadata(agentId);
2626

src/api/codeApi.ts renamed to src/api/coderApi.ts

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,11 @@ import {
2020
import { Logger } from "../logging/logger";
2121
import { RequestConfigWithMeta, HttpClientLogLevel } from "../logging/types";
2222
import { WsLogger } from "../logging/wsLogger";
23-
import { OneWayWebSocket } from "../websocket/oneWayWebSocket";
24-
import { createHttpAgent } from "./auth";
23+
import {
24+
OneWayWebSocket,
25+
OneWayWebSocketInit,
26+
} from "../websocket/oneWayWebSocket";
27+
import { createHttpAgent } from "./utils";
2528

2629
const coderSessionTokenHeader = "Coder-Session-Token";
2730

@@ -31,7 +34,7 @@ type WorkspaceConfigurationProvider = () => WorkspaceConfiguration;
3134
* Unified API class that includes both REST API methods from the base Api class
3235
* and WebSocket methods for real-time functionality.
3336
*/
34-
export class CodeApi extends Api {
37+
export class CoderApi extends Api {
3538
private constructor(
3639
private readonly output: Logger,
3740
private readonly configProvider: WorkspaceConfigurationProvider,
@@ -40,16 +43,16 @@ export class CodeApi extends Api {
4043
}
4144

4245
/**
43-
* Create a new CodeApi instance with the provided configuration.
46+
* Create a new CoderApi instance with the provided configuration.
4447
* Automatically sets up logging interceptors and certificate handling.
4548
*/
4649
static create(
4750
baseUrl: string,
4851
token: string | undefined,
4952
output: Logger,
5053
configProvider: WorkspaceConfigurationProvider,
51-
): CodeApi {
52-
const client = new CodeApi(output, configProvider);
54+
): CoderApi {
55+
const client = new CoderApi(output, configProvider);
5356
client.setHost(baseUrl);
5457
if (token) {
5558
client.setSessionToken(token);
@@ -106,12 +109,9 @@ export class CodeApi extends Api {
106109
return socket;
107110
};
108111

109-
private createWebSocket<TData = unknown>(configs: {
110-
apiRoute: string;
111-
protocols?: string | string[];
112-
searchParams?: Record<string, string> | URLSearchParams;
113-
options?: ClientOptions;
114-
}) {
112+
private createWebSocket<TData = unknown>(
113+
configs: Omit<OneWayWebSocketInit, "location">,
114+
) {
115115
const baseUrlRaw = this.getAxiosInstance().defaults.baseURL;
116116
if (!baseUrlRaw) {
117117
throw new Error("No base URL set on REST client");
@@ -163,10 +163,10 @@ export class CodeApi extends Api {
163163
}
164164

165165
/**
166-
* Set up logging and request interceptors for the CodeApi instance.
166+
* Set up logging and request interceptors for the CoderApi instance.
167167
*/
168168
function setupInterceptors(
169-
client: CodeApi,
169+
client: CoderApi,
170170
baseUrl: string,
171171
output: Logger,
172172
configProvider: WorkspaceConfigurationProvider,
File renamed without changes.

src/api/workspace.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import * as vscode from "vscode";
55
import { FeatureSet } from "../featureSet";
66
import { getGlobalFlags } from "../globalFlags";
77
import { errToStr, createWorkspaceIdentifier } from "./api-helper";
8-
import { CodeApi } from "./codeApi";
8+
import { CoderApi } from "./coderApi";
99

1010
/**
1111
* Start or update a workspace and return the updated workspace.
@@ -82,7 +82,7 @@ export async function startWorkspaceIfStoppedOrFailed(
8282
* Once completed, fetch the workspace again and return it.
8383
*/
8484
export async function waitForBuild(
85-
client: CodeApi,
85+
client: CoderApi,
8686
writeEmitter: vscode.EventEmitter<string>,
8787
workspace: Workspace,
8888
): Promise<Workspace> {

src/commands.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,16 @@ import {
88
import path from "node:path";
99
import * as vscode from "vscode";
1010
import { createWorkspaceIdentifier, extractAgents } from "./api/api-helper";
11-
import { needToken } from "./api/auth";
12-
import { CodeApi } from "./api/codeApi";
11+
import { CoderApi } from "./api/coderApi";
12+
import { needToken } from "./api/utils";
1313
import { CertificateError } from "./error";
1414
import { getGlobalFlags } from "./globalFlags";
1515
import { Storage } from "./storage";
1616
import { escapeCommandArg, toRemoteAuthority, toSafeHost } from "./util";
1717
import {
1818
AgentTreeItem,
19-
WorkspaceTreeItem,
2019
OpenableTreeItem,
20+
WorkspaceTreeItem,
2121
} from "./workspacesProvider";
2222

2323
export class Commands {
@@ -240,7 +240,7 @@ export class Commands {
240240
token: string,
241241
isAutologin: boolean,
242242
): Promise<{ user: User; token: string } | null> {
243-
const client = CodeApi.create(url, token, this.storage.output, () =>
243+
const client = CoderApi.create(url, token, this.storage.output, () =>
244244
vscode.workspace.getConfiguration(),
245245
);
246246
if (!needToken(vscode.workspace.getConfiguration())) {

src/extension.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import { getErrorMessage } from "coder/site/src/api/errors";
44
import * as module from "module";
55
import * as vscode from "vscode";
66
import { errToStr } from "./api/api-helper";
7-
import { needToken } from "./api/auth";
8-
import { CodeApi } from "./api/codeApi";
7+
import { CoderApi } from "./api/coderApi";
8+
import { needToken } from "./api/utils";
99
import { Commands } from "./commands";
1010
import { CertificateError, getErrorDetail } from "./error";
1111
import { Remote } from "./remote";
@@ -62,7 +62,7 @@ export async function activate(ctx: vscode.ExtensionContext): Promise<void> {
6262
// the plugin to poll workspaces for the current login, as well as being used
6363
// in commands that operate on the current login.
6464
const url = storage.getUrl();
65-
const client = CodeApi.create(
65+
const client = CoderApi.create(
6666
url || "",
6767
await storage.getSessionToken(),
6868
storage.output,

src/inbox.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import {
33
GetInboxNotificationResponse,
44
} from "coder/site/src/api/typesGenerated";
55
import * as vscode from "vscode";
6-
import { CodeApi } from "./api/codeApi";
6+
import { CoderApi } from "./api/coderApi";
77
import { type Storage } from "./storage";
88
import { OneWayWebSocket } from "./websocket/oneWayWebSocket";
99

@@ -18,7 +18,7 @@ export class Inbox implements vscode.Disposable {
1818
#disposed = false;
1919
#socket: OneWayWebSocket<GetInboxNotificationResponse>;
2020

21-
constructor(workspace: Workspace, client: CodeApi, storage: Storage) {
21+
constructor(workspace: Workspace, client: CoderApi, storage: Storage) {
2222
this.#storage = storage;
2323

2424
const watchTemplates = [

src/logging/formatters.ts

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,44 @@ import prettyBytes from "pretty-bytes";
33

44
const SENSITIVE_HEADERS = ["Coder-Session-Token", "Proxy-Authorization"];
55

6+
export function formatTime(ms: number): string {
7+
if (ms < 1000) {
8+
return `${ms}ms`;
9+
}
10+
if (ms < 60000) {
11+
return `${(ms / 1000).toFixed(2)}s`;
12+
}
13+
if (ms < 3600000) {
14+
return `${(ms / 60000).toFixed(2)}m`;
15+
}
16+
return `${(ms / 3600000).toFixed(2)}h`;
17+
}
18+
619
export function formatMethod(method: string | undefined): string {
720
return (method ?? "GET").toUpperCase();
821
}
922

10-
export function formatContentLength(headers: Record<string, unknown>): string {
23+
/**
24+
* Formats content-length for display. Returns the header value if available,
25+
* otherwise estimates size by serializing the data body (prefixed with ~).
26+
*/
27+
export function formatContentLength(
28+
headers: Record<string, unknown>,
29+
data: unknown,
30+
): string {
1131
const len = headers["content-length"];
1232
if (len && typeof len === "string") {
1333
const bytes = parseInt(len, 10);
1434
return isNaN(bytes) ? "(?b)" : `(${prettyBytes(bytes)})`;
1535
}
16-
return "(?b)";
36+
37+
// Estimate from data if no header
38+
if (data !== undefined && data !== null) {
39+
const estimated = Buffer.byteLength(JSON.stringify(data), "utf8");
40+
return `(~${prettyBytes(estimated)})`;
41+
}
42+
43+
return `(${prettyBytes(0)})`;
1744
}
1845

1946
export function formatUri(

src/logging/httpLogger.ts

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,26 @@
11
import type {
2-
InternalAxiosRequestConfig,
3-
AxiosResponse,
42
AxiosError,
3+
AxiosResponse,
4+
InternalAxiosRequestConfig,
55
} from "axios";
66
import { isAxiosError } from "axios";
77
import { getErrorMessage } from "coder/site/src/api/errors";
88
import { getErrorDetail } from "../error";
99
import {
10-
formatHeaders,
1110
formatBody,
12-
formatUri,
1311
formatContentLength,
12+
formatHeaders,
1413
formatMethod,
14+
formatTime,
15+
formatUri,
1516
} from "./formatters";
1617
import type { Logger } from "./logger";
1718
import {
1819
HttpClientLogLevel,
19-
RequestMeta,
2020
RequestConfigWithMeta,
21+
RequestMeta,
2122
} from "./types";
22-
import { shortId, formatTime, createRequestId } from "./utils";
23+
import { createRequestId, shortId } from "./utils";
2324

2425
export function createRequestMeta(): RequestMeta {
2526
return {
@@ -40,7 +41,7 @@ export function logRequest(
4041

4142
const method = formatMethod(config.method);
4243
const url = formatUri(config);
43-
const len = formatContentLength(config.headers);
44+
const len = formatContentLength(config.headers, config.data);
4445

4546
let msg = `→ ${shortId(requestId)} ${method} ${url} ${len}`;
4647
if (logLevel >= HttpClientLogLevel.HEADERS) {
@@ -67,7 +68,7 @@ export function logResponse(
6768
const method = formatMethod(response.config.method);
6869
const url = formatUri(response.config);
6970
const time = formatTime(Date.now() - meta.startedAt);
70-
const len = formatContentLength(response.headers);
71+
const len = formatContentLength(response.headers, response.data);
7172

7273
let msg = `← ${shortId(meta.requestId)} ${response.status} ${method} ${url} ${len} ${time}`;
7374

@@ -94,22 +95,32 @@ export function logRequestError(
9495
const requestId = meta?.requestId || "unknown";
9596
const time = meta ? formatTime(Date.now() - meta.startedAt) : "?ms";
9697

97-
const msg = getErrorMessage(error, "No error message");
98+
const msg = getErrorMessage(error, "");
9899
const detail = getErrorDetail(error) ?? "";
99100

100101
if (error.response) {
101-
const responseData =
102-
error.response.statusText || String(error.response.data).slice(0, 100);
103-
const errorInfo = [msg, detail, responseData].filter(Boolean).join(" - ");
104-
logger.error(
105-
`← ${shortId(requestId)} ${error.response.status} ${method} ${url} ${time} - ${errorInfo}`,
106-
error,
107-
);
102+
const msgParts = [
103+
`← ${shortId(requestId)} ${error.response.status} ${method} ${url} ${time}`,
104+
msg,
105+
detail,
106+
];
107+
if (msg.trim().length === 0 && detail.trim().length === 0) {
108+
const responseData =
109+
error.response.statusText ||
110+
String(error.response.data).slice(0, 100) ||
111+
"No error info";
112+
msgParts.push(responseData);
113+
}
114+
115+
const fullMsg = msgParts.map((str) => str.trim()).join(" - ");
116+
const headers = formatHeaders(error.response.headers);
117+
logger.error(`${fullMsg}\n${headers}`, error);
108118
} else {
109119
const reason = error.code || error.message || "Network error";
110120
const errorInfo = [msg, detail, reason].filter(Boolean).join(" - ");
121+
const headers = formatHeaders(config?.headers ?? {});
111122
logger.error(
112-
`✗ ${shortId(requestId)} ${method} ${url} ${time} - ${errorInfo}`,
123+
`✗ ${shortId(requestId)} ${method} ${url} ${time} - ${errorInfo}\n${headers}`,
113124
error,
114125
);
115126
}

0 commit comments

Comments
 (0)