Make sure we also log the typescript error properties on fatal error telemetry events

Fixes #86205

We already log error metadata for failed requests. However we don't include this on the fatalError event. This makes investigation of these errors difficult
This commit is contained in:
Matt Bierner 2019-12-04 10:57:19 -08:00
parent 9fd89ca349
commit 57455124b5
3 changed files with 61 additions and 31 deletions

View file

@ -60,7 +60,7 @@ export interface ITypeScriptServer {
}
export interface TsServerDelegate {
onFatalError(command: string): void;
onFatalError(command: string, error: Error): void;
}
export interface TsServerProcess {
@ -222,21 +222,13 @@ export class ProcessBasedTsServer extends Disposable implements ITypeScriptServe
if (!executeInfo.token || !executeInfo.token.isCancellationRequested) {
/* __GDPR__
"languageServiceErrorResponse" : {
"command" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"message" : { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" },
"stack" : { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" },
"errortext" : { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" },
"${include}": [
"${TypeScriptCommonProperties}"
"${TypeScriptCommonProperties}",
"${TypeScriptRequestErrorProperties}"
]
}
*/
this._telemetryReporter.logTelemetry('languageServiceErrorResponse', {
command: err.serverCommand,
message: err.serverMessage || '',
stack: err.serverStack || '',
errortext: err.serverErrorText || '',
});
this._telemetryReporter.logTelemetry('languageServiceErrorResponse', err.telemetry);
}
}
@ -367,9 +359,8 @@ export class SyntaxRoutingTsServer extends Disposable implements ITypeScriptServ
} else if (SyntaxRoutingTsServer.sharedCommands.has(command)) {
// Dispatch to both server but only return from syntax one
const enum RequestState { Unresolved, Resolved, Errored }
let syntaxRequestState = RequestState.Unresolved;
let semanticRequestState = RequestState.Unresolved;
let syntaxRequestState: RequestState.State = RequestState.Unresolved;
let semanticRequestState: RequestState.State = RequestState.Unresolved;
// Also make sure we never cancel requests to just one server
let token: vscode.CancellationToken | undefined = undefined;
@ -394,16 +385,16 @@ export class SyntaxRoutingTsServer extends Disposable implements ITypeScriptServ
semanticRequest
.then(result => {
semanticRequestState = RequestState.Resolved;
if (syntaxRequestState === RequestState.Errored) {
if (syntaxRequestState.type === RequestState.Type.Errored) {
// We've gone out of sync
this._delegate.onFatalError(command);
this._delegate.onFatalError(command, syntaxRequestState.err);
}
return result;
}, err => {
semanticRequestState = RequestState.Errored;
semanticRequestState = new RequestState.Errored(err);
if (syntaxRequestState === RequestState.Resolved) {
// We've gone out of sync
this._delegate.onFatalError(command);
this._delegate.onFatalError(command, err);
}
throw err;
});
@ -413,16 +404,16 @@ export class SyntaxRoutingTsServer extends Disposable implements ITypeScriptServ
syntaxRequest
.then(result => {
syntaxRequestState = RequestState.Resolved;
if (semanticRequestState === RequestState.Errored) {
if (semanticRequestState.type === RequestState.Type.Errored) {
// We've gone out of sync
this._delegate.onFatalError(command);
this._delegate.onFatalError(command, semanticRequestState.err);
}
return result;
}, err => {
syntaxRequestState = RequestState.Errored;
syntaxRequestState = new RequestState.Errored(err);
if (semanticRequestState === RequestState.Resolved) {
// We've gone out of sync
this._delegate.onFatalError(command);
this._delegate.onFatalError(command, err);
}
throw err;
});
@ -433,3 +424,21 @@ export class SyntaxRoutingTsServer extends Disposable implements ITypeScriptServ
}
}
}
namespace RequestState {
export const enum Type { Unresolved, Resolved, Errored }
export const Unresolved = { type: Type.Unresolved } as const;
export const Resolved = { type: Type.Resolved } as const;
export class Errored {
readonly type = Type.Errored;
constructor(
public readonly err: Error
) { }
}
export type State = typeof Unresolved | typeof Resolved | Errored;
}

View file

@ -7,6 +7,7 @@ import * as Proto from '../protocol';
import { escapeRegExp } from '../utils/regexp';
import { TypeScriptVersion } from '../utils/versionProvider';
export class TypeScriptServerError extends Error {
public static create(
serverId: string,
@ -31,6 +32,23 @@ export class TypeScriptServerError extends Error {
public get serverCommand() { return this.response.command; }
public get telemetry() {
/* __GDPR__FRAGMENT__
"TypeScriptRequestErrorProperties" : {
"command" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"message" : { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" },
"stack" : { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" },
"errortext" : { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" }
}
*/
return {
command: this.serverCommand,
message: this.serverMessage || '',
stack: this.serverStack || '',
errortext: this.serverErrorText || '',
} as const;
}
/**
* Given a `errorText` from a tsserver request indicating failure in handling a request,
* prepares a payload for telemetry-logging.

View file

@ -11,7 +11,9 @@ import BufferSyncSupport from './features/bufferSyncSupport';
import { DiagnosticKind, DiagnosticsManager } from './features/diagnostics';
import * as Proto from './protocol';
import { ITypeScriptServer } from './tsServer/server';
import { ITypeScriptServiceClient, ServerResponse, TypeScriptRequests, ExecConfig } from './typescriptService';
import { TypeScriptServerError } from './tsServer/serverError';
import { TypeScriptServerSpawner } from './tsServer/spawner';
import { ExecConfig, ITypeScriptServiceClient, ServerResponse, TypeScriptRequests } from './typescriptService';
import API from './utils/api';
import { TsServerLogLevel, TypeScriptServiceConfiguration } from './utils/configuration';
import { Disposable } from './utils/dispose';
@ -25,7 +27,6 @@ import Tracer from './utils/tracer';
import { inferredProjectConfig } from './utils/tsconfig';
import { TypeScriptVersionPicker } from './utils/versionPicker';
import { TypeScriptVersion, TypeScriptVersionProvider } from './utils/versionProvider';
import { TypeScriptServerSpawner } from './tsServer/spawner';
const localize = nls.loadMessageBundle();
@ -314,7 +315,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType
this.onDidChangeTypeScriptVersion(currentVersion);
let mytoken = ++this.token;
const handle = this.typescriptServerSpawner.spawn(currentVersion, this.configuration, this.pluginManager, {
onFatalError: (command) => this.fatalError(command),
onFatalError: (command, err) => this.fatalError(command, err),
});
this.serverState = new ServerState.Running(handle, apiVersion, undefined, true);
this.lastStart = Date.now();
@ -670,7 +671,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType
}
if (config?.nonRecoverable) {
execution.catch(() => this.fatalError(command));
execution.catch(err => this.fatalError(command, err));
}
return execution;
@ -704,14 +705,16 @@ export default class TypeScriptServiceClient extends Disposable implements IType
return this.bufferSyncSupport.interuptGetErr(f);
}
private fatalError(command: string): void {
private fatalError(command: string, error: Error): void {
/* __GDPR__
"fatalError" : {
"${include}": [ "${TypeScriptCommonProperties}" ],
"command" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
"${include}": [
"${TypeScriptCommonProperties}",
"${TypeScriptRequestErrorProperties}"
]
}
*/
this.logTelemetry('fatalError', { command });
this.logTelemetry('fatalError', { command, ...(error instanceof TypeScriptServerError ? error.telemetry : {}) });
console.error(`A non-recoverable error occured while executing tsserver command: ${command}`);
if (this.serverState.type === ServerState.Type.Running) {