Path: blob/main/src/vs/workbench/api/common/extHostConsoleForwarder.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { IStackArgument } from '../../../base/common/console.js';6import { safeStringify } from '../../../base/common/objects.js';7import { MainContext, MainThreadConsoleShape } from './extHost.protocol.js';8import { IExtHostInitDataService } from './extHostInitDataService.js';9import { IExtHostRpcService } from './extHostRpcService.js';1011export abstract class AbstractExtHostConsoleForwarder {1213private readonly _mainThreadConsole: MainThreadConsoleShape;14private readonly _includeStack: boolean;15private readonly _logNative: boolean;1617constructor(18@IExtHostRpcService extHostRpc: IExtHostRpcService,19@IExtHostInitDataService initData: IExtHostInitDataService,20) {21this._mainThreadConsole = extHostRpc.getProxy(MainContext.MainThreadConsole);22this._includeStack = initData.consoleForward.includeStack;23this._logNative = initData.consoleForward.logNative;2425// Pass console logging to the outside so that we have it in the main side if told so26this._wrapConsoleMethod('info', 'log');27this._wrapConsoleMethod('log', 'log');28this._wrapConsoleMethod('warn', 'warn');29this._wrapConsoleMethod('debug', 'debug');30this._wrapConsoleMethod('error', 'error');31}3233/**34* Wraps a console message so that it is transmitted to the renderer. If35* native logging is turned on, the original console message will be written36* as well. This is needed since the console methods are "magic" in V8 and37* are the only methods that allow later introspection of logged variables.38*39* The wrapped property is not defined with `writable: false` to avoid40* throwing errors, but rather a no-op setting. See https://github.com/microsoft/vscode-extension-telemetry/issues/8841*/42private _wrapConsoleMethod(method: 'log' | 'info' | 'warn' | 'error' | 'debug', severity: 'log' | 'warn' | 'error' | 'debug') {43const that = this;44const original = console[method];4546Object.defineProperty(console, method, {47set: () => { },48get: () => function () {49that._handleConsoleCall(method, severity, original, arguments);50},51});52}5354private _handleConsoleCall(method: 'log' | 'info' | 'warn' | 'error' | 'debug', severity: 'log' | 'warn' | 'error' | 'debug', original: (...args: any[]) => void, args: IArguments): void {55this._mainThreadConsole.$logExtensionHostMessage({56type: '__$console',57severity,58arguments: safeStringifyArgumentsToArray(args, this._includeStack)59});60if (this._logNative) {61this._nativeConsoleLogMessage(method, original, args);62}63}6465protected abstract _nativeConsoleLogMessage(method: 'log' | 'info' | 'warn' | 'error' | 'debug', original: (...args: any[]) => void, args: IArguments): void;6667}6869const MAX_LENGTH = 100000;7071/**72* Prevent circular stringify and convert arguments to real array73*/74function safeStringifyArgumentsToArray(args: IArguments, includeStack: boolean): string {75const argsArray = [];7677// Massage some arguments with special treatment78if (args.length) {79for (let i = 0; i < args.length; i++) {80let arg = args[i];8182// Any argument of type 'undefined' needs to be specially treated because83// JSON.stringify will simply ignore those. We replace them with the string84// 'undefined' which is not 100% right, but good enough to be logged to console85if (typeof arg === 'undefined') {86arg = 'undefined';87}8889// Any argument that is an Error will be changed to be just the error stack/message90// itself because currently cannot serialize the error over entirely.91else if (arg instanceof Error) {92const errorObj = arg;93if (errorObj.stack) {94arg = errorObj.stack;95} else {96arg = errorObj.toString();97}98}99100argsArray.push(arg);101}102}103104// Add the stack trace as payload if we are told so. We remove the message and the 2 top frames105// to start the stacktrace where the console message was being written106if (includeStack) {107const stack = new Error().stack;108if (stack) {109argsArray.push({ __$stack: stack.split('\n').slice(3).join('\n') } satisfies IStackArgument);110}111}112113try {114const res = safeStringify(argsArray);115116if (res.length > MAX_LENGTH) {117return 'Output omitted for a large object that exceeds the limits';118}119120return res;121} catch (error) {122return `Output omitted for an object that cannot be inspected ('${error.toString()}')`;123}124}125126127