From 2f3c2541e2b43366f94472c56c7a3284b44361fe Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 11 Jan 2023 20:02:58 +0100 Subject: [PATCH 1/4] persist log level of all loggers --- .../sharedProcess/sharedProcessMain.ts | 2 +- src/vs/platform/log/common/log.ts | 86 +++++++++++-------- src/vs/platform/log/common/logIpc.ts | 16 +++- .../electron-main/sharedProcess.ts | 4 +- .../sharedProcess/node/sharedProcess.ts | 4 +- .../test/common/telemetryLogAppender.test.ts | 1 + src/vs/platform/window/common/window.ts | 1 + .../windows/electron-main/windowImpl.ts | 4 +- .../electron-main/windowsMainService.ts | 4 +- .../api/browser/mainThreadLogService.ts | 11 +-- .../api/common/extHostLoggerService.ts | 4 +- src/vs/workbench/api/common/extHostOutput.ts | 12 ++- .../contrib/logs/browser/logs.contribution.ts | 4 - .../contrib/logs/common/logLevelService.ts | 55 ------------ .../contrib/logs/common/logsActions.ts | 61 ++++++------- .../logs/electron-sandbox/logLevelService.ts | 51 ----------- .../electron-sandbox/logs.contribution.ts | 5 -- .../remote/common/remote.contribution.ts | 32 +++++-- .../electron-sandbox/desktop.main.ts | 2 +- .../browser/webWorkerExtensionHost.ts | 4 +- .../common/extensionHostProtocol.ts | 1 + .../extensions/common/remoteExtensionHost.ts | 4 +- .../localProcessExtensionHost.ts | 4 +- .../electron-browser/workbenchTestServices.ts | 1 + 24 files changed, 151 insertions(+), 222 deletions(-) delete mode 100644 src/vs/workbench/contrib/logs/common/logLevelService.ts delete mode 100644 src/vs/workbench/contrib/logs/electron-sandbox/logLevelService.ts diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts index d9b35e475e9..20966f711f0 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts @@ -216,7 +216,7 @@ class SharedProcessMain extends Disposable { // Logger const logLevelClient = new LogLevelChannelClient(this.server.getChannel('logLevel', mainRouter)); - const loggerService = new LoggerChannelClient(this.configuration.logLevel, logLevelClient.onDidChangeLogLevel, mainProcessService.getChannel('logger')); + const loggerService = new LoggerChannelClient(this.configuration.logLevel, logLevelClient.onDidChangeLogLevel, this.configuration.logLevels, mainProcessService.getChannel('logger')); services.set(ILoggerService, loggerService); // Log diff --git a/src/vs/platform/log/common/log.ts b/src/vs/platform/log/common/log.ts index fa75115b7b6..baadc017846 100644 --- a/src/vs/platform/log/common/log.ts +++ b/src/vs/platform/log/common/log.ts @@ -117,6 +117,17 @@ export interface ILoggerService { */ getLogger(resource: URI): ILogger | undefined; + + /** + * all resources log levels + */ + readonly logLevels: Iterable<[URI, LogLevel]>; + + /** + * An event which fires when the log level of the resource changes. + */ + readonly onDidChangeLogLevel: Event<[URI, LogLevel]>; + /** * Set log level for a logger. */ @@ -485,74 +496,81 @@ export class LogService extends Disposable implements ILogService { } } -interface ILoggerItem { - readonly logger: ILogger; - logLevel: LogLevel | undefined; -} - export abstract class AbstractLoggerService extends Disposable implements ILoggerService { declare readonly _serviceBrand: undefined; - private readonly loggerItems = new ResourceMap(); + private readonly _loggers = new ResourceMap(); + + private readonly _logLevels = new ResourceMap(); + get logLevels() { return this._logLevels.entries(); } + private _onDidChangeLogLevel = this._register(new Emitter<[URI, LogLevel]>); + readonly onDidChangeLogLevel = this._onDidChangeLogLevel.event; constructor( private logLevel: LogLevel, onDidChangeLogLevel: Event, + logLevels?: Iterable<[URI, LogLevel]>, ) { super(); - this._register(onDidChangeLogLevel(logLevel => this.setLevel(logLevel))); + this._register(onDidChangeLogLevel(logLevel => this.setGlobalLogLevel(logLevel))); + if (logLevels) { + for (const [resource, logLevel] of logLevels) { + this._logLevels.set(resource, logLevel); + } + } } getLoggers(): ILogger[] { - return [...this.loggerItems.values()].map(({ logger }) => logger); + return [...this._loggers.values()]; } getLogger(resource: URI): ILogger | undefined { - return this.loggerItems.get(resource)?.logger; + return this._loggers.get(resource); } createLogger(resource: URI, options?: ILoggerOptions, logLevel?: LogLevel): ILogger { - let logger = this.loggerItems.get(resource)?.logger; + let logger = this._loggers.get(resource); if (!logger) { logLevel = options?.always ? LogLevel.Trace : logLevel; - logger = this.doCreateLogger(resource, logLevel ?? this.logLevel, options); - this.loggerItems.set(resource, { logger, logLevel }); + logger = this.doCreateLogger(resource, logLevel ?? this.getLogLevel(resource) ?? this.logLevel, options); + this._loggers.set(resource, logger); + if (logLevel !== undefined) { + this._logLevels.set(resource, logLevel); + } } return logger; } - setLevel(logLevel: LogLevel): void; - setLevel(resource: URI, logLevel: LogLevel): void; - setLevel(arg1: any, arg2?: any): void { - const resource = URI.isUri(arg1) ? arg1 : undefined; - const logLevel = resource ? arg2 : arg1; - - if (resource) { - const logger = this.loggerItems.get(resource); - if (logger && logger.logLevel !== logLevel) { - logger.logLevel = logLevel; - logger.logger.setLevel(logLevel); + setLevel(resource: URI, logLevel: LogLevel): void { + if (logLevel !== this._logLevels.get(resource)) { + if (logLevel === this.logLevel) { + this._logLevels.delete(resource); + } else { + this._logLevels.set(resource, logLevel); } - } else { - this.logLevel = logLevel; - this.loggerItems.forEach(({ logLevel, logger }) => { - if (logLevel === undefined) { - logger.setLevel(this.logLevel); - } - }); + this._loggers.get(resource)?.setLevel(logLevel); + this._onDidChangeLogLevel.fire([resource, logLevel]); } + } + protected setGlobalLogLevel(logLevel: LogLevel): void { + this.logLevel = logLevel; + for (const [resource, logger] of this._loggers.entries()) { + if (this._logLevels.get(resource) === undefined) { + logger.setLevel(this.logLevel); + } + } } getLogLevel(resource: URI): LogLevel | undefined { - const logger = this.loggerItems.get(resource); - return logger?.logLevel; + return this._logLevels.get(resource); } override dispose(): void { - this.loggerItems.forEach(({ logger }) => logger.dispose()); - this.loggerItems.clear(); + this._loggers.forEach(logger => logger.dispose()); + this._loggers.clear(); + this._logLevels.clear(); super.dispose(); } diff --git a/src/vs/platform/log/common/logIpc.ts b/src/vs/platform/log/common/logIpc.ts index 84e6b347ddb..6e4887b1b0a 100644 --- a/src/vs/platform/log/common/logIpc.ts +++ b/src/vs/platform/log/common/logIpc.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Event } from 'vs/base/common/event'; -import { URI } from 'vs/base/common/uri'; +import { URI, UriComponents } from 'vs/base/common/uri'; import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; import { AbstractLoggerService, AbstractMessageLogger, AdapterLogger, ILogger, ILoggerOptions, ILoggerService, ILogService, log, LogLevel, LogService } from 'vs/platform/log/common/log'; @@ -62,6 +62,9 @@ export class LoggerChannel implements IServerChannel { constructor(private readonly loggerService: ILoggerService) { } listen(_: unknown, event: string): Event { + switch (event) { + case 'onDidChangeLogLevel': return this.loggerService.onDidChangeLogLevel; + } throw new Error(`Event not found: ${event}`); } @@ -70,6 +73,7 @@ export class LoggerChannel implements IServerChannel { case 'createLogger': this.createLogger(URI.revive(arg[0]), arg[1]); return; case 'log': return this.log(URI.revive(arg[0]), arg[1]); case 'consoleLog': return this.consoleLog(arg[0], arg[1]); + case 'setLogLevel': return this.loggerService.setLevel(URI.revive(arg[0]), arg[1]); } throw new Error(`Call not found: ${command}`); @@ -110,8 +114,9 @@ export class LoggerChannel implements IServerChannel { export class LoggerChannelClient extends AbstractLoggerService implements ILoggerService { - constructor(logLevel: LogLevel, onDidChangeLogLevel: Event, private readonly channel: IChannel) { - super(logLevel, onDidChangeLogLevel); + constructor(logLevel: LogLevel, onDidChangeLogLevel: Event, logLevels: [UriComponents, LogLevel][], private readonly channel: IChannel) { + super(logLevel, onDidChangeLogLevel, logLevels.map(([resource, logLevel]) => [URI.revive(resource), logLevel])); + this._register(channel.listen<[UriComponents, LogLevel]>('onDidChangeLogLevel')(([resource, logLevel]) => super.setLevel(URI.revive(resource), logLevel))); } createConsoleMainLogger(): ILogger { @@ -122,6 +127,11 @@ export class LoggerChannelClient extends AbstractLoggerService implements ILogge }); } + override setLevel(resource: URI, logLevel: LogLevel): void { + super.setLevel(resource, logLevel); + this.channel.call('setLogLevel', [resource, logLevel]); + } + protected doCreateLogger(file: URI, logLevel: LogLevel, options?: ILoggerOptions): ILogger { return new Logger(this.channel, file, logLevel, options); } diff --git a/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts b/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts index 43b70a639b5..d9c443c4b59 100644 --- a/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts +++ b/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts @@ -14,7 +14,7 @@ import { assertIsDefined } from 'vs/base/common/types'; import { connect as connectMessagePort } from 'vs/base/parts/ipc/electron-main/ipc.mp'; import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService'; import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; -import { ILogService } from 'vs/platform/log/common/log'; +import { ILogService, ILoggerService } from 'vs/platform/log/common/log'; import product from 'vs/platform/product/common/product'; import { IProtocolMainService } from 'vs/platform/protocol/electron-main/protocol'; import { ISharedProcess, ISharedProcessConfiguration } from 'vs/platform/sharedProcess/node/sharedProcess'; @@ -42,6 +42,7 @@ export class SharedProcess extends Disposable implements ISharedProcess { @IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService, @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService, @ILogService private readonly logService: ILogService, + @ILoggerService private readonly loggerService: ILoggerService, @IPolicyService private readonly policyService: IPolicyService, @IThemeMainService private readonly themeMainService: IThemeMainService, @IProtocolMainService private readonly protocolMainService: IProtocolMainService @@ -245,6 +246,7 @@ export class SharedProcess extends Disposable implements ISharedProcess { userEnv: this.userEnv, args: this.environmentMainService.args, logLevel: this.logService.getLevel(), + logLevels: [...this.loggerService.logLevels], product, policiesData: this.policyService.serialize() }); diff --git a/src/vs/platform/sharedProcess/node/sharedProcess.ts b/src/vs/platform/sharedProcess/node/sharedProcess.ts index 6cb7f88c0a0..2a2cdfa226f 100644 --- a/src/vs/platform/sharedProcess/node/sharedProcess.ts +++ b/src/vs/platform/sharedProcess/node/sharedProcess.ts @@ -9,7 +9,7 @@ import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; import { LogLevel } from 'vs/platform/log/common/log'; import { IUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; import { PolicyDefinition, PolicyValue } from 'vs/platform/policy/common/policy'; -import { UriDto } from 'vs/base/common/uri'; +import { UriComponents, UriDto } from 'vs/base/common/uri'; export interface ISharedProcess { @@ -27,6 +27,8 @@ export interface ISharedProcessConfiguration extends ISandboxConfiguration { readonly logLevel: LogLevel; + readonly logLevels: [UriComponents, LogLevel][]; + readonly profiles: readonly UriDto[]; readonly policiesData?: IStringDictionary<{ definition: PolicyDefinition; value: PolicyValue }>; diff --git a/src/vs/platform/telemetry/test/common/telemetryLogAppender.test.ts b/src/vs/platform/telemetry/test/common/telemetryLogAppender.test.ts index 2b0ec4b4e77..ff1a521c1e7 100644 --- a/src/vs/platform/telemetry/test/common/telemetryLogAppender.test.ts +++ b/src/vs/platform/telemetry/test/common/telemetryLogAppender.test.ts @@ -72,6 +72,7 @@ export class TestTelemetryLoggerService implements ILoggerService { } onDidChangeLogLevel = Event.None; + logLevels = []; setLevel(): void { } getLogLevel() { return undefined; } getDefaultLogLevel() { return this.logLevel; } diff --git a/src/vs/platform/window/common/window.ts b/src/vs/platform/window/common/window.ts index 186a1a81c83..b3de97481a3 100644 --- a/src/vs/platform/window/common/window.ts +++ b/src/vs/platform/window/common/window.ts @@ -299,6 +299,7 @@ export interface INativeWindowConfiguration extends IWindowConfiguration, Native isInitialStartup?: boolean; logLevel: LogLevel; + logLevels: [UriComponents, LogLevel][]; fullscreen?: boolean; maximized?: boolean; diff --git a/src/vs/platform/windows/electron-main/windowImpl.ts b/src/vs/platform/windows/electron-main/windowImpl.ts index 5bd02e2f0df..a50d52b2309 100644 --- a/src/vs/platform/windows/electron-main/windowImpl.ts +++ b/src/vs/platform/windows/electron-main/windowImpl.ts @@ -25,7 +25,7 @@ import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/e import { isLaunchedFromCli } from 'vs/platform/environment/node/argvHelper'; import { IFileService } from 'vs/platform/files/common/files'; import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; -import { ILogService } from 'vs/platform/log/common/log'; +import { ILogService, ILoggerService } from 'vs/platform/log/common/log'; import { IProductService } from 'vs/platform/product/common/productService'; import { IProtocolMainService } from 'vs/platform/protocol/electron-main/protocol'; import { resolveMarketplaceHeaders } from 'vs/platform/externalServices/common/marketplace'; @@ -183,6 +183,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { constructor( config: IWindowCreationOptions, @ILogService private readonly logService: ILogService, + @ILoggerService private readonly loggerService: ILoggerService, @IEnvironmentMainService private readonly environmentMainService: IEnvironmentMainService, @IPolicyService private readonly policyService: IPolicyService, @IUserDataProfilesMainService private readonly userDataProfilesService: IUserDataProfilesMainService, @@ -1085,6 +1086,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { profile: this.profile || this.userDataProfilesService.defaultProfile }; configuration.logLevel = this.logService.getLevel(); + configuration.logLevels = [...this.loggerService.logLevels]; // Load config this.load(configuration, { isReload: true, disableExtensions: cli?.['disable-extensions'] }); diff --git a/src/vs/platform/windows/electron-main/windowsMainService.ts b/src/vs/platform/windows/electron-main/windowsMainService.ts index 5117c5d7acb..3c7fda4f58d 100644 --- a/src/vs/platform/windows/electron-main/windowsMainService.ts +++ b/src/vs/platform/windows/electron-main/windowsMainService.ts @@ -32,7 +32,7 @@ import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/e import { FileType, IFileService } from 'vs/platform/files/common/files'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; -import { ILogService } from 'vs/platform/log/common/log'; +import { ILogService, ILoggerService } from 'vs/platform/log/common/log'; import product from 'vs/platform/product/common/product'; import { IProductService } from 'vs/platform/product/common/productService'; import { IProtocolMainService } from 'vs/platform/protocol/electron-main/protocol'; @@ -201,6 +201,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic private readonly machineId: string, private readonly initialUserEnv: IProcessEnvironment, @ILogService private readonly logService: ILogService, + @ILoggerService private readonly loggerService: ILoggerService, @IStateMainService private readonly stateMainService: IStateMainService, @IPolicyService private readonly policyService: IPolicyService, @IEnvironmentMainService private readonly environmentMainService: IEnvironmentMainService, @@ -1370,6 +1371,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic filesToWait: options.filesToOpen?.filesToWait, logLevel: this.logService.getLevel(), + logLevels: [...this.loggerService.logLevels], logsPath: this.environmentMainService.logsPath, product, diff --git a/src/vs/workbench/api/browser/mainThreadLogService.ts b/src/vs/workbench/api/browser/mainThreadLogService.ts index 2dbe789a04f..799f032bd18 100644 --- a/src/vs/workbench/api/browser/mainThreadLogService.ts +++ b/src/vs/workbench/api/browser/mainThreadLogService.ts @@ -11,9 +11,7 @@ import { UriComponents, URI } from 'vs/base/common/uri'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { ILogLevelService } from 'vs/workbench/contrib/logs/common/logLevelService'; import { IOutputService } from 'vs/workbench/services/output/common/output'; -import { localExtHostLog, remoteExtHostLog, webWorkerExtHostLog } from 'vs/workbench/services/extensions/common/extensions'; @extHostNamedCustomer(MainContext.MainThreadLogger) export class MainThreadLoggerService implements MainThreadLoggerShape { @@ -24,18 +22,11 @@ export class MainThreadLoggerService implements MainThreadLoggerShape { extHostContext: IExtHostContext, @ILogService logService: ILogService, @ILoggerService private readonly loggerService: ILoggerService, - @ILogLevelService extensionLoggerService: ILogLevelService, @IOutputService outputService: IOutputService, ) { const proxy = extHostContext.getProxy(ExtHostContext.ExtHostLogLevelServiceShape); this.disposables.add(logService.onDidChangeLogLevel(level => proxy.$setLevel(level))); - this.disposables.add(extensionLoggerService.onDidChangeLogLevel(({ id, logLevel }) => { - const channel = outputService.getChannelDescriptor(id); - const resource = channel?.log ? channel.file : undefined; - if (resource && (channel?.extensionId || id === localExtHostLog || id === remoteExtHostLog || id === webWorkerExtHostLog)) { - proxy.$setLevel(logLevel, resource); - } - })); + this.disposables.add(loggerService.onDidChangeLogLevel(([resource, logLevel]) => proxy.$setLevel(logLevel, resource))); } $log(file: UriComponents, messages: [LogLevel, string][]): void { diff --git a/src/vs/workbench/api/common/extHostLoggerService.ts b/src/vs/workbench/api/common/extHostLoggerService.ts index 47ec1627609..c3c22da85a7 100644 --- a/src/vs/workbench/api/common/extHostLoggerService.ts +++ b/src/vs/workbench/api/common/extHostLoggerService.ts @@ -20,7 +20,7 @@ export class ExtHostLoggerService extends AbstractLoggerService implements ExtHo @IExtHostRpcService rpc: IExtHostRpcService, @IExtHostInitDataService initData: IExtHostInitDataService, ) { - super(initData.logLevel, Event.None); + super(initData.logLevel, Event.None, initData.logLevels.map(([resource, logLevel]) => ([URI.revive(resource), logLevel]))); this._proxy = rpc.getProxy(MainContext.MainThreadLogger); } @@ -28,7 +28,7 @@ export class ExtHostLoggerService extends AbstractLoggerService implements ExtHo if (resource) { this.setLevel(URI.revive(resource), level); } else if (!isUndefined(level)) { - this.setLevel(level); + this.setGlobalLogLevel(level); } } diff --git a/src/vs/workbench/api/common/extHostOutput.ts b/src/vs/workbench/api/common/extHostOutput.ts index f491a6aa169..94d786e83b2 100644 --- a/src/vs/workbench/api/common/extHostOutput.ts +++ b/src/vs/workbench/api/common/extHostOutput.ts @@ -246,12 +246,16 @@ export class ExtHostOutputService implements ExtHostOutputServiceShape { } }; const onDidChangeLogLevel = disposables.add(new Emitter()); + function setLogLevel(newLogLevel: LogLevel): void { + logLevel = newLogLevel; + onDidChangeLogLevel.fire(newLogLevel); + } channelPromise.then(channel => { disposables.add(channel); - disposables.add(channel.onDidChangeLogLevel(e => { - logLevel = e; - onDidChangeLogLevel.fire(e); - })); + if (channel.logLevel !== logLevel) { + setLogLevel(channel.logLevel); + } + disposables.add(channel.onDidChangeLogLevel(e => setLogLevel(e))); }); return { ...this.createExtHostOutputChannel(name, channelPromise), diff --git a/src/vs/workbench/contrib/logs/browser/logs.contribution.ts b/src/vs/workbench/contrib/logs/browser/logs.contribution.ts index b42615bdd09..23f019bac51 100644 --- a/src/vs/workbench/contrib/logs/browser/logs.contribution.ts +++ b/src/vs/workbench/contrib/logs/browser/logs.contribution.ts @@ -12,10 +12,6 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { LogsDataCleaner } from 'vs/workbench/contrib/logs/common/logsDataCleaner'; -import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { ILogLevelService, LogLevelService } from 'vs/workbench/contrib/logs/common/logLevelService'; - -registerSingleton(ILogLevelService, LogLevelService, InstantiationType.Delayed); class WebLogOutputChannels extends Disposable implements IWorkbenchContribution { diff --git a/src/vs/workbench/contrib/logs/common/logLevelService.ts b/src/vs/workbench/contrib/logs/common/logLevelService.ts deleted file mode 100644 index 51e60fc9e0d..00000000000 --- a/src/vs/workbench/contrib/logs/common/logLevelService.ts +++ /dev/null @@ -1,55 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { ILoggerService, LogLevel } from 'vs/platform/log/common/log'; -import { Emitter, Event } from 'vs/base/common/event'; -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { Disposable } from 'vs/base/common/lifecycle'; -import { IOutputService } from 'vs/workbench/services/output/common/output'; - -export const ILogLevelService = createDecorator('ILogLevelService'); -export interface ILogLevelService { - readonly _serviceBrand: undefined; - readonly onDidChangeLogLevel: Event<{ readonly id: string; logLevel: LogLevel }>; - setLogLevel(id: string, logLevel: LogLevel): void; - getLogLevel(id: string): LogLevel | undefined; -} - -export class LogLevelService extends Disposable implements ILogLevelService { - readonly _serviceBrand: undefined; - - private readonly _onDidChangeLogLevel = this._register(new Emitter<{ readonly id: string; logLevel: LogLevel }>()); - readonly onDidChangeLogLevel = this._onDidChangeLogLevel.event; - - private readonly logLevels = new Map(); - - constructor( - @IOutputService protected readonly outputService: IOutputService, - @ILoggerService private readonly loggerService: ILoggerService - ) { - super(); - } - - getLogLevel(id: string): LogLevel | undefined { - return this.logLevels.get(id); - } - - setLogLevel(id: string, logLevel: LogLevel): boolean { - if (this.getLogLevel(id) === logLevel) { - return false; - } - - this.logLevels.set(id, logLevel); - const channel = this.outputService.getChannelDescriptor(id); - const resource = channel?.log ? channel.file : undefined; - if (resource) { - this.loggerService.setLevel(resource, logLevel); - } - this._onDidChangeLogLevel.fire({ id, logLevel }); - return true; - } - -} - diff --git a/src/vs/workbench/contrib/logs/common/logsActions.ts b/src/vs/workbench/contrib/logs/common/logsActions.ts index 001b51875aa..c031ab83de8 100644 --- a/src/vs/workbench/contrib/logs/common/logsActions.ts +++ b/src/vs/workbench/contrib/logs/common/logsActions.ts @@ -5,22 +5,21 @@ import * as nls from 'vs/nls'; import { Action } from 'vs/base/common/actions'; -import { ILogService, LogLevel, getLogLevel, parseLogLevel } from 'vs/platform/log/common/log'; +import { ILogService, ILoggerService, LogLevel, getLogLevel, parseLogLevel } from 'vs/platform/log/common/log'; import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; import { URI } from 'vs/base/common/uri'; import { IFileService } from 'vs/platform/files/common/files'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { dirname, basename, isEqual } from 'vs/base/common/resources'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IOutputChannelDescriptor, IOutputService } from 'vs/workbench/services/output/common/output'; +import { IOutputService } from 'vs/workbench/services/output/common/output'; import { isNumber } from 'vs/base/common/types'; -import { ILogLevelService } from 'vs/workbench/contrib/logs/common/logLevelService'; import { extensionTelemetryLogChannelId, telemetryLogChannelId } from 'vs/workbench/contrib/logs/common/logConstants'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; type LogLevelQuickPickItem = IQuickPickItem & { level: LogLevel }; -type LogChannelQuickPickItem = IQuickPickItem & { channel: IOutputChannelDescriptor }; +type LogChannelQuickPickItem = IQuickPickItem & { id: string; resource: URI; extensionId?: string }; function isLogLevel(thing: unknown): thing is LogLevel { return isNumber(thing); @@ -34,7 +33,7 @@ export class SetLogLevelAction extends Action { constructor(id: string, label: string, @IQuickInputService private readonly quickInputService: IQuickInputService, @ILogService private readonly logService: ILogService, - @ILogLevelService private readonly logLevelService: ILogLevelService, + @ILoggerService private readonly loggerService: ILoggerService, @IOutputService private readonly outputService: IOutputService, @IEnvironmentService private readonly environmentService: IEnvironmentService, ) { @@ -52,54 +51,44 @@ export class SetLogLevelAction extends Action { } } - private async selectLogLevelOrChannel(): Promise { - const extensionLogs = [], logs = []; + private async selectLogLevelOrChannel(): Promise { + const extensionLogs: LogChannelQuickPickItem[] = [], logs: LogChannelQuickPickItem[] = []; + const logLevel = this.logService.getLevel(); for (const channel of this.outputService.getChannelDescriptors()) { - if (!channel.log || channel.id === telemetryLogChannelId || channel.id === extensionTelemetryLogChannelId) { + if (!channel.log || !channel.file || channel.id === telemetryLogChannelId || channel.id === extensionTelemetryLogChannelId) { continue; } + const channelLogLevel = this.loggerService.getLogLevel(channel.file) ?? logLevel; + const item: LogChannelQuickPickItem = { id: channel.id, resource: channel.file, label: channel.label, description: channelLogLevel !== logLevel ? this.getLabel(channelLogLevel) : undefined, extensionId: channel.extensionId }; if (channel.extensionId) { - extensionLogs.push(channel); + extensionLogs.push(item); } else { - logs.push(channel); + logs.push(item); } } const entries: (LogLevelQuickPickItem | LogChannelQuickPickItem | IQuickPickSeparator)[] = []; entries.push({ type: 'separator', label: nls.localize('all', "All") }); - entries.push(...this.getLogLevelEntries(this.getDefaultLogLevel(null), this.logService.getLevel())); + entries.push(...this.getLogLevelEntries(this.getDefaultLogLevel(), this.logService.getLevel())); entries.push({ type: 'separator', label: nls.localize('loggers', "Logs") }); - const logLevel = this.logService.getLevel(); - for (const channel of logs.sort((a, b) => a.label.localeCompare(b.label))) { - const channelLogLevel = this.logLevelService.getLogLevel(channel.id) ?? logLevel; - entries.push({ label: channel.label, channel, description: channelLogLevel !== logLevel ? this.getLabel(channelLogLevel) : undefined }); - } + entries.push(...logs.sort((a, b) => a.label.localeCompare(b.label))); if (extensionLogs.length && logs.length) { entries.push({ type: 'separator', label: nls.localize('extensionLogs', "Extension Logs") }); } - for (const channel of extensionLogs.sort((a, b) => a.label.localeCompare(b.label))) { - const channelLogLevel = this.logLevelService.getLogLevel(channel.id) ?? logLevel; - entries.push({ label: channel.label, channel, description: channelLogLevel !== logLevel ? this.getLabel(channelLogLevel) : undefined }); - } + entries.push(...extensionLogs.sort((a, b) => a.label.localeCompare(b.label))); const entry = await this.quickInputService.pick(entries, { placeHolder: nls.localize('selectlog', "Set Log Level") }); - if (entry) { - if ((entry).level) { - return (entry).level; - } - if ((entry).channel) { - return (entry).channel; - } - } - return null; + return entry + ? (entry).level ? (entry).level : entry + : null; } - private async setLogLevelForChannel(logChannel: IOutputChannelDescriptor): Promise { - const defaultLogLevel = this.getDefaultLogLevel(logChannel); - const currentLogLevel = this.logLevelService.getLogLevel(logChannel.id) ?? defaultLogLevel; + private async setLogLevelForChannel(logChannel: LogChannelQuickPickItem): Promise { + const defaultLogLevel = this.getDefaultLogLevel(logChannel.extensionId); + const currentLogLevel = this.loggerService.getLogLevel(logChannel.resource) ?? defaultLogLevel; const entries = this.getLogLevelEntries(defaultLogLevel, currentLogLevel); const entry = await this.quickInputService.pick(entries, { placeHolder: logChannel ? nls.localize('selectLogLevelFor', " {0}: Select log level", logChannel?.label) : nls.localize('selectLogLevel', "Select log level"), activeItem: entries[this.logService.getLevel()] }); if (entry) { - this.logLevelService.setLogLevel(logChannel.id, entry.level); + this.loggerService.setLevel(logChannel.resource, entry.level); } } @@ -132,10 +121,10 @@ export class SetLogLevelAction extends Action { return defaultLogLevel === level ? nls.localize('default', "Default") : undefined; } - private getDefaultLogLevel(outputChannel: IOutputChannelDescriptor | null): LogLevel { + private getDefaultLogLevel(extensionId?: string): LogLevel { let logLevel: LogLevel | undefined; - if (outputChannel?.extensionId) { - const logLevelValue = this.environmentService.extensionLogLevel?.find(([id]) => areSameExtensions({ id }, { id: outputChannel.extensionId! }))?.[1]; + if (extensionId) { + const logLevelValue = this.environmentService.extensionLogLevel?.find(([id]) => areSameExtensions({ id }, { id: extensionId }))?.[1]; if (logLevelValue) { logLevel = parseLogLevel(logLevelValue); } diff --git a/src/vs/workbench/contrib/logs/electron-sandbox/logLevelService.ts b/src/vs/workbench/contrib/logs/electron-sandbox/logLevelService.ts deleted file mode 100644 index 323d8e30dec..00000000000 --- a/src/vs/workbench/contrib/logs/electron-sandbox/logLevelService.ts +++ /dev/null @@ -1,51 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { ILoggerService, LogLevel } from 'vs/platform/log/common/log'; -import { IOutputService } from 'vs/workbench/services/output/common/output'; -import { IMainProcessService, ISharedProcessService } from 'vs/platform/ipc/electron-sandbox/services'; -import { LogLevelService as CommonLogLevelService } from 'vs/workbench/contrib/logs/common/logLevelService'; -import { remotePtyHostLog, remoteServerLog, sharedLogChannelId, userDataSyncLogChannelId } from 'vs/workbench/contrib/logs/common/logConstants'; -import { LogLevelChannelClient } from 'vs/platform/log/common/logIpc'; -import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; -import { LOG_CHANNEL_ID as remoteTunnelLogChannelId } from 'vs/platform/remoteTunnel/common/remoteTunnel'; - -export class LogLevelService extends CommonLogLevelService { - - constructor( - @IOutputService outputService: IOutputService, - @ILoggerService loggerService: ILoggerService, - @ISharedProcessService private readonly sharedProcessService: ISharedProcessService, - @IMainProcessService private readonly mainProcessService: IMainProcessService, - @IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService, - ) { - super(outputService, loggerService); - } - - override setLogLevel(id: string, logLevel: LogLevel): boolean { - if (!super.setLogLevel(id, logLevel)) { - return false; - } - - const channel = this.outputService.getChannelDescriptor(id); - const resource = channel?.log ? channel.file : undefined; - - LogLevelChannelClient.setLevel(this.mainProcessService.getChannel('logLevel'), logLevel, resource); - if (id === sharedLogChannelId || id === userDataSyncLogChannelId || id === remoteTunnelLogChannelId) { - LogLevelChannelClient.setLevel(this.sharedProcessService.getChannel('logLevel'), logLevel, resource); - return true; - } - - const connection = this.remoteAgentService.getConnection(); - if ((id === remoteServerLog || id === remotePtyHostLog) && connection) { - connection.withChannel('logger', (channel) => LogLevelChannelClient.setLevel(channel, logLevel, resource)); - return true; - } - - return true; - } - -} - diff --git a/src/vs/workbench/contrib/logs/electron-sandbox/logs.contribution.ts b/src/vs/workbench/contrib/logs/electron-sandbox/logs.contribution.ts index 57f5dadd802..5066dfcfe34 100644 --- a/src/vs/workbench/contrib/logs/electron-sandbox/logs.contribution.ts +++ b/src/vs/workbench/contrib/logs/electron-sandbox/logs.contribution.ts @@ -20,11 +20,6 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/ import { registerLogChannel } from 'vs/workbench/services/output/common/output'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { ILogLevelService } from 'vs/workbench/contrib/logs/common/logLevelService'; -import { LogLevelService } from 'vs/workbench/contrib/logs/electron-sandbox/logLevelService'; - -registerSingleton(ILogLevelService, LogLevelService, InstantiationType.Delayed); class NativeLogOutputChannels extends Disposable implements IWorkbenchContribution { diff --git a/src/vs/workbench/contrib/remote/common/remote.contribution.ts b/src/vs/workbench/contrib/remote/common/remote.contribution.ts index 88d2a14aff5..efd8fc9926f 100644 --- a/src/vs/workbench/contrib/remote/common/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/common/remote.contribution.ts @@ -34,6 +34,7 @@ import { DownloadServiceChannel } from 'vs/platform/download/common/downloadIpc' import { timeout } from 'vs/base/common/async'; import { TerminalLogConstants } from 'vs/platform/terminal/common/terminal'; import { remotePtyHostLog, remoteServerLog } from 'vs/workbench/contrib/logs/common/logConstants'; +import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; export class LabelContribution implements IWorkbenchContribution { constructor( @@ -93,18 +94,31 @@ class RemoteChannelsContribution extends Disposable implements IWorkbenchContrib } } -class RemoteLogOutputChannels implements IWorkbenchContribution { +class RemoteLogOutputChannels extends Disposable implements IWorkbenchContribution { constructor( - @IRemoteAgentService remoteAgentService: IRemoteAgentService + @IRemoteAgentService remoteAgentService: IRemoteAgentService, + @ILoggerService loggerService: ILoggerService, + @IUriIdentityService uriIdentityService: IUriIdentityService, ) { - remoteAgentService.getEnvironment().then(remoteEnv => { - if (remoteEnv) { - const outputChannelRegistry = Registry.as(OutputExt.OutputChannels); - outputChannelRegistry.registerChannel({ id: remoteServerLog, label: localize('remoteExtensionLog', "Remote Server"), file: joinPath(remoteEnv.logsPath, `${RemoteExtensionLogFileName}.log`), log: true }); - outputChannelRegistry.registerChannel({ id: remotePtyHostLog, label: localize('remotePtyHostLog', "Remote Pty Host"), file: joinPath(remoteEnv.logsPath, `${TerminalLogConstants.FileName}.log`), log: true }); - } - }); + super(); + const connection = remoteAgentService.getConnection(); + if (connection) { + remoteAgentService.getEnvironment().then(remoteEnv => { + if (remoteEnv) { + const outputChannelRegistry = Registry.as(OutputExt.OutputChannels); + const remoteExtensionHostLogFile = joinPath(remoteEnv.logsPath, `${RemoteExtensionLogFileName}.log`); + const remotePtyLogFile = joinPath(remoteEnv.logsPath, `${TerminalLogConstants.FileName}.log`); + outputChannelRegistry.registerChannel({ id: remoteServerLog, label: localize('remoteExtensionLog', "Remote Server"), file: remoteExtensionHostLogFile, log: true }); + outputChannelRegistry.registerChannel({ id: remotePtyHostLog, label: localize('remotePtyHostLog', "Remote Pty Host"), file: remotePtyLogFile, log: true }); + this._register(loggerService.onDidChangeLogLevel(([resource, logLevel]) => { + if (uriIdentityService.extUri.isEqual(resource, remoteExtensionHostLogFile) || uriIdentityService.extUri.isEqual(resource, remotePtyLogFile)) { + connection.withChannel('logger', (channel) => LogLevelChannelClient.setLevel(channel, logLevel, resource)); + } + })); + } + }); + } } } diff --git a/src/vs/workbench/electron-sandbox/desktop.main.ts b/src/vs/workbench/electron-sandbox/desktop.main.ts index 87785f4d8d3..2f2cbba10fb 100644 --- a/src/vs/workbench/electron-sandbox/desktop.main.ts +++ b/src/vs/workbench/electron-sandbox/desktop.main.ts @@ -171,7 +171,7 @@ export class DesktopMain extends Disposable { // Logger const logLevelChannelClient = new LogLevelChannelClient(mainProcessService.getChannel('logLevel')); - const loggerService = new LoggerChannelClient(this.configuration.logLevel, logLevelChannelClient.onDidChangeLogLevel, mainProcessService.getChannel('logger')); + const loggerService = new LoggerChannelClient(this.configuration.logLevel, logLevelChannelClient.onDidChangeLogLevel, this.configuration.logLevels, mainProcessService.getChannel('logger')); serviceCollection.set(ILoggerService, loggerService); // Log diff --git a/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts b/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts index 5322a0bf81b..df7f755aef8 100644 --- a/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts +++ b/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts @@ -11,7 +11,7 @@ import { createMessageOfType, MessageType, isMessageOfType, ExtensionHostExitCod import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { ILabelService } from 'vs/platform/label/common/label'; -import { ILogService } from 'vs/platform/log/common/log'; +import { ILogService, ILoggerService } from 'vs/platform/log/common/log'; import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import * as platform from 'vs/base/common/platform'; import * as dom from 'vs/base/browser/dom'; @@ -66,6 +66,7 @@ export class WebWorkerExtensionHost extends Disposable implements IExtensionHost @IWorkspaceContextService private readonly _contextService: IWorkspaceContextService, @ILabelService private readonly _labelService: ILabelService, @ILogService private readonly _logService: ILogService, + @ILoggerService private readonly _loggerService: ILoggerService, @IBrowserWorkbenchEnvironmentService private readonly _environmentService: IBrowserWorkbenchEnvironmentService, @IUserDataProfilesService private readonly _userDataProfilesService: IUserDataProfilesService, @IProductService private readonly _productService: IProductService, @@ -315,6 +316,7 @@ export class WebWorkerExtensionHost extends Disposable implements IExtensionHost nlsBaseUrl: nlsUrlWithDetails, telemetryInfo, logLevel: this._logService.getLevel(), + logLevels: [...this._loggerService.logLevels], logsLocation: this._extensionHostLogsLocation, logFile: this._extensionHostLogFile, autoStart: initData.autoStart, diff --git a/src/vs/workbench/services/extensions/common/extensionHostProtocol.ts b/src/vs/workbench/services/extensions/common/extensionHostProtocol.ts index eb9db688f2f..255716357b4 100644 --- a/src/vs/workbench/services/extensions/common/extensionHostProtocol.ts +++ b/src/vs/workbench/services/extensions/common/extensionHostProtocol.ts @@ -31,6 +31,7 @@ export interface IExtensionHostInitData { nlsBaseUrl?: URI; telemetryInfo: ITelemetryInfo; logLevel: LogLevel; + logLevels: [UriComponents, LogLevel][]; logsLocation: URI; logFile: URI; autoStart: boolean; diff --git a/src/vs/workbench/services/extensions/common/remoteExtensionHost.ts b/src/vs/workbench/services/extensions/common/remoteExtensionHost.ts index 6f317b0fc93..24a2cd3e111 100644 --- a/src/vs/workbench/services/extensions/common/remoteExtensionHost.ts +++ b/src/vs/workbench/services/extensions/common/remoteExtensionHost.ts @@ -16,7 +16,7 @@ import { localize } from 'vs/nls'; import { IExtensionHostDebugService } from 'vs/platform/debug/common/extensionHostDebug'; import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { ILabelService } from 'vs/platform/label/common/label'; -import { ILogService } from 'vs/platform/log/common/log'; +import { ILogService, ILoggerService } from 'vs/platform/log/common/log'; import { IProductService } from 'vs/platform/product/common/productService'; import { Registry } from 'vs/platform/registry/common/platform'; import { connectRemoteAgentExtensionHost, IConnectionOptions, IRemoteExtensionHostStartParams, ISocketFactory } from 'vs/platform/remote/common/remoteAgentConnection'; @@ -68,6 +68,7 @@ export class RemoteExtensionHost extends Disposable implements IExtensionHost { @IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService, @ITelemetryService private readonly _telemetryService: ITelemetryService, @ILogService private readonly _logService: ILogService, + @ILoggerService protected readonly _loggerService: ILoggerService, @ILabelService private readonly _labelService: ILabelService, @IRemoteAuthorityResolverService private readonly remoteAuthorityResolverService: IRemoteAuthorityResolverService, @IExtensionHostDebugService private readonly _extensionHostDebugService: IExtensionHostDebugService, @@ -248,6 +249,7 @@ export class RemoteExtensionHost extends Disposable implements IExtensionHost { myExtensions: deltaExtensions.myToAdd, telemetryInfo, logLevel: this._logService.getLevel(), + logLevels: [...this._loggerService.logLevels], logsLocation: remoteInitData.extensionHostLogsPath, logFile: joinPath(remoteInitData.extensionHostLogsPath, `${ExtensionHostLogFileName}.log`), autoStart: true, diff --git a/src/vs/workbench/services/extensions/electron-sandbox/localProcessExtensionHost.ts b/src/vs/workbench/services/extensions/electron-sandbox/localProcessExtensionHost.ts index dd79f7253ff..bdbc71dca01 100644 --- a/src/vs/workbench/services/extensions/electron-sandbox/localProcessExtensionHost.ts +++ b/src/vs/workbench/services/extensions/electron-sandbox/localProcessExtensionHost.ts @@ -16,7 +16,7 @@ import { BufferedEmitter } from 'vs/base/parts/ipc/common/ipc.net'; import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; import { ILabelService } from 'vs/platform/label/common/label'; import { ILifecycleService, WillShutdownEvent } from 'vs/workbench/services/lifecycle/common/lifecycle'; -import { ILogService } from 'vs/platform/log/common/log'; +import { ILogService, ILoggerService } from 'vs/platform/log/common/log'; import { IProductService } from 'vs/platform/product/common/productService'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -139,6 +139,7 @@ export class NativeLocalProcessExtensionHost implements IExtensionHost { @IUserDataProfilesService private readonly _userDataProfilesService: IUserDataProfilesService, @ITelemetryService private readonly _telemetryService: ITelemetryService, @ILogService protected readonly _logService: ILogService, + @ILoggerService protected readonly _loggerService: ILoggerService, @ILabelService private readonly _labelService: ILabelService, @IExtensionHostDebugService private readonly _extensionHostDebugService: IExtensionHostDebugService, @IHostService private readonly _hostService: IHostService, @@ -472,6 +473,7 @@ export class NativeLocalProcessExtensionHost implements IExtensionHost { myExtensions: deltaExtensions.myToAdd, telemetryInfo, logLevel: this._logService.getLevel(), + logLevels: [...this._loggerService.logLevels], logsLocation: this._environmentService.extHostLogsPath, logFile: this._extensionHostLogFile, autoStart: initData.autoStart, diff --git a/src/vs/workbench/test/electron-browser/workbenchTestServices.ts b/src/vs/workbench/test/electron-browser/workbenchTestServices.ts index 1716e83a94c..1d2478a19c3 100644 --- a/src/vs/workbench/test/electron-browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/electron-browser/workbenchTestServices.ts @@ -75,6 +75,7 @@ export const TestNativeWindowConfiguration: INativeWindowConfiguration = { windowId: 0, machineId: 'testMachineId', logLevel: LogLevel.Error, + logLevels: [], mainPid: 0, appRoot: '', userEnv: {}, From 7606e812a6f8d437e4cf51eca2cbb7950c3c21b9 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 12 Jan 2023 02:23:04 +0100 Subject: [PATCH 2/4] - register logger resources in main - scope logger resources by window --- .../sharedProcess/sharedProcessMain.ts | 9 +- src/vs/code/electron-main/app.ts | 6 +- src/vs/code/electron-main/main.ts | 4 +- src/vs/platform/log/common/log.ts | 119 ++++++++++++------ src/vs/platform/log/common/logIpc.ts | 88 ++++--------- src/vs/platform/log/electron-main/logIpc.ts | 71 +++++++++++ .../log/electron-main/loggerService.ts | 88 +++++++++++++ .../electron-main/sharedProcess.ts | 7 +- .../sharedProcess/node/sharedProcess.ts | 6 +- .../test/common/telemetryLogAppender.test.ts | 7 +- .../platform/terminal/node/ptyHostService.ts | 17 ++- src/vs/platform/window/common/window.ts | 4 +- .../windows/electron-main/windowImpl.ts | 7 +- .../electron-main/windowsMainService.ts | 10 +- .../electron-main/windowsStateHandler.ts | 4 + .../api/browser/mainThreadLogService.ts | 2 +- .../api/browser/mainThreadOutputService.ts | 12 +- .../api/common/extHostLoggerService.ts | 5 +- .../contrib/logs/common/logsActions.ts | 2 +- .../remote/common/remote.contribution.ts | 13 +- .../electron-sandbox/desktop.main.ts | 2 +- .../browser/webWorkerExtensionHost.ts | 6 +- .../common/extensionHostProtocol.ts | 6 +- .../extensions/common/remoteExtensionHost.ts | 6 +- .../localProcessExtensionHost.ts | 6 +- .../electron-browser/workbenchTestServices.ts | 2 +- 26 files changed, 359 insertions(+), 150 deletions(-) create mode 100644 src/vs/platform/log/electron-main/logIpc.ts create mode 100644 src/vs/platform/log/electron-main/loggerService.ts diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts index 20966f711f0..d8dcc4b4941 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts @@ -216,7 +216,7 @@ class SharedProcessMain extends Disposable { // Logger const logLevelClient = new LogLevelChannelClient(this.server.getChannel('logLevel', mainRouter)); - const loggerService = new LoggerChannelClient(this.configuration.logLevel, logLevelClient.onDidChangeLogLevel, this.configuration.logLevels, mainProcessService.getChannel('logger')); + const loggerService = new LoggerChannelClient(undefined, this.configuration.logLevel, logLevelClient.onDidChangeLogLevel, this.configuration.loggers, mainProcessService.getChannel('logger')); services.set(ILoggerService, loggerService); // Log @@ -275,7 +275,8 @@ class SharedProcessMain extends Disposable { ]); // URI Identity - services.set(IUriIdentityService, new UriIdentityService(fileService)); + const uriIdentityService = new UriIdentityService(fileService); + services.set(IUriIdentityService, uriIdentityService); // Request services.set(IRequestService, new SharedProcessRequestService(mainProcessService, configurationService, productService, logService)); @@ -374,7 +375,9 @@ class SharedProcessMain extends Disposable { }, configurationService, environmentService, - logService + logService, + loggerService, + uriIdentityService ); ptyHostService.initialize(); diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index f11f2162456..1df6eaf9bec 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -59,7 +59,7 @@ import { IKeyboardLayoutMainService, KeyboardLayoutMainService } from 'vs/platfo import { ILaunchMainService, LaunchMainService } from 'vs/platform/launch/electron-main/launchMainService'; import { ILifecycleMainService, LifecycleMainPhase, ShutdownReason } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; import { ILoggerService, ILogService } from 'vs/platform/log/common/log'; -import { LoggerChannel, LogLevelChannel } from 'vs/platform/log/common/logIpc'; +import { LogLevelChannel } from 'vs/platform/log/common/logIpc'; import { IMenubarMainService, MenubarMainService } from 'vs/platform/menubar/electron-main/menubarMainService'; import { INativeHostMainService, NativeHostMainService } from 'vs/platform/native/electron-main/nativeHostMainService'; import { IProductService } from 'vs/platform/product/common/productService'; @@ -110,6 +110,8 @@ import { ProfileStorageChangesListenerChannel } from 'vs/platform/userDataProfil import { Promises, RunOnceScheduler, runWhenIdle } from 'vs/base/common/async'; import { resolveMachineId } from 'vs/platform/telemetry/electron-main/telemetryUtils'; import { ExtensionsProfileScannerService } from 'vs/platform/extensionManagement/node/extensionsProfileScannerService'; +import { LoggerChannel } from 'vs/platform/log/electron-main/logIpc'; +import { ILoggerMainService } from 'vs/platform/log/electron-main/loggerService'; /** * The main VS Code application. There will only ever be one instance, @@ -804,7 +806,7 @@ export class CodeApplication extends Disposable { sharedProcessClient.then(client => client.registerChannel('logLevel', logLevelChannel)); // Logger - const loggerChannel = new LoggerChannel(accessor.get(ILoggerService),); + const loggerChannel = new LoggerChannel(accessor.get(ILoggerMainService),); mainProcessElectronServer.registerChannel('logger', loggerChannel); sharedProcessClient.then(client => client.registerChannel('logger', loggerChannel)); diff --git a/src/vs/code/electron-main/main.ts b/src/vs/code/electron-main/main.ts index 1d45f28d4d6..c05d4b3aa7e 100644 --- a/src/vs/code/electron-main/main.ts +++ b/src/vs/code/electron-main/main.ts @@ -46,7 +46,6 @@ import { ILaunchMainService } from 'vs/platform/launch/electron-main/launchMainS import { ILifecycleMainService, LifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; import { BufferLogService } from 'vs/platform/log/common/bufferLog'; import { ConsoleMainLogger, getLogLevel, ILoggerService, ILogService, MultiplexLogService } from 'vs/platform/log/common/log'; -import { LoggerService } from 'vs/platform/log/node/loggerService'; import product from 'vs/platform/product/common/product'; import { IProductService } from 'vs/platform/product/common/productService'; import { IProtocolMainService } from 'vs/platform/protocol/electron-main/protocol'; @@ -69,6 +68,7 @@ import { DisposableStore } from 'vs/base/common/lifecycle'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService'; import { PROFILES_ENABLEMENT_CONFIG } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { ILoggerMainService, LoggerMainService } from 'vs/platform/log/electron-main/loggerService'; /** * The main VS Code entry point. @@ -177,7 +177,7 @@ class CodeMain { services.set(IUriIdentityService, uriIdentityService); // Logger - services.set(ILoggerService, new LoggerService(logService)); + services.set(ILoggerMainService, new LoggerMainService(logService)); // State const stateMainService = new StateMainService(environmentMainService, logService, fileService); diff --git a/src/vs/platform/log/common/log.ts b/src/vs/platform/log/common/log.ts index baadc017846..55ce986b8a9 100644 --- a/src/vs/platform/log/common/log.ts +++ b/src/vs/platform/log/common/log.ts @@ -8,6 +8,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { ResourceMap } from 'vs/base/common/map'; import { isWindows } from 'vs/base/common/platform'; +import { Mutable } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; @@ -104,7 +105,20 @@ export interface ILoggerOptions { always?: boolean; } +export interface ILoggerResource { + readonly resource: URI; + readonly name?: string; + readonly id?: string; + readonly logLevel?: LogLevel; +} + +export type DidChangeLoggerResourceEvent = { + readonly added: Iterable; + readonly removed: Iterable; +}; + export interface ILoggerService { + readonly _serviceBrand: undefined; /** @@ -117,26 +131,40 @@ export interface ILoggerService { */ getLogger(resource: URI): ILogger | undefined; - - /** - * all resources log levels - */ - readonly logLevels: Iterable<[URI, LogLevel]>; - - /** - * An event which fires when the log level of the resource changes. - */ - readonly onDidChangeLogLevel: Event<[URI, LogLevel]>; - /** * Set log level for a logger. */ - setLevel(resource: URI, level: LogLevel | undefined): void; + setLogLevel(resource: URI, level: LogLevel): void; /** * Get log level for a logger. */ getLogLevel(resource: URI): LogLevel | undefined; + + /** + * An event which fires when the log level of a logger has changed + */ + readonly onDidChangeLogLevel: Event; + + /** + * An event which fires when the logger resources are changed + */ + readonly onDidChangeLoggerResources: Event; + + /** + * Register the logger resoruce + */ + registerLoggerResource(resource: ILoggerResource): void; + + /** + * Deregister the logger resoruce + */ + deregisterLoggerResource(resource: URI): void; + + /** + * Get all registered logger resources + */ + getLoggerResources(): Iterable; } export abstract class AbstractLogger extends Disposable implements ILogger { @@ -502,29 +530,28 @@ export abstract class AbstractLoggerService extends Disposable implements ILogge private readonly _loggers = new ResourceMap(); - private readonly _logLevels = new ResourceMap(); - get logLevels() { return this._logLevels.entries(); } - private _onDidChangeLogLevel = this._register(new Emitter<[URI, LogLevel]>); + private readonly _loggerResources = new ResourceMap>(); + + private _onDidChangeLoggerResources = this._register(new Emitter<{ added: ILoggerResource[]; removed: ILoggerResource[] }>); + readonly onDidChangeLoggerResources = this._onDidChangeLoggerResources.event; + + private _onDidChangeLogLevel = this._register(new Emitter); readonly onDidChangeLogLevel = this._onDidChangeLogLevel.event; constructor( - private logLevel: LogLevel, + protected logLevel: LogLevel, onDidChangeLogLevel: Event, - logLevels?: Iterable<[URI, LogLevel]>, + loggerResources?: Iterable, ) { super(); this._register(onDidChangeLogLevel(logLevel => this.setGlobalLogLevel(logLevel))); - if (logLevels) { - for (const [resource, logLevel] of logLevels) { - this._logLevels.set(resource, logLevel); + if (loggerResources) { + for (const loggerResource of loggerResources) { + this._loggerResources.set(loggerResource.resource, loggerResource); } } } - getLoggers(): ILogger[] { - return [...this._loggers.values()]; - } - getLogger(resource: URI): ILogger | undefined { return this._loggers.get(resource); } @@ -535,42 +562,58 @@ export abstract class AbstractLoggerService extends Disposable implements ILogge logLevel = options?.always ? LogLevel.Trace : logLevel; logger = this.doCreateLogger(resource, logLevel ?? this.getLogLevel(resource) ?? this.logLevel, options); this._loggers.set(resource, logger); - if (logLevel !== undefined) { - this._logLevels.set(resource, logLevel); - } + this.registerLoggerResource({ resource, logLevel, name: options?.name }); } return logger; } - setLevel(resource: URI, logLevel: LogLevel): void { - if (logLevel !== this._logLevels.get(resource)) { - if (logLevel === this.logLevel) { - this._logLevels.delete(resource); - } else { - this._logLevels.set(resource, logLevel); - } + setLogLevel(resource: URI, logLevel: LogLevel): void { + const loggerResource = this._loggerResources.get(resource); + if (loggerResource && logLevel !== loggerResource.logLevel) { + loggerResource.logLevel = logLevel === this.logLevel ? undefined : logLevel; this._loggers.get(resource)?.setLevel(logLevel); - this._onDidChangeLogLevel.fire([resource, logLevel]); + this._loggerResources.set(loggerResource.resource, loggerResource); + this._onDidChangeLogLevel.fire(loggerResource); } } protected setGlobalLogLevel(logLevel: LogLevel): void { this.logLevel = logLevel; for (const [resource, logger] of this._loggers.entries()) { - if (this._logLevels.get(resource) === undefined) { + if (this._loggerResources.get(resource)?.logLevel === undefined) { logger.setLevel(this.logLevel); } } } getLogLevel(resource: URI): LogLevel | undefined { - return this._logLevels.get(resource); + return this._loggerResources.get(resource)?.logLevel; + } + + registerLoggerResource(resource: ILoggerResource): void { + const existing = this._loggerResources.get(resource.resource); + if (!existing) { + this._loggerResources.set(resource.resource, resource); + this._onDidChangeLoggerResources.fire({ added: [resource], removed: [] }); + } + } + + deregisterLoggerResource(resource: URI): void { + const existing = this._loggerResources.get(resource); + if (existing) { + this._loggerResources.delete(resource); + this._onDidChangeLoggerResources.fire({ added: [], removed: [existing] }); + } + } + + getLoggerResources(): Iterable { + return this._loggerResources.values(); } override dispose(): void { this._loggers.forEach(logger => logger.dispose()); this._loggers.clear(); - this._logLevels.clear(); + this._loggerResources.clear(); super.dispose(); } diff --git a/src/vs/platform/log/common/logIpc.ts b/src/vs/platform/log/common/logIpc.ts index 6e4887b1b0a..79a4c5e689c 100644 --- a/src/vs/platform/log/common/logIpc.ts +++ b/src/vs/platform/log/common/logIpc.ts @@ -4,9 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import { Event } from 'vs/base/common/event'; -import { URI, UriComponents } from 'vs/base/common/uri'; +import { URI, UriDto } from 'vs/base/common/uri'; import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; -import { AbstractLoggerService, AbstractMessageLogger, AdapterLogger, ILogger, ILoggerOptions, ILoggerService, ILogService, log, LogLevel, LogService } from 'vs/platform/log/common/log'; +import { AbstractLoggerService, AbstractMessageLogger, AdapterLogger, DidChangeLoggerResourceEvent, ILogger, ILoggerOptions, ILoggerResource, ILoggerService, ILogService, LogLevel, LogService } from 'vs/platform/log/common/log'; export class LogLevelChannel implements IServerChannel { @@ -29,7 +29,7 @@ export class LogLevelChannel implements IServerChannel { async call(_: unknown, command: string, arg?: any): Promise { switch (command) { - case 'setLevel': return arg[1] ? this.loggerService.setLevel(URI.revive(arg[1]), arg[0]) : this.logService.setLevel(arg[0]); + case 'setLevel': return arg[1] ? this.loggerService.setLogLevel(URI.revive(arg[1]), arg[0]) : this.logService.setLevel(arg[0]); } throw new Error(`Call not found: ${command}`); @@ -55,68 +55,19 @@ export class LogLevelChannelClient { } -export class LoggerChannel implements IServerChannel { - - private readonly loggers = new Map(); - - constructor(private readonly loggerService: ILoggerService) { } - - listen(_: unknown, event: string): Event { - switch (event) { - case 'onDidChangeLogLevel': return this.loggerService.onDidChangeLogLevel; - } - throw new Error(`Event not found: ${event}`); - } - - async call(_: unknown, command: string, arg?: any): Promise { - switch (command) { - case 'createLogger': this.createLogger(URI.revive(arg[0]), arg[1]); return; - case 'log': return this.log(URI.revive(arg[0]), arg[1]); - case 'consoleLog': return this.consoleLog(arg[0], arg[1]); - case 'setLogLevel': return this.loggerService.setLevel(URI.revive(arg[0]), arg[1]); - } - - throw new Error(`Call not found: ${command}`); - } - - private createLogger(file: URI, options: ILoggerOptions): void { - this.loggers.set(file.toString(), this.loggerService.createLogger(file, options)); - } - - private consoleLog(level: LogLevel, args: any[]): void { - let consoleFn = console.log; - - switch (level) { - case LogLevel.Error: - consoleFn = console.error; - break; - case LogLevel.Warning: - consoleFn = console.warn; - break; - case LogLevel.Info: - consoleFn = console.info; - break; - } - - consoleFn.call(console, ...args); - } - - private log(file: URI, messages: [LogLevel, string][]): void { - const logger = this.loggers.get(file.toString()); - if (!logger) { - throw new Error('Create the logger before logging'); - } - for (const [level, message] of messages) { - log(logger, level, message); - } - } -} - export class LoggerChannelClient extends AbstractLoggerService implements ILoggerService { - constructor(logLevel: LogLevel, onDidChangeLogLevel: Event, logLevels: [UriComponents, LogLevel][], private readonly channel: IChannel) { - super(logLevel, onDidChangeLogLevel, logLevels.map(([resource, logLevel]) => [URI.revive(resource), logLevel])); - this._register(channel.listen<[UriComponents, LogLevel]>('onDidChangeLogLevel')(([resource, logLevel]) => super.setLevel(URI.revive(resource), logLevel))); + constructor(private readonly windowId: number | undefined, logLevel: LogLevel, onDidChangeLogLevel: Event, loggers: UriDto[], private readonly channel: IChannel) { + super(logLevel, onDidChangeLogLevel, loggers.map(loggerResource => ({ ...loggerResource, resource: URI.revive(loggerResource.resource) }))); + this._register(channel.listen('onDidChangeLogLevel', windowId)((loggerResource) => super.setLogLevel(URI.revive(loggerResource.resource), loggerResource.logLevel ?? this.logLevel))); + this._register(channel.listen('onDidChangeLoggerResources', windowId)(({ added, removed }) => { + for (const loggerResource of added) { + super.registerLoggerResource({ ...loggerResource, resource: URI.revive(loggerResource.resource) }); + } + for (const loggerResource of removed) { + super.deregisterLoggerResource(loggerResource.resource); + } + })); } createConsoleMainLogger(): ILogger { @@ -127,8 +78,15 @@ export class LoggerChannelClient extends AbstractLoggerService implements ILogge }); } - override setLevel(resource: URI, logLevel: LogLevel): void { - super.setLevel(resource, logLevel); + override registerLoggerResource(resource: ILoggerResource): void { + this.channel.call('registerLoggerResource', [resource, this.windowId]); + } + + override deregisterLoggerResource(resource: URI): void { + this.channel.call('deregisterLoggerResource', [resource, this.windowId]); + } + + override setLogLevel(resource: URI, logLevel: LogLevel): void { this.channel.call('setLogLevel', [resource, logLevel]); } diff --git a/src/vs/platform/log/electron-main/logIpc.ts b/src/vs/platform/log/electron-main/logIpc.ts new file mode 100644 index 00000000000..63c71f296e3 --- /dev/null +++ b/src/vs/platform/log/electron-main/logIpc.ts @@ -0,0 +1,71 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Event } from 'vs/base/common/event'; +import { URI } from 'vs/base/common/uri'; +import { IServerChannel } from 'vs/base/parts/ipc/common/ipc'; +import { ILogger, ILoggerOptions, log, LogLevel } from 'vs/platform/log/common/log'; +import { ILoggerMainService } from 'vs/platform/log/electron-main/loggerService'; + +export class LoggerChannel implements IServerChannel { + + private readonly loggers = new Map(); + + constructor(private readonly loggerService: ILoggerMainService) { } + + listen(_: unknown, event: string, windowId?: number): Event { + switch (event) { + case 'onDidChangeLoggerResources': return windowId ? this.loggerService.getOnDidChangeLoggerResourcesEvent(windowId) : this.loggerService.onDidChangeLoggerResources; + case 'onDidChangeLogLevel': return windowId ? this.loggerService.getOnDidChangeLogLevelEvent(windowId) : this.loggerService.onDidChangeLogLevel; + } + throw new Error(`Event not found: ${event}`); + } + + async call(_: unknown, command: string, arg?: any): Promise { + switch (command) { + case 'createLogger': this.createLogger(URI.revive(arg[0]), arg[1]); return; + case 'log': return this.log(URI.revive(arg[0]), arg[1]); + case 'consoleLog': return this.consoleLog(arg[0], arg[1]); + case 'setLogLevel': return this.loggerService.setLogLevel(URI.revive(arg[0]), arg[1]); + case 'registerLoggerResource': return this.loggerService.registerLoggerResource({ ...arg[0], resource: URI.revive(arg[0].resource) }, arg[1]); + case 'deregisterLoggerResource': return this.loggerService.deregisterLoggerResource(URI.revive(arg[0])); + } + + throw new Error(`Call not found: ${command}`); + } + + private createLogger(file: URI, options: ILoggerOptions): void { + this.loggers.set(file.toString(), this.loggerService.createLogger(file, options)); + } + + private consoleLog(level: LogLevel, args: any[]): void { + let consoleFn = console.log; + + switch (level) { + case LogLevel.Error: + consoleFn = console.error; + break; + case LogLevel.Warning: + consoleFn = console.warn; + break; + case LogLevel.Info: + consoleFn = console.info; + break; + } + + consoleFn.call(console, ...args); + } + + private log(file: URI, messages: [LogLevel, string][]): void { + const logger = this.loggers.get(file.toString()); + if (!logger) { + throw new Error('Create the logger before logging'); + } + for (const [level, message] of messages) { + log(logger, level, message); + } + } +} + diff --git a/src/vs/platform/log/electron-main/loggerService.ts b/src/vs/platform/log/electron-main/loggerService.ts new file mode 100644 index 00000000000..025a1d3dc84 --- /dev/null +++ b/src/vs/platform/log/electron-main/loggerService.ts @@ -0,0 +1,88 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ResourceMap } from 'vs/base/common/map'; +import { URI } from 'vs/base/common/uri'; +import { Event } from 'vs/base/common/event'; +import { refineServiceDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { DidChangeLoggerResourceEvent, ILoggerResource, ILoggerService } from 'vs/platform/log/common/log'; +import { LoggerService } from 'vs/platform/log/node/loggerService'; + +export const ILoggerMainService = refineServiceDecorator(ILoggerService); + +export interface ILoggerMainService extends ILoggerService { + + getOnDidChangeLogLevelEvent(windowId: number): Event; + + getOnDidChangeLoggerResourcesEvent(windowId: number): Event; + + registerLoggerResource(resource: ILoggerResource, windowId?: number): void; + + getLoggerResources(windowId?: number): ILoggerResource[]; + + deregisterLoggerResources(windowId: number): void; + +} + +export class LoggerMainService extends LoggerService implements ILoggerMainService { + + private readonly loggerResourcesByWindow = new ResourceMap(); + + override registerLoggerResource(resource: ILoggerResource, windowId?: number): void { + if (windowId !== undefined) { + this.loggerResourcesByWindow.set(resource.resource, windowId); + } + super.registerLoggerResource(resource); + } + + override deregisterLoggerResource(resource: URI): void { + this.loggerResourcesByWindow.delete(resource); + super.deregisterLoggerResource(resource); + } + + override getLoggerResources(windowId?: number): ILoggerResource[] { + const resources: ILoggerResource[] = []; + for (const resource of super.getLoggerResources()) { + if (this.isInterestedLoggerResource(resource.resource, windowId)) { + resources.push(resource); + } + } + return resources; + } + + getOnDidChangeLogLevelEvent(windowId: number): Event { + return Event.filter(this.onDidChangeLogLevel, e => this.isInterestedLoggerResource(e.resource, windowId)); + } + + getOnDidChangeLoggerResourcesEvent(windowId: number): Event { + return Event.filter( + Event.map(this.onDidChangeLoggerResources, e => { + const r = { + added: [...e.added].filter(loggerResource => this.isInterestedLoggerResource(loggerResource.resource, windowId)), + removed: [...e.removed].filter(loggerResource => this.isInterestedLoggerResource(loggerResource.resource, windowId)), + }; + return r; + }), e => e.added.length > 0 || e.removed.length > 0); + } + + deregisterLoggerResources(windowId: number): void { + for (const [resource, resourceWindow] of this.loggerResourcesByWindow) { + if (resourceWindow === windowId) { + this.deregisterLoggerResource(resource); + } + } + } + + private isInterestedLoggerResource(resource: URI, windowId: number | undefined): boolean { + const loggerWindowId = this.loggerResourcesByWindow.get(resource); + return loggerWindowId === undefined || loggerWindowId === windowId; + } + + override dispose(): void { + super.dispose(); + this.loggerResourcesByWindow.clear(); + } +} + diff --git a/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts b/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts index d9c443c4b59..1ef264da23d 100644 --- a/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts +++ b/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts @@ -14,7 +14,7 @@ import { assertIsDefined } from 'vs/base/common/types'; import { connect as connectMessagePort } from 'vs/base/parts/ipc/electron-main/ipc.mp'; import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService'; import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; -import { ILogService, ILoggerService } from 'vs/platform/log/common/log'; +import { ILogService } from 'vs/platform/log/common/log'; import product from 'vs/platform/product/common/product'; import { IProtocolMainService } from 'vs/platform/protocol/electron-main/protocol'; import { ISharedProcess, ISharedProcessConfiguration } from 'vs/platform/sharedProcess/node/sharedProcess'; @@ -24,6 +24,7 @@ import { WindowError } from 'vs/platform/window/electron-main/window'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; import { IPolicyService } from 'vs/platform/policy/common/policy'; +import { ILoggerMainService } from 'vs/platform/log/electron-main/loggerService'; export class SharedProcess extends Disposable implements ISharedProcess { @@ -42,7 +43,7 @@ export class SharedProcess extends Disposable implements ISharedProcess { @IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService, @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService, @ILogService private readonly logService: ILogService, - @ILoggerService private readonly loggerService: ILoggerService, + @ILoggerMainService private readonly loggerMainService: ILoggerMainService, @IPolicyService private readonly policyService: IPolicyService, @IThemeMainService private readonly themeMainService: IThemeMainService, @IProtocolMainService private readonly protocolMainService: IProtocolMainService @@ -246,7 +247,7 @@ export class SharedProcess extends Disposable implements ISharedProcess { userEnv: this.userEnv, args: this.environmentMainService.args, logLevel: this.logService.getLevel(), - logLevels: [...this.loggerService.logLevels], + loggers: this.loggerMainService.getLoggerResources(), product, policiesData: this.policyService.serialize() }); diff --git a/src/vs/platform/sharedProcess/node/sharedProcess.ts b/src/vs/platform/sharedProcess/node/sharedProcess.ts index 2a2cdfa226f..3b568b3b57f 100644 --- a/src/vs/platform/sharedProcess/node/sharedProcess.ts +++ b/src/vs/platform/sharedProcess/node/sharedProcess.ts @@ -6,10 +6,10 @@ import { IStringDictionary } from 'vs/base/common/collections'; import { ISandboxConfiguration } from 'vs/base/parts/sandbox/common/sandboxTypes'; import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; -import { LogLevel } from 'vs/platform/log/common/log'; +import { ILoggerResource, LogLevel } from 'vs/platform/log/common/log'; import { IUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; import { PolicyDefinition, PolicyValue } from 'vs/platform/policy/common/policy'; -import { UriComponents, UriDto } from 'vs/base/common/uri'; +import { UriDto } from 'vs/base/common/uri'; export interface ISharedProcess { @@ -27,7 +27,7 @@ export interface ISharedProcessConfiguration extends ISandboxConfiguration { readonly logLevel: LogLevel; - readonly logLevels: [UriComponents, LogLevel][]; + readonly loggers: UriDto[]; readonly profiles: readonly UriDto[]; diff --git a/src/vs/platform/telemetry/test/common/telemetryLogAppender.test.ts b/src/vs/platform/telemetry/test/common/telemetryLogAppender.test.ts index ff1a521c1e7..f8b71d67558 100644 --- a/src/vs/platform/telemetry/test/common/telemetryLogAppender.test.ts +++ b/src/vs/platform/telemetry/test/common/telemetryLogAppender.test.ts @@ -72,10 +72,13 @@ export class TestTelemetryLoggerService implements ILoggerService { } onDidChangeLogLevel = Event.None; - logLevels = []; - setLevel(): void { } + onDidChangeLoggerResources = Event.None; + setLogLevel(): void { } getLogLevel() { return undefined; } getDefaultLogLevel() { return this.logLevel; } + registerLoggerResource() { } + deregisterLoggerResource(): void { } + getLoggerResources() { return []; } } suite('TelemetryLogAdapter', () => { diff --git a/src/vs/platform/terminal/node/ptyHostService.ts b/src/vs/platform/terminal/node/ptyHostService.ts index 00e92dab867..b94ba82403a 100644 --- a/src/vs/platform/terminal/node/ptyHostService.ts +++ b/src/vs/platform/terminal/node/ptyHostService.ts @@ -13,14 +13,18 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IEnvironmentService, INativeEnvironmentService } from 'vs/platform/environment/common/environment'; import { parsePtyHostPort } from 'vs/platform/environment/common/environmentService'; import { getResolvedShellEnv } from 'vs/platform/shell/node/shellEnv'; -import { ILogService } from 'vs/platform/log/common/log'; +import { ILogService, ILoggerService } from 'vs/platform/log/common/log'; import { LogLevelChannelClient } from 'vs/platform/log/common/logIpc'; import { RequestStore } from 'vs/platform/terminal/common/requestStore'; -import { HeartbeatConstants, IHeartbeatService, IProcessDataEvent, IPtyService, IReconnectConstants, IRequestResolveVariablesEvent, IShellLaunchConfig, ITerminalLaunchError, ITerminalProfile, ITerminalsLayoutInfo, TerminalIcon, TerminalIpcChannels, IProcessProperty, TitleEventSource, ProcessPropertyType, IProcessPropertyMap, TerminalSettingId, ISerializedTerminalState, ITerminalProcessOptions } from 'vs/platform/terminal/common/terminal'; +import { HeartbeatConstants, IHeartbeatService, IProcessDataEvent, IPtyService, IReconnectConstants, IRequestResolveVariablesEvent, IShellLaunchConfig, ITerminalLaunchError, ITerminalProfile, ITerminalsLayoutInfo, TerminalIcon, TerminalIpcChannels, IProcessProperty, TitleEventSource, ProcessPropertyType, IProcessPropertyMap, TerminalSettingId, ISerializedTerminalState, ITerminalProcessOptions, TerminalLogConstants } from 'vs/platform/terminal/common/terminal'; import { registerTerminalPlatformConfiguration } from 'vs/platform/terminal/common/terminalPlatformConfiguration'; import { IGetTerminalLayoutInfoArgs, IProcessDetails, ISetTerminalLayoutInfoArgs } from 'vs/platform/terminal/common/terminalProcess'; import { detectAvailableProfiles } from 'vs/platform/terminal/node/terminalProfiles'; import { IPtyHostProcessReplayEvent } from 'vs/platform/terminal/common/capabilities/capabilities'; +import { localize } from 'vs/nls'; +import { URI } from 'vs/base/common/uri'; +import { join } from 'vs/base/common/path'; +import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; enum Constants { MaxRestarts = 5 @@ -82,6 +86,8 @@ export class PtyHostService extends Disposable implements IPtyService { @IConfigurationService private readonly _configurationService: IConfigurationService, @IEnvironmentService private readonly _environmentService: INativeEnvironmentService, @ILogService private readonly _logService: ILogService, + @ILoggerService private readonly _loggerService: ILoggerService, + @IUriIdentityService private readonly _uriIdentityService: IUriIdentityService, ) { super(); @@ -180,9 +186,16 @@ export class PtyHostService extends Disposable implements IPtyService { // Setup logging const logChannel = client.getChannel(TerminalIpcChannels.Log); LogLevelChannelClient.setLevel(logChannel, this._logService.getLevel()); + const ptyHostLogResource = URI.file(join(this._environmentService.logsPath, `${TerminalLogConstants.FileName}.log`)); + this._loggerService.registerLoggerResource({ id: 'ptyHostLog', name: localize('ptyHost', "Pty Host"), resource: ptyHostLogResource }); this._register(this._logService.onDidChangeLogLevel(() => { LogLevelChannelClient.setLevel(logChannel, this._logService.getLevel()); })); + this._register(this._loggerService.onDidChangeLogLevel(({ resource, logLevel }) => { + if (this._uriIdentityService.extUri.isEqual(ptyHostLogResource, resource)) { + LogLevelChannelClient.setLevel(logChannel, logLevel ?? this._logService.getLevel()); + } + })); // Create proxy and forward events const proxy = ProxyChannel.toService(client.getChannel(TerminalIpcChannels.PtyHost)); diff --git a/src/vs/platform/window/common/window.ts b/src/vs/platform/window/common/window.ts index b3de97481a3..c1f8d509a96 100644 --- a/src/vs/platform/window/common/window.ts +++ b/src/vs/platform/window/common/window.ts @@ -12,7 +12,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IEditorOptions } from 'vs/platform/editor/common/editor'; import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; import { FileType } from 'vs/platform/files/common/files'; -import { LogLevel } from 'vs/platform/log/common/log'; +import { ILoggerResource, LogLevel } from 'vs/platform/log/common/log'; import { PolicyDefinition, PolicyValue } from 'vs/platform/policy/common/policy'; import { IPartsSplash } from 'vs/platform/theme/common/themeService'; import { IUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; @@ -299,7 +299,7 @@ export interface INativeWindowConfiguration extends IWindowConfiguration, Native isInitialStartup?: boolean; logLevel: LogLevel; - logLevels: [UriComponents, LogLevel][]; + loggers: UriDto[]; fullscreen?: boolean; maximized?: boolean; diff --git a/src/vs/platform/windows/electron-main/windowImpl.ts b/src/vs/platform/windows/electron-main/windowImpl.ts index a50d52b2309..068edd3757d 100644 --- a/src/vs/platform/windows/electron-main/windowImpl.ts +++ b/src/vs/platform/windows/electron-main/windowImpl.ts @@ -25,7 +25,7 @@ import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/e import { isLaunchedFromCli } from 'vs/platform/environment/node/argvHelper'; import { IFileService } from 'vs/platform/files/common/files'; import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; -import { ILogService, ILoggerService } from 'vs/platform/log/common/log'; +import { ILogService } from 'vs/platform/log/common/log'; import { IProductService } from 'vs/platform/product/common/productService'; import { IProtocolMainService } from 'vs/platform/protocol/electron-main/protocol'; import { resolveMarketplaceHeaders } from 'vs/platform/externalServices/common/marketplace'; @@ -50,6 +50,7 @@ import { getPiiPathsFromEnvironment, isInternalTelemetry, ITelemetryAppender, su import { resolveCommonProperties } from 'vs/platform/telemetry/common/commonProperties'; import { hostname, release } from 'os'; import { resolveMachineId } from 'vs/platform/telemetry/electron-main/telemetryUtils'; +import { ILoggerMainService } from 'vs/platform/log/electron-main/loggerService'; export interface IWindowCreationOptions { readonly state: IWindowState; @@ -183,7 +184,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { constructor( config: IWindowCreationOptions, @ILogService private readonly logService: ILogService, - @ILoggerService private readonly loggerService: ILoggerService, + @ILoggerMainService private readonly loggerMainService: ILoggerMainService, @IEnvironmentMainService private readonly environmentMainService: IEnvironmentMainService, @IPolicyService private readonly policyService: IPolicyService, @IUserDataProfilesMainService private readonly userDataProfilesService: IUserDataProfilesMainService, @@ -1086,7 +1087,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { profile: this.profile || this.userDataProfilesService.defaultProfile }; configuration.logLevel = this.logService.getLevel(); - configuration.logLevels = [...this.loggerService.logLevels]; + configuration.loggers = this.loggerMainService.getLoggerResources(this.id); // Load config this.load(configuration, { isReload: true, disableExtensions: cli?.['disable-extensions'] }); diff --git a/src/vs/platform/windows/electron-main/windowsMainService.ts b/src/vs/platform/windows/electron-main/windowsMainService.ts index 3c7fda4f58d..c52f9e01f63 100644 --- a/src/vs/platform/windows/electron-main/windowsMainService.ts +++ b/src/vs/platform/windows/electron-main/windowsMainService.ts @@ -32,7 +32,7 @@ import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/e import { FileType, IFileService } from 'vs/platform/files/common/files'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; -import { ILogService, ILoggerService } from 'vs/platform/log/common/log'; +import { ILogService } from 'vs/platform/log/common/log'; import product from 'vs/platform/product/common/product'; import { IProductService } from 'vs/platform/product/common/productService'; import { IProtocolMainService } from 'vs/platform/protocol/electron-main/protocol'; @@ -54,6 +54,7 @@ import { IEditorOptions, ITextEditorOptions } from 'vs/platform/editor/common/ed import { IUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; import { IPolicyService } from 'vs/platform/policy/common/policy'; import { IUserDataProfilesMainService } from 'vs/platform/userDataProfile/electron-main/userDataProfile'; +import { ILoggerMainService } from 'vs/platform/log/electron-main/loggerService'; //#region Helper Interfaces @@ -195,13 +196,13 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic private readonly _onDidTriggerSystemContextMenu = this._register(new Emitter<{ window: ICodeWindow; x: number; y: number }>()); readonly onDidTriggerSystemContextMenu = this._onDidTriggerSystemContextMenu.event; - private readonly windowsStateHandler = this._register(new WindowsStateHandler(this, this.stateMainService, this.lifecycleMainService, this.logService, this.configurationService)); + private readonly windowsStateHandler = this._register(new WindowsStateHandler(this, this.stateMainService, this.lifecycleMainService, this.logService, this.loggerService, this.configurationService)); constructor( private readonly machineId: string, private readonly initialUserEnv: IProcessEnvironment, @ILogService private readonly logService: ILogService, - @ILoggerService private readonly loggerService: ILoggerService, + @ILoggerMainService private readonly loggerService: ILoggerMainService, @IStateMainService private readonly stateMainService: IStateMainService, @IPolicyService private readonly policyService: IPolicyService, @IEnvironmentMainService private readonly environmentMainService: IEnvironmentMainService, @@ -1371,7 +1372,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic filesToWait: options.filesToOpen?.filesToWait, logLevel: this.logService.getLevel(), - logLevels: [...this.loggerService.logLevels], + loggers: this.loggerService.getLoggerResources(), logsPath: this.environmentMainService.logsPath, product, @@ -1448,6 +1449,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic configuration['extensions-dir'] = currentWindowConfig['extensions-dir']; configuration['disable-extensions'] = currentWindowConfig['disable-extensions']; } + configuration.loggers = currentWindowConfig?.loggers ?? configuration.loggers; } // Update window identifier and session now diff --git a/src/vs/platform/windows/electron-main/windowsStateHandler.ts b/src/vs/platform/windows/electron-main/windowsStateHandler.ts index a4de6b2bb81..2574944ccf0 100644 --- a/src/vs/platform/windows/electron-main/windowsStateHandler.ts +++ b/src/vs/platform/windows/electron-main/windowsStateHandler.ts @@ -16,6 +16,7 @@ import { INativeWindowConfiguration, IWindowSettings } from 'vs/platform/window/ import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows'; import { defaultWindowState, ICodeWindow, IWindowState as IWindowUIState, WindowMode } from 'vs/platform/window/electron-main/window'; import { isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace'; +import { ILoggerMainService } from 'vs/platform/log/electron-main/loggerService'; export interface IWindowState { readonly windowId?: number; @@ -66,6 +67,7 @@ export class WindowsStateHandler extends Disposable { @IStateMainService private readonly stateMainService: IStateMainService, @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService, @ILogService private readonly logService: ILogService, + @ILoggerMainService private readonly loggerMainService: ILoggerMainService, @IConfigurationService private readonly configurationService: IConfigurationService ) { super(); @@ -227,6 +229,8 @@ export class WindowsStateHandler extends Disposable { return; // during quit, many windows close in parallel so let it be handled in the before-quit handler } + this.loggerMainService.deregisterLoggerResources(window.id); + // On Window close, update our stored UI state of this window const state: IWindowState = this.toWindowState(window); if (window.isExtensionDevelopmentHost && !window.isExtensionTestHost) { diff --git a/src/vs/workbench/api/browser/mainThreadLogService.ts b/src/vs/workbench/api/browser/mainThreadLogService.ts index 799f032bd18..f0bb1e6c3b6 100644 --- a/src/vs/workbench/api/browser/mainThreadLogService.ts +++ b/src/vs/workbench/api/browser/mainThreadLogService.ts @@ -26,7 +26,7 @@ export class MainThreadLoggerService implements MainThreadLoggerShape { ) { const proxy = extHostContext.getProxy(ExtHostContext.ExtHostLogLevelServiceShape); this.disposables.add(logService.onDidChangeLogLevel(level => proxy.$setLevel(level))); - this.disposables.add(loggerService.onDidChangeLogLevel(([resource, logLevel]) => proxy.$setLevel(logLevel, resource))); + this.disposables.add(loggerService.onDidChangeLogLevel(loggerResource => proxy.$setLevel(loggerResource.logLevel ?? logService.getLevel(), loggerResource.resource))); } $log(file: UriComponents, messages: [LogLevel, string][]): void { diff --git a/src/vs/workbench/api/browser/mainThreadOutputService.ts b/src/vs/workbench/api/browser/mainThreadOutputService.ts index 2767324ed23..16410364083 100644 --- a/src/vs/workbench/api/browser/mainThreadOutputService.ts +++ b/src/vs/workbench/api/browser/mainThreadOutputService.ts @@ -12,6 +12,7 @@ import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { Event } from 'vs/base/common/event'; import { IViewsService } from 'vs/workbench/common/views'; import { isNumber } from 'vs/base/common/types'; +import { ILoggerService } from 'vs/platform/log/common/log'; @extHostNamedCustomer(MainContext.MainThreadOutputService) export class MainThreadOutputService extends Disposable implements MainThreadOutputServiceShape { @@ -21,15 +22,18 @@ export class MainThreadOutputService extends Disposable implements MainThreadOut private readonly _proxy: ExtHostOutputServiceShape; private readonly _outputService: IOutputService; private readonly _viewsService: IViewsService; + private readonly _loggerService: ILoggerService; constructor( extHostContext: IExtHostContext, @IOutputService outputService: IOutputService, - @IViewsService viewsService: IViewsService + @IViewsService viewsService: IViewsService, + @ILoggerService loggerService: ILoggerService, ) { super(); this._outputService = outputService; this._viewsService = viewsService; + this._loggerService = loggerService; this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostOutputService); @@ -45,8 +49,12 @@ export class MainThreadOutputService extends Disposable implements MainThreadOut const idCounter = (MainThreadOutputService._extensionIdPool.get(extensionId) || 0) + 1; MainThreadOutputService._extensionIdPool.set(extensionId, idCounter); const id = `extension-output-${extensionId}-#${idCounter}-${label}`; + const resource = URI.revive(file); - Registry.as(Extensions.OutputChannels).registerChannel({ id, label, file: URI.revive(file), log, languageId, extensionId }); + Registry.as(Extensions.OutputChannels).registerChannel({ id, label, file: resource, log, languageId, extensionId }); + if (log) { + this._loggerService.registerLoggerResource({ resource, id, name: label }); + } this._register(toDisposable(() => this.$dispose(id))); return id; } diff --git a/src/vs/workbench/api/common/extHostLoggerService.ts b/src/vs/workbench/api/common/extHostLoggerService.ts index c3c22da85a7..92b37abfc39 100644 --- a/src/vs/workbench/api/common/extHostLoggerService.ts +++ b/src/vs/workbench/api/common/extHostLoggerService.ts @@ -10,6 +10,7 @@ import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { URI, UriComponents } from 'vs/base/common/uri'; import { Event } from 'vs/base/common/event'; import { isUndefined } from 'vs/base/common/types'; +import { revive } from 'vs/base/common/marshalling'; export class ExtHostLoggerService extends AbstractLoggerService implements ExtHostLogLevelServiceShape { @@ -20,13 +21,13 @@ export class ExtHostLoggerService extends AbstractLoggerService implements ExtHo @IExtHostRpcService rpc: IExtHostRpcService, @IExtHostInitDataService initData: IExtHostInitDataService, ) { - super(initData.logLevel, Event.None, initData.logLevels.map(([resource, logLevel]) => ([URI.revive(resource), logLevel]))); + super(initData.logLevel, Event.None, initData.loggers.map(logger => revive(logger))); this._proxy = rpc.getProxy(MainContext.MainThreadLogger); } $setLevel(level: LogLevel, resource?: UriComponents): void { if (resource) { - this.setLevel(URI.revive(resource), level); + this.setLogLevel(URI.revive(resource), level); } else if (!isUndefined(level)) { this.setGlobalLogLevel(level); } diff --git a/src/vs/workbench/contrib/logs/common/logsActions.ts b/src/vs/workbench/contrib/logs/common/logsActions.ts index c031ab83de8..c8ee05f4fef 100644 --- a/src/vs/workbench/contrib/logs/common/logsActions.ts +++ b/src/vs/workbench/contrib/logs/common/logsActions.ts @@ -88,7 +88,7 @@ export class SetLogLevelAction extends Action { const entry = await this.quickInputService.pick(entries, { placeHolder: logChannel ? nls.localize('selectLogLevelFor', " {0}: Select log level", logChannel?.label) : nls.localize('selectLogLevel', "Select log level"), activeItem: entries[this.logService.getLevel()] }); if (entry) { - this.loggerService.setLevel(logChannel.resource, entry.level); + this.loggerService.setLogLevel(logChannel.resource, entry.level); } } diff --git a/src/vs/workbench/contrib/remote/common/remote.contribution.ts b/src/vs/workbench/contrib/remote/common/remote.contribution.ts index efd8fc9926f..2177865be86 100644 --- a/src/vs/workbench/contrib/remote/common/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/common/remote.contribution.ts @@ -99,6 +99,7 @@ class RemoteLogOutputChannels extends Disposable implements IWorkbenchContributi constructor( @IRemoteAgentService remoteAgentService: IRemoteAgentService, @ILoggerService loggerService: ILoggerService, + @ILogService logService: ILogService, @IUriIdentityService uriIdentityService: IUriIdentityService, ) { super(); @@ -109,11 +110,15 @@ class RemoteLogOutputChannels extends Disposable implements IWorkbenchContributi const outputChannelRegistry = Registry.as(OutputExt.OutputChannels); const remoteExtensionHostLogFile = joinPath(remoteEnv.logsPath, `${RemoteExtensionLogFileName}.log`); const remotePtyLogFile = joinPath(remoteEnv.logsPath, `${TerminalLogConstants.FileName}.log`); - outputChannelRegistry.registerChannel({ id: remoteServerLog, label: localize('remoteExtensionLog', "Remote Server"), file: remoteExtensionHostLogFile, log: true }); - outputChannelRegistry.registerChannel({ id: remotePtyHostLog, label: localize('remotePtyHostLog', "Remote Pty Host"), file: remotePtyLogFile, log: true }); - this._register(loggerService.onDidChangeLogLevel(([resource, logLevel]) => { + const remoteServerLoggerResource = { id: remoteServerLog, name: localize('remoteExtensionLog', "Remote Server"), resource: remoteExtensionHostLogFile }; + loggerService.registerLoggerResource(remoteServerLoggerResource); + outputChannelRegistry.registerChannel({ id: remoteServerLoggerResource.id, label: remoteServerLoggerResource.name, file: remoteServerLoggerResource.resource, log: true }); + const remotePtyHostLoggerResource = { id: remotePtyHostLog, name: localize('remotePtyHostLog', "Remote Pty Host"), resource: remotePtyLogFile }; + loggerService.registerLoggerResource(remotePtyHostLoggerResource); + outputChannelRegistry.registerChannel({ id: remotePtyHostLoggerResource.id, label: remotePtyHostLoggerResource.name, file: remotePtyHostLoggerResource.resource, log: true }); + this._register(loggerService.onDidChangeLogLevel(({ resource, logLevel }) => { if (uriIdentityService.extUri.isEqual(resource, remoteExtensionHostLogFile) || uriIdentityService.extUri.isEqual(resource, remotePtyLogFile)) { - connection.withChannel('logger', (channel) => LogLevelChannelClient.setLevel(channel, logLevel, resource)); + connection.withChannel('logger', (channel) => LogLevelChannelClient.setLevel(channel, logLevel ?? logService.getLevel(), resource)); } })); } diff --git a/src/vs/workbench/electron-sandbox/desktop.main.ts b/src/vs/workbench/electron-sandbox/desktop.main.ts index 2f2cbba10fb..26150420673 100644 --- a/src/vs/workbench/electron-sandbox/desktop.main.ts +++ b/src/vs/workbench/electron-sandbox/desktop.main.ts @@ -171,7 +171,7 @@ export class DesktopMain extends Disposable { // Logger const logLevelChannelClient = new LogLevelChannelClient(mainProcessService.getChannel('logLevel')); - const loggerService = new LoggerChannelClient(this.configuration.logLevel, logLevelChannelClient.onDidChangeLogLevel, this.configuration.logLevels, mainProcessService.getChannel('logger')); + const loggerService = new LoggerChannelClient(this.configuration.windowId, this.configuration.logLevel, logLevelChannelClient.onDidChangeLogLevel, this.configuration.loggers, mainProcessService.getChannel('logger')); serviceCollection.set(ILoggerService, loggerService); // Log diff --git a/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts b/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts index df7f755aef8..4313f01ff86 100644 --- a/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts +++ b/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts @@ -252,7 +252,9 @@ export class WebWorkerExtensionHost extends Disposable implements IExtensionHost } // Register log channel for web worker exthost log - Registry.as(Extensions.OutputChannels).registerChannel({ id: webWorkerExtHostLog, label: localize('name', "Worker Extension Host"), file: this._extensionHostLogFile, log: true }); + const webWorkerExtHostLoggerResource = { id: webWorkerExtHostLog, name: localize('name', "Worker Extension Host"), resource: this._extensionHostLogFile }; + this._loggerService.registerLoggerResource(webWorkerExtHostLoggerResource); + Registry.as(Extensions.OutputChannels).registerChannel({ id: webWorkerExtHostLoggerResource.id, label: webWorkerExtHostLoggerResource.name, file: webWorkerExtHostLoggerResource.resource, log: true }); return protocol; } @@ -316,7 +318,7 @@ export class WebWorkerExtensionHost extends Disposable implements IExtensionHost nlsBaseUrl: nlsUrlWithDetails, telemetryInfo, logLevel: this._logService.getLevel(), - logLevels: [...this._loggerService.logLevels], + loggers: [...this._loggerService.getLoggerResources()], logsLocation: this._extensionHostLogsLocation, logFile: this._extensionHostLogFile, autoStart: initData.autoStart, diff --git a/src/vs/workbench/services/extensions/common/extensionHostProtocol.ts b/src/vs/workbench/services/extensions/common/extensionHostProtocol.ts index 255716357b4..cff156573aa 100644 --- a/src/vs/workbench/services/extensions/common/extensionHostProtocol.ts +++ b/src/vs/workbench/services/extensions/common/extensionHostProtocol.ts @@ -4,9 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import { VSBuffer } from 'vs/base/common/buffer'; -import { URI, UriComponents } from 'vs/base/common/uri'; +import { URI, UriComponents, UriDto } from 'vs/base/common/uri'; import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; -import { LogLevel } from 'vs/platform/log/common/log'; +import { ILoggerResource, LogLevel } from 'vs/platform/log/common/log'; import { IRemoteConnectionData } from 'vs/platform/remote/common/remoteAuthorityResolver'; import { ITelemetryInfo } from 'vs/platform/telemetry/common/telemetry'; @@ -31,7 +31,7 @@ export interface IExtensionHostInitData { nlsBaseUrl?: URI; telemetryInfo: ITelemetryInfo; logLevel: LogLevel; - logLevels: [UriComponents, LogLevel][]; + loggers: UriDto[]; logsLocation: URI; logFile: URI; autoStart: boolean; diff --git a/src/vs/workbench/services/extensions/common/remoteExtensionHost.ts b/src/vs/workbench/services/extensions/common/remoteExtensionHost.ts index 24a2cd3e111..92a3001eda1 100644 --- a/src/vs/workbench/services/extensions/common/remoteExtensionHost.ts +++ b/src/vs/workbench/services/extensions/common/remoteExtensionHost.ts @@ -172,7 +172,9 @@ export class RemoteExtensionHost extends Disposable implements IExtensionHost { disposable.dispose(); // Register log channel for remote exthost log - Registry.as(Extensions.OutputChannels).registerChannel({ id: remoteExtHostLog, label: localize('remote extension host Log', "Remote Extension Host"), file: logFile, log: true }); + const remoteExtHostLoggerResource = { id: remoteExtHostLog, name: localize('remote extension host Log', "Remote Extension Host"), resource: logFile }; + this._loggerService.registerLoggerResource(remoteExtHostLoggerResource); + Registry.as(Extensions.OutputChannels).registerChannel({ id: remoteExtHostLoggerResource.id, label: remoteExtHostLoggerResource.name, file: remoteExtHostLoggerResource.resource, log: true }); // release this promise this._protocol = protocol; @@ -249,7 +251,7 @@ export class RemoteExtensionHost extends Disposable implements IExtensionHost { myExtensions: deltaExtensions.myToAdd, telemetryInfo, logLevel: this._logService.getLevel(), - logLevels: [...this._loggerService.logLevels], + loggers: [...this._loggerService.getLoggerResources()], logsLocation: remoteInitData.extensionHostLogsPath, logFile: joinPath(remoteInitData.extensionHostLogsPath, `${ExtensionHostLogFileName}.log`), autoStart: true, diff --git a/src/vs/workbench/services/extensions/electron-sandbox/localProcessExtensionHost.ts b/src/vs/workbench/services/extensions/electron-sandbox/localProcessExtensionHost.ts index bdbc71dca01..666bbf12f27 100644 --- a/src/vs/workbench/services/extensions/electron-sandbox/localProcessExtensionHost.ts +++ b/src/vs/workbench/services/extensions/electron-sandbox/localProcessExtensionHost.ts @@ -418,7 +418,9 @@ export class NativeLocalProcessExtensionHost implements IExtensionHost { disposable.dispose(); // Register log channel for exthost log - Registry.as(Extensions.OutputChannels).registerChannel({ id: localExtHostLog, label: nls.localize('extension host Log', "Extension Host"), file: this._extensionHostLogFile, log: true }); + const localExtHostLoggerResource = { id: localExtHostLog, name: nls.localize('extension host Log', "Extension Host"), resource: this._extensionHostLogFile }; + this._loggerService.registerLoggerResource(localExtHostLoggerResource); + Registry.as(Extensions.OutputChannels).registerChannel({ id: localExtHostLoggerResource.id, label: localExtHostLoggerResource.name, file: localExtHostLoggerResource.resource, log: true }); // release this promise resolve(); @@ -473,7 +475,7 @@ export class NativeLocalProcessExtensionHost implements IExtensionHost { myExtensions: deltaExtensions.myToAdd, telemetryInfo, logLevel: this._logService.getLevel(), - logLevels: [...this._loggerService.logLevels], + loggers: [...this._loggerService.getLoggerResources()], logsLocation: this._environmentService.extHostLogsPath, logFile: this._extensionHostLogFile, autoStart: initData.autoStart, diff --git a/src/vs/workbench/test/electron-browser/workbenchTestServices.ts b/src/vs/workbench/test/electron-browser/workbenchTestServices.ts index 1d2478a19c3..523ece59dc4 100644 --- a/src/vs/workbench/test/electron-browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/electron-browser/workbenchTestServices.ts @@ -75,7 +75,7 @@ export const TestNativeWindowConfiguration: INativeWindowConfiguration = { windowId: 0, machineId: 'testMachineId', logLevel: LogLevel.Error, - logLevels: [], + loggers: [], mainPid: 0, appRoot: '', userEnv: {}, From d8c15d8e077302ba4bea2dd1b7e24c7dc0131be1 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 12 Jan 2023 09:10:56 +0100 Subject: [PATCH 3/4] adopt to feedback --- src/vs/platform/windows/electron-main/windowImpl.ts | 3 +++ src/vs/platform/windows/electron-main/windowsMainService.ts | 2 +- src/vs/platform/windows/electron-main/windowsStateHandler.ts | 4 ---- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/vs/platform/windows/electron-main/windowImpl.ts b/src/vs/platform/windows/electron-main/windowImpl.ts index 068edd3757d..200b2ec2f91 100644 --- a/src/vs/platform/windows/electron-main/windowImpl.ts +++ b/src/vs/platform/windows/electron-main/windowImpl.ts @@ -1660,6 +1660,9 @@ export class CodeWindow extends Disposable implements ICodeWindow { override dispose(): void { super.dispose(); + // Deregister the loggers for this window + this.loggerMainService.deregisterLoggerResources(this.id); + this._win = null!; // Important to dereference the window object to allow for GC } } diff --git a/src/vs/platform/windows/electron-main/windowsMainService.ts b/src/vs/platform/windows/electron-main/windowsMainService.ts index c52f9e01f63..818db32eb9c 100644 --- a/src/vs/platform/windows/electron-main/windowsMainService.ts +++ b/src/vs/platform/windows/electron-main/windowsMainService.ts @@ -196,7 +196,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic private readonly _onDidTriggerSystemContextMenu = this._register(new Emitter<{ window: ICodeWindow; x: number; y: number }>()); readonly onDidTriggerSystemContextMenu = this._onDidTriggerSystemContextMenu.event; - private readonly windowsStateHandler = this._register(new WindowsStateHandler(this, this.stateMainService, this.lifecycleMainService, this.logService, this.loggerService, this.configurationService)); + private readonly windowsStateHandler = this._register(new WindowsStateHandler(this, this.stateMainService, this.lifecycleMainService, this.logService, this.configurationService)); constructor( private readonly machineId: string, diff --git a/src/vs/platform/windows/electron-main/windowsStateHandler.ts b/src/vs/platform/windows/electron-main/windowsStateHandler.ts index 2574944ccf0..a4de6b2bb81 100644 --- a/src/vs/platform/windows/electron-main/windowsStateHandler.ts +++ b/src/vs/platform/windows/electron-main/windowsStateHandler.ts @@ -16,7 +16,6 @@ import { INativeWindowConfiguration, IWindowSettings } from 'vs/platform/window/ import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows'; import { defaultWindowState, ICodeWindow, IWindowState as IWindowUIState, WindowMode } from 'vs/platform/window/electron-main/window'; import { isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace'; -import { ILoggerMainService } from 'vs/platform/log/electron-main/loggerService'; export interface IWindowState { readonly windowId?: number; @@ -67,7 +66,6 @@ export class WindowsStateHandler extends Disposable { @IStateMainService private readonly stateMainService: IStateMainService, @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService, @ILogService private readonly logService: ILogService, - @ILoggerMainService private readonly loggerMainService: ILoggerMainService, @IConfigurationService private readonly configurationService: IConfigurationService ) { super(); @@ -229,8 +227,6 @@ export class WindowsStateHandler extends Disposable { return; // during quit, many windows close in parallel so let it be handled in the before-quit handler } - this.loggerMainService.deregisterLoggerResources(window.id); - // On Window close, update our stored UI state of this window const state: IWindowState = this.toWindowState(window); if (window.isExtensionDevelopmentHost && !window.isExtensionTestHost) { From 73dbd244c23534d4e9c81fda33d6ae3979c14cf0 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 12 Jan 2023 11:26:46 +0100 Subject: [PATCH 4/4] set logger service in server --- src/vs/server/node/serverServices.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vs/server/node/serverServices.ts b/src/vs/server/node/serverServices.ts index 13ca9aa96d8..2b35b1ea28f 100644 --- a/src/vs/server/node/serverServices.ts +++ b/src/vs/server/node/serverServices.ts @@ -38,7 +38,7 @@ import { InstantiationService } from 'vs/platform/instantiation/common/instantia import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { ILanguagePackService } from 'vs/platform/languagePacks/common/languagePacks'; import { NativeLanguagePackService } from 'vs/platform/languagePacks/node/languagePacks'; -import { AbstractLogger, DEFAULT_LOG_LEVEL, getLogLevel, ILogService, LogLevel, MultiplexLogService } from 'vs/platform/log/common/log'; +import { AbstractLogger, DEFAULT_LOG_LEVEL, getLogLevel, ILoggerService, ILogService, LogLevel, MultiplexLogService } from 'vs/platform/log/common/log'; import { LogLevelChannel } from 'vs/platform/log/common/logIpc'; import product from 'vs/platform/product/common/product'; import { IProductService } from 'vs/platform/product/common/productService'; @@ -98,6 +98,8 @@ export async function setupServerServices(connectionToken: ServerConnectionToken setTimeout(() => cleanupOlderLogs(environmentService.logsPath).then(null, err => logService.error(err)), 10000); const loggerService = new LoggerService(logService); + services.set(ILoggerService, loggerService); + bufferLogService.logger = loggerService.createLogger(URI.file(path.join(environmentService.logsPath, `${RemoteExtensionLogFileName}.log`)), { name: RemoteExtensionLogFileName }); logService.trace(`Remote configuration data at ${REMOTE_DATA_FOLDER}`);