Fix open ts server log command on web (#172885)

Fixes #172853
This commit is contained in:
Matt Bierner
2023-01-30 21:34:00 -08:00
committed by GitHub
parent c18f80a2d4
commit 855dd78852
7 changed files with 102 additions and 79 deletions

View File

@@ -13,16 +13,16 @@ import { createLazyClientHost, lazilyActivateClient } from './lazyClientHost';
import RemoteRepositories from './remoteRepositories.browser';
import { noopRequestCancellerFactory } from './tsServer/cancellation';
import { noopLogDirectoryProvider } from './tsServer/logDirectoryProvider';
import { WorkerServerProcess } from './tsServer/serverProcess.browser';
import { WorkerServerProcessFactory } from './tsServer/serverProcess.browser';
import { ITypeScriptVersionProvider, TypeScriptVersion, TypeScriptVersionSource } from './tsServer/versionProvider';
import { ActiveJsTsEditorTracker } from './utils/activeJsTsEditorTracker';
import API from './utils/api';
import { TypeScriptServiceConfiguration } from './utils/configuration';
import { BrowserServiceConfigurationProvider } from './utils/configuration.browser';
import { getPackageInfo } from './utils/packageInfo';
import { PluginManager } from './utils/plugins';
import { Logger } from './utils/logger';
import { getPackageInfo } from './utils/packageInfo';
import { isWebAndHasSharedArrayBuffers } from './utils/platform';
import { PluginManager } from './utils/plugins';
class StaticVersionProvider implements ITypeScriptVersionProvider {
@@ -78,7 +78,7 @@ export async function activate(context: vscode.ExtensionContext): Promise<Api> {
logDirectoryProvider: noopLogDirectoryProvider,
cancellerFactory: noopRequestCancellerFactory,
versionProvider,
processFactory: WorkerServerProcess,
processFactory: new WorkerServerProcessFactory(context.extensionUri),
activeJsTsEditorTracker,
serviceConfigurationProvider: new BrowserServiceConfigurationProvider(),
experimentTelemetryReporter,

View File

@@ -8,7 +8,7 @@ import 'mocha';
import * as stream from 'stream';
import type * as Proto from '../../protocol';
import { NodeRequestCanceller } from '../../tsServer/cancellation.electron';
import { ProcessBasedTsServer, TsServerProcess } from '../../tsServer/server';
import { SingleTsServer, TsServerProcess } from '../../tsServer/server';
import { ServerType } from '../../typescriptService';
import { nulToken } from '../../utils/cancellation';
import { Logger } from '../../utils/logger';
@@ -65,7 +65,7 @@ suite.skip('Server', () => {
test('should send requests with increasing sequence numbers', async () => {
const process = new FakeServerProcess();
const server = new ProcessBasedTsServer('semantic', ServerType.Semantic, process, undefined, new NodeRequestCanceller('semantic', tracer), undefined!, NoopTelemetryReporter, tracer);
const server = new SingleTsServer('semantic', ServerType.Semantic, process, undefined, new NodeRequestCanceller('semantic', tracer), undefined!, NoopTelemetryReporter, tracer);
const onWrite1 = process.onWrite();
server.executeImpl('geterr', {}, { isAsync: false, token: nulToken, expectsResult: true });

View File

@@ -30,12 +30,16 @@ export interface TypeScriptServerExitEvent {
readonly signal: string | null;
}
export type TsServerLog =
{ readonly type: 'file'; readonly uri: vscode.Uri } |
{ readonly type: 'output'; readonly output: vscode.OutputChannel };
export interface ITypeScriptServer {
readonly onEvent: vscode.Event<Proto.Event>;
readonly onExit: vscode.Event<TypeScriptServerExitEvent>;
readonly onError: vscode.Event<any>;
readonly tsServerLogFile: vscode.Uri | undefined;
readonly tsServerLog: TsServerLog | undefined;
kill(): void;
@@ -66,7 +70,7 @@ export interface TsServerProcessFactory {
kind: TsServerProcessKind,
configuration: TypeScriptServiceConfiguration,
versionManager: TypeScriptVersionManager,
extensionUri: vscode.Uri,
tsServerLog: TsServerLog | undefined,
): TsServerProcess;
}
@@ -80,7 +84,7 @@ export interface TsServerProcess {
kill(): void;
}
export class ProcessBasedTsServer extends Disposable implements ITypeScriptServer {
export class SingleTsServer extends Disposable implements ITypeScriptServer {
private readonly _requestQueue = new RequestQueue();
private readonly _callbacks = new CallbackMap<Proto.Response>();
private readonly _pendingResponses = new Set<number>();
@@ -89,7 +93,7 @@ export class ProcessBasedTsServer extends Disposable implements ITypeScriptServe
private readonly _serverId: string,
private readonly _serverSource: ServerType,
private readonly _process: TsServerProcess,
private readonly _tsServerLogFile: vscode.Uri | undefined,
private readonly _tsServerLog: TsServerLog | undefined,
private readonly _requestCanceller: OngoingRequestCanceller,
private readonly _version: TypeScriptVersion,
private readonly _telemetryReporter: TelemetryReporter,
@@ -121,7 +125,7 @@ export class ProcessBasedTsServer extends Disposable implements ITypeScriptServe
private readonly _onError = this._register(new vscode.EventEmitter<any>());
public readonly onError = this._onError.event;
public get tsServerLogFile() { return this._tsServerLogFile; }
public get tsServerLog() { return this._tsServerLog; }
private write(serverRequest: Proto.Request) {
this._process.write(serverRequest);
@@ -215,7 +219,7 @@ export class ProcessBasedTsServer extends Disposable implements ITypeScriptServe
request,
expectsResponse: executeInfo.expectsResult,
isAsync: executeInfo.isAsync,
queueingType: ProcessBasedTsServer.getQueueingType(command, executeInfo.lowPriority)
queueingType: SingleTsServer.getQueueingType(command, executeInfo.lowPriority)
};
let result: Promise<ServerResponse.Response<Proto.Response>> | undefined;
if (executeInfo.expectsResult) {
@@ -304,7 +308,7 @@ export class ProcessBasedTsServer extends Disposable implements ITypeScriptServe
command: string,
lowPriority?: boolean
): RequestQueueingType {
if (ProcessBasedTsServer.fenceCommands.has(command)) {
if (SingleTsServer.fenceCommands.has(command)) {
return RequestQueueingType.Fence;
}
return lowPriority ? RequestQueueingType.LowPriority : RequestQueueingType.Normal;
@@ -465,7 +469,7 @@ export class GetErrRoutingTsServer extends Disposable implements ITypeScriptServ
private readonly _onError = this._register(new vscode.EventEmitter<any>());
public readonly onError = this._onError.event;
public get tsServerLogFile() { return this.mainServer.tsServerLogFile; }
public get tsServerLog() { return this.mainServer.tsServerLog; }
public kill(): void {
this.getErrServer.kill();
@@ -605,7 +609,7 @@ export class SyntaxRoutingTsServer extends Disposable implements ITypeScriptServ
private readonly _onError = this._register(new vscode.EventEmitter<any>());
public readonly onError = this._onError.event;
public get tsServerLogFile() { return this.semanticServer.tsServerLogFile; }
public get tsServerLog() { return this.semanticServer.tsServerLog; }
public kill(): void {
this.syntaxServer.kill();

View File

@@ -3,16 +3,15 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/// <reference lib='webworker' />
import { ServiceConnection } from '@vscode/sync-api-common/browser';
import { ApiService, Requests } from '@vscode/sync-api-service';
import * as vscode from 'vscode';
import type * as Proto from '../protocol';
import { TypeScriptServiceConfiguration } from '../utils/configuration';
import { memoize } from '../utils/memoize';
import { TsServerProcess, TsServerProcessKind } from './server';
import { TypeScriptVersion } from './versionProvider';
import { ServiceConnection } from '@vscode/sync-api-common/browser';
import { Requests, ApiService } from '@vscode/sync-api-service';
import { TypeScriptVersionManager } from './versionManager';
import { FileWatcherManager } from './fileWatchingManager';
import { TsServerLog, TsServerProcess, TsServerProcessFactory, TsServerProcessKind } from './server';
import { TypeScriptVersionManager } from './versionManager';
import { TypeScriptVersion } from './versionProvider';
type BrowserWatchEvent = {
type: 'watchDirectory' | 'watchFile';
@@ -28,29 +27,31 @@ type BrowserWatchEvent = {
id: number;
};
export class WorkerServerProcess implements TsServerProcess {
@memoize
private static get tsServerlogOutputChannel(): vscode.OutputChannel {
return vscode.window.createOutputChannel(vscode.l10n.t("TypeScript Server Log"));
}
export class WorkerServerProcessFactory implements TsServerProcessFactory {
constructor(
private readonly _extensionUri: vscode.Uri,
) { }
public static fork(
public fork(
version: TypeScriptVersion,
args: readonly string[],
kind: TsServerProcessKind,
_configuration: TypeScriptServiceConfiguration,
_versionManager: TypeScriptVersionManager,
extensionUri: vscode.Uri,
tsServerLog: TsServerLog | undefined,
) {
const tsServerPath = version.tsServerPath;
return new WorkerServerProcess(kind, tsServerPath, extensionUri, [
return new WorkerServerProcess(kind, tsServerPath, this._extensionUri, [
...args,
// Explicitly give TS Server its path so it can
// load local resources
'--executingFilePath', tsServerPath,
]);
], tsServerLog);
}
}
class WorkerServerProcess implements TsServerProcess {
private static idPool = 0;
@@ -75,6 +76,7 @@ export class WorkerServerProcess implements TsServerProcess {
tsServerPath: string,
extensionUri: vscode.Uri,
args: readonly string[],
private readonly tsServerLog: TsServerLog | undefined,
) {
this.worker = new Worker(tsServerPath, { name: `TS ${kind} server #${this.id}` });
@@ -167,7 +169,9 @@ export class WorkerServerProcess implements TsServerProcess {
}
private appendLog(msg: string) {
WorkerServerProcess.tsServerlogOutputChannel.appendLine(`(${this.id} - ${this.kind}) ${msg}`);
if (this.tsServerLog?.type === 'output') {
this.tsServerLog.output.appendLine(`(${this.id} - ${this.kind}) ${msg}`);
}
}
}

View File

@@ -12,7 +12,7 @@ import type * as Proto from '../protocol';
import API from '../utils/api';
import { TypeScriptServiceConfiguration } from '../utils/configuration';
import { Disposable } from '../utils/dispose';
import { TsServerProcess, TsServerProcessFactory, TsServerProcessKind } from './server';
import { TsServerLog, TsServerProcess, TsServerProcessFactory, TsServerProcessKind } from './server';
import { TypeScriptVersionManager } from './versionManager';
import { TypeScriptVersion } from './versionProvider';
@@ -253,6 +253,7 @@ export class ElectronServiceProcessFactory implements TsServerProcessFactory {
kind: TsServerProcessKind,
configuration: TypeScriptServiceConfiguration,
versionManager: TypeScriptVersionManager,
_tsserverLog: TsServerLog | undefined,
): TsServerProcess {
let tsServerPath = version.tsServerPath;

View File

@@ -15,9 +15,10 @@ import { PluginManager } from '../utils/plugins';
import { TelemetryReporter } from '../utils/telemetry';
import Tracer from '../utils/tracer';
import { ILogDirectoryProvider } from './logDirectoryProvider';
import { GetErrRoutingTsServer, ITypeScriptServer, ProcessBasedTsServer, SyntaxRoutingTsServer, TsServerDelegate, TsServerProcessFactory, TsServerProcessKind } from './server';
import { GetErrRoutingTsServer, ITypeScriptServer, SingleTsServer, SyntaxRoutingTsServer, TsServerDelegate, TsServerLog, TsServerProcessFactory, TsServerProcessKind } from './server';
import { TypeScriptVersionManager } from './versionManager';
import { ITypeScriptVersionProvider, TypeScriptVersion } from './versionProvider';
import { memoize } from '../utils/memoize';
const enum CompositeServerType {
/** Run a single server that handles all commands */
@@ -34,6 +35,12 @@ const enum CompositeServerType {
}
export class TypeScriptServerSpawner {
@memoize
public static get tsServerLogOutputChannel(): vscode.OutputChannel {
return vscode.window.createOutputChannel(vscode.l10n.t("TypeScript Server Log"));
}
public constructor(
private readonly _versionProvider: ITypeScriptVersionProvider,
private readonly _versionManager: TypeScriptVersionManager,
@@ -43,7 +50,6 @@ export class TypeScriptServerSpawner {
private readonly _telemetryReporter: TelemetryReporter,
private readonly _tracer: Tracer,
private readonly _factory: TsServerProcessFactory,
private readonly _extensionUri: vscode.Uri,
) { }
public spawn(
@@ -133,13 +139,15 @@ export class TypeScriptServerSpawner {
const apiVersion = version.apiVersion || API.defaultVersion;
const canceller = cancellerFactory.create(kind, this._tracer);
const { args, tsServerLogFile, tsServerTraceDirectory } = this.getTsServerArgs(kind, configuration, version, apiVersion, pluginManager, canceller.cancellationPipeName);
const { args, tsServerLog, tsServerTraceDirectory } = this.getTsServerArgs(kind, configuration, version, apiVersion, pluginManager, canceller.cancellationPipeName);
if (TypeScriptServerSpawner.isLoggingEnabled(configuration)) {
if (tsServerLogFile) {
this._logger.info(`<${kind}> Log file: ${tsServerLogFile}`);
} else {
this._logger.error(`<${kind}> Could not create log directory`);
if (!isWeb()) {
if (tsServerLog) {
this._logger.info(`<${kind}> Log file: ${tsServerLog}`);
} else {
this._logger.error(`<${kind}> Could not create log directory`);
}
}
}
@@ -152,14 +160,14 @@ export class TypeScriptServerSpawner {
}
this._logger.info(`<${kind}> Forking...`);
const process = this._factory.fork(version, args, kind, configuration, this._versionManager, this._extensionUri);
const process = this._factory.fork(version, args, kind, configuration, this._versionManager, tsServerLog);
this._logger.info(`<${kind}> Starting...`);
return new ProcessBasedTsServer(
return new SingleTsServer(
kind,
this.kindToServerType(kind),
process!,
tsServerLogFile,
tsServerLog,
canceller,
version,
this._telemetryReporter,
@@ -186,9 +194,9 @@ export class TypeScriptServerSpawner {
apiVersion: API,
pluginManager: PluginManager,
cancellationPipeName: string | undefined,
): { args: string[]; tsServerLogFile: vscode.Uri | undefined; tsServerTraceDirectory: vscode.Uri | undefined } {
): { args: string[]; tsServerLog: TsServerLog | undefined; tsServerTraceDirectory: vscode.Uri | undefined } {
const args: string[] = [];
let tsServerLogFile: vscode.Uri | undefined;
let tsServerLog: TsServerLog | undefined;
let tsServerTraceDirectory: vscode.Uri | undefined;
if (kind === TsServerProcessKind.Syntax) {
@@ -216,13 +224,15 @@ export class TypeScriptServerSpawner {
if (TypeScriptServerSpawner.isLoggingEnabled(configuration)) {
if (isWeb()) {
args.push('--logVerbosity', TsServerLogLevel.toString(configuration.tsServerLogLevel));
tsServerLog = { type: 'output', output: TypeScriptServerSpawner.tsServerLogOutputChannel };
} else {
const logDir = this._logDirectoryProvider.getNewLogDirectory();
if (logDir) {
tsServerLogFile = vscode.Uri.joinPath(logDir, `tsserver.log`);
const logFilePath = vscode.Uri.joinPath(logDir, `tsserver.log`);
tsServerLog = { type: 'file', uri: logFilePath };
args.push('--logVerbosity', TsServerLogLevel.toString(configuration.tsServerLogLevel));
args.push('--logFile', tsServerLogFile.path);
args.push('--logFile', logFilePath.path);
}
}
}
@@ -265,7 +275,7 @@ export class TypeScriptServerSpawner {
args.push('--enableProjectWideIntelliSenseOnWeb');
}
return { args, tsServerLogFile, tsServerTraceDirectory };
return { args, tsServerLog: tsServerLog, tsServerTraceDirectory };
}
private static isLoggingEnabled(configuration: TypeScriptServiceConfiguration) {

View File

@@ -12,7 +12,7 @@ import { EventName } from './protocol.const';
import BufferSyncSupport from './tsServer/bufferSyncSupport';
import { OngoingRequestCancellerFactory } from './tsServer/cancellation';
import { ILogDirectoryProvider } from './tsServer/logDirectoryProvider';
import { ITypeScriptServer, TsServerProcessFactory, TypeScriptServerExitEvent } from './tsServer/server';
import { ITypeScriptServer, TsServerLog, TsServerProcessFactory, TypeScriptServerExitEvent } from './tsServer/server';
import { TypeScriptServerError } from './tsServer/serverError';
import { TypeScriptServerSpawner } from './tsServer/spawner';
import { TypeScriptVersionManager } from './tsServer/versionManager';
@@ -84,7 +84,7 @@ namespace ServerState {
readonly type = Type.Errored;
constructor(
public readonly error: Error,
public readonly tsServerLogFile: vscode.Uri | undefined,
public readonly tsServerLog: TsServerLog | undefined,
) { }
}
@@ -97,8 +97,6 @@ export default class TypeScriptServiceClient extends Disposable implements IType
private readonly emptyAuthority = 'ts-nul-authority';
private readonly inMemoryResourcePrefix = '^';
private readonly workspaceState: vscode.Memento;
private readonly _onReady?: { promise: Promise<void>; resolve: () => void; reject: () => void };
private _configuration: TypeScriptServiceConfiguration;
private readonly pluginPathsProvider: TypeScriptPluginPathsProvider;
@@ -146,8 +144,6 @@ export default class TypeScriptServiceClient extends Disposable implements IType
this.logger = services.logger;
this.tracer = new Tracer(this.logger);
this.workspaceState = context.workspaceState;
this.pluginManager = services.pluginManager;
this.logDirectoryProvider = services.logDirectoryProvider;
this.cancellerFactory = services.cancellerFactory;
@@ -171,7 +167,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType
this.versionProvider.updateConfiguration(this._configuration);
this.pluginPathsProvider = new TypeScriptPluginPathsProvider(this._configuration);
this._versionManager = this._register(new TypeScriptVersionManager(this._configuration, this.versionProvider, this.workspaceState));
this._versionManager = this._register(new TypeScriptVersionManager(this._configuration, this.versionProvider, context.workspaceState));
this._register(this._versionManager.onDidPickNewVersion(() => {
this.restartTsServer();
}));
@@ -218,7 +214,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType
return this.apiVersion.fullVersionString;
});
this.typescriptServerSpawner = new TypeScriptServerSpawner(this.versionProvider, this._versionManager, this.logDirectoryProvider, this.pluginPathsProvider, this.logger, this.telemetryReporter, this.tracer, this.processFactory, context.extensionUri);
this.typescriptServerSpawner = new TypeScriptServerSpawner(this.versionProvider, this._versionManager, this.logDirectoryProvider, this.pluginPathsProvider, this.logger, this.telemetryReporter, this.tracer, this.processFactory);
this._register(this.pluginManager.onDidUpdateConfig(update => {
this.configurePlugin(update.pluginId, update.config);
@@ -426,10 +422,10 @@ export default class TypeScriptServiceClient extends Disposable implements IType
vscode.window.showErrorMessage(vscode.l10n.t("TypeScript language server exited with error. Error message is: {0}", err.message || err.name));
}
this.serverState = new ServerState.Errored(err, handle.tsServerLogFile);
this.serverState = new ServerState.Errored(err, handle.tsServerLog);
this.error('TSServer errored with error.', err);
if (handle.tsServerLogFile) {
this.error(`TSServer log file: ${handle.tsServerLogFile}`);
if (handle.tsServerLog) {
this.error(`TSServer log file: ${handle.tsServerLog}`);
}
/* __GDPR__
@@ -468,8 +464,8 @@ export default class TypeScriptServiceClient extends Disposable implements IType
return;
}
if (handle.tsServerLogFile) {
this.info(`TSServer log file: ${handle.tsServerLogFile}`);
if (handle.tsServerLog) {
this.info(`TSServer log file: ${handle.tsServerLog}`);
}
this.serviceExited(!this.isRestarting);
this.isRestarting = false;
@@ -511,25 +507,33 @@ export default class TypeScriptServiceClient extends Disposable implements IType
return false;
}
if (this.serverState.type !== ServerState.Type.Running || !this.serverState.server.tsServerLogFile) {
if (this.serverState.type !== ServerState.Type.Running || !this.serverState.server.tsServerLog) {
vscode.window.showWarningMessage(vscode.l10n.t("TS Server has not started logging."));
return false;
}
try {
const doc = await vscode.workspace.openTextDocument(this.serverState.server.tsServerLogFile);
await vscode.window.showTextDocument(doc);
return true;
} catch {
// noop
}
switch (this.serverState.server.tsServerLog.type) {
case 'output': {
this.serverState.server.tsServerLog.output.show();
return true;
}
case 'file': {
try {
const doc = await vscode.workspace.openTextDocument(this.serverState.server.tsServerLog.uri);
await vscode.window.showTextDocument(doc);
return true;
} catch {
// noop
}
try {
await vscode.commands.executeCommand('revealFileInOS', this.serverState.server.tsServerLogFile);
return true;
} catch {
vscode.window.showWarningMessage(vscode.l10n.t("Could not open TS Server log file"));
return false;
try {
await vscode.commands.executeCommand('revealFileInOS', this.serverState.server.tsServerLog.uri);
return true;
} catch {
vscode.window.showWarningMessage(vscode.l10n.t("Could not open TS Server log file"));
return false;
}
}
}
}
@@ -661,7 +665,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType
});
} else {
const args = previousState.type === ServerState.Type.Errored && previousState.error instanceof TypeScriptServerError
? getReportIssueArgsForError(previousState.error, previousState.tsServerLogFile, this.pluginManager.plugins)
? getReportIssueArgsForError(previousState.error, previousState.tsServerLog, this.pluginManager.plugins)
: undefined;
vscode.commands.executeCommand('workbench.action.openIssueReporter', args);
}
@@ -882,7 +886,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType
if (this.serverState.type === ServerState.Type.Running) {
this.info('Killing TS Server');
const logfile = this.serverState.server.tsServerLogFile;
const logfile = this.serverState.server.tsServerLog;
this.serverState.server.kill();
if (error instanceof TypeScriptServerError) {
this.serverState = new ServerState.Errored(error, logfile);
@@ -1020,7 +1024,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType
function getReportIssueArgsForError(
error: TypeScriptServerError,
logPath: vscode.Uri | undefined,
tsServerLog: TsServerLog | undefined,
globalPlugins: readonly TypeScriptServerPlugin[],
): { extensionId: string; issueTitle: string; issueBody: string } | undefined {
if (!error.serverStack || !error.serverMessage) {
@@ -1050,12 +1054,12 @@ function getReportIssueArgsForError(
);
}
if (logPath) {
if (tsServerLog?.type === 'file') {
sections.push(`**TS Server Log**
❗️ Please review and upload this log file to help us diagnose this crash:
\`${logPath.fsPath}\`
\`${tsServerLog.uri.fsPath}\`
The log file may contain personal data, including full paths and source code from your workspace. You can scrub the log file to remove paths or other personal information.
`);