Route jsonrpc connection logging to the output channel instead of console - #1853
Conversation
…sole Motivation: BaseLanguageClient's internal jsonrpc Connection was created with a ConsoleLogger, so protocol-level diagnostics such as "Received response message without id" (jsonrpc/src/common/connection.ts) only showed up in VS Code's Developer Tools console. Extensions typically point users at their own Output channel for troubleshooting, so these messages were effectively invisible to end users and to extension authors reproducing bug reports. Approach: Add an OutputChannelLogger that implements the jsonrpc Logger interface (error/warn/info/log) by forwarding to a LogOutputChannel, mapping the generic log() to outputChannel.info() since LogOutputChannel has no generic log method. The module-level createConnection() now accepts an optional lazy accessor (() => LogOutputChannel) and uses OutputChannelLogger when provided, falling back to the previous ConsoleLogger otherwise. BaseLanguageClient.createConnection() passes "() => this.outputChannel" rather than a resolved channel so the output channel keeps its existing lazy-creation semantics: a channel is still only created the first time something is actually logged, matching the behavior of the pre-existing this.error/warn/info/debug client methods and shouldLogToOutputChannel(). Validation: - npm run compile:client (tsc -b ./client/tsconfig.json) passes with no errors, type-checked against the real @types/vscode LogOutputChannel shape. - cd client && npm run lint (eslint) passes clean. - Traced call sites in jsonrpc/src/common/connection.ts to confirm logger.error/warn/log are only invoked from message-processing paths, never from connection setup, so the lazy output-channel accessor is never forced eagerly and a session that never logs still creates no output channel, preserving prior behavior for that case. - Could not run this repo's client-node-tests integration suite (real VS Code Insiders launched via @vscode/test-electron, as used in build/azure-pipelines/darwin/build.yml): the sandbox this change was developed in has no free disk space to download the ~300MB VS Code build (ENOSPC), an environment constraint unrelated to this change. There is no lighter-weight unit-test harness for client.ts in this repo since everything under client-node-tests only runs inside a real VS Code Electron process. Report: microsoft#1613 Signed-off-by: Pujitha Paladugu <10557236+pujitha24@users.noreply.github.com> Assisted-by: claude-sonnet-5 (via Claude Code)
|
Azure Pipelines: There may be pipelines that require an authorized user to comment /azp run to run. |
|
|
||
| function createConnection(input: MessageReader, output: MessageWriter, errorHandler: ConnectionErrorHandler, closeHandler: ConnectionCloseHandler, options?: ConnectionOptions): Connection { | ||
| const logger = new ConsoleLogger(); | ||
| function createConnection(input: MessageReader, output: MessageWriter, errorHandler: ConnectionErrorHandler, closeHandler: ConnectionCloseHandler, getOutputChannel: (() => LogOutputChannel) | undefined, options?: ConnectionOptions): Connection { |
There was a problem hiding this comment.
Can we make a type / interface ChannelProvider like
interface ChannelProvider {
outputChannel: LogOutputChanneln
}
And then pass the client instead of a function instance.
…nnel closure
Per review feedback, createConnection/OutputChannelLogger now take a
ChannelProvider ({ outputChannel: LogOutputChannel }) and the client
itself is passed in, rather than a bound () => this.outputChannel
function. BaseLanguageClient's outputChannel getter already satisfies
the interface structurally, so lazy output-channel creation is
unchanged.
Signed-off-by: Pujitha Paladugu <10557236+pujitha24@users.noreply.github.com>
|
Dirk Bäumer (@dbaeumer) I've pushed changes addressing your review — the branch is now at |
|
/AzurePipelines run |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
There was a problem hiding this comment.
🟡 Changes recommended
Logging can recreate the disposed output channel after shutdown, and the behavior lacks regression coverage.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
Routes JSON-RPC diagnostics to the language client’s output channel for improved visibility.
Changes:
- Adds an output-channel-backed JSON-RPC logger.
- Preserves lazy channel creation.
- Maps generic logs to info severity.
File summaries
| File | Description |
|---|---|
client/src/common/client.ts |
Connects protocol logging to the client output channel. |
Review details
- Files reviewed: 1/1 changed files
- Comments generated: 2
- Review effort level: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| outputChannel: LogOutputChannel; | ||
| } | ||
|
|
||
| class OutputChannelLogger implements Logger { |
|
Deleted user (@ghost) see the Copilot review comments. |
Per Copilot's review comment, OutputChannelLogger previously dereferenced channelProvider.outputChannel directly, bypassing BaseLanguageClient's shouldLogToOutputChannel() guard. An in-flight JSON-RPC handler rejection after shutdown() could still reach the logger and recreate a disposed output channel for a stopped client. ChannelProvider now exposes error/warn/info (matching the client's existing public logging methods) and OutputChannelLogger calls those with notifications disabled, so the stopped-state guard applies to protocol-level logging too. Signed-off-by: Pujitha Paladugu <10557236+pujitha24@users.noreply.github.com>
|
Dirk Bäumer (@dbaeumer) I've pushed changes addressing your review — the branch is now at |
…annel Per Copilot's review comment, adds a test in the 'Server output' suite that triggers a jsonrpc-level diagnostic (a throwing notification handler) and asserts it reaches the client's guarded error() method (showNotification=false) rather than bypassing it, covering the routing fixed in f739ee8. Signed-off-by: Pujitha Paladugu <10557236+pujitha24@users.noreply.github.com>
Copilot's review flagged that the earlier regression test (1282da8) never verified "no channel is created before the first diagnostic". Investigating client.ts shows the output channel is already created during start() as a side effect of wiring the trace log-level listener, unrelated to diagnostics, so that literal assertion would be false. Instead this adds a test for the actual regression f739ee8 fixed: a diagnostic logged after stop() must not recreate the disposed output channel. Signed-off-by: Pujitha Paladugu <10557236+pujitha24@users.noreply.github.com>
|
/AzurePipelines run |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
|
Dirk Bäumer (@dbaeumer) I've pushed changes addressing your review — the branch is now at |
|
This is approved with green CI and no outstanding comments as far as I can tell — ready whenever you have a moment. Happy to rebase first if you'd like it freshened. |


Motivation:
BaseLanguageClient's internal jsonrpc Connection was created with a
ConsoleLogger, so protocol-level diagnostics such as "Received response
message without id" (jsonrpc/src/common/connection.ts) only showed up in
VS Code's Developer Tools console. Extensions typically point users at
their own Output channel for troubleshooting, so these messages were
effectively invisible to end users and to extension authors reproducing
bug reports.
Approach:
Add an OutputChannelLogger that implements the jsonrpc Logger interface
(error/warn/info/log) by forwarding to a LogOutputChannel, mapping the
generic log() to outputChannel.info() since LogOutputChannel has no
generic log method. The module-level createConnection() now accepts an
optional lazy accessor (() => LogOutputChannel) and uses
OutputChannelLogger when provided, falling back to the previous
ConsoleLogger otherwise. BaseLanguageClient.createConnection() passes
"() => this.outputChannel" rather than a resolved channel so the output
channel keeps its existing lazy-creation semantics: a channel is still
only created the first time something is actually logged, matching the
behavior of the pre-existing this.error/warn/info/debug client methods
and shouldLogToOutputChannel().
Validation:
errors, type-checked against the real @types/vscode LogOutputChannel
shape.
logger.error/warn/log are only invoked from message-processing paths,
never from connection setup, so the lazy output-channel accessor is
never forced eagerly and a session that never logs still creates no
output channel, preserving prior behavior for that case.
VS Code Insiders launched via @vscode/test-electron, as used in
build/azure-pipelines/darwin/build.yml): the sandbox this change was
developed in has no free disk space to download the ~300MB VS Code
build (ENOSPC), an environment constraint unrelated to this change.
There is no lighter-weight unit-test harness for client.ts in this
repo since everything under client-node-tests only runs inside a real
VS Code Electron process.
Report: #1613
Signed-off-by: Pujitha Paladugu 10557236+pujitha24@users.noreply.github.com
Assisted-by: claude-sonnet-5 (via Claude Code)
Fixes #1613