Change:
I see that Typescript extension is creating an output channel for logging. Recently we have introduced a log output channel that has following advantages You can log at different levels (trace, debug, info, warn, error) Respects the log level defined at the product level User can control the logging level from VS Code UI For the complete product For only your log Across restarts Will make your logs consistent with VS Code logging It will also allow you to remove some formatting code It would be great if you can adopt to the new log output channel. If there are any gaps for adopting, please let me know.
Do language servers now use these log levels instead of having their own settings to enable logging?
I have an issue to convert to log channels in the LSP client. What I plan to do is the following: keep the old implementation to not break extensions with their settings add a new implementation based on log channel. This one will work with log levels only
Looks great overall for TS! One quick question: when I log an object, is it possible for the logger to nicely format it automatically? Right now I still have to use
I believe logger is fomatting the object argument as you expected Is this not what you are seeing?
Should that line be so that the object is nicely formatted with whitespace? Right now the objects are printed as a single line json string Will submit a PR for this
Actually maybe pretty print objects needs to be a separate option to avoid regressions?
I think so too.
Hi all! Partially documenting the new workload since it's not very explicit from the description. It looks like for users who were previously relying on the new alternative is to the Command Palette (<kbdCtrl</kbd+<kbdP</kbdor <kbdCmd</kbd+<kbdP</kbd) Is that correct? Also just confirming - there's no way to persist these settings in a , right?
That's correct. To persist the log level you can change the default log level of the Typescript log. You can do it using the same command but selecting the action to change the default log level -
I gave this feedback in the TS editor sync channel, but I'm not sure that I'm personally a fan as someone who uses this for debugging tsserver problems. Now, I have to go through 4 UI menus to enable it, and by then it's too late to capture the trace, so I have to restart tsserver to get back to the beginning again. And if it doesn't persist, any reloading of my window means I get to do it all over again. If it does persist, now there's a setting not in my that has a massive impact on performance, because logging the entire tsserver trace is very costly. I accidentally spent a month with tracing enabled as it is, only discovering I had it on when I was cleaning up , but at least I had insight into the setting as it was in the usual place. Not to mention that we have to instruct users on how to do this to get traces from them... There's definitely no way to configure this via ?
I hear you. But I feel the issue is magnified a bit. Is not it 2 menus excluding opening the command pallette which is obvious for any action/command? Probably might take same time / gestures from before which is open settings look for setting update value. You also have an option to set the log level to trace for the complete application that makes it more handy and if you are not concerned about other logs. This reduces one additional click. You can persist the log level as mentioned here and there is better one place UI now to see what log levels are configured. Probably makes it more easy to discover. One main intention is to streamline and unify the logging in VS Code and make it consistent. It will also help users to configure/discover various logs in the application.
What I am experiencing is: Open command palette. Type and select "Set log level". Select "TypeScript" (I do not want to enable tracing for every extension). Click on "Trace", or, make it default. Reloading without making it the default kept the setting around, which is helpful, though relaunching the editor entirely didn't keep it, which matches my expectation. I don't mind being able to use a UI to do it. But, now, if I want to capture a trace from the beginning, my options are: Restart/reload or the window after enabling trace mode. Make "Trace" the default, and hope I don't forget to turn it off. This is exactly the kind of thing I will forget; I legitimately do not remember what I had for lunch yesterday. I am not joking. I will forget that this is set, as I have already done many times before, but a least I eventually noticed the setting in my user JSON. Frankly, I don't agree that this is more discoverable; I had to know what the command was called ("log level"). I had started by looking for "trace" because (again) I forgot the name overnight and had to refer back to this issue. Before, I could type "typescript." into my JSON settings and auto-complete gives me all applicable settings. I could type "trace" and get a fuzzy match. I can quickly uncomment out the line I keep in my settings to remind me how to do it, and can give people that string in order to get logs from them. So, like I said, I don't mind being able to use a UI to do it. But, I don't use the settings UI either; I would strongly appreciate a way to achieve the same thing via a JSON setting I can quickly toggle or share.
The default log level is stored in the runtime arguments JSON file ! in the following format You can probably directly edit this file you do not like to use UI I am also open to suggestions for how to make the current flow more easy and discoverable. One idea is to provide a context menu action in the output view to configure log level of a given logger directly.
Thanks, that's at least a little better than the UI, provided I remember it, anyway. Another wart I noticed is that you can't change the logging level for an extension until it has activated, so in my case, I can't get early logs without a reload or restart (but, that already didn't work). I don't know if this is possible, but letting the search also search for the different level names themselves would be an improvement, such that I can type "trace" and know that "log level" has something to do with that. Additionally, my most preferred option would be to allow a setting in to override such that it doesn't have to be set in . Having all of my settings easily readable in one file is a big benefit, and will be a lot easier to explain to users when I ask them to give logs. Otherwise, we will have to give them the explicit steps of "open command palette, search log level, click it, select typescript, choose trace, reopen command palette", compared to "paste this into your ".
Thanks for the feedback. Created for setting log level for an extension even before activating.

