mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-25 02:58:56 +01:00
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:
@@ -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));
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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));
|
||||
|
||||
Reference in New Issue
Block a user