diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md
index 3c341a6e19..c6ccb7dbdc 100644
--- a/docs/troubleshooting.md
+++ b/docs/troubleshooting.md
@@ -253,11 +253,11 @@ Logs provide context for what was happening when the issue occurred. **You shoul
 your logs for any sensitive information you would not like to share online!**
 
 * Before sending through logs, try and reproduce the issue with **log level set to
-  Diagnostic**. You can set this in the [VS Code Settings][]
+  Trace**. You can set this in the [VS Code Settings][]
   (<kbd>Ctrl</kbd>+<kbd>,</kbd>) with:
 
   ```json
-  "powershell.developer.editorServicesLogLevel": "Diagnostic"
+  "powershell.developer.editorServicesLogLevel": "Trace"
   ```
 
 * After you have captured the issue with the log level turned up, you may want to return
diff --git a/package.json b/package.json
index 55b397dab6..73687b5fae 100644
--- a/package.json
+++ b/package.json
@@ -916,24 +916,24 @@
         },
         "powershell.developer.editorServicesLogLevel": {
           "type": "string",
-          "default": "Normal",
+          "default": "Warning",
           "enum": [
-            "Diagnostic",
-            "Verbose",
-            "Normal",
+            "Trace",
+            "Debug",
+            "Information",
             "Warning",
             "Error",
             "None"
           ],
           "markdownEnumDescriptions": [
             "Enables all logging possible, please use this setting when submitting logs for bug reports!",
-            "Enables more logging than normal.",
-            "The default logging level.",
-            "Only log warnings and errors.",
+            "Enables more detailed logging of the extension",
+            "Logs high-level information about what the extension is doing.",
+            "Only log warnings and errors. This is the default setting",
             "Only log errors.",
             "Disable all logging possible. No log files will be written!"
           ],
-          "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!**"
+          "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!**"
         },
         "powershell.developer.editorServicesWaitForDebugger": {
           "type": "boolean",
@@ -953,6 +953,21 @@
           "default": [],
           "markdownDescription": "An array of strings that enable experimental features in the PowerShell extension. **No flags are currently available!**"
         },
+        "powershell.developer.traceDap": {
+          "type": "boolean",
+          "default": false,
+          "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!**"
+        },
+        "powershell.trace.server": {
+          "type": "string",
+          "enum": [
+            "off",
+            "messages",
+            "verbose"
+          ],
+          "default": "off",
+          "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!**"
+        },
         "powershell.developer.waitForSessionFileTimeoutSeconds": {
           "type": "number",
           "default": 240,
@@ -1002,21 +1017,6 @@
           "type": "boolean",
           "default": false,
           "markdownDescription": "Show buttons in the editor's title bar for moving the terminals pane (with the PowerShell Extension Terminal) around."
-        },
-        "powershell.trace.server": {
-          "type": "string",
-          "enum": [
-            "off",
-            "messages",
-            "verbose"
-          ],
-          "default": "off",
-          "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!**"
-        },
-        "powershell.trace.dap": {
-          "type": "boolean",
-          "default": false,
-          "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!**"
         }
       }
     },
diff --git a/src/extension.ts b/src/extension.ts
index 0cb8f3f52e..8676724ab1 100644
--- a/src/extension.ts
+++ b/src/extension.ts
@@ -22,7 +22,7 @@ import { ShowHelpFeature } from "./features/ShowHelp";
 import { SpecifyScriptArgsFeature } from "./features/DebugSession";
 import { Logger } from "./logging";
 import { SessionManager } from "./session";
-import { LogLevel, getSettings } from "./settings";
+import { getSettings } from "./settings";
 import { PowerShellLanguageId } from "./utils";
 import { LanguageClientConsumer } from "./languageClientConsumer";
 
@@ -43,14 +43,12 @@ const documentSelector: DocumentSelector = [
 ];
 
 export async function activate(context: vscode.ExtensionContext): Promise<IPowerShellExtensionClient> {
-    const logLevel = vscode.workspace.getConfiguration(`${PowerShellLanguageId}.developer`)
-        .get<string>("editorServicesLogLevel", LogLevel.Normal);
-    logger = new Logger(logLevel, context.globalStorageUri);
+    logger = new Logger();
 
     telemetryReporter = new TelemetryReporter(TELEMETRY_KEY);
 
     const settings = getSettings();
-    logger.writeVerbose(`Loaded settings:\n${JSON.stringify(settings, undefined, 2)}`);
+    logger.writeDebug(`Loaded settings:\n${JSON.stringify(settings, undefined, 2)}`);
 
     languageConfigurationDisposable = vscode.languages.setLanguageConfiguration(
         PowerShellLanguageId,
@@ -141,6 +139,19 @@ export async function activate(context: vscode.ExtensionContext): Promise<IPower
         new PesterTestsFeature(sessionManager, logger),
         new CodeActionsFeature(logger),
         new SpecifyScriptArgsFeature(context),
+
+        vscode.commands.registerCommand(
+            "PowerShell.OpenLogFolder",
+            async () => {await vscode.commands.executeCommand(
+                "vscode.openFolder",
+                context.logUri,
+                { forceNewWindow: true }
+            );}
+        ),
+        vscode.commands.registerCommand(
+            "PowerShell.ShowLogs",
+            () => {logger.showLogPanel();}
+        )
     ];
 
     const externalApi = new ExternalApiFeature(context, sessionManager, logger);
@@ -169,6 +180,7 @@ export async function activate(context: vscode.ExtensionContext): Promise<IPower
         getPowerShellVersionDetails: uuid => externalApi.getPowerShellVersionDetails(uuid),
         waitUntilStarted: uuid => externalApi.waitUntilStarted(uuid),
         getStorageUri: () => externalApi.getStorageUri(),
+        getLogUri: () => externalApi.getLogUri(),
     };
 }
 
diff --git a/src/features/DebugSession.ts b/src/features/DebugSession.ts
index f9e4feae07..4af8c83b89 100644
--- a/src/features/DebugSession.ts
+++ b/src/features/DebugSession.ts
@@ -335,8 +335,8 @@ export class DebugSessionFeature extends LanguageClientConsumer
         // Create or show the debug terminal (either temporary or session).
         this.sessionManager.showDebugTerminal(true);
 
-        this.logger.writeVerbose(`Connecting to pipe: ${sessionDetails.debugServicePipeName}`);
-        this.logger.writeVerbose(`Debug configuration: ${JSON.stringify(session.configuration, undefined, 2)}`);
+        this.logger.writeDebug(`Connecting to pipe: ${sessionDetails.debugServicePipeName}`);
+        this.logger.writeDebug(`Debug configuration: ${JSON.stringify(session.configuration, undefined, 2)}`);
 
         return new DebugAdapterNamedPipeServer(sessionDetails.debugServicePipeName);
     }
@@ -424,7 +424,7 @@ export class DebugSessionFeature extends LanguageClientConsumer
                 // 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
                 startDebugEvent.dispose();
 
-                this.logger.writeVerbose(`Debugger session detected: ${dotnetAttachSession.name} (${dotnetAttachSession.id})`);
+                this.logger.writeDebug(`Debugger session detected: ${dotnetAttachSession.name} (${dotnetAttachSession.id})`);
 
                 tempConsoleDotnetAttachSession = dotnetAttachSession;
 
@@ -434,7 +434,7 @@ export class DebugSessionFeature extends LanguageClientConsumer
                     // Makes the event one-time
                     stopDebugEvent.dispose();
 
