Skip to content

Commit fe5ea7f

Browse files
committed
Implement LogOutputChannel and move settings to UI
1 parent 9cd6b91 commit fe5ea7f

File tree

4 files changed

+75
-71
lines changed

4 files changed

+75
-71
lines changed

src/extension.ts

+3-4
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import { ShowHelpFeature } from "./features/ShowHelp";
2222
import { SpecifyScriptArgsFeature } from "./features/DebugSession";
2323
import { Logger } from "./logging";
2424
import { SessionManager } from "./session";
25-
import { LogLevel, getSettings } from "./settings";
25+
import { getSettings } from "./settings";
2626
import { PowerShellLanguageId } from "./utils";
2727
import { LanguageClientConsumer } from "./languageClientConsumer";
2828

@@ -47,9 +47,8 @@ const documentSelector: DocumentSelector = [
4747
];
4848

4949
export async function activate(context: vscode.ExtensionContext): Promise<IPowerShellExtensionClient> {
50-
const logLevel = vscode.workspace.getConfiguration(`${PowerShellLanguageId}.developer`)
51-
.get<string>("editorServicesLogLevel", LogLevel.Normal);
52-
logger = new Logger(logLevel, context.globalStorageUri);
50+
// This is the output channel for the PowerShell extension
51+
logger = new Logger(context.globalStorageUri);
5352

5453
telemetryReporter = new TelemetryReporter(TELEMETRY_KEY);
5554

src/logging.ts

+44-64
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,15 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

4-
import utils = require("./utils");
54
import os = require("os");
6-
import vscode = require("vscode");
7-
8-
// NOTE: This is not a string enum because the order is used for comparison.
9-
export enum LogLevel {
10-
Diagnostic,
11-
Verbose,
12-
Normal,
13-
Warning,
14-
Error,
15-
None,
16-
}
5+
import { sleep, checkIfFileExists } from "./utils";
6+
import { LogOutputChannel, Uri, Disposable, LogLevel, window, env, commands, workspace } from "vscode";
177

188
/** Interface for logging operations. New features should use this interface for the "type" of logger.
199
* This will allow for easy mocking of the logger during unit tests.
2010
*/
2111
export interface ILogger {
22-
logDirectoryPath: vscode.Uri;
23-
updateLogLevel(logLevelName: string): void;
12+
logDirectoryPath: Uri;
2413
write(message: string, ...additionalMessages: string[]): void;
2514
writeAndShowInformation(message: string, ...additionalMessages: string[]): Promise<void>;
2615
writeDiagnostic(message: string, ...additionalMessages: string[]): void;
@@ -35,37 +24,36 @@ export interface ILogger {
3524
}
3625

3726
export class Logger implements ILogger {
38-
public logDirectoryPath: vscode.Uri; // The folder for all the logs
39-
private logLevel: LogLevel;
40-
private commands: vscode.Disposable[];
41-
private logChannel: vscode.OutputChannel;
42-
private logFilePath: vscode.Uri; // The client's logs
27+
public logDirectoryPath: Uri; // The folder for all the logs
28+
private commands: Disposable[];
29+
// Log output channel handles all the verbosity management so we don't have to.
30+
private logChannel: LogOutputChannel;
31+
private logFilePath: Uri; // The client's logs
4332
private logDirectoryCreated = false;
4433
private writingLog = false;
34+
public get logLevel(): LogLevel { return this.logChannel.logLevel;}
4535

46-
constructor(logLevelName: string, globalStorageUri: vscode.Uri) {
47-
this.logLevel = Logger.logLevelNameToValue(logLevelName);
48-
this.logChannel = vscode.window.createOutputChannel("PowerShell Extension Logs");
36+
constructor(globalStorageUri: Uri, logChannel?: LogOutputChannel) {
37+
this.logChannel = logChannel ?? window.createOutputChannel("PowerShell", {log: true});
4938
// We have to override the scheme because it defaults to
5039
// 'vscode-userdata' which breaks UNC paths.
51-
this.logDirectoryPath = vscode.Uri.joinPath(
40+
this.logDirectoryPath = Uri.joinPath(
5241
globalStorageUri.with({ scheme: "file" }),
5342
"logs",
54-
`${Math.floor(Date.now() / 1000)}-${vscode.env.sessionId}`);
55-
this.logFilePath = vscode.Uri.joinPath(this.logDirectoryPath, "vscode-powershell.log");
43+
`${Math.floor(Date.now() / 1000)}-${env.sessionId}`);
44+
this.logFilePath = Uri.joinPath(this.logDirectoryPath, "vscode-powershell.log");
5645

5746
// Early logging of the log paths for debugging.
58-
if (LogLevel.Diagnostic >= this.logLevel) {
59-
const uriMessage = Logger.timestampMessage(`Log file path: '${this.logFilePath}'`, LogLevel.Verbose);
60-
this.logChannel.appendLine(uriMessage);
47+
if (this.logLevel >= LogLevel.Trace) {
48+
this.logChannel.trace(`Log directory: ${this.logDirectoryPath.fsPath}`);
6149
}
6250

6351
this.commands = [
64-
vscode.commands.registerCommand(
52+
commands.registerCommand(
6553
"PowerShell.ShowLogs",
6654
() => { this.showLogPanel(); }),
6755

68-
vscode.commands.registerCommand(
56+
commands.registerCommand(
6957
"PowerShell.OpenLogFolder",
7058
async () => { await this.openLogFolder(); }),
7159
];
@@ -89,24 +77,24 @@ export class Logger implements ILogger {
8977
}
9078

9179
public write(message: string, ...additionalMessages: string[]): void {
92-
this.writeAtLevel(LogLevel.Normal, message, ...additionalMessages);
80+
this.writeAtLevel(LogLevel.Info, message, ...additionalMessages);
9381
}
9482

9583
public async writeAndShowInformation(message: string, ...additionalMessages: string[]): Promise<void> {
9684
this.write(message, ...additionalMessages);
9785

98-
const selection = await vscode.window.showInformationMessage(message, "Show Logs", "Okay");
86+
const selection = await window.showInformationMessage(message, "Show Logs", "Okay");
9987
if (selection === "Show Logs") {
10088
this.showLogPanel();
10189
}
10290
}
10391

10492
public writeDiagnostic(message: string, ...additionalMessages: string[]): void {
105-
this.writeAtLevel(LogLevel.Diagnostic, message, ...additionalMessages);
93+
this.writeAtLevel(LogLevel.Trace, message, ...additionalMessages);
10694
}
10795

10896
public writeVerbose(message: string, ...additionalMessages: string[]): void {
109-
this.writeAtLevel(LogLevel.Verbose, message, ...additionalMessages);
97+
this.writeAtLevel(LogLevel.Debug, message, ...additionalMessages);
11098
}
11199

112100
public writeWarning(message: string, ...additionalMessages: string[]): void {
@@ -116,7 +104,7 @@ export class Logger implements ILogger {
116104
public async writeAndShowWarning(message: string, ...additionalMessages: string[]): Promise<void> {
117105
this.writeWarning(message, ...additionalMessages);
118106

119-
const selection = await vscode.window.showWarningMessage(message, "Show Logs");
107+
const selection = await window.showWarningMessage(message, "Show Logs");
120108
if (selection !== undefined) {
121109
this.showLogPanel();
122110
}
@@ -129,7 +117,7 @@ export class Logger implements ILogger {
129117
public async writeAndShowError(message: string, ...additionalMessages: string[]): Promise<void> {
130118
this.writeError(message, ...additionalMessages);
131119

132-
const choice = await vscode.window.showErrorMessage(message, "Show Logs");
120+
const choice = await window.showErrorMessage(message, "Show Logs");
133121
if (choice !== undefined) {
134122
this.showLogPanel();
135123
}
@@ -147,7 +135,7 @@ export class Logger implements ILogger {
147135

148136
const actionKeys: string[] = fullActions.map((action) => action.prompt);
149137

150-
const choice = await vscode.window.showErrorMessage(message, ...actionKeys);
138+
const choice = await window.showErrorMessage(message, ...actionKeys);
151139
if (choice) {
152140
for (const action of fullActions) {
153141
if (choice === action.prompt && action.action !== undefined ) {
@@ -158,23 +146,6 @@ export class Logger implements ILogger {
158146
}
159147
}
160148

161-
// TODO: Make the enum smarter about strings so this goes away.
162-
private static logLevelNameToValue(logLevelName: string): LogLevel {
163-
switch (logLevelName.trim().toLowerCase()) {
164-
case "diagnostic": return LogLevel.Diagnostic;
165-
case "verbose": return LogLevel.Verbose;
166-
case "normal": return LogLevel.Normal;
167-
case "warning": return LogLevel.Warning;
168-
case "error": return LogLevel.Error;
169-
case "none": return LogLevel.None;
170-
default: return LogLevel.Normal;
171-
}
172-
}
173-
174-
public updateLogLevel(logLevelName: string): void {
175-
this.logLevel = Logger.logLevelNameToValue(logLevelName);
176-
}
177-
178149
private showLogPanel(): void {
179150
this.logChannel.show();
180151
}
@@ -183,7 +154,7 @@ export class Logger implements ILogger {
183154
if (this.logDirectoryCreated) {
184155
// Open the folder in VS Code since there isn't an easy way to
185156
// open the folder in the platform's file browser
186-
await vscode.commands.executeCommand("vscode.openFolder", this.logDirectoryPath, true);
157+
await commands.executeCommand("openFolder", this.logDirectoryPath, true);
187158
} else {
188159
void this.writeAndShowError("Cannot open PowerShell log directory as it does not exist!");
189160
}
@@ -195,26 +166,35 @@ export class Logger implements ILogger {
195166
}
196167

197168
// TODO: Should we await this function above?
198-
private async writeLine(message: string, level: LogLevel = LogLevel.Normal): Promise<void> {
199-
const timestampedMessage = Logger.timestampMessage(message, level);
200-
this.logChannel.appendLine(timestampedMessage);
201-
if (this.logLevel !== LogLevel.None) {
169+
private async writeLine(message: string, level: LogLevel = LogLevel.Info): Promise<void> {
170+
switch (level) {
171+
case LogLevel.Trace: this.logChannel.trace(message); break;
172+
case LogLevel.Debug: this.logChannel.debug(message); break;
173+
case LogLevel.Info: this.logChannel.info(message); break;
174+
case LogLevel.Warning: this.logChannel.warn(message); break;
175+
case LogLevel.Error: this.logChannel.error(message); break;
176+
default: this.logChannel.appendLine(message); break;
177+
}
178+
179+
if (this.logLevel !== LogLevel.Off) {
202180
// A simple lock because this function isn't re-entrant.
203181
while (this.writingLog) {
204-
await utils.sleep(300);
182+
await sleep(300);
205183
}
206184
try {
207185
this.writingLog = true;
208186
if (!this.logDirectoryCreated) {
209187
this.writeVerbose(`Creating log directory at: '${this.logDirectoryPath}'`);
210-
await vscode.workspace.fs.createDirectory(this.logDirectoryPath);
188+
await workspace.fs.createDirectory(this.logDirectoryPath);
211189
this.logDirectoryCreated = true;
212190
}
213191
let log = new Uint8Array();
214-
if (await utils.checkIfFileExists(this.logFilePath)) {
215-
log = await vscode.workspace.fs.readFile(this.logFilePath);
192+
if (await checkIfFileExists(this.logFilePath)) {
193+
log = await workspace.fs.readFile(this.logFilePath);
216194
}
217-
await vscode.workspace.fs.writeFile(
195+
196+
const timestampedMessage = Logger.timestampMessage(message, level);
197+
await workspace.fs.writeFile(
218198
this.logFilePath,
219199
Buffer.concat([log, Buffer.from(timestampedMessage)]));
220200
} catch (err) {

src/session.ts

+2-3
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import utils = require("./utils");
1414
import {
1515
CloseAction, CloseHandlerResult, DocumentSelector, ErrorAction, ErrorHandlerResult,
1616
LanguageClientOptions, Middleware, NotificationType,
17-
RequestType0, ResolveCodeLensSignature, RevealOutputChannelOn
17+
RequestType0, ResolveCodeLensSignature
1818
} from "vscode-languageclient";
1919
import { LanguageClient, StreamInfo } from "vscode-languageclient/node";
2020

@@ -454,7 +454,6 @@ export class SessionManager implements Middleware {
454454
private async onConfigurationUpdated(): Promise<void> {
455455
const settings = getSettings();
456456
const shellIntegrationEnabled = vscode.workspace.getConfiguration("terminal.integrated.shellIntegration").get<boolean>("enabled");
457-
this.logger.updateLogLevel(settings.developer.editorServicesLogLevel);
458457

459458
// Detect any setting changes that would affect the session.
460459
if (!this.suppressRestartPrompt
@@ -656,9 +655,9 @@ export class SessionManager implements Middleware {
656655
};
657656
},
658657
},
659-
revealOutputChannelOn: RevealOutputChannelOn.Never,
660658
middleware: this,
661659
traceOutputChannel: vscode.window.createOutputChannel("PowerShell Trace - LSP", {log: true}),
660+
outputChannel: vscode.window.createOutputChannel("PowerShell Editor Services", {log: true}),
662661
};
663662

664663
const languageClient = new LanguageClient("powershell", "PowerShell Editor Services Client", connectFunc, clientOptions);

src/utils.ts

+26
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,32 @@ export function sleep(ms: number): Promise<void> {
8686
return new Promise(resolve => setTimeout(resolve, ms));
8787
}
8888

89+
/**
90+
* Invokes the specified action when a PowerShell setting changes
91+
* @param setting a string representation of the setting you wish to evaluate
92+
* @param action the action to take when the setting changes
93+
* @param scope the scope in which the vscode setting should be evaluated. Defaults to
94+
* @param workspace
95+
* @param onDidChangeConfiguration
96+
* @example onPowerShellSettingChange("settingName", (newValue) => console.log(newValue));
97+
* @returns a Disposable object that can be used to stop listening for changes
98+
*/
99+
100+
// Because we actually do use the constraint in the callback
101+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
102+
export function onPowerShellSettingChange<T>(
103+
setting: string,
104+
listener: (newValue: T | undefined) => void,
105+
section: "powershell",
106+
scope?: vscode.ConfigurationScope,
107+
): vscode.Disposable {
108+
return vscode.workspace.onDidChangeConfiguration(e => {
109+
if (!e.affectsConfiguration(section, scope)) { return; }
110+
const value = vscode.workspace.getConfiguration(section, scope).get<T>(setting);
111+
listener(value);
112+
});
113+
}
114+
89115
export const isMacOS: boolean = process.platform === "darwin";
90116
export const isWindows: boolean = process.platform === "win32";
91117
export const isLinux: boolean = !isMacOS && !isWindows;

0 commit comments

Comments
 (0)