Skip to content

Commit 400fd75

Browse files
authored
Implement LogOutputWindow for Logging (#5065)
* Implement LogOutputChannel and move settings to UI * Remove unnecessary comment * Remove File Logging (done by LogOutputWindow automatically) * First Connect * Add output adapters, LSP restart settings * Fix Log Uri Test * Forgot to add to extension facing API * Accidentally made a recursive rather than reference what I wanted to. Thanks Copilot... * Pre-Restart Experiments * Move Commands out of logger temporarily * Initial Cleanup of Logging, looks good ATM * Merge client and server editorservices logs * Add new MergedOutputChannel log * Remove unnecessary Import * Update settings for new EditorServicesLogLevels * Wire up loglevels in-band due to LSP bug * Rework multiple classes into a parser function injection * Fix some glyphs * Revert extra config settings for dynamic log configuration for now * Remove SetLSPTrace for now * Clean import * Align logging terminology to vscode output windows and remove editorServices from options definitions
1 parent 7126891 commit 400fd75

12 files changed

+422
-245
lines changed

docs/troubleshooting.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -253,11 +253,11 @@ Logs provide context for what was happening when the issue occurred. **You shoul
253253
your logs for any sensitive information you would not like to share online!**
254254

255255
* Before sending through logs, try and reproduce the issue with **log level set to
256-
Diagnostic**. You can set this in the [VS Code Settings][]
256+
Trace**. You can set this in the [VS Code Settings][]
257257
(<kbd>Ctrl</kbd>+<kbd>,</kbd>) with:
258258

259259
```json
260-
"powershell.developer.editorServicesLogLevel": "Diagnostic"
260+
"powershell.developer.editorServicesLogLevel": "Trace"
261261
```
262262

263263
* After you have captured the issue with the log level turned up, you may want to return

package.json

+23-23
Original file line numberDiff line numberDiff line change
@@ -916,24 +916,24 @@
916916
},
917917
"powershell.developer.editorServicesLogLevel": {
918918
"type": "string",
919-
"default": "Normal",
919+
"default": "Warning",
920920
"enum": [
921-
"Diagnostic",
922-
"Verbose",
923-
"Normal",
921+
"Trace",
922+
"Debug",
923+
"Information",
924924
"Warning",
925925
"Error",
926926
"None"
927927
],
928928
"markdownEnumDescriptions": [
929929
"Enables all logging possible, please use this setting when submitting logs for bug reports!",
930-
"Enables more logging than normal.",
931-
"The default logging level.",
932-
"Only log warnings and errors.",
930+
"Enables more detailed logging of the extension",
931+
"Logs high-level information about what the extension is doing.",
932+
"Only log warnings and errors. This is the default setting",
933933
"Only log errors.",
934934
"Disable all logging possible. No log files will be written!"
935935
],
936-
"markdownDescription": "Sets the log verbosity for both the extension and its LSP server, PowerShell Editor Services. **Please set to `Diagnostic` when recording logs for a bug report!**"
936+
"markdownDescription": "Sets the log verbosity for both the extension and its LSP server, PowerShell Editor Services. **Please set to `Trace` when recording logs for a bug report!**"
937937
},
938938
"powershell.developer.editorServicesWaitForDebugger": {
939939
"type": "boolean",
@@ -953,6 +953,21 @@
953953
"default": [],
954954
"markdownDescription": "An array of strings that enable experimental features in the PowerShell extension. **No flags are currently available!**"
955955
},
956+
"powershell.developer.traceDap": {
957+
"type": "boolean",
958+
"default": false,
959+
"markdownDescription": "Traces the DAP communication between VS Code and the PowerShell Editor Services [DAP Server](https://microsoft.github.io/debug-adapter-protocol/). The output will be logged and also visible in the Output pane, where the verbosity is configurable. **For extension developers and issue troubleshooting only!**"
960+
},
961+
"powershell.trace.server": {
962+
"type": "string",
963+
"enum": [
964+
"off",
965+
"messages",
966+
"verbose"
967+
],
968+
"default": "off",
969+
"markdownDescription": "Traces the communication between VS Code and the PowerShell Editor Services [LSP Server](https://microsoft.github.io/language-server-protocol/). The output will be logged and also visible in the Output pane, where the verbosity is configurable. **For extension developers and issue troubleshooting only!**"
970+
},
956971
"powershell.developer.waitForSessionFileTimeoutSeconds": {
957972
"type": "number",
958973
"default": 240,
@@ -1002,21 +1017,6 @@
10021017
"type": "boolean",
10031018
"default": false,
10041019
"markdownDescription": "Show buttons in the editor's title bar for moving the terminals pane (with the PowerShell Extension Terminal) around."
1005-
},
1006-
"powershell.trace.server": {
1007-
"type": "string",
1008-
"enum": [
1009-
"off",
1010-
"messages",
1011-
"verbose"
1012-
],
1013-
"default": "off",
1014-
"markdownDescription": "Traces the communication between VS Code and the PowerShell Editor Services [LSP Server](https://microsoft.github.io/language-server-protocol/). **only for extension developers and issue troubleshooting!**"
1015-
},
1016-
"powershell.trace.dap": {
1017-
"type": "boolean",
1018-
"default": false,
1019-
"markdownDescription": "Traces the communication between VS Code and the PowerShell Editor Services [DAP Server](https://microsoft.github.io/debug-adapter-protocol/). **This setting is only meant for extension developers and issue troubleshooting!**"
10201020
}
10211021
}
10221022
},