-                    this.logger.writeVerbose(`Debugger session terminated: ${tempConsoleSession.name} (${tempConsoleSession.id})`);
+                    this.logger.writeDebug(`Debugger session terminated: ${tempConsoleSession.name} (${tempConsoleSession.id})`);
 
                     // 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.
                     const disconnectRequest: DebugProtocol.DisconnectRequest = {
@@ -462,8 +462,8 @@ export class DebugSessionFeature extends LanguageClientConsumer
             // Start a child debug session to attach the dotnet debugger
             // TODO: Accommodate multi-folder workspaces if the C# code is in a different workspace folder
             await debug.startDebugging(undefined, dotnetAttachConfig, session);
-            this.logger.writeVerbose(`Dotnet attach debug configuration: ${JSON.stringify(dotnetAttachConfig, undefined, 2)}`);
-            this.logger.writeVerbose(`Attached dotnet debugger to process: ${pid}`);
+            this.logger.writeDebug(`Dotnet attach debug configuration: ${JSON.stringify(dotnetAttachConfig, undefined, 2)}`);
+            this.logger.writeDebug(`Attached dotnet debugger to process: ${pid}`);
         }
 
         return this.tempSessionDetails;
@@ -606,36 +606,27 @@ export class DebugSessionFeature extends LanguageClientConsumer
 
 class PowerShellDebugAdapterTrackerFactory implements DebugAdapterTrackerFactory, Disposable {
     disposables: Disposable[] = [];
-    dapLogEnabled: boolean = workspace.getConfiguration("powershell").get<boolean>("trace.dap") ?? false;
-    constructor(private adapterName = "PowerShell") {
-        this.disposables.push(workspace.onDidChangeConfiguration(change => {
-            if (
-                change.affectsConfiguration("powershell.trace.dap")
-            ) {
-                this.dapLogEnabled = workspace.getConfiguration("powershell").get<boolean>("trace.dap") ?? false;
-                if (this.dapLogEnabled) {
-                    // Trigger the output pane to appear. This gives the user time to position it before starting a debug.
-                    this.log?.show(true);
-                }
-            }
-        }));
-    }
+    constructor(private adapterName = "PowerShell") {}
 
-    /* We want to use a shared output log for separate debug sessions as usually only one is running at a time and we
-    *  dont need an output window for every debug session. We also want to leave it active so user can copy and paste
-    *  even on run end. When user changes the setting and disables it getter will return undefined, which will result
+
+    _log: LogOutputChannel | undefined;
+    /** Lazily creates a {@link LogOutputChannel} for debug tracing, and presents it only when DAP logging is enabled.
+    *
+    * We want to use a shared output log for separate debug sessions as usually only one is running at a time and we
+    * dont need an output window for every debug session. We also want to leave it active so user can copy and paste
+    * even on run end. When user changes the setting and disables it getter will return undefined, which will result
     * in a noop for the logging activities, effectively pausing logging but not disposing the output channel. If the
     * user re-enables, then logging resumes.
     */
-    _log: LogOutputChannel | undefined;
     get log(): LogOutputChannel | undefined {
-        if (this.dapLogEnabled && this._log === undefined) {
-            this._log = window.createOutputChannel(`${this.adapterName} Trace - DAP`, { log: true });
+        if (workspace.getConfiguration("powershell.developer").get<boolean>("traceDap") && this._log === undefined) {
+            this._log = window.createOutputChannel(`${this.adapterName}: Trace DAP`, { log: true });
             this.disposables.push(this._log);
         }
-        return this.dapLogEnabled ? this._log : undefined;
+        return this._log;
     }
 
+    // This tracker effectively implements the logging for the debug adapter to a LogOutputChannel
     createDebugAdapterTracker(session: DebugSession): DebugAdapterTracker {
         const sessionInfo = `${this.adapterName} Debug Session: ${session.name} [${session.id}]`;
         return {
diff --git a/src/features/ExternalApi.ts b/src/features/ExternalApi.ts
index 7943bf8fa6..29e3427f88 100644
--- a/src/features/ExternalApi.ts
+++ b/src/features/ExternalApi.ts
@@ -19,6 +19,7 @@ export interface IPowerShellExtensionClient {
     getPowerShellVersionDetails(uuid: string): Promise<IExternalPowerShellDetails>;
     waitUntilStarted(uuid: string): Promise<void>;
     getStorageUri(): vscode.Uri;
+    getLogUri(): vscode.Uri;
 }
 
 /*
@@ -55,7 +56,7 @@ export class ExternalApiFeature implements IPowerShellExtensionClient {
         string session uuid
     */
     public registerExternalExtension(id: string, apiVersion = "v1"): string {
-        this.logger.writeVerbose(`Registering extension '${id}' for use with API version '${apiVersion}'.`);
+        this.logger.writeDebug(`Registering extension '${id}' for use with API version '${apiVersion}'.`);
 
         // eslint-disable-next-line @typescript-eslint/no-unused-vars
         for (const [_name, externalExtension] of ExternalApiFeature.registeredExternalExtension) {
@@ -96,7 +97,7 @@ export class ExternalApiFeature implements IPowerShellExtensionClient {
         true if it worked, otherwise throws an error.
     */
     public unregisterExternalExtension(uuid = ""): boolean {
-        this.logger.writeVerbose(`Unregistering extension with session UUID: ${uuid}`);
+        this.logger.writeDebug(`Unregistering extension with session UUID: ${uuid}`);
         if (!ExternalApiFeature.registeredExternalExtension.delete(uuid)) {
             throw new Error(`No extension registered with session UUID: ${uuid}`);
         }
@@ -133,7 +134,7 @@ export class ExternalApiFeature implements IPowerShellExtensionClient {
     */
     public async getPowerShellVersionDetails(uuid = ""): Promise<IExternalPowerShellDetails> {
         const extension = this.getRegisteredExtension(uuid);
-        this.logger.writeVerbose(`Extension '${extension.id}' called 'getPowerShellVersionDetails'.`);
+        this.logger.writeDebug(`Extension '${extension.id}' called 'getPowerShellVersionDetails'.`);
 
         await this.sessionManager.waitUntilStarted();
         const versionDetails = this.sessionManager.getPowerShellVersionDetails();
@@ -161,7 +162,7 @@ export class ExternalApiFeature implements IPowerShellExtensionClient {
     */
     public async waitUntilStarted(uuid = ""): Promise<void> {
         const extension = this.getRegisteredExtension(uuid);
-        this.logger.writeVerbose(`Extension '${extension.id}' called 'waitUntilStarted'.`);
+        this.logger.writeDebug(`Extension '${extension.id}' called 'waitUntilStarted'.`);
         await this.sessionManager.waitUntilStarted();
     }
 
@@ -171,6 +172,10 @@ export class ExternalApiFeature implements IPowerShellExtensionClient {
         return this.extensionContext.globalStorageUri.with({ scheme: "file"});
     }
 
+    public getLogUri(): vscode.Uri {
+        return this.extensionContext.logUri.with({ scheme: "file"});
+    }
+
     public dispose(): void {
         // Nothing to dispose.
     }
diff --git a/src/features/UpdatePowerShell.ts b/src/features/UpdatePowerShell.ts
index 01c31eb385..6805272cc8 100644
--- a/src/features/UpdatePowerShell.ts
+++ b/src/features/UpdatePowerShell.ts
@@ -51,20 +51,20 @@ export class UpdatePowerShell {
     private shouldCheckForUpdate(): boolean {
         // Respect user setting.
         if (!this.sessionSettings.promptToUpdatePowerShell) {
-            this.logger.writeVerbose("Setting 'promptToUpdatePowerShell' was false.");
+            this.logger.writeDebug("Setting 'promptToUpdatePowerShell' was false.");
             return false;
         }
 
         // Respect environment configuration.
         if (process.env.POWERSHELL_UPDATECHECK?.toLowerCase() === "off") {
-            this.logger.writeVerbose("Environment variable 'POWERSHELL_UPDATECHECK' was 'Off'.");
+            this.logger.writeDebug("Environment variable 'POWERSHELL_UPDATECHECK' was 'Off'.");
             return false;
         }
 
         // Skip prompting when using Windows PowerShell for now.
         if (this.localVersion.compare("6.0.0") === -1) {
             // TODO: Maybe we should announce PowerShell Core?
-            this.logger.writeVerbose("Not prompting to update Windows PowerShell.");
+            this.logger.writeDebug("Not prompting to update Windows PowerShell.");
             return false;
         }
 
@@ -78,13 +78,13 @@ export class UpdatePowerShell {
 
             // Skip if PowerShell is self-built, that is, this contains a commit hash.
             if (commit.length >= 40) {
-                this.logger.writeVerbose("Not prompting to update development build.");
+                this.logger.writeDebug("Not prompting to update development build.");
                 return false;
             }
 
             // Skip if preview is a daily build.
             if (daily.toLowerCase().startsWith("daily")) {
-                this.logger.writeVerbose("Not prompting to update daily build.");
+                this.logger.writeDebug("Not prompting to update daily build.");
                 return false;
             }
         }
@@ -106,7 +106,7 @@ export class UpdatePowerShell {
         //     "ReleaseTag": "v7.2.7"
         // }
         const data = await response.json();
-        this.logger.writeVerbose(`Received from '${url}':\n${JSON.stringify(data, undefined, 2)}`);
+        this.logger.writeDebug(`Received from '${url}':\n${JSON.stringify(data, undefined, 2)}`);
         return data.ReleaseTag;
     }
 
@@ -115,18 +115,18 @@ export class UpdatePowerShell {
             return undefined;
         }
 
-        this.logger.writeVerbose("Checking for PowerShell update...");
+        this.logger.writeDebug("Checking for PowerShell update...");
         const tags: string[] = [];
         if (process.env.POWERSHELL_UPDATECHECK?.toLowerCase() === "lts") {
             // Only check for update to LTS.
-            this.logger.writeVerbose("Checking for LTS update...");
+            this.logger.writeDebug("Checking for LTS update...");
             const tag = await this.getRemoteVersion(UpdatePowerShell.LTSBuildInfoURL);
             if (tag != undefined) {
                 tags.push(tag);
             }
         } else {
             // Check for update to stable.
-            this.logger.writeVerbose("Checking for stable update...");
+            this.logger.writeDebug("Checking for stable update...");
             const tag = await this.getRemoteVersion(UpdatePowerShell.StableBuildInfoURL);
             if (tag != undefined) {
                 tags.push(tag);
@@ -134,7 +134,7 @@ export class UpdatePowerShell {
 
             // Also check for a preview update.
             if (this.localVersion.prerelease.length > 0) {
-                this.logger.writeVerbose("Checking for preview update...");
+                this.logger.writeDebug("Checking for preview update...");
                 const tag = await this.getRemoteVersion(UpdatePowerShell.PreviewBuildInfoURL);
                 if (tag != undefined) {
                     tags.push(tag);
@@ -181,11 +181,11 @@ export class UpdatePowerShell {
 
         // If the user cancels the notification.
         if (!result) {
-            this.logger.writeVerbose("User canceled PowerShell update prompt.");
+            this.logger.writeDebug("User canceled PowerShell update prompt.");
             return;
         }
 
-        this.logger.writeVerbose(`User said '${UpdatePowerShell.promptOptions[result.id].title}'.`);
+        this.logger.writeDebug(`User said '${UpdatePowerShell.promptOptions[result.id].title}'.`);
 
         switch (result.id) {
         // Yes
diff --git a/src/logging.ts b/src/logging.ts
index a7176c08ef..7ce8d09e16 100644
--- a/src/logging.ts
+++ b/src/logging.ts
@@ -1,30 +1,16 @@
 // Copyright (c) Microsoft Corporation.
 // Licensed under the MIT License.
 
-import utils = require("./utils");
-import os = require("os");
-import vscode = require("vscode");
-
-// NOTE: This is not a string enum because the order is used for comparison.
-export enum LogLevel {
-    Diagnostic,
-    Verbose,
-    Normal,
-    Warning,
-    Error,
-    None,
-}
+import { LogOutputChannel, LogLevel, window, Event } from "vscode";
 
 /** Interface for logging operations. New features should use this interface for the "type" of logger.
  *  This will allow for easy mocking of the logger during unit tests.
  */
 export interface ILogger {
-    logDirectoryPath: vscode.Uri;
-    updateLogLevel(logLevelName: string): void;
     write(message: string, ...additionalMessages: string[]): void;
     writeAndShowInformation(message: string, ...additionalMessages: string[]): Promise<void>;
-    writeDiagnostic(message: string, ...additionalMessages: string[]): void;
-    writeVerbose(message: string, ...additionalMessages: string[]): void;
+    writeTrace(message: string, ...additionalMessages: string[]): void;
+    writeDebug(message: string, ...additionalMessages: string[]): void;
     writeWarning(message: string, ...additionalMessages: string[]): void;
     writeAndShowWarning(message: string, ...additionalMessages: string[]): Promise<void>;
     writeError(message: string, ...additionalMessages: string[]): void;
@@ -35,47 +21,16 @@ export interface ILogger {
 }
 
 export class Logger implements ILogger {
-    public logDirectoryPath: vscode.Uri; // The folder for all the logs
-    private logLevel: LogLevel;
-    private commands: vscode.Disposable[];
-    private logChannel: vscode.OutputChannel;
-    private logFilePath: vscode.Uri; // The client's logs
-    private logDirectoryCreated = false;
-    private writingLog = false;
-
-    constructor(logLevelName: string, globalStorageUri: vscode.Uri) {
-        this.logLevel = Logger.logLevelNameToValue(logLevelName);
-        this.logChannel = vscode.window.createOutputChannel("PowerShell Extension Logs");
-        // We have to override the scheme because it defaults to
-        // 'vscode-userdata' which breaks UNC paths.
-        this.logDirectoryPath = vscode.Uri.joinPath(
-            globalStorageUri.with({ scheme: "file" }),
-            "logs",
-            `${Math.floor(Date.now() / 1000)}-${vscode.env.sessionId}`);
-        this.logFilePath = vscode.Uri.joinPath(this.logDirectoryPath, "vscode-powershell.log");
-
-        // Early logging of the log paths for debugging.
-        if (LogLevel.Diagnostic >= this.logLevel) {
-            const uriMessage = Logger.timestampMessage(`Log file path: '${this.logFilePath}'`, LogLevel.Verbose);
-            this.logChannel.appendLine(uriMessage);
-        }
-
-        this.commands = [
-            vscode.commands.registerCommand(
-                "PowerShell.ShowLogs",
-                () => { this.showLogPanel(); }),
+    // Log output channel handles all the verbosity management so we don't have to.
+    private logChannel: LogOutputChannel;
+    public get logLevel(): LogLevel { return this.logChannel.logLevel;}
 
-            vscode.commands.registerCommand(
-                "PowerShell.OpenLogFolder",
-                async () => { await this.openLogFolder(); }),
-        ];
+    constructor(logChannel?: LogOutputChannel) {
+        this.logChannel = logChannel ?? window.createOutputChannel("PowerShell", {log: true});
     }
 
     public dispose(): void {
         this.logChannel.dispose();
-        for (const command of this.commands) {
-            command.dispose();
-        }
     }
 
     private writeAtLevel(logLevel: LogLevel, message: string, ...additionalMessages: string[]): void {
@@ -89,24 +44,24 @@ export class Logger implements ILogger {
     }
 
     public write(message: string, ...additionalMessages: string[]): void {
-        this.writeAtLevel(LogLevel.Normal, message, ...additionalMessages);
+        this.writeAtLevel(LogLevel.Info, message, ...additionalMessages);
     }
 
     public async writeAndShowInformation(message: string, ...additionalMessages: string[]): Promise<void> {
         this.write(message, ...additionalMessages);
 
-        const selection = await vscode.window.showInformationMessage(message, "Show Logs", "Okay");
+        const selection = await window.showInformationMessage(message, "Show Logs", "Okay");
         if (selection === "Show Logs") {
             this.showLogPanel();
         }
     }
 
-    public writeDiagnostic(message: string, ...additionalMessages: string[]): void {
-        this.writeAtLevel(LogLevel.Diagnostic, message, ...additionalMessages);
+    public writeTrace(message: string, ...additionalMessages: string[]): void {
+        this.writeAtLevel(LogLevel.Trace, message, ...additionalMessages);
     }
 
-    public writeVerbose(message: string, ...additionalMessages: string[]): void {
-        this.writeAtLevel(LogLevel.Verbose, message, ...additionalMessages);
+    public writeDebug(message: string, ...additionalMessages: string[]): void {
+        this.writeAtLevel(LogLevel.Debug, message, ...additionalMessages);
     }
 
     public writeWarning(message: string, ...additionalMessages: string[]): void {
@@ -116,7 +71,7 @@ export class Logger implements ILogger {
     public async writeAndShowWarning(message: string, ...additionalMessages: string[]): Promise<void> {
         this.writeWarning(message, ...additionalMessages);
 
-        const selection = await vscode.window.showWarningMessage(message, "Show Logs");
+        const selection = await window.showWarningMessage(message, "Show Logs");
         if (selection !== undefined) {
             this.showLogPanel();
         }
@@ -129,7 +84,7 @@ export class Logger implements ILogger {
     public async writeAndShowError(message: string, ...additionalMessages: string[]): Promise<void> {
         this.writeError(message, ...additionalMessages);
 
-        const choice = await vscode.window.showErrorMessage(message, "Show Logs");
+        const choice = await window.showErrorMessage(message, "Show Logs");
         if (choice !== undefined) {
             this.showLogPanel();
         }
@@ -147,7 +102,7 @@ export class Logger implements ILogger {
 
         const actionKeys: string[] = fullActions.map((action) => action.prompt);
 
-        const choice = await vscode.window.showErrorMessage(message, ...actionKeys);
+        const choice = await window.showErrorMessage(message, ...actionKeys);
         if (choice) {
             for (const action of fullActions) {
                 if (choice === action.prompt && action.action !== undefined ) {
@@ -158,70 +113,177 @@ export class Logger implements ILogger {
         }
     }
 
-    // TODO: Make the enum smarter about strings so this goes away.
-    private static logLevelNameToValue(logLevelName: string): LogLevel {
-        switch (logLevelName.trim().toLowerCase()) {
-        case "diagnostic": return LogLevel.Diagnostic;
-        case "verbose": return LogLevel.Verbose;
-        case "normal": return LogLevel.Normal;
-        case "warning": return LogLevel.Warning;
-        case "error": return LogLevel.Error;
-        case "none": return LogLevel.None;
-        default: return LogLevel.Normal;
+    public showLogPanel(): void {
+        this.logChannel.show();
+    }
+
+    private async writeLine(message: string, level: LogLevel = LogLevel.Info): Promise<void> {
+        return new Promise<void>((resolve) => {
+            switch (level) {
+            case LogLevel.Off: break;
+            case LogLevel.Trace: this.logChannel.trace(message); break;
+            case LogLevel.Debug: this.logChannel.debug(message); break;
+            case LogLevel.Info: this.logChannel.info(message); break;
+            case LogLevel.Warning: this.logChannel.warn(message); break;
+            case LogLevel.Error: this.logChannel.error(message); break;
+            default: this.logChannel.appendLine(message); break;
+            }
+            resolve();
+        });
+    }
+}
+
+/** Parses logs received via the legacy OutputChannel to LogOutputChannel with proper severity.
+ *
+ * HACK: This is for legacy compatability and can be removed when https://github.com/microsoft/vscode-languageserver-node/issues/1116 is merged and replaced with a normal LogOutputChannel. We don't use a middleware here because any direct logging calls like client.warn() and server-initiated messages would not be captured by middleware.
+ */
+export class LanguageClientOutputChannelAdapter implements LogOutputChannel {
+    private _channel: LogOutputChannel | undefined;
+    private get channel(): LogOutputChannel {
+        if (!this._channel) {
+            this._channel = window.createOutputChannel(this.channelName, {log: true});
         }
+        return this._channel;
     }
 
-    public updateLogLevel(logLevelName: string): void {
-        this.logLevel = Logger.logLevelNameToValue(logLevelName);
+    /**
+     * Creates an instance of the logging class.
+     *
+     * @param channelName - The name of the output channel.
+     * @param parser - A function that parses a log message and returns a tuple containing the parsed message and its log level, or undefined if the log should be filtered.
+     */
+    constructor(
+        private channelName: string,
+        private parser: (message: string) => [string, LogLevel] | undefined = LanguageClientOutputChannelAdapter.omnisharpLspParser.bind(this)
+    ) {
     }
 
-    private showLogPanel(): void {
-        this.logChannel.show();
+    public appendLine(message: string): void {
+        this.append(message);
     }
 
-    private async openLogFolder(): Promise<void> {
-        if (this.logDirectoryCreated) {
-            // Open the folder in VS Code since there isn't an easy way to
-            // open the folder in the platform's file browser
-            await vscode.commands.executeCommand("vscode.openFolder", this.logDirectoryPath, true);
-        } else {
-            void this.writeAndShowError("Cannot open PowerShell log directory as it does not exist!");
-        }
+    public append(message: string): void {
+        const parseResult = this.parser(message);
+        if (parseResult !== undefined) {this.sendLogMessage(...parseResult);}
     }
 
-    private static timestampMessage(message: string, level: LogLevel): string {
-        const now = new Date();
-        return `${now.toLocaleDateString()} ${now.toLocaleTimeString()} [${LogLevel[level].toUpperCase()}] - ${message}${os.EOL}`;
+    /** Converts from Omnisharp logs since middleware for LogMessage does not currently exist **/
+    public static omnisharpLspParser(message: string): [string, LogLevel] {
+        const logLevelMatch = /^\[(?<level>Trace|Debug|Info|Warn|Error) +- \d+:\d+:\d+ [AP]M\] (?<message>.+)/.exec(message);
+        const logLevel: LogLevel = logLevelMatch?.groups?.level
+            ? LogLevel[logLevelMatch.groups.level as keyof typeof LogLevel]
+            : LogLevel.Info;
+        const logMessage = logLevelMatch?.groups?.message ?? message;
+
+        return [logMessage, logLevel];
     }
 
-    // TODO: Should we await this function above?
-    private async writeLine(message: string, level: LogLevel = LogLevel.Normal): Promise<void> {
-        const timestampedMessage = Logger.timestampMessage(message, level);
-        this.logChannel.appendLine(timestampedMessage);
-        if (this.logLevel !== LogLevel.None) {
-            // A simple lock because this function isn't re-entrant.
-            while (this.writingLog) {
-                await utils.sleep(300);
-            }
-            try {
-                this.writingLog = true;
-                if (!this.logDirectoryCreated) {
-                    this.writeVerbose(`Creating log directory at: '${this.logDirectoryPath}'`);
-                    await vscode.workspace.fs.createDirectory(this.logDirectoryPath);
-                    this.logDirectoryCreated = true;
-                }
-                let log = new Uint8Array();
-                if (await utils.checkIfFileExists(this.logFilePath)) {
-                    log = await vscode.workspace.fs.readFile(this.logFilePath);
-                }
-                await vscode.workspace.fs.writeFile(
-                    this.logFilePath,
-                    Buffer.concat([log, Buffer.from(timestampedMessage)]));
-            } catch (err) {
-                console.log(`Error writing to vscode-powershell log file: ${err}`);
-            } finally {
-                this.writingLog = false;
-            }
+    protected sendLogMessage(message: string, level: LogLevel): void {
+        switch (level) {
+        case LogLevel.Trace:
+            this.channel.trace(message);
+            break;
+        case LogLevel.Debug:
+            this.channel.debug(message);
+            break;
+        case LogLevel.Info:
+            this.channel.info(message);
+            break;
+        case LogLevel.Warning:
+            this.channel.warn(message);
+            break;
+        case LogLevel.Error:
+            this.channel.error(message);
+            break;
+        default:
+            this.channel.error("!UNKNOWN LOG LEVEL!: " + message);
+            break;
         }
     }
+
+    // #region Passthru Implementation
+    public get name(): string {
+        // prevents the window from being created unless we get a log request
+        return this.channelName;
+    }
+    public get logLevel(): LogLevel {
+        return this.channel.logLevel;
+    }
+    replace(value: string): void {
+        this.channel.replace(value);
+    }
+    show(_column?: undefined, preserveFocus?: boolean): void {
+        this.channel.show(preserveFocus);
+    }
+    public get onDidChangeLogLevel(): Event<LogLevel> {
+        return this.channel.onDidChangeLogLevel;
+    }
+    // eslint-disable-next-line @typescript-eslint/no-explicit-any
+    public trace(message: string, ...args: any[]): void {
+        this.channel.trace(message, ...args);
+    }
+    // eslint-disable-next-line @typescript-eslint/no-explicit-any
+    public debug(message: string, ...args: any[]): void {
+        this.channel.debug(message, ...args);
+    }
+    // eslint-disable-next-line @typescript-eslint/no-explicit-any
+    public info(message: string, ...args: any[]): void {
+        this.channel.info(message, ...args);
+    }
+    // eslint-disable-next-line @typescript-eslint/no-explicit-any
+    public warn(message: string, ...args: any[]): void {
+        this.channel.warn(message, ...args);
+    }
+    // eslint-disable-next-line @typescript-eslint/no-explicit-any
+    public error(message: string, ...args: any[]): void {
+        this.channel.error(message, ...args);
+    }
+    public clear(): void {
+        this.channel.clear();
+    }
+    public hide(): void {
+        this.channel.hide();
+    }
+    public dispose(): void {
+        this.channel.dispose();
+    }
+    // #endregion
+}
+
+/** Special parsing for PowerShell Editor Services LSP messages since the LogLevel cannot be read due to vscode
+ * LanguageClient Limitations (https://github.com/microsoft/vscode-languageserver-node/issues/1116)
+  */
+export function PsesParser(message: string): [string, LogLevel] {
+    const logLevelMatch = /^<(?<level>Trace|Debug|Info|Warning|Error)>(?<message>.+)/.exec(message);
+    const logLevel: LogLevel = logLevelMatch?.groups?.level
+        ? LogLevel[logLevelMatch.groups.level as keyof typeof LogLevel]
+        : LogLevel.Info;
+    const logMessage = logLevelMatch?.groups?.message ?? message;
+
+    return ["[PSES] " + logMessage, logLevel];
+}
+
+/** Lsp Trace Parser that does some additional parsing and formatting to make it look nicer */
+export function LspTraceParser(message: string): [string, LogLevel] {
+    let [parsedMessage, level] = LanguageClientOutputChannelAdapter.omnisharpLspParser(message);
+    if (parsedMessage.startsWith("Sending ")) {
+        parsedMessage = parsedMessage.replace("Sending", "➡️");
+        level = LogLevel.Debug;
+    }
+    if (parsedMessage.startsWith("Received ")) {
+        parsedMessage = parsedMessage.replace("Received", "⬅️");
+        level = LogLevel.Debug;
+    }
+    if (parsedMessage.startsWith("Params:")
+        || parsedMessage.startsWith("Result:")
+    ) {
+        level = LogLevel.Trace;
+    }
+
+    // These are PSES messages that get logged to the output channel anyways so we drop these to trace for easy noise filtering
+    if (parsedMessage.startsWith("⬅️ notification 'window/logMessage'")) {
+        level = LogLevel.Trace;
+    }
+
+    return [parsedMessage.trimEnd(), level];
 }
diff --git a/src/process.ts b/src/process.ts
index d363c8e2ee..052da2b8bf 100644
--- a/src/process.ts
+++ b/src/process.ts
@@ -30,6 +30,7 @@ export class PowerShellProcess {
         private isTemp: boolean,
         private shellIntegrationEnabled: boolean,
         private logger: ILogger,
+        private logDirectoryPath: vscode.Uri,
         private startPsesArgs: string,
         private sessionFilePath: vscode.Uri,
         private sessionSettings: Settings) {
@@ -51,7 +52,7 @@ export class PowerShellProcess {
                 : "";
 
         this.startPsesArgs +=
-            `-LogPath '${utils.escapeSingleQuotes(this.logger.logDirectoryPath.fsPath)}' ` +
+            `-LogPath '${utils.escapeSingleQuotes(this.logDirectoryPath.fsPath)}' ` +
             `-SessionDetailsPath '${utils.escapeSingleQuotes(this.sessionFilePath.fsPath)}' ` +
             `-FeatureFlags @(${featureFlags}) `;
 
@@ -89,13 +90,13 @@ export class PowerShellProcess {
                 startEditorServices);
         } else {
             // Otherwise use -EncodedCommand for better quote support.
-            this.logger.writeVerbose("Using Base64 -EncodedCommand but logging as -Command equivalent.");
+            this.logger.writeDebug("Using Base64 -EncodedCommand but logging as -Command equivalent.");
             powerShellArgs.push(
                 "-EncodedCommand",
                 Buffer.from(startEditorServices, "utf16le").toString("base64"));
         }
 
-        this.logger.writeVerbose(`Starting process: ${this.exePath} ${powerShellArgs.slice(0, -2).join(" ")} -Command ${startEditorServices}`);
+        this.logger.writeDebug(`Starting process: ${this.exePath} ${powerShellArgs.slice(0, -2).join(" ")} -Command ${startEditorServices}`);
 
         // Make sure no old session file exists
         await this.deleteSessionFile(this.sessionFilePath);
@@ -173,7 +174,7 @@ export class PowerShellProcess {
     }
 
     public dispose(): void {
-        this.logger.writeVerbose(`Disposing PowerShell process with PID: ${this.pid}`);
+        this.logger.writeDebug(`Disposing PowerShell process with PID: ${this.pid}`);
 
         void this.deleteSessionFile(this.sessionFilePath);
 
@@ -226,7 +227,7 @@ export class PowerShellProcess {
         const warnAt = numOfTries - PowerShellProcess.warnUserThreshold;
 
         // Check every second.
-        this.logger.writeVerbose(`Waiting for session file: ${this.sessionFilePath}`);
+        this.logger.writeDebug(`Waiting for session file: ${this.sessionFilePath}`);
         for (let i = numOfTries; i > 0; i--) {
             if (cancellationToken.isCancellationRequested) {
                 this.logger.writeWarning("Canceled while waiting for session file.");
@@ -239,7 +240,7 @@ export class PowerShellProcess {
             }
 
             if (await utils.checkIfFileExists(this.sessionFilePath)) {
-                this.logger.writeVerbose("Session file found.");
+                this.logger.writeDebug("Session file found.");
                 return await this.readSessionFile(this.sessionFilePath);
             }
 
diff --git a/src/session.ts b/src/session.ts
index a5f4314845..0b1037a116 100644
--- a/src/session.ts
+++ b/src/session.ts
@@ -6,7 +6,7 @@ import path = require("path");
 import vscode = require("vscode");
 import TelemetryReporter, { TelemetryEventProperties, TelemetryEventMeasurements } from "@vscode/extension-telemetry";
 import { Message } from "vscode-jsonrpc";
-import { ILogger } from "./logging";
+import { ILogger, LanguageClientOutputChannelAdapter, LspTraceParser, PsesParser } from "./logging";
 import { PowerShellProcess } from "./process";
 import { Settings, changeSetting, getSettings, getEffectiveConfigurationTarget, validateCwdSetting } from "./settings";
 import utils = require("./utils");
@@ -14,7 +14,8 @@ import utils = require("./utils");
 import {
     CloseAction, CloseHandlerResult, DocumentSelector, ErrorAction, ErrorHandlerResult,
     LanguageClientOptions, Middleware, NotificationType,
-    RequestType0, ResolveCodeLensSignature, RevealOutputChannelOn
+    RequestType0, ResolveCodeLensSignature,
+    RevealOutputChannelOn,
 } from "vscode-languageclient";
 import { LanguageClient, StreamInfo } from "vscode-languageclient/node";
 
@@ -93,6 +94,7 @@ export class SessionManager implements Middleware {
     private startCancellationTokenSource: vscode.CancellationTokenSource | undefined;
     private suppressRestartPrompt = false;
     private versionDetails: IPowerShellVersionDetails | undefined;
+    private traceLogLevelHandler?: vscode.Disposable;
 
     constructor(
         private extensionContext: vscode.ExtensionContext,
@@ -104,7 +106,6 @@ export class SessionManager implements Middleware {
         hostVersion: string,
         publisher: string,
         private telemetryReporter: TelemetryReporter) {
-
         // Create the language status item
         this.languageStatusItem = this.createStatusBarItem();
         // We have to override the scheme because it defaults to
@@ -161,7 +162,7 @@ export class SessionManager implements Middleware {
             return;
         case SessionStatus.Running:
             // We're started, just return.
-            this.logger.writeVerbose("Already started.");
+            this.logger.writeDebug("Already started.");
             return;
         case SessionStatus.Busy:
             // We're started but busy so notify and return.
@@ -170,12 +171,12 @@ export class SessionManager implements Middleware {
             return;
         case SessionStatus.Stopping:
             // Wait until done stopping, then start.
-            this.logger.writeVerbose("Still stopping.");
+            this.logger.writeDebug("Still stopping.");
             await this.waitWhileStopping();
             break;
         case SessionStatus.Failed:
             // Try to start again.
-            this.logger.writeVerbose("Previously failed, starting again.");
+            this.logger.writeDebug("Previously failed, starting again.");
             break;
         }
 
@@ -277,6 +278,8 @@ export class SessionManager implements Middleware {
         this.startCancellationTokenSource?.dispose();
         this.startCancellationTokenSource = undefined;
         this.sessionDetails = undefined;
+        this.traceLogLevelHandler?.dispose();
+        this.traceLogLevelHandler = undefined;
 
         this.setSessionStatus("Not Started", SessionStatus.NotStarted);
     }
@@ -291,7 +294,7 @@ export class SessionManager implements Middleware {
         if (exeNameOverride) {
             // Reset the version and PowerShell details since we're launching a
             // new executable.
-            this.logger.writeVerbose(`Starting with executable overriden to: ${exeNameOverride}`);
+            this.logger.writeDebug(`Starting with executable overriden to: ${exeNameOverride}`);
             this.sessionSettings.powerShellDefaultVersion = exeNameOverride;
             this.versionDetails = undefined;
             this.PowerShellExeDetails = undefined;
@@ -335,7 +338,6 @@ export class SessionManager implements Middleware {
         // handler when the process is disposed).
         this.debugSessionProcess?.dispose();
         this.debugEventHandler?.dispose();
-
         if (this.PowerShellExeDetails === undefined) {
             return Promise.reject(new Error("Required PowerShellExeDetails undefined!"));
         }
@@ -353,6 +355,7 @@ export class SessionManager implements Middleware {
                 true,
                 false,
                 this.logger,
+                this.extensionContext.logUri,
                 this.getEditorServicesArgs(bundledModulesPath, this.PowerShellExeDetails) + "-DebugServiceOnly ",
                 this.getNewSessionFilePath(),
                 this.sessionSettings);
@@ -451,34 +454,58 @@ export class SessionManager implements Middleware {
         }
     }
 
-    private async onConfigurationUpdated(): Promise<void> {
+    /** There are some changes we cannot "hot" set, so these require a restart of the session */
+    private async restartOnCriticalConfigChange(changeEvent: vscode.ConfigurationChangeEvent): Promise<void> {
+        if (this.suppressRestartPrompt) {return;}
+        if (this.sessionStatus !== SessionStatus.Running) {return;}
+
+        // Restart not needed if shell integration is enabled but the shell is backgrounded.
         const settings = getSettings();
-        const shellIntegrationEnabled = vscode.workspace.getConfiguration("terminal.integrated.shellIntegration").get<boolean>("enabled");
-        this.logger.updateLogLevel(settings.developer.editorServicesLogLevel);
+        if (changeEvent.affectsConfiguration("terminal.integrated.shellIntegration.enabled")) {
+            const shellIntegrationEnabled = vscode.workspace.getConfiguration("terminal.integrated.shellIntegration").get<boolean>("enabled") ?? false;
+            if (shellIntegrationEnabled && !settings.integratedConsole.startInBackground) {
+                return this.restartWithPrompt();
+            }
+        }
+
+        // Early return if the change doesn't affect the PowerShell extension settings from this point forward
+        if (!changeEvent.affectsConfiguration("powershell")) {return;}
+
 
         // Detect any setting changes that would affect the session.
-        if (!this.suppressRestartPrompt
-            && this.sessionStatus === SessionStatus.Running
-            && ((shellIntegrationEnabled !== this.shellIntegrationEnabled
-                && !settings.integratedConsole.startInBackground)
-            || settings.cwd !== this.sessionSettings.cwd
+        const coldRestartSettingNames = [
+            "developer.traceLsp",
+            "developer.traceDap",
+            "developer.editorServicesLogLevel",
+        ];
+        for (const settingName of coldRestartSettingNames) {
+            if (changeEvent.affectsConfiguration("powershell" + "." + settingName)) {
+                return this.restartWithPrompt();
+            }
+        }
+
+        // TODO: Migrate these to affectsConfiguration style above
+        if (settings.cwd !== this.sessionSettings.cwd
             || settings.powerShellDefaultVersion !== this.sessionSettings.powerShellDefaultVersion
-            || settings.developer.editorServicesLogLevel !== this.sessionSettings.developer.editorServicesLogLevel
             || settings.developer.bundledModulesPath !== this.sessionSettings.developer.bundledModulesPath
             || settings.developer.editorServicesWaitForDebugger !== this.sessionSettings.developer.editorServicesWaitForDebugger
             || settings.developer.setExecutionPolicy !== this.sessionSettings.developer.setExecutionPolicy
             || settings.integratedConsole.useLegacyReadLine !== this.sessionSettings.integratedConsole.useLegacyReadLine
             || settings.integratedConsole.startInBackground !== this.sessionSettings.integratedConsole.startInBackground
-            || settings.integratedConsole.startLocation !== this.sessionSettings.integratedConsole.startLocation)) {
+            || settings.integratedConsole.startLocation !== this.sessionSettings.integratedConsole.startLocation
+        ) {
+            return this.restartWithPrompt();
+        }
+    }
 
-            this.logger.writeVerbose("Settings changed, prompting to restart...");
-            const response = await vscode.window.showInformationMessage(
-                "The PowerShell runtime configuration has changed, would you like to start a new session?",
-                "Yes", "No");
+    private async restartWithPrompt(): Promise<void> {
+        this.logger.writeDebug("Settings changed, prompting to restart...");
+        const response = await vscode.window.showInformationMessage(
+            "The PowerShell runtime configuration has changed, would you like to start a new session?",
+            "Yes", "No");
 
-            if (response === "Yes") {
-                await this.restartSession();
-            }
+        if (response === "Yes") {
+            await this.restartSession();
         }
     }
 
@@ -486,14 +513,14 @@ export class SessionManager implements Middleware {
         this.registeredCommands = [
             vscode.commands.registerCommand("PowerShell.RestartSession", async () => { await this.restartSession(); }),
             vscode.commands.registerCommand(this.ShowSessionMenuCommandName, async () => { await this.showSessionMenu(); }),
-            vscode.workspace.onDidChangeConfiguration(async () => { await this.onConfigurationUpdated(); }),
+            vscode.workspace.onDidChangeConfiguration((e) => this.restartOnCriticalConfigChange(e)),
             vscode.commands.registerCommand(
                 "PowerShell.ShowSessionConsole", (isExecute?: boolean) => { this.showSessionTerminal(isExecute); })
         ];
     }
 
     private async findPowerShell(): Promise<IPowerShellExeDetails | undefined> {
-        this.logger.writeVerbose("Finding PowerShell...");
+        this.logger.writeDebug("Finding PowerShell...");
         const powershellExeFinder = new PowerShellExeFinder(
             this.platformDetails,
             this.sessionSettings.powerShellAdditionalExePaths,
@@ -539,6 +566,7 @@ export class SessionManager implements Middleware {
                 false,
                 this.shellIntegrationEnabled,
                 this.logger,
+                this.extensionContext.logUri,
                 this.getEditorServicesArgs(bundledModulesPath, powerShellExeDetails),
                 this.getNewSessionFilePath(),
                 this.sessionSettings);
@@ -591,7 +619,7 @@ export class SessionManager implements Middleware {
     }
 
     private sessionStarted(sessionDetails: IEditorServicesSessionDetails): boolean {
-        this.logger.writeVerbose(`Session details: ${JSON.stringify(sessionDetails, undefined, 2)}`);
+        this.logger.writeDebug(`Session details: ${JSON.stringify(sessionDetails, undefined, 2)}`);
         if (sessionDetails.status === "started") { // Successful server start with a session file
             return true;
         }
@@ -610,7 +638,7 @@ export class SessionManager implements Middleware {
     }
 
     private async startLanguageClient(sessionDetails: IEditorServicesSessionDetails): Promise<LanguageClient> {
-        this.logger.writeVerbose("Connecting to language service...");
+        this.logger.writeDebug("Connecting to language service...");
         const connectFunc = (): Promise<StreamInfo> => {
             return new Promise<StreamInfo>(
                 (resolve, _reject) => {
@@ -618,11 +646,12 @@ export class SessionManager implements Middleware {
                     socket.on(
                         "connect",
                         () => {
-                            this.logger.writeVerbose("Language service connected.");
+                            this.logger.writeDebug("Language service connected.");
                             resolve({ writer: socket, reader: socket });
                         });
                 });
         };
+
         const clientOptions: LanguageClientOptions = {
             documentSelector: this.documentSelector,
             synchronize: {
@@ -646,9 +675,11 @@ export class SessionManager implements Middleware {
                 // hangs up (ECONNRESET errors).
                 error: (_error: Error, _message: Message, _count: number): ErrorHandlerResult => {
                     // TODO: Is there any error worth terminating on?
+                    this.logger.writeError(`${_error.name}: ${_error.message} ${_error.cause}`);
                     return { action: ErrorAction.Continue };
                 },
                 closed: (): CloseHandlerResult => {
+                    this.logger.write("Language service connection closed.");
                     // We have our own restart experience
                     return {
                         action: CloseAction.DoNotRestart,
@@ -656,9 +687,11 @@ export class SessionManager implements Middleware {
                     };
                 },
             },
-            revealOutputChannelOn: RevealOutputChannelOn.Never,
             middleware: this,
-            traceOutputChannel: vscode.window.createOutputChannel("PowerShell Trace - LSP", {log: true}),
+            traceOutputChannel: new LanguageClientOutputChannelAdapter("PowerShell: Trace LSP", LspTraceParser),
+            // This is named the same as the Client log to merge the logs, but will be handled and disposed separately.
+            outputChannel: new LanguageClientOutputChannelAdapter("PowerShell", PsesParser),
+            revealOutputChannelOn: RevealOutputChannelOn.Never
         };
 
         const languageClient = new LanguageClient("powershell", "PowerShell Editor Services Client", connectFunc, clientOptions);
@@ -763,8 +796,8 @@ Type 'help' to get help.
             && this.extensionContext.extensionMode === vscode.ExtensionMode.Development) {
             editorServicesArgs += "-WaitForDebugger ";
         }
-
-        editorServicesArgs += `-LogLevel '${this.sessionSettings.developer.editorServicesLogLevel}' `;
+        const logLevel = vscode.workspace.getConfiguration("powershell.developer").get<string>("editorServicesLogLevel");
+        editorServicesArgs += `-LogLevel '${logLevel}' `;
 
         return editorServicesArgs;
     }
@@ -836,7 +869,7 @@ Type 'help' to get help.
     }
 
     private setSessionStatus(detail: string, status: SessionStatus): void {
-        this.logger.writeVerbose(`Session status changing from '${this.sessionStatus}' to '${status}'.`);
+        this.logger.writeDebug(`Session status changing from '${this.sessionStatus}' to '${status}'.`);
         this.sessionStatus = status;
         this.languageStatusItem.text = "$(terminal-powershell)";
         this.languageStatusItem.detail = "PowerShell";
diff --git a/src/settings.ts b/src/settings.ts
index 9c2ef38452..f29079846a 100644
--- a/src/settings.ts
+++ b/src/settings.ts
@@ -56,15 +56,6 @@ export enum PipelineIndentationStyle {
     None = "None",
 }
 
-export enum LogLevel {
-    Diagnostic = "Diagnostic",
-    Verbose = "Verbose",
-    Normal = "Normal",
-    Warning = "Warning",
-    Error = "Error",
-    None = "None",
-}
-
 export enum CommentType {
     Disabled = "Disabled",
     BlockComment = "BlockComment",
@@ -120,7 +111,6 @@ class DeveloperSettings extends PartialSettings {
     // From `<root>/out/main.js` we go to the directory before <root> and
     // then into the other repo.
     bundledModulesPath = "../../PowerShellEditorServices/module";
-    editorServicesLogLevel = LogLevel.Normal;
     editorServicesWaitForDebugger = false;
     setExecutionPolicy = true;
     waitForSessionFileTimeoutSeconds = 240;
@@ -209,7 +199,7 @@ export async function changeSetting(
     configurationTarget: vscode.ConfigurationTarget | boolean | undefined,
     logger: ILogger | undefined): Promise<void> {
 
-    logger?.writeVerbose(`Changing '${settingName}' at scope '${configurationTarget}' to '${newValue}'.`);
+    logger?.writeDebug(`Changing '${settingName}' at scope '${configurationTarget}' to '${newValue}'.`);
 
     try {
         const configuration = vscode.workspace.getConfiguration(utils.PowerShellLanguageId);
@@ -242,7 +232,7 @@ export async function getChosenWorkspace(logger: ILogger | undefined): Promise<v
 
         chosenWorkspace = await vscode.window.showWorkspaceFolderPick(options);
 
-        logger?.writeVerbose(`User selected workspace: '${chosenWorkspace?.name}'`);
+        logger?.writeDebug(`User selected workspace: '${chosenWorkspace?.name}'`);
         if (chosenWorkspace === undefined) {
             chosenWorkspace = vscode.workspace.workspaceFolders[0];
         } else {
@@ -296,7 +286,7 @@ export async function validateCwdSetting(logger: ILogger | undefined): Promise<s
     // Otherwise get a cwd from the workspace, if possible.
     const workspace = await getChosenWorkspace(logger);
     if (workspace === undefined) {
-        logger?.writeVerbose("Workspace was undefined, using homedir!");
+        logger?.writeDebug("Workspace was undefined, using homedir!");
         return os.homedir();
     }
 
@@ -316,3 +306,84 @@ export async function validateCwdSetting(logger: ILogger | undefined): Promise<s
     // If all else fails, use the home directory.
     return os.homedir();
 }
+
+
+/**
+ * Options for the `onSettingChange` function.
+ * @param scope the scope in which the vscode setting should be evaluated.
+ * @param run Indicates whether the function should be run now in addition to when settings change, or if it should be run only once and stop listening after a single change. If this is undefined, the function will be run only when the setting changes.
+ */
+interface onSettingChangeOptions {
+    scope?: vscode.ConfigurationScope;
+    run?: "now" | "once";
+}
+
+/**
+ * Invokes the specified action when a setting changes
+ * @param section the section of the vscode settings to evaluate. Defaults to `powershell`
+ * @param setting a string representation of the setting you wish to evaluate, e.g. `trace.server`
+ * @param action the action to take when the setting changes
+ * @param scope the scope in which the vscode setting should be evaluated.
+ * @returns a Disposable object that can be used to stop listening for changes with dispose()
+ * @example
+ * onSettingChange("powershell", "settingName", (newValue) => console.log(newValue));
+ */
+
+// Because we actually do use the constraint in the callback
+// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
+export function onSettingChange<T>(
+    section: string,
+    setting: string,
+    action: (newValue: T | undefined) => void,
+    options?: onSettingChangeOptions,
+): vscode.Disposable {
+    const settingPath = `${section}.${setting}`;
+    const disposable = vscode.workspace.onDidChangeConfiguration(e => {
+        if (!e.affectsConfiguration(settingPath, options?.scope)) { return; }
+
+        doOnSettingsChange(section, setting, action, options?.scope);
+        if (options?.run === "once") {
+            disposable.dispose(); // Javascript black magic, referring to an outer reference before it exists
+        }
+    });
+    if (options?.run === "now") {
+        doOnSettingsChange(section, setting, action, options.scope);
+    }
+    return disposable;
+}
+
+/** Implementation is separate to avoid duplicate code for run now */
+
+// Because we actually do use the constraint in the callback
+// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
+function doOnSettingsChange<T>(
+    section: string,
+    setting: string,
+    action: (newValue: T | undefined) => void,
+    scope?: vscode.ConfigurationScope,
+): void {
+    const value = vscode.workspace.getConfiguration(section, scope).get<T>(setting);
+    action(value);
+}
+
+/**
+ * Invokes the specified action when a PowerShell setting changes. Convenience function for `onSettingChange`
+ * @param setting a string representation of the setting you wish to evaluate, e.g. `trace.server`
+ * @param action the action to take when the setting changes
+ * @param scope the scope in which the vscode setting should be evaluated.n
+ * @returns a Disposable object that can be used to stop listening for changes
+ * @example
+ * onPowerShellSettingChange("settingName", (newValue) => console.log(newValue));
+ */
+
+// Because we actually do use the constraint in the callback
+// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
+export function onPowerShellSettingChange<T>(
+    setting: string,
+    action: (newValue: T | undefined) => void,
+    options?: onSettingChangeOptions
+
+): vscode.Disposable {
+    const section = "powershell";
+    return onSettingChange(section, setting, action, options);
+}
diff --git a/test/core/paths.test.ts b/test/core/paths.test.ts
index 15f60f5bd1..703b22a53f 100644
--- a/test/core/paths.test.ts
+++ b/test/core/paths.test.ts
@@ -9,9 +9,11 @@ import { checkIfDirectoryExists, checkIfFileExists, ShellIntegrationScript } fro
 
 describe("Path assumptions", function () {
     let globalStorageUri: vscode.Uri;
+    let logUri: vscode.Uri;
     before(async () => {
         const extension: IPowerShellExtensionClient = await utils.ensureEditorServicesIsConnected();
         globalStorageUri = extension.getStorageUri();
+        logUri = extension.getLogUri();
     });
 
     it("Creates the session folder at the correct path", async function () {
@@ -19,7 +21,7 @@ describe("Path assumptions", function () {
     });
 
     it("Creates the log folder at the correct path", async function () {
-        assert(await checkIfDirectoryExists(vscode.Uri.joinPath(globalStorageUri, "logs")));
+        assert(await checkIfDirectoryExists(logUri));
     });
 
     it("Finds the Terminal Shell Integration Script", async function () {
diff --git a/test/utils.ts b/test/utils.ts
index 7d601aadf2..e62de2d87e 100644
--- a/test/utils.ts
+++ b/test/utils.ts
@@ -25,10 +25,10 @@ export class TestLogger implements ILogger {
     writeAndShowInformation(_message: string, ..._additionalMessages: string[]): Promise<void> {
         return Promise.resolve();
     }
-    writeDiagnostic(_message: string, ..._additionalMessages: string[]): void {
+    writeTrace(_message: string, ..._additionalMessages: string[]): void {
         return;
     }
-    writeVerbose(_message: string, ..._additionalMessages: string[]): void {
+    writeDebug(_message: string, ..._additionalMessages: string[]): void {
         return;
     }
     writeWarning(_message: string, ..._additionalMessages: string[]): void {