Skip to content

Commit f728a62

Browse files
Add output logging (#350)
* Initial plan * 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> * Address code review: use structured logging, rename variable, avoid exposing full paths Co-authored-by: garrytrinder <11563347+garrytrinder@users.noreply.github.com> * Add changelog and readme updates for debug logging feature Co-authored-by: garrytrinder <11563347+garrytrinder@users.noreply.github.com> * Fix failing tests: wrap logger calls in try-catch for closed channel Co-authored-by: garrytrinder <11563347+garrytrinder@users.noreply.github.com> * 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 --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: garrytrinder <11563347+garrytrinder@users.noreply.github.com> Co-authored-by: Garry Trinder <garry@trinder365.co.uk>
1 parent c9dcb3b commit f728a62

27 files changed

Lines changed: 344 additions & 55 deletions

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1414
- Install: Added automated install and upgrade support for Linux using official setup scripts
1515
- Notification: Detect outdated Dev Proxy config files in workspace and show warning when schema versions don't match installed version
1616
- Command: `dev-proxy-toolkit.upgrade-configs` - Upgrade config files with Copilot Chat using Dev Proxy MCP tools
17+
- Logging: Added leveled logging to Output panel (`Dev Proxy Toolkit`) across the entire extension covering commands, diagnostics, services, and utilities to help investigate issues
1718
- Quick Fixes: Enable local language model fix now adds or updates `languageModel.enabled: true` for supported plugins only
1819
- Workspace: Added automatic prompt to recommend extension in `.vscode/extensions.json` when Dev Proxy config files are detected
1920
- Command: Added `Add to Workspace Recommendations` to manually add extension to workspace recommendations
@@ -23,6 +24,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2324

2425
### Fixed:
2526

27+
- Logging: Fixed log flooding from repeated running state checks by only logging on state changes
28+
- Logging: Fixed log feedback loop caused by Output Channel document events triggering diagnostics
29+
2630
- 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
2731

2832
## [1.12.0] - 2026-01-29

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,10 @@ This extension includes an MCP server for AI-assisted development. See [Dev Prox
266266

267267
## Troubleshooting
268268

269+
**View debug logs**
270+
- Open the Output panel (`View > Output`) and select **Dev Proxy Toolkit** from the dropdown to see extension logs
271+
- Use the log level setting to control verbosity (trace, debug, info, warning, error)
272+
269273
**Dev Proxy not detected?**
270274
- Ensure Dev Proxy is installed and available in your PATH
271275
- Check the `dev-proxy-toolkit.version` setting if you have both stable and beta installed

src/code-actions.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import parse from 'json-to-ast';
44
import { getASTNode, getRangeFromASTNode } from './utils';
55
import { pluginSnippets } from './data';
66
import snippetsJson from './snippets/json-snippets.json';
7+
import * as logger from './logger';
78

89
/**
910
* Extract the diagnostic code value from the object format.
@@ -60,6 +61,7 @@ export const registerCodeActions = (context: vscode.ExtensionContext) => {
6061
context.globalState.get<DevProxyInstall>('devProxyInstall');
6162

6263
if (!devProxyInstall) {
64+
logger.debug('Dev Proxy install not found, code actions disabled');
6365
return;
6466
}
6567

@@ -124,8 +126,8 @@ export function extractSchemaFilename(schemaUrl: string): string {
124126
if (match) {
125127
return match[1];
126128
}
127-
} catch {
128-
// Fall through to default
129+
} catch (error) {
130+
logger.warn('Failed to extract schema filename, using default', { schemaUrl, error });
129131
}
130132

131133
return defaultSchema;

src/code-lens.ts

Lines changed: 39 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import * as vscode from 'vscode';
22
import { isConfigFile, getASTNode, getRangeFromASTNode } from './utils';
33
import parse from 'json-to-ast';
44
import { pluginSnippets } from './data';
5+
import * as logger from './logger';
56

67
export const registerCodeLens = (context: vscode.ExtensionContext) => {
78
context.subscriptions.push(
@@ -31,44 +32,48 @@ export const pluginLensProvider: vscode.CodeLensProvider = {
3132
export const createCodeLensForPluginNodes = (document: vscode.TextDocument) => {
3233
const codeLens: vscode.CodeLens[] = [];
3334
if (isConfigFile(document)) {
34-
const documentNode = parse(document.getText()) as parse.ObjectNode;
35-
const pluginsNode = getASTNode(
36-
documentNode.children,
37-
'Identifier',
38-
'plugins'
39-
);
35+
try {
36+
const documentNode = parse(document.getText()) as parse.ObjectNode;
37+
const pluginsNode = getASTNode(
38+
documentNode.children,
39+
'Identifier',
40+
'plugins'
41+
);
4042

41-
if (
42-
pluginsNode &&
43-
(pluginsNode.value as parse.ArrayNode).children.length !== 0
44-
) {
45-
const pluginNodes = (pluginsNode.value as parse.ArrayNode)
46-
.children as parse.ObjectNode[];
43+
if (
44+
pluginsNode &&
45+
(pluginsNode.value as parse.ArrayNode).children.length !== 0
46+
) {
47+
const pluginNodes = (pluginsNode.value as parse.ArrayNode)
48+
.children as parse.ObjectNode[];
4749

48-
pluginNodes.forEach((pluginNode: parse.ObjectNode) => {
49-
const pluginNameNode = getASTNode(
50-
pluginNode.children,
51-
'Identifier',
52-
'name'
53-
);
54-
if (!pluginNameNode) {
55-
return;
56-
}
57-
const pluginName = (pluginNameNode?.value as parse.LiteralNode)
58-
.value as string;
50+
pluginNodes.forEach((pluginNode: parse.ObjectNode) => {
51+
const pluginNameNode = getASTNode(
52+
pluginNode.children,
53+
'Identifier',
54+
'name'
55+
);
56+
if (!pluginNameNode) {
57+
return;
58+
}
59+
const pluginName = (pluginNameNode?.value as parse.LiteralNode)
60+
.value as string;
5961

60-
const isValidName = pluginSnippets[pluginName];
62+
const isValidName = pluginSnippets[pluginName];
6163

62-
if (isValidName) {
63-
codeLens.push(
64-
new vscode.CodeLens(getRangeFromASTNode(pluginNameNode), {
65-
title: `📄 ${pluginName}`,
66-
command: 'dev-proxy-toolkit.openPluginDoc',
67-
arguments: [pluginName],
68-
})
69-
);
70-
}
71-
});
64+
if (isValidName) {
65+
codeLens.push(
66+
new vscode.CodeLens(getRangeFromASTNode(pluginNameNode), {
67+
title: `📄 ${pluginName}`,
68+
command: 'dev-proxy-toolkit.openPluginDoc',
69+
arguments: [pluginName],
70+
})
71+
);
72+
}
73+
});
74+
}
75+
} catch (error) {
76+
logger.warn('Failed to parse config file for code lens generation', { file: document.fileName, error });
7277
}
7378
}
7479

src/commands/config.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { Commands } from '../constants';
33
import { executeCommand } from '../utils/shell';
44
import { getDevProxyExe } from '../detect';
55
import { VersionPreference } from '../enums';
6+
import * as logger from '../logger';
67

78
/**
89
* Configuration file commands: open, create new.
@@ -25,6 +26,7 @@ export function registerConfigCommands(
2526
}
2627

2728
async function openConfig(devProxyExe: string): Promise<void> {
29+
logger.debug('Opening Dev Proxy config', { devProxyExe });
2830
await executeCommand(`${devProxyExe} config open`);
2931
}
3032

@@ -36,15 +38,18 @@ async function createNewConfig(devProxyExe: string): Promise<void> {
3638

3739
const workspaceFolder = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath;
3840
if (!workspaceFolder) {
41+
logger.warn('Cannot create config: no workspace folder open');
3942
vscode.window.showErrorMessage('No workspace folder open');
4043
return;
4144
}
45+
logger.info('Creating new config file', { fileName, workspaceFolder });
4246

4347
const devProxyFolder = vscode.Uri.file(`${workspaceFolder}/.devproxy`);
4448
const configUri = vscode.Uri.file(`${workspaceFolder}/.devproxy/${fileName}`);
4549

4650
// Check if file already exists
4751
if (await fileExists(configUri)) {
52+
logger.warn('Config file already exists', { path: configUri.fsPath });
4853
vscode.window.showErrorMessage('A file with that name already exists');
4954
return;
5055
}
@@ -67,9 +72,11 @@ async function createNewConfig(devProxyExe: string): Promise<void> {
6772
);
6873

6974
// Open the newly created file
75+
logger.info('Config file created', { path: configUri.fsPath });
7076
const document = await vscode.workspace.openTextDocument(configUri);
7177
await vscode.window.showTextDocument(document);
7278
} catch (error) {
79+
logger.error('Failed to create new config file', error);
7380
vscode.window.showErrorMessage('Failed to create new config file');
7481
}
7582
}

src/commands/discovery.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { Commands } from '../constants';
33
import { TerminalService } from '../services/terminal';
44
import { getDevProxyExe } from '../detect';
55
import { VersionPreference } from '../enums';
6+
import * as logger from '../logger';
67

78
/**
89
* URL discovery command.
@@ -40,10 +41,12 @@ async function discoverUrlsToWatch(
4041

4142
// User cancelled
4243
if (processNames === undefined) {
44+
logger.debug('URL discovery cancelled by user');
4345
return;
4446
}
4547

4648
const command = buildDiscoverCommand(devProxyExe, processNames);
49+
logger.info('Starting URL discovery', { processNames: processNames || '(all processes)', command });
4750
terminalService.sendCommand(terminal, command);
4851
}
4952

src/commands/docs.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { Commands } from '../constants';
33
import { pluginDocs } from '../data';
44
import parse from 'json-to-ast';
55
import { getASTNode, getRangeFromASTNode } from '../utils/ast';
6+
import * as logger from '../logger';
67

78
/**
89
* Documentation and language model configuration commands.
@@ -21,8 +22,11 @@ export function registerDocCommands(context: vscode.ExtensionContext): void {
2122
function openPluginDocumentation(pluginName: string): void {
2223
const doc = pluginDocs[pluginName];
2324
if (doc) {
25+
logger.debug('Opening plugin docs', { pluginName, url: doc.url });
2426
const target = vscode.Uri.parse(doc.url);
2527
vscode.env.openExternal(target);
28+
} else {
29+
logger.warn('Plugin docs not found', { pluginName });
2630
}
2731
}
2832

@@ -42,11 +46,13 @@ async function addLanguageModelConfig(uri: vscode.Uri): Promise<void> {
4246
}
4347
} catch (error) {
4448
// Fallback to simple text-based insertion
49+
logger.debug('Failed to parse document with json-to-ast, using text-based fallback', error);
4550
addLanguageModelFallback(document, edit, uri);
4651
}
4752

4853
await vscode.workspace.applyEdit(edit);
4954
await document.save();
55+
logger.info('Language model configuration added');
5056

5157
vscode.window.showInformationMessage('Language model configuration added');
5258
}

src/commands/install.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
openUpgradeDocumentation,
99
} from '../utils/shell';
1010
import { PackageManager, VersionPreference } from '../enums';
11+
import * as logger from '../logger';
1112

1213
/**
1314
* Installation and upgrade commands.
@@ -34,6 +35,7 @@ async function installDevProxy(
3435
): Promise<void> {
3536
const message = vscode.window.setStatusBarMessage('Installing Dev Proxy...');
3637
const versionPreference = configuration.get('version') as VersionPreference;
38+
logger.info('Installing Dev Proxy', { platform, versionPreference });
3739

3840
try {
3941
if (platform === 'win32') {
@@ -55,17 +57,21 @@ async function installOnWindows(versionPreference: VersionPreference): Promise<v
5557
try {
5658
await executeCommand('winget --version');
5759
} catch {
60+
logger.warn('Winget not found on PATH');
5861
vscode.window.showErrorMessage('Winget is not installed. Please install winget and try again.');
5962
return;
6063
}
6164

6265
try {
66+
logger.info('Installing Dev Proxy via winget', { packageId });
6367
await executeCommand(`winget install ${packageId} --silent`);
68+
logger.info('Dev Proxy installed successfully via winget');
6469
const result = await vscode.window.showInformationMessage('Dev Proxy installed.', 'Reload');
6570
if (result === 'Reload') {
6671
await vscode.commands.executeCommand('workbench.action.reloadWindow');
6772
}
6873
} catch (error) {
74+
logger.error('Failed to install Dev Proxy via winget', error);
6975
vscode.window.showErrorMessage(`Failed to install Dev Proxy.\n${error}`);
7076
}
7177
}
@@ -77,18 +83,22 @@ async function installOnMac(versionPreference: VersionPreference): Promise<void>
7783
try {
7884
await executeCommand('brew --version');
7985
} catch {
86+
logger.warn('Homebrew not found on PATH');
8087
vscode.window.showErrorMessage('Homebrew is not installed. Please install brew and try again.');
8188
return;
8289
}
8390

8491
try {
92+
logger.info('Installing Dev Proxy via Homebrew', { packageId });
8593
await executeCommand('brew tap dotnet/dev-proxy');
8694
await executeCommand(`brew install ${packageId}`);
95+
logger.info('Dev Proxy installed successfully via Homebrew');
8796
const result = await vscode.window.showInformationMessage('Dev Proxy installed.', 'Reload');
8897
if (result === 'Reload') {
8998
await vscode.commands.executeCommand('workbench.action.reloadWindow');
9099
}
91100
} catch (error) {
101+
logger.error('Failed to install Dev Proxy via Homebrew', error);
92102
vscode.window.showErrorMessage(`Failed to install Dev Proxy.\n${error}`);
93103
}
94104
}
@@ -137,6 +147,7 @@ async function upgradeDevProxy(configuration: vscode.WorkspaceConfiguration): Pr
137147
const platform = process.platform;
138148
const versionPreference = configuration.get('version') as VersionPreference;
139149
const isBeta = versionPreference === VersionPreference.Beta;
150+
logger.info('Upgrading Dev Proxy', { platform, versionPreference });
140151

141152
// Linux uses install script to upgrade
142153
if (platform === 'linux') {
@@ -182,6 +193,7 @@ async function upgradeDevProxy(configuration: vscode.WorkspaceConfiguration): Pr
182193
isBeta
183194
);
184195
if (!upgraded) {
196+
logger.warn('Upgrade via winget failed, opening upgrade docs');
185197
openUpgradeDocumentation();
186198
}
187199
return;
@@ -202,11 +214,13 @@ async function upgradeDevProxy(configuration: vscode.WorkspaceConfiguration): Pr
202214
isBeta
203215
);
204216
if (!upgraded) {
217+
logger.warn('Upgrade via Homebrew failed, opening upgrade docs');
205218
openUpgradeDocumentation();
206219
}
207220
return;
208221
}
209222

210223
// Unknown platform
224+
logger.warn('Unknown platform, opening upgrade docs', { platform });
211225
openUpgradeDocumentation();
212226
}

src/commands/jwt.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { Commands } from '../constants';
33
import { executeCommand } from '../utils/shell';
44
import { getDevProxyExe } from '../detect';
55
import { VersionPreference } from '../enums';
6+
import * as logger from '../logger';
67

78
/**
89
* JWT (JSON Web Token) generation commands.
@@ -36,8 +37,10 @@ interface JwtParams {
3637
async function createJwt(devProxyExe: string): Promise<void> {
3738
const params = await collectJwtParams();
3839
if (!params) {
40+
logger.debug('JWT creation cancelled by user');
3941
return; // User cancelled
4042
}
43+
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 });
4144

4245
await vscode.window.withProgress(
4346
{
@@ -50,8 +53,10 @@ async function createJwt(devProxyExe: string): Promise<void> {
5053
const command = buildJwtCommand(devProxyExe, params);
5154
const result = await executeCommand(command);
5255
const token = extractToken(result);
56+
logger.info('JWT token generated successfully');
5357
await presentToken(token, command);
5458
} catch (error) {
59+
logger.error('Failed to generate JWT token', error);
5560
vscode.window.showErrorMessage(`Failed to generate JWT token: ${error}`);
5661
}
5762
}

0 commit comments

Comments
 (0)