src/extension.ts

+17-5
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

@@ -43,14 +43,12 @@ const documentSelector: DocumentSelector = [
4343
];
4444

4545
export async function activate(context: vscode.ExtensionContext): Promise<IPowerShellExtensionClient> {
46-
const logLevel = vscode.workspace.getConfiguration(`${PowerShellLanguageId}.developer`)
47-
.get<string>("editorServicesLogLevel", LogLevel.Normal);
48-
logger = new Logger(logLevel, context.globalStorageUri);
46+
logger = new Logger();
4947

5048
telemetryReporter = new TelemetryReporter(TELEMETRY_KEY);
5149

5250
const settings = getSettings();
53-
logger.writeVerbose(`Loaded settings:\n${JSON.stringify(settings, undefined, 2)}`);
51+
logger.writeDebug(`Loaded settings:\n${JSON.stringify(settings, undefined, 2)}`);
5452

5553
languageConfigurationDisposable = vscode.languages.setLanguageConfiguration(
5654
PowerShellLanguageId,
@@ -141,6 +139,19 @@ export async function activate(context: vscode.ExtensionContext): Promise<IPower
141139
new PesterTestsFeature(sessionManager, logger),
142140
new CodeActionsFeature(logger),
143141
new SpecifyScriptArgsFeature(context),
142+
143+
vscode.commands.registerCommand(
144+
"PowerShell.OpenLogFolder",
145+
async () => {await vscode.commands.executeCommand(
146+
"vscode.openFolder",
147+
context.logUri,
148+
{ forceNewWindow: true }
149+
);}
150+
),
151+
vscode.commands.registerCommand(
152+
"PowerShell.ShowLogs",
153+
() => {logger.showLogPanel();}
154+
)
144155
];
145156

146157
const externalApi = new ExternalApiFeature(context, sessionManager, logger);
@@ -169,6 +180,7 @@ export async function activate(context: vscode.ExtensionContext): Promise<IPower
169180
getPowerShellVersionDetails: uuid => externalApi.getPowerShellVersionDetails(uuid),
170181
waitUntilStarted: uuid => externalApi.waitUntilStarted(uuid),
171182
getStorageUri: () => externalApi.getStorageUri(),
183+
getLogUri: () => externalApi.getLogUri(),
172184
};
173185
}
174186

src/features/DebugSession.ts

+18-27
Original file line numberDiff line numberDiff line change
@@ -335,8 +335,8 @@ export class DebugSessionFeature extends LanguageClientConsumer
335335
// Create or show the debug terminal (either temporary or session).
336336
this.sessionManager.showDebugTerminal(true);
337337

338-
this.logger.writeVerbose(`Connecting to pipe: ${sessionDetails.debugServicePipeName}`);
339-
this.logger.writeVerbose(`Debug configuration: ${JSON.stringify(session.configuration, undefined, 2)}`);
338+
this.logger.writeDebug(`Connecting to pipe: ${sessionDetails.debugServicePipeName}`);
339+
this.logger.writeDebug(`Debug configuration: ${JSON.stringify(session.configuration, undefined, 2)}`);
340340

