Skip to content

Use VS Code's LogOutputChannel for logging #553

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jul 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
and configFile are provided.
- Add `coder.disableUpdateNotifications` setting to disable workspace template
update notifications.
- Coder output panel enhancements: All log entries now include timestamps, and you
can filter messages by log level in the panel.

## [v1.9.2](https://github.com/coder/vscode-coder/releases/tag/v1.9.2) 2025-06-25

Expand Down
2 changes: 1 addition & 1 deletion src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ export function makeCoderSdk(
restClient.getAxiosInstance().interceptors.response.use(
(r) => r,
async (err) => {
throw await CertificateError.maybeWrap(err, baseUrl, storage);
throw await CertificateError.maybeWrap(err, baseUrl, storage.output);
},
);

Expand Down
5 changes: 3 additions & 2 deletions src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -245,8 +245,9 @@ export class Commands {
} catch (err) {
const message = getErrorMessage(err, "no response from the server");
if (isAutologin) {
this.storage.writeToCoderOutputChannel(
`Failed to log in to Coder server: ${message}`,
this.storage.output.warn(
"Failed to log in to Coder server:",
message,
);
} else {
this.vscodeProposed.window.showErrorMessage(
Expand Down
15 changes: 11 additions & 4 deletions src/error.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import https from "https";
import * as path from "path";
import { afterAll, beforeAll, it, expect, vi } from "vitest";
import { CertificateError, X509_ERR, X509_ERR_CODE } from "./error";
import { Logger } from "./logger";

// Before each test we make a request to sanity check that we really get the
// error we are expecting, then we run it through CertificateError.
Expand All @@ -23,10 +24,16 @@ beforeAll(() => {
});
});

const logger = {
writeToCoderOutputChannel(message: string) {
throw new Error(message);
},
const throwingLog = (message: string) => {
throw new Error(message);
};

const logger: Logger = {
trace: throwingLog,
debug: throwingLog,
info: throwingLog,
warn: throwingLog,
error: throwingLog,
};

const disposers: (() => void)[] = [];
Expand Down
9 changes: 2 additions & 7 deletions src/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { isApiError, isApiErrorResponse } from "coder/site/src/api/errors";
import * as forge from "node-forge";
import * as tls from "tls";
import * as vscode from "vscode";
import { Logger } from "./logger";

// X509_ERR_CODE represents error codes as returned from BoringSSL/OpenSSL.
export enum X509_ERR_CODE {
Expand All @@ -21,10 +22,6 @@ export enum X509_ERR {
UNTRUSTED_CHAIN = "Your Coder deployment's certificate chain does not appear to be trusted by this system. The root of the certificate chain must be added to this system's trust store. ",
}

export interface Logger {
writeToCoderOutputChannel(message: string): void;
}

interface KeyUsage {
keyCertSign: boolean;
}
Expand Down Expand Up @@ -59,9 +56,7 @@ export class CertificateError extends Error {
await CertificateError.determineVerifyErrorCause(address);
return new CertificateError(err.message, cause);
} catch (error) {
logger.writeToCoderOutputChannel(
`Failed to parse certificate from ${address}: ${error}`,
);
logger.warn(`Failed to parse certificate from ${address}`, error);
break;
}
case X509_ERR_CODE.DEPTH_ZERO_SELF_SIGNED_CERT:
Expand Down
24 changes: 9 additions & 15 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export async function activate(ctx: vscode.ExtensionContext): Promise<void> {
);
}

const output = vscode.window.createOutputChannel("Coder");
const output = vscode.window.createOutputChannel("Coder", { log: true });
const storage = new Storage(
output,
ctx.globalState,
Expand Down Expand Up @@ -317,7 +317,7 @@ export async function activate(ctx: vscode.ExtensionContext): Promise<void> {
}
} catch (ex) {
if (ex instanceof CertificateError) {
storage.writeToCoderOutputChannel(ex.x509Err || ex.message);
storage.output.warn(ex.x509Err || ex.message);
await ex.showModal("Failed to open workspace");
} else if (isAxiosError(ex)) {
const msg = getErrorMessage(ex, "None");
Expand All @@ -326,7 +326,7 @@ export async function activate(ctx: vscode.ExtensionContext): Promise<void> {
const method = ex.config?.method?.toUpperCase() || "request";
const status = ex.response?.status || "None";
const message = `API ${method} to '${urlString}' failed.\nStatus code: ${status}\nMessage: ${msg}\nDetail: ${detail}`;
storage.writeToCoderOutputChannel(message);
storage.output.warn(message);
await vscodeProposed.window.showErrorMessage(
"Failed to open workspace",
{
Expand All @@ -337,7 +337,7 @@ export async function activate(ctx: vscode.ExtensionContext): Promise<void> {
);
} else {
const message = errToStr(ex, "No error message was provided");
storage.writeToCoderOutputChannel(message);
storage.output.warn(message);
await vscodeProposed.window.showErrorMessage(
"Failed to open workspace",
{
Expand All @@ -356,14 +356,12 @@ export async function activate(ctx: vscode.ExtensionContext): Promise<void> {
// See if the plugin client is authenticated.
const baseUrl = restClient.getAxiosInstance().defaults.baseURL;
if (baseUrl) {
storage.writeToCoderOutputChannel(
`Logged in to ${baseUrl}; checking credentials`,
);
storage.output.info(`Logged in to ${baseUrl}; checking credentials`);
restClient
.getAuthenticatedUser()
.then(async (user) => {
if (user && user.roles) {
storage.writeToCoderOutputChannel("Credentials are valid");
storage.output.info("Credentials are valid");
vscode.commands.executeCommand(
"setContext",
"coder.authenticated",
Expand All @@ -381,17 +379,13 @@ export async function activate(ctx: vscode.ExtensionContext): Promise<void> {
myWorkspacesProvider.fetchAndRefresh();
allWorkspacesProvider.fetchAndRefresh();
} else {
storage.writeToCoderOutputChannel(
`No error, but got unexpected response: ${user}`,
);
storage.output.warn("No error, but got unexpected response", user);
}
})
.catch((error) => {
// This should be a failure to make the request, like the header command
// errored.
storage.writeToCoderOutputChannel(
`Failed to check user authentication: ${error.message}`,
);
storage.output.warn("Failed to check user authentication", error);
vscode.window.showErrorMessage(
`Failed to check user authentication: ${error.message}`,
);
Expand All @@ -400,7 +394,7 @@ export async function activate(ctx: vscode.ExtensionContext): Promise<void> {
vscode.commands.executeCommand("setContext", "coder.loaded", true);
});
} else {
storage.writeToCoderOutputChannel("Not currently logged in");
storage.output.info("Not currently logged in");
vscode.commands.executeCommand("setContext", "coder.loaded", true);

// Handle autologin, if not already logged in.
Expand Down
13 changes: 8 additions & 5 deletions src/headers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@ import * as os from "os";
import { it, expect, describe, beforeEach, afterEach, vi } from "vitest";
import { WorkspaceConfiguration } from "vscode";
import { getHeaderCommand, getHeaders } from "./headers";

const logger = {
writeToCoderOutputChannel() {
// no-op
},
import { Logger } from "./logger";

const logger: Logger = {
trace: () => {},
debug: () => {},
info: () => {},
warn: () => {},
error: () => {},
};

it("should return no headers", async () => {
Expand Down
13 changes: 4 additions & 9 deletions src/headers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,9 @@ import * as cp from "child_process";
import * as os from "os";
import * as util from "util";
import type { WorkspaceConfiguration } from "vscode";
import { Logger } from "./logger";
import { escapeCommandArg } from "./util";

export interface Logger {
writeToCoderOutputChannel(message: string): void;
}

interface ExecException {
code?: number;
stderr?: string;
Expand Down Expand Up @@ -78,11 +75,9 @@ export async function getHeaders(
});
} catch (error) {
if (isExecException(error)) {
logger.writeToCoderOutputChannel(
`Header command exited unexpectedly with code ${error.code}`,
);
logger.writeToCoderOutputChannel(`stdout: ${error.stdout}`);
logger.writeToCoderOutputChannel(`stderr: ${error.stderr}`);
logger.warn("Header command exited unexpectedly with code", error.code);
logger.warn("stdout:", error.stdout);
logger.warn("stderr:", error.stderr);
throw new Error(
`Header command exited unexpectedly with code ${error.code}`,
);
Expand Down
8 changes: 3 additions & 5 deletions src/inbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export class Inbox implements vscode.Disposable {
});

this.#socket.on("open", () => {
this.#storage.writeToCoderOutputChannel("Listening to Coder Inbox");
this.#storage.output.info("Listening to Coder Inbox");
});

this.#socket.on("error", (error) => {
Expand All @@ -86,9 +86,7 @@ export class Inbox implements vscode.Disposable {

dispose() {
if (!this.#disposed) {
this.#storage.writeToCoderOutputChannel(
"No longer listening to Coder Inbox",
);
this.#storage.output.info("No longer listening to Coder Inbox");
this.#socket.close();
this.#disposed = true;
}
Expand All @@ -99,6 +97,6 @@ export class Inbox implements vscode.Disposable {
error,
"Got empty error while monitoring Coder Inbox",
);
this.#storage.writeToCoderOutputChannel(message);
this.#storage.output.error(message);
}
}
7 changes: 7 additions & 0 deletions src/logger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export interface Logger {
trace(message: string, ...args: unknown[]): void;
debug(message: string, ...args: unknown[]): void;
info(message: string, ...args: unknown[]): void;
warn(message: string, ...args: unknown[]): void;
error(message: string, ...args: unknown[]): void;
}
Loading