From d1abc16a8e3b579ed1fe43d0042528b6ebe7016f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 27 Feb 2026 15:22:29 +0000 Subject: [PATCH 1/6] Initial plan From bf68ad4373431c0cd3b8799c0a439628f0c2946c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 27 Feb 2026 15:37:57 +0000 Subject: [PATCH 2/6] Add centralized debug logging with LogOutputChannel - Create src/logger.ts with leveled logging (trace/debug/info/warn/error) - Initialize logger during extension activation - Replace all console.error/warn calls with logger equivalents - Add debug logging to extension lifecycle, detect, state, documents, status-bar, API client, proxy commands, recording, notifications, MCP server, and task provider - Add logger test suite Co-authored-by: garrytrinder <11563347+garrytrinder@users.noreply.github.com> --- src/commands/proxy.ts | 4 +++ src/commands/recording.ts | 3 ++ src/detect.ts | 13 +++++-- src/diagnostics.ts | 3 +- src/documents.ts | 9 +++-- src/extension.ts | 13 ++++++- src/logger.ts | 60 ++++++++++++++++++++++++++++++++ src/mcp.ts | 2 ++ src/notifications.ts | 4 +++ src/services/api-client.ts | 5 +++ src/services/schema-validator.ts | 5 +-- src/state.ts | 3 ++ src/status-bar.ts | 3 +- src/task-provider.ts | 2 ++ src/test/logger.test.ts | 46 ++++++++++++++++++++++++ 15 files changed, 166 insertions(+), 9 deletions(-) create mode 100644 src/logger.ts create mode 100644 src/test/logger.test.ts diff --git a/src/commands/proxy.ts b/src/commands/proxy.ts index 75ea94d..dbdc361 100644 --- a/src/commands/proxy.ts +++ b/src/commands/proxy.ts @@ -5,6 +5,7 @@ import { TerminalService } from '../services/terminal'; import { isConfigFile } from '../utils'; import { getDevProxyExe } from '../detect'; import { VersionPreference } from '../enums'; +import * as logger from '../logger'; /** * Proxy lifecycle commands: start, stop, restart. @@ -47,6 +48,7 @@ async function startDevProxy( const configFilePath = getActiveConfigFilePath(); const command = configFilePath ? `${devProxyExe} --config-file "${configFilePath}"` : devProxyExe; + logger.debug('Starting Dev Proxy', { command }); terminalService.sendCommand(terminal, command); } @@ -55,6 +57,7 @@ async function stopDevProxy( devProxyExe: string, configuration: vscode.WorkspaceConfiguration ): Promise { + logger.debug('Stopping Dev Proxy'); await apiClient.stop(); const closeTerminal = configuration.get('closeTerminal', true); @@ -71,6 +74,7 @@ async function restartDevProxy( configuration: vscode.WorkspaceConfiguration, devProxyExe: string ): Promise { + logger.debug('Restarting Dev Proxy'); try { await apiClient.stop(); await waitForProxyToStop(apiClient); diff --git a/src/commands/recording.ts b/src/commands/recording.ts index 3afc139..e39e504 100644 --- a/src/commands/recording.ts +++ b/src/commands/recording.ts @@ -1,6 +1,7 @@ import * as vscode from 'vscode'; import { Commands, ContextKeys } from '../constants'; import { DevProxyApiClient } from '../services/api-client'; +import * as logger from '../logger'; /** * Recording commands: start/stop recording API requests. @@ -27,6 +28,7 @@ async function startRecording(apiClient: DevProxyApiClient): Promise { await apiClient.startRecording(); vscode.commands.executeCommand('setContext', ContextKeys.isRecording, true); } catch { + logger.error('Failed to start recording'); vscode.window.showErrorMessage('Failed to start recording'); } } @@ -36,6 +38,7 @@ async function stopRecording(apiClient: DevProxyApiClient): Promise { await apiClient.stopRecording(); vscode.commands.executeCommand('setContext', ContextKeys.isRecording, false); } catch { + logger.error('Failed to stop recording'); vscode.window.showErrorMessage('Failed to stop recording'); } } diff --git a/src/detect.ts b/src/detect.ts index c47e8a5..37e2b1a 100644 --- a/src/detect.ts +++ b/src/detect.ts @@ -3,19 +3,23 @@ import os from 'os'; import { VersionExeName, VersionPreference } from './enums'; import { executeCommand, resolveDevProxyExecutable } from './utils/shell'; import * as vscode from 'vscode'; +import * as logger from './logger'; export const getVersion = async (devProxyExe: string) => { try { const version = await executeCommand(`${devProxyExe} --version`); const versionLines = version.trim().split('\n'); const lastLine = versionLines[versionLines.length - 1]; + logger.debug('Dev Proxy version detected', lastLine.trim()); return lastLine.trim(); } catch (error) { + logger.debug('Failed to get Dev Proxy version', error); return ""; } }; export const detectDevProxyInstall = async (versionPreference: VersionPreference): Promise => { + logger.debug('Detecting Dev Proxy installation', { versionPreference }); const configuration = vscode.workspace.getConfiguration('dev-proxy-toolkit'); const customPath = configuration.get('devProxyPath'); const exeName = getDevProxyExe(versionPreference); @@ -28,7 +32,7 @@ export const detectDevProxyInstall = async (versionPreference: VersionPreference const isOutdated = isInstalled && outdatedVersion !== ''; const isRunning = await isDevProxyRunning(devProxyExe); vscode.commands.executeCommand('setContext', 'isDevProxyRunning', isRunning); - return { + const install = { version, isInstalled, isBeta, @@ -37,6 +41,8 @@ export const detectDevProxyInstall = async (versionPreference: VersionPreference isOutdated, isRunning }; + logger.debug('Dev Proxy installation detected', install); + return install; }; export const extractVersionFromOutput = (output: string): string => { @@ -91,9 +97,12 @@ export const isDevProxyRunning = async (devProxyExe: string): Promise = }); // If we get any response (even an error), Dev Proxy is running - return response.status >= 200 && response.status < 500; + const running = response.status >= 200 && response.status < 500; + logger.debug('Dev Proxy running check', { running, status: response.status }); + return running; } catch (error) { // If the request fails (connection refused, timeout, etc.), Dev Proxy is not running + logger.debug('Dev Proxy is not running'); return false; } }; diff --git a/src/diagnostics.ts b/src/diagnostics.ts index b4007f6..2bade85 100644 --- a/src/diagnostics.ts +++ b/src/diagnostics.ts @@ -7,6 +7,7 @@ import { DiagnosticCodes } from './constants'; import { getDiagnosticCode } from './utils'; import * as semver from 'semver'; import { fetchSchema, validateAgainstSchema } from './services'; +import * as logger from './logger'; export const updateConfigFileDiagnostics = async ( context: vscode.ExtensionContext, @@ -778,7 +779,7 @@ async function validateSingleConfigSection( }); } } catch (error) { - console.warn(`Error validating config section ${configSectionName}:`, error); + logger.warn(`Error validating config section ${configSectionName}`, error); } } diff --git a/src/documents.ts b/src/documents.ts index 9a88d9d..58a46bc 100644 --- a/src/documents.ts +++ b/src/documents.ts @@ -1,12 +1,14 @@ import * as vscode from 'vscode'; import { isConfigFile, isProxyFile } from './utils'; import { updateFileDiagnostics, updateConfigFileDiagnostics } from './diagnostics'; +import * as logger from './logger'; export const registerDocumentListeners = (context: vscode.ExtensionContext, collection: vscode.DiagnosticCollection) => { context.subscriptions.push( vscode.workspace.onDidOpenTextDocument(document => { try { if (isProxyFile(document)) { + logger.debug('Proxy file opened', document.uri.fsPath); updateFileDiagnostics(context, document, collection); vscode.commands.executeCommand('setContext', 'isDevProxyConfigFile', false); } @@ -14,11 +16,12 @@ export const registerDocumentListeners = (context: vscode.ExtensionContext, coll vscode.commands.executeCommand('setContext', 'isDevProxyConfigFile', false); return; } else { + logger.debug('Config file opened', document.uri.fsPath); vscode.commands.executeCommand('setContext', 'isDevProxyConfigFile', true); updateConfigFileDiagnostics(context, document, collection); } } catch (error) { - console.error('Error handling document open:', error); + logger.error('Error handling document open', error); } }) ); @@ -31,16 +34,18 @@ export const registerDocumentListeners = (context: vscode.ExtensionContext, coll return; } if (isConfigFile(event.document)) { + logger.debug('Config file changed', event.document.uri.fsPath); updateConfigFileDiagnostics(context, event.document, collection); vscode.commands.executeCommand('setContext', 'isDevProxyConfigFile', true); return; } if (isProxyFile(event.document)) { + logger.debug('Proxy file changed', event.document.uri.fsPath); updateFileDiagnostics(context, event.document, collection); vscode.commands.executeCommand('setContext', 'isDevProxyConfigFile', false); } } catch (error) { - console.error('Error handling document change:', error); + logger.error('Error handling document change', error); } }) ); diff --git a/src/extension.ts b/src/extension.ts index 1cb62a3..8c8806e 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -10,14 +10,21 @@ import { VersionPreference } from './enums'; import { registerMcpServer } from './mcp'; import { registerTaskProvider } from './task-provider'; import { promptForWorkspaceRecommendation } from './utils'; +import * as logger from './logger'; // Global variable to track the interval let statusBarInterval: NodeJS.Timeout | undefined; export const activate = async (context: vscode.ExtensionContext): Promise => { + const logChannel = logger.initializeLogger(); + context.subscriptions.push(logChannel); + + logger.info('Activating Dev Proxy Toolkit extension'); + const configuration = vscode.workspace.getConfiguration('dev-proxy-toolkit'); const versionPreference = configuration.get('version') as VersionPreference; + logger.debug('Configuration loaded', { versionPreference }); const statusBar = createStatusBar(context); await updateGlobalState(context, versionPreference); @@ -32,6 +39,7 @@ export const activate = async (context: vscode.ExtensionContext): Promise { + logger.info('Deactivating Dev Proxy Toolkit extension'); // Clean up the interval if it's still running if (statusBarInterval) { clearInterval(statusBarInterval); diff --git a/src/logger.ts b/src/logger.ts new file mode 100644 index 0000000..ca95fb8 --- /dev/null +++ b/src/logger.ts @@ -0,0 +1,60 @@ +import * as vscode from 'vscode'; + +/** + * Centralized logger for the Dev Proxy Toolkit extension. + * + * Uses a VS Code LogOutputChannel to provide leveled logging that + * appears in the Output panel and respects the user's log level settings. + */ + +let logger: vscode.LogOutputChannel | undefined; + +/** + * Initialize the logger. Must be called once during extension activation. + */ +export function initializeLogger(): vscode.LogOutputChannel { + logger = vscode.window.createOutputChannel('Dev Proxy Toolkit', { log: true }); + return logger; +} + +/** + * Get the logger instance. Falls back to a no-op if not initialized. + */ +function getLogger(): vscode.LogOutputChannel | undefined { + return logger; +} + +/** + * Log a trace-level message. + */ +export function trace(message: string, ...args: unknown[]): void { + getLogger()?.trace(message, ...args); +} + +/** + * Log a debug-level message. + */ +export function debug(message: string, ...args: unknown[]): void { + getLogger()?.debug(message, ...args); +} + +/** + * Log an info-level message. + */ +export function info(message: string, ...args: unknown[]): void { + getLogger()?.info(message, ...args); +} + +/** + * Log a warning-level message. + */ +export function warn(message: string, ...args: unknown[]): void { + getLogger()?.warn(message, ...args); +} + +/** + * Log an error-level message. + */ +export function error(message: string, ...args: unknown[]): void { + getLogger()?.error(message, ...args); +} diff --git a/src/mcp.ts b/src/mcp.ts index 6f6c8d4..9dab806 100644 --- a/src/mcp.ts +++ b/src/mcp.ts @@ -1,6 +1,8 @@ import * as vscode from 'vscode'; +import * as logger from './logger'; export const registerMcpServer = (context: vscode.ExtensionContext) => { + logger.debug('Registering MCP server definition provider'); const didChangeEmitter = new vscode.EventEmitter(); context.subscriptions.push( diff --git a/src/notifications.ts b/src/notifications.ts index 364055c..9a80451 100644 --- a/src/notifications.ts +++ b/src/notifications.ts @@ -2,10 +2,12 @@ import * as vscode from 'vscode'; import { DevProxyInstall } from './types'; import { Commands } from './constants'; import { findOutdatedConfigFiles, getNormalizedVersion } from './utils'; +import * as logger from './logger'; export const handleStartNotification = (context: vscode.ExtensionContext) => { const devProxyInstall = context.globalState.get('devProxyInstall'); if (!devProxyInstall) { + logger.debug('Dev Proxy install not found in global state'); return () => { const message = `Dev Proxy is not installed, or not in PATH.`; return { @@ -20,6 +22,7 @@ export const handleStartNotification = (context: vscode.ExtensionContext) => { }; }; if (!devProxyInstall.isInstalled) { + logger.debug('Dev Proxy is not installed'); return () => { const message = `Dev Proxy is not installed, or not in PATH.`; return { @@ -34,6 +37,7 @@ export const handleStartNotification = (context: vscode.ExtensionContext) => { }; }; if (devProxyInstall.isOutdated) { + logger.debug('Dev Proxy is outdated', { current: devProxyInstall.version, available: devProxyInstall.outdatedVersion }); return () => { const message = `New Dev Proxy version ${devProxyInstall.outdatedVersion} is available.`; return { diff --git a/src/services/api-client.ts b/src/services/api-client.ts index 06c65a6..af967f6 100644 --- a/src/services/api-client.ts +++ b/src/services/api-client.ts @@ -1,4 +1,5 @@ import * as vscode from 'vscode'; +import * as logger from '../logger'; /** * Client for communicating with the Dev Proxy API. @@ -49,6 +50,7 @@ export class DevProxyApiClient { * Stop the proxy. */ async stop(): Promise { + logger.debug('Stopping Dev Proxy'); await this.post('/proxy/stopproxy'); } @@ -56,6 +58,7 @@ export class DevProxyApiClient { * Raise a mock request. */ async raiseMockRequest(): Promise { + logger.debug('Raising mock request'); await this.post('/proxy/mockrequest'); } @@ -63,6 +66,7 @@ export class DevProxyApiClient { * Start recording API requests. */ async startRecording(): Promise { + logger.debug('Starting recording'); await this.post('/proxy', { recording: true }); } @@ -70,6 +74,7 @@ export class DevProxyApiClient { * Stop recording API requests. */ async stopRecording(): Promise { + logger.debug('Stopping recording'); await this.post('/proxy', { recording: false }); } diff --git a/src/services/schema-validator.ts b/src/services/schema-validator.ts index bb58ef5..1c3bfd3 100644 --- a/src/services/schema-validator.ts +++ b/src/services/schema-validator.ts @@ -1,4 +1,5 @@ import Ajv, { ErrorObject } from 'ajv'; +import * as logger from '../logger'; /** * Schema cache to avoid fetching the same schema multiple times. @@ -54,7 +55,7 @@ export async function fetchSchema(schemaUrl: string): Promise { + logger.debug('Updating global state'); const devProxyInstall = await detectDevProxyInstall(versionPreference); vscode.commands.executeCommand('setContext', 'isDevProxyInstalled', devProxyInstall.isInstalled); context.globalState.update('devProxyInstall', devProxyInstall); + logger.debug('Global state updated', { isInstalled: devProxyInstall.isInstalled, version: devProxyInstall.version }); }; \ No newline at end of file diff --git a/src/status-bar.ts b/src/status-bar.ts index 42a56e6..a91462f 100644 --- a/src/status-bar.ts +++ b/src/status-bar.ts @@ -2,6 +2,7 @@ import * as vscode from 'vscode'; import { DevProxyInstall } from './types'; import { getDevProxyExe, isDevProxyRunning } from './detect'; import { VersionPreference } from './enums'; +import * as logger from './logger'; export const createStatusBar = (context: vscode.ExtensionContext): vscode.StatusBarItem => { const statusBar = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, 100); @@ -64,6 +65,6 @@ export const statusBarLoop = async (context: vscode.ExtensionContext, statusBar: } } catch (error) { // Log but don't throw to prevent extension crashes - console.error('Error in statusBarLoop:', error); + logger.error('Error in statusBarLoop', error); } }; diff --git a/src/task-provider.ts b/src/task-provider.ts index bc36a89..64f9a07 100644 --- a/src/task-provider.ts +++ b/src/task-provider.ts @@ -1,6 +1,7 @@ import * as vscode from 'vscode'; import { getDevProxyExe } from './detect'; import { VersionPreference } from './enums'; +import * as logger from './logger'; interface DevProxyTaskDefinition extends vscode.TaskDefinition { type: 'devproxy'; @@ -134,6 +135,7 @@ export class DevProxyTaskProvider implements vscode.TaskProvider { } export const registerTaskProvider = (context: vscode.ExtensionContext) => { + logger.debug('Registering task provider'); const provider = new DevProxyTaskProvider(context); context.subscriptions.push( vscode.tasks.registerTaskProvider(DevProxyTaskProvider.DevProxyType, provider) diff --git a/src/test/logger.test.ts b/src/test/logger.test.ts new file mode 100644 index 0000000..a7bee8a --- /dev/null +++ b/src/test/logger.test.ts @@ -0,0 +1,46 @@ +/** + * Logger module tests. + * Verifies the logger initializes and provides leveled logging functions. + */ +import * as assert from 'assert'; +import * as sinon from 'sinon'; +import * as vscode from 'vscode'; +import { initializeLogger, debug, info, warn, error, trace } from '../logger'; + +suite('Logger', () => { + let sandbox: sinon.SinonSandbox; + + setup(() => { + sandbox = sinon.createSandbox(); + }); + + teardown(() => { + sandbox.restore(); + }); + + test('initializeLogger should create a LogOutputChannel', () => { + const channel = initializeLogger(); + assert.ok(channel); + assert.strictEqual(channel.name, 'Dev Proxy Toolkit'); + channel.dispose(); + }); + + test('logging functions should not throw before initialization', () => { + // These should be safe to call even if a different logger instance is active + assert.doesNotThrow(() => trace('test')); + assert.doesNotThrow(() => debug('test')); + assert.doesNotThrow(() => info('test')); + assert.doesNotThrow(() => warn('test')); + assert.doesNotThrow(() => error('test')); + }); + + test('logging functions should not throw after initialization', () => { + const channel = initializeLogger(); + assert.doesNotThrow(() => trace('test trace')); + assert.doesNotThrow(() => debug('test debug')); + assert.doesNotThrow(() => info('test info')); + assert.doesNotThrow(() => warn('test warn')); + assert.doesNotThrow(() => error('test error')); + channel.dispose(); + }); +}); From 9a1e54bd1bf6f3e360be6d0b3e6272eef370a49a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 27 Feb 2026 15:39:12 +0000 Subject: [PATCH 3/6] Address code review: use structured logging, rename variable, avoid exposing full paths Co-authored-by: garrytrinder <11563347+garrytrinder@users.noreply.github.com> --- src/commands/proxy.ts | 2 +- src/detect.ts | 6 +++--- src/documents.ts | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/commands/proxy.ts b/src/commands/proxy.ts index dbdc361..f0a1532 100644 --- a/src/commands/proxy.ts +++ b/src/commands/proxy.ts @@ -48,7 +48,7 @@ async function startDevProxy( const configFilePath = getActiveConfigFilePath(); const command = configFilePath ? `${devProxyExe} --config-file "${configFilePath}"` : devProxyExe; - logger.debug('Starting Dev Proxy', { command }); + logger.debug('Starting Dev Proxy', { configFile: configFilePath ?? 'default' }); terminalService.sendCommand(terminal, command); } diff --git a/src/detect.ts b/src/detect.ts index 37e2b1a..0e3630c 100644 --- a/src/detect.ts +++ b/src/detect.ts @@ -32,7 +32,7 @@ export const detectDevProxyInstall = async (versionPreference: VersionPreference const isOutdated = isInstalled && outdatedVersion !== ''; const isRunning = await isDevProxyRunning(devProxyExe); vscode.commands.executeCommand('setContext', 'isDevProxyRunning', isRunning); - const install = { + const devProxyInstall = { version, isInstalled, isBeta, @@ -41,8 +41,8 @@ export const detectDevProxyInstall = async (versionPreference: VersionPreference isOutdated, isRunning }; - logger.debug('Dev Proxy installation detected', install); - return install; + logger.debug('Dev Proxy installation detected', devProxyInstall); + return devProxyInstall; }; export const extractVersionFromOutput = (output: string): string => { diff --git a/src/documents.ts b/src/documents.ts index 58a46bc..54afb47 100644 --- a/src/documents.ts +++ b/src/documents.ts @@ -8,7 +8,7 @@ export const registerDocumentListeners = (context: vscode.ExtensionContext, coll vscode.workspace.onDidOpenTextDocument(document => { try { if (isProxyFile(document)) { - logger.debug('Proxy file opened', document.uri.fsPath); + logger.debug('Proxy file opened', { path: document.uri.fsPath }); updateFileDiagnostics(context, document, collection); vscode.commands.executeCommand('setContext', 'isDevProxyConfigFile', false); } @@ -16,7 +16,7 @@ export const registerDocumentListeners = (context: vscode.ExtensionContext, coll vscode.commands.executeCommand('setContext', 'isDevProxyConfigFile', false); return; } else { - logger.debug('Config file opened', document.uri.fsPath); + logger.debug('Config file opened', { path: document.uri.fsPath }); vscode.commands.executeCommand('setContext', 'isDevProxyConfigFile', true); updateConfigFileDiagnostics(context, document, collection); } @@ -34,13 +34,13 @@ export const registerDocumentListeners = (context: vscode.ExtensionContext, coll return; } if (isConfigFile(event.document)) { - logger.debug('Config file changed', event.document.uri.fsPath); + logger.debug('Config file changed', { path: event.document.uri.fsPath }); updateConfigFileDiagnostics(context, event.document, collection); vscode.commands.executeCommand('setContext', 'isDevProxyConfigFile', true); return; } if (isProxyFile(event.document)) { - logger.debug('Proxy file changed', event.document.uri.fsPath); + logger.debug('Proxy file changed', { path: event.document.uri.fsPath }); updateFileDiagnostics(context, event.document, collection); vscode.commands.executeCommand('setContext', 'isDevProxyConfigFile', false); } From a4cfae43fbbec295629899f54fe0b2b193c761be Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 27 Feb 2026 21:15:22 +0000 Subject: [PATCH 4/6] Add changelog and readme updates for debug logging feature Co-authored-by: garrytrinder <11563347+garrytrinder@users.noreply.github.com> --- CHANGELOG.md | 1 + README.md | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 13b8ec8..45b0901 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Install: Added automated install and upgrade support for Linux using official setup scripts - Notification: Detect outdated Dev Proxy config files in workspace and show warning when schema versions don't match installed version - Command: `dev-proxy-toolkit.upgrade-configs` - Upgrade config files with Copilot Chat using Dev Proxy MCP tools +- Logging: Added debug logging to Output panel (`Dev Proxy Toolkit`) to help investigate issues - Quick Fixes: Enable local language model fix now adds or updates `languageModel.enabled: true` for supported plugins only - Workspace: Added automatic prompt to recommend extension in `.vscode/extensions.json` when Dev Proxy config files are detected - Command: Added `Add to Workspace Recommendations` to manually add extension to workspace recommendations diff --git a/README.md b/README.md index 3bcab6a..c1fa26f 100644 --- a/README.md +++ b/README.md @@ -266,6 +266,10 @@ This extension includes an MCP server for AI-assisted development. See [Dev Prox ## Troubleshooting +**View debug logs** +- Open the Output panel (`View > Output`) and select **Dev Proxy Toolkit** from the dropdown to see extension logs +- Use the log level setting to control verbosity (trace, debug, info, warning, error) + **Dev Proxy not detected?** - Ensure Dev Proxy is installed and available in your PATH - Check the `dev-proxy-toolkit.version` setting if you have both stable and beta installed From 5bed79e8285ec06a066b579f3391ac209d47b6e9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 27 Feb 2026 21:51:19 +0000 Subject: [PATCH 5/6] Fix failing tests: wrap logger calls in try-catch for closed channel Co-authored-by: garrytrinder <11563347+garrytrinder@users.noreply.github.com> --- src/logger.ts | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/logger.ts b/src/logger.ts index ca95fb8..d247719 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -28,33 +28,43 @@ function getLogger(): vscode.LogOutputChannel | undefined { * Log a trace-level message. */ export function trace(message: string, ...args: unknown[]): void { - getLogger()?.trace(message, ...args); + try { + getLogger()?.trace(message, ...args); + } catch { /* channel may be closed */ } } /** * Log a debug-level message. */ export function debug(message: string, ...args: unknown[]): void { - getLogger()?.debug(message, ...args); + try { + getLogger()?.debug(message, ...args); + } catch { /* channel may be closed */ } } /** * Log an info-level message. */ export function info(message: string, ...args: unknown[]): void { - getLogger()?.info(message, ...args); + try { + getLogger()?.info(message, ...args); + } catch { /* channel may be closed */ } } /** * Log a warning-level message. */ export function warn(message: string, ...args: unknown[]): void { - getLogger()?.warn(message, ...args); + try { + getLogger()?.warn(message, ...args); + } catch { /* channel may be closed */ } } /** * Log an error-level message. */ export function error(message: string, ...args: unknown[]): void { - getLogger()?.error(message, ...args); + try { + getLogger()?.error(message, ...args); + } catch { /* channel may be closed */ } } From 5ed337809b97613dc8b72be8b263b3f1298a2f5d Mon Sep 17 00:00:00 2001 From: Garry Trinder Date: Tue, 3 Mar 2026 11:09:22 +0000 Subject: [PATCH 6/6] Expand logging coverage across entire extension - Add leveled logging to all commands, services, utilities, and providers - Add diagnostic detail logging with severity, line number, and message - Fix log flooding from repeated running state checks (only log on change) - Fix feedback loop from Output Channel document events triggering diagnostics - Promote key startup logs from debug to info/warn level - Update CHANGELOG to reflect expanded logging scope and bug fixes --- CHANGELOG.md | 5 ++- src/code-actions.ts | 6 ++- src/code-lens.ts | 73 +++++++++++++++++--------------- src/commands/config.ts | 7 +++ src/commands/discovery.ts | 3 ++ src/commands/docs.ts | 6 +++ src/commands/install.ts | 14 ++++++ src/commands/jwt.ts | 5 +++ src/commands/proxy.ts | 6 ++- src/commands/recording.ts | 20 ++++++--- src/detect.ts | 20 ++++++--- src/diagnostics.ts | 20 +++++++++ src/documents.ts | 6 +++ src/notifications.ts | 6 +-- src/services/api-client.ts | 7 ++- src/services/schema-validator.ts | 1 + src/services/terminal.ts | 6 +++ src/state.ts | 2 +- src/utils/config-detection.ts | 7 ++- src/utils/shell.ts | 15 ++++++- 20 files changed, 176 insertions(+), 59 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 45b0901..5642421 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Install: Added automated install and upgrade support for Linux using official setup scripts - Notification: Detect outdated Dev Proxy config files in workspace and show warning when schema versions don't match installed version - Command: `dev-proxy-toolkit.upgrade-configs` - Upgrade config files with Copilot Chat using Dev Proxy MCP tools -- Logging: Added debug logging to Output panel (`Dev Proxy Toolkit`) to help investigate issues +- Logging: Added leveled logging to Output panel (`Dev Proxy Toolkit`) across the entire extension covering commands, diagnostics, services, and utilities to help investigate issues - Quick Fixes: Enable local language model fix now adds or updates `languageModel.enabled: true` for supported plugins only - Workspace: Added automatic prompt to recommend extension in `.vscode/extensions.json` when Dev Proxy config files are detected - Command: Added `Add to Workspace Recommendations` to manually add extension to workspace recommendations @@ -24,6 +24,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed: +- Logging: Fixed log flooding from repeated running state checks by only logging on state changes +- Logging: Fixed log feedback loop caused by Output Channel document events triggering diagnostics + - Diagnostics: Language model diagnostic now correctly targets plugins that can use a local language model (OpenAIMockResponsePlugin, OpenApiSpecGeneratorPlugin, TypeSpecGeneratorPlugin) and shows as an informational hint instead of a warning ## [1.12.0] - 2026-01-29 diff --git a/src/code-actions.ts b/src/code-actions.ts index f88afc6..f4f21c0 100644 --- a/src/code-actions.ts +++ b/src/code-actions.ts @@ -4,6 +4,7 @@ import parse from 'json-to-ast'; import { getASTNode, getRangeFromASTNode } from './utils'; import { pluginSnippets } from './data'; import snippetsJson from './snippets/json-snippets.json'; +import * as logger from './logger'; /** * Extract the diagnostic code value from the object format. @@ -60,6 +61,7 @@ export const registerCodeActions = (context: vscode.ExtensionContext) => { context.globalState.get('devProxyInstall'); if (!devProxyInstall) { + logger.debug('Dev Proxy install not found, code actions disabled'); return; } @@ -124,8 +126,8 @@ export function extractSchemaFilename(schemaUrl: string): string { if (match) { return match[1]; } - } catch { - // Fall through to default + } catch (error) { + logger.warn('Failed to extract schema filename, using default', { schemaUrl, error }); } return defaultSchema; diff --git a/src/code-lens.ts b/src/code-lens.ts index d1b5ade..f452ea8 100644 --- a/src/code-lens.ts +++ b/src/code-lens.ts @@ -2,6 +2,7 @@ import * as vscode from 'vscode'; import { isConfigFile, getASTNode, getRangeFromASTNode } from './utils'; import parse from 'json-to-ast'; import { pluginSnippets } from './data'; +import * as logger from './logger'; export const registerCodeLens = (context: vscode.ExtensionContext) => { context.subscriptions.push( @@ -31,44 +32,48 @@ export const pluginLensProvider: vscode.CodeLensProvider = { export const createCodeLensForPluginNodes = (document: vscode.TextDocument) => { const codeLens: vscode.CodeLens[] = []; if (isConfigFile(document)) { - const documentNode = parse(document.getText()) as parse.ObjectNode; - const pluginsNode = getASTNode( - documentNode.children, - 'Identifier', - 'plugins' - ); + try { + const documentNode = parse(document.getText()) as parse.ObjectNode; + const pluginsNode = getASTNode( + documentNode.children, + 'Identifier', + 'plugins' + ); - if ( - pluginsNode && - (pluginsNode.value as parse.ArrayNode).children.length !== 0 - ) { - const pluginNodes = (pluginsNode.value as parse.ArrayNode) - .children as parse.ObjectNode[]; + if ( + pluginsNode && + (pluginsNode.value as parse.ArrayNode).children.length !== 0 + ) { + const pluginNodes = (pluginsNode.value as parse.ArrayNode) + .children as parse.ObjectNode[]; - pluginNodes.forEach((pluginNode: parse.ObjectNode) => { - const pluginNameNode = getASTNode( - pluginNode.children, - 'Identifier', - 'name' - ); - if (!pluginNameNode) { - return; - } - const pluginName = (pluginNameNode?.value as parse.LiteralNode) - .value as string; + pluginNodes.forEach((pluginNode: parse.ObjectNode) => { + const pluginNameNode = getASTNode( + pluginNode.children, + 'Identifier', + 'name' + ); + if (!pluginNameNode) { + return; + } + const pluginName = (pluginNameNode?.value as parse.LiteralNode) + .value as string; - const isValidName = pluginSnippets[pluginName]; + const isValidName = pluginSnippets[pluginName]; - if (isValidName) { - codeLens.push( - new vscode.CodeLens(getRangeFromASTNode(pluginNameNode), { - title: `📄 ${pluginName}`, - command: 'dev-proxy-toolkit.openPluginDoc', - arguments: [pluginName], - }) - ); - } - }); + if (isValidName) { + codeLens.push( + new vscode.CodeLens(getRangeFromASTNode(pluginNameNode), { + title: `📄 ${pluginName}`, + command: 'dev-proxy-toolkit.openPluginDoc', + arguments: [pluginName], + }) + ); + } + }); + } + } catch (error) { + logger.warn('Failed to parse config file for code lens generation', { file: document.fileName, error }); } } diff --git a/src/commands/config.ts b/src/commands/config.ts index 0c7414c..4af5c5d 100644 --- a/src/commands/config.ts +++ b/src/commands/config.ts @@ -3,6 +3,7 @@ import { Commands } from '../constants'; import { executeCommand } from '../utils/shell'; import { getDevProxyExe } from '../detect'; import { VersionPreference } from '../enums'; +import * as logger from '../logger'; /** * Configuration file commands: open, create new. @@ -25,6 +26,7 @@ export function registerConfigCommands( } async function openConfig(devProxyExe: string): Promise { + logger.debug('Opening Dev Proxy config', { devProxyExe }); await executeCommand(`${devProxyExe} config open`); } @@ -36,15 +38,18 @@ async function createNewConfig(devProxyExe: string): Promise { const workspaceFolder = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath; if (!workspaceFolder) { + logger.warn('Cannot create config: no workspace folder open'); vscode.window.showErrorMessage('No workspace folder open'); return; } + logger.info('Creating new config file', { fileName, workspaceFolder }); const devProxyFolder = vscode.Uri.file(`${workspaceFolder}/.devproxy`); const configUri = vscode.Uri.file(`${workspaceFolder}/.devproxy/${fileName}`); // Check if file already exists if (await fileExists(configUri)) { + logger.warn('Config file already exists', { path: configUri.fsPath }); vscode.window.showErrorMessage('A file with that name already exists'); return; } @@ -67,9 +72,11 @@ async function createNewConfig(devProxyExe: string): Promise { ); // Open the newly created file + logger.info('Config file created', { path: configUri.fsPath }); const document = await vscode.workspace.openTextDocument(configUri); await vscode.window.showTextDocument(document); } catch (error) { + logger.error('Failed to create new config file', error); vscode.window.showErrorMessage('Failed to create new config file'); } } diff --git a/src/commands/discovery.ts b/src/commands/discovery.ts index e1154c3..6ec8498 100644 --- a/src/commands/discovery.ts +++ b/src/commands/discovery.ts @@ -3,6 +3,7 @@ import { Commands } from '../constants'; import { TerminalService } from '../services/terminal'; import { getDevProxyExe } from '../detect'; import { VersionPreference } from '../enums'; +import * as logger from '../logger'; /** * URL discovery command. @@ -40,10 +41,12 @@ async function discoverUrlsToWatch( // User cancelled if (processNames === undefined) { + logger.debug('URL discovery cancelled by user'); return; } const command = buildDiscoverCommand(devProxyExe, processNames); + logger.info('Starting URL discovery', { processNames: processNames || '(all processes)', command }); terminalService.sendCommand(terminal, command); } diff --git a/src/commands/docs.ts b/src/commands/docs.ts index ca76f84..6923ba5 100644 --- a/src/commands/docs.ts +++ b/src/commands/docs.ts @@ -3,6 +3,7 @@ import { Commands } from '../constants'; import { pluginDocs } from '../data'; import parse from 'json-to-ast'; import { getASTNode, getRangeFromASTNode } from '../utils/ast'; +import * as logger from '../logger'; /** * Documentation and language model configuration commands. @@ -21,8 +22,11 @@ export function registerDocCommands(context: vscode.ExtensionContext): void { function openPluginDocumentation(pluginName: string): void { const doc = pluginDocs[pluginName]; if (doc) { + logger.debug('Opening plugin docs', { pluginName, url: doc.url }); const target = vscode.Uri.parse(doc.url); vscode.env.openExternal(target); + } else { + logger.warn('Plugin docs not found', { pluginName }); } } @@ -42,11 +46,13 @@ async function addLanguageModelConfig(uri: vscode.Uri): Promise { } } catch (error) { // Fallback to simple text-based insertion + logger.debug('Failed to parse document with json-to-ast, using text-based fallback', error); addLanguageModelFallback(document, edit, uri); } await vscode.workspace.applyEdit(edit); await document.save(); + logger.info('Language model configuration added'); vscode.window.showInformationMessage('Language model configuration added'); } diff --git a/src/commands/install.ts b/src/commands/install.ts index 705fc2f..9993295 100644 --- a/src/commands/install.ts +++ b/src/commands/install.ts @@ -8,6 +8,7 @@ import { openUpgradeDocumentation, } from '../utils/shell'; import { PackageManager, VersionPreference } from '../enums'; +import * as logger from '../logger'; /** * Installation and upgrade commands. @@ -34,6 +35,7 @@ async function installDevProxy( ): Promise { const message = vscode.window.setStatusBarMessage('Installing Dev Proxy...'); const versionPreference = configuration.get('version') as VersionPreference; + logger.info('Installing Dev Proxy', { platform, versionPreference }); try { if (platform === 'win32') { @@ -55,17 +57,21 @@ async function installOnWindows(versionPreference: VersionPreference): Promise try { await executeCommand('brew --version'); } catch { + logger.warn('Homebrew not found on PATH'); vscode.window.showErrorMessage('Homebrew is not installed. Please install brew and try again.'); return; } try { + logger.info('Installing Dev Proxy via Homebrew', { packageId }); await executeCommand('brew tap dotnet/dev-proxy'); await executeCommand(`brew install ${packageId}`); + logger.info('Dev Proxy installed successfully via Homebrew'); const result = await vscode.window.showInformationMessage('Dev Proxy installed.', 'Reload'); if (result === 'Reload') { await vscode.commands.executeCommand('workbench.action.reloadWindow'); } } catch (error) { + logger.error('Failed to install Dev Proxy via Homebrew', error); vscode.window.showErrorMessage(`Failed to install Dev Proxy.\n${error}`); } } @@ -137,6 +147,7 @@ async function upgradeDevProxy(configuration: vscode.WorkspaceConfiguration): Pr const platform = process.platform; const versionPreference = configuration.get('version') as VersionPreference; const isBeta = versionPreference === VersionPreference.Beta; + logger.info('Upgrading Dev Proxy', { platform, versionPreference }); // Linux uses install script to upgrade if (platform === 'linux') { @@ -182,6 +193,7 @@ async function upgradeDevProxy(configuration: vscode.WorkspaceConfiguration): Pr isBeta ); if (!upgraded) { + logger.warn('Upgrade via winget failed, opening upgrade docs'); openUpgradeDocumentation(); } return; @@ -202,11 +214,13 @@ async function upgradeDevProxy(configuration: vscode.WorkspaceConfiguration): Pr isBeta ); if (!upgraded) { + logger.warn('Upgrade via Homebrew failed, opening upgrade docs'); openUpgradeDocumentation(); } return; } // Unknown platform + logger.warn('Unknown platform, opening upgrade docs', { platform }); openUpgradeDocumentation(); } diff --git a/src/commands/jwt.ts b/src/commands/jwt.ts index 973e496..823bfa9 100644 --- a/src/commands/jwt.ts +++ b/src/commands/jwt.ts @@ -3,6 +3,7 @@ import { Commands } from '../constants'; import { executeCommand } from '../utils/shell'; import { getDevProxyExe } from '../detect'; import { VersionPreference } from '../enums'; +import * as logger from '../logger'; /** * JWT (JSON Web Token) generation commands. @@ -36,8 +37,10 @@ interface JwtParams { async function createJwt(devProxyExe: string): Promise { const params = await collectJwtParams(); if (!params) { + logger.debug('JWT creation cancelled by user'); return; // User cancelled } + logger.info('Generating JWT', { name: params.name, issuer: params.issuer, audiences: params.audiences.length, roles: params.roles.length, scopes: params.scopes.length, validFor: params.validFor }); await vscode.window.withProgress( { @@ -50,8 +53,10 @@ async function createJwt(devProxyExe: string): Promise { const command = buildJwtCommand(devProxyExe, params); const result = await executeCommand(command); const token = extractToken(result); + logger.info('JWT token generated successfully'); await presentToken(token, command); } catch (error) { + logger.error('Failed to generate JWT token', error); vscode.window.showErrorMessage(`Failed to generate JWT token: ${error}`); } } diff --git a/src/commands/proxy.ts b/src/commands/proxy.ts index f0a1532..3c207d8 100644 --- a/src/commands/proxy.ts +++ b/src/commands/proxy.ts @@ -62,10 +62,12 @@ async function stopDevProxy( const closeTerminal = configuration.get('closeTerminal', true); if (closeTerminal) { + logger.debug('Waiting for Dev Proxy to stop before closing terminal'); await waitForProxyToStop(apiClient); const terminalService = TerminalService.fromConfiguration(); terminalService.disposeDevProxyTerminals(); + logger.debug('Dev Proxy terminals disposed'); } } @@ -88,7 +90,9 @@ async function restartDevProxy( : devProxyExe; terminalService.sendCommand(terminal, command); - } catch { + logger.debug('Dev Proxy restart command sent'); + } catch (error) { + logger.error('Failed to restart Dev Proxy', error); vscode.window.showErrorMessage('Failed to restart Dev Proxy'); } } diff --git a/src/commands/recording.ts b/src/commands/recording.ts index e39e504..4e9a8a6 100644 --- a/src/commands/recording.ts +++ b/src/commands/recording.ts @@ -26,9 +26,10 @@ export function registerRecordingCommands(context: vscode.ExtensionContext): voi async function startRecording(apiClient: DevProxyApiClient): Promise { try { await apiClient.startRecording(); + logger.info('Recording started'); vscode.commands.executeCommand('setContext', ContextKeys.isRecording, true); - } catch { - logger.error('Failed to start recording'); + } catch (error) { + logger.error('Failed to start recording', error); vscode.window.showErrorMessage('Failed to start recording'); } } @@ -36,14 +37,21 @@ async function startRecording(apiClient: DevProxyApiClient): Promise { async function stopRecording(apiClient: DevProxyApiClient): Promise { try { await apiClient.stopRecording(); + logger.info('Recording stopped'); vscode.commands.executeCommand('setContext', ContextKeys.isRecording, false); - } catch { - logger.error('Failed to stop recording'); + } catch (error) { + logger.error('Failed to stop recording', error); vscode.window.showErrorMessage('Failed to stop recording'); } } async function raiseMockRequest(apiClient: DevProxyApiClient): Promise { - await apiClient.raiseMockRequest(); - vscode.window.showInformationMessage('Mock request raised'); + try { + await apiClient.raiseMockRequest(); + logger.info('Mock request raised'); + vscode.window.showInformationMessage('Mock request raised'); + } catch (error) { + logger.error('Failed to raise mock request', error); + vscode.window.showErrorMessage('Failed to raise mock request'); + } } diff --git a/src/detect.ts b/src/detect.ts index 0e3630c..daad28d 100644 --- a/src/detect.ts +++ b/src/detect.ts @@ -5,21 +5,23 @@ import { executeCommand, resolveDevProxyExecutable } from './utils/shell'; import * as vscode from 'vscode'; import * as logger from './logger'; +let lastKnownRunningState: boolean | undefined; + export const getVersion = async (devProxyExe: string) => { try { const version = await executeCommand(`${devProxyExe} --version`); const versionLines = version.trim().split('\n'); const lastLine = versionLines[versionLines.length - 1]; - logger.debug('Dev Proxy version detected', lastLine.trim()); + logger.info('Dev Proxy version detected', lastLine.trim()); return lastLine.trim(); } catch (error) { - logger.debug('Failed to get Dev Proxy version', error); + logger.warn('Failed to get Dev Proxy version', error); return ""; } }; export const detectDevProxyInstall = async (versionPreference: VersionPreference): Promise => { - logger.debug('Detecting Dev Proxy installation', { versionPreference }); + logger.info('Detecting Dev Proxy installation', { versionPreference }); const configuration = vscode.workspace.getConfiguration('dev-proxy-toolkit'); const customPath = configuration.get('devProxyPath'); const exeName = getDevProxyExe(versionPreference); @@ -41,7 +43,7 @@ export const detectDevProxyInstall = async (versionPreference: VersionPreference isOutdated, isRunning }; - logger.debug('Dev Proxy installation detected', devProxyInstall); + logger.info('Dev Proxy installation detected', devProxyInstall); return devProxyInstall; }; @@ -98,11 +100,17 @@ export const isDevProxyRunning = async (devProxyExe: string): Promise = // If we get any response (even an error), Dev Proxy is running const running = response.status >= 200 && response.status < 500; - logger.debug('Dev Proxy running check', { running, status: response.status }); + if (running !== lastKnownRunningState) { + logger.info('Dev Proxy running state changed', { running, status: response.status }); + lastKnownRunningState = running; + } return running; } catch (error) { // If the request fails (connection refused, timeout, etc.), Dev Proxy is not running - logger.debug('Dev Proxy is not running'); + if (lastKnownRunningState !== false) { + logger.info('Dev Proxy is not running'); + lastKnownRunningState = false; + } return false; } }; diff --git a/src/diagnostics.ts b/src/diagnostics.ts index 2bade85..cc23ea2 100644 --- a/src/diagnostics.ts +++ b/src/diagnostics.ts @@ -9,6 +9,14 @@ import * as semver from 'semver'; import { fetchSchema, validateAgainstSchema } from './services'; import * as logger from './logger'; +const logDiagnostics = (diagnostics: vscode.Diagnostic[]) => { + for (const d of diagnostics) { + const severity = ['Error', 'Warning', 'Information', 'Hint'][d.severity]; + const line = d.range.start.line + 1; + logger.info(`[${severity}] Line ${line}: ${d.message}`); + } +}; + export const updateConfigFileDiagnostics = async ( context: vscode.ExtensionContext, document: vscode.TextDocument, @@ -17,8 +25,10 @@ export const updateConfigFileDiagnostics = async ( const devProxyInstall = context.globalState.get('devProxyInstall'); if (!devProxyInstall) { + logger.warn('No Dev Proxy install found, skipping config diagnostics'); return; } + logger.info('Running config diagnostics', { file: document.fileName }); const diagnostics: vscode.Diagnostic[] = []; const documentNode = getObjectNodeFromDocument(document); const pluginsNode = getPluginsNode(documentNode); @@ -32,10 +42,14 @@ export const updateConfigFileDiagnostics = async ( // Set initial diagnostics synchronously collection.set(document.uri, diagnostics); + logger.info('Config diagnostics complete', { file: document.fileName, count: diagnostics.length }); + logDiagnostics(diagnostics); // Run async schema content validation and update diagnostics const asyncDiagnostics = await validateConfigSectionContents(document, documentNode); if (asyncDiagnostics.length > 0) { + logger.info('Async schema validation found issues', { file: document.fileName, count: asyncDiagnostics.length }); + logDiagnostics(asyncDiagnostics); collection.set(document.uri, [...diagnostics, ...asyncDiagnostics]); } }; @@ -48,8 +62,10 @@ export const updateFileDiagnostics = ( const devProxyInstall = context.globalState.get('devProxyInstall'); if (!devProxyInstall) { + logger.warn('No Dev Proxy install found, skipping file diagnostics'); return; } + logger.info('Running file diagnostics', { file: document.fileName }); const diagnostics: vscode.Diagnostic[] = []; const documentNode = getObjectNodeFromDocument(document); @@ -57,6 +73,8 @@ export const updateFileDiagnostics = ( checkSchemaCompatibility(documentNode, devProxyInstall, diagnostics); collection.set(document.uri, diagnostics); + logger.info('File diagnostics complete', { file: document.fileName, count: diagnostics.length }); + logDiagnostics(diagnostics); }; const checkConfigSection = ( @@ -120,6 +138,7 @@ const checkSchemaCompatibility = ( const devProxyVersion = devProxyInstall.isBeta ? devProxyInstall.version.split('-')[0] : devProxyInstall.version; + logger.debug('Checking schema compatibility', { schema: schemaValue, installedVersion: devProxyVersion }); if (!schemaValue.includes(`${devProxyVersion}`)) { const diagnostic = new vscode.Diagnostic( getRangeFromASTNode(schemaValueNode), @@ -141,6 +160,7 @@ const checkPlugins = ( if (pluginsNode && (pluginsNode.value as parse.ArrayNode)) { const pluginNodes = (pluginsNode.value as parse.ArrayNode) .children as parse.ObjectNode[]; + logger.debug('Checking plugins', { count: pluginNodes.length }); checkAtLeastOneEnabledPlugin(pluginNodes, diagnostics, pluginsNode); warnOnReporterPosition(pluginNodes, diagnostics); diff --git a/src/documents.ts b/src/documents.ts index 54afb47..41890e5 100644 --- a/src/documents.ts +++ b/src/documents.ts @@ -6,6 +6,9 @@ import * as logger from './logger'; export const registerDocumentListeners = (context: vscode.ExtensionContext, collection: vscode.DiagnosticCollection) => { context.subscriptions.push( vscode.workspace.onDidOpenTextDocument(document => { + if (document.uri.scheme !== 'file') { + return; + } try { if (isProxyFile(document)) { logger.debug('Proxy file opened', { path: document.uri.fsPath }); @@ -28,6 +31,9 @@ export const registerDocumentListeners = (context: vscode.ExtensionContext, coll context.subscriptions.push( vscode.workspace.onDidChangeTextDocument(event => { + if (event.document.uri.scheme !== 'file') { + return; + } try { if (!isConfigFile(event.document) && !isProxyFile(event.document)) { collection.delete(event.document.uri); diff --git a/src/notifications.ts b/src/notifications.ts index 9a80451..4b6c504 100644 --- a/src/notifications.ts +++ b/src/notifications.ts @@ -7,7 +7,7 @@ import * as logger from './logger'; export const handleStartNotification = (context: vscode.ExtensionContext) => { const devProxyInstall = context.globalState.get('devProxyInstall'); if (!devProxyInstall) { - logger.debug('Dev Proxy install not found in global state'); + logger.warn('Dev Proxy install not found in global state'); return () => { const message = `Dev Proxy is not installed, or not in PATH.`; return { @@ -22,7 +22,7 @@ export const handleStartNotification = (context: vscode.ExtensionContext) => { }; }; if (!devProxyInstall.isInstalled) { - logger.debug('Dev Proxy is not installed'); + logger.warn('Dev Proxy is not installed'); return () => { const message = `Dev Proxy is not installed, or not in PATH.`; return { @@ -37,7 +37,7 @@ export const handleStartNotification = (context: vscode.ExtensionContext) => { }; }; if (devProxyInstall.isOutdated) { - logger.debug('Dev Proxy is outdated', { current: devProxyInstall.version, available: devProxyInstall.outdatedVersion }); + logger.warn('Dev Proxy is outdated', { current: devProxyInstall.version, available: devProxyInstall.outdatedVersion }); return () => { const message = `New Dev Proxy version ${devProxyInstall.outdatedVersion} is available.`; return { diff --git a/src/services/api-client.ts b/src/services/api-client.ts index af967f6..a9544ad 100644 --- a/src/services/api-client.ts +++ b/src/services/api-client.ts @@ -41,7 +41,8 @@ export class DevProxyApiClient { signal: AbortSignal.timeout(2000), }); return response.status >= 200 && response.status < 500; - } catch { + } catch (error) { + logger.debug('Dev Proxy API unreachable', error); return false; } } @@ -88,10 +89,12 @@ export class DevProxyApiClient { signal: AbortSignal.timeout(this.timeout), }); if (!response.ok) { + logger.debug('Failed to get proxy status', { status: response.status }); return null; } return (await response.json()) as ProxyStatus; - } catch { + } catch (error) { + logger.debug('Failed to get proxy status', error); return null; } } diff --git a/src/services/schema-validator.ts b/src/services/schema-validator.ts index 1c3bfd3..0628dd9 100644 --- a/src/services/schema-validator.ts +++ b/src/services/schema-validator.ts @@ -49,6 +49,7 @@ export interface SchemaValidationError { export async function fetchSchema(schemaUrl: string): Promise { // Check cache first if (schemaCache.has(schemaUrl)) { + logger.debug('Schema loaded from cache', { schemaUrl }); return schemaCache.get(schemaUrl); } diff --git a/src/services/terminal.ts b/src/services/terminal.ts index 2d9e6a5..0f41939 100644 --- a/src/services/terminal.ts +++ b/src/services/terminal.ts @@ -1,4 +1,5 @@ import * as vscode from 'vscode'; +import * as logger from '../logger'; /** * Service for managing terminal instances used by Dev Proxy. @@ -38,9 +39,11 @@ export class TerminalService { */ getOrCreateTerminal(): vscode.Terminal { if (!this.createNewTerminal && vscode.window.activeTerminal) { + logger.debug('Reusing active terminal', { name: vscode.window.activeTerminal.name }); return vscode.window.activeTerminal; } + logger.debug('Creating new Dev Proxy terminal'); const terminal = vscode.window.createTerminal('Dev Proxy'); if (this.showTerminal) { @@ -57,11 +60,13 @@ export class TerminalService { */ disposeDevProxyTerminals(): void { if (!this.closeTerminalOnStop) { + logger.debug('closeTerminal disabled, preserving Dev Proxy terminals'); return; } vscode.window.terminals.forEach(terminal => { if (terminal.name === 'Dev Proxy') { + logger.debug('Disposing Dev Proxy terminal'); terminal.dispose(); } }); @@ -71,6 +76,7 @@ export class TerminalService { * Send a command to a terminal. */ sendCommand(terminal: vscode.Terminal, command: string): void { + logger.debug('Sending command to terminal', { terminal: terminal.name, command }); terminal.sendText(command); } } diff --git a/src/state.ts b/src/state.ts index fc9480f..9f4f367 100644 --- a/src/state.ts +++ b/src/state.ts @@ -8,5 +8,5 @@ export const updateGlobalState = async (context: vscode.ExtensionContext, versio const devProxyInstall = await detectDevProxyInstall(versionPreference); vscode.commands.executeCommand('setContext', 'isDevProxyInstalled', devProxyInstall.isInstalled); context.globalState.update('devProxyInstall', devProxyInstall); - logger.debug('Global state updated', { isInstalled: devProxyInstall.isInstalled, version: devProxyInstall.version }); + logger.info('Global state updated', { isInstalled: devProxyInstall.isInstalled, version: devProxyInstall.version }); }; \ No newline at end of file diff --git a/src/utils/config-detection.ts b/src/utils/config-detection.ts index 105ee9e..db91a1f 100644 --- a/src/utils/config-detection.ts +++ b/src/utils/config-detection.ts @@ -1,5 +1,6 @@ import * as vscode from 'vscode'; import parse from 'json-to-ast'; +import * as logger from '../logger'; /** * Find a property node by its key type and value. @@ -62,7 +63,8 @@ export function isConfigFile(document: vscode.TextDocument): boolean { } return false; - } catch { + } catch (error) { + logger.debug('Failed to parse document for config file detection', { file: document.fileName, error }); return false; } } @@ -83,7 +85,8 @@ export function isProxyFile(document: vscode.TextDocument): boolean { } return false; - } catch { + } catch (error) { + logger.debug('Failed to parse document for proxy file detection', { file: document.fileName, error }); return false; } } diff --git a/src/utils/shell.ts b/src/utils/shell.ts index bd00e70..398c5f1 100644 --- a/src/utils/shell.ts +++ b/src/utils/shell.ts @@ -9,6 +9,7 @@ import { VersionPreference, WingetPackageIdentifier, } from '../enums'; +import * as logger from '../logger'; /** * Utility functions for shell execution and package management. @@ -81,6 +82,7 @@ export async function upgradeDevProxyWithPackageManager( try { // Check if package manager is available await executeCommand(`${packageManager} --version`); + logger.debug('Package manager available', { packageManager }); // Check if Dev Proxy is installed via package manager const listCommand = @@ -88,6 +90,7 @@ export async function upgradeDevProxyWithPackageManager( const listOutput = await executeCommand(listCommand); if (!listOutput.includes(packageId)) { + logger.warn('Dev Proxy not found in package manager', { packageManager, packageId }); return false; } @@ -111,6 +114,7 @@ export async function upgradeDevProxyWithPackageManager( try { await executeCommand(upgradeCommand); statusMessage.dispose(); + logger.info('Dev Proxy upgraded successfully', { packageManager }); const result = await vscode.window.showInformationMessage( `${versionText} has been successfully upgraded!`, @@ -122,10 +126,12 @@ export async function upgradeDevProxyWithPackageManager( return true; } catch (error) { statusMessage.dispose(); + logger.error('Failed to upgrade Dev Proxy', { packageManager, error }); vscode.window.showErrorMessage(`Failed to upgrade ${versionText}: ${error}`); return false; } } catch { + logger.warn('Package manager not available for upgrade', { packageManager }); return false; } } @@ -164,13 +170,16 @@ export async function resolveDevProxyExecutable( ): Promise { // 1. Use custom path if provided if (customPath && customPath.trim() !== '') { + logger.info('Using custom Dev Proxy path from settings', { customPath: customPath.trim() }); return customPath.trim(); } // 2. Try bare command first if (await canExecute(exeName)) { + logger.info('Dev Proxy found on PATH', { exeName }); return exeName; } + logger.debug('Dev Proxy not found on PATH, trying alternatives', { exeName }); const platform = os.platform(); @@ -178,6 +187,7 @@ export async function resolveDevProxyExecutable( if (platform !== 'win32') { const loginShellPath = await tryLoginShell(exeName); if (loginShellPath) { + logger.info('Dev Proxy found via login shell', { path: loginShellPath }); return loginShellPath; } } @@ -187,11 +197,13 @@ export async function resolveDevProxyExecutable( for (const dir of commonPaths) { const fullPath = `${dir}/${exeName}`; if (fs.existsSync(fullPath)) { + logger.info('Dev Proxy found at common path', { path: fullPath }); return fullPath; } } // Fallback to bare command (will fail gracefully in getVersion) + logger.warn('Dev Proxy executable not found, falling back to bare command', { exeName }); return exeName; } @@ -220,13 +232,14 @@ async function tryLoginShell(exeName: string): Promise { } try { + logger.debug('Trying login shell', { shell, exeName }); const result = await executeCommand(`${shell} -l -c "which ${exeName}"`); const path = result.trim(); if (path && !path.includes('not found')) { return path; } } catch { - // Shell failed, try next + logger.debug('Login shell did not find executable', { shell, exeName }); } }