341341
return new DebugAdapterNamedPipeServer(sessionDetails.debugServicePipeName);
342342
}
@@ -424,7 +424,7 @@ export class DebugSessionFeature extends LanguageClientConsumer
424424
// The dispose shorthand demonry for making an event one-time courtesy of: https://github.com/OmniSharp/omnisharp-vscode/blob/b8b07bb12557b4400198895f82a94895cb90c461/test/integrationTests/launchConfiguration.integration.test.ts#L41-L45
425425
startDebugEvent.dispose();
426426

427-
this.logger.writeVerbose(`Debugger session detected: ${dotnetAttachSession.name} (${dotnetAttachSession.id})`);
427+
this.logger.writeDebug(`Debugger session detected: ${dotnetAttachSession.name} (${dotnetAttachSession.id})`);
428428

429429
tempConsoleDotnetAttachSession = dotnetAttachSession;
430430

@@ -434,7 +434,7 @@ export class DebugSessionFeature extends LanguageClientConsumer
434434
// Makes the event one-time
435435
stopDebugEvent.dispose();
436436

437-
this.logger.writeVerbose(`Debugger session terminated: ${tempConsoleSession.name} (${tempConsoleSession.id})`);
437+
this.logger.writeDebug(`Debugger session terminated: ${tempConsoleSession.name} (${tempConsoleSession.id})`);
438438

439439
// HACK: As of 2023-08-17, there is no vscode debug API to request the C# debugger to detach, so we send it a custom DAP request instead.
440440
const disconnectRequest: DebugProtocol.DisconnectRequest = {
@@ -462,8 +462,8 @@ export class DebugSessionFeature extends LanguageClientConsumer
462462
// Start a child debug session to attach the dotnet debugger
463463
// TODO: Accommodate multi-folder workspaces if the C# code is in a different workspace folder
464464
await debug.startDebugging(undefined, dotnetAttachConfig, session);
465-
this.logger.writeVerbose(`Dotnet attach debug configuration: ${JSON.stringify(dotnetAttachConfig, undefined, 2)}`);
466-
this.logger.writeVerbose(`Attached dotnet debugger to process: ${pid}`);
465+
this.logger.writeDebug(`Dotnet attach debug configuration: ${JSON.stringify(dotnetAttachConfig, undefined, 2)}`);
466+
this.logger.writeDebug(`Attached dotnet debugger to process: ${pid}`);
467467
}
468468

469469
return this.tempSessionDetails;
@@ -606,36 +606,27 @@ export class DebugSessionFeature extends LanguageClientConsumer
606606

