diff --git a/src/vs/platform/extensions/common/extensions.ts b/src/vs/platform/extensions/common/extensions.ts index cee0043bad6..687fd50dc36 100644 --- a/src/vs/platform/extensions/common/extensions.ts +++ b/src/vs/platform/extensions/common/extensions.ts @@ -447,6 +447,20 @@ export class ExtensionIdentifierMap { } } +/** + * An error that is clearly from an extension, identified by the `ExtensionIdentifier` + */ +export class ExtensionError extends Error { + + readonly extension: ExtensionIdentifier; + + constructor(extensionIdentifier: ExtensionIdentifier, cause: Error, message?: string) { + super(`Error in extension ${ExtensionIdentifier.toKey(extensionIdentifier)}: ${message ?? cause.message}`, { cause }); + this.name = 'ExtensionError'; + this.extension = extensionIdentifier; + } +} + export interface IRelaxedExtensionDescription extends IRelaxedExtensionManifest { id?: string; identifier: ExtensionIdentifier; diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 85ff1aa52b2..868906908ea 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -14,7 +14,7 @@ import { TextEditorCursorStyle } from '../../../editor/common/config/editorOptio import { score, targetsNotebooks } from '../../../editor/common/languageSelector.js'; import * as languageConfiguration from '../../../editor/common/languages/languageConfiguration.js'; import { OverviewRulerLane } from '../../../editor/common/model.js'; -import { ExtensionIdentifierSet, IExtensionDescription } from '../../../platform/extensions/common/extensions.js'; +import { ExtensionError, ExtensionIdentifierSet, IExtensionDescription } from '../../../platform/extensions/common/extensions.js'; import * as files from '../../../platform/files/common/files.js'; import { ServicesAccessor } from '../../../platform/instantiation/common/instantiation.js'; import { ILogService, ILoggerService, LogLevel } from '../../../platform/log/common/log.js'; @@ -245,8 +245,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I try { listener.call(thisArgs, e); } catch (err) { - errors.onUnexpectedExternalError(new Error(`[ExtensionListenerError] Extension '${extension.identifier.value}' FAILED to handle event: ${err.toString()}`, { cause: err })); - extHostTelemetry.onExtensionError(extension.identifier, err); + errors.onUnexpectedExternalError(new ExtensionError(extension.identifier, err, 'FAILED to handle event')); } }); disposables?.push(handle); diff --git a/src/vs/workbench/api/common/extensionHostMain.ts b/src/vs/workbench/api/common/extensionHostMain.ts index 6c93b88e428..d0180c1a081 100644 --- a/src/vs/workbench/api/common/extensionHostMain.ts +++ b/src/vs/workbench/api/common/extensionHostMain.ts @@ -11,7 +11,7 @@ import { IMessagePassingProtocol } from '../../../base/parts/ipc/common/ipc.js'; import { MainContext, MainThreadConsoleShape } from './extHost.protocol.js'; import { IExtensionHostInitData } from '../../services/extensions/common/extensionHostProtocol.js'; import { RPCProtocol } from '../../services/extensions/common/rpcProtocol.js'; -import { ExtensionIdentifier, IExtensionDescription } from '../../../platform/extensions/common/extensions.js'; +import { ExtensionError, ExtensionIdentifier, IExtensionDescription } from '../../../platform/extensions/common/extensions.js'; import { ILogService } from '../../../platform/log/common/log.js'; import { getSingletonServiceDescriptors } from '../../../platform/instantiation/common/extensions.js'; import { ServiceCollection } from '../../../platform/instantiation/common/serviceCollection.js'; @@ -119,15 +119,24 @@ export abstract class ErrorHandler { logService.error(err); const errorData = errors.transformErrorForSerialization(err); - const stackData = extensionErrors.get(err); - if (!stackData?.extensionIdentifier) { - mainThreadErrors.$onUnexpectedError(errorData); - return; + + let extension: ExtensionIdentifier | undefined; + if (err instanceof ExtensionError) { + extension = err.extension; + } else { + const stackData = extensionErrors.get(err); + extension = stackData?.extensionIdentifier; } - mainThreadExtensions.$onExtensionRuntimeError(stackData.extensionIdentifier, errorData); - const reported = extensionTelemetry.onExtensionError(stackData.extensionIdentifier, err); - logService.trace('forwarded error to extension?', reported, stackData); + if (extension) { + mainThreadExtensions.$onExtensionRuntimeError(extension, errorData); + const reported = extensionTelemetry.onExtensionError(extension, err); + logService.trace('forwarded error to extension?', reported, extension); + } + }); + + errors.errorHandler.addListener(err => { + mainThreadErrors.$onUnexpectedError(err); }); } } diff --git a/src/vs/workbench/api/test/common/extensionHostMain.test.ts b/src/vs/workbench/api/test/common/extensionHostMain.test.ts index cda646f07cf..aa96a67b445 100644 --- a/src/vs/workbench/api/test/common/extensionHostMain.test.ts +++ b/src/vs/workbench/api/test/common/extensionHostMain.test.ts @@ -14,7 +14,7 @@ import { ExtensionIdentifier, IExtensionDescription } from '../../../../platform import { InstantiationService } from '../../../../platform/instantiation/common/instantiationService.js'; import { ServiceCollection } from '../../../../platform/instantiation/common/serviceCollection.js'; import { ILogService, NullLogService } from '../../../../platform/log/common/log.js'; -import { MainThreadExtensionServiceShape } from '../../common/extHost.protocol.js'; +import { MainThreadErrorsShape, MainThreadExtensionServiceShape } from '../../common/extHost.protocol.js'; import { ExtensionPaths, IExtHostExtensionService } from '../../common/extHostExtensionService.js'; import { IExtHostRpcService } from '../../common/extHostRpcService.js'; import { IExtHostTelemetry } from '../../common/extHostTelemetry.js'; @@ -30,9 +30,12 @@ suite('ExtensionHostMain#ErrorHandler - Wrapping prepareStackTrace can cause slo } const extensionsIndex = TernarySearchTree.forUris(); - const mainThreadExtensionsService = new class extends mock() { + const mainThreadExtensionsService = new class extends mock() implements MainThreadErrorsShape { override $onExtensionRuntimeError(extensionId: ExtensionIdentifier, data: SerializedError): void { + } + $onUnexpectedError(err: any | SerializedError): void { + } };