Skip to content

Commit 0dd2dc8

Browse files
authored
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
1 parent 17c7a08 commit 0dd2dc8

File tree

7 files changed

+110
-65
lines changed

7 files changed

+110
-65
lines changed

src/vs/base/common/errors.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -123,15 +123,15 @@ export function transformErrorForSerialization(error: any): any {
123123

124124
// see https://github.com/v8/v8/wiki/Stack%20Trace%20API#basic-stack-traces
125125
export interface V8CallSite {
126-
getThis(): any;
127-
getTypeName(): string;
128-
getFunction(): string;
129-
getFunctionName(): string;
130-
getMethodName(): string;
131-
getFileName(): string;
132-
getLineNumber(): number;
133-
getColumnNumber(): number;
134-
getEvalOrigin(): string;
126+
getThis(): unknown;
127+
getTypeName(): string | null;
128+
getFunction(): Function | undefined;
129+
getFunctionName(): string | null;
130+
getMethodName(): string | null;
131+
getFileName(): string | null;
132+
getLineNumber(): number | null;
133+
getColumnNumber(): number | null;
134+
getEvalOrigin(): string | undefined;
135135
isToplevel(): boolean;
136136
isEval(): boolean;
137137
isNative(): boolean;

src/vs/workbench/api/common/extHost.api.impl.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
167167
const extHostEditorInsets = rpcProtocol.set(ExtHostContext.ExtHostEditorInsets, new ExtHostEditorInsets(rpcProtocol.getProxy(MainContext.MainThreadEditorInsets), extHostEditors, initData.remote));
168168
const extHostDiagnostics = rpcProtocol.set(ExtHostContext.ExtHostDiagnostics, new ExtHostDiagnostics(rpcProtocol, extHostLogService, extHostFileSystemInfo));
169169
const extHostLanguages = rpcProtocol.set(ExtHostContext.ExtHostLanguages, new ExtHostLanguages(rpcProtocol, extHostDocuments, extHostCommands.converter, uriTransformer));
170-
const extHostLanguageFeatures = rpcProtocol.set(ExtHostContext.ExtHostLanguageFeatures, new ExtHostLanguageFeatures(rpcProtocol, uriTransformer, extHostDocuments, extHostCommands, extHostDiagnostics, extHostLogService, extHostApiDeprecation));
170+
const extHostLanguageFeatures = rpcProtocol.set(ExtHostContext.ExtHostLanguageFeatures, new ExtHostLanguageFeatures(rpcProtocol, uriTransformer, extHostDocuments, extHostCommands, extHostDiagnostics, extHostLogService, extHostApiDeprecation, extHostTelemetry));
171171
const extHostFileSystem = rpcProtocol.set(ExtHostContext.ExtHostFileSystem, new ExtHostFileSystem(rpcProtocol, extHostLanguageFeatures));
172172
const extHostFileSystemEvent = rpcProtocol.set(ExtHostContext.ExtHostFileSystemEventService, new ExtHostFileSystemEventService(rpcProtocol, extHostLogService, extHostDocumentsAndEditors));
173173
const extHostQuickOpen = rpcProtocol.set(ExtHostContext.ExtHostQuickOpen, createExtHostQuickOpen(rpcProtocol, extHostWorkspace, extHostCommands));

src/vs/workbench/api/common/extHostLanguageFeatures.ts

Lines changed: 10 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import { StopWatch } from 'vs/base/common/stopwatch';
3434
import { isCancellationError, NotImplementedError } from 'vs/base/common/errors';
3535
import { raceCancellationError } from 'vs/base/common/async';
3636
import { isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions';
37+
import { IExtHostTelemetry } from 'vs/workbench/api/common/extHostTelemetry';
3738

3839
// --- adapter
3940

@@ -1831,31 +1832,20 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF
18311832

18321833
private static _handlePool: number = 0;
18331834

1834-
private readonly _uriTransformer: IURITransformer;
18351835
private readonly _proxy: extHostProtocol.MainThreadLanguageFeaturesShape;
1836-
private readonly _documents: ExtHostDocuments;
1837-
private readonly _commands: ExtHostCommands;
1838-
private readonly _diagnostics: ExtHostDiagnostics;
18391836
private readonly _adapter = new Map<number, AdapterData>();
1840-
private readonly _logService: ILogService;
1841-
private readonly _apiDeprecation: IExtHostApiDeprecationService;
18421837

18431838
constructor(
18441839
mainContext: extHostProtocol.IMainContext,
1845-
uriTransformer: IURITransformer,
1846-
documents: ExtHostDocuments,
1847-
commands: ExtHostCommands,
1848-
diagnostics: ExtHostDiagnostics,
1849-
logService: ILogService,
1850-
apiDeprecationService: IExtHostApiDeprecationService,
1840+
private readonly _uriTransformer: IURITransformer,
1841+
private readonly _documents: ExtHostDocuments,
1842+
private readonly _commands: ExtHostCommands,
1843+
private readonly _diagnostics: ExtHostDiagnostics,
1844+
private readonly _logService: ILogService,
1845+
private readonly _apiDeprecation: IExtHostApiDeprecationService,
1846+
private readonly _extensionTelemetry: IExtHostTelemetry
18511847
) {
1852-
this._uriTransformer = uriTransformer;
18531848
this._proxy = mainContext.getProxy(extHostProtocol.MainContext.MainThreadLanguageFeatures);
1854-
this._documents = documents;
1855-
this._commands = commands;
1856-
this._diagnostics = diagnostics;
1857-
this._logService = logService;
1858-
this._apiDeprecation = apiDeprecationService;
18591849
}
18601850

18611851
private _transformDocumentSelector(selector: vscode.DocumentSelector): Array<extHostProtocol.IDocumentFilterDto> {
@@ -1898,6 +1888,8 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF
18981888
if (!isCancellationError(err)) {
18991889
this._logService.error(`[${data.extension.identifier.value}] provider FAILED`);
19001890
this._logService.error(err);
1891+
1892+
this._extensionTelemetry.onExtensionError(data.extension.identifier, err);
19011893
}
19021894
}).finally(() => {
19031895
if (!doNotLog) {

src/vs/workbench/api/common/extHostTelemetry.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { ExtHostTelemetryShape } from 'vs/workbench/api/common/extHost.protocol'
1010
import { TelemetryLevel } from 'vs/platform/telemetry/common/telemetry';
1111
import { ILogger, ILoggerService } from 'vs/platform/log/common/log';
1212
import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService';
13-
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
13+
import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
1414
import { UIKind } from 'vs/workbench/services/extensions/common/extensionHostProtocol';
1515
import { getRemoteName } from 'vs/platform/remote/common/remoteHosts';
1616
import { cleanData, cleanRemoteAuthority } from 'vs/platform/telemetry/common/telemetryUtils';
@@ -107,6 +107,15 @@ export class ExtHostTelemetry implements ExtHostTelemetryShape {
107107
}
108108
this._onDidChangeTelemetryConfiguration.fire(this.getTelemetryDetails());
109109
}
110+
111+
onExtensionError(extension: ExtensionIdentifier, error: Error): boolean {
112+
const logger = this._telemetryLoggers.get(extension.value);
113+
if (!logger) {
114+
return false;
115+
}
116+
logger.logError(error);
117+
return true;
118+
}
110119
}
111120

112121
export class ExtHostTelemetryLogger {

src/vs/workbench/api/common/extensionHostMain.ts

Lines changed: 76 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,17 @@ import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc';
1111
import { MainContext, MainThreadConsoleShape } from 'vs/workbench/api/common/extHost.protocol';
1212
import { IExtensionHostInitData } from 'vs/workbench/services/extensions/common/extensionHostProtocol';
1313
import { RPCProtocol } from 'vs/workbench/services/extensions/common/rpcProtocol';
14-
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
14+
import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
1515
import { ILogService } from 'vs/platform/log/common/log';
1616
import { getSingletonServiceDescriptors } from 'vs/platform/instantiation/common/extensions';
1717
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
1818
import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService';
1919
import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService';
20-
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
20+
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
2121
import { IExtHostRpcService, ExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
2222
import { IURITransformerService, URITransformerService } from 'vs/workbench/api/common/extHostUriTransformerService';
2323
import { IExtHostExtensionService, IHostUtils } from 'vs/workbench/api/common/extHostExtensionService';
24+
import { IExtHostTelemetry } from 'vs/workbench/api/common/extHostTelemetry';
2425

2526
export interface IExitFn {
2627
(code?: number): any;
@@ -30,6 +31,75 @@ export interface IConsolePatchFn {
3031
(mainThreadConsole: MainThreadConsoleShape): any;
3132
}
3233

34+
abstract class ErrorHandler {
35+
36+
static {
37+
// increase number of stack frames (from 10, https://github.com/v8/v8/wiki/Stack-Trace-API)
38+
Error.stackTraceLimit = 100;
39+
}
40+
41+
static async installEarlyHandler(accessor: ServicesAccessor): Promise<void> {
42+
// does NOT dependent of extension information, can be installed immediately, and simply forwards
43+
// to the log service and main thread errors
44+
const logService = accessor.get(ILogService);
45+
const rpcService = accessor.get(IExtHostRpcService);
46+
const mainThreadErrors = rpcService.getProxy(MainContext.MainThreadErrors);
47+
48+
errors.setUnexpectedErrorHandler(err => {
49+
logService.error(err);
50+
const data = errors.transformErrorForSerialization(err);
51+
mainThreadErrors.$onUnexpectedError(data);
52+
});
53+
}
54+
55+
static async installFullHandler(accessor: ServicesAccessor): Promise<void> {
56+
// uses extension knowledges to correlate errors with extensions
57+
58+
const logService = accessor.get(ILogService);
59+
const rpcService = accessor.get(IExtHostRpcService);
60+
const extensionService = accessor.get(IExtHostExtensionService);
61+
const extensionTelemetry = accessor.get(IExtHostTelemetry);
62+
63+
const mainThreadExtensions = rpcService.getProxy(MainContext.MainThreadExtensionService);
64+
const mainThreadErrors = rpcService.getProxy(MainContext.MainThreadErrors);
65+
66+
const map = await extensionService.getExtensionPathIndex();
67+
const extensionErrors = new WeakMap<Error, ExtensionIdentifier | undefined>();
68+
69+
// set the prepareStackTrace-handle and use it as a side-effect to associate errors
70+
// with extensions - this works by looking up callsites in the extension path index
71+
(<any>Error).prepareStackTrace = (error: Error, stackTrace: errors.V8CallSite[]) => {
72+
let stackTraceMessage = '';
73+
let extension: IExtensionDescription | undefined;
74+
let fileName: string | null;
75+
for (const call of stackTrace) {
76+
stackTraceMessage += `\n\tat ${call.toString()}`;
77+
fileName = call.getFileName();
78+
if (!extension && fileName) {
79+
extension = map.findSubstr(URI.file(fileName));
80+
}
81+
}
82+
extensionErrors.set(error, extension?.identifier);
83+
return `${error.name || 'Error'}: ${error.message || ''}${stackTraceMessage}`;
84+
};
85+
86+
errors.setUnexpectedErrorHandler(err => {
87+
logService.error(err);
88+
89+
const data = errors.transformErrorForSerialization(err);
90+
const extension = extensionErrors.get(err);
91+
if (!extension) {
92+
mainThreadErrors.$onUnexpectedError(data);
93+
return;
94+
}
95+
96+
mainThreadExtensions.$onExtensionRuntimeError(extension, data);
97+
const reported = extensionTelemetry.onExtensionError(extension, err);
98+
logService.trace('forwarded error to extension?', reported, extension);
99+
});
100+
}
101+
}
102+
33103
export class ExtensionHostMain {
34104

35105
private readonly _hostUtils: IHostUtils;
@@ -59,6 +129,8 @@ export class ExtensionHostMain {
59129

60130
const instaService: IInstantiationService = new InstantiationService(services, true);
61131

132+
instaService.invokeFunction(ErrorHandler.installEarlyHandler);
133+
62134
// ugly self - inject
63135
this._logService = instaService.invokeFunction(accessor => accessor.get(ILogService));
64136

@@ -76,38 +148,8 @@ export class ExtensionHostMain {
76148
this._extensionService = instaService.invokeFunction(accessor => accessor.get(IExtHostExtensionService));
77149
this._extensionService.initialize();
78150

79-
// error forwarding and stack trace scanning
80-
Error.stackTraceLimit = 100; // increase number of stack frames (from 10, https://github.com/v8/v8/wiki/Stack-Trace-API)
81-
const extensionErrors = new WeakMap<Error, IExtensionDescription | undefined>();
82-
this._extensionService.getExtensionPathIndex().then(map => {
83-
(<any>Error).prepareStackTrace = (error: Error, stackTrace: errors.V8CallSite[]) => {
84-
let stackTraceMessage = '';
85-
let extension: IExtensionDescription | undefined;
86-
let fileName: string;
87-
for (const call of stackTrace) {
88-
stackTraceMessage += `\n\tat ${call.toString()}`;
89-
fileName = call.getFileName();
90-
if (!extension && fileName) {
91-
extension = map.findSubstr(URI.file(fileName));
92-
}
93-
94-
}
95-
extensionErrors.set(error, extension);
96-
return `${error.name || 'Error'}: ${error.message || ''}${stackTraceMessage}`;
97-
};
98-
});
99-
100-
const mainThreadExtensions = this._rpcProtocol.getProxy(MainContext.MainThreadExtensionService);
101-
const mainThreadErrors = this._rpcProtocol.getProxy(MainContext.MainThreadErrors);
102-
errors.setUnexpectedErrorHandler(err => {
103-
const data = errors.transformErrorForSerialization(err);
104-
const extension = extensionErrors.get(err);
105-
if (extension) {
106-
mainThreadExtensions.$onExtensionRuntimeError(extension.identifier, data);
107-
} else {
108-
mainThreadErrors.$onUnexpectedError(data);
109-
}
110-
});
151+
// install error handler that is extension-aware
152+
instaService.invokeFunction(ErrorHandler.installFullHandler);
111153
}
112154

113155
async asBrowserUri(uri: URI): Promise<URI> {

src/vs/workbench/api/test/browser/extHostApiCommands.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeat
5858
import { LanguageFeaturesService } from 'vs/editor/common/services/languageFeaturesService';
5959
import { assertType } from 'vs/base/common/types';
6060
import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity';
61+
import { IExtHostTelemetry } from 'vs/workbench/api/common/extHostTelemetry';
6162

6263
function assertRejects(fn: () => Promise<any>, message: string = 'Expected rejection') {
6364
return fn().then(() => assert.ok(false, message), _err => assert.ok(true));
@@ -167,7 +168,7 @@ suite('ExtHostLanguageFeatureCommands', function () {
167168
const diagnostics = new ExtHostDiagnostics(rpcProtocol, new NullLogService(), new class extends mock<IExtHostFileSystemInfo>() { });
168169
rpcProtocol.set(ExtHostContext.ExtHostDiagnostics, diagnostics);
169170

170-
extHost = new ExtHostLanguageFeatures(rpcProtocol, new URITransformerService(null), extHostDocuments, commands, diagnostics, new NullLogService(), NullApiDeprecationService);
171+
extHost = new ExtHostLanguageFeatures(rpcProtocol, new URITransformerService(null), extHostDocuments, commands, diagnostics, new NullLogService(), NullApiDeprecationService, new class extends mock<IExtHostTelemetry>() { });
171172
rpcProtocol.set(ExtHostContext.ExtHostLanguageFeatures, extHost);
172173

173174
mainThread = rpcProtocol.set(MainContext.MainThreadLanguageFeatures, insta.createInstance(MainThreadLanguageFeatures, rpcProtocol));

src/vs/workbench/api/test/browser/extHostLanguageFeatures.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeat
5555
import { LanguageFeaturesService } from 'vs/editor/common/services/languageFeaturesService';
5656
import { CodeActionTriggerSource } from 'vs/editor/contrib/codeAction/browser/types';
5757
import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity';
58+
import { IExtHostTelemetry } from 'vs/workbench/api/common/extHostTelemetry';
5859

5960
suite('ExtHostLanguageFeatures', function () {
6061

@@ -121,7 +122,7 @@ suite('ExtHostLanguageFeatures', function () {
121122
const diagnostics = new ExtHostDiagnostics(rpcProtocol, new NullLogService(), new class extends mock<IExtHostFileSystemInfo>() { });
122123
rpcProtocol.set(ExtHostContext.ExtHostDiagnostics, diagnostics);
123124

124-
extHost = new ExtHostLanguageFeatures(rpcProtocol, new URITransformerService(null), extHostDocuments, commands, diagnostics, new NullLogService(), NullApiDeprecationService);
125+
extHost = new ExtHostLanguageFeatures(rpcProtocol, new URITransformerService(null), extHostDocuments, commands, diagnostics, new NullLogService(), NullApiDeprecationService, new class extends mock<IExtHostTelemetry>() { });
125126
rpcProtocol.set(ExtHostContext.ExtHostLanguageFeatures, extHost);
126127

127128
mainThread = rpcProtocol.set(MainContext.MainThreadLanguageFeatures, inst.createInstance(MainThreadLanguageFeatures, rpcProtocol));

0 commit comments

Comments
 (0)