607607
class PowerShellDebugAdapterTrackerFactory implements DebugAdapterTrackerFactory, Disposable {
608608
disposables: Disposable[] = [];
609-
dapLogEnabled: boolean = workspace.getConfiguration("powershell").get<boolean>("trace.dap") ?? false;
610-
constructor(private adapterName = "PowerShell") {
611-
this.disposables.push(workspace.onDidChangeConfiguration(change => {
612-
if (
613-
change.affectsConfiguration("powershell.trace.dap")
614-
) {
615-
this.dapLogEnabled = workspace.getConfiguration("powershell").get<boolean>("trace.dap") ?? false;
616-
if (this.dapLogEnabled) {
617-
// Trigger the output pane to appear. This gives the user time to position it before starting a debug.
618-
this.log?.show(true);
619-
}
620-
}
621-
}));
622-
}
609+
constructor(private adapterName = "PowerShell") {}
623610

624-
/* We want to use a shared output log for separate debug sessions as usually only one is running at a time and we
625-
* dont need an output window for every debug session. We also want to leave it active so user can copy and paste
626-
* even on run end. When user changes the setting and disables it getter will return undefined, which will result
611+
612+
_log: LogOutputChannel | undefined;
613+
/** Lazily creates a {@link LogOutputChannel} for debug tracing, and presents it only when DAP logging is enabled.
614+
*
615+
* We want to use a shared output log for separate debug sessions as usually only one is running at a time and we
616+
* dont need an output window for every debug session. We also want to leave it active so user can copy and paste
617+
* even on run end. When user changes the setting and disables it getter will return undefined, which will result
627618
* in a noop for the logging activities, effectively pausing logging but not disposing the output channel. If the
628619
* user re-enables, then logging resumes.
629620
*/
630-
_log: LogOutputChannel | undefined;
631621
get log(): LogOutputChannel | undefined {
632-
if (this.dapLogEnabled && this._log === undefined) {
633-
this._log = window.createOutputChannel(`${this.adapterName} Trace - DAP`, { log: true });
622+
if (workspace.getConfiguration("powershell.developer").get<boolean>("traceDap") && this._log === undefined) {
623+
this._log = window.createOutputChannel(`${this.adapterName}: Trace DAP`, { log: true });
634624
this.disposables.push(this._log);
635625
}
636-
return this.dapLogEnabled ? this._log : undefined;
626+
return this._log;
637627
}
638628

629+
// This tracker effectively implements the logging for the debug adapter to a LogOutputChannel
639630
createDebugAdapterTracker(session: DebugSession): DebugAdapterTracker {
640631
const sessionInfo = `${this.adapterName} Debug Session: ${session.name} [${session.id}]`;
641632
return {

src/features/ExternalApi.ts

+9-4
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export interface IPowerShellExtensionClient {
1919
getPowerShellVersionDetails(uuid: string): Promise<IExternalPowerShellDetails>;
2020
waitUntilStarted(uuid: string): Promise<void>;
2121
getStorageUri(): vscode.Uri;
22+
getLogUri(): vscode.Uri;
2223
}
2324

2425
/*
@@ -55,7 +56,7 @@ export class ExternalApiFeature implements IPowerShellExtensionClient {
5556
string session uuid
5657
*/
5758
public registerExternalExtension(id: string, apiVersion = "v1"): string {
58-
this.logger.writeVerbose(`Registering extension '${id}' for use with API version '${apiVersion}'.`);
59+
this.logger.writeDebug(`Registering extension '${id}' for use with API version '${apiVersion}'.`);
5960

6061
// eslint-disable-next-line @typescript-eslint/no-unused-vars
6162
for (const [_name, externalExtension] of ExternalApiFeature.registeredExternalExtension) {
@@ -96,7 +97,7 @@ export class ExternalApiFeature implements IPowerShellExtensionClient {
9697
true if it worked, otherwise throws an error.
9798
*/
9899
public unregisterExternalExtension(uuid = ""): boolean {
99-
this.logger.writeVerbose(`Unregistering extension with session UUID: ${uuid}`);
100+
this.logger.writeDebug(`Unregistering extension with session UUID: ${uuid}`);
100101
if (!ExternalApiFeature.registeredExternalExtension.delete(uuid)) {
101102
throw new Error(`No extension registered with session UUID: ${uuid}`);
102103
}
@@ -133,7 +134,7 @@ export class ExternalApiFeature implements IPowerShellExtensionClient {
133134
*/
134135
public async getPowerShellVersionDetails(uuid = ""): Promise<IExternalPowerShellDetails> {
135136
const extension = this.getRegisteredExtension(uuid);
136-
this.logger.writeVerbose(`Extension '${extension.id}' called 'getPowerShellVersionDetails'.`);
137+
this.logger.writeDebug(`Extension '${extension.id}' called 'getPowerShellVersionDetails'.`);
137138

138139
await this.sessionManager.waitUntilStarted();
139140
const versionDetails = this.sessionManager.getPowerShellVersionDetails();
@@ -161,7 +162,7 @@ export class ExternalApiFeature implements IPowerShellExtensionClient {
161162
*/
162163
public async waitUntilStarted(uuid = ""): Promise<void> {
163164
const extension = this.getRegisteredExtension(uuid);
164-
this.logger.writeVerbose(`Extension '${extension.id}' called 'waitUntilStarted'.`);
165+
this.logger.writeDebug(`Extension '${extension.id}' called 'waitUntilStarted'.`);
165166
await this.sessionManager.waitUntilStarted();
166167
}
167168

@@ -171,6 +172,10 @@ export class ExternalApiFeature implements IPowerShellExtensionClient {
171172
return this.extensionContext.globalStorageUri.with({ scheme: "file"});
172173
}
173174

175+
public getLogUri(): vscode.Uri {
176+
return this.extensionContext.logUri.with({ scheme: "file"});
177+
}
178+
174179
public dispose(): void {
175180
// Nothing to dispose.
176181
}

src/features/UpdatePowerShell.ts

+12-12
Original file line numberDiff line numberDiff line change
@@ -51,20 +51,20 @@ export class UpdatePowerShell {
5151
private shouldCheckForUpdate(): boolean {
5252
// Respect user setting.
5353
if (!this.sessionSettings.promptToUpdatePowerShell) {
54-
this.logger.writeVerbose("Setting 'promptToUpdatePowerShell' was false.");
54+
this.logger.writeDebug("Setting 'promptToUpdatePowerShell' was false.");
5555
return false;
5656
}
5757

5858
// Respect environment configuration.
5959
if (process.env.POWERSHELL_UPDATECHECK?.toLowerCase() === "off") {
60-
this.logger.writeVerbose("Environment variable 'POWERSHELL_UPDATECHECK' was 'Off'.");
60+
this.logger.writeDebug("Environment variable 'POWERSHELL_UPDATECHECK' was 'Off'.");
6161
return false;
6262
}
6363

6464
// Skip prompting when using Windows PowerShell for now.
6565
if (this.localVersion.compare("6.0.0") === -1) {
6666
// TODO: Maybe we should announce PowerShell Core?
67-
this.logger.writeVerbose("Not prompting to update Windows PowerShell.");
67+
this.logger.writeDebug("Not prompting to update Windows PowerShell.");
6868
return false;
6969
}
7070

@@ -78,13 +78,13 @@ export class UpdatePowerShell {
7878

7979
// Skip if PowerShell is self-built, that is, this contains a commit hash.
8080
if (commit.length >= 40) {
81-
this.logger.writeVerbose("Not prompting to update development build.");
81+
this.logger.writeDebug("Not prompting to update development build.");
8282
return false;
8383
}
8484

8585
// Skip if preview is a daily build.
8686
if (daily.toLowerCase().startsWith("daily")) {
87-
this.logger.writeVerbose("Not prompting to update daily build.");
87+
this.logger.writeDebug("Not prompting to update daily build.");
8888
return false;
8989
}
9090
}
@@ -106,7 +106,7 @@ export class UpdatePowerShell {
106106
// "ReleaseTag": "v7.2.7"
107107
// }
108108
const data = await response.json();
109-
this.logger.writeVerbose(`Received from '${url}':\n${JSON.stringify(data, undefined, 2)}`);
109+
this.logger.writeDebug(`Received from '${url}':\n${JSON.stringify(data, undefined, 2)}`);
110110
return data.ReleaseTag;
111111
}
112112

@@ -115,26 +115,26 @@ export class UpdatePowerShell {
115115
return undefined;
116116
}
117117

118-
this.logger.writeVerbose("Checking for PowerShell update...");
118+
this.logger.writeDebug("Checking for PowerShell update...");
119119
const tags: string[] = [];
120120
if (process.env.POWERSHELL_UPDATECHECK?.toLowerCase() === "lts") {
121121
// Only check for update to LTS.
122-
this.logger.writeVerbose("Checking for LTS update...");
122+
this.logger.writeDebug("Checking for LTS update...");
123123
const tag = await this.getRemoteVersion(UpdatePowerShell.LTSBuildInfoURL);
124124
if (tag != undefined) {
125125
tags.push(tag);
126126
}
127127
} else {
128128
// Check for update to stable.
129-
this.logger.writeVerbose("Checking for stable update...");
129+
this.logger.writeDebug("Checking for stable update...");
130130
const tag = await this.getRemoteVersion(UpdatePowerShell.StableBuildInfoURL);
131131
if (tag != undefined) {
132132
tags.push(tag);
133133
}
134134

135135
// Also check for a preview update.
136136
if (this.localVersion.prerelease.length > 0) {
137-
this.logger.writeVerbose("Checking for preview update...");
137+
this.logger.writeDebug("Checking for preview update...");
138138
const tag = await this.getRemoteVersion(UpdatePowerShell.PreviewBuildInfoURL);
139139
if (tag != undefined) {
140140
tags.push(tag);
@@ -181,11 +181,11 @@ export class UpdatePowerShell {
181181

182182
// If the user cancels the notification.
183183
if (!result) {
184-
this.logger.writeVerbose("User canceled PowerShell update prompt.");
184+
this.logger.writeDebug("User canceled PowerShell update prompt.");
185185
return;
186186
}
187187

188-
this.logger.writeVerbose(`User said '${UpdatePowerShell.promptOptions[result.id].title}'.`);
188+
this.logger.writeDebug(`User said '${UpdatePowerShell.promptOptions[result.id].title}'.`);
189189

190190
switch (result.id) {
191191
// Yes

0 commit comments

Comments
 (0)