Discussion:
"description": "%typescript.tsserver.pluginPaths%", "scope": "machine" }, <del> "typescript.tsserver.trace": { "type": "string", "enum": [ "off", "messages", "verbose" ], "default": "off", "description": "%typescript.tsserver.trace%", "scope": "window" }, </del> "javascript.suggest.completeFunctionCalls": { "type": "boolean", "default": false,

import * as vscode from 'vscode'; import { memoize } from '../utils/memoize'; <del> type LogLevel = 'Trace' | 'Info' | 'Error'; </del> export class Logger { @memoize <del> private get output(): vscode.OutputChannel { return vscode.window.createOutputChannel('TypeScript'); </del> <ins> private get output(): vscode.LogOutputChannel { return vscode.window.createOutputChannel('TypeScript', { log: true }); </ins> } <del> private data2String(data: any): string { if (data instanceof Error) { return data.stack || data.message; } if (data.success === false && data.message) { return data.message; } return data.toString(); </del> <ins> public get logLevel(): vscode.LogLevel { return this.output.logLevel; </ins> } <del> public info(message: string, data?: any): void { this.logLevel('Info', message, data); </del> <ins> public info(message: string, ...args: any[]): void { this.output.info(message, ...args); } public trace(message: string, ...args: any[]): void { this.output.trace(message, ...args); </ins> } public error(message: string, data?: any): void {

if (data && data.message === 'No content available.') { return; } <del> this.logLevel('Error', message, data); </del> <ins> this.output.error(message, ...(data ? [data] : [])); </ins> } <del> public logLevel(level: LogLevel, message: string, data?: any): void { this.output.appendLine(`[${level} - ${this.now()}] ${message}`); if (data) { this.output.appendLine(this.data2String(data)); } } private now(): string { const now = new Date(); return padLeft(now.getUTCHours() + '', 2, '0') + ':' + padLeft(now.getMinutes() + '', 2, '0') + ':' + padLeft(now.getUTCSeconds() + '', 2, '0') + '.' + now.getMilliseconds(); } } function padLeft(s: string, n: number, pad = ' ') { return pad.repeat(Math.max(0, n - s.length)) + s; </del> }

import * as vscode from 'vscode'; import type * as Proto from '../tsServer/protocol/protocol'; <ins> import { Disposable } from '../utils/dispose'; </ins> import { Logger } from './logger'; <del> enum Trace { Off, Messages, Verbose, } namespace Trace { export function fromString(value: string): Trace { value = value.toLowerCase(); switch (value) { case 'off': return Trace.Off; case 'messages': return Trace.Messages; case 'verbose': return Trace.Verbose; default: return Trace.Off; } } } </del> interface RequestExecutionMetadata { readonly queuingStartTime: number; } <del> export default class Tracer { private trace?: Trace; </del> <ins> export default class Tracer extends Disposable { </ins> constructor( private readonly logger: Logger ) { <del> this.updateConfiguration(); } public updateConfiguration() { this.trace = Tracer.readTrace(); } private static readTrace(): Trace { let result: Trace = Trace.fromString(vscode.workspace.getConfiguration().get<string>('typescript.tsserver.trace', 'off')); if (result === Trace.Off && !!process.env.TSS_TRACE) { result = Trace.Messages; } return result; </del> <ins> super(); </ins> } public traceRequest(serverId: string, request: Proto.Request, responseExpected: boolean, queueLength: number): void { <del> if (this.trace === Trace.Off) { return; } let data: string | undefined = undefined; if (this.trace === Trace.Verbose && request.arguments) { data = `Arguments: ${JSON.stringify(request.arguments, null, 4)}`; </del> <ins> if (this.logger.logLevel === vscode.LogLevel.Trace) { this.trace(serverId, `Sending request: ${request.command} (${request.seq}). Response expected: ${responseExpected ? 'yes' : 'no'}. Current queue length: ${queueLength}`, request.arguments); </ins> } <del> this.logTrace(serverId, `Sending request: ${request.command} (${request.seq}). Response expected: ${responseExpected ? 'yes' : 'no'}. Current queue length: ${queueLength}`, data); </del> } public traceResponse(serverId: string, response: Proto.Response, meta: RequestExecutionMetadata): void { <del> if (this.trace === Trace.Off) { return; </del> <ins> if (this.logger.logLevel === vscode.LogLevel.Trace) { this.trace(serverId, `Response received: ${response.command} (${response.request_seq}). Request took ${Date.now() - meta.queuingStartTime} ms. Success: ${response.success} ${!response.success ? '. Message: ' + response.message : ''}`, response.body); </ins> } <del> let data: string | undefined = undefined; if (this.trace === Trace.Verbose && response.body) { data = `Result: ${JSON.stringify(response.body, null, 4)}`; } this.logTrace(serverId, `Response received: ${response.command} (${response.request_seq}). Request took ${Date.now() - meta.queuingStartTime} ms. Success: ${response.success} ${!response.success ? '. Message: ' + response.message : ''}`, data); </del> } public traceRequestCompleted(serverId: string, command: string, request_seq: number, meta: RequestExecutionMetadata): any { <del> if (this.trace === Trace.Off) { return; </del> <ins> if (this.logger.logLevel === vscode.LogLevel.Trace) { this.trace(serverId, `Async response received: ${command} (${request_seq}). Request took ${Date.now() - meta.queuingStartTime} ms.`); </ins> } <del> this.logTrace(serverId, `Async response received: ${command} (${request_seq}). Request took ${Date.now() - meta.queuingStartTime} ms.`); </del> } public traceEvent(serverId: string, event: Proto.Event): void { <del> if (this.trace === Trace.Off) { return; } let data: string | undefined = undefined; if (this.trace === Trace.Verbose && event.body) { data = `Data: ${JSON.stringify(event.body, null, 4)}`; </del> <ins> if (this.logger.logLevel === vscode.LogLevel.Trace) { this.trace(serverId, `Event received: ${event.event} (${event.seq}).`, event.body); </ins> } <del> this.logTrace(serverId, `Event received: ${event.event} (${event.seq}).`, data); </del> } <del> public logTrace(serverId: string, message: string, data?: any): void { if (this.trace !== Trace.Off) { this.logger.logLevel('Trace', `<${serverId}> ${message}`, data); } </del> <ins> public trace(serverId: string, message: string, data?: unknown): void { this.logger.trace(`<${serverId}> ${message}`, ...(data ? [JSON.stringify(data, null, 4)] : [])); </ins> } }

if (!this.cancellationPipeName) { return false; } <del> this._tracer.logTrace(this._serverId, `TypeScript Server: trying to cancel ongoing request with sequence number ${seq}`); </del> <ins> this._tracer.trace(this._serverId, `TypeScript Server: trying to cancel ongoing request with sequence number ${seq}`); </ins> try { fs.writeFileSync(this.cancellationPipeName + seq, ''); } catch {

} private logTrace(message: string) { <del> this._tracer.logTrace(this._serverId, message); </del> <ins> this._tracer.trace(this._serverId, message); </ins> } private static readonly fenceCommands = new Set(['change', 'close', 'open', 'updateOpen']);

this.versionProvider.updateConfiguration(this._configuration); this._versionManager.updateConfiguration(this._configuration); this.pluginPathsProvider.updateConfiguration(this._configuration); <del> this.tracer.updateConfiguration(); </del> if (this.serverState.type === ServerState.Type.Running) { if (!this._configuration.implicitProjectConfiguration.isEqualTo(oldConfiguration.implicitProjectConfiguration)) {