Skip to content

Logging adapter and usage #545

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft

Logging adapter and usage #545

wants to merge 6 commits into from

Conversation

jaggederest
Copy link
Contributor

No description provided.

jaggederest and others added 6 commits July 2, 2025 19:24
Implement a centralized logging system to improve debugging capabilities
and separate logging concerns from the Storage class.

- Add logger module with configurable debug/info levels
- Support runtime configuration via coder.verbose setting
- Provide OutputChannelAdapter for VS Code integration
- Add ArrayAdapter for isolated unit testing
- Include performance benchmarks in tests
- Maintain backward compatibility via Storage delegation
- Update all components to use centralized logger

The logger responds to configuration changes without requiring extension
restart and includes source location tracking for debug messages.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
Copy link
Member

@code-asher code-asher left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the direction, very glad to see all those writeToCoderOutputChannel calls going away.

try {
this.outputChannel.appendLine(message);
} catch {
// Silently ignore - channel may be disposed
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could perhaps console.log as a last-ditch effort

try {
this.outputChannel.clear();
} catch {
// Silently ignore - channel may be disposed
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here


private setupConfigListener(): void {
// In test environment, vscode.workspace might not be available
if (!vscode.workspace?.onDidChangeConfiguration) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be possible to always mock these? I feel like that would be preferable so we can minimize the number of test-specific branches in the production code, which increases the chance we miss something in tests.

Also, thinking out loud, this is only a problem because of our custom test setup right? When you scaffold an extension by default, even in unit tests vscode is available I think? Or am I wrong about that lol


private updateLogLevel(): void {
// In test environment, vscode.workspace might not be available
if (!vscode.workspace?.getConfiguration) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same question here

return;
}
const config = vscode.workspace.getConfiguration("coder");
const verbose = config.get<boolean>("verbose", false);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think it would make sense to add a setting for levels instead? I could see one day adding a level setting, then we would have both levels and verbose.

Which is itself not that strange, I think plenty of tools have both a verbose and log level flag, but it would match the remote extensions which have dropdowns for level settings.

At a minimum I think we would want trace, debug, info, warn, and error (even if we only use info right now, just so we have no problems with the numbering in the future where a setting of 2 used to mean none but would now mean info).

Also, we need to add the setting to the package.json as well right?

}
}

export class NoOpAdapter implements LogAdapter {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is logger performance really something we need to worry about? If so, could we create a separate benchmarking suite, maybe with mitata or something? I think to settle on hard performance thresholds in a unit test we would need to see the performance data first, although maybe we would want a separate pipeline for enforcing performance constraints.

But also, the current test is comparing the difference between doing nothing and pushing to an array, which I am not sure tells us anything we could act on, and neither adapter is used in production anyway.

Comment on lines +148 to +183
setAdapter(adapter: LogAdapter): void {
if (process.env.NODE_ENV !== "test") {
throw new Error("setAdapter can only be called in test environment");
}
if (this.adapter !== null) {
throw new Error(
"Adapter already set. Use reset() first or withAdapter() for temporary changes",
);
}
this.adapter = adapter;
}

withAdapter<T>(adapter: LogAdapter, fn: () => T): T {
const previous = this.adapter;
this.adapter = adapter;
try {
return fn();
} finally {
this.adapter = previous;
}
}

reset(): void {
if (process.env.NODE_ENV !== "test") {
throw new Error("reset can only be called in test environment");
}
this.adapter = null;
this.level = LogLevel.INFO;
if (this.configListener) {
this.configListener.dispose();
this.configListener = null;
}
// Re-setup config listener for next test
this.updateLogLevel();
this.setupConfigListener();
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is all purely for tests, could we refactor in a way that does not add unused API to the production code? Maybe by extending the logger class or something?

I also see that the only places we use withAdapter is to test withAdapter, so it exists only for itself and seems like we could just delete it entirely.

For reset, we could create new instances instead, might be better to do that anyway to ensure unit tests are isolated.

Comment on lines +186 to +191
initialize(outputChannel: vscode.OutputChannel): void {
if (this.adapter !== null) {
throw new Error("Logger already initialized");
}
this.adapter = new OutputChannelAdapter(outputChannel);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It feels a little backwards to me that the logger is built around a generic adapter, but also has knowledge about a specific adapter, if that makes sense? Could we do something like new Logger(new OutputChannelAdapter(output)) or something like that (or allow setting the adapter outside of tests).

Comment on lines +196 to +198

// Export types for testing
export type Logger = typeof logger;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we only need it for testing, we should move it to the test file!

Comment on lines +511 to +516
/**
* Compatibility method for Logger interface used by headers.ts and error.ts.
* Delegates to the logger module.
*/
public writeToCoderOutputChannel(message: string): void {
logger.info(message);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh wait I thought you changed headers.ts and error.ts. Can we delete this function now?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants