Skip to content
Open
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
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,12 +85,14 @@
"enum": [
"debug",
"info",
"warn",
"error",
"off"
],
"markdownEnumDescriptions": [
"Log debug, info, and errors",
"Log info and errors",
"Log debug, info, warnings, and errors",
"Log info, warnings, and errors",
"Log warnings and errors",
"Log errors only",
"Disable logging"
],
Expand Down
1 change: 1 addition & 0 deletions src/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1002,6 +1002,7 @@ export class Configuration {
),
},
};

logger.debug("Environment:", env);

// Log the extension's user configuration settings.
Expand Down
4 changes: 3 additions & 1 deletion src/extensionData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -350,10 +350,12 @@ export class ExtensionData {

/**
* Get all extension discovery paths.
* Used for logging the paths to the output channel.
*
* @returns {ReadonlyMap<keyof ExtensionPaths, string>} A read-only Map containing all extension discovery paths.
*/
public getAllExtensionDiscoveryPaths(): ReadonlyMap<keyof ExtensionPaths, string> {
return this.extensionDiscoveryPaths;
// Return a new Map to prevent external mutation of the internal state.
return new Map(this.extensionDiscoveryPaths);
}
Comment thread
yCodeTech marked this conversation as resolved.
}
1 change: 1 addition & 0 deletions src/interfaces/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export type LanguageId = string;
export const logLevels = {
debug: "debug",
info: "info",
warn: "warn",
error: "error",
off: "off",
} as const;
Expand Down
72 changes: 69 additions & 3 deletions src/logger.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import * as os from "node:os";
import {OutputChannel, window} from "vscode";
import {LogLevel, logLevels} from "./interfaces/utils";

Expand Down Expand Up @@ -26,6 +27,14 @@ class Logger {
*/
private logLevel: LogLevel = "debug";

/**
* Whether the user has already been warned that username redaction is unavailable,
* so the warning is only emitted once per session.
*
* @type {boolean}
*/
private hasWarnedAboutRedactionFailure: boolean = false;

/***********
* Methods *
***********/
Expand Down Expand Up @@ -121,6 +130,17 @@ class Logger {
}
}

/**
* Sends a warning log to the output channel.
*
* @param {string} message The message to be logged.
*/
public warn(message: string): void {
if (this.shouldLog("warn")) {
this.logMessage("WARN", message);
}
}

/**
* Send an important message to the output channel.
* This is a special log level that is always emitted regardless of the log level,
Expand Down Expand Up @@ -150,8 +170,9 @@ class Logger {
private shouldLog(requiredLevel: LogLevel): boolean {
// Numeric weights used for level comparison.
const levelWeight: Record<LogLevel, number> = {
debug: 3, // Emits debug, info, and error logs - the most verbose level.
info: 2, // Emits info and error logs.
debug: 4, // Emits debug, info, warn, and error logs - the most verbose level.
info: 3, // Emits info, warn, and error logs.
warn: 2, // Emits warn and error logs.
error: 1, // Emits error logs only.
off: 0, // Disables all logs, except for the special "important" logs that are always emitted.
};
Expand Down Expand Up @@ -208,7 +229,7 @@ class Logger {
data = lines.join(",\n");
}

return data;
return this.redactUsername(data);
}

/**
Expand All @@ -232,6 +253,51 @@ class Logger {

return value;
}

/**
* Redact the OS username from a string, replacing it with `<redacted>`, to avoid leaking
* it into debug logs that could be shared.
*
* @param {string} text The text to redact.
* @returns {string} The text with OS username replaced with `<redacted>`.
*/
private redactUsername(text: string): string {
let username: string;

// Get the current OS username using Node's userInfo() method which is
// cross-platform compatible. It can throw an error in sandboxed/remote environments where
// the username can't be determined. So catch any errors and return the original text
// if we can't get the username.
try {
username = os.userInfo().username;
} catch {
this.warnOnRedactionFailure();
return text;
}

// If the username is empty, return the original text.
if (!username) {
this.warnOnRedactionFailure();
return text;
}

// Escape special characters in the username.
const escapedUsername = username.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
// Replace any occurrence of the username in the text with "<redacted>", case-insensitively, and return it.
return text.replace(new RegExp(escapedUsername, "gi"), "<redacted>");
}

/**
* Warn once per session that debug logs may not have the username redacted,
* so users don't unknowingly share it in a bug report.
*/
private warnOnRedactionFailure(): void {
if (this.hasWarnedAboutRedactionFailure) {
return;
}
this.hasWarnedAboutRedactionFailure = true;
this.warn("Could not determine OS username; debug logs may not be redacted before sharing.");
}
}

export const logger = new Logger();
Loading