hook up unhandled extension errors with extension telemetry (#163424)

* hook up unhandled extension errors with extension telemetry

* fix layering

* forward unhandled language provider errors to extension telemetry loggers
This commit is contained in:
Johannes Rieken
2022-10-13 15:04:08 +02:00
committed by GitHub
parent 17c7a08962
commit 0dd2dc89d2
7 changed files with 110 additions and 65 deletions

View File

@@ -167,7 +167,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
const extHostEditorInsets = rpcProtocol.set(ExtHostContext.ExtHostEditorInsets, new ExtHostEditorInsets(rpcProtocol.getProxy(MainContext.MainThreadEditorInsets), extHostEditors, initData.remote));
const extHostDiagnostics = rpcProtocol.set(ExtHostContext.ExtHostDiagnostics, new ExtHostDiagnostics(rpcProtocol, extHostLogService, extHostFileSystemInfo));
const extHostLanguages = rpcProtocol.set(ExtHostContext.ExtHostLanguages, new ExtHostLanguages(rpcProtocol, extHostDocuments, extHostCommands.converter, uriTransformer));
const extHostLanguageFeatures = rpcProtocol.set(ExtHostContext.ExtHostLanguageFeatures, new ExtHostLanguageFeatures(rpcProtocol, uriTransformer, extHostDocuments, extHostCommands, extHostDiagnostics, extHostLogService, extHostApiDeprecation));
const extHostLanguageFeatures = rpcProtocol.set(ExtHostContext.ExtHostLanguageFeatures, new ExtHostLanguageFeatures(rpcProtocol, uriTransformer, extHostDocuments, extHostCommands, extHostDiagnostics, extHostLogService, extHostApiDeprecation, extHostTelemetry));
const extHostFileSystem = rpcProtocol.set(ExtHostContext.ExtHostFileSystem, new ExtHostFileSystem(rpcProtocol, extHostLanguageFeatures));
const extHostFileSystemEvent = rpcProtocol.set(ExtHostContext.ExtHostFileSystemEventService, new ExtHostFileSystemEventService(rpcProtocol, extHostLogService, extHostDocumentsAndEditors));
const extHostQuickOpen = rpcProtocol.set(ExtHostContext.ExtHostQuickOpen, createExtHostQuickOpen(rpcProtocol, extHostWorkspace, extHostCommands));

View File

@@ -34,6 +34,7 @@ import { StopWatch } from 'vs/base/common/stopwatch';
import { isCancellationError, NotImplementedError } from 'vs/base/common/errors';
import { raceCancellationError } from 'vs/base/common/async';
import { isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions';
import { IExtHostTelemetry } from 'vs/workbench/api/common/extHostTelemetry';
// --- adapter
@@ -1831,31 +1832,20 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF
private static _handlePool: number = 0;
private readonly _uriTransformer: IURITransformer;
private readonly _proxy: extHostProtocol.MainThreadLanguageFeaturesShape;
private readonly _documents: ExtHostDocuments;
private readonly _commands: ExtHostCommands;
private readonly _diagnostics: ExtHostDiagnostics;
private readonly _adapter = new Map<number, AdapterData>();
private readonly _logService: ILogService;
private readonly _apiDeprecation: IExtHostApiDeprecationService;
constructor(
mainContext: extHostProtocol.IMainContext,
uriTransformer: IURITransformer,
documents: ExtHostDocuments,
commands: ExtHostCommands,
diagnostics: ExtHostDiagnostics,
logService: ILogService,
apiDeprecationService: IExtHostApiDeprecationService,
private readonly _uriTransformer: IURITransformer,
private readonly _documents: ExtHostDocuments,
private readonly _commands: ExtHostCommands,
private readonly _diagnostics: ExtHostDiagnostics,
private readonly _logService: ILogService,
private readonly _apiDeprecation: IExtHostApiDeprecationService,
private readonly _extensionTelemetry: IExtHostTelemetry
) {
this._uriTransformer = uriTransformer;
this._proxy = mainContext.getProxy(extHostProtocol.MainContext.MainThreadLanguageFeatures);
this._documents = documents;
this._commands = commands;
this._diagnostics = diagnostics;
this._logService = logService;
this._apiDeprecation = apiDeprecationService;
}
private _transformDocumentSelector(selector: vscode.DocumentSelector): Array<extHostProtocol.IDocumentFilterDto> {
@@ -1898,6 +1888,8 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF
if (!isCancellationError(err)) {
this._logService.error(`[${data.extension.identifier.value}] provider FAILED`);
this._logService.error(err);
this._extensionTelemetry.onExtensionError(data.extension.identifier, err);
}
}).finally(() => {
if (!doNotLog) {

View File

@@ -10,7 +10,7 @@ import { ExtHostTelemetryShape } from 'vs/workbench/api/common/extHost.protocol'
import { TelemetryLevel } from 'vs/platform/telemetry/common/telemetry';
import { ILogger, ILoggerService } from 'vs/platform/log/common/log';
import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService';
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { UIKind } from 'vs/workbench/services/extensions/common/extensionHostProtocol';
import { getRemoteName } from 'vs/platform/remote/common/remoteHosts';
import { cleanData, cleanRemoteAuthority } from 'vs/platform/telemetry/common/telemetryUtils';
@@ -107,6 +107,15 @@ export class ExtHostTelemetry implements ExtHostTelemetryShape {
}
this._onDidChangeTelemetryConfiguration.fire(this.getTelemetryDetails());
}
onExtensionError(extension: ExtensionIdentifier, error: Error): boolean {
const logger = this._telemetryLoggers.get(extension.value);
if (!logger) {
return false;
}
logger.logError(error);
return true;
}
}
export class ExtHostTelemetryLogger {

View File

@@ -11,16 +11,17 @@ import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc';
import { MainContext, MainThreadConsoleShape } from 'vs/workbench/api/common/extHost.protocol';
import { IExtensionHostInitData } from 'vs/workbench/services/extensions/common/extensionHostProtocol';
import { RPCProtocol } from 'vs/workbench/services/extensions/common/rpcProtocol';
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { ILogService } from 'vs/platform/log/common/log';
import { getSingletonServiceDescriptors } from 'vs/platform/instantiation/common/extensions';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService';
import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { IExtHostRpcService, ExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
import { IURITransformerService, URITransformerService } from 'vs/workbench/api/common/extHostUriTransformerService';
import { IExtHostExtensionService, IHostUtils } from 'vs/workbench/api/common/extHostExtensionService';
import { IExtHostTelemetry } from 'vs/workbench/api/common/extHostTelemetry';
export interface IExitFn {
(code?: number): any;
@@ -30,6 +31,75 @@ export interface IConsolePatchFn {
(mainThreadConsole: MainThreadConsoleShape): any;
}
abstract class ErrorHandler {
static {
// increase number of stack frames (from 10, https://github.com/v8/v8/wiki/Stack-Trace-API)
Error.stackTraceLimit = 100;
}
static async installEarlyHandler(accessor: ServicesAccessor): Promise<void> {
// does NOT dependent of extension information, can be installed immediately, and simply forwards
// to the log service and main thread errors
const logService = accessor.get(ILogService);
const rpcService = accessor.get(IExtHostRpcService);
const mainThreadErrors = rpcService.getProxy(MainContext.MainThreadErrors);
errors.setUnexpectedErrorHandler(err => {
logService.error(err);
const data = errors.transformErrorForSerialization(err);
mainThreadErrors.$onUnexpectedError(data);
});
}
static async installFullHandler(accessor: ServicesAccessor): Promise<void> {
// uses extension knowledges to correlate errors with extensions
const logService = accessor.get(ILogService);
const rpcService = accessor.get(IExtHostRpcService);
const extensionService = accessor.get(IExtHostExtensionService);
const extensionTelemetry = accessor.get(IExtHostTelemetry);
const mainThreadExtensions = rpcService.getProxy(MainContext.MainThreadExtensionService);
const mainThreadErrors = rpcService.getProxy(MainContext.MainThreadErrors);
const map = await extensionService.getExtensionPathIndex();
const extensionErrors = new WeakMap<Error, ExtensionIdentifier | undefined>();
// set the prepareStackTrace-handle and use it as a side-effect to associate errors
// with extensions - this works by looking up callsites in the extension path index
(<any>Error).prepareStackTrace = (error: Error, stackTrace: errors.V8CallSite[]) => {
let stackTraceMessage = '';
let extension: IExtensionDescription | undefined;
let fileName: string | null;
for (const call of stackTrace) {
stackTraceMessage += `\n\tat ${call.toString()}`;
fileName = call.getFileName();
if (!extension && fileName) {
extension = map.findSubstr(URI.file(fileName));
}
}
extensionErrors.set(error, extension?.identifier);
return `${error.name || 'Error'}: ${error.message || ''}${stackTraceMessage}`;
};
errors.setUnexpectedErrorHandler(err => {
logService.error(err);
const data = errors.transformErrorForSerialization(err);
const extension = extensionErrors.get(err);
if (!extension) {
mainThreadErrors.$onUnexpectedError(data);
return;
}
mainThreadExtensions.$onExtensionRuntimeError(extension, data);
const reported = extensionTelemetry.onExtensionError(extension, err);
logService.trace('forwarded error to extension?', reported, extension);
});
}
}
export class ExtensionHostMain {
private readonly _hostUtils: IHostUtils;
@@ -59,6 +129,8 @@ export class ExtensionHostMain {
const instaService: IInstantiationService = new InstantiationService(services, true);
instaService.invokeFunction(ErrorHandler.installEarlyHandler);
// ugly self - inject
this._logService = instaService.invokeFunction(accessor => accessor.get(ILogService));
@@ -76,38 +148,8 @@ export class ExtensionHostMain {
this._extensionService = instaService.invokeFunction(accessor => accessor.get(IExtHostExtensionService));
this._extensionService.initialize();
// error forwarding and stack trace scanning
Error.stackTraceLimit = 100; // increase number of stack frames (from 10, https://github.com/v8/v8/wiki/Stack-Trace-API)
const extensionErrors = new WeakMap<Error, IExtensionDescription | undefined>();
this._extensionService.getExtensionPathIndex().then(map => {
(<any>Error).prepareStackTrace = (error: Error, stackTrace: errors.V8CallSite[]) => {
let stackTraceMessage = '';
let extension: IExtensionDescription | undefined;
let fileName: string;
for (const call of stackTrace) {
stackTraceMessage += `\n\tat ${call.toString()}`;
fileName = call.getFileName();
if (!extension && fileName) {
extension = map.findSubstr(URI.file(fileName));
}
}
extensionErrors.set(error, extension);
return `${error.name || 'Error'}: ${error.message || ''}${stackTraceMessage}`;
};
});
const mainThreadExtensions = this._rpcProtocol.getProxy(MainContext.MainThreadExtensionService);
const mainThreadErrors = this._rpcProtocol.getProxy(MainContext.MainThreadErrors);
errors.setUnexpectedErrorHandler(err => {
const data = errors.transformErrorForSerialization(err);
const extension = extensionErrors.get(err);
if (extension) {
mainThreadExtensions.$onExtensionRuntimeError(extension.identifier, data);
} else {
mainThreadErrors.$onUnexpectedError(data);
}
});
// install error handler that is extension-aware
instaService.invokeFunction(ErrorHandler.installFullHandler);
}
async asBrowserUri(uri: URI): Promise<URI> {

View File

@@ -58,6 +58,7 @@ import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeat
import { LanguageFeaturesService } from 'vs/editor/common/services/languageFeaturesService';
import { assertType } from 'vs/base/common/types';
import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity';
import { IExtHostTelemetry } from 'vs/workbench/api/common/extHostTelemetry';
function assertRejects(fn: () => Promise<any>, message: string = 'Expected rejection') {
return fn().then(() => assert.ok(false, message), _err => assert.ok(true));
@@ -167,7 +168,7 @@ suite('ExtHostLanguageFeatureCommands', function () {
const diagnostics = new ExtHostDiagnostics(rpcProtocol, new NullLogService(), new class extends mock<IExtHostFileSystemInfo>() { });
rpcProtocol.set(ExtHostContext.ExtHostDiagnostics, diagnostics);
extHost = new ExtHostLanguageFeatures(rpcProtocol, new URITransformerService(null), extHostDocuments, commands, diagnostics, new NullLogService(), NullApiDeprecationService);
extHost = new ExtHostLanguageFeatures(rpcProtocol, new URITransformerService(null), extHostDocuments, commands, diagnostics, new NullLogService(), NullApiDeprecationService, new class extends mock<IExtHostTelemetry>() { });
rpcProtocol.set(ExtHostContext.ExtHostLanguageFeatures, extHost);
mainThread = rpcProtocol.set(MainContext.MainThreadLanguageFeatures, insta.createInstance(MainThreadLanguageFeatures, rpcProtocol));

View File

@@ -55,6 +55,7 @@ import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeat
import { LanguageFeaturesService } from 'vs/editor/common/services/languageFeaturesService';
import { CodeActionTriggerSource } from 'vs/editor/contrib/codeAction/browser/types';
import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity';
import { IExtHostTelemetry } from 'vs/workbench/api/common/extHostTelemetry';
suite('ExtHostLanguageFeatures', function () {
@@ -121,7 +122,7 @@ suite('ExtHostLanguageFeatures', function () {
const diagnostics = new ExtHostDiagnostics(rpcProtocol, new NullLogService(), new class extends mock<IExtHostFileSystemInfo>() { });
rpcProtocol.set(ExtHostContext.ExtHostDiagnostics, diagnostics);
extHost = new ExtHostLanguageFeatures(rpcProtocol, new URITransformerService(null), extHostDocuments, commands, diagnostics, new NullLogService(), NullApiDeprecationService);
extHost = new ExtHostLanguageFeatures(rpcProtocol, new URITransformerService(null), extHostDocuments, commands, diagnostics, new NullLogService(), NullApiDeprecationService, new class extends mock<IExtHostTelemetry>() { });
rpcProtocol.set(ExtHostContext.ExtHostLanguageFeatures, extHost);
mainThread = rpcProtocol.set(MainContext.MainThreadLanguageFeatures, inst.createInstance(MainThreadLanguageFeatures, rpcProtocol));